Skip to content

Commit

Permalink
Add pickup books dialog on order page
Browse files Browse the repository at this point in the history
  • Loading branch information
diogotcorreia committed Jul 7, 2021
1 parent 4b22e60 commit 278ecdf
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 12 deletions.
39 changes: 30 additions & 9 deletions src/components/Orders/OrderPage/OrderData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
Typography,
} from '@material-ui/core';
import { useSnackbar } from 'notistack';
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { Order } from '../../../types/database';
import { moveOrderToNextStatus } from '../../../utils/api';
import OrderStatusChip from '../OrderStatusChip';
import OrderBookList from './OrderBookList';
import PickupBooksDialog from './PickupBooksDialog';

const useStyles = makeStyles((theme) => ({
card: {
Expand All @@ -21,16 +22,25 @@ const useStyles = makeStyles((theme) => ({
nextStatusBtn: {
margin: theme.spacing(1),
},
headerBar: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing(2),
},
}));

interface Props {
order: Order;
setOrder: React.Dispatch<React.SetStateAction<Order | undefined>>;
updateHook(): void;
}

export default function OrderData({ order, setOrder }: Props) {
export default function OrderData({ order, updateHook }: Props) {
const classes = useStyles();
const { enqueueSnackbar } = useSnackbar();
const [pickupDialogOpen, setPickupDialogOpen] = useState(false);

const togglePickupDialog = () => setPickupDialogOpen((state) => !state);

const handleNextStatus = useCallback(async () => {
const newStatus = await moveOrderToNextStatus(order.id);
Expand All @@ -42,15 +52,26 @@ export default function OrderData({ order, setOrder }: Props) {
return;
}

setOrder({ ...order, status: newStatus });
updateHook();
enqueueSnackbar('Estado alterado com sucesso', { variant: 'success' });
}, [enqueueSnackbar, order, setOrder]);
}, [enqueueSnackbar, order, updateHook]);

return (
<div>
<Typography variant="h4" className={classes.card}>
Encomenda #{order.id}
</Typography>
<div className={classes.headerBar}>
<Typography variant="h4" className={classes.card}>
Encomenda #{order.id}
</Typography>
<Button color="primary" variant="outlined" onClick={togglePickupDialog}>
Levantamento de Produtos
</Button>
<PickupBooksDialog
order={order}
open={pickupDialogOpen}
handleToggle={togglePickupDialog}
updateHook={updateHook}
/>
</div>
<Card className={classes.card}>
<CardContent>
<Typography variant="h5">Informação</Typography>
Expand All @@ -73,7 +94,7 @@ export default function OrderData({ order, setOrder }: Props) {
onClick={handleNextStatus}
className={classes.nextStatusBtn}
>
Marcar como avisado
Marcar como notificado
</Button>
)}
</Grid>
Expand Down
154 changes: 154 additions & 0 deletions src/components/Orders/OrderPage/PickupBooksDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
makeStyles,
Typography,
} from '@material-ui/core';
import React, { useState, useEffect, useMemo } from 'react';
import IncreaseIcon from '@material-ui/icons/AddRounded';
import DecreaseIcon from '@material-ui/icons/RemoveRounded';
import { useSnackbar } from 'notistack';
import { Order } from '../../../types/database';
import { pickupProducts } from '../../../utils/api';

interface Props {
order: Order;
open: boolean;
handleToggle(): void;
updateHook(): void;
}

const useStyles = makeStyles((theme) => ({
listItem: {
display: 'flex',
justifyContent: 'space-between',
},
quantityControls: {
marginLeft: theme.spacing(4),
display: 'flex',
alignItems: 'center',
},
quantityText: {
textAlign: 'center',
},
}));

export default function PickupBooksDialog({
order,
open,
handleToggle,
updateHook,
}: Props) {
const classes = useStyles();
const [quantities, setQuantities] = useState<Record<string, number>>({});
const { enqueueSnackbar } = useSnackbar();

const increaseQuantity = (isbn: string) => () => {
const currentQuantity = quantities[isbn] || 0;
const book = order.books?.find((b) => b.isbn === isbn);
const maxQuantity =
(book?.availableQuantity ?? 0) - (book?.pickedupQuantity ?? 0) || 0;
if (currentQuantity < maxQuantity)
setQuantities({ ...quantities, [isbn]: currentQuantity + 1 });
};

const decreaseQuantity = (isbn: string) => () => {
const currentQuantity = quantities[isbn] || 0;
if (currentQuantity > 0)
setQuantities({ ...quantities, [isbn]: currentQuantity - 1 });
};

const booksToPickup = useMemo(
() =>
order.books?.filter(
(book) => book.availableQuantity > book.pickedupQuantity
) ?? [],
[order.books]
);

useEffect(() => {
setQuantities(
booksToPickup.reduce(
(acc, book) => ({
...acc,
[book.isbn]: book.availableQuantity - book.pickedupQuantity,
}),
{}
)
);
}, [booksToPickup]);

const handleSubmit = async () => {
const success = await pickupProducts(order.id, quantities);
if (success) {
enqueueSnackbar('Livros marcados como levantados!', {
variant: 'success',
});
updateHook();
} else {
enqueueSnackbar('Ocorreu um erro a marcar os livros como levantados', {
variant: 'error',
});
}
// Close dialog
handleToggle();
};

return (
<Dialog onClose={handleToggle} open={open} maxWidth="md">
<DialogTitle>Levantamento de Produtos</DialogTitle>
<DialogContent>
{booksToPickup.length === 0 && (
<Typography color="textSecondary">
Não existem livros por levantar
</Typography>
)}
{booksToPickup.map((book) => (
<div key={book.isbn} className={classes.listItem}>
<div>
<Typography>{book.name}</Typography>
<Typography color="textSecondary">
{book.isbn} | {book.publisher}
</Typography>
</div>
<div className={classes.quantityControls}>
<IconButton
onClick={decreaseQuantity(book.isbn)}
disabled={(quantities[book.isbn] || 0) === 0}
>
<DecreaseIcon />
</IconButton>
<div className={classes.quantityText}>
<Typography>{quantities[book.isbn] || 0}</Typography>
<Typography color="textSecondary" variant="caption" noWrap>
Max. {book.availableQuantity - book.pickedupQuantity}
</Typography>
</div>
<IconButton
onClick={increaseQuantity(book.isbn)}
disabled={
(quantities[book.isbn] || 0) ===
book.availableQuantity - book.pickedupQuantity
}
>
<IncreaseIcon />
</IconButton>
</div>
</div>
))}
</DialogContent>
<DialogActions>
<Button onClick={handleToggle} color="primary">
Cancelar
</Button>
<Button onClick={handleSubmit} color="primary">
Levantar
</Button>
</DialogActions>
</Dialog>
);
}
10 changes: 7 additions & 3 deletions src/pages/OrderPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSnackbar } from 'notistack';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router';
import BackButton from '../components/BackButton';
import Loading from '../components/Loading';
Expand All @@ -18,7 +18,7 @@ export default function OrderOnePage() {
const history = useHistory();
const { enqueueSnackbar } = useSnackbar();

useEffect(() => {
const loadData = useCallback(() => {
ipcRenderer.send('db-order-find-one', id);
ipcRenderer.once(
'db-result-order-find-one',
Expand All @@ -33,12 +33,16 @@ export default function OrderOnePage() {
);
}, [enqueueSnackbar, history, id]);

useEffect(() => {
loadData();
}, [loadData]);

if (!data) return <Loading />;

return (
<div>
<BackButton />
<OrderData order={data} setOrder={setData} />
<OrderData order={data} updateHook={loadData} />
</div>
);
}
65 changes: 65 additions & 0 deletions src/server/queries/orders.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ipcMain, IpcMainEvent } from 'electron';
import log from 'electron-log';
import { Knex } from 'knex';
import db from '../database';
import { registerListener } from '../ipcWrapper';

Expand Down Expand Up @@ -202,3 +203,67 @@ registerListener('db-order-next-status', async (orderId: number) => {
await db('orders').update({ status: newStatus }).where('id', orderId);
return newStatus;
});

const recalculateOrderStatus = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
trx: Knex.Transaction<any, any[]>,
id: number
) => {
const orderBooks = await trx
.select('id')
.from('orders_books')
.whereNot('target_quantity', trx.ref('pickedup_quantity'))
.andWhere('order_id', id);

if (orderBooks.length > 0) return;

await trx('orders').update({ status: 'finished' }).where('id', id);
};

registerListener(
'db-order-pick-up',
async (orderId: number, bookMap: Record<string, number>) => {
await db.transaction(async (trx) => {
await Promise.all(
Object.entries(bookMap).map(async ([isbn, quantity]) => {
if (quantity === 0) return;

const [
{ id, availableQuantity, pickedupQuantity },
] = await trx
.select(
'id',
'id',
'available_quantity as availableQuantity',
'pickedup_quantity as pickedupQuantity'
)
.from('orders_books')
.where('isbn', isbn)
.andWhere('order_id', orderId);

if (availableQuantity - pickedupQuantity < quantity)
throw new Error(
`Tried to pickup more quantity than available (${
availableQuantity - pickedupQuantity
} < ${quantity}). Order ${orderId}. ISBN ${isbn}`
);

await trx('orders_books')
.update({ pickedup_quantity: pickedupQuantity + quantity })
.where('id', id);

await trx
.insert({
orders_books_id: id,
timestamp: trx.fn.now(),
quantity,
type: 'pickedup',
})
.into('orders_books_history');
})
);
await recalculateOrderStatus(trx, orderId);
});
return true;
}
);
5 changes: 5 additions & 0 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ export const parseImportFromWook = (wookIds: string[]) =>

export const moveOrderToNextStatus = (orderId: number) =>
fetchFromIpc<OrderStatus | false>('db-order-next-status', orderId);

export const pickupProducts = (
orderId: number,
bookMap: Record<string, number>
) => fetchFromIpc<boolean>('db-order-pick-up', orderId, bookMap);

0 comments on commit 278ecdf

Please sign in to comment.