Redesign pick lists for reuse
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Button, Container, Stack, Typography } from '@mui/material';
|
import { Button, Container, Stack, Typography } from '@mui/material';
|
||||||
import { useParams, useNavigate, Link as RouterLink } from 'react-router-dom';
|
import { useParams, useNavigate, Link as RouterLink } from 'react-router-dom';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useAreas, usePickItems, usePickList, useProducts } from '../hooks/dataHooks';
|
import { useAreas, usePickItems, usePickList, useProducts } from '../hooks/dataHooks';
|
||||||
import { useDatabase } from '../context/DBProvider';
|
import { useDatabase } from '../context/DBProvider';
|
||||||
import { PickItemRow } from '../components/PickItemRow';
|
import { PickItemRow } from '../components/PickItemRow';
|
||||||
@@ -14,10 +14,6 @@ export const ActivePickListScreen = () => {
|
|||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!id || !pickList) return;
|
|
||||||
}, [id, pickList]);
|
|
||||||
|
|
||||||
const areaName = useMemo(
|
const areaName = useMemo(
|
||||||
() => areas.find((area) => area.id === pickList?.area_id)?.name ?? 'Area',
|
() => areas.find((area) => area.id === pickList?.area_id)?.name ?? 'Area',
|
||||||
[areas, pickList?.area_id],
|
[areas, pickList?.area_id],
|
||||||
@@ -49,9 +45,7 @@ export const ActivePickListScreen = () => {
|
|||||||
await db.pickItems.delete(itemId);
|
await db.pickItems.delete(itemId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeList = async () => {
|
const returnToLists = () => {
|
||||||
if (!id) return;
|
|
||||||
await db.pickLists.update(id, { completed_at: Date.now() });
|
|
||||||
navigate('/pick-lists');
|
navigate('/pick-lists');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,6 +57,11 @@ export const ActivePickListScreen = () => {
|
|||||||
Add Item
|
Add Item
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{pickList?.notes ? (
|
||||||
|
<Typography variant="body2" color="text.secondary" mb={2}>
|
||||||
|
{pickList.notes}
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<PickItemRow
|
<PickItemRow
|
||||||
@@ -76,8 +75,8 @@ export const ActivePickListScreen = () => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button fullWidth sx={{ mt: 3 }} variant="outlined" onClick={completeList}>
|
<Button fullWidth sx={{ mt: 3 }} variant="outlined" onClick={returnToLists}>
|
||||||
Complete List
|
Save and Return
|
||||||
</Button>
|
</Button>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ export const HomeScreen = () => (
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Button component={RouterLink} to="/start" variant="contained">
|
<Button component={RouterLink} to="/start" variant="contained">
|
||||||
Start New Pick List
|
Create Pick List
|
||||||
</Button>
|
</Button>
|
||||||
<Button component={RouterLink} to="/pick-lists" variant="outlined">
|
<Button component={RouterLink} to="/pick-lists" variant="outlined">
|
||||||
View Pick Lists
|
View & Edit Pick Lists
|
||||||
</Button>
|
</Button>
|
||||||
<Button component={RouterLink} to="/products" variant="outlined">
|
<Button component={RouterLink} to="/products" variant="outlined">
|
||||||
Manage Products
|
Manage Products
|
||||||
|
|||||||
@@ -1,32 +1,156 @@
|
|||||||
import { Button, Container, List, ListItemButton, ListItemText, Typography } from '@mui/material';
|
import {
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { Delete, Edit } from '@mui/icons-material';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { usePickLists, useAreas } from '../hooks/dataHooks';
|
import { usePickLists, useAreas } from '../hooks/dataHooks';
|
||||||
|
import { useDatabase } from '../context/DBProvider';
|
||||||
|
import { PickList } from '../models/PickList';
|
||||||
|
|
||||||
export const PickListsScreen = () => {
|
export const PickListsScreen = () => {
|
||||||
const lists = usePickLists();
|
const lists = usePickLists();
|
||||||
const areas = useAreas();
|
const areas = useAreas();
|
||||||
|
const db = useDatabase();
|
||||||
|
const [editingList, setEditingList] = useState<PickList | null>(null);
|
||||||
|
const [areaId, setAreaId] = useState('');
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
|
const sortedLists = useMemo(() => {
|
||||||
|
return [...lists].sort((a, b) => a.created_at - b.created_at);
|
||||||
|
}, [lists]);
|
||||||
|
|
||||||
const getAreaName = (areaId: string) => areas.find((a) => a.id === areaId)?.name ?? 'Unknown area';
|
const getAreaName = (areaId: string) => areas.find((a) => a.id === areaId)?.name ?? 'Unknown area';
|
||||||
|
|
||||||
|
const openEdit = (list: PickList) => {
|
||||||
|
setEditingList(list);
|
||||||
|
setAreaId(list.area_id);
|
||||||
|
setNotes(list.notes ?? '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeEdit = () => {
|
||||||
|
setEditingList(null);
|
||||||
|
setAreaId('');
|
||||||
|
setNotes('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveEdit = async () => {
|
||||||
|
if (!editingList || !areaId) return;
|
||||||
|
await db.pickLists.update(editingList.id, {
|
||||||
|
area_id: areaId,
|
||||||
|
notes: notes.trim() || undefined,
|
||||||
|
});
|
||||||
|
closeEdit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteList = async (list: PickList) => {
|
||||||
|
const confirmed = window.confirm('Remove this pick list and its items?');
|
||||||
|
if (!confirmed) return;
|
||||||
|
await db.pickItems.where('pick_list_id').equals(list.id).delete();
|
||||||
|
await db.pickLists.delete(list.id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 4 }}>
|
<Container sx={{ py: 4 }}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Pick Lists
|
Pick Lists
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button component={RouterLink} to="/start" variant="contained" sx={{ mb: 2 }}>
|
<Button component={RouterLink} to="/start" variant="contained" sx={{ mb: 2 }}>
|
||||||
Start New
|
Add Pick List
|
||||||
</Button>
|
</Button>
|
||||||
<List>
|
<List>
|
||||||
{lists.map((list) => (
|
{sortedLists.map((list) => (
|
||||||
<ListItemButton key={list.id} component={RouterLink} to={`/pick-lists/${list.id}`} divider>
|
<ListItem key={list.id} divider secondaryAction={
|
||||||
<ListItemText
|
<Stack direction="row" spacing={1}>
|
||||||
primary={getAreaName(list.area_id)}
|
<IconButton
|
||||||
secondary={format(list.created_at, 'PPpp')}
|
edge="end"
|
||||||
/>
|
aria-label="Edit"
|
||||||
</ListItemButton>
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
openEdit(list);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="Delete"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
deleteList(list);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
}>
|
||||||
|
<ListItemButton component={RouterLink} to={`/pick-lists/${list.id}`}>
|
||||||
|
<ListItemText
|
||||||
|
primary={getAreaName(list.area_id)}
|
||||||
|
secondary={
|
||||||
|
<Stack spacing={0.5}>
|
||||||
|
{list.notes ? <span>{list.notes}</span> : null}
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Created {format(list.created_at, 'PPpp')}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
<Dialog open={Boolean(editingList)} onClose={closeEdit} fullWidth>
|
||||||
|
<DialogTitle>Edit Pick List</DialogTitle>
|
||||||
|
<DialogContent sx={{ pt: 1 }}>
|
||||||
|
<Stack spacing={2} mt={1}>
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
fullWidth
|
||||||
|
label="Area"
|
||||||
|
value={areaId}
|
||||||
|
onChange={(event) => setAreaId(event.target.value)}
|
||||||
|
>
|
||||||
|
{areas.map((area) => (
|
||||||
|
<MenuItem key={area.id} value={area.id}>
|
||||||
|
{area.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
label="Notes"
|
||||||
|
value={notes}
|
||||||
|
onChange={(event) => setNotes(event.target.value)}
|
||||||
|
multiline
|
||||||
|
minRows={2}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={closeEdit}>Cancel</Button>
|
||||||
|
<Button variant="contained" onClick={saveEdit} disabled={!areaId}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,18 +10,27 @@ export const StartPickListScreen = () => {
|
|||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [areaId, setAreaId] = useState('');
|
const [areaId, setAreaId] = useState('');
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
if (!areaId) return;
|
if (!areaId) return;
|
||||||
const pickListId = uuidv4();
|
const pickListId = uuidv4();
|
||||||
await db.pickLists.add({ id: pickListId, area_id: areaId, created_at: Date.now() });
|
await db.pickLists.add({
|
||||||
|
id: pickListId,
|
||||||
|
area_id: areaId,
|
||||||
|
created_at: Date.now(),
|
||||||
|
notes: notes.trim() || undefined,
|
||||||
|
});
|
||||||
navigate(`/pick-lists/${pickListId}`);
|
navigate(`/pick-lists/${pickListId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 4 }}>
|
<Container sx={{ py: 4 }}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Start Pick List
|
Create Pick List
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
|
Pick lists are reusable. Create one for each store area and update it anytime you restock.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -37,8 +46,15 @@ export const StartPickListScreen = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
label="Notes (optional)"
|
||||||
|
value={notes}
|
||||||
|
onChange={(event) => setNotes(event.target.value)}
|
||||||
|
multiline
|
||||||
|
minRows={2}
|
||||||
|
/>
|
||||||
<Button variant="contained" disabled={!areaId} onClick={start}>
|
<Button variant="contained" disabled={!areaId} onClick={start}>
|
||||||
Start
|
Save Pick List
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user