Files
star-erp/resources/js/Pages/Admin/User/Edit.tsx
sky121113 7dfe46ff9a
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 54s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
refactor: 優化使用者管理介面與角色顯示
- 移除 Header 中的角色名稱顯示
- 調整使用者表單欄位順序(使用者名稱、姓名並排)
- 將角色分配區塊移至基本資料下方
- 修復 email 欄位 null 值警告
- 修復角色選擇無限迴圈錯誤
- 統一角色顯示格式(中文名稱在上,代號在下)
2026-01-14 09:52:56 +08:00

219 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, useForm } from '@inertiajs/react';
import { Users, ArrowLeft, Check, Lock, Mail, User, AlertCircle } from 'lucide-react';
import { Button } from '@/Components/ui/button';
import { Input } from '@/Components/ui/input';
import { Label } from '@/Components/ui/label';
import { Checkbox } from '@/Components/ui/checkbox';
import { FormEvent } from 'react';
interface Role {
id: number;
name: string;
display_name: string;
}
interface UserData {
id: number;
name: string;
email: string;
username: string | null;
}
interface Props {
user: UserData;
roles: Role[];
currentRoles: string[];
}
export default function UserEdit({ user, roles, currentRoles }: Props) {
const { data, setData, put, processing, errors } = useForm({
name: user.name,
email: user.email || '',
username: user.username || '',
password: '',
password_confirmation: '',
roles: currentRoles,
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
put(route('users.update', user.id));
};
const toggleRole = (roleName: string) => {
if (data.roles.includes(roleName)) {
setData('roles', data.roles.filter(r => r !== roleName));
} else {
setData('roles', [...data.roles, roleName]);
}
};
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: '系統管理', href: '#' },
{ label: '使用者管理', href: route('users.index') },
{ label: '編輯使用者', href: route('users.edit', user.id), isPage: true },
]}
>
<Head title={`編輯使用者 - ${user.name}`} />
<div className="container mx-auto p-6 max-w-7xl">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Header Area */}
<div>
<Link href={route('users.index')}>
<Button
variant="outline"
type="button"
className="gap-2 button-outlined-primary mb-2"
>
<ArrowLeft className="h-4 w-4" />
使
</Button>
</Link>
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Users className="h-6 w-6 text-[#01ab83]" />
使
</h1>
<p className="text-gray-500 mt-1">
使
</p>
</div>
<Button
type="submit"
className="button-filled-primary"
disabled={processing}
>
<Check className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
<div className="space-y-6">
{/* Basic Info */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm space-y-6">
<h3 className="font-bold text-gray-900 border-b pb-2 mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="username" className="flex items-center gap-2">
<User className="h-4 w-4" /> 使 ()
</Label>
<Input
id="username"
value={data.username}
onChange={e => setData('username', e.target.value)}
placeholder="請輸入登入帳號"
/>
{errors.username && <p className="text-sm text-red-500">{errors.username}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="name" className="flex items-center gap-2">
<User className="h-4 w-4" />
</Label>
<Input
id="name"
value={data.name}
onChange={e => setData('name', e.target.value)}
placeholder="例如:王小明"
/>
{errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email" className="flex items-center gap-2">
<Mail className="h-4 w-4" /> ()
</Label>
<Input
id="email"
type="email"
value={data.email}
onChange={e => setData('email', e.target.value)}
placeholder="user@example.com (可省略)"
/>
{errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
</div>
</div>
{/* Roles */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
<h3 className="font-bold text-gray-900 border-b pb-2 mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{roles.map((role) => (
<div key={role.id} className="flex items-center space-x-3 p-3 border border-gray-100 bg-gray-50/50 hover:bg-gray-100 rounded-lg transition-colors">
<Checkbox
id={`role-${role.id}`}
checked={data.roles.includes(role.name)}
onCheckedChange={() => toggleRole(role.name)}
/>
<div className="grid gap-1 leading-none">
<label
htmlFor={`role-${role.id}`}
className="text-sm font-medium leading-none cursor-pointer"
onClick={() => toggleRole(role.name)}
>
{role.display_name}
</label>
<p className="text-xs text-gray-500 font-mono">
{role.name}
</p>
</div>
</div>
))}
</div>
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
</div>
{/* Password Reset */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm space-y-6">
<h3 className="font-bold text-gray-900 border-b pb-2 mb-4"></h3>
<div className="bg-amber-50 text-amber-800 p-3 rounded-lg text-sm flex items-start gap-2 mb-4">
<AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="password" className="flex items-center gap-2">
<Lock className="h-4 w-4" />
</Label>
<Input
id="password"
type="password"
value={data.password}
onChange={e => setData('password', e.target.value)}
placeholder="••••••••"
/>
{errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="password_confirmation" className="flex items-center gap-2">
<Lock className="h-4 w-4" />
</Label>
<Input
id="password_confirmation"
type="password"
value={data.password_confirmation}
onChange={e => setData('password_confirmation', e.target.value)}
placeholder="••••••••"
/>
</div>
</div>
</div>
</div>
</form>
</div>
</AuthenticatedLayout>
);
}