202507301751

調整Room status log
調整Room 不能修改名稱
This commit is contained in:
allen.yan 2025-07-30 18:08:09 +08:00
parent 2983899678
commit cb530f94b8
12 changed files with 117 additions and 236 deletions

View File

@ -97,7 +97,8 @@ class RoomControlController extends Controller
return ApiResponse::error('包廂不存在');
}
$command = $validated['command'];
$room->log_source='api';
$room->log_message='sendSwitch';
$room->update([
'status' => $command,
'started_at' => $validated['started_at'] ?? null,
@ -137,6 +138,8 @@ class RoomControlController extends Controller
])->first();
if ($room) {
// 更新
$room->log_source='api';
$room->log_message='receiveSwitch';
$room->update([
'is_online' => $validated['is_online'],
'status' => $validated['status'],
@ -146,7 +149,7 @@ class RoomControlController extends Controller
//Log::info('Room updated', ['room_id' => $room->id, 'name' => $room->name]);
} else {
// 新增
$room = Room::create([
$room = new Room([
'branch_id' => $validated['branch_id'],
'floor' => $validated['floor'],
'type' => $validated['type'],
@ -156,6 +159,9 @@ class RoomControlController extends Controller
'started_at' => $validated['started_at'],
'ended_at' => $validated['ended_at'],
]);
$room->log_source = 'api';
$room->log_message = 'receiveSwitch';
$room->save();
//Log::info('Room created', ['room_id' => $room->id, 'name' => $room->name]);
}
return ApiResponse::success(new RoomResource($room->refresh()));

View File

@ -1,125 +0,0 @@
<?php
namespace App\Livewire\Forms;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use WireUi\Traits\WireUiActions;
use App\Models\Room;
use App\Enums\RoomType;
class RoomForm extends Component
{
use WireUiActions;
protected $listeners = ['openModal','closeModal', 'deleteRoom'];
public bool $canCreate;
public bool $canEdit;
public bool $canDelect;
public bool $showModal = false;
public ?int $roomId = null;
public array $typeOptions =[];
public array $fields = [
'floor' =>'',
'type' =>'',
'name' =>''
];
public function mount()
{
$this->typeOptions = collect(RoomType::cases())->map(fn ($type) => [
'name' => $type->labels(),
'value' => $type->value,
])->toArray();
$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) {
$room = Room::findOrFail($id);
$this->roomId = $room->id;
$this->fields = $room->only(array_keys($this->fields));
}
$this->showModal = true;
}
public function closeModal()
{
$this->resetFields();
$this->showModal = false;
}
public function save()
{
$description ="無權修改";
if ($this->roomId) {
if ($this->canEdit) {
$room = Room::findOrFail($this->roomId);
$room->update($this->fields);
$description='分店已更新';
}
} else {
if ($this->canCreate) {
$room = Room::create([
'floor' => $this->fields['floor'],
'type' => $this->fields['type'],
'name' => $this->fields['name'],
]);
$description='分店已新增';
}
}
$this->notification()->send([
'icon' => 'success',
'title' => '成功',
'description' => $description,
]);
$this->resetFields();
$this->showModal = false;
$this->dispatch('pg:eventRefresh-room-table');
}
public function deleteBranch($id)
{
if ($this->canDelect) {
Room::findOrFail($id)->delete();
$this->notification()->send([
'icon' => 'success',
'title' => '成功',
'description' => '分店已刪除',
]);
$this->dispatch('pg:eventRefresh-room-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.forms.room-form');
}
}

View File

@ -37,28 +37,39 @@ final class RoomStatusLogTable extends PowerGridComponent
public function datasource(): Builder
{
return RoomStatusLog::query()->latest();;
return RoomStatusLog::with(['room', 'branch'])->latest();
}
public function relationSearch(): array
{
return [];
return [
'branch' => ['name'],
'room' => ['name'],
'user' => ['name'],
];
}
public function fields(): PowerGridFields
{
return PowerGrid::fields()
->add('id')
->add('branch_name', function (RoomStatusLog $model) {
return $model->branch?->name;
})
->add('room_name', function (RoomStatusLog $model) {
return $model->room?->type->labelPowergridFilter().$model->room?->name;
})
->add('user_name', function (RoomStatusLog $model){
return $model->user?->name;
})
->add('is_online')
->add('status_str',function (RoomStatusLog $model){
return $model->status->labelPowergridFilter();
})
->add('started_at')
->add('ended_at')
->add('message')
->add('source')
->add('created_at');
}
@ -66,10 +77,15 @@ final class RoomStatusLogTable extends PowerGridComponent
{
$column=[];
$column[]=Column::make(__('room-status-log.id'), 'id');
$column[]=Column::make(__('room-status-log.branch'), 'branch_name');
$column[]=Column::make(__('room-status-log.room'), 'room_name');
$column[]=Column::make(__('room-status-log.user'), 'user_name');
$column[]=Column::make(__('room-status-log.is_online'), 'is_online');
$column[]=Column::make(__('room-status-log.status'), 'status_str');
$column[]=Column::make(__('room-status-log.started_at'), 'started_at');
$column[]=Column::make(__('room-status-log.ended_at'), 'ended_at');
$column[]=Column::make(__('room-status-log.message'), 'message');
$column[]=Column::make(__('room-status-log.source'), 'source');
$column[]=Column::make(__('room-status-log.created_at'), 'created_at');
return $column;
}
@ -77,6 +93,7 @@ final class RoomStatusLogTable extends PowerGridComponent
public function filters(): array
{
return [
];
}
}

View File

@ -24,8 +24,6 @@ final class RoomTable extends PowerGridComponent
{
use WithExport, WireUiActions;
public string $tableName = 'room-table';
public bool $canCreate;
public bool $canEdit;
public bool $canDownload;
public bool $canDelect;
public ?int $selectedBranchId = null;
@ -36,8 +34,6 @@ final class RoomTable extends PowerGridComponent
{
config(['livewire-powergrid.filter' => '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;
$branch = Branch::first();
@ -58,9 +54,7 @@ final class RoomTable extends PowerGridComponent
$header = PowerGrid::header()
->withoutLoading()
->showToggleColumns();
if($this->canCreate){
$header->includeViewOnTop('livewire.forms.headers.room');
}
$header->includeViewOnTop('livewire.forms.headers.room');
$actions[]=$header;
$actions[]=PowerGrid::footer()->showPerPage()->showRecordCount();
return $actions;
@ -104,17 +98,6 @@ final class RoomTable extends PowerGridComponent
->add('id')
->add('floor')
->add('type_str',function(Room $model){
if ($this->canEdit) {
return Blade::render(
'<x-select-dropdown type="occurrence" :options=$options :modelId=$modelId :fieldName=$fieldName :selected=$selected/>',
[
'options' => RoomType::options(),
'modelId' => intval($model->id),
'fieldName'=>'type',
'selected' => $model->type->value
]
);
}
return $model->type->labelPowergridFilter();
})
->add('name')
@ -131,20 +114,9 @@ final class RoomTable extends PowerGridComponent
{
$column=[];
$column[]=Column::make(__('rooms.id'), 'id');
$column[]=Column::make(__('rooms.floor'), 'floor')->sortable()->searchable()->editOnClick(
hasPermission: $this->canEdit,
dataField: 'floor',
fallback: 'N/A',
saveOnMouseOut: true
);
$column[]=Column::make(__('rooms.floor'), 'floor')->sortable()->searchable();
$column[]=Column::make(__('rooms.type'), 'type_str','room.type')->sortable()->searchable();
$column[]=Column::make(__('rooms.name'), 'name')->sortable()->searchable()
->editOnClick(
hasPermission: $this->canEdit,
dataField: 'name',
fallback: 'N/A',
saveOnMouseOut: true
);
$column[]=Column::make(__('rooms.name'), 'name')->sortable()->searchable();
$column[]=Column::make(__('rooms.isOnline'), 'is_online');
$column[]=Column::make(__('rooms.status'), 'status_str','room.status')->sortable()->searchable()->hidden(true, false);
$column[]=Column::make(__('rooms.started_at'), 'str_started_at', 'started_at')->sortable()->hidden(true, false);
@ -186,58 +158,32 @@ final class RoomTable extends PowerGridComponent
$branch = Branch::find($this->selectedBranchId);
$this->external_ip=$branch->external_ip;
}
if ($fieldName == 'type' && $this->canEdit) {
$this->noUpdated($modelId,$fieldName,$value);
}
}
#[On('onUpdatedEditable')]
public function onUpdatedEditable($id, $field, $value): void
#[On('deleteRoom')]
public function deleteRoom($rowId)
{
if($field === 'floor' && $this->canEdit){
if (!is_numeric($value)) {
$this->notification()->send([
'icon' => 'error',
'title' => '無效輸入',
'description' => '樓層必須是數字',
]);
return;
}
$this->noUpdated($id,$field,$value);
}else if ($field === 'name' && $this->canEdit) {
$this->noUpdated($id,$field,$value);
}
}
if ($this->canDelect) {
Room::findOrFail($rowId)->delete();
$this->notification()->send([
'icon' => 'success',
'title' => '成功',
'description' => '包廂已刪除',
]);
private function noUpdated($id,$field,$value){
$room = Room::find($id);
if ($room) {
$room->{$field} = $value;
$room->save(); // 明確觸發 saving
$this->dispatch('pg:eventRefresh-room-table');
}
$this->notification()->send([
'icon' => 'success',
'title' => $id.'.'.__('room.'.$field).':'.$value,
'description' => '已經寫入',
]);
}
public function actions(Room $row): array
{
$actions = [];
if ($this->canEdit) {
$actions[] =Button::add('edit')
->slot(__('rooms.edit'))
->icon('solid-pencil-square')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('forms.room-form', 'openModal', ['id' => $row->id]);
}
if($this->canDelect){
$actions[] =Button::add('delete')
->slot(__('rooms.delete'))
->icon('solid-trash')
->class('inline-flex items-center gap-1 px-3 py-1 rounded ')
->dispatchTo('forms.room-form', 'deleteRoom', ['id' => $row->id]);
->dispatch('deleteRoom', ['rowId' => $row->id]);
}
if ($row->type->value === 'pc') {
$actions[] = Button::add('room-settings')

View File

@ -27,6 +27,9 @@ class Room extends Model
/** @use HasFactory<\Database\Factories\ArtistFactory> */
use HasFactory, LogsModelActivity;
public string $log_message = 'BranchForm-add';
public string $log_source = 'manual';
protected $fillable = [
'branch_id',
'floor',
@ -55,7 +58,6 @@ class Room extends Model
'status' => \App\Enums\RoomStatus::class,
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
public function str_started_at(){

View File

@ -13,18 +13,30 @@ class RoomStatusLog extends Model
protected $fillable =
[
'branch_id',
'room_id',
'user_id',
'is_online',
'status',
'started_at',
'ended_at',
'message',
'source',
];
protected $casts = [
'is_online' => 'boolean',
'status' => \App\Enums\RoomStatus::class,
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
public function user(){
return $this->belongsTo(User::class);
}
public function branch() {
return $this->belongsTo(Branch::class);
}
public function room() {
return $this->belongsTo(Room::class);
}

View File

@ -3,6 +3,7 @@
namespace App\Observers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Arr;
use App\Models\Room;
use App\Models\RoomStatusLog;
@ -14,7 +15,17 @@ class RoomObserver
*/
public function created(Room $room): void
{
//
RoomStatusLog::create([
'branch_id' => $room->branch->id,
'room_id' => $room->id,
'user_id' => Auth::id()?? 0,
'is_online' => $room->is_online,
'status' => $room->status ?? 'error',
'started_at' => $room->started_at,
'ended_at' => $room->ended_at,
'message' => $room->log_message ?? '',
'source' => $this->getSource($room),
]);
}
/**
@ -23,12 +34,17 @@ class RoomObserver
public function updated(Room $room): void
{
// 檢查是否有變更狀態
if ($room->wasChanged('status')) {
if ($room->wasChanged()) {
RoomStatusLog::create([
'branch_id' => $room->branch->id,
'room_id' => $room->id,
'user_id' => Auth::id(), // 若是 console 或系統自動操作可能為 null
'user_id' => Auth::id() ?? 0,
'is_online' => $room->is_online,
'status' => $room->status,
'message' => 'started_at:'.$room->started_at.',ended_at:'.$room->ended_at,
'started_at' =>$room->started_at,
'ended_at' =>$room->ended_at,
'message' => $room->log_message ?? '',
'source' => $this->getSource($room),
]);
}
}
@ -38,7 +54,25 @@ class RoomObserver
*/
public function deleted(Room $room): void
{
//
$message = sprintf(
"%s:%s%s (%s:%s) 已刪除",
$room->branch->name,
$room->type->value,
$room->name,
$room->internal_ip,
$room->port
);
RoomStatusLog::create([
'branch_id' => $room->branch->id,
'room_id' => $room->id,
'user_id' => Auth::id() ?? 0,
'is_online' => $room->is_online,
'status' => $room->status,
'started_at' =>$room->started_at,
'ended_at' =>$room->ended_at,
'message' => $message,
'source' => $this->getSource($room),
]);
}
/**
@ -56,4 +90,9 @@ class RoomObserver
{
//
}
private function getSource(Room $room): string
{
return app()->runningInConsole() ? 'system' : ($room->log_source ?? 'manual');
}
}

View File

@ -13,10 +13,19 @@ return new class extends Migration
{
Schema::create('room_status_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('room_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null'); // 操作者,可為 null系統
$table->unsignedBigInteger('branch_id');
$table->unsignedBigInteger('room_id');
$table->unsignedBigInteger('user_id');
$table->tinyInteger('is_online')->default(0);
$table->enum('status', ['active', 'closed','fire', 'error', 'maintain']);
$table->timestamp('started_at')->nullable();
$table->timestamp('ended_at')->nullable();
$table->text('message')->nullable(); // 可填異常原因或操作說明
$table->enum('source', ['system', 'manual', 'api'])->default('manual');
$table->index('branch_id');
$table->index('room_id');
$table->index('user_id');
$table->index('started_at');
$table->timestamps();
});
}

View File

@ -4,10 +4,15 @@ return [
'list' => '包廂狀態紀錄',
'id' => '編號',
'branch' => '分店',
'room' => '包廂',
'user' => '操成者',
'is_online' => '在線?',
'status' => '狀態',
'started_at' => '開始於',
'ended_at' => '結束於',
'message' => '紀錄',
'source' => '來源',
'created_at' => '建立於'
];

View File

@ -2,6 +2,5 @@
<x-layouts.admin>
<x-wireui:notifications/>
<livewire:tables.room-table/>
<livewire:forms.room-form />
<livewire:modals.room-detail-modal />
</x-layouts.admin>

View File

@ -1,10 +1,2 @@
<x-admin.section-header :title="view('components.admin.branch-select', ['selectedBranchId' => $selectedBranchId,'external_ip' => $external_ip])">
@if ($canCreate)
<x-wireui:button
wire:click="$dispatchTo('forms.room-form', 'openModal')"
icon="plus"
label="{{ __('rooms.CreateNew') }}"
class="bg-blue-600 text-white"
/>
@endif
</x-admin.section-header>

View File

@ -1,21 +0,0 @@
<x-wireui:modal-card title="{{ $roomId ? __('rooms.EditRoom') : __('rooms.CreateNew') }}" blur wire:model.defer="showModal">
<div class="space-y-4">
<x-wireui:input type="number" label="{{__('rooms.floor')}}" wire:model.defer="fields.floor" />
<x-wireui:select
label="{{__('rooms.type')}}"
wire:model.defer="fields.type"
placeholder="{{__('rooms.select_type')}}"
:options="$typeOptions"
option-label="name"
option-value="value"
/>
<x-wireui:input label="{{__('rooms.name')}}" wire:model.defer="fields.name" />
</div>
<x-slot name="footer">
<div class="flex justify-between w-full">
<x-wireui:button flat label="{{__('rooms.cancel')}}" wire:click="closeModal" />
<x-wireui:button primary label="{{__('rooms.submit')}}" wire:click="save" />
</div>
</x-slot>
</x-wireui:modal-card>