Skip to content

Commit 824df47

Browse files
committed
FEAT: Added the new admin page + new permissions model.
1 parent f981cef commit 824df47

File tree

12 files changed

+1124
-40
lines changed

12 files changed

+1124
-40
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { useState } from 'react';
2+
import { useToast } from '@/hooks/use-toast';
3+
import { Input } from '@/components/ui/input';
4+
import { Button } from '@/components/ui/button';
5+
import { Trash2, Edit, PlusCircle } from 'lucide-react';
6+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
7+
import { useTeams, useCreateTeam, useUpdateTeam, useDeleteTeam } from '@/hooks/api/useTeams';
8+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
9+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
10+
11+
const TeamManagement = () => {
12+
const { data: teams = [], isLoading } = useTeams();
13+
const { toast } = useToast();
14+
15+
const createTeamMutation = useCreateTeam();
16+
const updateTeamMutation = useUpdateTeam();
17+
const deleteTeamMutation = useDeleteTeam();
18+
19+
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
20+
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
21+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
22+
23+
const [newTeamName, setNewTeamName] = useState('');
24+
const [editingTeam, setEditingTeam] = useState<{ id: string; name: string } | null>(null);
25+
const [deletingTeam, setDeletingTeam] = useState<{ id: string; name: string } | null>(null);
26+
27+
const handleCreate = async () => {
28+
if (!newTeamName.trim()) {
29+
toast({ title: 'Error', description: 'Team name cannot be empty.', variant: 'destructive', duration: 2000 });
30+
return;
31+
}
32+
try {
33+
await createTeamMutation.mutateAsync(newTeamName);
34+
toast({ title: 'Success', description: 'Team created successfully.' });
35+
36+
setIsCreateDialogOpen(false);
37+
setNewTeamName('');
38+
}
39+
catch (error: any) {
40+
toast({ title: 'Error', description: error.message, variant: 'destructive' });
41+
}
42+
};
43+
44+
const handleEdit = (team: { id: string; name: string }) => {
45+
setEditingTeam(team);
46+
setNewTeamName(team.name);
47+
setIsEditDialogOpen(true);
48+
};
49+
50+
const handleUpdate = async () => {
51+
if (!editingTeam || !newTeamName.trim()) {
52+
toast({ title: 'Error', description: 'Team name cannot be empty.', variant: 'destructive' });
53+
return;
54+
}
55+
56+
try {
57+
await updateTeamMutation.mutateAsync({ teamId: editingTeam.id, newName: newTeamName });
58+
59+
toast({ title: 'Success', description: 'Team updated successfully.' });
60+
setIsEditDialogOpen(false);
61+
setEditingTeam(null);
62+
setNewTeamName('');
63+
}
64+
catch (error: any) {
65+
toast({ title: 'Error', description: error.message, variant: 'destructive' });
66+
}
67+
};
68+
69+
const handleDelete = (team: { id: string; name: string }) => {
70+
setDeletingTeam(team);
71+
setIsDeleteDialogOpen(true);
72+
};
73+
74+
const confirmDelete = async () => {
75+
if (!deletingTeam) return;
76+
77+
try {
78+
await deleteTeamMutation.mutateAsync(deletingTeam.id);
79+
80+
toast({ title: 'Success', description: 'Team deleted successfully.' });
81+
setIsDeleteDialogOpen(false);
82+
setDeletingTeam(null);
83+
}
84+
catch (error: any) {
85+
toast({ title: 'Error', description: error.message, variant: 'destructive' });
86+
}
87+
};
88+
89+
return (
90+
<Card>
91+
<CardHeader>
92+
<CardTitle className="flex justify-between items-center">
93+
Teams
94+
<Button onClick={() => setIsCreateDialogOpen(true)}>
95+
<PlusCircle className="mr-2 h-4 w-4" /> Create Team
96+
</Button>
97+
</CardTitle>
98+
</CardHeader>
99+
100+
<CardContent>
101+
<Table>
102+
<TableHeader>
103+
<TableRow>
104+
<TableHead>Team Name</TableHead>
105+
<TableHead className="text-right">Actions</TableHead>
106+
</TableRow>
107+
</TableHeader>
108+
109+
<TableBody>
110+
{isLoading ? (
111+
<TableRow>
112+
<TableCell colSpan={2}>Loading teams...</TableCell>
113+
</TableRow>
114+
) : teams.length === 0 ? (
115+
<TableRow>
116+
<TableCell colSpan={2}>No teams found.</TableCell>
117+
</TableRow>
118+
) : (
119+
teams.map(team => (
120+
<TableRow key={team.id}>
121+
<TableCell className="font-medium">{team.name}</TableCell>
122+
123+
<TableCell className="text-right">
124+
<Button variant="ghost" size="icon" onClick={() => handleEdit(team)}>
125+
<Edit className="h-4 w-4" />
126+
</Button>
127+
128+
<Button variant="ghost" size="icon" onClick={() => handleDelete(team)} className="text-red-500">
129+
<Trash2 className="h-4 w-4" />
130+
</Button>
131+
</TableCell>
132+
</TableRow>
133+
))
134+
)}
135+
</TableBody>
136+
</Table>
137+
</CardContent>
138+
139+
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
140+
<DialogContent className="max-w-3xl">
141+
<DialogHeader>
142+
<DialogTitle>Create New Team</DialogTitle>
143+
</DialogHeader>
144+
145+
<div className="py-4">
146+
<Input placeholder="Team name" value={newTeamName} onChange={e => setNewTeamName(e.target.value)} />
147+
</div>
148+
149+
<DialogFooter>
150+
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
151+
Cancel
152+
</Button>
153+
154+
<Button onClick={handleCreate} disabled={createTeamMutation.isPending}>
155+
{createTeamMutation.isPending ? 'Creating...' : 'Create'}
156+
</Button>
157+
</DialogFooter>
158+
</DialogContent>
159+
</Dialog>
160+
161+
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
162+
<DialogContent className="max-w-3xl">
163+
<DialogHeader>
164+
<DialogTitle>Edit Team: {editingTeam?.name}</DialogTitle>
165+
</DialogHeader>
166+
167+
<div className="py-4">
168+
<Input placeholder="New team name" value={newTeamName} onChange={e => setNewTeamName(e.target.value)} />
169+
</div>
170+
171+
<DialogFooter>
172+
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>
173+
Cancel
174+
</Button>
175+
176+
<Button onClick={handleUpdate} disabled={updateTeamMutation.isPending}>
177+
{updateTeamMutation.isPending ? 'Updating...' : 'Update'}
178+
</Button>
179+
</DialogFooter>
180+
</DialogContent>
181+
</Dialog>
182+
183+
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
184+
<DialogContent className="max-w-3xl">
185+
<DialogHeader>
186+
<DialogTitle>Are you sure?</DialogTitle>
187+
188+
<DialogDescription className="py-1 leading-6">
189+
This will permanently delete the team "{deletingTeam?.name}". This action will remove the team from all users
190+
and cannot be undone.
191+
</DialogDescription>
192+
</DialogHeader>
193+
194+
<DialogFooter>
195+
<Button variant="outline" onClick={() => setIsDeleteDialogOpen(false)}>
196+
Cancel
197+
</Button>
198+
199+
<Button variant="destructive" onClick={confirmDelete} disabled={deleteTeamMutation.isPending}>
200+
{deleteTeamMutation.isPending ? 'Deleting...' : 'Delete'}
201+
</Button>
202+
</DialogFooter>
203+
</DialogContent>
204+
</Dialog>
205+
</Card>
206+
);
207+
};
208+
209+
export default TeamManagement;

0 commit comments

Comments
 (0)