diff --git a/package.json b/package.json index 773839b..b5e2557 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "cmdk": "^1.0.0", "date-fns": "^3.6.0", "embla-carousel-react": "^8.3.0", + "html5-qrcode": "^2.3.8", "input-otp": "^1.2.4", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37f0b5d..7151e0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: embla-carousel-react: specifier: ^8.3.0 version: 8.6.0(react@18.3.1) + html5-qrcode: + specifier: ^2.3.8 + version: 2.3.8 input-otp: specifier: ^1.2.4 version: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1829,6 +1832,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html5-qrcode@2.3.8: + resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4052,6 +4058,8 @@ snapshots: dependencies: function-bind: 1.1.2 + html5-qrcode@2.3.8: {} + ignore@5.3.2: {} import-fresh@3.3.1: diff --git a/src/components/stock/QRScannerDialog.tsx b/src/components/stock/QRScannerDialog.tsx new file mode 100644 index 0000000..75c662b --- /dev/null +++ b/src/components/stock/QRScannerDialog.tsx @@ -0,0 +1,74 @@ +"use client" + +import { useEffect } from "react" +import { Html5QrcodeScanner } from "html5-qrcode" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog" + +interface QRScannerDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + onScanSuccess: (decodedText: string) => void +} + +const QRScannerDialog = ({ open, onOpenChange, onScanSuccess }: QRScannerDialogProps) => { + useEffect(() => { + if (!open) return; + + const scanner = new Html5QrcodeScanner( + "reader", // The ID of the element to render the scanner in + { + qrbox: { + width: 250, + height: 250, + }, + fps: 5, + }, + false // verbose + ); + + const handleSuccess = (decodedText: string) => { + scanner.clear(); + onScanSuccess(decodedText); + }; + + const handleError = (error: any) => { + // Don't log common errors like QR code not found + if (typeof error !== 'string' || !error.includes('No QR code found')) { + console.error("QR Scanner Error:", error); + } + }; + + scanner.render(handleSuccess, handleError); + + // Cleanup function to stop the scanner when the component unmounts or dialog closes + return () => { + if (scanner) { + scanner.clear().catch(error => { + console.error("Failed to clear scanner.", error); + }); + } + }; + }, [open, onScanSuccess]); + + return ( + + + + Scan QR Code + + Point your camera at a QR code to scan it. + + +
+
+
+ ); +}; + +export default QRScannerDialog; \ No newline at end of file diff --git a/src/components/stock/data-table.tsx b/src/components/stock/data-table.tsx index 03c642f..7c53179 100644 --- a/src/components/stock/data-table.tsx +++ b/src/components/stock/data-table.tsx @@ -35,11 +35,15 @@ import { interface DataTableProps { columns: ColumnDef[] data: TData[] + filterValue: string + onFilterChange: (value: string) => void } export function DataTable({ columns, data, + filterValue, + onFilterChange, }: DataTableProps) { const [sorting, setSorting] = React.useState([]) const [columnFilters, setColumnFilters] = React.useState([]) @@ -76,15 +80,18 @@ export function DataTable({ }, }) + // Sync external filter value with the table's internal state + React.useEffect(() => { + table.getColumn("sku")?.setFilterValue(filterValue) + }, [filterValue, table]) + return (
- table.getColumn("name")?.setFilterValue(event.target.value) - } + placeholder="Filter by SKU..." + value={filterValue} + onChange={(event) => onFilterChange(event.target.value)} className="max-w-sm" /> diff --git a/src/pages/StockList.tsx b/src/pages/StockList.tsx index 55d83d1..f1099ed 100644 --- a/src/pages/StockList.tsx +++ b/src/pages/StockList.tsx @@ -12,6 +12,7 @@ import { DialogTrigger, } from "@/components/ui/dialog" import { AddItemForm } from "../components/stock/AddItemForm" +import QRScannerDialog from "../components/stock/QRScannerDialog" // In a real app, you would fetch this data from an API. const data: StockItem[] = [ @@ -88,21 +89,33 @@ const data: StockItem[] = [ ]; const StockList = () => { - const [open, setOpen] = useState(false); + const [addItemOpen, setAddItemOpen] = useState(false); + const [scannerOpen, setScannerOpen] = useState(false); + const [filterValue, setFilterValue] = useState(""); + + const handleScanSuccess = (decodedText: string) => { + setFilterValue(decodedText); + setScannerOpen(false); + }; return (
+

Stock List

Manage your inventory here.

- - +
- +
) }