[dyad] Implemented a QR code scanner to filter the stock list - wrote 5 file(s), added html5-qrcode package(s)
This commit is contained in:
parent
d72b809813
commit
f9a6bdc346
5 changed files with 118 additions and 10 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
74
src/components/stock/QRScannerDialog.tsx
Normal file
74
src/components/stock/QRScannerDialog.tsx
Normal file
|
|
@ -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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Scan QR Code</DialogTitle>
|
||||
<DialogDescription>
|
||||
Point your camera at a QR code to scan it.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div id="reader" className="w-full"></div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRScannerDialog;
|
||||
|
|
@ -35,11 +35,15 @@ import {
|
|||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
filterValue: string
|
||||
onFilterChange: (value: string) => void
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
filterValue,
|
||||
onFilterChange,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||
|
|
@ -76,15 +80,18 @@ export function DataTable<TData, TValue>({
|
|||
},
|
||||
})
|
||||
|
||||
// Sync external filter value with the table's internal state
|
||||
React.useEffect(() => {
|
||||
table.getColumn("sku")?.setFilterValue(filterValue)
|
||||
}, [filterValue, table])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter by item name..."
|
||||
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) =>
|
||||
table.getColumn("name")?.setFilterValue(event.target.value)
|
||||
}
|
||||
placeholder="Filter by SKU..."
|
||||
value={filterValue}
|
||||
onChange={(event) => onFilterChange(event.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<QRScannerDialog
|
||||
open={scannerOpen}
|
||||
onOpenChange={setScannerOpen}
|
||||
onScanSuccess={handleScanSuccess}
|
||||
/>
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Stock List</h1>
|
||||
<p className="text-gray-500">Manage your inventory here.</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline">
|
||||
<Button variant="outline" onClick={() => setScannerOpen(true)}>
|
||||
<Camera className="mr-2 h-4 w-4" />
|
||||
Scan Barcode
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Dialog open={addItemOpen} onOpenChange={setAddItemOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
|
|
@ -116,12 +129,17 @@ const StockList = () => {
|
|||
Fill in the details below to add a new item to your inventory.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AddItemForm setOpen={setOpen} />
|
||||
<AddItemForm setOpen={setAddItemOpen} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable columns={columns} data={data} />
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
filterValue={filterValue}
|
||||
onFilterChange={setFilterValue}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue