diff --git a/README.md b/README.md index adaf841..e6f0dfb 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,5 @@ ## CHEF POINT Menu -This is a very simple app that nicely displays scheduled dishes from a JSON file. It automatically discards dishes from the past. Supports Dark Mode +This is a very simple app that nicely displays scheduled dishes from MongoDB. It automatically discards dishes from the past. Supports Dark Mode and large screen formats. diff --git a/components/day/Day.js b/components/day/Day.js index 1161dc7..d9cd472 100644 --- a/components/day/Day.js +++ b/components/day/Day.js @@ -42,9 +42,9 @@ export default function Day({ content }) { {content.special_day ?
{content.special_day.icon}
: null}
- - - + + +
); diff --git a/components/dish/Dish.js b/components/dish/Dish.js index 4258ced..c8a9f3c 100644 --- a/components/dish/Dish.js +++ b/components/dish/Dish.js @@ -35,8 +35,8 @@ export default function Dish({ type, name }) { > {label} -

{name.pt}

-

{name.en}

+

{name ? name.title_pt : ''}

+

{name ? name.title_en : ''}

); } diff --git a/components/info/Info.js b/components/info/Info.js new file mode 100644 index 0000000..7632809 --- /dev/null +++ b/components/info/Info.js @@ -0,0 +1,5 @@ +import styles from './Info.module.css'; + +export default function Info({ text }) { + return
{text}
; +} diff --git a/components/info/Info.module.css b/components/info/Info.module.css new file mode 100644 index 0000000..06e2d57 --- /dev/null +++ b/components/info/Info.module.css @@ -0,0 +1,8 @@ +.container { + width: 100%; + text-align: center; + padding-top: 50px; + font-size: 30px; + font-weight: var(--font-medium); + color: var(--gray-4); +} diff --git a/data/menu.json b/data/menu.json deleted file mode 100644 index c016a0c..0000000 --- a/data/menu.json +++ /dev/null @@ -1,322 +0,0 @@ -[ - { - "date": "2022-05-02", - "special_day": false, - "vegan": { - "pt": "Tortilla Espanhola de Batata e Pimento", - "en": "Spanish Potato and Pepper Tortilla" - }, - "fish": { - "pt": "Tiras de Pota Frita c/ Arroz de Coentros e Maionese de Limão", - "en": "Fried Cuttlefish Strips w/ Coriander Rice and Lemon Mayonnaise" - }, - "meat": { - "pt": "Costeleta de Porco c/ Batata Frita e Coleslaw", - "en": "Pork Chop w/ French Fries and Coleslaw" - } - }, - { - "date": "2022-05-03", - "special_day": false, - "vegan": { - "pt": "Quiche de Espinafres c/ Coleslaw", - "en": "Spinach Quiche w/ Coleslaw" - }, - "fish": { - "pt": "Lombo de Pescada c/ Molho de Vinho Branco e Espinafres Frescos", - "en": "Hake Loin w/ White Wine Sauce and Fresh Spinach" - }, - "meat": { - "pt": "Bifinhos de Frango c/ Molho de Ervas Aromáticas", - "en": "Chicken Steaks w/ Herb Sauce" - } - }, - { - "date": "2022-05-04", - "special_day": false, - "vegan": { - "pt": "Hamburguer Vegetariano c/ Arroz de Cenoura", - "en": "Vegetarian Burger w/ Carrot Rice" - }, - "fish": { - "pt": "Spaghetti Frutti Di Mare", - "en": "Spaghetti Frutti Di Mare" - }, - "meat": { - "pt": "Arroz à Valenciana", - "en": "'Valenciana'-style Rice" - } - }, - { - "date": "2022-05-05", - "special_day": false, - "vegan": { - "pt": "Noodles c/ Legumes Assados e Leite de Côco", - "en": "Noodles w/ Roasted Vegetables and Coconut Milk" - }, - "fish": { - "pt": "Lasanha de Atum", - "en": "Tuna Lasagna" - }, - "meat": { - "pt": "Vitela Picada c/ Sabores Mexicanos", - "en": "Minced Veal w/ Mexican Flavors" - } - }, - { - "date": "2022-05-06", - "special_day": false, - "vegan": { - "pt": "Massa Napolitana c/ Tofu", - "en": "Neapolitan Pasta w/ Tofu" - }, - "fish": { - "pt": "Panado de Pescada c/ Salada de Batata", - "en": "Breaded Hake w/ Potato Salad" - }, - "meat": { - "pt": "Panado de Frango c/ Puré de Batata", - "en": "Breaded Chicken w/ Mashed Potato" - } - }, - { - "date": "2022-05-09", - "special_day": false, - "vegan": { - "pt": "Almôndegas de Vegetais c/ Couscous de Laranja", - "en": "Vegetable Meatballs w/ Orange Couscous" - }, - "fish": { - "pt": "Filetes de Pescada à Portuguesa c/ Arroz de Tomate", - "en": "Portuguese-style Hake Fillets w/ Tomato Rice" - }, - "meat": { - "pt": "Lombo de Porco Assado c/ Batatas e Grelos Salteados", - "en": "Roasted Pork Loin w/ Sauteed Potatoes and Greens" - } - }, - { - "date": "2022-05-10", - "special_day": false, - "vegan": { - "pt": "Salada Grega c/ Tzatziki", - "en": "Greek Salad w/ Tzatziki" - }, - "fish": { - "pt": "Tintureira no Forno à Portuguesa", - "en": "Portuguese-style Blue Shark" - }, - "meat": { - "pt": "Coxas de Frango Panadas c/ Salada de Batata", - "en": "Breaded Chicken Thighs w/ Potato Salad" - } - }, - { - "date": "2022-05-11", - "special_day": false, - "vegan": { - "pt": "Noodles c/ Legumes Grelhados e Molho Sweet & Sour", - "en": "Noodles w/ Grilled Vegetables and Sweet & Sour Sauce" - }, - "fish": { - "pt": "Caril de Pota c/ Arroz Branco", - "en": "Octopus Curry w/ White Rice" - }, - "meat": { - "pt": "Empadão de Carne c/ Cenoura e Pimentos", - "en": "Meat Pie w/ Carrots and Peppers" - } - }, - { - "date": "2022-05-12", - "special_day": false, - "vegan": { - "pt": "Crepes c/ Recheio de Cogumelos", - "en": "Crepes w/ Mushroom Stuffing" - }, - "fish": { - "pt": "Massa Fusilli c/ Molho Cremoso de Atum", - "en": "Fusilli Pasta w/ Creamy Tuna Sauce" - }, - "meat": { - "pt": "Arroz de Frango c/ Chouriço e Bacon", - "en": "Chicken Rice w/ Portuguese Chouriço and Bacon" - } - }, - { - "date": "2022-05-13", - "special_day": false, - "vegan": { - "pt": "Massa Pesto c/ Manjericão e Tofu", - "en": "Pesto Pasta w/ Basil and Tofu" - }, - "fish": { - "pt": "Salada de Peixe c/ Feijão Verde, Tomate e Batata", - "en": "Fish Salad w/ Green Beans, Tomatoes and Potatoes" - }, - "meat": { - "pt": "Hamburguer c/ Cebola Picklada", - "en": "Burger w/ Pickled Onion" - } - }, - { - "date": "2022-05-16", - "special_day": false, - "vegan": { - "pt": "Hambúrguer de Vegetais c/ Arroz de Cenoura", - "en": "Vegetable Burger w/ Carrot Rice" - }, - "fish": { - "pt": "Pescada c/ Molho de Vinho Branco e Espinafres Frescos", - "en": "Hake w/ White Wine Sauce and Fresh Spinach" - }, - "meat": { - "pt": "Frango Agridoce c/ Arroz Branco", - "en": "Sweet & Sour Chicken w/ White Rice" - } - }, - { - "date": "2022-05-17", - "special_day": { "icon": "🇳🇴", "label": "Dia da Noruega" }, - "vegan": { - "pt": "Tabbouleh c/ Hummus", - "en": "Tabbouleh w/ Hummus" - }, - "fish": { - "pt": "Hamburguer de Peixe c/ Molho Tártaro", - "en": "Fish Burger w/ Tartar Sauce" - }, - "meat": { - "pt": "Dia da Noruega", - "en": "Norges Dag" - } - }, - { - "date": "2022-05-18", - "special_day": false, - "vegan": { - "pt": "Lasanha de Legumes", - "en": "Vegetable Lasagna" - }, - "fish": { - "pt": "Salada Italiana de Atum", - "en": "Italian Tuna Salad" - }, - "meat": { - "pt": "Massa Penne Rústica", - "en": "Rustic Penne Pasta" - } - }, - { - "date": "2022-05-19", - "special_day": false, - "vegan": { - "pt": "Tortilla Mexicana", - "en": "Mexican Tortilla" - }, - "fish": { - "pt": "Pastéis de Bacalhau c/ Arroz de Tomate", - "en": "Cod Pies w/ Tomato Rice" - }, - "meat": { - "pt": "Panado de Frango c/ Coleslow e Batatas Fritas", - "en": "Breaded Chicken w/ Coleslow and French Fries" - } - }, - { - "date": "2022-05-20", - "special_day": false, - "vegan": { - "pt": "Crepes Primavera c/ Couscous e Mix de Legumes", - "en": "Spring Crepes w/ Couscous and Vegetable Mix" - }, - "fish": { - "pt": "Rissóis de Camarão c/ Arroz Árabe", - "en": "Shrimp Patties w/ Arabic rice" - }, - "meat": { - "pt": "Tortellini de Carne c/ Molho de Tomate e Azeite de Manjericão", - "en": "Beef Tortellini w/ Tomato Sauce and Basil Oil" - } - }, - { - "date": "2022-05-23", - "special_day": false, - "vegan": { - "pt": "Crepes de Espinafres", - "en": "Spinach Crepes" - }, - "fish": { - "pt": "Feijoada de Pota", - "en": "Octopus w/ Beans" - }, - "meat": { - "pt": "Tiras de Frango c/ Molho Satay de Amendoim", - "en": "Chicken Strips w/ Peanut Satay Sauce" - } - }, - { - "date": "2022-05-24", - "special_day": false, - "vegan": { - "pt": "Guisado de Grão c/ Caril", - "en": "Chickpea Stew with/Curry" - }, - "fish": { - "pt": "Hambúrguer de Peixe c/ Molho Tártaro", - "en": "Fish Burger w/ Tartar Sauce" - }, - "meat": { - "pt": "Lasanha de Carne", - "en": "Meat Lasagna" - } - }, - { - "date": "2022-05-25", - "special_day": false, - "vegan": { - "pt": "Ratatouille c/ Batatas Assadas e Pistou", - "en": "Ratatouille w/ Roasted Potatoes and Pistou Sauce" - }, - "fish": { - "pt": "Pescada à Zé do Pipo", - "en": "'Zé do Pipo'-style Hake" - }, - "meat": { - "pt": "Porco Agridoce c/ Arroz Branco", - "en": "Sweet & Sour Pork w/ White Rice" - } - }, - { - "date": "2022-05-26", - "special_day": false, - "vegan": { - "pt": "Arroz Frito c/ Cogumelos e Ovo", - "en": "Fried Rice w/ Mushrooms and Egg" - }, - "fish": { - "pt": "Calamares c/ Salada de Batata e Coleslaw", - "en": "Squid Rings w/ Potato Salad and Coleslaw" - }, - "meat": { - "pt": "Almôndegas Italianas c/ Puré de Batata", - "en": "Italian Meatballs w/ Mashed Potato" - } - }, - { - "date": "2022-05-27", - "special_day": false, - "vegan": { - "pt": "Hambúrguer de Vegetais c/ Salada de Cenoura e Laranja", - "en": "Vegetable Burger w/ Carrot and Orange Salad" - }, - "fish": { - "pt": "Salada de Pescada c/ Batatas, Azeitonas e Tomate", - "en": "Hake Salad w/ Potatoes, Olives and Tomato" - }, - "meat": { - "pt": "Peito de Perú no Forno c/ Molho de Laranja", - "en": "Oven-baked Turkey Breast w/ Orange Sauce" - } - } -] diff --git a/models/Day.js b/models/Day.js new file mode 100644 index 0000000..b15e49f --- /dev/null +++ b/models/Day.js @@ -0,0 +1,67 @@ +/* * * * * */ +/* MODEL: DAY */ +/* * */ + +/* * */ +/* IMPORTS */ +import mongoose from 'mongoose'; + +/* * */ +/* Schema for MongoDB ["Day"] Object */ +module.exports = + mongoose.models.Day || + mongoose.model( + 'Day', + new mongoose.Schema({ + date: { + type: String, + maxlength: 50, + unique: true, + }, + is_public: { + type: Boolean, + }, + special_day: { + icon: { + type: String, + maxlength: 500, + }, + label: { + type: String, + maxlength: 500, + }, + }, + dishes: { + vegan: { + title_pt: { + type: String, + maxlength: 500, + }, + title_en: { + type: String, + maxlength: 500, + }, + }, + fish: { + title_pt: { + type: String, + maxlength: 500, + }, + title_en: { + type: String, + maxlength: 500, + }, + }, + meat: { + title_pt: { + type: String, + maxlength: 500, + }, + title_en: { + type: String, + maxlength: 500, + }, + }, + }, + }) + ); diff --git a/package.json b/package.json index b712d13..59069c8 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,15 @@ }, "dependencies": { "classnames": "^2.3.1", - "next": "12.1.5", + "mongoose": "^6.3.3", + "next": "12.1.6", "react": "18.1.0", "react-dom": "18.1.0", "swr": "^1.3.0" }, "devDependencies": { - "eslint": "8.14.0", - "eslint-config-next": "12.1.5", - "typescript": "^4.6.3" + "eslint": "8.15.0", + "eslint-config-next": "12.1.6", + "typescript": "^4.6.4" } } diff --git a/pages/_app.js b/pages/_app.js index 9f2d75b..8fb2847 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -9,7 +9,7 @@ import '../styles/variables.css'; export default function LunchMenu({ Component, pageProps }) { return ( - + diff --git a/pages/api/[date].js b/pages/api/[date].js new file mode 100644 index 0000000..5e0beb4 --- /dev/null +++ b/pages/api/[date].js @@ -0,0 +1,58 @@ +import database from '../../services/database'; +import Day from '../../models/Day'; + +export default async function days(req, res) { + // + + // Connect to the Database + database.connect(); + + switch (req.method) { + // + case 'GET': + let getResult; + if (req.query.date == '*') getResult = await getAllDays(); + else getResult = await getDayWith(req.query.date); + await res.status(getResult.status).json(getResult.data); + break; + // + default: + res.setHeader('Allow', ['GET']); + res.status(405).end(`Method ${req.method} Not Allowed`); + break; + } +} + +/* * */ +/* REST: GET */ +async function getAllDays() { + // Fetch all documents from the database and sort them + const allDays = await Day.find({}); + + const filteredDays = allDays.filter((item) => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const itemDate = new Date(item.date); + itemDate.setHours(0, 0, 0, 0); + const isBetweenDates = today.getTime() <= itemDate.getTime(); + return isBetweenDates && item.is_public; + }); + + const sortedDays = filteredDays.sort((a, b) => new Date(a.date) - new Date(b.date)); + return { status: 200, data: sortedDays }; +} + +/* * */ +/* REST: GET */ +async function getDayWith(date) { + // Fetch documents from the database that match the requested 'date' + const foundDay = await Day.find({ date: date }); + + if (foundDay.length > 0) { + // If document with date exists + return { status: 200, data: foundDay[0] }; + } else { + // If document with date does not exist + return { status: 404, data: { message: `Day with date: ${date} not found.` } }; + } +} diff --git a/pages/index.js b/pages/index.js index 99d26c5..d2a53ce 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,21 +1,14 @@ +import useSWR from 'swr'; import Image from 'next/image'; import Day from '../components/day/Day'; import IconButton from '../components/iconButton/IconButton'; -import menu from '../data/menu.json'; +import Info from '../components/info/Info'; import styles from '../styles/Home.module.css'; export default function Home() { // - const filterMenu = menu.filter((item) => { - const today = new Date(); - today.setHours(0, 0, 0, 0); - - const itemDate = new Date(item.date); - itemDate.setHours(0, 0, 0, 0); - - return today.getTime() <= itemDate.getTime(); - }); + const { data: menu } = useSWR('/api/*'); return (
@@ -31,9 +24,15 @@ export default function Home() {
- {filterMenu.map((item, index) => ( - - ))} + {menu ? ( + menu.length ? ( + menu.map((item, index) => ) + ) : ( + + ) + ) : ( + + )}
); diff --git a/pages/tv/index.js b/pages/tv/index.js index 9d48469..bfc2185 100644 --- a/pages/tv/index.js +++ b/pages/tv/index.js @@ -1,4 +1,4 @@ -import menu from '../../data/menu.json'; +import useSWR from 'swr'; import Day from '../../components/day/Day'; import Image from 'next/image'; import styles from '../../styles/Tv.module.css'; @@ -6,15 +6,7 @@ import styles from '../../styles/Tv.module.css'; export default function Home() { // - const filterMenu = menu.filter((item) => { - const today = new Date(); - today.setHours(0, 0, 0, 0); - - const itemDate = new Date(item.date); - itemDate.setHours(0, 0, 0, 0); - - return today.getTime() === itemDate.getTime(); - }); + const { data: menu } = useSWR('/api/*'); return (
@@ -24,11 +16,7 @@ export default function Home() { Full month available at menu.chefpoint.pt

-
- {filterMenu.map((item, index) => ( - - ))} -
+
{menu ? menu.map((item, index) => ) : null}
); } diff --git a/services/database.js b/services/database.js new file mode 100644 index 0000000..30d22b8 --- /dev/null +++ b/services/database.js @@ -0,0 +1,30 @@ +/* * * * * */ +/* DATABASE */ +/* * */ + +/* * */ +/* IMPORTS */ +import mongoose from 'mongoose'; + +module.exports.connect = async function () { + await mongoose + .connect(process.env.MONGODB_CONNECTION_STRING) + // .then(() => console.log('Connected to MongoDB.')) + .catch((error) => { + console.log('Connection to MongoDB failed.'); + console.log('At database.js > mongoose.connect()'); + console.log(error); + process.exit(); + }); +}; + +module.exports.disconnect = async function () { + await mongoose + .disconnect() + // .then(() => console.log('Disconnected from MongoDB.')) + .catch((error) => { + console.log('Failed closing connection to MongoDB.'); + console.log('At database.js > mongoose.disconnect()'); + console.log(error); + }); +};