修正:UI 優化與使用者角色分配改為單選
This commit is contained in:
33
package-lock.json
generated
33
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
@@ -1514,6 +1515,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-radio-group": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
|
||||
"integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-previous": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
|
||||
@@ -348,7 +348,7 @@ export default function AuthenticatedLayout({
|
||||
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
||||
<div className="hidden md:flex flex-col items-end mr-1">
|
||||
<span className="text-sm font-medium text-slate-700 group-hover:text-slate-900 transition-colors">
|
||||
{user.name}
|
||||
{user.name} ({user.username})
|
||||
</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
{user.role_labels?.[0] || user.roles?.[0] || '一般用戶'}
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function LandlordLayout({ children, title }: LandlordLayoutProps)
|
||||
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
||||
<div className="flex flex-col items-end mr-1">
|
||||
<span className="text-sm font-medium text-slate-700 group-hover:text-slate-900 transition-colors">
|
||||
{user.name}
|
||||
{user.name} ({user.username})
|
||||
</span>
|
||||
<span className="text-xs text-slate-500">
|
||||
{user.role_labels?.[0] || user.roles?.[0] || '系統管理員'}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Users, ArrowLeft, Check, Lock, Mail, User } 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 { RadioGroup, RadioGroupItem } from '@/Components/ui/radio-group';
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
interface Props {
|
||||
@@ -26,14 +26,6 @@ export default function UserCreate({ roles }: Props) {
|
||||
post(route('users.store'));
|
||||
};
|
||||
|
||||
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
|
||||
@@ -131,30 +123,37 @@ export default function UserCreate({ roles }: Props) {
|
||||
|
||||
{/* 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">
|
||||
<h3 className="font-bold text-gray-900 border-b pb-2 mb-4">角色分配 (單選)</h3>
|
||||
<RadioGroup
|
||||
value={data.roles[0] || ''}
|
||||
onValueChange={(value) => setData('roles', [value])}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{Object.entries(roles).map(([roleName, displayName]) => (
|
||||
<div key={roleName} 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
|
||||
<Label
|
||||
key={roleName}
|
||||
htmlFor={`role-${roleName}`}
|
||||
className={`flex items-center space-x-3 p-3 border rounded-lg transition-colors cursor-pointer ${data.roles.includes(roleName)
|
||||
? 'border-primary-main bg-primary-lightest'
|
||||
: 'border-gray-100 bg-gray-50/50 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={roleName}
|
||||
id={`role-${roleName}`}
|
||||
checked={data.roles.includes(roleName)}
|
||||
onCheckedChange={() => toggleRole(roleName)}
|
||||
className="text-primary-main border-gray-300"
|
||||
/>
|
||||
<div className="grid gap-1 leading-none">
|
||||
<label
|
||||
htmlFor={`role-${roleName}`}
|
||||
className="text-sm font-medium leading-none cursor-pointer"
|
||||
onClick={() => toggleRole(roleName)}
|
||||
>
|
||||
<span className="text-sm font-medium leading-none">
|
||||
{displayName}
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 font-mono">
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 font-mono">
|
||||
{roleName}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Users, ArrowLeft, Check, Lock, Mail, User, AlertCircle } from 'lucide-r
|
||||
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 { RadioGroup, RadioGroupItem } from '@/Components/ui/radio-group';
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
interface Role {
|
||||
@@ -41,15 +41,6 @@ export default function UserEdit({ user, roles, currentRoles }: Props) {
|
||||
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={[
|
||||
@@ -146,30 +137,37 @@ export default function UserEdit({ user, roles, currentRoles }: Props) {
|
||||
|
||||
{/* 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">
|
||||
<h3 className="font-bold text-gray-900 border-b pb-2 mb-4">角色分配 (單選)</h3>
|
||||
<RadioGroup
|
||||
value={data.roles[0] || ''}
|
||||
onValueChange={(value) => setData('roles', [value])}
|
||||
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
|
||||
<Label
|
||||
key={role.id}
|
||||
htmlFor={`role-${role.id}`}
|
||||
className={`flex items-center space-x-3 p-3 border rounded-lg transition-colors cursor-pointer ${data.roles.includes(role.name)
|
||||
? 'border-primary-main bg-primary-lightest'
|
||||
: 'border-gray-100 bg-gray-50/50 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value={role.name}
|
||||
id={`role-${role.id}`}
|
||||
checked={data.roles.includes(role.name)}
|
||||
onCheckedChange={() => toggleRole(role.name)}
|
||||
className="text-primary-main border-gray-300"
|
||||
/>
|
||||
<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)}
|
||||
>
|
||||
<span className="text-sm font-medium leading-none">
|
||||
{role.display_name}
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 font-mono">
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 font-mono">
|
||||
{role.name}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user