input('per_page', 10); $sortBy = $request->input('sort_by', 'id'); $sortOrder = $request->input('sort_order', 'asc'); $search = $request->input('search'); $roleId = $request->input('role'); $query = User::with(['roles:id,name,display_name']); // Handle Search if ($search) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%") ->orWhere('username', 'like', "%{$search}%"); }); } // Handle Role Filter if ($roleId && $roleId !== 'all') { $query->whereHas('roles', function ($q) use ($roleId) { $q->where('id', $roleId); }); } // Handle sorting if (in_array($sortBy, ['name', 'created_at'])) { $query->orderBy($sortBy, $sortOrder); } else { $query->orderBy('id', 'asc'); } $users = $query->paginate($perPage)->withQueryString(); $roles = Role::select('id', 'name', 'display_name')->get(); return Inertia::render('Admin/User/Index', [ 'users' => $users, 'roles' => $roles, 'filters' => $request->only(['per_page', 'sort_by', 'sort_order', 'search', 'role']), ]); } /** * Show the form for creating a new resource. */ public function create() { $roles = Role::pluck('display_name', 'name'); return Inertia::render('Admin/User/Create', [ 'roles' => $roles ]); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users'], 'username' => ['required', 'string', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], 'roles' => ['array'], ], [ 'password.required' => '請輸入密碼', 'password.min' => '密碼長度至少需 :min 個字元', 'password.confirmed' => '密碼確認不符', ]); $user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'username' => $validated['username'], 'password' => Hash::make($validated['password']), ]); if (!empty($validated['roles'])) { $user->syncRoles($validated['roles']); // Update the 'created' log to include roles $activity = \Spatie\Activitylog\Models\Activity::where('subject_type', get_class($user)) ->where('subject_id', $user->id) ->where('event', 'created') ->latest() ->first(); if ($activity) { $roleNames = $user->roles()->pluck('display_name')->join(', '); $properties = $activity->properties->toArray(); $properties['attributes']['role_id'] = $roleNames; $activity->properties = $properties; $activity->save(); } } return redirect()->route('users.index')->with('success', '使用者建立成功'); } /** * Show the form for editing the specified resource. */ public function edit(string $id) { $user = User::with('roles')->findOrFail($id); $roles = Role::get(['id', 'name', 'display_name']); return Inertia::render('Admin/User/Edit', [ 'user' => $user, 'roles' => $roles, 'currentRoles' => $user->getRoleNames() ]); } /** * Update the specified resource in storage. */ public function update(Request $request, string $id) { $user = User::findOrFail($id); $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 'username' => ['required', 'string', 'max:255', Rule::unique('users')->ignore($user->id)], 'password' => ['nullable', 'string', 'min:8', 'confirmed'], 'roles' => ['array'], ], [ 'password.min' => '密碼長度至少需 :min 個字元', 'password.confirmed' => '密碼確認不符', ]); // 1. Prepare data and detect changes $userData = [ 'name' => $validated['name'], 'email' => $validated['email'], 'username' => $validated['username'], ]; if (!empty($validated['password'])) { $userData['password'] = Hash::make($validated['password']); } $user->fill($userData); // Capture dirty attributes for manual logging $dirty = $user->getDirty(); $oldAttributes = []; $newAttributes = []; foreach ($dirty as $key => $value) { $oldAttributes[$key] = $user->getOriginal($key); $newAttributes[$key] = $value; } // Save without triggering events (prevents duplicate log) $user->saveQuietly(); // 2. Handle Roles $roleChanges = null; if (isset($validated['roles'])) { $oldRoles = $user->roles()->pluck('display_name')->join(', '); $user->syncRoles($validated['roles']); $newRoles = $user->roles()->pluck('display_name')->join(', '); if ($oldRoles !== $newRoles) { $roleChanges = [ 'old' => $oldRoles, 'new' => $newRoles ]; } } // 3. Manually Log activity (Single Consolidated Log) if (!empty($newAttributes) || $roleChanges) { $properties = [ 'attributes' => $newAttributes, 'old' => $oldAttributes, ]; if ($roleChanges) { $properties['attributes']['role_id'] = $roleChanges['new']; $properties['old']['role_id'] = $roleChanges['old']; } activity() ->performedOn($user) ->causedBy(auth()->user()) ->event('updated') ->withProperties($properties) ->tap(function (\Spatie\Activitylog\Contracts\Activity $activity) use ($user) { // Manually add snapshot since we aren't using the model's LogOptions due to saveQuietly $activity->properties = $activity->properties->merge([ 'snapshot' => [ 'name' => $user->name, 'username' => $user->username, ] ]); }) ->log('updated'); } return redirect()->route('users.index')->with('success', '使用者更新成功'); } /** * Remove the specified resource from storage. */ public function destroy(string $id) { $user = User::findOrFail($id); if ($user->hasRole('super-admin')) { return back()->with('error', '無法刪除超級管理員帳號'); } if ($user->id === auth()->id()) { return back()->with('error', '無法刪除自己'); } $user->delete(); return redirect()->route('users.index')->with('success', '使用者已刪除'); } }