diff --git a/app/Http/Controllers/Admin/MachineController.php b/app/Http/Controllers/Admin/MachineController.php index 09d093f..06a0705 100644 --- a/app/Http/Controllers/Admin/MachineController.php +++ b/app/Http/Controllers/Admin/MachineController.php @@ -131,7 +131,7 @@ class MachineController extends AdminController */ public function slotsAjax(Machine $machine) { - $slots = $machine->slots()->with('product:id,name,image')->orderByRaw('CAST(slot_no AS UNSIGNED) ASC')->get(); + $slots = $machine->slots()->with('product:id,name,image_url')->orderByRaw('CAST(slot_no AS UNSIGNED) ASC')->get(); return response()->json([ 'success' => true, diff --git a/app/Http/Controllers/Admin/PermissionController.php b/app/Http/Controllers/Admin/PermissionController.php index 7463914..46ac8c2 100644 --- a/app/Http/Controllers/Admin/PermissionController.php +++ b/app/Http/Controllers/Admin/PermissionController.php @@ -27,7 +27,7 @@ class PermissionController extends Controller // 篩選:公司名稱 (僅限系統管理員) if ($user->isSystemAdmin() && request()->filled('company_id')) { if (request()->company_id === 'system') { - $query->where('is_system', true); + $query->whereNull('company_id'); } else { $query->where('company_id', request()->company_id); } @@ -36,7 +36,9 @@ class PermissionController extends Controller $roles = $query->latest()->paginate($per_page)->withQueryString(); $companies = $user->isSystemAdmin() ? \App\Models\System\Company::all() : collect(); - // 權限遞迴約束:租戶管理員只能看到並指派自己擁有的權限 + // 權限分組邏輯中的標題與過濾 + $isSubAccountRoles = request()->routeIs('*.sub-account-roles'); + $title = $isSubAccountRoles ? __('Sub Account Roles') : __('Role Settings'); $permissionQuery = \Spatie\Permission\Models\Permission::query(); if (!$user->isSystemAdmin()) { $permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name')); @@ -44,17 +46,13 @@ class PermissionController extends Controller // 權限分組邏輯 $all_permissions = $permissionQuery->get() + ->reject(fn($p) => $p->name === 'menu.data-config.sub-account-roles') ->groupBy(function($perm) { if (str_starts_with($perm->name, 'menu.')) { - // 主選單權限:menu.xxx (兩段) - // 子選單權限:menu.xxx.yyy (三段) return 'menu'; } return 'other'; }); - - // 根據路由決定標題 - $title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings'); $currentUserRoleIds = $user->roles->pluck('id')->toArray(); return view('admin.permission.roles', compact('roles', 'all_permissions', 'title', 'currentUserRoleIds', 'companies')); @@ -77,7 +75,9 @@ class PermissionController extends Controller $all_permissions = $permissionQuery->get()->groupBy(fn($p) => str_starts_with($p->name, 'menu.') ? 'menu' : 'other'); $title = request()->routeIs('*.sub-account-roles.create') ? __('Create Sub Account Role') : __('Create New Role'); - $back_url = request()->routeIs('*.sub-account-roles.create') ? route('admin.data-config.sub-account-roles') : route('admin.permission.roles'); + $back_url = request()->routeIs('*.sub-account-roles.create') + ? route('admin.data-config.sub-accounts', ['tab' => 'roles']) + : route('admin.permission.roles'); return view('admin.permission.roles-edit', compact('role', 'all_permissions', 'title', 'back_url')); } @@ -98,6 +98,7 @@ class PermissionController extends Controller // 權限分組邏輯 $all_permissions = $permissionQuery->get() + ->reject(fn($p) => $p->name === 'menu.data-config.sub-account-roles') ->groupBy(function($perm) { if (str_starts_with($perm->name, 'menu.')) { return 'menu'; @@ -109,7 +110,9 @@ class PermissionController extends Controller $title = request()->routeIs('*.sub-account-roles.edit') ? __('Edit Sub Account Role') : __('Edit Role Permissions'); // 麵包屑/返回路徑 - $back_url = request()->routeIs('*.sub-account-roles.edit') ? route('admin.data-config.sub-account-roles') : route('admin.permission.roles'); + $back_url = request()->routeIs('*.sub-account-roles.edit') + ? route('admin.data-config.sub-accounts', ['tab' => 'roles']) + : route('admin.permission.roles'); return view('admin.permission.roles-edit', compact('role', 'all_permissions', 'title', 'back_url')); } @@ -158,8 +161,8 @@ class PermissionController extends Controller $role->syncPermissions($perms); } - $target_route = request()->routeIs('*.sub-account-roles.*') ? 'admin.data-config.sub-account-roles' : 'admin.permission.roles'; - return redirect()->route($target_route)->with('success', __('Role created successfully.')); + $target_route = request()->routeIs('*.sub-account-roles.*') ? route('admin.data-config.sub-accounts', ['tab' => 'roles']) : route('admin.permission.roles'); + return redirect()->to($target_route)->with('success', __('Role created successfully.')); } /** @@ -219,8 +222,8 @@ class PermissionController extends Controller } $role->syncPermissions($perms); - $target_route = request()->routeIs('*.sub-account-roles.*') ? 'admin.data-config.sub-account-roles' : 'admin.permission.roles'; - return redirect()->route($target_route)->with('success', __('Role updated successfully.')); + $target_route = request()->routeIs('*.sub-account-roles.*') ? route('admin.data-config.sub-accounts', ['tab' => 'roles']) : route('admin.permission.roles'); + return redirect()->to($target_route)->with('success', __('Role updated successfully.')); } /** @@ -244,46 +247,95 @@ class PermissionController extends Controller $role->delete(); + if (request()->routeIs('*.sub-account-roles.*')) { + return redirect()->route('admin.data-config.sub-accounts', ['tab' => 'roles'])->with('success', __('Role deleted successfully.')); + } + return redirect()->back()->with('success', __('Role deleted successfully.')); } // 帳號管理 public function accounts(Request $request) { - $query = \App\Models\System\User::query()->with(['company', 'roles', 'machines']); + $user = auth()->user(); + $isSubAccountRoute = $request->routeIs('admin.data-config.sub-accounts'); + $tab = $request->input('tab', 'accounts'); - // 租戶隔離:如果不是系統管理員,則只看自己公司的成員 - if (!auth()->user()->isSystemAdmin()) { - $query->where('company_id', auth()->user()->company_id); + // 初始化變數 + $users = collect(); + $roles = collect(); + $paginated_roles = collect(); + $all_permissions = collect(); + $currentUserRoleIds = $user->roles->pluck('id')->toArray(); + $companies = $user->isSystemAdmin() ? \App\Models\System\Company::all() : collect(); + + if ($isSubAccountRoute && $tab === 'roles') { + // 處理角色分頁邏輯 (移植自 roles()) + $per_page = $request->input('per_page', 10); + $roles_query = \App\Models\System\Role::query()->with(['permissions', 'users', 'company']); + + if (!$user->isSystemAdmin()) { + $roles_query->where('company_id', $user->company_id); + } + + if ($search = $request->input('search')) { + $roles_query->where('name', 'like', "%{$search}%"); + } + + if ($user->isSystemAdmin() && $request->filled('company_id')) { + if ($request->company_id === 'system') { + $roles_query->where('is_system', true); + } else { + $roles_query->where('company_id', $request->company_id); + } + } + + $paginated_roles = $roles_query->latest()->paginate($per_page)->withQueryString(); + + // 權限分組邏輯 + $permissionQuery = \Spatie\Permission\Models\Permission::query(); + if (!$user->isSystemAdmin()) { + $permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name')); + } + + $all_permissions = $permissionQuery->get() + ->reject(fn($p) => $p->name === 'menu.data-config.sub-account-roles') + ->groupBy(fn($p) => str_starts_with($p->name, 'menu.') ? 'menu' : 'other'); + } else { + // 處理帳號名單邏輯 + $query = \App\Models\System\User::query()->with(['company', 'roles', 'machines']); + + if (!$user->isSystemAdmin()) { + $query->where('company_id', $user->company_id); + } + + if ($search = $request->input('search')) { + $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('username', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + } + + if ($user->isSystemAdmin() && $request->filled('company_id')) { + $query->where('company_id', $request->company_id); + } + + $per_page = $request->input('per_page', 10); + $users = $query->latest()->paginate($per_page)->withQueryString(); + + $roles_query = \App\Models\System\Role::query(); + if (!$user->isSystemAdmin()) { + $roles_query->forCompany($user->company_id); + } + $roles = $roles_query->get(); } - // 搜尋 - if ($search = $request->input('search')) { - $query->where(function($q) use ($search) { - $q->where('name', 'like', "%{$search}%") - ->orWhere('username', 'like', "%{$search}%") - ->orWhere('email', 'like', "%{$search}%"); - }); - } + $title = $isSubAccountRoute ? __('Sub Account Management') : __('Account Management'); - // 公司篩選 (僅限 super-admin) - if (auth()->user()->isSystemAdmin() && $request->filled('company_id')) { - $query->where('company_id', $request->company_id); - } - - $per_page = $request->input('per_page', 10); - $users = $query->latest()->paginate($per_page)->withQueryString(); - $companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect(); - $roles_query = \App\Models\System\Role::query(); - if (!auth()->user()->isSystemAdmin()) { - $roles_query->forCompany(auth()->user()->company_id); - } - $roles = $roles_query->get(); - - // 根據路由決定標題 - $title = request()->routeIs('*.sub-accounts') ? __('Sub Account Management') : __('Account Management'); - - return view('admin.data-config.accounts', compact('users', 'companies', 'roles', 'title')); + return view('admin.data-config.accounts', compact( + 'users', 'companies', 'roles', 'paginated_roles', 'all_permissions', 'title', 'tab', 'currentUserRoleIds' + )); } /** diff --git a/database/migrations/2026_04_01_092300_remove_sub_account_roles_permission.php b/database/migrations/2026_04_01_092300_remove_sub_account_roles_permission.php new file mode 100644 index 0000000..b61d54f --- /dev/null +++ b/database/migrations/2026_04_01_092300_remove_sub_account_roles_permission.php @@ -0,0 +1,48 @@ +where('name', 'menu.data-config.sub-account-roles') + ->first(); + + if ($permission) { + // 2. 移除角色與該權限的關聯 (雖然 Spatie 通常會處理,但手動確保清理乾淨) + DB::table('role_has_permissions') + ->where('permission_id', $permission->id) + ->delete(); + + // 3. 移除權限本身 + DB::table('permissions') + ->where('id', $permission->id) + ->delete(); + } + + // 4. 清理權限快取 (如果有的話) + try { + app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); + } catch (\Exception $e) { + // 忽略快取清理失敗(例如在沒有 Redis 的環境中) + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // 由於是要永久拿掉,down 邏輯通常不需要重建, + // 若真要復原,應透過重跑 Seeder 或手動新增。 + } +}; diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index 363d985..2e5b070 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -34,7 +34,6 @@ class RoleSeeder extends Seeder 'menu.data-config.products', 'menu.data-config.advertisements', 'menu.data-config.sub-accounts', - 'menu.data-config.sub-account-roles', 'menu.data-config.points', 'menu.data-config.badges', 'menu.remote', @@ -81,7 +80,6 @@ class RoleSeeder extends Seeder 'menu.data-config.products', 'menu.data-config.advertisements', 'menu.data-config.sub-accounts', - 'menu.data-config.sub-account-roles', 'menu.data-config.points', 'menu.data-config.badges', 'menu.remote', diff --git a/lang/en.json b/lang/en.json index c070498..68f34d2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -13,6 +13,7 @@ "Account": "Account", "Account :name status has been changed to :status.": "Account :name status has been changed to :status.", "Account Info": "Account Info", + "Account List": "Account List", "Account Management": "Account Management", "Account Name": "Account Name", "Account Settings": "Account Settings", @@ -429,6 +430,7 @@ "No matching machines": "No matching machines", "No permissions": "No permissions", "No roles found.": "No roles found.", + "No roles available": "No roles available", "No slots found": "No slots found", "No users found": "No users found", "None": "None", @@ -559,7 +561,7 @@ "Risk": "Risk", "Role": "Role", "Role Identification": "Role Identification", - "Role Management": "Role Permission Management", + "Role Management": "Role Management", "Role Name": "Role Name", "Role Permissions": "Role Permissions", "Role Settings": "Role Permissions", @@ -777,7 +779,6 @@ "menu.data-config.badges": "Badge Settings", "menu.data-config.points": "Point Settings", "menu.data-config.products": "Product Management", - "menu.data-config.sub-account-roles": "Sub Account Roles", "menu.data-config.sub-accounts": "Sub Account Management", "menu.line": "Line Management", "menu.machines": "Machine Management", diff --git a/lang/ja.json b/lang/ja.json index 757f174..188a36a 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -13,6 +13,7 @@ "Account": "帳號", "Account :name status has been changed to :status.": "アカウント :name のステータスが :status に変更されました。", "Account Management": "アカウント管理", + "Account List": "アカウント一覧", "Account Name": "帳號姓名", "Account Settings": "アカウント設定", "Account Status": "アカウント狀態", @@ -424,6 +425,7 @@ "No matching machines": "一致する機台がありません", "No permissions": "権限項目なし", "No roles found.": "ロールが見つかりませんでした。", + "No roles available": "利用可能なロールがありません", "No slots found": "未找到貨道資訊", "No users found": "ユーザーが見つかりません", "None": "なし", @@ -557,7 +559,7 @@ "Risk": "リスク", "Role": "ロール", "Role Identification": "ロール識別情報", - "Role Management": "ロール權限管理", + "Role Management": "ロール管理", "Role Name": "ロール名", "Role Permissions": "ロール權限", "Role Settings": "ロール權限", @@ -777,7 +779,6 @@ "menu.data-config.badges": "バッジ設定", "menu.data-config.points": "ポイント設定", "menu.data-config.products": "商品管理", - "menu.data-config.sub-account-roles": "サブアカウントロール", "menu.data-config.sub-accounts": "サブアカウント管理", "menu.line": "LINE 設定", "menu.machines": "機台管理", diff --git a/lang/zh_TW.json b/lang/zh_TW.json index 793a740..8c3a1f7 100644 --- a/lang/zh_TW.json +++ b/lang/zh_TW.json @@ -13,6 +13,7 @@ "Account": "帳號", "Account :name status has been changed to :status.": "帳號 :name 的狀態已變更為 :status。", "Account Info": "帳號資訊", + "Account List": "帳號列表", "Account Management": "帳號管理", "Account Name": "帳號名稱", "Account Settings": "帳戶設定", @@ -439,6 +440,7 @@ "No matching machines": "查無匹配機台", "No permissions": "無權限項目", "No roles found.": "找不到角色資料。", + "No roles available": "目前沒有角色資料。", "No slots found": "未找到貨道資訊", "No users found": "找不到用戶資料", "None": "無", @@ -806,7 +808,6 @@ "menu.data-config.badges": "徽章設定", "menu.data-config.points": "點數設定", "menu.data-config.products": "商品管理", - "menu.data-config.sub-account-roles": "子帳號角色", "menu.data-config.sub-accounts": "子帳號管理", "menu.line": "LINE 配置", "menu.machines": "機台管理", @@ -912,5 +913,6 @@ "System Default (All Companies)": "系統預設 (所有公司)", "No materials available": "沒有可用的素材", "Search...": "搜尋...", + "Category Name": "分類名稱", "Category Management": "分類管理" } \ No newline at end of file diff --git a/resources/views/admin/basic-settings/machines/index.blade.php b/resources/views/admin/basic-settings/machines/index.blade.php index ce4e7bf..d9c0933 100644 --- a/resources/views/admin/basic-settings/machines/index.blade.php +++ b/resources/views/admin/basic-settings/machines/index.blade.php @@ -297,7 +297,7 @@
+ class="w-10 h-10 rounded-xl 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 group-hover:text-white group-hover:border-cyan-500 shadow-sm group-hover:shadow-cyan-500/50 transition-all duration-300 overflow-hidden"> @if(isset($machine->image_urls[0])) @else @@ -437,7 +437,7 @@
+ class="w-10 h-10 rounded-xl 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 group-hover:text-white group-hover:border-cyan-500 shadow-sm group-hover:shadow-cyan-500/50 transition-all duration-300"> @@ -540,7 +540,7 @@
+ class="flex-shrink-0 w-10 h-10 rounded-xl 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 group-hover:text-white group-hover:border-cyan-500 shadow-sm group-hover:shadow-cyan-500/50 transition-all duration-300"> {{ $title }}

- {{ __('Manage administrative and tenant accounts') }} + {{ $tab === 'roles' ? __('Define and manage security roles and permissions.') : __('Manage administrative and tenant accounts') }}

- + @if($tab === 'roles') + + + + + {{ __('Add Role') }} + + @else + + @endif
+ @if(request()->routeIs('admin.data-config.sub-accounts') && auth()->user()->can('menu.data-config.sub-accounts')) + +
+ + +
+ @endif + -
- -
-
- - - - - - - -
+
+ @if($tab === 'roles') + + + +
+ + + + + + + +
- @if(auth()->user()->isSystemAdmin()) -
- -
- @endif - - + @if(auth()->user()->isSystemAdmin()) +
+ + + +
+ @endif + + -
- - - - - - @if(auth()->user()->isSystemAdmin()) - - @endif - - - - - - - @forelse($users as $user) - - + + + + + @empty + + + + @endforelse + +
- {{ __('User Info') }} - {{ __('Contact Info') }} - {{ __('Affiliation') }} - {{ __('Role') }} - {{ __('Status') }} - {{ __('Actions') }}
-
-
- @if($user->avatar) - - @else - - - +
+ + + + + + + + + + + + @forelse($paginated_roles as $role) + + - - @if(auth()->user()->isSystemAdmin()) - - @endif - - - + - - @empty - - - - @endforelse - -
{{ __('Role Name') }}{{ __('Affiliation') }}{{ __('Permissions') }}{{ __('Users') }}{{ __('Actions') }}
+
+
+ +
+ {{ $role->name }} + @if($role->is_system) + + + @endif
-
- {{ $user->name }} - {{ $user->username }} -
- -
-
- @if($user->phone) - {{ $user->phone }} - @endif - {{ $user->email ?? '-' }} -
-
- @if($user->company) - {{ $user->company->name }} - @else - {{ __('SYSTEM') }} - @endif - - @foreach($user->roles as $role) - - {{ $role->name }} - - @endforeach - - @if($user->status) - - {{ __('Active') }} - - @else - - {{ __('Disabled') }} - - @endif - -
- @if(!$user->hasRole('super-admin') || auth()->user()->hasRole('super-admin')) - @if($user->status) - - @else - - @endif - -
- @csrf - @method('DELETE') - -
+
+ @if($role->is_system) + + {{ __('System Level') }} + @else - {{ __('Protected') }} + + {{ $role->company->name ?? __('Company Level') }} + @endif - -
-
- - - -

{{ __('No accounts found') }}

-
-
-
+
+
+ @forelse($role->permissions->take(6) as $permission) + {{ __($permission->name) }} + @empty + {{ __('No permissions') }} + @endforelse + @if($role->permissions->count() > 6) + +{{ $role->permissions->count() - 6 }} + @endif +
+
+ {{ $role->users()->count() }} + +
+ + + + @if($role->name !== 'super-admin' && (auth()->user()->isSystemAdmin() || !$role->is_system)) +
+ @csrf + @method('DELETE') + +
+ @endif +
+
+
+ +

{{ __('No roles found.') }}

+
+
+
-
- {{ $users->links('vendor.pagination.luxury') }} -
+
+ {{ $paginated_roles->links('vendor.pagination.luxury') }} +
+ + @else + +
+ +
+ + + + + + + +
+ + @if(auth()->user()->isSystemAdmin()) +
+ +
+ @endif + +
+ +
+ + + + + + @if(auth()->user()->isSystemAdmin()) + + @endif + + + + + + + @forelse($users as $user) + + + + @if(auth()->user()->isSystemAdmin()) + + @endif + + + + + @empty + + + + @endforelse + +
+ {{ __('User Info') }} + {{ __('Contact Info') }} + {{ __('Affiliation') }} + {{ __('Role') }} + {{ __('Status') }} + {{ __('Actions') }}
+
+
+ @if($user->avatar) + + @else + + + + @endif +
+
+ {{ $user->name }} + {{ $user->username }} +
+
+
+
+ @if($user->phone) + {{ $user->phone }} + @endif + {{ $user->email ?? '-' }} +
+
+ @if($user->company) + {{ $user->company->name }} + @else + {{ __('SYSTEM') }} + @endif + + @foreach($user->roles as $role) + + {{ $role->name }} + + @endforeach + + @if($user->status) + + {{ __('Active') }} + + @else + + {{ __('Disabled') }} + + @endif + +
+ @if(!$user->hasRole('super-admin') || auth()->user()->hasRole('super-admin')) + @if($user->status) + + @else + + @endif + +
+ @csrf + @method('DELETE') + +
+ @else + {{ __('Protected') }} + @endif +
+
+
+ + + +

{{ __('No accounts found') }}

+
+
+
+ +
+ {{ $users->links('vendor.pagination.luxury') }} +
+ @endif
@@ -403,6 +529,49 @@ $roleSelectConfig = [ @csrf @method('PATCH') + + +
+
+
+ + + +
+ +
+
+ + + +
+
+

+ {{ __('Cannot Delete Role') }} +

+
+

+
+
+
+
+ +
+
+
+
@endsection @@ -423,6 +592,7 @@ $roleSelectConfig = [ role: initData.oldValues.role || '', status: initData.oldValues.status || 1 }, + tab: initData.tab || 'accounts', roleSelectConfig: initData.roleSelectConfig, isDeleteConfirmOpen: false, deleteFormAction: '', @@ -430,6 +600,20 @@ $roleSelectConfig = [ toggleFormAction: '', statusToggleSource: 'list', + isWarningModalOpen: false, + deleteWarningMsg: '', + triggerDeleteWarning(msg) { + this.deleteWarningMsg = msg; + this.isWarningModalOpen = true; + }, + + switchTab(newTab) { + const url = new URL(window.location.href); + url.searchParams.set('tab', newTab); + // Clear search and filters when switching tabs if desired, but here we just go to the URL + window.location.href = url.toString(); + }, + confirmDelete(action) { this.deleteFormAction = action; this.isDeleteConfirmOpen = true; diff --git a/resources/views/admin/machines/index.blade.php b/resources/views/admin/machines/index.blade.php index c2570ea..86cc68a 100644 --- a/resources/views/admin/machines/index.blade.php +++ b/resources/views/admin/machines/index.blade.php @@ -398,20 +398,17 @@ window.machineApp = function(initialTab) {
+ class="w-12 h-12 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/info:bg-cyan-500 group-hover/info:text-white transition-all duration-300 shadow-sm relative"> @if(isset($machine->image_urls[0])) @else - @endif -
-
-