NeahNew/components/announcement/announcements-list.tsx
2025-05-04 22:42:54 +02:00

252 lines
8.3 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Trash2,
Eye,
AlertTriangle
} from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
DialogTrigger
} from "@/components/ui/dialog";
import { Announcement } from "@/app/types/announcement";
import { useToast } from "@/components/ui/use-toast";
interface AnnouncementsListProps {
userRole: string[];
}
export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null);
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { toast } = useToast();
// Fetch announcements
const fetchAnnouncements = async () => {
try {
setLoading(true);
const response = await fetch('/api/announcements');
if (!response.ok) {
throw new Error('Failed to fetch announcements');
}
const data = await response.json();
setAnnouncements(data);
setError(null);
} catch (err) {
console.error('Error fetching announcements:', err);
setError('Failed to load announcements');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchAnnouncements();
}, []);
// Handle viewing an announcement
const handleViewAnnouncement = (announcement: Announcement) => {
setSelectedAnnouncement(announcement);
setIsViewDialogOpen(true);
};
// Handle deleting an announcement
const handleDeleteClick = (announcement: Announcement) => {
setSelectedAnnouncement(announcement);
setIsDeleteDialogOpen(true);
};
const confirmDelete = async () => {
if (!selectedAnnouncement) return;
try {
const response = await fetch(`/api/announcements/${selectedAnnouncement.id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete announcement');
}
// Update the local state
setAnnouncements(announcements.filter(a => a.id !== selectedAnnouncement.id));
setIsDeleteDialogOpen(false);
toast({
title: "Announcement deleted",
description: "The announcement has been deleted successfully.",
});
} catch (err) {
console.error('Error deleting announcement:', err);
toast({
title: "Error",
description: "Failed to delete the announcement. Please try again.",
variant: "destructive",
});
}
};
// Format roles for display
const formatRoles = (roles: string[]) => {
return roles.map(role => {
const roleName = role === "all"
? "All Users"
: role.charAt(0).toUpperCase() + role.slice(1);
return (
<Badge key={role} variant="outline" className="mr-1">
{roleName}
</Badge>
);
});
};
return (
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>All Announcements</CardTitle>
<CardDescription>
Manage announcements for different user roles
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
{/* Announcements table with scroll */}
{loading ? (
<div className="flex items-center justify-center h-40">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
) : error ? (
<div className="text-center py-10 text-red-500">
{error}
<Button onClick={fetchAnnouncements} className="ml-4">Retry</Button>
</div>
) : announcements.length === 0 ? (
<div className="text-center py-10 text-gray-500">
No announcements found
</div>
) : (
<div className="border rounded-md max-h-[500px] overflow-y-auto">
<Table>
<TableHeader className="sticky top-0 bg-white z-10">
<TableRow>
<TableHead>Title</TableHead>
<TableHead>Created</TableHead>
<TableHead>Author</TableHead>
<TableHead>Target Roles</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{announcements.map((announcement) => (
<TableRow key={announcement.id}>
<TableCell className="font-medium">{announcement.title}</TableCell>
<TableCell>{new Date(announcement.createdAt).toLocaleDateString()}</TableCell>
<TableCell>{announcement.author.email}</TableCell>
<TableCell>{formatRoles(announcement.targetRoles)}</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleViewAnnouncement(announcement)}
>
<Eye className="h-4 w-4" />
<span className="sr-only">View</span>
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteClick(announcement)}
>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Delete</span>
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{/* View Announcement Dialog */}
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
<DialogContent className="sm:max-w-xl">
<DialogHeader>
<DialogTitle>{selectedAnnouncement?.title}</DialogTitle>
<DialogDescription>
Posted by {selectedAnnouncement?.author.email} on {selectedAnnouncement && new Date(selectedAnnouncement.createdAt).toLocaleDateString()}
</DialogDescription>
</DialogHeader>
<div className="mt-4">
<div className="mb-2">
<span className="text-sm text-gray-500">Target Audience:</span>{" "}
{selectedAnnouncement && formatRoles(selectedAnnouncement.targetRoles)}
</div>
<p className="text-sm leading-6 text-gray-700">
{selectedAnnouncement?.content}
</p>
</div>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertTriangle className="text-red-500 h-5 w-5" />
Confirm Deletion
</DialogTitle>
<DialogDescription>
Are you sure you want to delete the announcement "{selectedAnnouncement?.title}"? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-4">
<Button variant="outline" onClick={() => setIsDeleteDialogOpen(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={confirmDelete}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</CardContent>
</Card>
);
}