diff --git a/app/Enums/TextAdColors.php b/app/Enums/TextAdColors.php index 3c4f2aa..4fbd805 100644 --- a/app/Enums/TextAdColors.php +++ b/app/Enums/TextAdColors.php @@ -8,7 +8,6 @@ enum TextAdColors: string { use HasLabels; - case Black = 'black'; case White = 'white'; case Red = 'red'; case Green = 'green'; @@ -17,7 +16,6 @@ enum TextAdColors: string public function labels(): string { return match ($this) { - self::Black => '黑色', self::White => '白色', self::Red => '紅色', self::Green => '綠色', diff --git a/app/Livewire/Forms/BroadcastTemplateForm.php b/app/Livewire/Forms/BroadcastTemplateForm.php new file mode 100644 index 0000000..ebbefe0 --- /dev/null +++ b/app/Livewire/Forms/BroadcastTemplateForm.php @@ -0,0 +1,136 @@ +'', + 'color' => 'white', + 'is_active' => true, + ]; + public function rules() + { + return [ + 'fields.content' => 'required|string|max:255', + 'fields.color' => ['required',new Enum(TextAdColors::class)], + 'fields.is_active' => 'required|boolean', + ]; + } + + + public function mount() + { + $this->colorOptions = collect(TextAdColors::cases())->map(fn ($color) => [ + 'name' => $color->labels(), + 'value' => $color->value, + ])->toArray(); + $this->canCreate = Auth::user()?->can('broadcast-edit') ?? false; + $this->canEdit = Auth::user()?->can('broadcast-edit') ?? false; + $this->canDelect = Auth::user()?->can('broadcast-delete') ?? false; + } + + public function openModal($id = null) + { + $this->resetFields(); + + if ($id) { + $text = BroadcastTemplate::findOrFail($id); + $this->textId = $text->id; + $this->fields = $text->only(array_keys($this->fields)); + } + + $this->showModal = true; + } + + public function closeModal() + { + $this->resetFields(); + $this->showModal = false; + } + + public function save() + { + $validated = $this->validate($this->rules()); + if ($this->textId) { + if ($this->canEdit) { + $text = BroadcastTemplate::findOrFail($this->textId); + $text->update($this->fields); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '文字公告已更新', + ]); + } + } else { + if ($this->canCreate) { + $text = BroadcastTemplate::create($this->fields); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '文字公告已新增', + ]); + } + } + $this->resetFields(); + $this->showModal = false; + $this->dispatch('pg:eventRefresh-broadcast-table'); + } + + public function deleteBroadcast($id) + { + if ($this->canDelect) { + BroadcastTemplate::findOrFail($id)->delete(); + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '文字公告已刪除', + ]); + + $this->dispatch('pg:eventRefresh-broadcast-table'); + } + } + + public function resetFields() + { + foreach ($this->fields as $key => $value) { + if ($key == 'color') { + $this->fields[$key] = 'white'; + } else if($key == 'is_active'){ + $this->fields[$key] = true; + } else { + $this->fields[$key] = ''; + } + } + $this->textId = null; + } + + public function render() + { + return view('livewire.forms.broadcast-template-form'); + } +} diff --git a/app/Livewire/Tables/BroadcastTemplateTable.php b/app/Livewire/Tables/BroadcastTemplateTable.php new file mode 100644 index 0000000..46933ed --- /dev/null +++ b/app/Livewire/Tables/BroadcastTemplateTable.php @@ -0,0 +1,212 @@ + 'outside']); + $this->canCreate = Auth::user()?->can('broadcast-create') ?? false; + $this->canEdit = Auth::user()?->can('broadcast-edit') ?? false; + $this->canDownload=Auth::user()?->can('broadcast-delete') ?? false; + $this->canDelect = Auth::user()?->can('broadcast-delete') ?? false; + } + + public function setUp(): array + { + if($this->canDownload || $this->canDelect){ + $this->showCheckBox(); + } + $actions = []; + if($this->canDownload){ + $actions[]=PowerGrid::exportable(fileName: $this->tableName.'-file') + ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); + } + $header = PowerGrid::header()->showSoftDeletes()->showToggleColumns(); + $header->includeViewOnTop('livewire.headers.broadcast-template'); + $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 BroadcastTemplate::query(); + } + + public function relationSearch(): array + { + return []; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields() + ->add('id') + ->add('content') + ->add( + 'content_short', + fn (BroadcastTemplate $model) => + '' . e(Str::limit($model->content, 50)) . '' + ) + ->add('color') + ->add('color_str', function (BroadcastTemplate $model) { + if ($this->canEdit) { + return Blade::render( + '', + [ + 'options' => TextAdColors::options(), + 'modelId' => intval($model->id), + 'fieldName'=>'color', + 'selected' => $model->color->value + ] + ); + } + // 沒有權限就顯示對應的文字 + + return $model->color->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 + } ) + ->add('is_active') + ->add('created_at'); + } + + public function columns(): array + { + $column=[]; + $column[] = Column::make(__('broadcast-templates.id'), 'id'); + $column[] = Column::make(__('broadcast-templates.content'), 'content_short', 'broadcast-templates.content')->sortable()->searchable(); + $column[] = Column::make(__('broadcast-templates.color'),'color_str', 'text-ads.color')->searchable(); + $column[] = Column::make(__('broadcast-templates.is_active'), 'is_active')->toggleable(hasPermission: $this->canEdit, trueLabel: 'yes', falseLabel: 'no'); + $column[] = Column::make(__('broadcast-templates.created_at'), 'created_at')->sortable()->searchable(); + $column[] = Column::action(__('broadcast-templates.actions')); + return $column; + } + + public function filters(): array + { + return [ + ]; + } + #[On('bulkDelete.{tableName}')] + public function bulkDelete(): void + { + $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); + if($this->checkboxValues){ + foreach ($this->checkboxValues as $id) { + $template = BroadcastTemplate::find($id); + if ($template) { + $template->delete(); + } + } + $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. + } + } + #[On('selectChanged')] + public function selectChanged($value,$fieldName, $modelId): void + { + //dd($value,$fieldName, $modelId); + if (in_array($fieldName, ['color'])) { + $this->noUpdated($modelId,$fieldName,$value); + } + } + #[On('onUpdatedEditable')] + public function onUpdatedEditable($id, $field, $value): void + { + if (in_array($field,[ + '' + ]) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + #[On('onUpdatedToggleable')] + public function onUpdatedToggleable($id, $field, $value): void + { + if (in_array($field,['is_active']) && $this->canEdit) { + $this->noUpdated($id,$field,$value); + } + } + private function noUpdated($id,$field,$value){ + $template = BroadcastTemplate::find($id); + if ($template) { + $template->{$field} = $value; + $template->save(); // 明確觸發 saving + } + $this->notification()->send([ + 'icon' => 'success', + 'title' => $id.'.'.__('broadcast.'.$field).':'.$value, + 'description' => '已經寫入', + ]); + } + + public function actions(BroadcastTemplate $row): array + { + $actions = []; + if ($this->canEdit) { + $actions[]=Button::add('edit') + ->slot(__('broadcast-templates.edit')) + ->icon('solid-pencil-square') + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatchTo('forms.broadcast-template-form', 'openModal', ['id' => $row->id]); + } + if($this->canDelect){ + $actions[]=Button::add('delete') + ->slot(__('broadcast-templates.delete')) + ->icon('solid-trash') + ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') + ->dispatchTo('forms.broadcast-template-form', 'deleteBroadcast', ['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/Models/BroadcastTemplate.php b/app/Models/BroadcastTemplate.php new file mode 100644 index 0000000..a53b78f --- /dev/null +++ b/app/Models/BroadcastTemplate.php @@ -0,0 +1,18 @@ + \App\Enums\TextAdColors::class, + 'is_active' => 'boolean', + ]; +} diff --git a/app/Models/TextBroadcast.php b/app/Models/TextBroadcast.php new file mode 100644 index 0000000..94da1fe --- /dev/null +++ b/app/Models/TextBroadcast.php @@ -0,0 +1,20 @@ +belongsToMany(Room::class, 'text_broadcast_room'); + } + + public function template() + { + return $this->belongsTo(BroadcastTemplate::class); + } +} diff --git a/database/migrations/2025_06_26_114551_create_text_ads_table.php b/database/migrations/2025_06_26_114551_create_text_ads_table.php index 777d8a0..7d17046 100644 --- a/database/migrations/2025_06_26_114551_create_text_ads_table.php +++ b/database/migrations/2025_06_26_114551_create_text_ads_table.php @@ -14,7 +14,7 @@ return new class extends Migration Schema::create('text_ads', function (Blueprint $table) { $table->id(); $table->string('content')->comment('廣告內容'); - $table->enum('color', ['black','white', 'red', 'green','blue'])->default('black')->comment('顯示顏色'); + $table->enum('color', ['white', 'red', 'green','blue'])->default('white')->comment('顯示顏色'); $table->integer('duration')->default(1)->comment('播放間隔時間(分鐘)'); $table->boolean('is_active')->default(true); // 啟用狀態 $table->timestamps(); diff --git a/database/migrations/2025_08_03_162846_create_broadcast_table.php b/database/migrations/2025_08_03_162846_create_broadcast_table.php new file mode 100644 index 0000000..8174571 --- /dev/null +++ b/database/migrations/2025_08_03_162846_create_broadcast_table.php @@ -0,0 +1,44 @@ +id(); + $table->text('content')->comment('公告內容'); // e.g., "親愛的 **name**,歡迎光臨!" + $table->enum('color', ['white', 'red', 'green','blue'])->default('white')->comment('顯示顏色'); + $table->boolean('is_active')->default(true); // 啟用狀態 + $table->timestamps(); + }); + Schema::create('text_broadcasts', function (Blueprint $table) { + $table->id(); + $table->foreignId('template_id')->nullable()->constrained('broadcast_templates')->nullOnDelete(); + $table->text('content'); // Final content with variables replaced + $table->enum('target_type', ['all', 'rooms'])->default('all'); + $table->timestamps(); + }); + Schema::create('text_broadcast_room', function (Blueprint $table) { + $table->id(); + $table->foreignId('text_broadcast_id')->constrained()->onDelete('cascade'); + $table->foreignId('room_id')->constrained()->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('text_broadcast_room'); + Schema::dropIfExists('text_broadcasts'); + Schema::dropIfExists('broadcast_templates'); + } +}; diff --git a/database/seeders/BroadcastTemplateSeeder.php b/database/seeders/BroadcastTemplateSeeder.php new file mode 100644 index 0000000..691456a --- /dev/null +++ b/database/seeders/BroadcastTemplateSeeder.php @@ -0,0 +1,58 @@ + '親愛的 **name**,祝你生日快樂!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**room_name** 歡迎您蒞臨本店,祝您有美好的一天!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 拾獲您的證件,請至櫃台領取', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 請移動你的愛車,謝謝!!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 祝你生日快樂!!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 櫃台有您訪客!!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**room_name** 警察臨檢不便之處敬請原諒!!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 您的愛車被拖吊了!!', + 'color' => TextAdColors::White, + ], + [ + 'content' => '**name** 請移動您的愛車,謝謝', + 'color' => TextAdColors::White, + ], + ]; + foreach ($txs as $tx) { + BroadcastTemplate::create([ + 'content' => $tx['content'], + 'color' => $tx['color'], + 'is_active' => true, + ]); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 10aa327..642bfb1 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,11 +14,7 @@ class DatabaseSeeder extends Seeder { (new TransferSqliteTableJob('database/User.data', false))->handle(); $this->call([ - // TextAdPermissionSeeder::class, - // TextAdSeeder::class, - // PermissionTableSeeder::class, - // CreateAdminUserSeeder::class, - // FavoriteSongsSeeder::class, + BroadcastTemplateSeeder::class, ]); } } diff --git a/resources/lang/zh-tw/broadcast-templates.php b/resources/lang/zh-tw/broadcast-templates.php new file mode 100644 index 0000000..c7d2d68 --- /dev/null +++ b/resources/lang/zh-tw/broadcast-templates.php @@ -0,0 +1,27 @@ + '文字公告管理', + 'list' => '文字公告列表', + 'CreateNew' => '新增文字公告', + 'EditArtist' => '編輯文字公告', + 'ImportData' => '滙入文字公告', + 'create_edit' => '新增 / 編輯', + 'create' => '新增', + 'edit' => '編輯', + 'delete' => '刪除', + 'id' => '編號', + 'name' => '主題', + 'content' => '內容', + 'description' => '描述', + 'color' => '顏色', + 'duration' => '播放間隔時間(分鐘)', + 'is_active' => '是否推播', + 'created_at' =>'建立於', + + + 'actions' => '操作', + 'view' => '查看', + 'submit' => '提交', + 'cancel' => '取消', +]; \ No newline at end of file diff --git a/resources/views/components/select-dropdown.blade.php b/resources/views/components/select-dropdown.blade.php new file mode 100644 index 0000000..4580191 --- /dev/null +++ b/resources/views/components/select-dropdown.blade.php @@ -0,0 +1,16 @@ +@props(['options', 'modelId' , 'fieldName', 'selected' ]) +
+ +
\ No newline at end of file diff --git a/resources/views/livewire/admin/broadcast-templates.blade.php b/resources/views/livewire/admin/broadcast-templates.blade.php new file mode 100644 index 0000000..b4151ad --- /dev/null +++ b/resources/views/livewire/admin/broadcast-templates.blade.php @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/resources/views/livewire/forms/broadcast-template-form.blade.php b/resources/views/livewire/forms/broadcast-template-form.blade.php new file mode 100644 index 0000000..3ffb545 --- /dev/null +++ b/resources/views/livewire/forms/broadcast-template-form.blade.php @@ -0,0 +1,23 @@ + +
+ + + + +
+ + +
+ + +
+
+
+ \ No newline at end of file diff --git a/resources/views/livewire/headers/broadcast-template.blade.php b/resources/views/livewire/headers/broadcast-template.blade.php new file mode 100644 index 0000000..a22d6cf --- /dev/null +++ b/resources/views/livewire/headers/broadcast-template.blade.php @@ -0,0 +1,10 @@ + + @if ($canCreate) + + @endif + \ 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 f9be3c9..f117289 100644 --- a/resources/views/livewire/layout/admin/sidebar.blade.php +++ b/resources/views/livewire/layout/admin/sidebar.blade.php @@ -15,6 +15,7 @@ new class extends Component ['label' => 'Room', 'route' => 'admin.rooms', 'icon' => 'building-library', 'permission' => 'room-list'], ['label' => 'RoomGrid', 'route' => 'admin.room-grids', 'icon' => 'film', 'permission' => 'room-list'], ['label' => 'TextAd', 'route' => 'admin.text-ads', 'icon' => 'megaphone', 'permission' => 'text-ad-list'], + ['label' => 'BroadcastTemplate', 'route' => 'admin.broadcast-templates', 'icon' => 'megaphone', 'permission' => 'broadcast-list'], ]; /** diff --git a/routes/web.php b/routes/web.php index 3464fa7..e7fbfb0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,4 +27,5 @@ Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () Route::get('/rooms', function () {return view('livewire.admin.rooms');})->name('rooms'); Route::get('/room-grids', function () {return view('livewire.admin.room-grids');})->name('room-grids'); Route::get('/text-ads', function () {return view('livewire.admin.text-ads');})->name('text-ads'); + Route::get('/broadcast-templates', function () {return view('livewire.admin.broadcast-templates');})->name('broadcast-templates'); }); \ No newline at end of file