fix(audit-wave-9): standardize on Sheet for previews; doctrine in CLAUDE.md
Swap the one outlier (client-interests-tab.tsx) from Vaul Drawer to Sheet side=right so every detail-preview surface uses the same primitive. Document the doctrine: Sheet for side panels on both desktop and mobile; Vaul Drawer reserved for mobile-only bottom-sheet UX (currently just MoreSheet). Closes ui/ux M11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
import { formatErrorBanner } from '@/lib/api/toast-error';
|
||||
import { formatEnum } from '@/lib/constants';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -120,7 +121,7 @@ const GROUP_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
function formatAction(action: string): string {
|
||||
return action.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
return formatEnum(action);
|
||||
}
|
||||
|
||||
interface RoleFormProps {
|
||||
@@ -136,41 +137,36 @@ interface RoleFormProps {
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function RoleForm({ open, onOpenChange, role, onSuccess }: RoleFormProps) {
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [permissions, setPermissions] = useState<Record<string, Record<string, boolean>>>(
|
||||
structuredClone(DEFAULT_PERMISSIONS),
|
||||
export function RoleForm(props: RoleFormProps) {
|
||||
return (
|
||||
<RoleFormBody key={props.open ? `open:${props.role?.id ?? 'new'}` : 'closed'} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function RoleFormBody({ open, onOpenChange, role, onSuccess }: RoleFormProps) {
|
||||
// Merge role permissions over defaults to fill any missing keys.
|
||||
const initialPermissions = (() => {
|
||||
const merged = structuredClone(DEFAULT_PERMISSIONS);
|
||||
if (role) {
|
||||
for (const [group, actions] of Object.entries(role.permissions)) {
|
||||
if (merged[group]) {
|
||||
for (const [action, value] of Object.entries(actions as Record<string, boolean>)) {
|
||||
merged[group]![action] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
})();
|
||||
const [name, setName] = useState(role?.name ?? '');
|
||||
const [description, setDescription] = useState(role?.description ?? '');
|
||||
const [permissions, setPermissions] =
|
||||
useState<Record<string, Record<string, boolean>>>(initialPermissions);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const isEdit = !!role;
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (role) {
|
||||
setName(role.name);
|
||||
setDescription(role.description ?? '');
|
||||
// Merge role permissions over defaults to fill any missing keys
|
||||
const merged = structuredClone(DEFAULT_PERMISSIONS);
|
||||
for (const [group, actions] of Object.entries(role.permissions)) {
|
||||
if (merged[group]) {
|
||||
for (const [action, value] of Object.entries(actions as Record<string, boolean>)) {
|
||||
merged[group]![action] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
setPermissions(merged);
|
||||
} else {
|
||||
setName('');
|
||||
setDescription('');
|
||||
setPermissions(structuredClone(DEFAULT_PERMISSIONS));
|
||||
}
|
||||
setError(null);
|
||||
}
|
||||
}, [open, role]);
|
||||
|
||||
function togglePermission(group: string, action: string) {
|
||||
setPermissions((prev) => {
|
||||
const next = structuredClone(prev);
|
||||
|
||||
Reference in New Issue
Block a user