diff --git a/src/app/page.scss b/src/app/page.scss index 7cfa8a3..2c11957 100644 --- a/src/app/page.scss +++ b/src/app/page.scss @@ -42,28 +42,52 @@ body { #index { display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - align-items: stretch; + flex-direction: column; + justify-content: felx-start; + flex-wrap: nowrap; + align-items: flex-start; height: calc(100% - 50px); font-size: 3em; - div { - height: calc(33% - 20px - 4px); - width: calc(50% - 20px - 4px - 20px); - display: flex; - justify-content: center; - align-items: center; - background-color: var(--primary); - margin: 10px; + div.link_category { + display: grid; + grid-template-columns: 100%; + grid-template-rows: 3rem auto; + row-gap: 10px; + height: calc(33% - 20px); + width: calc(100% - 20px); + padding: 10px; - .fa { - margin-right: 15px; + p { + font-size: 1em; + margin: 0px; + width: max-content; } - &:hover { - filter: brightness(0.9); + div.links_btns { + height: calc(100%); + width: 100%; + display: flex; + flex-direction: row; + + div { + height: 100%; + width: calc(25% - 20px); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + row-gap: 5%; + background-color: var(--primary); + margin: 0px 10px; + border-radius: .2em; + + &:hover { + filter: brightness(0.9); + } + } } } -} + + +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 2339463..d6fc478 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,11 +2,12 @@ import React from 'react'; import 'moment/locale/fr'; import moment from 'moment'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import Navbar from '@/components/navbar'; import FontAwesome from 'react-fontawesome'; import { logout } from '@/reducers/login'; import { Action } from 'redux'; +import { State } from "@/types"; import { useRouter } from 'next/navigation'; moment.locale('fr'); @@ -14,6 +15,7 @@ moment.locale('fr'); const App = () => { const dispatch = useDispatch(); const router = useRouter(); + const login = useSelector((state: State) => state.login); return ( <> @@ -22,23 +24,72 @@ const App = () => {
-
router.push('/sell?except=goodies')}> - Vente de bouffe -
-
router.push('/sell?only=goodies')}> - Vente de goodies -
-
router.push('/preparation')}> - Préparation générale -
-
router.push('/preparation?only=pizzas')}> - Préparation des pizzas -
-
router.push('/tv')}> - TV -
-
router.push('/items')}> - Gestion des items + { ['admin','preparator'].includes(login.key) && + <> +
+

Préparation par commande :

+
+
router.push('/preparation')}> + Général +
+
router.push('/preparation?only=pizzas')}> + Pizzas +
+
router.push('/preparation?only=crepes,galettes')}> + Crêpes +
+
router.push('/preparation?only=croques')}> + Croques +
+
+
+
+

Préparation par item :

+
+
router.push('/preparation?by=item')}> + Général +
+
router.push('/preparation?only=pizzas&by=item')}> + Pizzas +
+
router.push('/preparation?only=crepes,galettes&by=item')}> + Crêpes +
+
router.push('/preparation?only=croques&by=item')}> + Croques +
+
+
+ + } +
+ { ['admin','seller','preparator','tv'].includes(login.key) && + <> +

Autres controles :

+
+ { ['admin','seller'].includes(login.key) && +
router.push('/sell')}> + Vente +
+ } + { login.key === 'admin' && +
router.push('/items')}> + Gestion +
+ } + { ['admin','preparator'].includes(login.key) && +
router.push('/service')}> + Service +
+ } + { ['admin','tv'].includes(login.key) && +
router.push('/tv')}> + TV +
+ } +
+ + }
diff --git a/src/app/preparation/page.scss b/src/app/preparation/page.scss index c10a323..10d2b77 100644 --- a/src/app/preparation/page.scss +++ b/src/app/preparation/page.scss @@ -19,12 +19,12 @@ .status { margin: auto; width: 100%; - height: 100%; + height: calc(100% - 20px); padding: 10px 14px 0; - - &::-webkit-scrollbar { - width: 0 !important; - } + overflow-x: hidden; + overflow-y: auto; + scrollbar-color: var(--primary-light) transparent; + scrollbar-width: auto; .title { font-size: 1.5em; @@ -40,6 +40,7 @@ .status .orders .order { background: var(--primary-light); padding-left: 10px; + position: relative; display: flex; justify-content: space-between; align-items: center; @@ -51,9 +52,13 @@ margin: 5px 0; width: 20%; + .place, + .quantity { + font-size: 1.75em; + } + .place { margin-bottom: 10px; - font-size: 1.75em; } } @@ -66,6 +71,23 @@ } } + &.timewarning:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 10px; + height: 100%; + } + + &.orange:before { + background-color: var(--toastify-color-warning); + } + + &.red:before { + background-color: var(--toastify-color-error); + } + .next { background-color: var(--success); width: 20%; @@ -81,6 +103,11 @@ transition: transform 0.5s ease-in, opacity 0.5s ease-in; } + &.disabled { + visibility: hidden; + background-color: var(--warning-dark); + } + &.downgrade { background-color: var(--warning-dark); diff --git a/src/app/preparation/page.tsx b/src/app/preparation/page.tsx index 4a10a9c..5f20893 100644 --- a/src/app/preparation/page.tsx +++ b/src/app/preparation/page.tsx @@ -19,9 +19,15 @@ const Page = () => { // Renvoie les commandes contenant au moins un item dans la catégory du paramètre if (searchParams.has("only")) { + const categoriesToDisplay = searchParams.get("only").split(","); orders = orders.filter((order) => - order.orderItems.some((orderItem) => orderItem.item.category.key == searchParams.get("only")) + order.orderItems.some((orderItem) => categoriesToDisplay.includes(orderItem.item.category.key)) ); + if (searchParams.has("by") && searchParams.get("by") == "item") { + orders.forEach((order) => { + order.orderItems = order.orderItems.filter((orderItem) => categoriesToDisplay.includes(orderItem.item.category.key)); + }); + } } // used only to refresh the component every minute @@ -65,11 +71,44 @@ const Page = () => { } }; + const separate_by: string = searchParams.get('by') ?? 'order'; + const displayOrders = (orders: Array) => { + + const tenMinutesAgo: moment.Moment = moment().subtract(1, 'minutes'); + const twentyMinutesAgo: moment.Moment = moment().subtract(20, 'minutes'); + + type itemQuantity = { + id: number, + name: string, + quantity: number, + } + + const items: Array = []; + + if (separate_by === 'item') { + orders.forEach((order) => { + order.orderItems.forEach(orderItem => { + const itemId: number = orderItem.item.id; + const itemName: string = orderItem.item.name + if (items.some(item => item.id === itemId)) { + items.find(item => item.id === itemId).quantity ++; + } else { + const item: itemQuantity = { + id: itemId, + name: itemName, + quantity: 1 + }; + items.push(item); + } + }) + }); + } + return (
- {orders.map((order) => ( -
+ {separate_by === 'order' && orders.map((order) => ( +
{order.place} {moment(order.createdAt).fromNow(true)} @@ -89,12 +128,22 @@ const Page = () => {
) : ( -
editOrder(order)}> +
editOrder(order)}>
)}
))} + {separate_by === 'item' && items.map((item) => ( +
+
+ x {item.quantity} +
+
    + {item.name} +
+
+ ))}
); }; diff --git a/src/app/service/page.scss b/src/app/service/page.scss new file mode 100644 index 0000000..73ebe18 --- /dev/null +++ b/src/app/service/page.scss @@ -0,0 +1,161 @@ +.preparation-mode-button { + padding: 0 10px; + width: 210px; + text-align: left; + height: 100%; + line-height: 50px; + + &.downgrade { + background-color: var(--warning-dark); + } +} + +#preparation { + display: flex; + height: calc(100% - 50px); + justify-content: space-between; + background-color: var(--primary); + + .status.service { + margin: auto; + width: 100%; + height: calc(100% - 20px); + padding: 10px 14px 0; + overflow-x: hidden; + overflow-y: auto; + scrollbar-color: var(--primary-light) transparent; + scrollbar-width: auto; + + .title { + font-size: 1.5em; + line-height: 60px; + } + + // Removes the visuals clicks for pending (because you can't downgrade a pending order) + &.pending .orders .order .next.downgrade { + background-color: var(--danger-dark); + } + } + + .status .orders.service { + width: calc(100% - 10px); + display: grid; + grid-template-columns: 50% 50%; + column-gap: 10px; + } + + .status .orders .order { + background: var(--primary-light); + padding-left: 10px; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; + + .titles { + display: flex; + flex-direction: column; + margin: 5px 0; + width: 20%; + + .place, + .quantity { + font-size: 1.75em; + } + + .place { + margin-bottom: 10px; + } + } + + .items { + text-align: left; + margin-right: auto; + + .options { + font-style: italic; + } + } + + &.timewarning:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 10px; + height: 100%; + } + + &.orange:before { + background-color: var(--toastify-color-warning); + } + + &.red:before { + background-color: var(--toastify-color-error); + } + + .next { + background-color: var(--success); + width: 20%; + font-size: 2em; + position: relative; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: center; + transition: background-color 0.5s ease-in; + + .fa { + transition: transform 0.5s ease-in, opacity 0.5s ease-in; + } + + &.downgrade { + background-color: var(--warning-dark); + + .fa { + transform: rotateZ(180deg); + } + } + + &:before { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &:hover { + filter: brightness(0.9); + } + } + } +} + +.preparation-modal { + .actions { + display: flex; + flex-flow: row nowrap; + justify-content: center; + + .button { + display: inline-block; + padding: 40px 100px; + margin: 0 20px; + + color: #fff; + + &.cancel { + background: var(--danger); + } + + &.confirm { + background: var(--success); + } + + &:hover { + filter: brightness(0.9); + } + } + } +} diff --git a/src/app/service/page.tsx b/src/app/service/page.tsx new file mode 100644 index 0000000..b779c10 --- /dev/null +++ b/src/app/service/page.tsx @@ -0,0 +1,144 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import "../page.scss"; +import "./page.scss"; +import Navbar from "../../components/navbar"; +import moment from "moment"; +import FontAwesome from "react-fontawesome"; +import { useSelector } from "react-redux"; +import { Order, State, Status } from "@/types"; +import { downgradeOrder, upgradeOrder } from "@/utils/orders"; +import Modal from "../../components/modals/modal"; +import Loader from "../../components/loader"; +import { useSearchParams } from "next/navigation"; + +const Page = () => { + const searchParams = useSearchParams(); + let orders = useSelector((state: State) => state.orders); + + // Renvoie les commandes contenant au moins un item dans la catégory du paramètre + if (searchParams.has("only")) { + orders = orders.filter((order) => + order.orderItems.some((orderItem) => orderItem.item.category.key == searchParams.get("only")) + ); + } + + // used only to refresh the component every minute + const [tictac, setTicTac] = useState(false); + const [loading, setLoading] = useState(null); + const [confirmOrder, setConfirmOrder] = useState(null); + const [downgradeMode, setDowngradeMode] = useState(false); + + // used only to refresh the component every minute to refresh the duration on the + useEffect(() => { + const interval = setInterval(() => { + setTicTac(!tictac); + }, 1000 * 60); + + return () => clearInterval(interval); + }); + + const editOrder = async (order: Order, confirmed = false) => { + // If ready to be confirmed + if (order.status === Status.READY && !confirmed && !downgradeMode) { + setConfirmOrder(order); + } + // If ready to be cancelled + else if (order.status === Status.PENDING && !confirmed && downgradeMode) { + setConfirmOrder(order); + } else { + if (!loading) { + setLoading(order); + try { + if (!downgradeMode) { + await upgradeOrder(order); + } else { + setDowngradeMode(false); + await downgradeOrder(order); + } + } catch (e) { + } + setLoading(null); + setConfirmOrder(null); + } + } + }; + + const displayOrders = (orders: Array) => { + + const tenMinutesAgo: moment.Moment = moment().subtract(10, 'minutes'); + const twentyMinutesAgo: moment.Moment = moment().subtract(20, 'minutes'); + + return ( +
+ {orders.map((order) => ( +
+
+ {order.place} + {moment(order.createdAt).fromNow(true)} +
+
    + {order.orderItems.map((orderItem, index) => ( +
  • + {orderItem.item.name} +
    + {orderItem.supplements.map((orderSuppl) => orderSuppl.supplement.name).join(", ")} +
    +
  • + ))} +
+ {loading && loading.id === order.id ? ( +
+ +
+ ) : ( +
editOrder(order)}> + +
+ )} +
+ ))} +
+ ); + }; + + return ( + <> + +
setDowngradeMode(!downgradeMode)} + className={`preparation-mode-button ${downgradeMode ? "downgrade" : ""}`}> + {!downgradeMode ? "Retour cuisine" : "Servir"} +
+
+
+
+ A servir + {displayOrders(orders.filter((order) => order.status === Status.READY))} +
+
+ + {downgradeMode ? ( +

Annuler la commande {confirmOrder && confirmOrder.place} ?

+ ) : ( +

La commande {confirmOrder && confirmOrder.place} a-t-elle bien été livrée ?

+ )} +
+
setConfirmOrder(null)}> + {loading ? : "Annuler"} +
+
{ + await editOrder(confirmOrder, true); + setConfirmOrder(null); + }}> + {loading ? : "Confirmer"} +
+
+
+ + ); +}; + +export default Page; diff --git a/src/app/tv/page.tsx b/src/app/tv/page.tsx index fbe03d7..8b5cfc9 100644 --- a/src/app/tv/page.tsx +++ b/src/app/tv/page.tsx @@ -70,9 +70,11 @@ const OrderGrid = ({ orders, passingRef }: { orders: Array; passingRe const Page = () => { const orders = useSelector((state: State) => state.orders); - const pendingOrders = orders.filter((order) => order.status === Status.PENDING); - const preparingOrders = orders.filter((order) => order.status === Status.PREPARING); - const readyOrders = orders.filter((order) => order.status === Status.READY); + const tvOrders = orders.filter(order => order.orderItems.some(item => item.item.category.needsPreparation) === true) + + const pendingOrders = tvOrders.filter((order) => order.status === Status.PENDING); + const preparingOrders = tvOrders.filter((order) => order.status === Status.PREPARING); + const readyOrders = tvOrders.filter((order) => order.status === Status.READY); const refs = useRef([null, null, null]); const router = useRouter(); diff --git a/src/types.ts b/src/types.ts index 6ada748..3c9f30a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,7 @@ export interface Category extends Identifiable { name: string; key: string; items: Array; + needsPreparation: boolean; } export interface OrderItem {