initial
This commit is contained in:
275
components/Dashboard.tsx
Normal file
275
components/Dashboard.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import { Card } from "./ui/card";
|
||||
import { Button } from "./ui/button";
|
||||
import { Badge } from "./ui/badge";
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
TrendingUp,
|
||||
Package,
|
||||
FileCheck,
|
||||
MessageSquare,
|
||||
ArrowRight,
|
||||
Bell,
|
||||
Camera,
|
||||
Activity
|
||||
} from "lucide-react";
|
||||
|
||||
type Tab = "dashboard" | "inventory" | "protocol";
|
||||
|
||||
interface DashboardProps {
|
||||
setActiveTab: (tab: Tab) => void;
|
||||
}
|
||||
|
||||
export function Dashboard({ setActiveTab }: DashboardProps) {
|
||||
const stats = [
|
||||
{ label: "Chemicals Tracked", value: "252", icon: Package, color: "text-[#5a9584]" },
|
||||
{ label: "Low Stock (<20%)", value: "2", icon: AlertTriangle, color: "text-amber-600" },
|
||||
{ label: "Expiring Soon", value: "3", icon: Clock, color: "text-red-600" },
|
||||
{ label: "Protocols Reviewed", value: "12", icon: FileCheck, color: "text-[#2d5a4a]" },
|
||||
];
|
||||
|
||||
const lowStockAlerts = [
|
||||
{ chemical: "Sodium Hydroxide", location: "Cabinet B-1", percentFull: 15, lab: "Lab 305" },
|
||||
{ chemical: "Ethanol", location: "Cabinet A-5", percentFull: 18, lab: "Lab 201" },
|
||||
];
|
||||
|
||||
const expirationAlerts = [
|
||||
{ chemical: "Hydrochloric Acid", location: "Acid Cabinet", daysLeft: -2, status: "expired", lab: "Lab 201" },
|
||||
{ chemical: "Benzene", location: "Flammables Cabinet", daysLeft: 15, status: "expiring-soon", lab: "Lab 305" },
|
||||
{ chemical: "Acetone", location: "Cabinet A-3", daysLeft: 28, status: "expiring-soon", lab: "Lab 201" },
|
||||
];
|
||||
|
||||
const recentActivity = [
|
||||
{ action: "Scanned and added Methanol via photo", time: "30 mins ago", type: "inventory" },
|
||||
{ action: "Protocol safety review completed", time: "2 hours ago", type: "protocol" },
|
||||
{ action: "Low stock alert: Sodium Hydroxide", time: "4 hours ago", type: "alert" },
|
||||
{ action: "Expiration reminder sent for 3 chemicals", time: "1 day ago", type: "reminder" },
|
||||
];
|
||||
|
||||
const alerts = [
|
||||
{ message: "Hydrochloric Acid expired 2 days ago - requires disposal", severity: "critical" },
|
||||
{ message: "Sodium Hydroxide below 20% full - reorder needed", severity: "warning" },
|
||||
{ message: "3 chemicals expiring in next 30 days", severity: "warning" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-foreground mb-2">Welcome to Labwise</h1>
|
||||
<p className="text-muted-foreground">Your AI-powered lab safety and compliance assistant</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{stats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<Card key={stat.label} className="p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<p className="text-muted-foreground mb-1">{stat.label}</p>
|
||||
<p className={`${stat.color}`}>{stat.value}</p>
|
||||
</div>
|
||||
<Icon className={`w-8 h-8 ${stat.color}`} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="p-3 bg-accent rounded-lg">
|
||||
<MessageSquare className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1">AI Safety Assistant</h3>
|
||||
<p className="text-muted-foreground">Ask questions with sourced answers</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setActiveTab("protocol")}
|
||||
className="w-full bg-primary hover:bg-primary/90"
|
||||
>
|
||||
Ask Question <ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="p-3 bg-accent rounded-lg">
|
||||
<FileCheck className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1">Check Protocol</h3>
|
||||
<p className="text-muted-foreground">Get AI safety feedback</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setActiveTab("protocol")}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
Upload Protocol <ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="p-3 bg-accent rounded-lg">
|
||||
<Camera className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1">Scan Chemical</h3>
|
||||
<p className="text-muted-foreground">Photo capture with auto-fill</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setActiveTab("inventory")}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
Scan Label <Camera className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* Low Stock Alerts */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="w-5 h-5 text-amber-600" />
|
||||
<h3 className="text-foreground">Low Stock Alerts</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-primary"
|
||||
onClick={() => setActiveTab("inventory")}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{lowStockAlerts.map((alert, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-amber-200 bg-amber-50 hover:shadow-sm transition-shadow cursor-pointer"
|
||||
onClick={() => setActiveTab("inventory")}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<p className="text-foreground">{alert.chemical}</p>
|
||||
<p className="text-muted-foreground text-sm">{alert.lab} • {alert.location}</p>
|
||||
</div>
|
||||
<Badge className="bg-amber-100 text-amber-700 border-amber-200">
|
||||
{alert.percentFull}% full
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Expiration Alerts */}
|
||||
<Card className="p-6 lg:col-span-2">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="w-5 h-5 text-primary" />
|
||||
<h3 className="text-foreground">Expiration Alerts</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-primary"
|
||||
onClick={() => setActiveTab("inventory")}
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{expirationAlerts.map((alert, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between p-4 rounded-lg border border-border hover:shadow-sm transition-shadow cursor-pointer"
|
||||
onClick={() => setActiveTab("inventory")}
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
alert.status === 'expired'
|
||||
? 'bg-red-500'
|
||||
: 'bg-amber-500'
|
||||
}`} />
|
||||
<div className="flex-1">
|
||||
<p className="text-foreground">{alert.chemical}</p>
|
||||
<p className="text-muted-foreground text-sm">{alert.lab} • {alert.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`${
|
||||
alert.status === 'expired'
|
||||
? 'bg-red-100 text-red-700 border-red-200'
|
||||
: 'bg-amber-100 text-amber-700 border-amber-200'
|
||||
} border`}>
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{alert.status === 'expired'
|
||||
? `Expired ${Math.abs(alert.daysLeft)}d ago`
|
||||
: `${alert.daysLeft}d left`}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Recent Activity */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-foreground mb-4">Recent Activity</h3>
|
||||
<div className="space-y-4">
|
||||
{recentActivity.map((activity, idx) => (
|
||||
<div key={idx} className="flex items-start gap-3">
|
||||
<Clock className="w-5 h-5 text-muted-foreground mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="text-foreground">{activity.action}</p>
|
||||
<p className="text-muted-foreground">{activity.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Safety Alerts */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-foreground mb-4">Safety Alerts</h3>
|
||||
<div className="space-y-4">
|
||||
{alerts.map((alert, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg ${
|
||||
alert.severity === "critical"
|
||||
? "bg-red-50 border border-red-200"
|
||||
: alert.severity === "warning"
|
||||
? "bg-amber-50 border border-amber-200"
|
||||
: "bg-accent border border-border"
|
||||
}`}
|
||||
>
|
||||
<AlertTriangle
|
||||
className={`w-5 h-5 mt-0.5 ${
|
||||
alert.severity === "critical"
|
||||
? "text-red-600"
|
||||
: alert.severity === "warning"
|
||||
? "text-amber-600"
|
||||
: "text-primary"
|
||||
}`}
|
||||
/>
|
||||
<p className="text-foreground flex-1">{alert.message}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user