feat: implement configuration management with CRUD operations

- Added configuration management page with table view and modal forms for adding/editing configurations.
- Integrated Zustand for state management of configurations.
- Implemented user management drawer for organizations with user addition/removal functionality.
- Created user management page with CRUD operations for users.
- Introduced admin store for user-related actions including user creation, deletion, and updates.
- Developed reusable drawer component for UI consistency across user management.
- Enhanced error handling and user feedback with toast notifications.
This commit is contained in:
2026-01-26 20:51:35 +08:00
parent e8e2765c27
commit 30388533c0
14 changed files with 1471 additions and 3 deletions

209
src/app/org/page.tsx Normal file
View File

@@ -0,0 +1,209 @@
'use client';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useOrgStore } from './store';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Plus, Pencil, Trash2, Users } from 'lucide-react';
import { LayoutMain } from '@/modules/layout';
import { UserDrawer } from './components/UserDrawer';
const TableList = () => {
const { list, setShowEdit, setFormData, deleteData, setOrgId } = useOrgStore();
const [userDrawerOpen, setUserDrawerOpen] = useState(false);
const handleOpenUserDrawer = (org: any) => {
setOrgId(org.id);
setUserDrawerOpen(true);
};
return (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{list.map((org) => (
<TableRow key={org.id}>
<TableCell>{org.username}</TableCell>
<TableCell>{org.description || '-'}</TableCell>
<TableCell>{org.createdAt ? new Date(org.createdAt).toLocaleString() : '-'}</TableCell>
<TableCell>{org.updatedAt ? new Date(org.updatedAt).toLocaleString() : '-'}</TableCell>
<TableCell className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleOpenUserDrawer(org)}>
<Users className="w-4 h-4 mr-1" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
setShowEdit(true);
setFormData(org);
}}>
<Pencil className="w-4 h-4 mr-1" />
</Button>
<Popover>
<PopoverTrigger asChild>
<Button
variant="destructive"
size="sm">
<Trash2 className="w-4 h-4 mr-1" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-48 p-2">
<div className="text-sm text-center mb-2"></div>
<div className="flex gap-2 justify-center">
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation();
deleteData(org.id);
}}>
</Button>
<Button
variant="ghost"
size="sm"
onClick={(e) => e.stopPropagation()}>
</Button>
</div>
</PopoverContent>
</Popover>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<UserDrawer open={userDrawerOpen} onOpenChange={setUserDrawerOpen} />
</div>
);
};
const FormModal = () => {
const { showEdit, setShowEdit, formData, setFormData, updateData } = useOrgStore();
const {
handleSubmit,
formState: { errors },
reset,
register,
} = useForm();
useEffect(() => {
if (!showEdit) return;
if (formData?.id) {
reset(formData);
} else {
reset({ username: '', description: '' });
}
}, [formData, showEdit, reset]);
const onSubmit = async (data: any) => {
const res = await updateData(data);
if (res.code === 200) {
setShowEdit(false);
setFormData({});
}
};
return (
<Dialog open={showEdit} onOpenChange={(open) => {
setShowEdit(open);
if (!open) setFormData({});
}}>
<DialogContent>
<DialogHeader>
<DialogTitle>{formData?.id ? '编辑组织' : '添加组织'}</DialogTitle>
</DialogHeader>
<div className="p-4 ">
<form className="w-full flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium"></label>
<Input
{...register('username', { required: '请输入名称' })}
placeholder="请输入名称"
className={errors.username ? "border-red-500" : ""}
/>
{errors.username && <span className="text-xs text-red-500">{errors.username.message as string}</span>}
</div>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium"></label>
<Input
{...register('description')}
placeholder="请输入描述"
/>
</div>
<div className="flex gap-2 justify-end">
<Button type="button" variant="outline" onClick={() => setShowEdit(false)}>
</Button>
<Button type="submit"></Button>
</div>
</form>
</div>
</DialogContent>
</Dialog>
);
};
export const List = () => {
const { getList, setShowEdit, setFormData } = useOrgStore();
useEffect(() => {
getList();
}, [getList]);
return (
<div className="p-4 w-full h-full overflow-auto">
<div className="flex mb-4">
<Button
onClick={() => {
setShowEdit(true);
setFormData({});
}}>
<Plus className="w-4 h-4 mr-1" />
</Button>
</div>
<TableList />
<FormModal />
</div>
);
};
export default () => {
return <LayoutMain><List /></LayoutMain>;
}