[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:
davidels1 2025-09-09 00:56:13 +02:00
parent d72b809813
commit f9a6bdc346
5 changed files with 118 additions and 10 deletions

View file

@ -46,6 +46,7 @@
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"html5-qrcode": "^2.3.8",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",

View file

@ -113,6 +113,9 @@ importers:
embla-carousel-react: embla-carousel-react:
specifier: ^8.3.0 specifier: ^8.3.0
version: 8.6.0(react@18.3.1) version: 8.6.0(react@18.3.1)
html5-qrcode:
specifier: ^2.3.8
version: 2.3.8
input-otp: input-otp:
specifier: ^1.2.4 specifier: ^1.2.4
version: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 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==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
html5-qrcode@2.3.8:
resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -4052,6 +4058,8 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
html5-qrcode@2.3.8: {}
ignore@5.3.2: {} ignore@5.3.2: {}
import-fresh@3.3.1: import-fresh@3.3.1:

View 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;

View file

@ -35,11 +35,15 @@ import {
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[] columns: ColumnDef<TData, TValue>[]
data: TData[] data: TData[]
filterValue: string
onFilterChange: (value: string) => void
} }
export function DataTable<TData, TValue>({ export function DataTable<TData, TValue>({
columns, columns,
data, data,
filterValue,
onFilterChange,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]) 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 ( return (
<div> <div>
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<Input <Input
placeholder="Filter by item name..." placeholder="Filter by SKU..."
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""} value={filterValue}
onChange={(event) => onChange={(event) => onFilterChange(event.target.value)}
table.getColumn("name")?.setFilterValue(event.target.value)
}
className="max-w-sm" className="max-w-sm"
/> />
<DropdownMenu> <DropdownMenu>

View file

@ -12,6 +12,7 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { AddItemForm } from "../components/stock/AddItemForm" import { AddItemForm } from "../components/stock/AddItemForm"
import QRScannerDialog from "../components/stock/QRScannerDialog"
// In a real app, you would fetch this data from an API. // In a real app, you would fetch this data from an API.
const data: StockItem[] = [ const data: StockItem[] = [
@ -88,21 +89,33 @@ const data: StockItem[] = [
]; ];
const StockList = () => { 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 ( return (
<div> <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 className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
<div> <div>
<h1 className="text-3xl font-bold">Stock List</h1> <h1 className="text-3xl font-bold">Stock List</h1>
<p className="text-gray-500">Manage your inventory here.</p> <p className="text-gray-500">Manage your inventory here.</p>
</div> </div>
<div className="flex items-center space-x-2"> <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" /> <Camera className="mr-2 h-4 w-4" />
Scan Barcode Scan Barcode
</Button> </Button>
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={addItemOpen} onOpenChange={setAddItemOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button> <Button>
<PlusCircle className="mr-2 h-4 w-4" /> <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. Fill in the details below to add a new item to your inventory.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<AddItemForm setOpen={setOpen} /> <AddItemForm setOpen={setAddItemOpen} />
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div> </div>
<DataTable columns={columns} data={data} /> <DataTable
columns={columns}
data={data}
filterValue={filterValue}
onFilterChange={setFilterValue}
/>
</div> </div>
) )
} }