分店介面 與包廂設定 20250514

This commit is contained in:
allen.yan 2025-05-14 15:32:54 +08:00
parent 023c00d20a
commit 5359037500
15 changed files with 229 additions and 10 deletions

View File

@ -9,12 +9,13 @@ use Livewire\Component;
use WireUi\Traits\WireUiActions;
use App\Models\Branch;
use App\Models\Room;
class BranchForm extends Component
{
use WireUiActions;
protected $listeners = ['openModal','closeModal', 'deleteArtist'];
protected $listeners = ['openModal','closeModal', 'deleteBranch'];
public bool $canCreate;
public bool $canEdit;
@ -27,6 +28,7 @@ class BranchForm extends Component
'name' =>'',
'external_ip' =>'',
'enable' => true,
'roomNotes' =>''
];
@ -70,7 +72,29 @@ class BranchForm extends Component
}
} else {
if ($this->canCreate) {
$branch = Branch::create($this->fields);
$branch = Branch::create([
'name' => $this->fields['name'],
'external_ip' => $this->fields['external_ip'],
'enable' => $this->fields['enable'],
]);
// 解析 roomNotes
$roomLines = explode("\n", trim($this->fields['roomNotes']));
$rooms = [];
foreach ($roomLines as $line) {
[$floor, $roomList] = explode(';', $line);
$roomNames = array_map('trim', explode(',', $roomList));
foreach ($roomNames as $roomName) {
$rooms[] = new Room([
'name' => $roomName,
]);
}
}
// 儲存所有 Room
$branch->rooms()->saveMany($rooms);
$this->notification()->send([
'icon' => 'success',
'title' => '成功',
@ -83,7 +107,7 @@ class BranchForm extends Component
$this->dispatch('pg:eventRefresh-branch-table');
}
public function deleteArtist($id)
public function deleteBranch($id)
{
if ($this->canDelect) {
Branch::findOrFail($id)->delete();

View File

@ -183,6 +183,11 @@ final class BranchTable extends PowerGridComponent
public function actions(Branch $row): array
{
$actions = [];
$actions[] = Button::add('room-settings')
->slot('包廂設定')
->icon('solid-cog')
->class('inline-flex items-center gap-1 px-3 py-1 rounded bg-amber-200 text-black')
->dispatchTo('admin.room-grid', 'openModal', ['branch_id' => $row->id]);
if ($this->canEdit) {
$actions[] =Button::add('edit')
->slot(__('branches.edit'))

View File

@ -0,0 +1,28 @@
<?php
namespace App\Livewire\Admin;
use App\Models\Room;
use Livewire\Component;
class RoomDetailModal extends Component
{
protected $listeners = ['openModal'];
public $room;
public bool $showModal = false;
public function openModal($roomId)
{
$this->room = Room::find($roomId);
$this->showModal = true;
}
public function render()
{
return view('livewire.admin.room-detail-modal');
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Livewire\Admin;
use App\Models\Room;
use App\Models\Branch;
use Livewire\Component;
use Illuminate\Database\Eloquent\Collection;
class RoomGrid extends Component
{
protected $listeners = ['openModal','closeModal'];//,'refreshRooms' => '$refresh'
public bool $showModal = false;
public $branchName="";
public Collection $rooms;
public function mount()
{
$this->rooms = new Collection();
}
public function openModal($branch_id = null)
{
$this->branchName=Branch::where('id',$branch_id)->first()->name;
$this->rooms = Room::where('branch_id',$branch_id)->get();
$this->showModal = true;
}
public function render()
{
return view('livewire.admin.room-grid');
}
}

View File

@ -37,7 +37,7 @@ class Artist extends Model
protected static function booted()
{
// 無論是 creating 或 updating都執行這段共用的邏輯
static::saving(function ($artist) {
static::saving(function (Artist $artist) {
$simplified=ChineseNameConverter::convertToSimplified($artist->name);// 繁體轉簡體
$artist->simplified = $simplified;
$artist->phonetic_abbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號
@ -47,6 +47,11 @@ class Artist extends Model
$firstChar = $chars[0] ?? null;
$artist->strokes_abbr=( $firstChar && preg_match('/\p{Han}/u', $firstChar) ) ? ChineseStrokesConverter::getStrokes($firstChar) : 0;
});
static::deleting(function (Artist $artist) {
// 解除與歌曲的多對多關聯
$artist->songs()->detach();
});
}
}

View File

@ -25,4 +25,10 @@ class Branch extends Model
->withPivot('counts')
->withTimestamps();
}
protected static function booted()
{
static::deleting(function (Branch $branch) {
$branch->rooms()->delete();
});
}
}

View File

@ -11,11 +11,41 @@ class Room extends Model
/** @use HasFactory<\Database\Factories\ArtistFactory> */
use HasFactory, LogsModelActivity;
protected $fillable = [
'name',
'internal_ip',
'port',
'status',
'started_at',
'ended_at',
];
protected $casts = [
'name' => 'string',
'internal_ip' =>'string',
'port' => 'int',
'status' => \App\Enums\RoomStatus::class,
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
public function str_started_at(){
$str ="Not Set";
if($this->started_at !=null){
$str = $this->started_at;
}
return $str;
}
public function str_ended_at(){
$str ="Not Set";
if($this->ended_at !=null){
$str = $this->ended_at;
}
return $str;
}
public function branch() {
return $this->belongsTo(Branch::class);
}

View File

@ -71,7 +71,7 @@ class Song extends Model
protected static function booted()
{
// 無論是 creating 或 updating都執行這段共用的邏輯
static::saving(function ($song) {
static::saving(function (Song $song) {
$simplified=ChineseNameConverter::convertToSimplified($song->name);// 繁體轉簡體
$song->simplified = $simplified;
$song->phonetic_abbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號
@ -83,5 +83,12 @@ class Song extends Model
$song->strokes_abbr=($firstChar && preg_match('/\p{Han}/u', $firstChar)) ? ChineseStrokesConverter::getStrokes($firstChar) : 0;
$song->song_number = mb_strlen($song->name, 'UTF-8');
});
static::deleting(function (Song $song) {
// Detach 關聯資料
$song->artists()->detach();
$song->categories()->detach();
$song->branches()->detach();
$song->users()->detach();
});
}
}

View File

@ -15,10 +15,10 @@ return new class extends Migration
$table->id();
$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->string('internal_ip')->nullable()->comment('內部 IP');
$table->unsignedSmallInteger('port')->nullable()->comment('通訊 Port');
$table->enum('status', ['active', 'closed', 'error', 'maintenance'])->default('error')->comment('狀態'); // :啟用中 / 已結束
$table->dateTime('started_at')->nullable()->comment('開始時間'); //
$table->dateTime('ended_at')->nullable()->comment('結束時間'); //
$table->timestamps();
});

View File

@ -23,4 +23,9 @@ return [
'user.status.Active' => '正常',
'user.status.Suspended' => '停權',
'user.status.Deleting' => '刪除中',
'room.status.Active' => '已占用',
'room.status.Closed' => '可用',
'room.status.Error' => '異常',
'room.status.Maintenance' => '維修',
];

View File

@ -0,0 +1,20 @@
@php
use App\Enums\RoomStatus;
$statusColors = [
RoomStatus::Active->value => 'green-600',
RoomStatus::Closed->value => 'gray-600',
RoomStatus::Error->value => 'red-600',
RoomStatus::Maintenance->value => 'yellow-600',
];
@endphp
<div class="border p-2 rounded shadow-md h-32 relative cursor-pointer bg-amber-50"
wire:click="$dispatchTo('admin.room-detail-modal','openModal', { roomId: {{ $room->id }} })">
<div class="font-bold">{{ $room->name }}</div>
<div class="text-sm text-{{ $statusColors[$room->status->value] ?? 'gray-500' }} text-center">
{{ $room->status->labels() }}
</div>
<div class="text-xs text-center whitespace-nowrap ">{{ $room->str_started_at() }}</div>
<div class="text-xs text-center whitespace-nowrap ">{{ $room->str_ended_at() }}</div>
</div>

View File

@ -4,7 +4,9 @@
<x-wireui:input label="{{__('branches.external_ip')}}" wire:model.defer="fields.external_ip" />
<x-wireui:toggle label="{{__('branches.enable')}}" wire:model.defer="fields.enable" />
</div>
@unless($branchId)
<x-wireui:textarea label="包廂資料" placeholder="請寫下包廂...範利<br>1樓;pc101,pc102,pc103" wire:model.defer="fields.roomNotes"/>
@endunless
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{__('branches.cancel')}}" wire:click="closeModal" />

View File

@ -2,6 +2,7 @@
<x-layouts.admin>
<x-wireui:notifications/>
<livewire:admin.branch-table />
<livewire:admin.room-grid />
<livewire:admin.branch-form />
<livewire:admin.branch-import-data />
</x-layouts.admin>

View File

@ -0,0 +1,29 @@
<x-wireui:modal id="room-detail-modal" wire:model.defer="showModal" persistent>
<x-wireui:card class="border border-gray-200">
<x-slot name="action">
<button class="cursor-pointer p-1 rounded-full focus:outline-none focus:outline-hidden focus:ring-2 focus:ring-secondary-200 text-secondary-300"
x-on:click="close"
tabindex="-1"
>
<x-dynamic-component
:component="WireUi::component('icon')"
name="x-mark"
class="w-5 h-5"
/>
</button>
</x-slot>
<x-slot name="title">
{{ $room->name ?? '未選擇' }}包廂設定
</x-slot>
<div class="grid grid-cols-3 gap-4 mb-4">
<x-wireui:button wire:click="startMachine" >開機</x-wireui:button>
<x-wireui:button wire:click="stopMachine" >關機</x-wireui:button>
<x-wireui:button wire:click="closeModal" >取消</x-wireui:button>
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<x-wireui:button wire:click="openAccount" >包廂開帳</x-wireui:button>
<x-wireui:button wire:click="closeAccount" >包廂關帳</x-wireui:button>
</div>
</x-wireui:card>
</x-wireui:modal>

View File

@ -0,0 +1,22 @@
<x-wireui:modal id="room-grid-modal" wire:model.defer="showModal" persistent >
<x-wireui:card class="border border-gray-200 w-full">
<x-slot name="action">
<button class="cursor-pointer p-1 rounded-full focus:outline-none focus:outline-hidden focus:ring-2 focus:ring-secondary-200 text-secondary-300"
x-on:click="close"
tabindex="-1"
>
<x-dynamic-component :component="WireUi::component('icon')" name="x-mark" class="w-5 h-5"/>
</button>
</x-slot>
<x-slot name="title">{{$branchName}} - 包廂設定</x-slot>
<div class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
@forelse($rooms as $room)
<x-room-card :room="$room" />
@empty
<div class="col-span-full text-center text-gray-500">尚無包廂資料</div>
@endforelse
</div>
<livewire:admin.room-detail-modal />
</x-wireui:card>
</x-wireui:modal>