diff --git a/packages/customer/package.json b/packages/customer/package.json index 1bf01ae..d52218b 100644 --- a/packages/customer/package.json +++ b/packages/customer/package.json @@ -33,6 +33,7 @@ "axios": "^0.19.0", "axios-hooks": "^1.7.2", "connected-react-router": "^6.6.1", + "normalizr": "^3.6.0", "query-string": "^6.10.1", "ramda": "^0.26.1", "react": "^16.12.0", @@ -58,6 +59,7 @@ "@types/fs-extra": "^8.0.1", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", + "@types/normalizr": "^2.0.18", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "@types/react-gtm-module": "^2.0.0", diff --git a/packages/customer/public/data/show/menu/en.json b/packages/customer/public/data/show/menu/en.json index 01fed2c..4de8553 100644 --- a/packages/customer/public/data/show/menu/en.json +++ b/packages/customer/public/data/show/menu/en.json @@ -2,7 +2,7 @@ { "index": 0, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "STARTERS", "dishName": "Beef tartare", "dishDescription": "With onion, pickled cucumber, mushrooms and yolk, served with bread", @@ -12,7 +12,7 @@ { "index": 1, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "STARTERS", "dishName": "Mexican nachos", "dishDescription": "Served with sultry beef", @@ -22,7 +22,7 @@ { "index": 2, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "SOUPS", "dishName": "Beef guts", "dishDescription": "According to a family recipe", @@ -32,7 +32,7 @@ { "index": 3, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "SOUPS", "dishName": "Beetroot", "dishDescription": "Served with a meat croquette", @@ -42,7 +42,7 @@ { "index": 4, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "MAIN DISHES", "dishName": "Oven trout", "dishDescription": "Served with grilled vegetables", @@ -52,7 +52,7 @@ { "index": 5, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "MAIN DISHES", "dishName": "Dumplings - 10 pcs", "dishVariantPrice": 14, @@ -61,10 +61,10 @@ { "index": 6, "language": "en", - "restaurant": "demo", + "restaurant": "show", "section": "MAIN DISHES", "dishName": "Dumplings - 10 pcs", "dishVariantPrice": 14, "dishVariantName": "with meat" } -] +] \ No newline at end of file diff --git a/packages/customer/public/index.html b/packages/customer/public/index.html index a2f1440..32fc941 100644 --- a/packages/customer/public/index.html +++ b/packages/customer/public/index.html @@ -1,29 +1,23 @@ - - - - - - - - - - menuo - - - - - -
- - + + + + + + + + + + + menuo + + + + + + +
+ + + \ No newline at end of file diff --git a/packages/customer/src/pages/Menu/components/MenuSection.tsx b/packages/customer/src/pages/Menu/components/MenuSection.tsx index 95a2146..316c155 100644 --- a/packages/customer/src/pages/Menu/components/MenuSection.tsx +++ b/packages/customer/src/pages/Menu/components/MenuSection.tsx @@ -39,9 +39,9 @@ export interface MenuSectionDispatchProps { export interface MenuSectionProps extends MenuSectionOwnProps, - MenuSectionStateProps, - MenuSectionDispatchProps, - WithStyles {} + MenuSectionStateProps, + MenuSectionDispatchProps, + WithStyles { } const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1) @@ -84,9 +84,9 @@ export const MenuSection: FC = ({ // const id = variant.entry._id const count = basket.filter( (entry: any) => - entry.dish === variant.entry.dishName && - (entry.variant === variant.entry.dishVariantName || - (!entry.variant && !variant.entry.dishVariantName)), + entry.dish === dish.name && + (entry.variant === variant.name || + (!entry.variant && !variant.name)), ).length const variantText = (variant.name ? variant.name + ' - ' : '') + diff --git a/packages/shared/interfaces/menu.ts b/packages/shared/interfaces/menu.ts index 6d37559..b2247d7 100644 --- a/packages/shared/interfaces/menu.ts +++ b/packages/shared/interfaces/menu.ts @@ -10,23 +10,24 @@ export interface IRestaurant { export type IMenu = ISection[] export interface ISection { + id: string name: string description?: string dishes: IDish[] } export interface IDish { + id: string name: string description?: string variants: IVariant[] } export interface IVariant { - _id: string + id: string price: number name?: string description?: string - entry: MenuEntry } // DB diff --git a/packages/shared/transformations/menu.spec.ts b/packages/shared/transformations/menu.spec.ts new file mode 100644 index 0000000..d22330f --- /dev/null +++ b/packages/shared/transformations/menu.spec.ts @@ -0,0 +1,710 @@ +import { normalizeMenu } from './menu' +import { IMenu } from '../interfaces' +import { pipe, toLower } from 'ramda' + +describe('Menu', () => { + const menu: IMenu = [ + { + id: '0', + name: 'Zupy', + dishes: [ + { + id: '0', + name: 'Zalewajka', + variants: [ + { + id: 'default-15', + price: 15, + }, + ], + description: 'Barley soup Cracow style', + }, + { + id: '1', + name: 'Barszcz czerwony z pierogami z mięsem', + variants: [ + { + id: 'default-15', + price: 15, + }, + ], + description: 'Beetroot soup with dumplings with meat', + }, + { + id: '2', + name: 'Grzybowa', + variants: [ + { + id: 'default-17', + price: 17, + }, + ], + description: 'Mushroom soup', + }, + ], + }, + { + id: '1', + name: 'Sałatki', + dishes: [ + { + id: '0', + name: 'Sałatka z watróbką', + variants: [ + { + id: '0', + price: 29, + }, + ], + description: + 'Wątróbka drobiowa,maliny, ser feta i dresing balsamiczny Lettuce with stewed chicken liver, feta cheese and raspberries', + }, + { + id: '0', + name: 'Sałatka z kurczakiem, kozim serem i pieczona papryką', + variants: [ + { + id: '0', + price: 30, + }, + ], + description: + 'Lettuce with grilled chicken, goat cheese and roasted pepper', + }, + ], + }, + { + name: 'Dania główne', + dishes: [ + { + name: 'Pierogi z kapustą i grzybami', + variants: [ + { + price: 28, + }, + ], + description: 'Polish dumplings with stewed sauerkraut and mushrooms', + }, + { + name: 'Placki ziemniaczane z gulaszem z dzika', + variants: [ + { + price: 35, + }, + ], + description: 'Potato pancakes served with wild boar goulash', + }, + { + name: 'Placki ziemniaczane z sosem borowikowym', + variants: [ + { + price: 30, + }, + ], + description: 'Potato pancakes served with porcini mushroom sauce', + }, + { + name: + 'Pierś kurczaka faszerowana szpinakiem i serem feta z sosem z suszonych pomidorów, ziemniaki i grillowanymi warzywami', + variants: [ + { + price: 38, + }, + ], + description: + 'Stuffed chicken breast with spinach and feta cheese served with sun dried tomato sauce, potatoes and grilled vegetables', + }, + { + name: + 'Tradycyjny kotlet schabowy podawany z ziemniakami i kapustą zasmażaną', + variants: [ + { + price: 37, + }, + ], + description: + 'Traditional breaded pork chop served with potatoes and fried cabbage', + }, + { + name: + 'Pstrąg z Doliny Kluczwody pieczony z masłem czosnkowym, pieczone ziemniaki i miks sałat', + variants: [ + { + price: 39, + }, + ], + description: + 'Roasted local trout with garlic butter, served with baked potatoes and mix of lettuce', + }, + { + name: 'Pierogi z cielęciną podawane z sosem borowikowym', + variants: [ + { + price: 33, + }, + ], + description: + 'Polish dumplings with minced veal served with porcini mushroom sauce', + }, + { + name: 'Pierogi chłopskie z ziemniakami, serem i boczkiem', + variants: [ + { + price: 38, + }, + ], + description: 'Dumplings with potatoes, cottage cheese and bacon', + }, + { + name: + 'Policzki wołowe w sosie winno - czekoladowym podawane z gorczycowym puree ziemniaczanym oraz jarmużem z piklowanym chilli', + variants: [ + { + price: 45, + }, + ], + description: + 'Beef cheeks in wine - chocolatr sauce served on potato and mustard seed puree, kale with pickled chilli peppers', + }, + { + name: + 'Żeberka wieprzowe pieczone w piwie i miodzie podawane z ziemniaczanym puree, kapusta zasmażana', + variants: [ + { + price: 39, + }, + ], + description: + 'Pork ribs roasted in beer and honey served with potato puree and fried cabbage', + }, + { + name: + 'Polędwica wołowa z grilla, zielona sałata z pomidorami, frytki lub ziemniak z dipem, sos borowikowy', + variants: [ + { + price: 75, + }, + ], + description: 'Tenderloin', + }, + { + name: + 'Antrykot z grilla, zielona sałata z pomidorami, frytki lub ziemniak z dipem, sos borowikowy', + variants: [ + { + price: 70, + }, + ], + description: 'Rib eye', + }, + ], + }, + { + name: 'Dodatki', + dishes: [ + { + name: 'Frytki', + variants: [ + { + price: 9, + }, + ], + description: 'Chips', + }, + { + name: 'Kapusta zasmażana', + variants: [ + { + price: 9, + }, + ], + description: 'Fried cabbage', + }, + { + name: 'Ziemniaki', + variants: [ + { + price: 9, + }, + ], + description: 'Potatoes', + }, + { + name: 'Mix sałat z pomidorkami', + variants: [ + { + price: 9, + }, + ], + description: 'Lettuce with cherry tomatoes', + }, + { + name: 'Grillowany ziemniak z dipem', + variants: [ + { + price: 9, + }, + ], + description: 'Grilled potato with tzatziki dip', + }, + { + name: 'Grillowane warzywa', + variants: [ + { + price: 9, + }, + ], + description: 'Grilled vegetables', + }, + ], + }, + { + name: 'Desery', + dishes: [ + { + name: 'Racuchy z jabłkami', + variants: [ + { + price: 16, + }, + ], + description: 'Polish apple pancakes with powdered sugar', + }, + { + name: 'Suflet czekoladowy podawany z lodami waniliowymi', + variants: [ + { + price: 19, + }, + ], + description: 'Chocolate souffle served with vanilla ice-cream', + }, + ], + }, + { + name: 'Napoje', + dishes: [ + { + name: 'Herbata / Tea', + variants: [ + { + price: 9, + }, + ], + }, + { + name: 'Espresso', + variants: [ + { + price: 7, + }, + ], + }, + { + name: 'Kawa czarna / Black coffee', + variants: [ + { + price: 8, + }, + ], + }, + { + name: 'Kawa biała / Coffee with milk', + variants: [ + { + price: 9, + }, + ], + }, + { + name: 'Capuccino', + variants: [ + { + price: 10, + }, + ], + }, + { + name: 'Latte', + variants: [ + { + price: 10, + }, + ], + }, + { + name: 'Pepsi', + variants: [ + { + name: '200ml', + price: 7, + }, + ], + }, + { + name: '7 Up', + variants: [ + { + name: '200ml', + price: 7, + }, + ], + }, + { + name: 'Mirinda', + variants: [ + { + name: '200ml', + price: 7, + }, + ], + }, + { + name: 'Soki owocowe', + variants: [ + { + name: '200ml', + price: 7, + }, + ], + }, + { + name: 'Cisowianka Perlage lekko gazowana', + variants: [ + { + name: '300ml', + price: 7, + }, + { + name: '700ml', + price: 14, + }, + ], + description: 'Sparkling water', + }, + { + name: 'Kwas chlebowy', + variants: [ + { + price: 14, + }, + ], + }, + { + name: 'Cydr lubelski', + variants: [ + { + price: 16, + }, + ], + }, + { + name: 'Sok ze świeżych owoców', + variants: [ + { + name: '250ml', + price: 16, + }, + ], + }, + { + name: 'Cisowianka niegazowana', + variants: [ + { + name: '300ml', + price: 7, + }, + { + name: '700ml', + price: 14, + }, + ], + description: 'Still water', + }, + ], + }, + { + name: 'Przystawki', + dishes: [ + { + name: 'Gołąbek podawany z sosem pomidorowym lub borowikowym', + variants: [ + { + price: 20, + }, + ], + description: + 'Stuffed cabbage leave with rice and meat served with tomato sauce or porcini mushroom sauce', + }, + { + name: 'Tatar z polędwicy wołowej', + variants: [ + { + price: 35, + }, + ], + description: 'Beef tenderloin tartare', + }, + { + name: 'Śledź po Krakowsku', + variants: [ + { + price: 19, + }, + ], + description: 'Herring Cracow style', + }, + { + name: + 'Kiełbaska jagnięco-cielęce z grilla podawane z konfiturą z czerwonej cebuli', + variants: [ + { + price: 29, + }, + ], + description: + 'Grilled lamb and veal sausage served withred onion confitur', + }, + ], + }, + { + name: 'Piwa', + dishes: [ + { + name: 'Piwo lane Miłosław Pilzner', + variants: [ + { + name: '330ml', + price: 10, + }, + { + name: '500ml', + price: 13, + }, + ], + }, + { + name: 'Regionalne piwa z browaru Trzy Korony', + variants: [ + { + price: 15, + }, + ], + description: '330ml', + }, + ], + }, + { + name: 'Wina', + dishes: [ + { + name: 'Trebbiano, Rocca Estate', + variants: [ + { + name: '150ml', + price: 16, + }, + { + name: '750ml', + price: 70, + }, + ], + description: 'ITG Puglia, Italy', + }, + { + name: 'Kaiken Malbec Reserva 2013', + variants: [ + { + name: '750ml', + price: 120, + }, + ], + description: 'Kaiken Mendoza Argentina', + }, + { + name: 'Sangiovese, Rocca Estate', + variants: [ + { + name: '150ml', + price: 16, + }, + { + name: '750ml', + price: 70, + }, + ], + description: 'ITG Puglia, Italy', + }, + { + name: 'Ripasso Valpolicella 2014', + variants: [ + { + name: '750ml', + price: 149, + }, + ], + description: 'D.O.C, Italy', + }, + { + name: 'Picton Bay Sauvignon Blanc', + variants: [ + { + name: '750ml', + price: 130, + }, + ], + description: 'Marlborough, New Zealand', + }, + { + name: 'Prosseco Sartori Brut', + variants: [ + { + name: '200ml', + price: 32, + }, + ], + description: 'Veneto, Italy', + }, + { + name: 'Bodegas Centenarias Reservado Cabernet Sauvignon', + variants: [ + { + name: '750ml', + price: 80, + }, + ], + description: 'Chile', + }, + { + name: 'Bodegas Centanarias Reservado Chardonnay', + variants: [ + { + name: '750ml', + price: 80, + }, + ], + description: 'Chile', + }, + ], + }, + { + name: 'Alkohle', + dishes: [ + { + name: 'Wyborowa', + variants: [ + { + name: '40ml', + price: 10, + }, + ], + }, + { + name: 'Finlandia', + variants: [ + { + name: '40ml', + price: 12, + }, + ], + }, + { + name: 'Chopin', + variants: [ + { + name: '40ml', + price: 14, + }, + ], + }, + { + name: 'Żołądkowa gorzka', + variants: [ + { + name: '40ml', + price: 10, + }, + ], + }, + { + name: 'Wiśniówka', + variants: [ + { + name: '40ml', + price: 10, + }, + ], + }, + { + name: 'Żubrówka', + variants: [ + { + name: '40ml', + price: 10, + }, + ], + }, + { + name: "Jack Daniel's", + variants: [ + { + name: '40ml', + price: 16, + }, + ], + }, + { + name: "Ballantine's Fnest", + variants: [ + { + name: '40ml', + price: 14, + }, + ], + }, + { + name: 'Glenvlivet 12YO', + variants: [ + { + name: '40ml', + price: 28, + }, + ], + }, + { + name: 'Hennesy VS', + variants: [ + { + name: '40ml', + price: 22, + }, + ], + }, + { + name: 'Hannesy Fine de Cognac', + variants: [ + { + name: '40ml', + price: 25, + }, + ], + }, + { + name: 'Lagavulin 16YO', + variants: [ + { + name: '40ml', + price: 42, + }, + ], + }, + ], + }, + ] + + test('Normalize menu', async () => { + const result = normalizeMenu(menu) + + expect(result).toEqual([1, 2, 3]) + }) +}) diff --git a/packages/shared/transformations/menu.ts b/packages/shared/transformations/menu.ts index 1f22824..a7a63b9 100644 --- a/packages/shared/transformations/menu.ts +++ b/packages/shared/transformations/menu.ts @@ -4,57 +4,91 @@ import { MenuEntry, IVariant, IMenu, + IDish, } from '../interfaces/menu' import { toPairs, groupBy } from 'ramda' -export const unnestMenu = ({ menu, restaurant, language }: IRestaurant): Menu => - menu - .flatMap(s => - s.dishes.flatMap(d => - d.variants.map(v => ({ - _id: v._id, - language, - restaurant, - section: s.name, - sectionDescription: s.description, - dishName: d.name, - dishDescription: d.description, - dishVariantDescription: v.description, - dishVariantPrice: v.price, - dishVariantName: v.name, - })), - ), - ) - .map((entry, index) => ({ index, ...entry })) - -const menuEntryToVariant = (entry: MenuEntry): IVariant => ({ - _id: entry._id, - name: entry.dishVariantName, - price: entry.dishVariantPrice, - entry, -}) - -const buildDishesGroup = (dishes: MenuEntry[]) => - toPairs(groupBy(d => d.dishName, dishes as MenuEntry[])) - .sort(([_s1, e1], [_s2, e2]) => e1[0].index - e2[0].index) - .map(([dishName, entries]) => ({ - name: dishName, - description: entries[0].dishDescription, - variants: entries - .sort((e1, e2) => e1.index - e2.index) - .map(menuEntryToVariant), - })) - -const buildSectionGroup = (menu: Menu): IMenu => - toPairs( - groupBy( - e => e.section, - menu.sort((e1, e2) => e1.index - e2.index), - ), - ).map(([sectionName, entries]) => ({ - name: sectionName, - description: entries[0].sectionDescription, - dishes: buildDishesGroup(entries), - })) - -export const nestMenu = (menu: Menu): IMenu => buildSectionGroup(menu) +// --- Normalizr --- // +import { denormalize, normalize, schema } from 'normalizr' + +const variant = new schema.Entity( + 'variants', + {}, + { idAttribute: (v) => '' + v.name + v.price }, +) +const dish = new schema.Entity( + 'dishes', + { + variants: [variant], + }, + { idAttribute: 'name' }, +) +const category = new schema.Entity( + 'categories', + { + dishes: [dish], + }, + { idAttribute: 'name' }, +) + +export const normalizeMenu = (menu: IMenu) => { + const normalizedData = normalize(menu, [category]) + console.log(normalizedData) + return normalizedData +} + +export const denormalizeMenu = (menu: Menu) => {} + +// --- Normalizr --- // + +// export const unnestMenu = ({ menu, restaurant, language }: IRestaurant): Menu => +// menu +// .flatMap((s) => +// s.dishes.flatMap((d) => +// d.variants.map((v) => ({ +// _id: v._id, +// language, +// restaurant, +// section: s.name, +// sectionDescription: s.description, +// dishName: d.name, +// dishDescription: d.description, +// dishVariantDescription: v.description, +// dishVariantPrice: v.price, +// dishVariantName: v.name, +// })), +// ), +// ) +// .map((entry, index) => ({ index, ...entry })) + +// const menuEntryToVariant = (entry: MenuEntry): IVariant => ({ +// _id: entry._id, +// name: entry.dishVariantName, +// price: entry.dishVariantPrice, +// entry, +// }) + +// const buildDishesGroup = (dishes: MenuEntry[]) => +// toPairs(groupBy((d) => d.dishName, dishes as MenuEntry[])) +// .sort(([_s1, e1], [_s2, e2]) => e1[0].index - e2[0].index) +// .map(([dishName, entries]) => ({ +// name: dishName, +// description: entries[0].dishDescription, +// variants: entries +// .sort((e1, e2) => e1.index - e2.index) +// .map(menuEntryToVariant), +// })) + +// const buildSectionGroup = (menu: Menu): IMenu => +// toPairs( +// groupBy( +// (e) => e.section, +// menu.sort((e1, e2) => e1.index - e2.index), +// ), +// ).map(([sectionName, entries]) => ({ +// name: sectionName, +// description: entries[0].sectionDescription, +// dishes: buildDishesGroup(entries), +// })) + +// export const nestMenu = (menu: Menu): IMenu => buildSectionGroup(menu) diff --git a/yarn.lock b/yarn.lock index 7d1b8e4..343648d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2198,13 +2198,13 @@ react-is "^16.8.0" "@menuo/qr@link:packages/qr": - version "1.0.3" + version "1.0.0" dependencies: "@types/ramda" "^0.26.39" ramda "^0.26.1" "@menuo/shared@link:packages/shared": - version "1.0.3" + version "1.0.0" dependencies: "@types/ramda" "^0.26.39" ramda "^0.26.1" @@ -2798,6 +2798,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.27.tgz#d7506f73160ad30fcebbcf5b8b7d2d976e649e42" integrity sha512-odQFl/+B9idbdS0e8IxDl2ia/LP8KZLXhV3BUeI98TrZp0uoIzQPhGd+5EtzHmT0SMOIaPd7jfz6pOHLWTtl7A== +"@types/normalizr@^2.0.18": + version "2.0.18" + resolved "https://registry.yarnpkg.com/@types/normalizr/-/normalizr-2.0.18.tgz#a42fc1e33a036be3009d6285dd776d94fd88d60d" + integrity sha1-pC/B4zoDa+MAnWKF3XdtlP2I1g0= + dependencies: + normalizr "*" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -11321,6 +11328,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalizr@*, normalizr@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.0.tgz#b8bbc4546ffe43c1c2200503041642915fcd3e1c" + integrity sha512-25cd8DiDu+pL46KIaxtVVvvEPjGacJgv0yUg950evr62dQ/ks2JO1kf7+Vi5/rMFjaSTSTls7aCnmRlUSljtiA== + npm-bundled@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"