修正: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-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@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-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@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": {
|
"node_modules/@radix-ui/react-roving-focus": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
"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-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@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-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
@@ -46,4 +47,4 @@
|
|||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0"
|
"tailwind-merge": "^3.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ export default function AuthenticatedLayout({
|
|||||||
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
||||||
<div className="hidden md:flex flex-col items-end mr-1">
|
<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">
|
<span className="text-sm font-medium text-slate-700 group-hover:text-slate-900 transition-colors">
|
||||||
{user.name}
|
{user.name} ({user.username})
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-slate-500">
|
<span className="text-xs text-slate-500">
|
||||||
{user.role_labels?.[0] || user.roles?.[0] || '一般用戶'}
|
{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">
|
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
||||||
<div className="flex flex-col items-end mr-1">
|
<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">
|
<span className="text-sm font-medium text-slate-700 group-hover:text-slate-900 transition-colors">
|
||||||
{user.name}
|
{user.name} ({user.username})
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-slate-500">
|
<span className="text-xs text-slate-500">
|
||||||
{user.role_labels?.[0] || user.roles?.[0] || '系統管理員'}
|
{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 { Button } from '@/Components/ui/button';
|
||||||
import { Input } from '@/Components/ui/input';
|
import { Input } from '@/Components/ui/input';
|
||||||
import { Label } from '@/Components/ui/label';
|
import { Label } from '@/Components/ui/label';
|
||||||
import { Checkbox } from '@/Components/ui/checkbox';
|
import { RadioGroup, RadioGroupItem } from '@/Components/ui/radio-group';
|
||||||
import { FormEvent } from 'react';
|
import { FormEvent } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -26,14 +26,6 @@ export default function UserCreate({ roles }: Props) {
|
|||||||
post(route('users.store'));
|
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 (
|
return (
|
||||||
<AuthenticatedLayout
|
<AuthenticatedLayout
|
||||||
@@ -131,30 +123,37 @@ export default function UserCreate({ roles }: Props) {
|
|||||||
|
|
||||||
{/* Roles */}
|
{/* Roles */}
|
||||||
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
|
<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>
|
<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">
|
<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]) => (
|
{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">
|
<Label
|
||||||
<Checkbox
|
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}`}
|
id={`role-${roleName}`}
|
||||||
checked={data.roles.includes(roleName)}
|
className="text-primary-main border-gray-300"
|
||||||
onCheckedChange={() => toggleRole(roleName)}
|
|
||||||
/>
|
/>
|
||||||
<div className="grid gap-1 leading-none">
|
<div className="grid gap-1 leading-none">
|
||||||
<label
|
<span className="text-sm font-medium leading-none">
|
||||||
htmlFor={`role-${roleName}`}
|
|
||||||
className="text-sm font-medium leading-none cursor-pointer"
|
|
||||||
onClick={() => toggleRole(roleName)}
|
|
||||||
>
|
|
||||||
{displayName}
|
{displayName}
|
||||||
</label>
|
</span>
|
||||||
<p className="text-xs text-gray-500 font-mono">
|
<span className="text-xs text-gray-500 font-mono">
|
||||||
{roleName}
|
{roleName}
|
||||||
</p>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</RadioGroup>
|
||||||
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Users, ArrowLeft, Check, Lock, Mail, User, AlertCircle } from 'lucide-r
|
|||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from '@/Components/ui/button';
|
||||||
import { Input } from '@/Components/ui/input';
|
import { Input } from '@/Components/ui/input';
|
||||||
import { Label } from '@/Components/ui/label';
|
import { Label } from '@/Components/ui/label';
|
||||||
import { Checkbox } from '@/Components/ui/checkbox';
|
import { RadioGroup, RadioGroupItem } from '@/Components/ui/radio-group';
|
||||||
import { FormEvent } from 'react';
|
import { FormEvent } from 'react';
|
||||||
|
|
||||||
interface Role {
|
interface Role {
|
||||||
@@ -41,15 +41,6 @@ export default function UserEdit({ user, roles, currentRoles }: Props) {
|
|||||||
put(route('users.update', user.id));
|
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 (
|
return (
|
||||||
<AuthenticatedLayout
|
<AuthenticatedLayout
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
@@ -146,30 +137,37 @@ export default function UserEdit({ user, roles, currentRoles }: Props) {
|
|||||||
|
|
||||||
{/* Roles */}
|
{/* Roles */}
|
||||||
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
|
<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>
|
<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">
|
<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) => (
|
{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">
|
<Label
|
||||||
<Checkbox
|
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}`}
|
id={`role-${role.id}`}
|
||||||
checked={data.roles.includes(role.name)}
|
className="text-primary-main border-gray-300"
|
||||||
onCheckedChange={() => toggleRole(role.name)}
|
|
||||||
/>
|
/>
|
||||||
<div className="grid gap-1 leading-none">
|
<div className="grid gap-1 leading-none">
|
||||||
<label
|
<span className="text-sm font-medium leading-none">
|
||||||
htmlFor={`role-${role.id}`}
|
|
||||||
className="text-sm font-medium leading-none cursor-pointer"
|
|
||||||
onClick={() => toggleRole(role.name)}
|
|
||||||
>
|
|
||||||
{role.display_name}
|
{role.display_name}
|
||||||
</label>
|
</span>
|
||||||
<p className="text-xs text-gray-500 font-mono">
|
<span className="text-xs text-gray-500 font-mono">
|
||||||
{role.name}
|
{role.name}
|
||||||
</p>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</RadioGroup>
|
||||||
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
{errors.roles && <p className="text-sm text-red-500 mt-2">{errors.roles}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user