Merge pull request #13 from beatz174-bit/codex/move-camera-frame-to-popup-on-scan

Move barcode scanning to modal and allow adding missing products
This commit is contained in:
beatz174-bit
2025-11-21 18:30:32 +10:00
committed by GitHub
2 changed files with 73 additions and 4 deletions
+64 -3
View File
@@ -1,6 +1,20 @@
import { Alert, Card, CardContent, Chip, Container, Divider, Stack, Typography } from '@mui/material'; import {
Alert,
Button,
Card,
CardContent,
Chip,
Container,
Dialog,
DialogContent,
DialogTitle,
Divider,
Stack,
Typography,
} from '@mui/material';
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { BarcodeScannerView } from '../components/BarcodeScannerView'; import { BarcodeScannerView } from '../components/BarcodeScannerView';
import { useDatabase } from '../context/DBProvider'; import { useDatabase } from '../context/DBProvider';
import { Area } from '../models/Area'; import { Area } from '../models/Area';
@@ -14,8 +28,10 @@ interface ProductLookup {
export const BarcodeScannerScreen = () => { export const BarcodeScannerScreen = () => {
const db = useDatabase(); const db = useDatabase();
const navigate = useNavigate();
const [lastCode, setLastCode] = useState(''); const [lastCode, setLastCode] = useState('');
const [lookup, setLookup] = useState<ProductLookup>({ pickLists: [] }); const [lookup, setLookup] = useState<ProductLookup>({ pickLists: [] });
const [scannerOpen, setScannerOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!lastCode) { if (!lastCode) {
@@ -54,13 +70,45 @@ export const BarcodeScannerScreen = () => {
const hasProduct = useMemo(() => lookup.product && lookup.product !== null, [lookup.product]); const hasProduct = useMemo(() => lookup.product && lookup.product !== null, [lookup.product]);
const handleDetected = (code: string) => {
setLastCode(code);
setScannerOpen(false);
};
const handleAddProduct = () => {
if (!lastCode) return;
navigate('/products', { state: { newBarcode: lastCode } });
};
return ( return (
<Container sx={{ py: 4 }}> <Container sx={{ py: 4 }}>
<Typography variant="h5" gutterBottom> <Typography variant="h5" gutterBottom>
Barcode Scanner Barcode Scanner
</Typography> </Typography>
<BarcodeScannerView onDetected={(code) => setLastCode(code)} />
<Stack spacing={2} sx={{ mt: 2 }}> <Stack spacing={2} sx={{ mt: 2 }}>
<Card variant="outlined">
<CardContent>
<Stack spacing={1.5}>
<Stack spacing={0.25}>
<Typography variant="subtitle1">Scan barcodes</Typography>
<Typography variant="body2" color="text.secondary">
Open the camera in a popup to keep scanned details easy to read.
</Typography>
</Stack>
<Stack direction="row" spacing={1} alignItems="center">
<Button variant="contained" onClick={() => setScannerOpen(true)}>
Scan barcode
</Button>
{lastCode ? (
<Button variant="outlined" onClick={() => setScannerOpen(true)}>
Scan another
</Button>
) : null}
</Stack>
</Stack>
</CardContent>
</Card>
{lastCode ? ( {lastCode ? (
<Card variant="outlined"> <Card variant="outlined">
<CardContent> <CardContent>
@@ -77,7 +125,14 @@ export const BarcodeScannerScreen = () => {
</Typography> </Typography>
{lookup.product === null ? ( {lookup.product === null ? (
<Alert severity="warning" sx={{ mt: 2 }}> <Alert severity="warning" sx={{ mt: 2 }}>
This barcode is not present in the product list. <Stack spacing={1}>
<Typography variant="body2">
This barcode is not present in the product list.
</Typography>
<Button variant="outlined" size="small" onClick={handleAddProduct}>
Add barcode to products
</Button>
</Stack>
</Alert> </Alert>
) : null} ) : null}
</CardContent> </CardContent>
@@ -129,6 +184,12 @@ export const BarcodeScannerScreen = () => {
</Card> </Card>
) : null} ) : null}
</Stack> </Stack>
<Dialog open={scannerOpen} onClose={() => setScannerOpen(false)} fullWidth>
<DialogTitle>Scan a barcode</DialogTitle>
<DialogContent>
<BarcodeScannerView onDetected={handleDetected} />
</DialogContent>
</Dialog>
</Container> </Container>
); );
}; };
+9 -1
View File
@@ -11,7 +11,7 @@ import {
DialogContent, DialogContent,
} from '@mui/material'; } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useLocation } from 'react-router-dom';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { ProductRow } from '../components/ProductRow'; import { ProductRow } from '../components/ProductRow';
@@ -24,6 +24,7 @@ export const ManageProductsScreen = () => {
const db = useDatabase(); const db = useDatabase();
const products = useProducts(); const products = useProducts();
const categories = useCategories(); const categories = useCategories();
const location = useLocation();
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [name, setName] = useState(''); const [name, setName] = useState('');
const [category, setCategory] = useState(''); const [category, setCategory] = useState('');
@@ -51,6 +52,13 @@ export const ManageProductsScreen = () => {
[products, search], [products, search],
); );
useEffect(() => {
const state = location.state as { newBarcode?: string } | null;
if (state?.newBarcode) {
setBarcode(state.newBarcode);
}
}, [location.state]);
const addProduct = async () => { const addProduct = async () => {
if (!name || !category) return; if (!name || !category) return;
await db.products.add({ await db.products.add({