diff --git a/src/components/BarcodeScannerView.tsx b/src/components/BarcodeScannerView.tsx
index 381edb7..a7cdcc7 100644
--- a/src/components/BarcodeScannerView.tsx
+++ b/src/components/BarcodeScannerView.tsx
@@ -1,3 +1,4 @@
+import { useEffect } from 'react';
import { Alert, Card, CardContent, Typography } from '@mui/material';
import { useBarcodeScanner } from '../hooks/useBarcodeScanner';
@@ -8,9 +9,11 @@ interface BarcodeScannerViewProps {
export const BarcodeScannerView = ({ onDetected }: BarcodeScannerViewProps) => {
const { videoRef, result } = useBarcodeScanner();
- if (result.code && onDetected) {
- onDetected(result.code);
- }
+ useEffect(() => {
+ if (result.code && onDetected) {
+ onDetected(result.code);
+ }
+ }, [onDetected, result.code]);
return (
diff --git a/src/components/ProductRow.tsx b/src/components/ProductRow.tsx
index beac226..c93d2c7 100644
--- a/src/components/ProductRow.tsx
+++ b/src/components/ProductRow.tsx
@@ -2,18 +2,23 @@ import {
Card,
CardActions,
CardContent,
+ Dialog,
+ DialogContent,
+ DialogTitle,
IconButton,
MenuItem,
Stack,
TextField,
Typography,
+ Button,
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import { ChangeEvent, useEffect, useState } from 'react';
-import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product';
+import { DEFAULT_UNIT_TYPE, Product } from '../models/Product';
+import { BarcodeScannerView } from './BarcodeScannerView';
interface ProductRowProps {
product: Product;
@@ -23,7 +28,7 @@ interface ProductRowProps {
updates: {
name: string;
category: string;
- units_per_bulk?: number;
+ barcode?: string;
},
) => Promise | void;
onDelete: (productId: string) => Promise | void;
@@ -32,18 +37,19 @@ interface ProductRowProps {
interface ProductFormState {
name: string;
category: string;
- unitsPerBulk: string;
+ barcode: string;
}
const getInitialFormState = (product: Product): ProductFormState => ({
name: product.name,
category: product.category,
- unitsPerBulk: product.units_per_bulk?.toString() ?? '',
+ barcode: product.barcode ?? '',
});
export const ProductRow = ({ product, categories, onSave, onDelete }: ProductRowProps) => {
const [isEditing, setIsEditing] = useState(false);
const [formState, setFormState] = useState(() => getInitialFormState(product));
+ const [isScannerOpen, setIsScannerOpen] = useState(false);
useEffect(() => {
setFormState(getInitialFormState(product));
@@ -58,7 +64,7 @@ export const ProductRow = ({ product, categories, onSave, onDelete }: ProductRow
await onSave(product.id, {
name: formState.name,
category: formState.category,
- units_per_bulk: formState.unitsPerBulk ? Number(formState.unitsPerBulk) : undefined,
+ barcode: formState.barcode || undefined,
});
setIsEditing(false);
};
@@ -87,13 +93,25 @@ export const ProductRow = ({ product, categories, onSave, onDelete }: ProductRow
))}
-
+ {formState.barcode ? (
+ setFormState((prev) => ({ ...prev, barcode: '' }))}>
+ Clear
+
+ ),
+ }}
+ />
+ ) : (
+
+ )}
) : (
@@ -103,9 +121,9 @@ export const ProductRow = ({ product, categories, onSave, onDelete }: ProductRow
{product.category} • {product.unit_type || DEFAULT_UNIT_TYPE}
- {product.units_per_bulk ? (
+ {product.barcode ? (
- {product.units_per_bulk} per {product.bulk_name || DEFAULT_BULK_NAME}
+ Barcode: {product.barcode}
) : null}
@@ -132,6 +150,17 @@ export const ProductRow = ({ product, categories, onSave, onDelete }: ProductRow
>
)}
+
);
};
diff --git a/src/db/seed.ts b/src/db/seed.ts
index 23f5c30..c262738 100644
--- a/src/db/seed.ts
+++ b/src/db/seed.ts
@@ -33,7 +33,6 @@ export const seedDatabase = async (db: StockFillDB) => {
category: 'Drinks',
unit_type: DEFAULT_UNIT_TYPE,
bulk_name: DEFAULT_BULK_NAME,
- units_per_bulk: 12,
archived: false,
created_at: now(),
updated_at: now(),
@@ -44,7 +43,6 @@ export const seedDatabase = async (db: StockFillDB) => {
category: 'Snacks',
unit_type: DEFAULT_UNIT_TYPE,
bulk_name: DEFAULT_BULK_NAME,
- units_per_bulk: 24,
archived: false,
created_at: now(),
updated_at: now(),
@@ -55,7 +53,6 @@ export const seedDatabase = async (db: StockFillDB) => {
category: 'Confectionery',
unit_type: DEFAULT_UNIT_TYPE,
bulk_name: DEFAULT_BULK_NAME,
- units_per_bulk: 32,
archived: false,
created_at: now(),
updated_at: now(),
diff --git a/src/hooks/useBarcodeScanner.ts b/src/hooks/useBarcodeScanner.ts
index 29f7b9d..c238742 100644
--- a/src/hooks/useBarcodeScanner.ts
+++ b/src/hooks/useBarcodeScanner.ts
@@ -1,6 +1,9 @@
import { useEffect, useRef, useState } from 'react';
import { BrowserMultiFormatReader } from '@zxing/browser';
+type BarcodeDetection = { rawValue: string };
+type BarcodeDetectorClass = new () => { detect: (source: ImageBitmapSource) => Promise };
+
export interface BarcodeResult {
code?: string;
error?: string;
@@ -14,15 +17,20 @@ export const useBarcodeScanner = () => {
const reader = new BrowserMultiFormatReader();
let active = true;
let stop: (() => void) | undefined;
+ let currentVideoElement: HTMLVideoElement | null = null;
const start = async () => {
try {
- if ('BarcodeDetector' in window) {
- const detector = new (window as typeof window & { BarcodeDetector: any }).BarcodeDetector();
+ const detectorClass = (
+ window as typeof window & { BarcodeDetector?: BarcodeDetectorClass }
+ ).BarcodeDetector;
+ if (detectorClass) {
+ const detector = new detectorClass();
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
if (videoRef.current) {
videoRef.current.srcObject = stream;
await videoRef.current.play();
+ currentVideoElement = videoRef.current;
}
const scan = async () => {
if (!active || !videoRef.current) return;
@@ -58,8 +66,9 @@ export const useBarcodeScanner = () => {
return () => {
active = false;
stop?.();
- if (videoRef.current?.srcObject) {
- (videoRef.current.srcObject as MediaStream).getTracks().forEach((track) => track.stop());
+ const stream = currentVideoElement?.srcObject as MediaStream | null;
+ if (stream) {
+ stream.getTracks().forEach((track) => track.stop());
}
};
}, []);
diff --git a/src/models/Product.ts b/src/models/Product.ts
index 34595fa..809cfa1 100644
--- a/src/models/Product.ts
+++ b/src/models/Product.ts
@@ -4,7 +4,6 @@ export interface Product {
category: string;
unit_type: string;
bulk_name?: string;
- units_per_bulk?: number;
barcode?: string;
archived: boolean;
created_at: number;
diff --git a/src/screens/ManageProductsScreen.tsx b/src/screens/ManageProductsScreen.tsx
index 1e80f08..f55ff81 100644
--- a/src/screens/ManageProductsScreen.tsx
+++ b/src/screens/ManageProductsScreen.tsx
@@ -6,6 +6,9 @@ import {
TextField,
Typography,
InputAdornment,
+ Dialog,
+ DialogTitle,
+ DialogContent,
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import { Link as RouterLink } from 'react-router-dom';
@@ -15,6 +18,7 @@ import { ProductRow } from '../components/ProductRow';
import { useCategories, useProducts } from '../hooks/dataHooks';
import { useDatabase } from '../context/DBProvider';
import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE } from '../models/Product';
+import { BarcodeScannerView } from '../components/BarcodeScannerView';
export const ManageProductsScreen = () => {
const db = useDatabase();
@@ -23,7 +27,8 @@ export const ManageProductsScreen = () => {
const [search, setSearch] = useState('');
const [name, setName] = useState('');
const [category, setCategory] = useState('');
- const [unitsPerBulk, setUnitsPerBulk] = useState(6);
+ const [barcode, setBarcode] = useState('');
+ const [scannerOpen, setScannerOpen] = useState(false);
const categoryOptions = useMemo(() => {
const categoryNames = categories.map((item) => item.name);
@@ -54,12 +59,13 @@ export const ManageProductsScreen = () => {
category,
unit_type: DEFAULT_UNIT_TYPE,
bulk_name: DEFAULT_BULK_NAME,
- units_per_bulk: unitsPerBulk,
+ barcode: barcode || undefined,
archived: false,
created_at: Date.now(),
updated_at: Date.now(),
});
setName('');
+ setBarcode('');
};
const updateProduct = async (
@@ -67,7 +73,7 @@ export const ManageProductsScreen = () => {
updates: {
name: string;
category: string;
- units_per_bulk?: number;
+ barcode?: string;
},
) => {
await db.products.update(productId, {
@@ -113,12 +119,24 @@ export const ManageProductsScreen = () => {
))}
- setUnitsPerBulk(Number(event.target.value))}
- />
+ {barcode ? (
+ setBarcode(event.target.value)}
+ InputProps={{
+ endAdornment: (
+
+ ),
+ }}
+ />
+ ) : (
+
+ )}
@@ -133,6 +151,17 @@ export const ManageProductsScreen = () => {
/>
))}
+
);
};