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 @@
+
匯入格式說明
+請依下列表格格式準備 Excel 或 CSV 檔案:
+ +欄位名稱 | +說明 | +範例 | +
---|---|---|
?? | +?? | +?? | +
+ 系統限制:最大上傳 {{ $maxUploadSize }} +
+