[FEAT] 重構機台日誌 UI 與增加多語系支援,並整合 IoT API 核心架構
- 機台日誌:對齊 Luxury UI 規範,實作整合式佈局與分頁組件。 - 多語系:完成機台日誌繁、英、日三語系翻譯與動態處理。 - UI 規範:更新 SKILL.md 定義「標準列表 Bible」。 - 後端:完善 TenantScoped 隔離邏輯,修復儀表板死循環與 User Model 缺失。 - IoT:擴展機台、會員 Model 並建立交易、商品、狀態等核心表結構。 - 基礎設施:設置台北時區與 Docker 環境變數同步。
This commit is contained in:
@@ -29,29 +29,32 @@
|
||||
}
|
||||
}">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Account Management') }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Manage administrative and tenant accounts') }}</p>
|
||||
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 mt-1 uppercase tracking-[0.2em]">{{ __('Manage administrative and tenant accounts') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button @click="openCreateModal()" class="btn-luxury-primary">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||
<span>{{ __('Add Account') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openCreateModal()" class="btn-luxury-primary">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||
<span>{{ __('Add Account') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filters & Search -->
|
||||
<!-- Accounts Content (Integrated Card) -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<form action="{{ route('admin.permission.accounts') }}" method="GET" class="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||
<div class="flex flex-col md:flex-row items-start md:items-center gap-4">
|
||||
<div class="relative group">
|
||||
<!-- Filters & Search -->
|
||||
<form action="{{ route('admin.permission.accounts') }}" method="GET" class="mb-10">
|
||||
<div class="flex flex-col md:flex-row items-start md:items-center gap-4 w-full md:w-auto">
|
||||
<div class="relative group w-full md:w-80">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" name="search" value="{{ request('search') }}" class="py-3 pl-12 pr-6 block w-full md:w-80 luxury-input" placeholder="{{ __('Search users...') }}">
|
||||
<input type="text" name="search" value="{{ request('search') }}" class="py-2.5 pl-12 pr-6 block w-full luxury-input" placeholder="{{ __('Search users...') }}">
|
||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||
</div>
|
||||
|
||||
@@ -63,42 +66,37 @@
|
||||
@endforeach
|
||||
</select>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- 移除了冗餘的 Filter 按鈕,下拉選單具備自動提交功能 -->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="overflow-x-auto mt-8">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/30">
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">{{ __('User Info') }}</th>
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
|
||||
@endif
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Role') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('User Info') }}</th>
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
|
||||
@endif
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Role') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/50">
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@forelse($users as $user)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<div class="w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 overflow-hidden group-hover:bg-cyan-500/10 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-all duration-500">
|
||||
@if($user->avatar)
|
||||
<img src="{{ Storage::url($user->avatar) }}" class="w-full h-full object-cover">
|
||||
@else
|
||||
<span class="text-sm font-black">{{ substr($user->name, 0, 1) }}</span>
|
||||
<span class="text-xs font-black">{{ substr($user->name, 0, 1) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $user->name }}</span>
|
||||
<span class="text-[11px] font-bold text-slate-400 dark:text-slate-500 mt-0.5 tracking-[0.15em]">{{ $user->username }} @if($user->email) • {{ $user->email }} @endif</span>
|
||||
<span class="text-[11px] font-bold text-slate-400 dark:text-slate-500 mt-0.5 tracking-tight">{{ $user->username }} @if($user->email) • {{ $user->email }} @endif</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -107,37 +105,38 @@
|
||||
@if($user->company)
|
||||
<span class="text-xs font-bold text-slate-600 dark:text-slate-300 tracking-tight">{{ $user->company->name }}</span>
|
||||
@else
|
||||
<span class="text-xs font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">{{ __('SYSTEM') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg text-[10px] font-black bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">{{ __('SYSTEM') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
@endif
|
||||
<td class="px-6 py-6 text-center">
|
||||
@foreach($user->roles as $role)
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-lg text-[11px] font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-widest">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-widest">
|
||||
{{ $role->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
@if($user->status)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[11px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-wider uppercase">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-wider uppercase">
|
||||
<span class="size-1.5 rounded-full bg-emerald-500 mr-2 animate-pulse"></span>
|
||||
{{ __('Active') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[11px] font-black bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-500 border border-slate-200 dark:border-slate-700 tracking-wider uppercase">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-500 border border-slate-200 dark:border-slate-700 tracking-wider uppercase">
|
||||
{{ __('Disabled') }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<button @click="openEditModal({{ json_encode($user) }})" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20">
|
||||
<button @click='openEditModal(@json($user))' class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/10 transition-all border border-transparent hover:border-cyan-500/20 shadow-sm">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
|
||||
</button>
|
||||
<form action="{{ route('admin.permission.accounts.destroy', $user->id) }}" method="POST" onsubmit="return confirm('{{ addslashes(__('Are you sure you want to delete this account?')) }}')">
|
||||
<form action="{{ route('admin.permission.accounts.destroy', $user->id) }}" method="POST" onsubmit="return confirm('{{ __('Are you sure you want to delete this account?') }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 transition-all border border-transparent hover:border-rose-500/20">
|
||||
<button type="submit" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all border border-transparent hover:border-rose-500/20 shadow-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
@@ -147,7 +146,10 @@
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="{{ auth()->user()->isSystemAdmin() ? 5 : 4 }}" class="px-6 py-24 text-center">
|
||||
<p class="text-slate-400 font-bold">{{ __('No users found') }}</p>
|
||||
<div class="flex flex-col items-center gap-3 opacity-20">
|
||||
<svg class="size-16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2m16-10a4 4 0 11-8 0 4 4 0 018 0zM23 21v-2a4 4 0 00-3-3.87m-4-12a4 4 0 010 7.75"/></svg>
|
||||
<p class="text-slate-400 font-extrabold tracking-widest uppercase text-xs">{{ __('No accounts found') }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('所有機台日誌') }}
|
||||
</h2>
|
||||
</div>
|
||||
@endsection
|
||||
@section('title', __('Machine Logs'))
|
||||
|
||||
@section('content')
|
||||
<div class="py-12">
|
||||
<div class="sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
<!-- 篩選器 -->
|
||||
<div class="luxury-card rounded-2xl p-6 animate-luxury-in">
|
||||
<div class="flex items-center gap-x-2 mb-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-slate-400">
|
||||
條件篩選
|
||||
</p>
|
||||
</div>
|
||||
<form method="GET" action="{{ route('admin.machines.logs') }}" class="flex flex-wrap gap-4 items-end">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">機台</label>
|
||||
<select name="machine_id" class="block w-48 rounded-md border-slate-300 shadow-sm focus:border-cyan-500 focus:ring focus:ring-cyan-500/20 text-sm dark:bg-slate-800 dark:border-slate-700 dark:text-white dark:focus:border-cyan-500">
|
||||
<option value="">全部機台</option>
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Logs') }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Monitor events and system activity across your vending fleet.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Machine Logs Content (Integrated Card - Same as Roles) -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Toolbar (Integrated Filters) -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10">
|
||||
<form method="GET" action="{{ route('admin.machines.logs') }}" class="flex flex-wrap items-center gap-4 group">
|
||||
<div class="space-y-1">
|
||||
<label class="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{{ __('Machine') }}</label>
|
||||
<select name="machine_id" class="luxury-select text-xs h-9 py-0" onchange="this.form.submit()">
|
||||
<option value="">{{ __('All Machines') }}</option>
|
||||
@foreach($machines as $machine)
|
||||
<option value="{{ $machine->id }}" {{ request('machine_id') == $machine->id ? 'selected' : '' }}>
|
||||
{{ $machine->name }}
|
||||
@@ -31,97 +28,102 @@
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">層級</label>
|
||||
<select name="level" class="block w-32 rounded-md border-slate-300 shadow-sm focus:border-cyan-500 focus:ring focus:ring-cyan-500/20 text-sm dark:bg-slate-800 dark:border-slate-700 dark:text-white dark:focus:border-cyan-500">
|
||||
<option value="">全部層級</option>
|
||||
<option value="info" {{ request('level') == 'info' ? 'selected' : '' }}>Info</option>
|
||||
<option value="warning" {{ request('level') == 'warning' ? 'selected' : '' }}>Warning</option>
|
||||
<option value="error" {{ request('level') == 'error' ? 'selected' : '' }}>Error</option>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">{{ __('Level') }}</label>
|
||||
<select name="level" class="luxury-select text-xs h-9 py-0" onchange="this.form.submit()">
|
||||
<option value="">{{ __('All Levels') }}</option>
|
||||
<option value="info" {{ request('level') == 'info' ? 'selected' : '' }}>{{ __('Info') }}</option>
|
||||
<option value="warning" {{ request('level') == 'warning' ? 'selected' : '' }}>{{ __('Warning') }}</option>
|
||||
<option value="error" {{ request('level') == 'error' ? 'selected' : '' }}>{{ __('Error') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">筆數</label>
|
||||
<select name="per_page" class="h-9 text-[11px] font-black bg-slate-50 dark:bg-slate-800 border-slate-200 dark:border-slate-700 rounded-lg focus:ring-cyan-500/20 focus:border-cyan-500 transition-all">
|
||||
@foreach([20, 50, 100, 200] as $size)
|
||||
<option value="{{ $size }}" {{ request('per_page', 20) == $size ? 'selected' : '' }}>{{ $size }} 筆</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="submit" class="btn-luxury-primary">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<span>篩選</span>
|
||||
|
||||
<div class="flex items-end gap-2 mt-5">
|
||||
<button type="submit" class="p-2 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 transition-colors border border-slate-200 dark:border-slate-700">
|
||||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
</button>
|
||||
<a href="{{ route('admin.machines.logs') }}" class="btn-luxury-ghost">
|
||||
重設
|
||||
<a href="{{ route('admin.machines.logs') }}" class="p-2 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 transition-colors border border-slate-200 dark:border-slate-700">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 日誌清單 -->
|
||||
<div class="luxury-card rounded-2xl p-6 animate-luxury-in overflow-hidden" style="animation-delay: 100ms">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-lg font-bold text-slate-800 dark:text-white">系統日誌清單</h2>
|
||||
<span class="text-xs text-slate-400">所有時間為系統時區</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-[#0f172a]">
|
||||
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700/50 font-mono text-xs">
|
||||
<thead class="bg-slate-50 dark:bg-slate-800/80">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">時間</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">機台</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">層級</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600 dark:text-slate-300">訊息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-700/50 bg-white dark:bg-transparent">
|
||||
@forelse ($logs as $log)
|
||||
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
|
||||
<td class="px-4 py-3 text-slate-500 dark:text-slate-400 whitespace-nowrap">{{ $log->created_at->format('Y-m-d H:i:s') }}</td>
|
||||
<td class="px-4 py-3 text-slate-700 dark:text-slate-300 whitespace-nowrap">
|
||||
<a href="{{ route('admin.machines.show', $log->machine_id) }}" class="hover:text-cyan-600 dark:hover:text-cyan-400 underline decoration-slate-300 dark:decoration-slate-600 underline-offset-2 transition-colors">
|
||||
{{ $log->machine->name ?? '未知機台' }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
@php
|
||||
$levelClasses = [
|
||||
'info' => 'text-cyan-600 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-500/20 border-cyan-200 dark:border-cyan-500/30',
|
||||
'warning' => 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/20 border-amber-200 dark:border-amber-500/30 font-semibold',
|
||||
'error' => 'text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-500/20 border-rose-200 dark:border-rose-500/30 font-bold',
|
||||
];
|
||||
@endphp
|
||||
<span class="px-2 py-0.5 rounded border {{ $levelClasses[$log->level] ?? 'text-slate-500 bg-slate-100 border-slate-200 dark:text-slate-300 dark:bg-slate-800 dark:border-slate-700' }}">
|
||||
{{ strtoupper($log->level) }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Timestamp') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Machine') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Level') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Message Content') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@forelse ($logs as $log)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6 transition-colors">
|
||||
<div class="text-[13px] font-bold font-display tracking-widest text-slate-600 dark:text-slate-300">
|
||||
{{ $log->created_at->format('Y-m-d') }}
|
||||
</div>
|
||||
<div class="text-[11px] font-bold text-slate-400 dark:text-slate-500 tracking-wider mt-0.5 uppercase">
|
||||
{{ $log->created_at->format('H:i:s') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 transition-colors">
|
||||
<a href="{{ route('admin.machines.show', $log->machine_id) }}" class="inline-flex items-center gap-2 group/link">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover/link:text-cyan-500 transition-colors">
|
||||
{{ $log->machine->name ?? __('Unknown') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-700 dark:text-slate-200 max-w-xl break-words">
|
||||
<svg class="w-3.5 h-3.5 text-slate-300 dark:text-slate-600 opacity-0 group-hover/link:opacity-100 transition-all -translate-x-2 group-hover/link:translate-x-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-6 py-6 transition-colors">
|
||||
@php
|
||||
$badgeStyles = [
|
||||
'info' => 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20',
|
||||
'warning' => 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20',
|
||||
'error' => 'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20',
|
||||
];
|
||||
$currentStyle = $badgeStyles[$log->level] ?? 'bg-slate-500/10 text-slate-600 border-slate-500/20';
|
||||
@endphp
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-lg border text-[11px] font-black uppercase tracking-wider {{ $currentStyle }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-current mr-2 animate-pulse"></span>
|
||||
{{ __(ucfirst($log->level)) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 transition-colors">
|
||||
<p class="text-[14px] font-medium text-slate-700 dark:text-slate-200 leading-relaxed max-w-xl">
|
||||
{{ $log->message }}
|
||||
@if($log->context)
|
||||
<div class="text-[10px] text-slate-500 dark:text-slate-400 mt-2 max-h-24 overflow-y-auto bg-slate-100 dark:bg-[#0f172a] p-2 rounded-lg border border-slate-200 dark:border-slate-800/50 shadow-inner">
|
||||
{{ json_encode($log->context, JSON_UNESCAPED_UNICODE) }}
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-12 text-center text-slate-500 dark:text-slate-400 italic">暫無相關日誌</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($logs->hasPages())
|
||||
<div class="mt-6">
|
||||
{{ $logs->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
@endif
|
||||
</p>
|
||||
@if($log->context)
|
||||
<div class="mt-3 p-4 rounded-xl bg-slate-50/50 dark:bg-[#0f172a]/50 border border-slate-100 dark:border-slate-800/50 group-hover:bg-white dark:group-hover:bg-[#0f172a] transition-colors">
|
||||
<pre class="text-[10px] font-bold text-slate-400 dark:text-slate-500 whitespace-pre-wrap break-all">{{ json_encode($log->context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) }}</pre>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-24 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="p-4 rounded-full bg-slate-50 dark:bg-slate-800/50 mb-4 border border-slate-100 dark:border-slate-800/50">
|
||||
<svg class="w-8 h-8 text-slate-300 dark:text-slate-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 7v10c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V7c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2z"/><path d="M12 11l4-4"/><path d="M8 15l4-4"/></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('No matching logs found') }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t border-slate-100 dark:border-slate-800 pt-6">
|
||||
{{ $logs->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
@@ -29,9 +29,10 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="luxury-card rounded-3xl p-6 mb-6 animate-luxury-in" style="animation-delay: 100ms">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||
<!-- Roles Content (Integrated Card) -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Toolbar -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10">
|
||||
<form action="{{ route('admin.permission.roles') }}" method="GET" class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -43,30 +44,27 @@
|
||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roles List -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="border-b border-slate-100 dark:border-slate-700">
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Role Name') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Type') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Permissions') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest text-center">{{ __('Users') }}</th>
|
||||
<th class="px-6 py-5 text-sm font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest text-right">{{ __('Actions') }}</th>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Role Name') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Type') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Permissions') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Users') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800">
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@forelse($roles as $role)
|
||||
<tr class="hover:bg-slate-50/80 dark:hover:bg-slate-800/50 transition-colors group">
|
||||
<td class="px-6 py-5">
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-xl flex items-center justify-center bg-slate-50 dark:bg-slate-800 text-slate-400 group-hover:bg-cyan-500/10 group-hover:text-cyan-500 transition-all duration-300">
|
||||
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500/10 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-all duration-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-200">{{ $role->name }}</span>
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $role->name }}</span>
|
||||
@if($role->is_system)
|
||||
<span class="p-1.5 bg-cyan-50 dark:bg-cyan-900/30 text-cyan-600 dark:text-cyan-400 rounded-lg tooltip" title="{{ __('System Role') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
||||
@@ -74,42 +72,42 @@
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-5">
|
||||
<td class="px-6 py-6">
|
||||
@if($role->is_system)
|
||||
<span class="px-2.5 py-1 text-[11px] font-black uppercase tracking-tight bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400 rounded-full">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-widest">
|
||||
{{ __('System') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="px-2.5 py-1 text-[11px] font-black uppercase tracking-tight bg-emerald-50 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 rounded-full">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-wider uppercase">
|
||||
{{ __('Custom') }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-5">
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex flex-wrap gap-1 max-w-xs">
|
||||
@forelse($role->permissions->take(5) as $permission)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-500 rounded uppercase font-bold">{{ __(str_replace('menu.', '', $permission->name)) }}</span>
|
||||
@forelse($role->permissions->take(6) as $permission)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">{{ __(str_replace('menu.', '', $permission->name)) }}</span>
|
||||
@empty
|
||||
<span class="text-xs text-slate-400 italic">{{ __('No permissions') }}</span>
|
||||
<span class="text-[11px] font-bold text-slate-400 italic tracking-tight">{{ __('No permissions') }}</span>
|
||||
@endforelse
|
||||
@if($role->permissions->count() > 5)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-400 rounded uppercase font-bold">+{{ $role->permissions->count() - 5 }}</span>
|
||||
@if($role->permissions->count() > 6)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">+{{ $role->permissions->count() - 6 }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-center">
|
||||
<td class="px-6 py-6 text-center">
|
||||
<span class="text-sm font-black text-slate-600 dark:text-slate-400">{{ $role->users()->count() }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-5 text-right">
|
||||
<div class="flex items-center justify-end gap-2 text-slate-400">
|
||||
<button @click="openModal(true, '{{ $role->id }}', '{{ $role->name }}', {{ $role->permissions->pluck('name') }})" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 hover:text-cyan-500 hover:bg-cyan-500/10 transition-all border border-transparent hover:border-cyan-500/20 shadow-sm tooltip" title="{{ __('Edit') }}">
|
||||
<td class="px-6 py-6 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button @click="openModal(true, '{{ $role->id }}', '{{ $role->name }}', {{ $role->permissions->pluck('name') }})" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/10 transition-all border border-transparent hover:border-cyan-500/20 shadow-sm tooltip" title="{{ __('Edit') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
|
||||
</button>
|
||||
@if(!$role->is_system)
|
||||
<form action="{{ route('admin.permission.roles.destroy', $role->id) }}" method="POST" @submit.prevent="if(confirm('{{ __('Are you sure you want to delete this role?') }}')) $el.submit()" class="inline text-slate-400">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 hover:text-rose-500 hover:bg-rose-500/10 transition-all border border-transparent hover:border-rose-500/20 shadow-sm tooltip" title="{{ __('Delete') }}">
|
||||
<button type="submit" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all border border-transparent hover:border-rose-500/20 shadow-sm tooltip" title="{{ __('Delete') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
// 3. 處理最後一個動作/頁面
|
||||
if ($lastSegment !== 'index') {
|
||||
$pageLabel = match($lastSegment) {
|
||||
'edit' => __('Edit'),
|
||||
'edit' => str_starts_with($routeName, 'profile') ? null : __('Edit'),
|
||||
'create' => __('Create'),
|
||||
'show' => __('Detail'),
|
||||
'logs' => __('Machine Logs'),
|
||||
|
||||
Reference in New Issue
Block a user