diff --git a/app/Livewire/Admin/BranchForm.php b/app/Livewire/Admin/BranchForm.php new file mode 100644 index 0000000..980b7e2 --- /dev/null +++ b/app/Livewire/Admin/BranchForm.php @@ -0,0 +1,116 @@ +'', + 'external_ip' =>'', + 'enable' => true, + ]; + + + public function mount() + { + $this->canCreate = Auth::user()?->can('room-edit') ?? false; + $this->canEdit = Auth::user()?->can('room-edit') ?? false; + $this->canDelect = Auth::user()?->can('room-delete') ?? false; + } + + public function openModal($id = null) + { + $this->resetFields(); + + if ($id) { + $branch = Branch::findOrFail($id); + $this->branchId = $branch->id; + $this->fields = $branch->only(array_keys($this->fields)); + } + + $this->showModal = true; + } + + public function closeModal() + { + $this->resetFields(); + $this->showModal = false; + } + + public function save() + { + if ($this->branchId) { + if ($this->canEdit) { + $branch = Branch::findOrFail($this->branchId); + $branch->update($this->fields); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '分店已更新', + ]); + } + } else { + if ($this->canCreate) { + $branch = Branch::create($this->fields); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '分店已新增', + ]); + } + } + $this->resetFields(); + $this->showModal = false; + $this->dispatch('pg:eventRefresh-branch-table'); + } + + public function deleteArtist($id) + { + if ($this->canDelect) { + Branch::findOrFail($id)->delete(); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '分店已刪除', + ]); + + $this->dispatch('pg:eventRefresh-branch-table'); + } + } + + public function resetFields() + { + foreach ($this->fields as $key => $value) { + if ($key == 'enable') { + $this->fields[$key] = true; + } else { + $this->fields[$key] = ''; + } + } + $this->branchId = null; + } + + public function render() + { + return view('livewire.admin.branch-form'); + } +} diff --git a/app/Livewire/Admin/BranchImportData.php b/app/Livewire/Admin/BranchImportData.php new file mode 100644 index 0000000..0cf5827 --- /dev/null +++ b/app/Livewire/Admin/BranchImportData.php @@ -0,0 +1,112 @@ +canCreate = Auth::user()?->can('room-edit') ?? false; + $this->maxUploadSize = $this->getMaxUploadSize(); + } + + public function openModal() + { + $this->showModal = true; + } + + public function closeModal() + { + $this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案 + $this->reset(['file']); + $this->showModal = false; + } + + public function import() + { + // 檢查檔案是否有上傳 + $this->validate([ + 'file' => 'required|file|mimes:csv,xlsx,xls' + ]); + if ($this->canCreate) { + // 儲存檔案至 storage + $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); + + // 丟到 queue 執行 + ImportJob::dispatch($path,'Branch'); + + $this->notification()->send([ + 'icon' => 'info', + 'title' => $this->file->getClientOriginalName(), + 'description' => '已排入背景匯入作業,請稍候查看結果', + ]); + $this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔 + $this->reset(['file']); + $this->showModal = false; + } + } + protected function deleteTmpFile() + { + $Path = $this->file->getRealPath(); + if ($Path && File::exists($Path)) { + File::delete($Path); + } + } + + private function getMaxUploadSize(): string + { + $uploadMax = $this->convertPHPSizeToBytes(ini_get('upload_max_filesize')); + $postMax = $this->convertPHPSizeToBytes(ini_get('post_max_size')); + $max = min($uploadMax, $postMax); + return $this->humanFileSize($max); + } + + private function convertPHPSizeToBytes(string $s): int + { + $s = trim($s); + $unit = strtolower($s[strlen($s) - 1]); + $bytes = (int) $s; + switch ($unit) { + case 'g': + $bytes *= 1024; + case 'm': + $bytes *= 1024; + case 'k': + $bytes *= 1024; + } + return $bytes; + } + + private function humanFileSize(int $bytes, int $decimals = 2): string + { + $sizes = ['B', 'KB', 'MB', 'GB']; + $factor = floor((strlen((string) $bytes) - 1) / 3); + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $sizes[$factor]; + } + + public function render() + { + return view('livewire.admin.branch-import-data'); + } + +} \ No newline at end of file diff --git a/app/Livewire/Admin/BranchTable.php b/app/Livewire/Admin/BranchTable.php new file mode 100644 index 0000000..aa31261 --- /dev/null +++ b/app/Livewire/Admin/BranchTable.php @@ -0,0 +1,213 @@ + 'outside']); + //權限設定 + $this->canCreate = Auth::user()?->can('room-edit') ?? false; + $this->canEdit = Auth::user()?->can('room-edit') ?? false; + $this->canDownload=Auth::user()?->can('room-delete') ?? false; + $this->canDelect = Auth::user()?->can('room-delete') ?? false; + } + + public function setUp(): array + { + if($this->canDownload || $this->canDelect){ + $this->showCheckBox(); + } + $actions = []; + if($this->canDownload){ + $actions[]=PowerGrid::exportable(fileName: 'branch-file') + ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); + } + $header = PowerGrid::header() + ->withoutLoading() + ->showToggleColumns(); + //->showSoftDeletes() + //->showSearchInput() + if($this->canCreate){ + $header->includeViewOnTop('livewire.admin.branch-header') ; + } + $actions[]=$header; + $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); + return $actions; + } + + public function header(): array + { + $actions = []; + if ($this->canDelect) { + $actions[]=Button::add('bulk-delete') + ->slot('Bulk delete ()') + ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatch('bulkDelete.' . $this->tableName, []); + } + return $actions; + } + + public function datasource(): Builder + { + return Branch::query(); + } + + public function relationSearch(): array + { + return []; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields() + ->add('id') + ->add('name') + ->add('external_ip') + ->add('enable') + ->add('created_at_formatted', fn (Branch $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s')); + } + + public function columns(): array + { + $column=[]; + $column[]=Column::make(__('branches.no'), 'id'); + $column[]=Column::make(__('branches.name'), 'name')->sortable()->searchable() + ->editOnClick( + hasPermission: $this->canEdit, + dataField: 'name', + fallback: 'N/A', + saveOnMouseOut: true + ); + $column[]=Column::make(__('branches.external_ip'), 'external_ip')->sortable()->searchable() + ->editOnClick( + hasPermission: $this->canEdit, + dataField: 'external_ip', + fallback: 'N/A', + saveOnMouseOut: true + ); + $column[]=Column::make(__('branches.enable'), 'enable') + ->toggleable( + hasPermission: $this->canEdit, + trueLabel: 'yes', + falseLabel: 'no' + ); + $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false); + $column[]=Column::action(__('branches.actions')); + return $column; + } + #[On('bulkDelete.{tableName}')] + public function bulkDelete(): void + { + if ($this->canDelect) { + $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); + if($this->checkboxValues){ + Branch::destroy($this->checkboxValues); + $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + } + } + } + #[On('categoryChanged')] + public function categoryChanged($value,$fieldName, $modelId): void + { + // dd($value,$fieldName, $modelId); + if ($fieldName == 'category' && $this->canEdit) { + $this->noUpdated($modelId,$fieldName,$value); + } + } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if ($field === 'name' && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + #[On('onUpdatedToggleable')] + public function onUpdatedToggleable($id, $field, $value): void + { + if (in_array($field,['enable']) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $branch = Branch::find($id); + if ($branch) { + $branch->{$field} = $value; + $branch->save(); // 明確觸發 saving + } + $this->notification()->send([ + 'icon' => 'success', + 'title' => $id.'.'.__('branches.'.$field).':'.$value, + 'description' => '已經寫入', + ]); + } + public function filters(): array + { + return [ + Filter::inputText('name')->placeholder(__('branches.name')), + Filter::inputText('external_ip')->placeholder(__('branches.external_ip')), + Filter::boolean('enable')->label('✅', '❌'), + Filter::datetimepicker('created_at'), + ]; + } + + public function actions(Branch $row): array + { + $actions = []; + if ($this->canEdit) { + $actions[] =Button::add('edit') + ->slot(__('branches.edit')) + ->icon('solid-pencil-square') + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatchTo('admin.branch-form', 'openModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[] =Button::add('delete') + ->slot(__('branches.delete')) + ->icon('solid-trash') + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatchTo('admin.branch-form', 'deleteBranch', ['id' => $row->id]); + } + return $actions; + } + + /* + public function actionRules($row): array + { + return [ + // Hide button edit for ID 1 + Rule::button('edit') + ->when(fn($row) => $row->id === 1) + ->hide(), + ]; + } + */ +} diff --git a/app/Livewire/Admin/BranchesTable.php b/app/Livewire/Admin/BranchesTable.php deleted file mode 100644 index 958bb73..0000000 --- a/app/Livewire/Admin/BranchesTable.php +++ /dev/null @@ -1,108 +0,0 @@ -showCheckBox(); - - return [ - PowerGrid::header() - ->showSearchInput(), - PowerGrid::footer() - ->showPerPage() - ->showRecordCount(), - ]; - } - - public function datasource(): Builder - { - return Branches::query(); - } - - public function relationSearch(): array - { - return []; - } - - public function fields(): PowerGridFields - { - return PowerGrid::fields() - ->add('id') - ->add('name') - ->add('external_ip') - ->add('created_at'); - } - - public function columns(): array - { - return [ - Column::make('Id', 'id'), - Column::make('Name', 'name') - ->sortable() - ->searchable(), - - Column::make('External ip', 'external_ip') - ->sortable() - ->searchable(), - - Column::make('Created at', 'created_at_formatted', 'created_at') - ->sortable(), - - Column::make('Created at', 'created_at') - ->sortable() - ->searchable(), - - Column::action('Action') - ]; - } - - public function filters(): array - { - return [ - ]; - } - - #[\Livewire\Attributes\On('edit')] - public function edit($rowId): void - { - $this->js('alert('.$rowId.')'); - } - - public function actions(Branches $row): array - { - return [ - Button::add('edit') - ->slot('Edit: '.$row->id) - ->id() - ->class('pg-btn-white dark:ring-pg-primary-600 dark:border-pg-primary-600 dark:hover:bg-pg-primary-700 dark:ring-offset-pg-primary-800 dark:text-pg-primary-300 dark:bg-pg-primary-700') - ->dispatch('edit', ['rowId' => $row->id]) - ]; - } - - /* - public function actionRules($row): array - { - return [ - // Hide button edit for ID 1 - Rule::button('edit') - ->when(fn($row) => $row->id === 1) - ->hide(), - ]; - } - */ -} diff --git a/database/migrations/2025_05_06_055303_create_branches_table.php b/database/migrations/2025_05_06_055303_create_branches_table.php index 19e9ebf..c0c2523 100644 --- a/database/migrations/2025_05_06_055303_create_branches_table.php +++ b/database/migrations/2025_05_06_055303_create_branches_table.php @@ -13,8 +13,9 @@ return new class extends Migration { Schema::create('branches', function (Blueprint $table) { $table->id(); - $table->string('name'); - $table->ipAddress('external_ip'); // 對外 IP + $table->string('name')->comment('店名'); + $table->ipAddress('external_ip')->comment('對外IP'); // 對外 IP + $table->tinyInteger('enable')->default(1)->comment('狀態'); $table->timestamps(); }); } diff --git a/database/migrations/2025_05_06_055307_create_rooms_table.php b/database/migrations/2025_05_06_055307_create_rooms_table.php index a71a614..195946e 100644 --- a/database/migrations/2025_05_06_055307_create_rooms_table.php +++ b/database/migrations/2025_05_06_055307_create_rooms_table.php @@ -13,13 +13,13 @@ return new class extends Migration { Schema::create('rooms', function (Blueprint $table) { $table->id(); - $table->foreignId('branch_id')->constrained()->onDelete('cascade'); // 關聯分店 - $table->string('name'); // 包廂名稱 - $table->string('internal_ip'); // 內部 IP - $table->unsignedSmallInteger('port'); // 通訊 Port - $table->enum('status', ['active', 'closed', 'error', 'maintenance']); // 狀態:啟用中 / 已結束 - $table->dateTime('started_at'); // 開始時間 - $table->dateTime('ended_at')->nullable(); // 結束時間 + $table->foreignId('branch_id')->constrained()->onDelete('cascade')->comment('關聯分店'); + $table->string('name')->comment('包廂名稱'); + $table->string('internal_ip')->comment('內部 IP'); + $table->unsignedSmallInteger('port')->comment('通訊 Port'); + $table->enum('status', ['active', 'closed', 'error', 'maintenance'])->comment('狀態'); // :啟用中 / 已結束 + $table->dateTime('started_at')->comment('開始時間'); // + $table->dateTime('ended_at')->nullable()->comment('結束時間'); // $table->timestamps(); }); } diff --git a/resources/lang/zh-tw/branches.php b/resources/lang/zh-tw/branches.php new file mode 100644 index 0000000..d84ef49 --- /dev/null +++ b/resources/lang/zh-tw/branches.php @@ -0,0 +1,23 @@ + '分店管理', + 'list' => '分店列表', + 'CreateNew' => '新增分店', + 'EditBranch' => '編輯分店', + 'ImportData' => '滙入分店', + 'create_edit' => '新增 / 編輯', + 'create' => '新增', + 'edit' => '編輯', + 'delete' => '刪除', + 'no' => '編號', + 'name' => '名稱', + 'external_ip' => '對外IP', + 'enable' => '狀態', + + + 'actions' => '操作', + 'view' => '查看', + 'submit' => '提交', + 'cancel' => '取消', +]; \ No newline at end of file diff --git a/resources/views/livewire/admin/branch-form.blade.php b/resources/views/livewire/admin/branch-form.blade.php new file mode 100644 index 0000000..e805dab --- /dev/null +++ b/resources/views/livewire/admin/branch-form.blade.php @@ -0,0 +1,15 @@ + +
+ + + +
+ + +
+ + +
+
+
+ \ No newline at end of file diff --git a/resources/views/livewire/admin/branch-header.blade.php b/resources/views/livewire/admin/branch-header.blade.php new file mode 100644 index 0000000..a934bfc --- /dev/null +++ b/resources/views/livewire/admin/branch-header.blade.php @@ -0,0 +1,15 @@ +
+ + + +
\ No newline at end of file diff --git a/resources/views/livewire/admin/branch-import-data.blade.php b/resources/views/livewire/admin/branch-import-data.blade.php new file mode 100644 index 0000000..3944ea4 --- /dev/null +++ b/resources/views/livewire/admin/branch-import-data.blade.php @@ -0,0 +1,62 @@ + + + {{-- 說明區塊 --}} +
+

匯入格式說明

+

請依下列表格格式準備 Excel 或 CSV 檔案:

+ +
+ + + + + + + + + + + + + + + +
欄位名稱說明範例
??????
+
+
+ + + {{-- 檔案上傳 --}} +
+
+ +

+ 系統限制:最大上傳 {{ $maxUploadSize }} +

+
+ +
+ + +
+ +
+ 檔案上傳中,請稍候... +
+
+ + + +
+ + +
+
+
\ No newline at end of file diff --git a/resources/views/livewire/admin/branches.blade.php b/resources/views/livewire/admin/branches.blade.php new file mode 100644 index 0000000..c45c9cb --- /dev/null +++ b/resources/views/livewire/admin/branches.blade.php @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/resources/views/livewire/layout/admin/sidebar.blade.php b/resources/views/livewire/layout/admin/sidebar.blade.php index 8659928..34e6f26 100644 --- a/resources/views/livewire/layout/admin/sidebar.blade.php +++ b/resources/views/livewire/layout/admin/sidebar.blade.php @@ -7,6 +7,7 @@ ['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'], ['label' => 'Artist', 'route' => 'admin.artists', 'icon' => 'musical-note', 'permission' => 'song-list'], ['label' => 'Song', 'route' => 'admin.songs', 'icon' => 'musical-note', 'permission' => 'song-list'], + ['label' => 'Branche', 'route' => 'admin.branches', 'icon' => 'building-library', 'permission' => 'room-list'], ]; @endphp