import { useState, useRef } from "react"; import { Card } from "./ui/card"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Badge } from "./ui/badge"; import { Camera, Sparkles, Mail, Users, Download, Upload, Search, Filter, Plus, AlertTriangle, Check, X } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "./ui/dialog"; import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./ui/select"; interface ChemicalInventory { id: string; // Required fields (red) piFirstName: string; physicalState: string; chemicalName: string; bldgCode: string; lab: string; storageLocation: string; storageDevice: string; numberOfContainers: string; amountPerContainer: string; unitOfMeasure: string; casNumber: string; // Nice to have fields chemicalFormula?: string; molecularWeight?: string; vendor?: string; catalogNumber?: string; foundInCatalog?: string; poNumber?: string; receiptDate?: string; openDate?: string; maxOnHand?: string; expirationDate?: string; contact?: string; comments?: string; dateEntered?: string; permitNumber?: string; barcode?: string; lastChanged?: string; concentration?: string; chemicalNumber?: string; lotNumber?: string; multipleCAS?: string; msds?: string; // Special fields percentageFull?: number; // blue needsManualEntry?: string[]; // yellow highlight scannedImage?: string; } const storageDeviceOptions = [ "Aerosol Can", "Ampule", "Bulked Item", "Fiber Box", "Gas Cylinder", "Glass Bottle", "Metal Can", "Metal Drum", "Metal Open Drum", "Pallet", "Plastic Bag", "Plastic Bottle", "Plastic Drum", "Plastic Open Drum" ]; export function Inventory() { const [searchQuery, setSearchQuery] = useState(""); const [isPhotoDialogOpen, setIsPhotoDialogOpen] = useState(false); const [capturedImage, setCapturedImage] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const [extractedData, setExtractedData] = useState | null>(null); const [shareDialogOpen, setShareDialogOpen] = useState(false); const fileInputRef = useRef(null); const [inventory, setInventory] = useState([ { id: "1", piFirstName: "Dr. Smith", physicalState: "Liquid", chemicalName: "Acetone", bldgCode: "BLD-A", lab: "Lab 201", storageLocation: "Cabinet A-3", storageDevice: "Glass Bottle", numberOfContainers: "2", amountPerContainer: "1", unitOfMeasure: "L", casNumber: "67-64-1", chemicalFormula: "C3H6O", molecularWeight: "58.08", vendor: "Sigma-Aldrich", catalogNumber: "179124", expirationDate: "2025-06-15", lotNumber: "SLCD1234", percentageFull: 65, dateEntered: "2024-11-15", openDate: "2024-11-15" }, { id: "2", piFirstName: "Dr. Johnson", physicalState: "Solid", chemicalName: "Sodium Hydroxide", bldgCode: "BLD-B", lab: "Lab 305", storageLocation: "Cabinet B-1", storageDevice: "Plastic Bottle", numberOfContainers: "1", amountPerContainer: "500", unitOfMeasure: "g", casNumber: "1310-73-2", chemicalFormula: "NaOH", molecularWeight: "39.997", vendor: "Fisher Scientific", expirationDate: "2025-10-22", lotNumber: "FS9876", percentageFull: 15, dateEntered: "2024-10-22" }, { id: "3", piFirstName: "Dr. Smith", physicalState: "Liquid", chemicalName: "Hydrochloric Acid", bldgCode: "BLD-A", lab: "Lab 201", storageLocation: "Acid Cabinet", storageDevice: "Glass Bottle", numberOfContainers: "1", amountPerContainer: "1", unitOfMeasure: "L", casNumber: "7647-01-0", chemicalFormula: "HCl", vendor: "Fisher Scientific", expirationDate: "2024-12-10", percentageFull: 80, dateEntered: "2024-08-15" } ]); const handlePhotoCapture = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { setCapturedImage(reader.result as string); processImage(reader.result as string); }; reader.readAsDataURL(file); } }; const processImage = (imageData: string) => { setIsProcessing(true); // Simulate AI processing with OCR setTimeout(() => { const mockExtractedData: Partial = { chemicalName: "Methanol", casNumber: "67-56-1", physicalState: "Liquid", vendor: "Sigma-Aldrich", lotNumber: "SLCD7890", catalogNumber: "34860", amountPerContainer: "1", unitOfMeasure: "L", numberOfContainers: "1", storageDevice: "Glass Bottle", chemicalFormula: "CH3OH", molecularWeight: "32.04", expirationDate: "2026-03-15", concentration: "99.8%", percentageFull: 95, // Scanner detected transparency // Fields that need manual entry needsManualEntry: ["piFirstName", "bldgCode", "lab", "storageLocation"], scannedImage: imageData }; setExtractedData(mockExtractedData); setIsProcessing(false); }, 2000); }; const handleAddFromScan = () => { if (extractedData) { const newChemical: ChemicalInventory = { id: Date.now().toString(), piFirstName: extractedData.piFirstName || "", physicalState: extractedData.physicalState || "", chemicalName: extractedData.chemicalName || "", bldgCode: extractedData.bldgCode || "", lab: extractedData.lab || "", storageLocation: extractedData.storageLocation || "", storageDevice: extractedData.storageDevice || "Glass Bottle", numberOfContainers: extractedData.numberOfContainers || "1", amountPerContainer: extractedData.amountPerContainer || "", unitOfMeasure: extractedData.unitOfMeasure || "", casNumber: extractedData.casNumber || "", chemicalFormula: extractedData.chemicalFormula, molecularWeight: extractedData.molecularWeight, vendor: extractedData.vendor, catalogNumber: extractedData.catalogNumber, expirationDate: extractedData.expirationDate, lotNumber: extractedData.lotNumber, concentration: extractedData.concentration, percentageFull: extractedData.percentageFull, dateEntered: new Date().toISOString().split('T')[0], needsManualEntry: extractedData.needsManualEntry, scannedImage: extractedData.scannedImage }; setInventory([newChemical, ...inventory]); setCapturedImage(null); setExtractedData(null); setIsPhotoDialogOpen(false); } }; const filteredInventory = inventory.filter(item => item.chemicalName.toLowerCase().includes(searchQuery.toLowerCase()) || item.casNumber.includes(searchQuery) || item.piFirstName.toLowerCase().includes(searchQuery.toLowerCase()) ); const lowStockChemicals = inventory.filter(item => item.percentageFull !== undefined && item.percentageFull < 20 ); const expiringChemicals = inventory.filter(item => { if (!item.expirationDate) return false; const today = new Date(); const expDate = new Date(item.expirationDate); const daysUntilExpiry = Math.floor((expDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); return daysUntilExpiry <= 30 || daysUntilExpiry < 0; }); const requiredFields = [ "piFirstName", "physicalState", "chemicalName", "bldgCode", "lab", "storageLocation", "storageDevice", "numberOfContainers", "amountPerContainer", "unitOfMeasure", "casNumber" ]; return (
{/* Header */}

Chemical Inventory

Collaborative inventory tracking with automated scanning

Share Inventory Access Give students and lab members access to this inventory

Current Access

Dr. Smith (You) Owner
Lab Assistant Editor
Scan Chemical Label Automatically extract information from chemical labels
{!capturedImage ? (
fileInputRef.current?.click()} >

Click to capture or upload label photo

AI will extract all available information

) : (
Scanned label
{isProcessing ? (

Scanning label...

Reading all visible information with AI

) : extractedData ? (

Information Extracted!

Yellow fields need manual entry. Review and complete before adding.

{/* Required Fields */}
setExtractedData({ ...extractedData, piFirstName: e.target.value })} placeholder="Required" />
setExtractedData({ ...extractedData, chemicalName: e.target.value })} />
setExtractedData({ ...extractedData, bldgCode: e.target.value })} placeholder="Required" />
setExtractedData({ ...extractedData, lab: e.target.value })} placeholder="Required" />
setExtractedData({ ...extractedData, storageLocation: e.target.value })} placeholder="Required" />
setExtractedData({ ...extractedData, numberOfContainers: e.target.value })} />
setExtractedData({ ...extractedData, amountPerContainer: e.target.value })} />
setExtractedData({ ...extractedData, unitOfMeasure: e.target.value })} />
setExtractedData({ ...extractedData, casNumber: e.target.value })} />
{/* Optional Fields */}
setExtractedData({ ...extractedData, chemicalFormula: e.target.value })} />
setExtractedData({ ...extractedData, molecularWeight: e.target.value })} />
setExtractedData({ ...extractedData, vendor: e.target.value })} />
setExtractedData({ ...extractedData, catalogNumber: e.target.value })} />
setExtractedData({ ...extractedData, lotNumber: e.target.value })} />
setExtractedData({ ...extractedData, expirationDate: e.target.value })} />
setExtractedData({ ...extractedData, percentageFull: parseFloat(e.target.value) })} placeholder="Auto-detected for transparent bottles" />
setExtractedData({ ...extractedData, concentration: e.target.value })} />
) : null}
)}
{/* Email Receipt Info */}

Auto-import chemical receipts: Email receipts to{" "} inventory@labwise-auto.com

We'll automatically extract and add chemicals to your inventory

{/* Search and Stats */}
setSearchQuery(e.target.value)} />

Total Chemicals

{inventory.length}

Low Stock

{lowStockChemicals.length}

{/* Spreadsheet Table */}
{/* Required columns - red headers */} {/* Blue column */} {/* Optional columns */} {filteredInventory.map((item) => ( {/* Required fields with yellow highlight if needed */} {/* Blue percentage full column */} {/* Optional fields */} ))}
PI First Name Physical State Chemical Name Bldg Code LAB Storage Location Storage Device # Containers Amount/Container Unit CAS #% FullFormula Mol. Weight Vendor Catalog # Expiration Lot # Concentration Date Entered Actions
{item.piFirstName || Missing} {item.physicalState} {item.chemicalName} {item.bldgCode || Missing} {item.lab || Missing} {item.storageLocation || Missing} {item.storageDevice} {item.numberOfContainers} {item.amountPerContainer} {item.unitOfMeasure} {item.casNumber} {item.percentageFull !== undefined ? (
{item.percentageFull}% {item.percentageFull < 20 && }
) : ( - )}
{item.chemicalFormula || "-"} {item.molecularWeight || "-"} {item.vendor || "-"} {item.catalogNumber || "-"} {item.expirationDate ? ( {item.expirationDate} ) : "-"} {item.lotNumber || "-"} {item.concentration || "-"} {item.dateEntered || "-"}
); }