More user form tweaks

This commit is contained in:
2025-09-02 12:15:35 -04:00
parent a0c442d1af
commit ad1ebeefe1
2 changed files with 149 additions and 143 deletions

View File

@@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Info } from "lucide-react"; import { Info } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
interface Permission { interface Permission {
id: number; id: number;
@@ -21,13 +22,15 @@ interface PermissionSelectorProps {
selectedPermissions: number[]; selectedPermissions: number[];
onChange: (selectedPermissions: number[]) => void; onChange: (selectedPermissions: number[]) => void;
disabled?: boolean; disabled?: boolean;
isAdmin?: boolean;
} }
export function PermissionSelector({ export function PermissionSelector({
permissionsByCategory, permissionsByCategory,
selectedPermissions, selectedPermissions,
onChange, onChange,
disabled = false disabled = false,
isAdmin = false
}: PermissionSelectorProps) { }: PermissionSelectorProps) {
// Handle permission checkbox change // Handle permission checkbox change
const handlePermissionChange = (permissionId: number) => { const handlePermissionChange = (permissionId: number) => {
@@ -68,7 +71,13 @@ export function PermissionSelector({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-lg font-medium">Permissions</h3> <h3 className="text-lg font-medium">Permissions</h3>
{isAdmin && (
<Alert variant="destructive">
<AlertDescription>
Administrators have access to all permissions by default. Individual permissions cannot be edited for admin users.
</AlertDescription>
</Alert>
)}
{permissionsByCategory.map(category => ( {permissionsByCategory.map(category => (
<Card key={category.category} className="mb-4"> <Card key={category.category} className="mb-4">

View File

@@ -66,7 +66,7 @@ const userFormSchema = z.object({
password: z.string().min(6, { message: "Password must be at least 6 characters" }).optional().or(z.literal("")), password: z.string().min(6, { message: "Password must be at least 6 characters" }).optional().or(z.literal("")),
is_admin: z.boolean().default(false), is_admin: z.boolean().default(false),
is_active: z.boolean().default(true), is_active: z.boolean().default(true),
rocket_chat_user_id: z.string().optional(), rocket_chat_user_id: z.string().default("none"),
}); });
type FormValues = z.infer<typeof userFormSchema>; type FormValues = z.infer<typeof userFormSchema>;
@@ -136,20 +136,24 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
} }
}; };
// Ensure rocket_chat_user_id is set to "none" initially if not set
if (!form.getValues().rocket_chat_user_id) {
form.setValue('rocket_chat_user_id', 'none');
}
fetchRocketChatUsers(); fetchRocketChatUsers();
}, []); }, [form]);
// Initialize selected permissions and form values // Initialize selected permissions and form values
useEffect(() => { useEffect(() => {
console.log("User permissions:", user?.permissions);
if (user?.permissions && Array.isArray(user.permissions) && user.permissions.length > 0) { if (user?.permissions && Array.isArray(user.permissions) && user.permissions.length > 0) {
// Extract IDs from the permissions // Extract IDs from the permissions
const permissionIds = user.permissions.map(p => p.id); const permissionIds = user.permissions.map(p => p.id);
console.log("Setting selected permissions:", permissionIds);
setSelectedPermissions(permissionIds); setSelectedPermissions(permissionIds);
} else { } else {
console.log("No permissions found or empty permissions array");
setSelectedPermissions([]); setSelectedPermissions([]);
} }
@@ -163,6 +167,16 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
is_active: user.is_active !== false, is_active: user.is_active !== false,
rocket_chat_user_id: user.rocket_chat_user_id || "none", rocket_chat_user_id: user.rocket_chat_user_id || "none",
}); });
} else {
// For new users, ensure rocket_chat_user_id defaults to "none"
form.reset({
username: "",
email: "",
password: "",
is_admin: false,
is_active: true,
rocket_chat_user_id: "none",
});
} }
}, [user, form]); }, [user, form]);
@@ -170,7 +184,7 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
const onSubmit = (data: FormValues) => { const onSubmit = (data: FormValues) => {
try { try {
setFormError(null); setFormError(null);
console.log("Form submitted with permissions:", selectedPermissions);
// Validate // Validate
if (!user && !data.password) { if (!user && !data.password) {
@@ -214,7 +228,7 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
userData.permissions = []; userData.permissions = [];
} }
console.log("Saving user data:", userData);
onSave(userData); onSave(userData);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "An error occurred"; const errorMessage = error instanceof Error ? error.message : "An error occurred";
@@ -222,12 +236,6 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
} }
}; };
// For debugging
console.log("Current form state:", form.getValues());
console.log("Available permissions categories:", permissions);
console.log("Selected permissions:", selectedPermissions);
console.log("Is admin:", form.watch("is_admin"));
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
@@ -242,105 +250,109 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField {/* Basic Information Section */}
control={form.control} <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
name="username" <FormField
render={({ field }) => ( control={form.control}
<FormItem> name="username"
<FormLabel>Username</FormLabel> render={({ field }) => (
<FormControl> <FormItem>
<Input {...field} /> <FormLabel>Username</FormLabel>
</FormControl> <FormControl>
<FormMessage /> <Input {...field} />
</FormItem> </FormControl>
)} <FormMessage />
/> </FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="email" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>{user ? "New Password" : "Password"}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input
</FormControl> type="password"
<FormMessage /> {...field}
</FormItem> placeholder={user ? "Leave blank to keep current password" : ""}
)} />
/> </FormControl>
<FormField <FormMessage />
control={form.control} </FormItem>
name="rocket_chat_user_id" )}
render={({ field }) => ( />
<FormItem>
<FormLabel>Rocket Chat User</FormLabel> <FormField
<FormControl> control={form.control}
{form.watch("is_admin") ? ( name="rocket_chat_user_id"
<div className="flex items-center gap-2 p-2 border rounded-md bg-muted"> render={({ field }) => (
<span className="text-muted-foreground"> <FormItem>
Admin users have access to all chat rooms by default <FormLabel>Rocket Chat User</FormLabel>
</span> <FormControl>
</div> {form.watch("is_admin") ? (
) : ( <div className="flex items-center gap-2 p-2 border text-sm rounded-md bg-muted">
<Select value={field.value} onValueChange={field.onChange}> <span className="text-muted-foreground">
<SelectTrigger> Admin users have access to all chat rooms by default
<SelectValue placeholder={loadingRocketChatUsers ? "Loading..." : "Select Rocket Chat user..."} /> </span>
</SelectTrigger> </div>
<SelectContent> ) : (
{!loadingRocketChatUsers && ( <Select value={field.value || "none"} onValueChange={field.onChange}>
<SelectItem value="none"> <SelectTrigger>
<span className="text-muted-foreground">None</span> <SelectValue placeholder={loadingRocketChatUsers ? "Loading..." : "Select Rocket Chat user..."} />
</SelectItem> </SelectTrigger>
)} <SelectContent>
{rocketChatUsers.map((rcUser) => ( {!loadingRocketChatUsers && (
<SelectItem key={rcUser.id} value={rcUser.id.toString()}> <SelectItem value="none">
<div className="flex items-center gap-2"> <span className="text-muted-foreground">None</span>
<Avatar className="h-6 w-6"> </SelectItem>
<AvatarImage )}
src={rcUser.mongo_id ? `${config.chatUrl}/avatar/${rcUser.mongo_id}` : undefined} {rocketChatUsers.map((rcUser) => (
alt={rcUser.name || rcUser.username} <SelectItem key={rcUser.id} value={rcUser.id.toString()}>
/> <div className="flex items-center gap-2">
<AvatarFallback className="text-xs"> <Avatar className="h-6 w-6">
{(rcUser.name || rcUser.username).charAt(0).toUpperCase()} <AvatarImage
</AvatarFallback> src={rcUser.mongo_id ? `${config.chatUrl}/avatar/${rcUser.mongo_id}` : undefined}
</Avatar> alt={rcUser.name || rcUser.username}
<span className={rcUser.active ? '' : 'text-muted-foreground'}> />
{rcUser.name || rcUser.username} <AvatarFallback className="text-xs">
{!rcUser.active && <span className="text-xs ml-1">(inactive)</span>} {(rcUser.name || rcUser.username).charAt(0).toUpperCase()}
</span> </AvatarFallback>
</div> </Avatar>
</SelectItem> <span className={rcUser.active ? '' : 'text-muted-foreground'}>
))} {rcUser.name || rcUser.username}
</SelectContent> {!rcUser.active && <span className="text-xs ml-1">(inactive)</span>}
</Select> </span>
)} </div>
</FormControl> </SelectItem>
<FormMessage /> ))}
</FormItem> </SelectContent>
)} </Select>
/> )}
</FormControl>
<FormField <FormMessage />
control={form.control} </FormItem>
name="password" )}
render={({ field }) => ( />
<FormItem> </div>
<FormLabel>{user ? "New Password" : "Password"}</FormLabel>
<FormControl>
<Input
type="password"
{...field}
placeholder={user ? "Leave blank to keep current password" : ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Status Switches - Two Columns */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<FormField <FormField
control={form.control} control={form.control}
@@ -384,41 +396,26 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
)} )}
/> />
</div> </div>
{permissions && permissions.length > 0 && ( {/* Permissions Section */}
<> {permissions && permissions.length > 0 && (
{form.watch("is_admin") ? ( <>
<div className="space-y-4"> <PermissionSelector
permissionsByCategory={permissions}
selectedPermissions={form.watch("is_admin") ? getAllPermissionIds(permissions) : selectedPermissions}
onChange={setSelectedPermissions}
disabled={form.watch("is_admin")}
isAdmin={form.watch("is_admin")}
/>
{!form.watch("is_admin") && selectedPermissions.length === 0 && (
<Alert variant="destructive"> <Alert variant="destructive">
<AlertDescription> <AlertDescription>
Administrators have access to all permissions by default. Individual permissions cannot be edited for admin users. Warning: This user has no permissions selected. They won't be able to access anything.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<PermissionSelector )}
permissionsByCategory={permissions} </>
selectedPermissions={getAllPermissionIds(permissions)} )}
onChange={() => {}}
disabled={true}
/>
</div>
) : (
<>
<PermissionSelector
permissionsByCategory={permissions}
selectedPermissions={selectedPermissions}
onChange={setSelectedPermissions}
/>
{selectedPermissions.length === 0 && (
<Alert>
<AlertDescription>
Warning: This user has no permissions selected. They won't be able to access anything.
</AlertDescription>
</Alert>
)}
</>
)}
</>
)}
<div className="flex justify-end space-x-4"> <div className="flex justify-end space-x-4">
<Button type="button" variant="outline" onClick={onCancel}> <Button type="button" variant="outline" onClick={onCancel}>