announcement

This commit is contained in:
alma 2025-05-04 21:39:29 +02:00
parent 3e3653183a
commit 4f63014500
3 changed files with 62 additions and 74 deletions

View File

@ -139,8 +139,8 @@ export function AnnouncementForm({ userRole }: AnnouncementFormProps) {
}; };
return ( return (
<Card> <Card className="bg-white">
<CardHeader> <CardHeader className="bg-white text-gray-800">
<CardTitle>Create New Announcement</CardTitle> <CardTitle>Create New Announcement</CardTitle>
<CardDescription> <CardDescription>
Create an announcement to be displayed to specific user roles Create an announcement to be displayed to specific user roles
@ -154,9 +154,13 @@ export function AnnouncementForm({ userRole }: AnnouncementFormProps) {
name="title" name="title"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Title</FormLabel> <FormLabel className="text-gray-700">Title</FormLabel>
<FormControl> <FormControl>
<Input placeholder="Enter announcement title" {...field} /> <Input
placeholder="Enter announcement title"
className="bg-white text-gray-800 border-gray-300"
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -168,11 +172,12 @@ export function AnnouncementForm({ userRole }: AnnouncementFormProps) {
name="content" name="content"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Content</FormLabel> <FormLabel className="text-gray-700">Content</FormLabel>
<FormControl> <FormControl>
<Textarea <Textarea
placeholder="Enter announcement content" placeholder="Enter announcement content"
rows={5} rows={5}
className="bg-white text-gray-800 border-gray-300"
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -186,26 +191,29 @@ export function AnnouncementForm({ userRole }: AnnouncementFormProps) {
name="targetRoles" name="targetRoles"
render={() => ( render={() => (
<FormItem> <FormItem>
<FormLabel>Target Audience</FormLabel> <FormLabel className="text-gray-700">Target Audience</FormLabel>
<FormControl> <FormControl>
<div className="flex flex-wrap gap-2"> <div className="p-3 border border-gray-200 rounded-md bg-gray-50">
{availableRoles.map(role => ( <p className="text-sm text-gray-500 mb-2">Select which roles can see this announcement:</p>
<Badge <div className="flex flex-wrap gap-2">
key={role.id} {availableRoles.map(role => (
variant={selectedRoles.includes(role.id) ? "default" : "outline"} <Badge
className={`cursor-pointer ${ key={role.id}
selectedRoles.includes(role.id) variant={selectedRoles.includes(role.id) ? "default" : "outline"}
? "bg-blue-600 hover:bg-blue-700" className={`cursor-pointer px-3 py-1 ${
: "hover:bg-gray-100" selectedRoles.includes(role.id)
}`} ? "bg-blue-600 hover:bg-blue-700"
onClick={() => handleRoleToggle(role.id)} : "hover:bg-gray-100"
> }`}
{role.name} onClick={() => handleRoleToggle(role.id)}
{selectedRoles.includes(role.id) && ( >
<CheckIcon className="ml-1 h-3 w-3" /> {role.name}
)} {selectedRoles.includes(role.id) && (
</Badge> <CheckIcon className="ml-1 h-3 w-3" />
))} )}
</Badge>
))}
</div>
</div> </div>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -213,21 +221,27 @@ export function AnnouncementForm({ userRole }: AnnouncementFormProps) {
)} )}
/> />
<Button type="submit" disabled={isSubmitting || isSuccess} className="w-full"> <div className="flex justify-end">
{isSubmitting ? ( <Button
<> type="submit"
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> disabled={isSubmitting || isSuccess}
Submitting... className="px-4"
</> >
) : isSuccess ? ( {isSubmitting ? (
<> <>
<CheckIcon className="mr-2 h-4 w-4" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
Announcement Created! Submitting...
</> </>
) : ( ) : isSuccess ? (
"Create Announcement" <>
)} <CheckIcon className="mr-2 h-4 w-4" />
</Button> Announcement Created!
</>
) : (
"Create Announcement"
)}
</Button>
</div>
</form> </form>
</Form> </Form>
</CardContent> </CardContent>

View File

@ -73,7 +73,7 @@ export function AnnouncementsDropdown() {
onValueChange={handleAnnouncementChange} onValueChange={handleAnnouncementChange}
defaultValue={selectedAnnouncement?.id} defaultValue={selectedAnnouncement?.id}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full bg-white text-gray-800 border-gray-300">
<SelectValue placeholder="Select an announcement" /> <SelectValue placeholder="Select an announcement" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -87,15 +87,15 @@ export function AnnouncementsDropdown() {
</div> </div>
{selectedAnnouncement && ( {selectedAnnouncement && (
<Card className="w-full"> <Card className="w-full bg-white">
<CardHeader> <CardHeader className="bg-white text-gray-800">
<CardTitle>{selectedAnnouncement.title}</CardTitle> <CardTitle>{selectedAnnouncement.title}</CardTitle>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
Posted by {selectedAnnouncement.author.email} on {new Date(selectedAnnouncement.createdAt).toLocaleDateString()} Posted by {selectedAnnouncement.author.email} on {new Date(selectedAnnouncement.createdAt).toLocaleDateString()}
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="prose"> <div className="prose max-h-[300px] overflow-y-auto pr-2">
{selectedAnnouncement.content} {selectedAnnouncement.content}
</div> </div>
</CardContent> </CardContent>

View File

@ -19,15 +19,10 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
Pencil,
Trash2, Trash2,
Eye, Eye,
Search,
Plus,
Filter,
AlertTriangle AlertTriangle
} from "lucide-react"; } from "lucide-react";
import { Input } from "@/components/ui/input";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -46,7 +41,6 @@ interface AnnouncementsListProps {
export function AnnouncementsList({ userRole }: AnnouncementsListProps) { export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
const [announcements, setAnnouncements] = useState<Announcement[]>([]); const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null); const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null);
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false); const [isViewDialogOpen, setIsViewDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
@ -79,13 +73,6 @@ export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
fetchAnnouncements(); fetchAnnouncements();
}, []); }, []);
// Filter announcements based on search term
const filteredAnnouncements = announcements.filter(
announcement =>
announcement.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
announcement.content.toLowerCase().includes(searchTerm.toLowerCase())
);
// Handle viewing an announcement // Handle viewing an announcement
const handleViewAnnouncement = (announcement: Announcement) => { const handleViewAnnouncement = (announcement: Announcement) => {
setSelectedAnnouncement(announcement); setSelectedAnnouncement(announcement);
@ -156,20 +143,7 @@ export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{/* Search and filter */} {/* Announcements table with scroll */}
<div className="mb-4 flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500" />
<Input
placeholder="Search announcements..."
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
{/* Announcements table */}
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-40"> <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 className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
@ -179,14 +153,14 @@ export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
{error} {error}
<Button onClick={fetchAnnouncements} className="ml-4">Retry</Button> <Button onClick={fetchAnnouncements} className="ml-4">Retry</Button>
</div> </div>
) : filteredAnnouncements.length === 0 ? ( ) : announcements.length === 0 ? (
<div className="text-center py-10 text-gray-500"> <div className="text-center py-10 text-gray-500">
No announcements found No announcements found
</div> </div>
) : ( ) : (
<div className="border rounded-md"> <div className="border rounded-md max-h-[500px] overflow-y-auto">
<Table> <Table>
<TableHeader> <TableHeader className="sticky top-0 bg-white z-10">
<TableRow> <TableRow>
<TableHead>Title</TableHead> <TableHead>Title</TableHead>
<TableHead>Created</TableHead> <TableHead>Created</TableHead>
@ -196,7 +170,7 @@ export function AnnouncementsList({ userRole }: AnnouncementsListProps) {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredAnnouncements.map((announcement) => ( {announcements.map((announcement) => (
<TableRow key={announcement.id}> <TableRow key={announcement.id}>
<TableCell className="font-medium">{announcement.title}</TableCell> <TableCell className="font-medium">{announcement.title}</TableCell>
<TableCell>{new Date(announcement.createdAt).toLocaleDateString()}</TableCell> <TableCell>{new Date(announcement.createdAt).toLocaleDateString()}</TableCell>