From b0cd20ced5827a6db85fdeb1b11b6d7d17fb679f Mon Sep 17 00:00:00 2001 From: Aditya Pulipaka Date: Wed, 18 Mar 2026 17:10:16 -0500 Subject: [PATCH] initial --- App.tsx | 60 ++ Attributions.md | 3 + components/AIChat.tsx | 206 +++++++ components/Dashboard.tsx | 275 ++++++++++ components/EHSDocumentation.tsx | 287 ++++++++++ components/Inventory.tsx | 716 ++++++++++++++++++++++++ components/ProtocolChecker.tsx | 471 ++++++++++++++++ components/SDSLibrary.tsx | 204 +++++++ components/figma/ImageWithFallback.tsx | 27 + components/ui/accordion.tsx | 66 +++ components/ui/alert-dialog.tsx | 157 ++++++ components/ui/alert.tsx | 66 +++ components/ui/aspect-ratio.tsx | 11 + components/ui/avatar.tsx | 53 ++ components/ui/badge.tsx | 46 ++ components/ui/breadcrumb.tsx | 109 ++++ components/ui/button.tsx | 58 ++ components/ui/calendar.tsx | 75 +++ components/ui/card.tsx | 92 ++++ components/ui/carousel.tsx | 241 ++++++++ components/ui/chart.tsx | 353 ++++++++++++ components/ui/checkbox.tsx | 32 ++ components/ui/collapsible.tsx | 33 ++ components/ui/command.tsx | 177 ++++++ components/ui/context-menu.tsx | 252 +++++++++ components/ui/dialog.tsx | 135 +++++ components/ui/drawer.tsx | 132 +++++ components/ui/dropdown-menu.tsx | 257 +++++++++ components/ui/form.tsx | 168 ++++++ components/ui/hover-card.tsx | 44 ++ components/ui/input-otp.tsx | 77 +++ components/ui/input.tsx | 21 + components/ui/label.tsx | 24 + components/ui/menubar.tsx | 276 ++++++++++ components/ui/navigation-menu.tsx | 168 ++++++ components/ui/pagination.tsx | 127 +++++ components/ui/popover.tsx | 48 ++ components/ui/progress.tsx | 31 ++ components/ui/radio-group.tsx | 45 ++ components/ui/resizable.tsx | 56 ++ components/ui/scroll-area.tsx | 58 ++ components/ui/select.tsx | 189 +++++++ components/ui/separator.tsx | 28 + components/ui/sheet.tsx | 139 +++++ components/ui/sidebar.tsx | 726 +++++++++++++++++++++++++ components/ui/skeleton.tsx | 13 + components/ui/slider.tsx | 63 +++ components/ui/sonner.tsx | 25 + components/ui/switch.tsx | 31 ++ components/ui/table.tsx | 116 ++++ components/ui/tabs.tsx | 66 +++ components/ui/textarea.tsx | 18 + components/ui/toggle-group.tsx | 73 +++ components/ui/toggle.tsx | 47 ++ components/ui/tooltip.tsx | 61 +++ components/ui/use-mobile.ts | 21 + components/ui/utils.ts | 6 + guidelines/Guidelines.md | 61 +++ styles/globals.css | 200 +++++++ 59 files changed, 7620 insertions(+) create mode 100644 App.tsx create mode 100644 Attributions.md create mode 100644 components/AIChat.tsx create mode 100644 components/Dashboard.tsx create mode 100644 components/EHSDocumentation.tsx create mode 100644 components/Inventory.tsx create mode 100644 components/ProtocolChecker.tsx create mode 100644 components/SDSLibrary.tsx create mode 100644 components/figma/ImageWithFallback.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-mobile.ts create mode 100644 components/ui/utils.ts create mode 100644 guidelines/Guidelines.md create mode 100644 styles/globals.css diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..f79e46c --- /dev/null +++ b/App.tsx @@ -0,0 +1,60 @@ +import { useState } from "react"; +import { Dashboard } from "./components/Dashboard"; +import { Inventory } from "./components/Inventory"; +import { ProtocolChecker } from "./components/ProtocolChecker"; +import { + LayoutDashboard, + Package, + FileCheck +} from "lucide-react"; +import logo from "figma:asset/e1cc93b8a3ca5c34482c2d8ace21b3610ba98443.png"; + +type Tab = "dashboard" | "inventory" | "protocol"; + +export default function App() { + const [activeTab, setActiveTab] = useState("dashboard"); + + const navItems = [ + { id: "dashboard" as Tab, label: "Dashboard", icon: LayoutDashboard }, + { id: "inventory" as Tab, label: "Inventory", icon: Package }, + { id: "protocol" as Tab, label: "Protocol Checker", icon: FileCheck }, + ]; + + return ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+ {activeTab === "dashboard" && } + {activeTab === "inventory" && } + {activeTab === "protocol" && } +
+
+ ); +} \ No newline at end of file diff --git a/Attributions.md b/Attributions.md new file mode 100644 index 0000000..9b7cd4e --- /dev/null +++ b/Attributions.md @@ -0,0 +1,3 @@ +This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md). + +This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license). \ No newline at end of file diff --git a/components/AIChat.tsx b/components/AIChat.tsx new file mode 100644 index 0000000..8f4710f --- /dev/null +++ b/components/AIChat.tsx @@ -0,0 +1,206 @@ +import { useState, useRef, useEffect } from "react"; +import { Card } from "./ui/card"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { Badge } from "./ui/badge"; +import { Send, Bot, User, Sparkles, FileText } from "lucide-react"; + +interface Message { + id: string; + role: "user" | "assistant"; + content: string; + timestamp: Date; +} + +export function AIChat() { + const [messages, setMessages] = useState([ + { + id: "1", + role: "assistant", + content: "Hello! I'm your Labwise AI assistant. I can help you with:\n\n• Chemical safety information\n• Protocol guidance\n• SDS lookups\n• EHS documentation questions\n• Emergency procedures\n\nWhat would you like to know?", + timestamp: new Date() + } + ]); + const [inputValue, setInputValue] = useState(""); + const [isTyping, setIsTyping] = useState(false); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages, isTyping]); + + const mockResponses: { [key: string]: string } = { + "acetone": "Acetone (CAS 67-64-1) is a flammable liquid. Key safety considerations:\n\n• Store in flammables cabinet away from ignition sources\n• Use in well-ventilated areas or fume hood\n• Wear nitrile gloves and safety goggles\n• Keep away from oxidizing agents\n• Flash point: -20°C\n\nWould you like me to pull up the full SDS?", + "disposal": "For chemical waste disposal:\n\n1. Segregate waste by compatibility class\n2. Label all waste containers with contents and hazards\n3. Use proper waste containers (no food/beverage containers)\n4. Contact EHS for pickup when containers are 80% full\n5. Never pour chemicals down the drain unless specifically approved\n\nWhat type of waste are you disposing of?", + "ppe": "Personal Protective Equipment (PPE) requirements depend on the chemicals and procedures:\n\n**Minimum PPE:**\n• Safety glasses or goggles\n• Lab coat\n• Closed-toe shoes\n• Long pants\n\n**Additional PPE may include:**\n• Chemical-resistant gloves (nitrile, neoprene, etc.)\n• Face shield for splash hazards\n• Respirator for specific vapors\n\nWhat chemicals are you working with?", + "default": "I can help you with that! Based on your question, here are some key points:\n\n• Always refer to the Safety Data Sheet (SDS) for specific chemical hazards\n• Ensure proper PPE is worn at all times\n• Work in well-ventilated areas or fume hoods when handling volatile chemicals\n• Follow your lab's standard operating procedures\n\nWould you like more specific information about a particular chemical or procedure?" + }; + + const handleSend = () => { + if (!inputValue.trim()) return; + + const userMessage: Message = { + id: Date.now().toString(), + role: "user", + content: inputValue, + timestamp: new Date() + }; + + setMessages(prev => [...prev, userMessage]); + setInputValue(""); + setIsTyping(true); + + setTimeout(() => { + const lowercaseInput = inputValue.toLowerCase(); + let response = mockResponses.default; + + if (lowercaseInput.includes("acetone")) { + response = mockResponses.acetone; + } else if (lowercaseInput.includes("disposal") || lowercaseInput.includes("waste")) { + response = mockResponses.disposal; + } else if (lowercaseInput.includes("ppe") || lowercaseInput.includes("protective equipment")) { + response = mockResponses.ppe; + } + + const assistantMessage: Message = { + id: (Date.now() + 1).toString(), + role: "assistant", + content: response, + timestamp: new Date() + }; + + setMessages(prev => [...prev, assistantMessage]); + setIsTyping(false); + }, 1500); + }; + + const quickQuestions = [ + "What PPE do I need for handling acids?", + "How do I dispose of organic solvents?", + "What are the hazards of acetone?", + "Where can I find the SDS for benzene?" + ]; + + return ( +
+
+
+
+
+ +
+

AI Safety Assistant

+
+

Ask questions about chemical safety, protocols, and lab procedures

+
+ +
+ {/* Messages */} + +
+ {messages.map((message) => ( +
+
+ {message.role === "user" ? ( + + ) : ( + + )} +
+
+
+

{message.content}

+
+

+ {message.timestamp.toLocaleTimeString()} +

+
+
+ ))} + + {isTyping && ( +
+
+ +
+
+
+
+
+
+
+
+
+ )} +
+
+ + + {/* Quick Questions */} + {messages.length === 1 && ( +
+

Quick questions:

+
+ {quickQuestions.map((question, idx) => ( + + ))} +
+
+ )} + + {/* Input */} + +
+ setInputValue(e.target.value)} + onKeyPress={(e) => e.key === "Enter" && handleSend()} + className="flex-1" + /> + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx new file mode 100644 index 0000000..21f9bb8 --- /dev/null +++ b/components/Dashboard.tsx @@ -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 ( +
+
+
+

Welcome to Labwise

+

Your AI-powered lab safety and compliance assistant

+
+ + {/* Stats Grid */} +
+ {stats.map((stat) => { + const Icon = stat.icon; + return ( + +
+
+

{stat.label}

+

{stat.value}

+
+ +
+
+ ); + })} +
+ + {/* Quick Actions */} +
+ +
+
+ +
+
+

AI Safety Assistant

+

Ask questions with sourced answers

+
+
+ +
+ + +
+
+ +
+
+

Check Protocol

+

Get AI safety feedback

+
+
+ +
+ + +
+
+ +
+
+

Scan Chemical

+

Photo capture with auto-fill

+
+
+ +
+
+ +
+ {/* Low Stock Alerts */} + +
+
+ +

Low Stock Alerts

+
+ +
+
+ {lowStockAlerts.map((alert, idx) => ( +
setActiveTab("inventory")} + > +
+

{alert.chemical}

+

{alert.lab} • {alert.location}

+
+ + {alert.percentFull}% full + +
+ ))} +
+
+ + {/* Expiration Alerts */} + +
+
+ +

Expiration Alerts

+
+ +
+
+ {expirationAlerts.map((alert, idx) => ( +
setActiveTab("inventory")} + > +
+
+
+

{alert.chemical}

+

{alert.lab} • {alert.location}

+
+
+ + + {alert.status === 'expired' + ? `Expired ${Math.abs(alert.daysLeft)}d ago` + : `${alert.daysLeft}d left`} + +
+ ))} +
+ +
+ +
+ {/* Recent Activity */} + +

Recent Activity

+
+ {recentActivity.map((activity, idx) => ( +
+ +
+

{activity.action}

+

{activity.time}

+
+
+ ))} +
+
+ + {/* Safety Alerts */} + +

Safety Alerts

+
+ {alerts.map((alert, idx) => ( +
+ +

{alert.message}

+
+ ))} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/EHSDocumentation.tsx b/components/EHSDocumentation.tsx new file mode 100644 index 0000000..07c268c --- /dev/null +++ b/components/EHSDocumentation.tsx @@ -0,0 +1,287 @@ +import { useState } from "react"; +import { Card } from "./ui/card"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { Label } from "./ui/label"; +import { Textarea } from "./ui/textarea"; +import { Badge } from "./ui/badge"; +import { + FileText, + CheckCircle2, + Clock, + AlertCircle, + Plus, + Calendar +} from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./ui/select"; + +interface Document { + id: string; + title: string; + type: string; + status: "complete" | "pending" | "overdue"; + dueDate: string; + lastModified: string; +} + +export function EHSDocumentation() { + const [selectedDoc, setSelectedDoc] = useState(null); + + const documents: Document[] = [ + { + id: "1", + title: "Quarterly Chemical Inventory Report", + type: "Inventory Report", + status: "complete", + dueDate: "2024-12-01", + lastModified: "2024-11-20" + }, + { + id: "2", + title: "Hazardous Waste Disposal Form", + type: "Waste Disposal", + status: "pending", + dueDate: "2024-11-30", + lastModified: "2024-11-22" + }, + { + id: "3", + title: "Lab Safety Inspection Checklist", + type: "Inspection", + status: "overdue", + dueDate: "2024-11-15", + lastModified: "2024-10-30" + }, + { + id: "4", + title: "Annual Training Documentation", + type: "Training", + status: "complete", + dueDate: "2024-09-30", + lastModified: "2024-09-28" + } + ]; + + const getStatusBadge = (status: string) => { + switch (status) { + case "complete": + return ( + + + Complete + + ); + case "pending": + return ( + + + Pending + + ); + case "overdue": + return ( + + + Overdue + + ); + default: + return null; + } + }; + + return ( +
+
+
+
+

EHS Documentation

+

Manage and track your Environmental Health & Safety documentation

+
+ + + + + + + Create EHS Document + + AI-assisted form filling using your inventory history + + +
+
+ + +
+ +
+ + +
+ +
+
+ +
+

AI Assistant Ready

+

+ I can auto-fill this form using data from your inventory history, recent chemical usage, and previous documentation. +

+
+
+
+ +
+ +