[FEAT] 優化帳號管理授權顯示邏輯與 UI 樣式一致性
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 59s

This commit is contained in:
2026-03-23 17:16:26 +08:00
parent 72812f9b0b
commit 38770b080b
26 changed files with 1265 additions and 444 deletions

View File

@@ -62,7 +62,7 @@
<!-- Left: Basic info & Hardware -->
<div class="lg:col-span-2 space-y-6">
<!-- Basic Information -->
<div class="luxury-card rounded-3xl p-7 animate-luxury-in">
<div class="luxury-card rounded-3xl p-7 animate-luxury-in relative z-20">
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -87,17 +87,39 @@
</div>
<div>
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Model') }}</label>
<select name="machine_model_id" class="luxury-select w-full" required>
<x-searchable-select
name="machine_model_id"
:selected="old('machine_model_id', $machine->machine_model_id)"
required
>
@foreach($models as $model)
<option value="{{ $model->id }}" {{ old('machine_model_id', $machine->machine_model_id) == $model->id ? 'selected' : '' }}>{{ $model->name }}</option>
@endforeach
</select>
</x-searchable-select>
</div>
@if(auth()->user()->isSystemAdmin())
<div>
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Company') }}</label>
<x-searchable-select
name="company_id"
:selected="old('company_id', $machine->company_id)"
:placeholder="__('No Company')"
>
@foreach($companies as $company)
<option value="{{ $company->id }}" {{ old('company_id', $machine->company_id) == $company->id ? 'selected' : '' }}
data-title="{{ $company->name }}{{ $company->code ? ' (' . $company->code . ')' : '' }}">
{{ $company->name }}{{ $company->code ? ' (' . $company->code . ')' : '' }}
</option>
@endforeach
</x-searchable-select>
@error('company_id') <p class="mt-1 text-xs text-rose-500 font-bold uppercase tracking-wider">{{ $message }}</p> @enderror
</div>
@endif
</div>
</div>
<!-- Operational Parameters -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative z-10" style="animation-delay: 50ms">
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-amber-500/10 flex items-center justify-center text-amber-500">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -176,7 +198,7 @@
<!-- Right: System & Payment -->
<div class="space-y-8">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative z-20" style="animation-delay: 200ms">
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -189,26 +211,34 @@
<div class="space-y-6">
<div>
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Payment Config') }}</label>
<select name="payment_config_id" class="luxury-select w-full">
<option value="">{{ __('Not Used') }}</option>
<x-searchable-select
name="payment_config_id"
:selected="old('payment_config_id', $machine->payment_config_id)"
:placeholder="__('Not Used')"
:hasSearch="false"
>
@foreach($paymentConfigs as $config)
<option value="{{ $config->id }}" {{ $machine->payment_config_id == $config->id ? 'selected' : '' }}>{{ $config->name }}</option>
@endforeach
</select>
</x-searchable-select>
</div>
<div>
<label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Invoice Status') }}</label>
<select name="invoice_status" class="luxury-select w-full">
<x-searchable-select
name="invoice_status"
:selected="old('invoice_status', $machine->invoice_status)"
:hasSearch="false"
>
<option value="0" {{ $machine->invoice_status == 0 ? 'selected' : '' }}>{{ __('No Invoice') }}</option>
<option value="1" {{ $machine->invoice_status == 1 ? 'selected' : '' }}>{{ __('Default Donate') }}</option>
<option value="2" {{ $machine->invoice_status == 2 ? 'selected' : '' }}>{{ __('Default Not Donate') }}</option>
</select>
</x-searchable-select>
</div>
</div>
</div>
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 300ms">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative z-10" style="animation-delay: 300ms">
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@@ -391,16 +391,17 @@
<input type="text" name="serial_no" required class="luxury-input w-full"
placeholder="{{ __('Enter serial number') }}">
</div>
<div>
<div class="relative z-20">
<label
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
__('Owner') }}</label>
<select name="company_id" required class="luxury-select w-full">
<option value="">{{ __('Select Owner') }}</option>
<x-searchable-select name="company_id" required :placeholder="__('Select Owner')">
@foreach($companies as $company)
<option value="{{ $company->id }}">{{ $company->name }}</option>
<option value="{{ $company->id }}" data-title="{{ $company->name }}{{ $company->code ? ' (' . $company->code . ')' : '' }}">
{{ $company->name }}{{ $company->code ? ' (' . $company->code . ')' : '' }}
</option>
@endforeach
</select>
</x-searchable-select>
</div>
<div>
<label
@@ -409,16 +410,15 @@
<input type="text" name="location" class="luxury-input w-full"
placeholder="{{ __('Enter machine location') }}">
</div>
<div>
<div class="relative z-10">
<label
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
__('Model') }}</label>
<select name="machine_model_id" required class="luxury-select w-full">
<option value="">{{ __('Select Model') }}</option>
<x-searchable-select name="machine_model_id" required :placeholder="__('Select Model')">
@foreach($models as $model)
<option value="{{ $model->id }}">{{ $model->name }}</option>
@endforeach
</select>
</x-searchable-select>
</div>
<div>
<label

View File

@@ -43,7 +43,7 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Column: Primary Info -->
<div class="lg:col-span-12 space-y-6">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative z-20">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Configuration Name') }} <span class="text-rose-500">*</span></label>
@@ -51,12 +51,16 @@
</div>
<div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Belongs To Company') }} <span class="text-rose-500">*</span></label>
<select name="company_id" required class="luxury-select w-full">
<option value="">{{ __('Select Company') }}</option>
<x-searchable-select
name="company_id"
required
:selected="old('company_id')"
:placeholder="__('Select Company')"
>
@foreach($companies as $company)
<option value="{{ $company->id }}">{{ $company->name }}</option>
<option value="{{ $company->id }}" data-title="{{ $company->name }}{{ $company->code ? ' ('.$company->code.')' : '' }}">{{ $company->name }}{{ $company->code ? ' ('.$company->code.')' : '' }}</option>
@endforeach
</select>
</x-searchable-select>
</div>
</div>
</div>

View File

@@ -44,7 +44,7 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Column: Primary Info -->
<div class="lg:col-span-12 space-y-6">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative z-20">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Configuration Name') }} <span class="text-rose-500">*</span></label>
@@ -52,12 +52,16 @@
</div>
<div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Belongs To Company') }} <span class="text-rose-500">*</span></label>
<select name="company_id" required class="luxury-select w-full">
<option value="">{{ __('Select Company') }}</option>
<x-searchable-select
name="company_id"
required
:selected="old('company_id', $paymentConfig->company_id)"
:placeholder="__('Select Company')"
>
@foreach($companies as $company)
<option value="{{ $company->id }}" {{ $paymentConfig->company_id == $company->id ? 'selected' : '' }}>{{ $company->name }}</option>
<option value="{{ $company->id }}" data-title="{{ $company->name }}{{ $company->code ? ' ('.$company->code.')' : '' }}">{{ $company->name }}{{ $company->code ? ' ('.$company->code.')' : '' }}</option>
@endforeach
</select>
</x-searchable-select>
</div>
</div>
</div>

View File

@@ -236,7 +236,7 @@
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="inline-block px-8 py-10 overflow-hidden text-left align-bottom transition-all transform luxury-card rounded-3xl dark:bg-slate-900 border-slate-200/50 dark:border-slate-700/50 shadow-2xl sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
class="inline-block px-8 py-10 overflow-visible text-left align-bottom transition-all transform luxury-card rounded-3xl dark:bg-slate-900 border-slate-200/50 dark:border-slate-700/50 shadow-2xl sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
<div class="flex justify-between items-center mb-8">
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight"
@@ -296,7 +296,7 @@
</div>
<!-- Admin Account Section (Account Creation) - Only show when creating -->
<div x-show="!editing" class="space-y-6 pt-6 border-t border-slate-100 dark:border-slate-800">
<div x-show="!editing" class="space-y-6 pt-6 border-t border-slate-100 dark:border-slate-800 relative z-20">
<div class="flex items-center gap-3">
<div class="h-6 w-1 bg-emerald-500 rounded-full"></div>
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">
@@ -324,21 +324,24 @@
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Admin Name') }}</label>
<input type="text" name="admin_name" class="luxury-input w-full" placeholder="{{ __('Admin display name') }}">
</div>
<div class="space-y-2">
<div class="space-y-2 relative z-[30]">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Initial Role') }}</label>
<select name="admin_role" class="luxury-select w-full">
<x-searchable-select
name="admin_role"
:hasSearch="false"
>
@foreach($template_roles as $role)
<option value="{{ $role->name }}" {{ $role->name == '客戶管理員角色模板' ? 'selected' : '' }}>
{{ $role->name }}
</option>
@endforeach
</select>
</x-searchable-select>
</div>
</div>
</div>
<!-- Contact Section -->
<div class="space-y-6 pt-6 border-t border-slate-100 dark:border-slate-800">
<div class="space-y-6 pt-6 border-t border-slate-100 dark:border-slate-800 relative z-10">
<div class="flex items-center gap-3">
<div class="h-6 w-1 bg-amber-500 rounded-full"></div>
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">{{
@@ -361,13 +364,25 @@
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<div class="space-y-2 relative z-[20]">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{
__('Status') }}</label>
<select name="status" x-model="currentCompany.status" class="luxury-select">
<x-searchable-select
name="status"
x-model="currentCompany.status"
:hasSearch="false"
x-init="$watch('currentCompany.status', (value) => {
$nextTick(() => {
const inst = HSSelect.getInstance($el);
if (inst) {
inst.setValue(String(value));
}
});
})"
>
<option value="1">{{ __('Active') }}</option>
<option value="0">{{ __('Disabled') }}</option>
</select>
</x-searchable-select>
</div>
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +0,0 @@
@extends('layouts.admin')
@section('content')
<div class="container mx-auto px-6 py-8">
<h3 class="text-gray-900 dark:text-gray-300 text-3xl font-medium">{{ __('Edit Machine') }}</h3>
<div class="mt-8">
<form action="{{ route('admin.machines.update', $machine) }}" method="POST"
class="bg-white dark:bg-gray-800 rounded-lg shadow-xl overflow-hidden p-6 space-y-6">
@csrf
@method('PUT')
<div>
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-400">{{ __('Machine
Name') }}</label>
<input type="text" name="name" id="name" value="{{ old('name', $machine->name) }}"
class="mt-1 block w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 text-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
required>
@error('name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="location" class="block text-sm font-medium text-gray-700 dark:text-gray-400">{{
__('Location') }}</label>
<input type="text" name="location" id="location" value="{{ old('location', $machine->location) }}"
class="mt-1 block w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 text-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
@error('location') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-400">{{ __('Status')
}}</label>
<select name="status" id="status"
class="mt-1 block w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 text-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="offline" {{ $machine->status == 'offline' ? 'selected' : '' }}>{{ __('Offline') }}
</option>
<option value="online" {{ $machine->status == 'online' ? 'selected' : '' }}>{{ __('Connecting...')
}}</option>
<option value="error" {{ $machine->status == 'error' ? 'selected' : '' }}>{{ __('Error') }}</option>
</select>
@error('status') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="temperature" class="block text-sm font-medium text-gray-700 dark:text-gray-400">{{
__('Temperature') }} (°C)</label>
<input type="number" step="0.1" name="temperature" id="temperature"
value="{{ old('temperature', $machine->temperature) }}"
class="mt-1 block w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 text-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
@error('temperature') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="firmware_version" class="block text-sm font-medium text-gray-700 dark:text-gray-400">{{
__('Firmware Version') }}</label>
<input type="text" name="firmware_version" id="firmware_version"
value="{{ old('firmware_version', $machine->firmware_version) }}"
class="mt-1 block w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 text-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
@error('firmware_version') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div class="flex justify-end">
<a href="{{ route('admin.machines.index') }}"
class="bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-bold py-2 px-4 rounded mr-2">{{
__('Cancel') }}</a>
<button type="submit" class="btn-luxury-primary">{{ __('Update') }}</button>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -3,6 +3,16 @@
@section('content')
<div class="space-y-6" x-data="{
selectedPermissions: {{ json_encode($role->permissions->pluck('name')->toArray()) }},
@php
$availablePermissions = $all_permissions->flatten();
if (!$role->is_system) {
$availablePermissions = $availablePermissions->filter(function($p) {
return !str_starts_with($p->name, 'menu.basic') &&
!str_starts_with($p->name, 'menu.permissions');
});
}
@endphp
availableCount: {{ $availablePermissions->count() }},
activeCategory: '',
toggleCategory(category, permissions) {
const allSelected = permissions.every(p => this.selectedPermissions.includes(p));
@@ -20,6 +30,32 @@
const selectedCount = permissions.filter(p => this.selectedPermissions.includes(p)).length;
return selectedCount > 0 && selectedCount < permissions.length;
},
toggleParent(parentName, childrenNames) {
const isSelected = this.selectedPermissions.includes(parentName);
if (isSelected) {
// 取消父項目與所有子項目
this.selectedPermissions = this.selectedPermissions.filter(p => p !== parentName && !childrenNames.includes(p));
} else {
// 勾選父項目
if (!this.selectedPermissions.includes(parentName)) {
this.selectedPermissions.push(parentName);
}
// 勾選所有尚未勾選的子項目
childrenNames.forEach(p => {
if (!this.selectedPermissions.includes(p)) this.selectedPermissions.push(p);
});
}
},
isParentSelected(parentName, childrenNames) {
return this.selectedPermissions.includes(parentName) &&
childrenNames.every(p => this.selectedPermissions.includes(p));
},
isParentPartial(parentName, childrenNames) {
const hasParent = this.selectedPermissions.includes(parentName);
const selectedChildrenCount = childrenNames.filter(p => this.selectedPermissions.includes(p)).length;
return (hasParent && selectedChildrenCount < childrenNames.length) ||
(!hasParent && selectedChildrenCount > 0);
},
scrollTo(id) {
const el = document.getElementById(id);
if (el) {
@@ -140,7 +176,7 @@
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Total Selected') }}</span>
<div class="flex items-baseline gap-1">
<span class="text-3xl font-display font-black text-cyan-500" x-text="selectedPermissions.length">0</span>
<span class="text-xs font-bold text-slate-400">/ {{ $all_permissions->flatten()->count() }}</span>
<span class="text-xs font-bold text-slate-400">/ <span x-text="availableCount"></span></span>
</div>
</div>
<div class="w-12 h-12 rounded-2xl bg-emerald-500/5 flex items-center justify-center text-emerald-500">
@@ -156,6 +192,13 @@
<div class="flex-1 w-full space-y-12">
@foreach($all_permissions as $group => $permissions)
@php
// 如果非系統角色,過濾掉敏感權限
if (!$role->is_system && $group === 'menu') {
$permissions = $permissions->filter(function($p) {
return !str_starts_with($p->name, 'menu.basic') &&
!str_starts_with($p->name, 'menu.permissions');
});
}
$groupId = 'group-' . $group;
$groupPermissions = $permissions->pluck('name')->toArray();
@endphp
@@ -218,9 +261,11 @@
<div class="flex items-center pr-1">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="permissions[]" value="{{ $parent->name }}"
x-model="selectedPermissions"
:checked="selectedPermissions.includes('{{ $parent->name }}')"
@change="toggleParent('{{ $parent->name }}', {{ json_encode($children->pluck('name')->toArray()) }})"
class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:outline-none peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500 transition-all duration-200 shadow-inner"></div>
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:outline-none peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500 transition-all duration-200 shadow-inner"
:class="isParentPartial('{{ $parent->name }}', {{ json_encode($children->pluck('name')->toArray()) }}) ? 'ring-2 ring-cyan-500/50' : ''"></div>
</label>
</div>
</div>
@@ -237,9 +282,9 @@
<div class="relative flex items-center flex-shrink-0">
<input type="checkbox"
name="permissions[]"
value="{{ $child->id }}"
class="w-4 h-4 rounded border-2 border-slate-300 dark:border-slate-700 text-cyan-500 focus:ring-cyan-500/20 transition-all cursor-pointer accent-cyan-500"
{{ $role->hasPermissionTo($child->name) ? 'checked' : '' }}>
value="{{ $child->name }}"
x-model="selectedPermissions"
class="w-4 h-4 rounded border-2 border-slate-300 dark:border-slate-700 text-cyan-500 focus:ring-cyan-500/20 transition-all cursor-pointer accent-cyan-500">
</div>
</label>
@endforeach

View File

@@ -66,13 +66,16 @@
@if(auth()->user()->isSystemAdmin())
<div class="relative">
<select name="company_id" onchange="this.form.submit()" class="py-2.5 pl-4 pr-10 block w-full md:w-60 luxury-input">
<option value="">{{ __('All Affiliations') }}</option>
<option value="system" {{ request('company_id') === 'system' ? 'selected' : '' }}>{{ __('System Level') }}</option>
@foreach($companies as $company)
<option value="{{ $company->id }}" {{ request('company_id') == $company->id ? 'selected' : '' }}>{{ $company->name }}</option>
@endforeach
</select>
<x-searchable-select
name="company_id"
:options="$companies"
:selected="request('company_id')"
placeholder="{{ __('All Affiliations') }}"
class="w-full md:w-auto min-w-[280px]"
onchange="this.form.submit()"
>
<option value="system" {{ request('company_id') === 'system' ? 'selected' : '' }} data-title="{{ __('System Level') }}">{{ __('System Level') }}</option>
</x-searchable-select>
</div>
@endif
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
@@ -108,11 +111,11 @@
</td>
<td class="px-6 py-6">
@if($role->is_system)
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-800 uppercase tracking-wider">
<span class="px-2.5 py-1 rounded-lg text-xs font-bold bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">
{{ __('System Level') }}
</span>
@else
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 tracking-wider">
<span class="text-xs font-bold text-slate-500 dark:text-slate-400 tracking-widest uppercase">
{{ $role->company->name ?? __('Company Level') }}
</span>
@endif

View File

@@ -0,0 +1,50 @@
@props([
'name' => null,
'options' => [],
'selected' => null,
'placeholder' => null,
'id' => null,
'hasSearch' => true,
])
@php
$id = $id ?? $name ?? 'select-' . uniqid();
$options = is_iterable($options) ? $options : [];
// Skill Standard: Use " " for empty/all options to bypass Preline hiding while staying 'blank'
$isEmptySelected = (is_null($selected) || (string)$selected === '' || (string)$selected === ' ');
$config = [
"hasSearch" => (bool)$hasSearch,
"searchPlaceholder" => $placeholder ?: __('Search...'),
"isHidePlaceholder" => false,
"searchClasses" => "block w-[calc(100%-16px)] mx-2 py-2 px-3 text-sm border-slate-200 dark:border-white/10 rounded-lg focus:border-cyan-500 focus:ring-cyan-500 bg-slate-50 dark:bg-slate-900/50 dark:text-slate-200 placeholder:text-slate-400 dark:placeholder:text-slate-500",
"searchWrapperClasses" => "sticky top-0 bg-white/95 dark:bg-slate-900/95 backdrop-blur-md p-2 z-10",
"toggleClasses" => "hs-select-toggle luxury-select-toggle",
"dropdownClasses" => "hs-select-menu w-full bg-white/95 dark:bg-slate-900/95 backdrop-blur-xl border border-slate-200 dark:border-white/10 rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.3)] mt-2 z-[100] animate-luxury-in",
"optionClasses" => "hs-select-option py-2.5 px-3 mb-0.5 text-sm text-slate-800 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-cyan-500/10 dark:hover:text-cyan-400 rounded-lg flex items-center justify-between transition-all duration-300",
"optionTemplate" => '<div class="flex items-center justify-between w-full"><span data-title></span><span class="hs-select-active-indicator hidden text-cyan-500"><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="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></span></div>'
];
@endphp
<div {{ $attributes->merge(['class' => 'relative w-full'])->only('class') }}>
<select name="{{ $name }}" id="{{ $id }}" data-hs-select='{!! json_encode($config) !!}' class="hidden" {{ $attributes->except(['options', 'selected', 'placeholder', 'id', 'name', 'class', 'hasSearch']) }}>
@if($placeholder)
<option value=" " {{ $isEmptySelected ? 'selected' : '' }} data-title="{{ $placeholder }}">
{{ $placeholder }}
</option>
@endif
{{ $slot }}
@foreach($options as $v => $l)
@php
$val = is_object($l) ? ($l->id ?? $l->value) : $v;
$text = is_object($l) ? ($l->name ?? $l->label ?? $l->title) : $l;
@endphp
<option value="{{ $val }}" {{ (string)$selected === (string)$val ? 'selected' : '' }} data-title="{{ $text }}">
{{ $text }}
</option>
@endforeach
</select>
</div>

View File

@@ -7,46 +7,46 @@
}
@endphp
<div class="fixed top-8 left-1/2 -translate-x-1/2 z-[99999] w-full max-w-sm px-4 space-y-3 pointer-events-none">
@if(session('success'))
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 3000)"
<div x-data="{
toasts: [],
add(message, type = 'success') {
const id = Date.now();
this.toasts.push({ id, message, type });
setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id);
}, type === 'success' ? 3000 : 5000);
}
}"
@toast.window="add($event.detail.message, $event.detail.type)"
x-init="
window.Alpine.store('toast', {
show(message, type = 'success') {
window.dispatchEvent(new CustomEvent('toast', { detail: { message, type } }));
}
});
@if(session('success')) add('{{ session('success') }}', 'success'); @endif
@if(session('error')) add('{{ session('error') }}', 'error'); @endif
@foreach($allErrors as $error) add('{{ addslashes($error) }}', 'error'); @endforeach
"
class="fixed top-8 left-1/2 -translate-x-1/2 z-[99999] w-full max-w-sm px-4 space-y-3 pointer-events-none">
<template x-for="toast in toasts" :key="toast.id">
<div x-show="true"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(16,185,129,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-emerald-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-emerald-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
:class="toast.type === 'success'
? 'border-emerald-500/30 text-emerald-600 dark:text-emerald-400 shadow-[0_20px_50px_rgba(16,185,129,0.15)]'
: 'border-rose-500/30 text-rose-600 dark:text-rose-400 shadow-[0_20px_50px_rgba(244,63,94,0.15)]'"
class="p-4 bg-white dark:bg-slate-900 border rounded-2xl font-bold flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div :class="toast.type === 'success' ? 'bg-emerald-500/20 text-emerald-500' : 'bg-rose-500/20 text-rose-500'"
class="size-8 rounded-xl flex items-center justify-center flex-shrink-0">
<svg x-show="toast.type === 'success'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
<svg x-show="toast.type === 'error'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<span>{{ session('success') }}</span>
<span x-text="toast.message"></span>
</div>
@endif
@if(session('error') || count($allErrors) > 0)
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 5000)"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-rose-500/30 text-rose-600 dark:text-rose-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(244,63,94,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-rose-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-rose-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<span>
@if(session('error'))
{{ session('error') }}
@else
{{ $allErrors[0] ?? __('Please check the form for errors.') }}
@endif
</span>
</div>
@endif
</template>
</div>

View File

@@ -58,7 +58,7 @@
<ul class="luxury-submenu" data-sidebar-sub>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.logs') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.logs') }}">{{ __('Machine Logs') }}</a></li>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.index') }}">{{ __('Machine List') }}</a></li>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.permissions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.permissions') }}">{{ __('Machine Permissions') }}</a></li>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.utilization') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.utilization') }}">{{ __('Utilization Rate') }}</a></li>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.expiry') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.expiry') }}">{{ __('Expiry Management') }}</a></li>
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.maintenance') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.maintenance') }}">{{ __('Maintenance Records') }}</a></li>