Login sequence and inventory/protocol storage groundwork

This commit is contained in:
2026-03-19 05:42:11 +00:00
parent 5b2c7e4506
commit 55bbd6909d
21 changed files with 3882 additions and 157 deletions

View File

@@ -1,4 +1,6 @@
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { chemicalsApi } from "../lib/api";
import type { ChemicalInventory } from "../shared/types";
import { Card } from "./ui/card";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
@@ -34,47 +36,6 @@ import {
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",
@@ -102,71 +63,15 @@ export function Inventory() {
const [shareDialogOpen, setShareDialogOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [inventory, setInventory] = useState<ChemicalInventory[]>([
{
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 [inventory, setInventory] = useState<ChemicalInventory[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
chemicalsApi.list()
.then(data => setInventory(data))
.catch(console.error)
.finally(() => setIsLoading(false));
}, []);
const handlePhotoCapture = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
@@ -211,38 +116,30 @@ export function Inventory() {
}, 2000);
};
const handleAddFromScan = () => {
const handleAddFromScan = async () => {
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);
try {
const saved = await chemicalsApi.create({
...extractedData,
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 || "",
});
setInventory([saved, ...inventory]);
setCapturedImage(null);
setExtractedData(null);
setIsPhotoDialogOpen(false);
} catch (err) {
console.error("Failed to save chemical:", err);
}
}
};
@@ -615,6 +512,9 @@ export function Inventory() {
</div>
{/* Spreadsheet Table */}
{isLoading && (
<p className="text-center text-muted-foreground py-8">Loading inventory...</p>
)}
<Card className="overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">

View File

@@ -1,4 +1,5 @@
import { useState } from "react";
import { protocolsApi } from "../lib/api";
import { Card } from "./ui/card";
import { Button } from "./ui/button";
import { Textarea } from "./ui/textarea";
@@ -18,13 +19,7 @@ import {
import { Alert, AlertDescription } from "./ui/alert";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
interface SafetyIssue {
type: "critical" | "warning" | "suggestion";
category: string;
message: string;
source?: string;
sourceUrl?: string;
}
import type { SafetyIssue } from "../shared/types";
interface ChatMessage {
id: string;
@@ -85,12 +80,21 @@ export function ProtocolChecker() {
}
];
const handleAnalyze = () => {
const handleAnalyze = async () => {
if (!protocol.trim()) return;
setIsAnalyzing(true);
setTimeout(() => {
setIsAnalyzing(false);
try {
const saved = await protocolsApi.createFromText(
`Protocol ${new Date().toLocaleDateString()}`,
protocol
);
await protocolsApi.saveAnalysis(saved.id, mockIssues);
setAnalyzed(true);
}, 2000);
} catch (err) {
console.error("Failed to save protocol:", err);
} finally {
setIsAnalyzing(false);
}
};
const handleSendMessage = () => {