Spaces:
Paused
Paused
Download src/components/NotificationCenter.tsx from yousseftallal/frontend: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/spaces/yousseftallal/frontend/resolve/main/src/components/NotificationCenter.tsx
- Command line
-
hf download hf://spaces/yousseftallal/frontend/src/components/NotificationCenter.tsx
-
curl -L -o NotificationCenter.tsx https://huggingface.co/spaces/yousseftallal/frontend/resolve/main/src/components/NotificationCenter.tsx
17.2 kB
| import { useState } from 'react'; | |
| import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; | |
| import { Button } from './ui/button'; | |
| import { Badge } from './ui/badge'; | |
| import { Switch } from './ui/switch'; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; | |
| import { | |
| Bell, | |
| AlertTriangle, | |
| Info, | |
| Check, | |
| X, | |
| Mail, | |
| MessageSquare, | |
| Smartphone | |
| } from 'lucide-react'; | |
| import { User } from '../types'; | |
| import { mockNotifications } from '../mockData'; | |
| interface NotificationCenterProps { | |
| user: User; | |
| } | |
| export default function NotificationCenter({ user }: NotificationCenterProps) { | |
| const [notifications, setNotifications] = useState(mockNotifications); | |
| const [filter, setFilter] = useState<'all' | 'unread' | 'critical'>('all'); | |
| // Notification preferences | |
| const [emailEnabled, setEmailEnabled] = useState(true); | |
| const [smsEnabled, setSmsEnabled] = useState(true); | |
| const [whatsappEnabled, setWhatsappEnabled] = useState(false); | |
| const [criticalAlerts, setCriticalAlerts] = useState(true); | |
| const [inventoryAlerts, setInventoryAlerts] = useState(true); | |
| const [healthAlerts, setHealthAlerts] = useState(true); | |
| const filteredNotifications = notifications.filter(notif => { | |
| if (filter === 'unread') return !notif.read; | |
| if (filter === 'critical') return notif.priority === 'critical'; | |
| return true; | |
| }); | |
| const unreadCount = notifications.filter(n => !n.read).length; | |
| const criticalCount = notifications.filter(n => n.priority === 'critical' && !n.read).length; | |
| const handleMarkAsRead = (id: string) => { | |
| setNotifications(notifications.map(n => | |
| n.id === id ? { ...n, read: true } : n | |
| )); | |
| }; | |
| const handleMarkAllAsRead = () => { | |
| setNotifications(notifications.map(n => ({ ...n, read: true }))); | |
| }; | |
| const handleDelete = (id: string) => { | |
| setNotifications(notifications.filter(n => n.id !== id)); | |
| }; | |
| const getNotificationIcon = (type: string) => { | |
| switch (type) { | |
| case 'alert': return AlertTriangle; | |
| case 'warning': return AlertTriangle; | |
| case 'info': return Info; | |
| default: return Bell; | |
| } | |
| }; | |
| const getNotificationColor = (priority: string) => { | |
| switch (priority) { | |
| case 'critical': return 'bg-red-50 border-red-200'; | |
| case 'high': return 'bg-orange-50 border-orange-200'; | |
| case 'medium': return 'bg-yellow-50 border-yellow-200'; | |
| case 'low': return 'bg-blue-50 border-blue-200'; | |
| default: return 'bg-gray-50 border-gray-200'; | |
| } | |
| }; | |
| const getPriorityBadgeColor = (priority: string) => { | |
| switch (priority) { | |
| case 'critical': return 'bg-red-100 text-red-800'; | |
| case 'high': return 'bg-orange-100 text-orange-800'; | |
| case 'medium': return 'bg-yellow-100 text-yellow-800'; | |
| case 'low': return 'bg-blue-100 text-blue-800'; | |
| default: return 'bg-gray-100 text-gray-800'; | |
| } | |
| }; | |
| const getTimeAgo = (timestamp: string) => { | |
| const now = new Date(); | |
| const time = new Date(timestamp); | |
| const diffInHours = Math.floor((now.getTime() - time.getTime()) / (1000 * 60 * 60)); | |
| if (diffInHours < 1) return 'Just now'; | |
| if (diffInHours < 24) return `${diffInHours}h ago`; | |
| const diffInDays = Math.floor(diffInHours / 24); | |
| return `${diffInDays}d ago`; | |
| }; | |
| return ( | |
| <div className="p-6 space-y-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <h1 className="text-2xl">Notification Center</h1> | |
| <p className="text-gray-600">Manage alerts and communication preferences</p> | |
| </div> | |
| <Button onClick={handleMarkAllAsRead} variant="outline"> | |
| <Check className="w-4 h-4 mr-2" /> | |
| Mark All Read | |
| </Button> | |
| </div> | |
| {/* Summary Cards */} | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <Card> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | |
| <CardTitle className="text-sm">Total Notifications</CardTitle> | |
| <Bell className="w-4 h-4 text-blue-600" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl">{notifications.length}</div> | |
| <p className="text-xs text-gray-600 mt-1">{unreadCount} unread</p> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | |
| <CardTitle className="text-sm">Critical Alerts</CardTitle> | |
| <AlertTriangle className="w-4 h-4 text-red-600" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl text-red-600">{criticalCount}</div> | |
| <p className="text-xs text-gray-600 mt-1">Require attention</p> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2"> | |
| <CardTitle className="text-sm">Active Channels</CardTitle> | |
| <Mail className="w-4 h-4 text-green-600" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl">{[emailEnabled, smsEnabled, whatsappEnabled].filter(Boolean).length}</div> | |
| <p className="text-xs text-gray-600 mt-1">Communication methods</p> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| <Tabs defaultValue="notifications" className="space-y-4"> | |
| <TabsList> | |
| <TabsTrigger value="notifications"> | |
| <Bell className="w-4 h-4 mr-2" /> | |
| Notifications | |
| </TabsTrigger> | |
| <TabsTrigger value="preferences"> | |
| Preferences | |
| </TabsTrigger> | |
| </TabsList> | |
| {/* Notifications Tab */} | |
| <TabsContent value="notifications" className="space-y-4"> | |
| <Card> | |
| <CardHeader> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant={filter === 'all' ? 'default' : 'outline'} | |
| size="sm" | |
| onClick={() => setFilter('all')} | |
| > | |
| All ({notifications.length}) | |
| </Button> | |
| <Button | |
| variant={filter === 'unread' ? 'default' : 'outline'} | |
| size="sm" | |
| onClick={() => setFilter('unread')} | |
| > | |
| Unread ({unreadCount}) | |
| </Button> | |
| <Button | |
| variant={filter === 'critical' ? 'default' : 'outline'} | |
| size="sm" | |
| onClick={() => setFilter('critical')} | |
| > | |
| Critical ({criticalCount}) | |
| </Button> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-2"> | |
| {filteredNotifications.map(notification => { | |
| const Icon = getNotificationIcon(notification.type); | |
| return ( | |
| <div | |
| key={notification.id} | |
| className={`p-4 border rounded-lg transition-all ${ | |
| getNotificationColor(notification.priority) | |
| } ${!notification.read ? 'border-l-4' : ''}`} | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className={`mt-0.5 ${ | |
| notification.priority === 'critical' ? 'text-red-600' : | |
| notification.priority === 'high' ? 'text-orange-600' : | |
| notification.priority === 'medium' ? 'text-yellow-600' : | |
| 'text-blue-600' | |
| }`}> | |
| <Icon className="w-5 h-5" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="flex items-start justify-between mb-1"> | |
| <div className="flex items-center gap-2 flex-1"> | |
| <p className={`text-sm ${!notification.read ? 'font-medium' : ''}`}> | |
| {notification.title} | |
| </p> | |
| <Badge className={getPriorityBadgeColor(notification.priority)} variant="outline"> | |
| {notification.priority} | |
| </Badge> | |
| {!notification.read && ( | |
| <div className="w-2 h-2 bg-blue-600 rounded-full" /> | |
| )} | |
| </div> | |
| <span className="text-xs text-gray-500 whitespace-nowrap ml-2"> | |
| {getTimeAgo(notification.timestamp)} | |
| </span> | |
| </div> | |
| <p className="text-sm text-gray-700 mb-2"> | |
| {notification.message} | |
| </p> | |
| <div className="flex items-center gap-2"> | |
| {!notification.read && ( | |
| <Button | |
| size="sm" | |
| variant="outline" | |
| onClick={() => handleMarkAsRead(notification.id)} | |
| > | |
| <Check className="w-3 h-3 mr-1" /> | |
| Mark Read | |
| </Button> | |
| )} | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| onClick={() => handleDelete(notification.id)} | |
| > | |
| <X className="w-3 h-3 mr-1" /> | |
| Dismiss | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| {filteredNotifications.length === 0 && ( | |
| <div className="text-center py-12"> | |
| <Bell className="w-12 h-12 text-gray-400 mx-auto mb-3" /> | |
| <p className="text-gray-600">No notifications</p> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| {filter === 'unread' | |
| ? "You're all caught up!" | |
| : filter === 'critical' | |
| ? "No critical alerts at this time" | |
| : "You'll see notifications here"} | |
| </p> | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </TabsContent> | |
| {/* Preferences Tab */} | |
| <TabsContent value="preferences" className="space-y-4"> | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="text-sm">Notification Channels</CardTitle> | |
| <p className="text-xs text-gray-600"> | |
| Choose how you want to receive alerts | |
| </p> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <Mail className="w-5 h-5 text-blue-600" /> | |
| <div> | |
| <p className="text-sm">Email Notifications</p> | |
| <p className="text-xs text-gray-600">{user.email}</p> | |
| </div> | |
| </div> | |
| <Switch checked={emailEnabled} onCheckedChange={setEmailEnabled} /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <Smartphone className="w-5 h-5 text-green-600" /> | |
| <div> | |
| <p className="text-sm">SMS Alerts</p> | |
| <p className="text-xs text-gray-600">{user.phone}</p> | |
| </div> | |
| </div> | |
| <Switch checked={smsEnabled} onCheckedChange={setSmsEnabled} /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <MessageSquare className="w-5 h-5 text-green-700" /> | |
| <div> | |
| <p className="text-sm">WhatsApp</p> | |
| <p className="text-xs text-gray-600">{user.phone}</p> | |
| </div> | |
| </div> | |
| <Switch checked={whatsappEnabled} onCheckedChange={setWhatsappEnabled} /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="text-sm">Alert Types</CardTitle> | |
| <p className="text-xs text-gray-600"> | |
| Customize which alerts you want to receive | |
| </p> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <AlertTriangle className="w-5 h-5 text-red-600" /> | |
| <div> | |
| <p className="text-sm">Critical System Alerts</p> | |
| <p className="text-xs text-gray-600"> | |
| Water quality issues, equipment failures | |
| </p> | |
| </div> | |
| </div> | |
| <Switch checked={criticalAlerts} onCheckedChange={setCriticalAlerts} /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <Bell className="w-5 h-5 text-orange-600" /> | |
| <div> | |
| <p className="text-sm">Inventory Alerts</p> | |
| <p className="text-xs text-gray-600"> | |
| Low stock, expiring items | |
| </p> | |
| </div> | |
| </div> | |
| <Switch checked={inventoryAlerts} onCheckedChange={setInventoryAlerts} /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <AlertTriangle className="w-5 h-5 text-purple-600" /> | |
| <div> | |
| <p className="text-sm">Health & Disease Alerts</p> | |
| <p className="text-xs text-gray-600"> | |
| Disease detection, mortality spikes | |
| </p> | |
| </div> | |
| </div> | |
| <Switch checked={healthAlerts} onCheckedChange={setHealthAlerts} /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <Info className="w-5 h-5 text-blue-600" /> | |
| <div> | |
| <p className="text-sm">Daily Reports</p> | |
| <p className="text-xs text-gray-600"> | |
| Performance summaries, growth updates | |
| </p> | |
| </div> | |
| </div> | |
| <Switch defaultChecked /> | |
| </div> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div className="flex items-center gap-3"> | |
| <Info className="w-5 h-5 text-green-600" /> | |
| <div> | |
| <p className="text-sm">Scheduled Tasks</p> | |
| <p className="text-xs text-gray-600"> | |
| Feeding reminders, sampling schedules | |
| </p> | |
| </div> | |
| </div> | |
| <Switch defaultChecked /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| <Card> | |
| <CardHeader> | |
| <CardTitle className="text-sm">Quiet Hours</CardTitle> | |
| <p className="text-xs text-gray-600"> | |
| Set times when non-critical notifications are paused | |
| </p> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="flex items-center justify-between p-3 border rounded-lg"> | |
| <div> | |
| <p className="text-sm">Enable Quiet Hours</p> | |
| <p className="text-xs text-gray-600"> | |
| 10:00 PM - 7:00 AM | |
| </p> | |
| </div> | |
| <Switch /> | |
| </div> | |
| <p className="text-xs text-gray-600 mt-3"> | |
| Note: Critical alerts will always be delivered immediately | |
| </p> | |
| </CardContent> | |
| </Card> | |
| <div className="flex justify-end"> | |
| <Button>Save Preferences</Button> | |
| </div> | |
| </TabsContent> | |
| </Tabs> | |
| </div> | |
| ); | |
| } | |