Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
am9zZWY committed Jun 28, 2024
2 parents e414913 + effd49b commit 3f7e7ac
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 26 deletions.
294 changes: 294 additions & 0 deletions src/app/admin/preparing/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
'use client'

import {useEffect, useState} from "react";
import {addToLocalStorage, getFromLocalStorage} from "@/lib/localStorage.js";
import WithAuth from "../WithAuth.jsx";
import {formatDateTime} from "@/lib/time";
import ErrorMessage from "@/app/components/ErrorMessage.jsx";
import { useRouter } from 'next/navigation';

const FoodItemHistory = ({items, disabled, actions}) => {
const handleForward = actions.forward;
const handleBack = actions.back;
const handleDone = actions.done;
const handleSkip = actions.skip;
const handleCancel = actions.cancel;

const hasBack = () => true;
const hasForward = () => true;

return (
<div className="flex justify-between items-center h-12">
<button onClick={handleBack}
className="font-bold bg-silver-400 px-4 py-2 rounded-lg w-full md:w-auto hover:bg-silver-300"
disabled={!hasBack()}>Back
</button>
{disabled === false && <button onClick={handleSkip}
className="bg-orange-950 text-white px-4 py-2 rounded-lg w-full md:w-auto hover:bg-orange-800"
disabled={disabled}>Skip Order(for now)</button>}
{disabled === false && <button onClick={handleCancel}
className="bg-red-950 text-white px-4 py-2 rounded-lg w-full md:w-auto hover:bg-red-800"
disabled={disabled}>Cancel Order</button>}
{disabled === false && <button onClick={handleDone}
className="bg-green-950 text-white px-4 py-2 rounded-lg w-full md:w-auto hover:bg-green-800"
disabled={disabled}>Order is now Baking</button>}
<button onClick={handleForward}
className="font-bold bg-silver-400 px-4 py-2 rounded-lg w-full md:w-auto hover:bg-silver-300"
disabled={!hasForward()}>Forward
</button>
</div>
);
};


const Checkbox = ({label, disabled, checked}) => {
return (
<div className="flex items-center mb-4">
<label className="inline-flex items-center">
<input type="checkbox" className="form-checkbox h-6 w-6 rounded" disabled={disabled} checked={checked}/>
<span className="ml-2 text-gray-700" disabled={disabled}>{label}</span>
</label>
</div>
);
};

const FoodItem = ({item, disabled}) => {
return (
<div className="flex flex-col">
<div className="flex justify-between items-center mb-2">
<h3 className="font-bold">{item.name || ""}</h3>
<h5 className="font-bold underline">Ingredients</h5>
<Checkbox label="Done"/>
</div>
<div className="bg-silver-400 px-4 py-2 rounded-lg mb-2">
<table className="table-auto w-full">
<thead>
<tr>
<th className="px-4 py-2">Ingredient</th>
<th className="px-4 py-2">Amount</th>
<th className="px-4 py-2">Added?</th>
</tr>
</thead>
<tbody>
{Object.entries(item.ingredients).map(([name, amount]) => (
<tr>
<td className="border px-4 py-2">{name}</td>
<td className="border px-4 py-2">{amount}</td>
<td className="border px-4 py-2">
<Checkbox label="Added" disabled={disabled}/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};

const getHeaders = () => {
const token = getFromLocalStorage('token', '');
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
};
};


const Page = () => {
const [error, setError] = useState('');
const [isDisabled, setDisabled] = useState({});
const [order, setOrder] = useState({});
const [orderNumber, setOrderNumber] = useState("");
const [history, setHistory] = useState([]);
const validOrderStates = ["paid", "pending"];

const router = useRouter();

const ingredients = {
'667bc9f81ad7a76bb34dbf4a': {'Cheese': 'grated', 'Tomato Sauce': 'all over', 'Mushrooms': '2'},
};
useEffect(() => {
// Get the order status from the server
if (!orderNumber) {
setOrder({
"_id": "no current order",
"name": "",
"comment": "",
"items": [],
"orderDate": new Date(),
"timeslot": new Date(),
"totalPrice": 0,
"finishedAt": null,
"status": "Waiting for new order"
})
//set to true if the order is not in valid order states
setDisabled(true)
setTimeout(() => {
// do a reload
actions.forward();
}, 5000);//5 s callback
} else {
fetch(`/api/order/${orderNumber}`)
.then(response => response.json())
.then(data => {
setOrder(data)
//set to true if the order is not in valid order states
setDisabled(validOrderStates.indexOf(data.status) < 0)
});
}
}, [orderNumber]);

const updateOrderStatus = (_id, status) => {
return fetch('/api/order', {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify({id: _id, status})
})
.then(response => response.json())
.catch(error => setError('ErrorMessage updating order status'));
}

const foodItems = (order.items || []).map(item => {
return {
orderid: order.orderid || "",
state: order.status || "",
name: item.name,
ingredients: item.ingredients || {"missing ingredients for": item._id},
}
});

const getNextOrderId = async () => {
// Get the order status from the server
return fetch('/api/order/', {
headers: getHeaders(),
})
.then(response => response.json())
.then(data => {
//now we have orders
//find the oldest that is in valid states
const next_orders = data.filter((order) => validOrderStates.indexOf(order.status) >= 0).filter((order) => history.indexOf(order._id) < 0);
const sorted_orders = next_orders.toSorted((a, b) => new Date(a.orderDate).getTime() - new Date(b.orderDate).getTime()) // Sort by date; latest first
if (sorted_orders.length === 0) {
return "";
}
return sorted_orders[0]._id || "";
})
.catch(error => setError('ErrorMessage fetching getNextOrderId'));
};

const hasComment = (order) => {
return (
typeof order.comment === "string" &&
order.comment !== "" &&
order.comment.toLowerCase() !== "No comment".toLowerCase()
);
};

const actions = {
// TODO:
back: () => {
const historyIndex = history.indexOf(orderNumber);
if (historyIndex > 0)
setOrderNumber(history[historyIndex]);
},
forward: () => {
getNextOrderId().then(nextOrderId => {
if (!nextOrderId) {
// no current Order
setOrderNumber("");
} else {
const new_history = [...history, nextOrderId];
setHistory(new_history);
// addToLocalStorage('baking.orderhistory', new_history);
setOrderNumber(nextOrderId);
}
});
},
skip: () => {
getNextOrderId().then(nextOrderId => {
if (!nextOrderId) {
// no current Order
setOrderNumber("");
} else {
const new_history = [...history, nextOrderId];
setHistory(new_history);
// addToLocalStorage('baking.orderhistory', new_history);
setOrderNumber(nextOrderId);
}
});
},
cancel: () => {
//get the current orderid
if (!isDisabled)
updateOrderStatus(order._id, 'cancelled').then(() => {
actions.forward();
});
},
done: () => {
//get the current orderid
if (!isDisabled)
updateOrderStatus(order._id, 'baking').then(() => {
actions.forward();
});
},
};

return (
<div className="content">
{error && <ErrorMessage error={error}/>}
<div className="p-4">
<h2 className="text-2xl mb-4">Preparing Food</h2>
<span className="font-bold bg-gray-200 px-4 py-2 rounded-lg">
Order: {order._id || ''}
</span>
<div className="flex gap-1 mt-5 items-center justify-start">
<span
className="text-xs text-gray-700 mr-2 uppercase tracking-wider mb-2 rounded px-2 py-0.5 bg-gray-200">
{order.status}
</span>
<span
className="text-xs text-gray-700 mr-2 uppercase tracking-wider mb-2 rounded px-2 py-0.5 bg-gray-200">
{order.totalPrice}
</span>
<span
className="text-xs text-gray-700 mr-2 uppercase tracking-wider mb-2 rounded px-2 py-0.5 bg-gray-200">
{(order.items || []).length} items
</span>

<span
className="text-xs text-gray-700 mr-2 uppercase tracking-wider mb-2 rounded px-2 py-0.5 bg-gray-200">
{formatDateTime(new Date(order.orderDate))}
</span>
</div>
<div className="list-disc list-inside text-sm font-light text-gray-600 mb-4">
<div className="flex flex-col">
<span className="font-bold">For:</span>
<span className="pl-4 italic">{order.name}</span>
</div>
</div>
{hasComment(order) && (
<div className="list-disc list-inside text-sm font-light text-gray-600 mb-4">
<div className="flex flex-col">
<span className="font-bold">Comment:</span>
<span className="pl-4 italic">{order.comment}</span>
</div>
</div>
)}
</div>
<div className="">
<FoodItemHistory items={foodItems} disabled={isDisabled} actions={actions}/>
</div>
{foodItems.map(item => (
<div className="w-full px-2 py-2">
<div
className={"border border-gray-300 rounded-lg shadow-md p-4 relative" + (isDisabled ? " bg-gray-400" : " bg-white")}>
<FoodItem item={item} disabled={isDisabled}/>
</div>
</div>
))}
</div>
);
}

export default WithAuth(Page);
45 changes: 28 additions & 17 deletions src/app/api/manage/db/prepare/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";

const pizzas = [
{ name: "Salami", ingredients: ["Cheese 🧀","Tomato Sauce 🍅","Salami 🍕"] },
{ name: "Ham and mushrooms", ingredients: ["Cheese 🧀","Tomato Sauce 🍅", "Ham 🥓", "Mushrooms 🍄"] },
{ name: "Capriccosa", ingredients: ["Cheese 🧀","Tomato Sauce 🍅","Mushrooms 🍄", "Artichokes 🌱", "Olives 🫒", "Ham 🥓", "Basil 🌿"] },
{ name: "Margherita", ingredients: ["Cheese 🧀","Tomato Sauce 🍅","Basil 🌿"] },
{ name: "Veggies", ingredients: ["Cheese 🧀", "Tomato Sauce 🍅", "Mushrooms 🍄", "Onions 🧅", "Green Peppers 🫑", "Olives 🫒"] },
{ name: "Margherita vegan", ingredients: ["Vegan Cheese 🧀","Tomato Sauce 🍅","Basil 🌿"] },
{ name: "Capriccosa vegan", ingredients: ["Vegan Cheese 🧀","Tomato Sauce 🍅","Mushrooms 🍄", "Artichokes 🌱", "Olives 🫒", "Basil 🌿"] }
];

const pizza_by_name = (pizza_name: string) => {
return pizzas.filter(pizza => pizza.name == pizza_name).map(pizza => pizza.ingredients).flat();
};

/**
* Fill the database
Expand All @@ -29,27 +42,25 @@ export async function POST() {

// Add pizzas
const pizzas = [
{ name: 'Salami/Pepperoni+Ham half', price: 3, type: 'pizza', size: 0.5 },
{ name: 'Salami/Pepperoni+Ham whole', price: 6, type: 'pizza', size: 1 },
{ name: 'Salami/Pepperoni+Ham half', price: 4, type: 'pizza', size: 0.5 },
{ name: 'Salami/Pepperoni+Ham whole', price: 8, type: 'pizza', size: 1 },
{ name: 'Capriccosa half', price: 4, type: 'pizza', size: 0.5 },
{ name: 'Capriccosa whole', price: 8, type: 'pizza', size: 1 },
{ name: 'Margherita half', price: 3, dietary: 'vegetarian', type: 'pizza', size: 0.5 },
{ name: 'Margherita whole', price: 6, dietary: 'vegetarian', type: 'pizza', size: 1 },
{ name: 'Capriccosa half', price: 3, dietary: 'vegetarian', type: 'pizza', size: 0.5 },
{ name: 'Capriccosa whole', price: 6, dietary: 'vegetarian', type: 'pizza', size: 1 },
{ name: 'Veggies half', price: 3, dietary: 'vegetarian', type: 'pizza', size: 0.5 },
{ name: 'Veggies whole', price: 6, dietary: 'vegetarian', type: 'pizza', size: 1 },
{ name: 'Margherita half', price: 3, dietary: 'vegan', type: 'pizza', size: 0.5 },
{ name: 'Margherita whole', price: 6, dietary: 'vegan', type: 'pizza', size: 1 },
{ name: 'Capriccosa half', price: 4, dietary: 'vegan', type: 'pizza', size: 0.5 },
{ name: 'Capriccosa whole', price: 8, dietary: 'vegan', type: 'pizza', size: 1 },
{ name: 'Salami half', price: 4, type: 'pizza', ingredients: pizza_by_name('Salami'), },
{ name: 'Salami full', price: 8, type: 'pizza', ingredients: pizza_by_name('Salami'), },
{ name: 'Ham and mushrooms half', price: 4, type: 'pizza', ingredients: pizza_by_name('Ham and mushrooms'), },
{ name: 'Ham and mushrooms full', price: 8, type: 'pizza', ingredients: pizza_by_name('Ham and mushrooms'), },
{ name: 'Capriccosa half', price: 4, type: 'pizza', ingredients: pizza_by_name('Capriccosa'), },
{ name: 'Capriccosa full', price: 8, type: 'pizza', ingredients: pizza_by_name('Capriccosa'), },
{ name: 'Margherita half', price: 3, dietary: 'vegetarian', type: 'pizza', ingredients: pizza_by_name('Margherita'), },
{ name: 'Margherita full', price: 6, dietary: 'vegetarian', type: 'pizza', ingredients: pizza_by_name('Margherita'), },
{ name: 'Veggies half', price: 3, dietary: 'vegetarian', type: 'pizza', ingredients: pizza_by_name('Veggies'), },
{ name: 'Veggies full', price: 6, dietary: 'vegetarian', type: 'pizza', ingredients: pizza_by_name('Veggies'), },
{ name: 'Margherita vegan half', price: 3, dietary: 'vegan', type: 'pizza', ingredients: pizza_by_name('Margherita vegan'), },
{ name: 'Margherita vegan full', price: 6, dietary: 'vegan', type: 'pizza', ingredients: pizza_by_name('Margherita vegan'), },
{ name: 'Capriccosa vegan half', price: 3, dietary: 'vegan', type: 'pizza', ingredients: pizza_by_name('Capriccosa vegan'), },
{ name: 'Capriccosa vegan full', price: 6, dietary: 'vegan', type: 'pizza', ingredients: pizza_by_name('Capriccosa vegan'), },
];
for (const pizza of pizzas) {
await new Food(pizza).save();
}

// Add the system
const system = new System({ name: constants.SYSTEM_NAME, status: 'active' })
await system.save()
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/order/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function GET(req: Request, { params }: { params: { id: string } })
// Get the foods for the order
const foodsDetails = await Food
.find({ _id: { $in: order.items } })
.select('name price');
.select('name price ingredients');

// Create a map of food details
const foodDetailsMap = foodsDetails
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/layout/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const Header = () => {
window.addEventListener("loginSuccessEvent", () => {
const token = getFromLocalStorage('token');
setAuthed(token != null);
});
})
}, []);

const adminLinks = [
{to: "/admin/prepare", text: "Prepare Food"}, //does not work yet
{to: "/admin/preparing", text: "Prepare Foods"}, //does not work yet
{to: "/admin/manage", text: "Manage DB"},
{to: "/admin/manage/order", text: "Manage Orders"},
{to: "/admin/manage/pizza", text: "Manage Foods"},
Expand Down
Loading

0 comments on commit 3f7e7ac

Please sign in to comment.