feat(outlet): add outlet module with CRUD and related resources
This commit is contained in:
parent
ec941e9390
commit
dcc6a70470
47
app/Enums/OutletStatus.php
Normal file
47
app/Enums/OutletStatus.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum OutletStatus: int
|
||||||
|
{
|
||||||
|
case OPERATIONAL = 1;
|
||||||
|
case UNDER_CONSTRUCTION = 2;
|
||||||
|
case TERMINATED = 3;
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::OPERATIONAL => 'Operasional',
|
||||||
|
self::UNDER_CONSTRUCTION => 'Dalam Konstruksi',
|
||||||
|
self::TERMINATED => 'Dihentikan',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::OPERATIONAL => 'emerald',
|
||||||
|
self::UNDER_CONSTRUCTION => 'yellow',
|
||||||
|
self::TERMINATED => 'red',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function icon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::OPERATIONAL => 'check-circle',
|
||||||
|
self::UNDER_CONSTRUCTION => 'wrench-screwdriver',
|
||||||
|
self::TERMINATED => 'x-circle',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function values(): array
|
||||||
|
{
|
||||||
|
return array_map(fn($case) => $case->value, self::cases());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function comment(): string
|
||||||
|
{
|
||||||
|
return implode(', ', array_map(fn($case) => "{$case->value}: {$case->label()}", self::cases()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,3 +29,16 @@ function timeAgo(?string $date = null): string
|
|||||||
return ! $date ? '-' : Carbon::parse($date)->diffForHumans();
|
return ! $date ? '-' : Carbon::parse($date)->diffForHumans();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('normalizeOpeningHours')) {
|
||||||
|
function normalizeOpeningHours(array $openingHours)
|
||||||
|
{
|
||||||
|
$days = ['senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu', 'minggu'];
|
||||||
|
|
||||||
|
return collect($days)
|
||||||
|
->mapWithKeys(fn($day) => [
|
||||||
|
$day => $openingHours[$day] ?? ['open' => null, 'close' => null],
|
||||||
|
])
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
120
app/Livewire/Datatable/OutletsTable.php
Normal file
120
app/Livewire/Datatable/OutletsTable.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Datatable;
|
||||||
|
|
||||||
|
use App\Enums\OutletStatus;
|
||||||
|
use App\Models\Outlet;
|
||||||
|
use App\Traits\Datatable\WithAppendColumn;
|
||||||
|
use App\Traits\Datatable\WithConfiguration;
|
||||||
|
use App\Traits\Datatable\WithPrependColumn;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
use Rappasoft\LaravelLivewireTables\DataTableComponent;
|
||||||
|
use Rappasoft\LaravelLivewireTables\Views\Column;
|
||||||
|
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
|
||||||
|
|
||||||
|
class OutletsTable extends DataTableComponent
|
||||||
|
{
|
||||||
|
use WithAppendColumn, WithConfiguration, WithPrependColumn;
|
||||||
|
|
||||||
|
protected $model = Outlet::class;
|
||||||
|
|
||||||
|
public function columns(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Column::make('Name', 'name')->searchable(),
|
||||||
|
|
||||||
|
Column::make('Nomor Telepon', 'phone_number')->searchable(),
|
||||||
|
|
||||||
|
Column::make('Alamat')
|
||||||
|
->label(function ($row) {
|
||||||
|
return <<<HTML
|
||||||
|
<div>
|
||||||
|
<div>{$row->landmark}</div>
|
||||||
|
<div>{$row->address}</div>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
})
|
||||||
|
->searchable(function ($query, $searchTerm) {
|
||||||
|
$query->orWhere('landmark', 'like', "%{$searchTerm}%")
|
||||||
|
->orWhere('address', 'like', "%{$searchTerm}%");
|
||||||
|
})
|
||||||
|
->html(),
|
||||||
|
|
||||||
|
ArrayColumn::make('Jam Buka', 'opening_hours')
|
||||||
|
->data(fn($value, $row) => normalizeOpeningHours($row->opening_hours))
|
||||||
|
->outputFormat(
|
||||||
|
fn($index, $value) => "<span class='flex items-center gap-2 py-1 text-xs'>
|
||||||
|
<span class='font-semibold'>" . ucfirst($index) . ": </span>
|
||||||
|
<span>"
|
||||||
|
. (($value['open'] === null && $value['close'] === null)
|
||||||
|
? 'Tutup'
|
||||||
|
: "{$value['open']} - {$value['close']}")
|
||||||
|
. "</span>
|
||||||
|
</span>"
|
||||||
|
)
|
||||||
|
->flexCol(['class' => 'gap-2 flex-wrap']),
|
||||||
|
|
||||||
|
ArrayColumn::make('Fasilitas', 'facilities')
|
||||||
|
->data(fn($value, $row) => $row->facilities)
|
||||||
|
->outputFormat(fn($index, $value) => Blade::render('<flux:badge color="zinc" dot>' . $value . '</flux:badge>'))
|
||||||
|
->flexRow(['class' => 'gap-2 flex-wrap']),
|
||||||
|
|
||||||
|
Column::make('Tanggal Operasi')
|
||||||
|
->label(function ($row) {
|
||||||
|
$openedDate = formatDate($row->opened_date);
|
||||||
|
$openedAgo = timeAgo($row->opened_date);
|
||||||
|
|
||||||
|
$closedDate = formatDate($row->closed_date);
|
||||||
|
$closedAgo = timeAgo($row->closed_date);
|
||||||
|
|
||||||
|
return <<<HTML
|
||||||
|
<div class="flex flex-col text-xs text-gray-700 dark:text-gray-300 gap-0.5">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-gray-900 dark:text-white">Tgl Buka:</span>
|
||||||
|
<span>{$openedDate}</span>
|
||||||
|
<span class="text-gray-500">({$openedAgo})</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-gray-900 dark:text-white">Tgl Tutup:</span>
|
||||||
|
<span>{$closedDate}</span>
|
||||||
|
<span class="text-gray-500">({$closedAgo})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
})
|
||||||
|
->searchable(function ($query, $searchTerm) {
|
||||||
|
$query->orWhere('landmark', 'like', "%{$searchTerm}%")
|
||||||
|
->orWhere('address', 'like', "%{$searchTerm}%");
|
||||||
|
})
|
||||||
|
->html(),
|
||||||
|
|
||||||
|
Column::make('Status', 'status')
|
||||||
|
->format(fn($value) => Blade::render('<flux:badge color="' . $value->color() . '" dot>' . $value->label() . '</flux:badge>'))
|
||||||
|
->html(),
|
||||||
|
|
||||||
|
Column::make('Aksi')
|
||||||
|
->label(function ($row) {
|
||||||
|
$actions = '';
|
||||||
|
|
||||||
|
$actions .= view('components.datatables.edit', [
|
||||||
|
'id' => $row->id,
|
||||||
|
'editRoute' => route('studio.master.outlet.edit', $row->id),
|
||||||
|
])->render();
|
||||||
|
|
||||||
|
$actions .= view('components.datatables.delete', [
|
||||||
|
'id' => $row->id,
|
||||||
|
'deleteRoute' => route('studio.master.outlet.delete', $row->id),
|
||||||
|
])->render();
|
||||||
|
|
||||||
|
return $actions;
|
||||||
|
})
|
||||||
|
->html()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function builder(): Builder
|
||||||
|
{
|
||||||
|
return Outlet::select('id', 'name', 'phone_number', 'landmark', 'address', 'opening_hours', 'facilities', 'opened_date', 'closed_date', 'status');
|
||||||
|
}
|
||||||
|
}
|
||||||
129
app/Livewire/Forms/OutletForm.php
Normal file
129
app/Livewire/Forms/OutletForm.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Forms;
|
||||||
|
|
||||||
|
use App\Enums\OutletStatus;
|
||||||
|
use App\Models\Outlet;
|
||||||
|
use App\Rules\PhoneNumber;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Livewire\Form;
|
||||||
|
|
||||||
|
class OutletForm extends Form
|
||||||
|
{
|
||||||
|
public ?Outlet $outlet;
|
||||||
|
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
public string $phone_number = '';
|
||||||
|
|
||||||
|
public string $address = '';
|
||||||
|
|
||||||
|
public string $landmark = '';
|
||||||
|
|
||||||
|
public ?string $maps_url = null;
|
||||||
|
|
||||||
|
public array $opening_hours = [
|
||||||
|
'senin' => ['open' => null, 'close' => null],
|
||||||
|
'selasa' => ['open' => null, 'close' => null],
|
||||||
|
'rabu' => ['open' => null, 'close' => null],
|
||||||
|
'kamis' => ['open' => null, 'close' => null],
|
||||||
|
'jumat' => ['open' => null, 'close' => null],
|
||||||
|
'sabtu' => ['open' => null, 'close' => null],
|
||||||
|
'minggu' => ['open' => null, 'close' => null],
|
||||||
|
];
|
||||||
|
|
||||||
|
public array $facilities = [''];
|
||||||
|
|
||||||
|
public string $opened_date = '';
|
||||||
|
|
||||||
|
public ?string $status = null;
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$days = ['senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu', 'minggu'];
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'name' => ['required', 'string', 'max:100'],
|
||||||
|
'phone_number' => ['required', 'string', new PhoneNumber],
|
||||||
|
'address' => ['required', 'string'],
|
||||||
|
'landmark' => ['required', 'string', 'max:40'],
|
||||||
|
'maps_url' => [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
'regex:/^(https?:\/\/)?(www\.)?(google\.[a-z.]+\/maps\/.+|goo\.gl\/maps\/.+|maps\.app\.goo\.gl\/.+)$/i'
|
||||||
|
],
|
||||||
|
'opening_hours' => ['required', 'array'],
|
||||||
|
'facilities' => ['required', 'array', 'min:1'],
|
||||||
|
'facilities.*' => ['required', 'string', 'max:20'],
|
||||||
|
'opened_date' => ['required', 'date'],
|
||||||
|
'status' => ['required', Rule::in(OutletStatus::cases())],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($days as $day) {
|
||||||
|
$rules["opening_hours.$day"] = ['required', 'array'];
|
||||||
|
$rules["opening_hours.$day.open"] = ['nullable', 'date_format:H:i'];
|
||||||
|
$rules["opening_hours.$day.close"] = ['nullable', 'date_format:H:i'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validationAttributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'nama',
|
||||||
|
'phone_number' => 'nomor telepon',
|
||||||
|
'address' => 'alamat',
|
||||||
|
'landmark' => 'patokan',
|
||||||
|
'maps_url' => 'tautan google maps',
|
||||||
|
'opening_hours.*' => 'jam beroperasi',
|
||||||
|
'facilities.*' => 'fasilitas',
|
||||||
|
'opened_date' => 'tanggal buka',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOutlet(Outlet $outlet)
|
||||||
|
{
|
||||||
|
$this->outlet = $outlet;
|
||||||
|
|
||||||
|
$this->name = $outlet->name;
|
||||||
|
$this->phone_number = $outlet->phone_number;
|
||||||
|
$this->address = $outlet->address;
|
||||||
|
$this->landmark = $outlet->landmark;
|
||||||
|
$this->maps_url = $outlet->maps_url;
|
||||||
|
$this->opening_hours = $outlet->opening_hours;
|
||||||
|
$this->facilities = $outlet->facilities;
|
||||||
|
$this->opened_date = $outlet->opened_date;
|
||||||
|
$this->status = $outlet->status->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$data = $this->all();
|
||||||
|
|
||||||
|
if ($data['status'] == OutletStatus::TERMINATED) {
|
||||||
|
$data['closed_date'] = now();
|
||||||
|
} else {
|
||||||
|
$data['closed_date'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Outlet::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$data = $this->all();
|
||||||
|
|
||||||
|
if ($data['status'] == OutletStatus::TERMINATED->value) {
|
||||||
|
$data['closed_date'] = now()->toDateString();
|
||||||
|
} else {
|
||||||
|
$data['closed_date'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->outlet->update($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Livewire/Studio/Master/Outlet/Create.php
Normal file
39
app/Livewire/Studio/Master/Outlet/Create.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Studio\Master\Outlet;
|
||||||
|
|
||||||
|
use App\Livewire\Forms\OutletForm;
|
||||||
|
use App\Traits\Outlet\WithFacilityHandler;
|
||||||
|
use App\Traits\WithUpdatedData;
|
||||||
|
use Flux\Flux;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
#[Title('Tambah Outlet')]
|
||||||
|
class Create extends Component
|
||||||
|
{
|
||||||
|
use WithFacilityHandler, WithUpdatedData;
|
||||||
|
|
||||||
|
public OutletForm $form;
|
||||||
|
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$this->form->store();
|
||||||
|
|
||||||
|
Flux::toast(
|
||||||
|
heading: 'Berhasil',
|
||||||
|
text: 'Outlet berhasil ditambahkan.',
|
||||||
|
variant: 'success',
|
||||||
|
duration: 3000
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->redirectRoute('studio.master.outlet.index', navigate: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.studio.master.outlet.form', [
|
||||||
|
'pageTitle' => 'Tambah Outlet',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/Livewire/Studio/Master/Outlet/Edit.php
Normal file
47
app/Livewire/Studio/Master/Outlet/Edit.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Studio\Master\Outlet;
|
||||||
|
|
||||||
|
use App\Livewire\Forms\OutletForm;
|
||||||
|
use App\Models\Outlet;
|
||||||
|
use App\Traits\Outlet\WithFacilityHandler;
|
||||||
|
use App\Traits\WithUpdatedData;
|
||||||
|
use Flux\Flux;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
#[Title('Ubah Outlet')]
|
||||||
|
class Edit extends Component
|
||||||
|
{
|
||||||
|
use WithFacilityHandler, WithUpdatedData;
|
||||||
|
|
||||||
|
public OutletForm $form;
|
||||||
|
|
||||||
|
public function mount(Outlet $outlet)
|
||||||
|
{
|
||||||
|
$this->form->setOutlet($outlet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$this->form->update();
|
||||||
|
|
||||||
|
$this->dispatch('refreshDatatable');
|
||||||
|
|
||||||
|
Flux::toast(
|
||||||
|
heading: 'Berhasil',
|
||||||
|
text: 'Outlet berhasil diperbarui.',
|
||||||
|
variant: 'success',
|
||||||
|
duration: 3000
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->redirectRoute('studio.master.outlet.index', navigate: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.studio.master.outlet.form', [
|
||||||
|
'pageTitle' => 'Ubah Outlet',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Livewire/Studio/Master/Outlet/Index.php
Normal file
38
app/Livewire/Studio/Master/Outlet/Index.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Studio\Master\Outlet;
|
||||||
|
|
||||||
|
use App\Models\Outlet;
|
||||||
|
use App\Traits\WithConfirmation;
|
||||||
|
use Flux\Flux;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
#[Title('Outlet')]
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
use WithConfirmation;
|
||||||
|
|
||||||
|
public function delete(Outlet $outlet)
|
||||||
|
{
|
||||||
|
$outlet->delete();
|
||||||
|
|
||||||
|
$this->dispatch('refreshDatatable');
|
||||||
|
|
||||||
|
Flux::toast(
|
||||||
|
heading: 'Berhasil',
|
||||||
|
text: 'Outlet berhasil dihapus.',
|
||||||
|
variant: 'success',
|
||||||
|
);
|
||||||
|
|
||||||
|
Flux::modals()->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.studio.master.outlet.index', [
|
||||||
|
'pageTitle' => 'Outlet',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Models/Outlet.php
Normal file
39
app/Models/Outlet.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\OutletStatus;
|
||||||
|
use Deligoez\LaravelModelHashId\Traits\HasHashId;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Sluggable\HasSlug;
|
||||||
|
use Spatie\Sluggable\SlugOptions;
|
||||||
|
|
||||||
|
class Outlet extends Model
|
||||||
|
{
|
||||||
|
use HasSlug, SoftDeletes;
|
||||||
|
|
||||||
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'opening_hours' => 'array',
|
||||||
|
'facilities' => 'array',
|
||||||
|
'status' => OutletStatus::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSlugOptions(): SlugOptions
|
||||||
|
{
|
||||||
|
return SlugOptions::create()
|
||||||
|
->generateSlugsFrom('name')
|
||||||
|
->saveSlugsTo('slug');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function users(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'user_outlet')->withPivot('position_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Traits/Outlet/WithFacilityHandler.php
Normal file
25
app/Traits/Outlet/WithFacilityHandler.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits\Outlet;
|
||||||
|
|
||||||
|
use App\Livewire\Forms\OutletForm;
|
||||||
|
|
||||||
|
trait WithFacilityHandler
|
||||||
|
{
|
||||||
|
public OutletForm $form;
|
||||||
|
|
||||||
|
public function addFacility(): void
|
||||||
|
{
|
||||||
|
$this->form->facilities[] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFacility(int $index): void
|
||||||
|
{
|
||||||
|
unset($this->form->facilities[$index]);
|
||||||
|
$this->form->facilities = array_values($this->form->facilities);
|
||||||
|
|
||||||
|
if (empty($this->form->facilities)) {
|
||||||
|
$this->form->facilities = [''];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\OutletStatus;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('outlets', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name', 100);
|
||||||
|
$table->string('slug', 120);
|
||||||
|
$table->string('phone_number', 20);
|
||||||
|
$table->text('address');
|
||||||
|
$table->string('landmark', 20);
|
||||||
|
$table->text('maps_url')->nullable();
|
||||||
|
$table->json('opening_hours');
|
||||||
|
$table->json('facilities');
|
||||||
|
$table->enum('status', [OutletStatus::values()])->default(OutletStatus::OPERATIONAL)->comment(OutletStatus::comment());
|
||||||
|
$table->date('opened_date');
|
||||||
|
$table->date('closed_date')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('outlets');
|
||||||
|
}
|
||||||
|
};
|
||||||
18
resources/views/components/confirmation/delete.blade.php
Normal file
18
resources/views/components/confirmation/delete.blade.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<flux:modal name="delete" class="w-[22rem]">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="lg">{{ $confirmingTitle }}</flux:heading>
|
||||||
|
<flux:text class="mt-2">{{ $confirmingMessage }}</flux:text>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<flux:spacer />
|
||||||
|
<flux:modal.close>
|
||||||
|
<flux:button variant="ghost">Batal</flux:button>
|
||||||
|
</flux:modal.close>
|
||||||
|
<flux:button href="javascript:void(0)" type="submit" variant="danger"
|
||||||
|
wire:click="delete('{{ $confirmingId }}')">
|
||||||
|
{{ $confirmingButtonText }}
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</flux:modal>
|
||||||
@ -9,6 +9,17 @@ class="px-2 hidden dark:flex" />
|
|||||||
<flux:navlist.item icon="squares-plus" href="{{ route('studio.dashboard.overview') }}"
|
<flux:navlist.item icon="squares-plus" href="{{ route('studio.dashboard.overview') }}"
|
||||||
:current="request()->routeIs('studio.dashboard.overview')" wire:navigate.hover>
|
:current="request()->routeIs('studio.dashboard.overview')" wire:navigate.hover>
|
||||||
Ringkasan</flux:navlist.item>
|
Ringkasan</flux:navlist.item>
|
||||||
|
|
||||||
|
<div class="mt-3 mb-1">
|
||||||
|
<div class="text-zinc-500 dark:text-gray-300 text-sm/6">Master
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<flux:navlist.item icon="home-modern" href="{{ route('studio.master.outlet.index') }}"
|
||||||
|
:current="request()->routeIs('studio.master.outlet.*')" wire:navigate.hover>Outlet
|
||||||
|
</flux:navlist.item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<flux:navlist.item icon="inbox" badge="12" href="#">Inbox</flux:navlist.item>
|
<flux:navlist.item icon="inbox" badge="12" href="#">Inbox</flux:navlist.item>
|
||||||
<flux:navlist.group expandable heading="Favorites" class="hidden lg:grid">
|
<flux:navlist.group expandable heading="Favorites" class="hidden lg:grid">
|
||||||
<flux:navlist.item href="#">Marketing site</flux:navlist.item>
|
<flux:navlist.item href="#">Marketing site</flux:navlist.item>
|
||||||
|
|||||||
100
resources/views/livewire/studio/master/outlet/form.blade.php
Normal file
100
resources/views/livewire/studio/master/outlet/form.blade.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<flux:main>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<flux:button href="{{ route('studio.master.outlet.index') }}" wire:navigate.hover class="text-sm">
|
||||||
|
Kembali
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col lg:flex-row gap-4">
|
||||||
|
<div class="w-full lg:w-3/4">
|
||||||
|
<flux:card class="space-y-6 p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<flux:input label="Nama" placeholder="Masukkan nama outlet"
|
||||||
|
wire:model.live.debounce.500ms="form.name" autofocus autocomplete="off" clearable />
|
||||||
|
|
||||||
|
<flux:input label="Nomor Telepon" placeholder="Masukkan nomor telepon"
|
||||||
|
mask="9999 9999 99999" wire:model.live.debounce.500ms="form.phone_number"
|
||||||
|
autocomplete="off" clearable />
|
||||||
|
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<flux:textarea label="Alamat" placeholder="Masukkan alamat lengkap"
|
||||||
|
wire:model.live.debounce.500ms="form.address" autocomplete="off" clearable
|
||||||
|
rows="2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<flux:input label="Patokan" placeholder="Masukkan patokan"
|
||||||
|
wire:model.live.debounce.500ms="form.landmark" autocomplete="off" clearable />
|
||||||
|
|
||||||
|
<flux:input label="Titik Maps" placeholder="Masukkan titik maps"
|
||||||
|
wire:model.live.debounce.500ms="form.maps_url" autocomplete="off" clearable />
|
||||||
|
|
||||||
|
<flux:date-picker label="Tanggal Buka" with-today
|
||||||
|
wire:model.live.debounce.500ms="form.opened_date" autocomplete="off" clearable
|
||||||
|
locale="id-ID" value="2023-01-01" />
|
||||||
|
|
||||||
|
<flux:radio.group wire:model.live="form.status" variant="buttons" class="w-full *:flex-1"
|
||||||
|
label="Status">
|
||||||
|
@foreach (\App\Enums\OutletStatus::cases() as $status)
|
||||||
|
<flux:radio value="{{ $status->value }}" icon="{{ $status->icon() }}">
|
||||||
|
{{ $status->label() }}
|
||||||
|
</flux:radio>
|
||||||
|
@endforeach
|
||||||
|
</flux:radio.group>
|
||||||
|
</div>
|
||||||
|
</flux:card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full lg:w-1/3 space-y-4">
|
||||||
|
<flux:card class="space-y-6 p-6">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<h3 class="text-sm font-medium">Jam Operasional</h3>
|
||||||
|
@foreach (['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'] as $day)
|
||||||
|
<div class="grid grid-cols-3 gap-2 items-center">
|
||||||
|
<span class="text-sm">{{ $day }}</span>
|
||||||
|
<flux:input type="time"
|
||||||
|
wire:model.live.debounce.500ms="form.opening_hours.{{ strtolower($day) }}.open" />
|
||||||
|
<flux:input type="time"
|
||||||
|
wire:model.live.debounce.500ms="form.opening_hours.{{ strtolower($day) }}.close" />
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</flux:card>
|
||||||
|
|
||||||
|
<flux:card class="space-y-6 p-6">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h3 class="text-sm font-medium">Fasilitas</h3>
|
||||||
|
@foreach ($form->facilities as $index => $facility)
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<flux:input wire:model.live.debounce.500ms="form.facilities.{{ $index }}"
|
||||||
|
placeholder="Masukkan fasilitas" class="flex-1" autocomplete="off" clearable />
|
||||||
|
@if ($index != 0)
|
||||||
|
<flux:button variant="danger" icon="trash"
|
||||||
|
wire:click="removeFacility({{ $index }})" class="cursor-pointer" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
<flux:button variant="primary" size="sm" wire:click="addFacility"
|
||||||
|
class="sm:w-auto cursor-pointer">
|
||||||
|
+ Tambah Fasilitas
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</flux:card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-start">
|
||||||
|
<flux:button variant="primary" class="sm:w-auto cursor-pointer" wire:click="save">
|
||||||
|
Simpan
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</flux:main>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<flux:main>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<flux:button href="{{ route('studio.master.outlet.create') }}" variant="primary" wire:navigate.hover
|
||||||
|
class="text-sm">
|
||||||
|
Tambah
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<livewire:datatable.outlets-table />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@include('components.confirmation.delete')
|
||||||
|
</flux:main>
|
||||||
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
use App\Livewire\Auth\Login;
|
use App\Livewire\Auth\Login;
|
||||||
use App\Livewire\Studio\Dashboard\Overview;
|
use App\Livewire\Studio\Dashboard\Overview;
|
||||||
|
use App\Livewire\Studio\Master\Outlet\Create as OutletCreate;
|
||||||
|
use App\Livewire\Studio\Master\Outlet\Edit as OutletEdit;
|
||||||
|
use App\Livewire\Studio\Master\Outlet\Index as OutletIndex;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@ -22,4 +25,13 @@
|
|||||||
->group(function () {
|
->group(function () {
|
||||||
Route::get('overview', Overview::class)->name('overview');
|
Route::get('overview', Overview::class)->name('overview');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::prefix('master')
|
||||||
|
->as('studio.master.outlet.')
|
||||||
|
->group(function () {
|
||||||
|
Route::get('outlets', OutletIndex::class)->name('index');
|
||||||
|
Route::get('outlets/create', OutletCreate::class)->name('create');
|
||||||
|
Route::get('outlets/{outlet}/edit', OutletEdit::class)->name('edit');
|
||||||
|
Route::get('outlets/{outlet}/delete', OutletCreate::class)->name('delete');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user