| // src/components/admin/AdminGroupsList.tsx | |
| import { useState } from "react"; | |
| import { Trash2, Users, RefreshCw, Calendar } from "lucide-react"; | |
| import { toast } from "react-toastify"; | |
| interface Group { | |
| id: number; | |
| name: string; | |
| description?: string; | |
| memberCount?: number; | |
| lastActivity?: string; | |
| } | |
| interface AdminGroupsListProps { | |
| groups: Group[]; | |
| onDelete: (id: number) => Promise<void>; | |
| } | |
| export function AdminGroupsList({ groups, onDelete }: AdminGroupsListProps) { | |
| const [loadingStates, setLoadingStates] = useState<Record<number, boolean>>({}); | |
| const handleDelete = async (id: number) => { | |
| setLoadingStates((prev) => ({ ...prev, [id]: true })); | |
| try { | |
| await onDelete(id); | |
| toast.success("Group deleted successfully"); | |
| } catch (err) { | |
| console.error(err); | |
| } finally { | |
| setLoadingStates((prev) => ({ ...prev, [id]: false })); | |
| } | |
| }; | |
| if (!groups || groups.length === 0) { | |
| return ( | |
| <div className="text-center py-12 bg-white/40 dark:bg-gray-900/40 backdrop-blur-md border border-gray-200 dark:border-gray-800 rounded-2xl text-gray-500"> | |
| No group chats found. | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| {groups.map((g) => { | |
| const isLoading = loadingStates[g.id] || false; | |
| return ( | |
| <div | |
| key={g.id} | |
| className="bg-white/40 dark:bg-gray-900/40 backdrop-blur-md border border-gray-200/50 dark:border-gray-800/50 rounded-2xl p-5 shadow-lg flex justify-between items-center gap-4 transition duration-200 hover:shadow-xl hover:border-gray-300 dark:hover:border-gray-700" | |
| > | |
| <div className="flex items-center gap-3.5 min-w-0"> | |
| <div className="w-11 h-11 rounded-xl bg-orange-500/10 dark:bg-orange-500/20 text-orange-600 dark:text-orange-400 flex items-center justify-center shrink-0"> | |
| <Users className="w-5 h-5" /> | |
| </div> | |
| <div className="min-w-0"> | |
| <h3 className="font-semibold text-sm truncate text-gray-850 dark:text-gray-150"> | |
| {g.name} | |
| </h3> | |
| {g.description && ( | |
| <p className="text-xs text-gray-500 truncate max-w-[200px] mt-0.5"> | |
| {g.description} | |
| </p> | |
| )} | |
| <div className="flex items-center gap-2 mt-1.5 text-[10px] font-medium text-gray-400"> | |
| <span>{g.memberCount ?? 0} members</span> | |
| <span>•</span> | |
| <span className="flex items-center gap-0.5"> | |
| <Calendar className="w-3 h-3" /> | |
| Active: {g.lastActivity ? new Date(g.lastActivity).toLocaleDateString() : "never"} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="shrink-0"> | |
| {isLoading ? ( | |
| <RefreshCw className="w-4 h-4 text-gray-400 animate-spin" /> | |
| ) : ( | |
| <button | |
| onClick={() => handleDelete(g.id)} | |
| className="p-2 text-gray-400 hover:text-rose-500 hover:bg-rose-50 dark:hover:bg-rose-500/10 rounded-xl transition duration-200" | |
| title="Delete Group" | |
| > | |
| <Trash2 className="w-4.5 h-4.5" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 3.48 kB
- Xet hash:
- bfb3ae8ae776584a01220c984277eacd0bee3bde23d701ce353c1c6bff7c4a67
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.