diff --git a/ISSUES.txt b/ISSUES.txt new file mode 100644 index 0000000..8fce82a --- /dev/null +++ b/ISSUES.txt @@ -0,0 +1,20 @@ +Idea, Issue, Work Tracking + +[TODO] + + +[Pending] + +- [] #2: Send change value from editing in recipe to machine +- [] #3: Save value to recipe +- [] #5: revert value on close dialog recipe + + +[Rejected] +- [] #4: From #1, will do sync value from server, so that user could save their current edit too + + +[Done] +- [x] #1: Topping value saving bug, fix by snapshot value + + diff --git a/src/lib/components/app-account-select.svelte b/src/lib/components/app-account-select.svelte index 2d4a4a3..319a787 100644 --- a/src/lib/components/app-account-select.svelte +++ b/src/lib/components/app-account-select.svelte @@ -47,9 +47,12 @@ authStore.set(null); let socket = get(socketStore); - if (socket) { - socket.close(1000, 'logout'); - } + + try { + if (socket) { + socket.close(1000, 'logout'); + } + } catch (e) {} socketStore.set(null); if (browser && 'cookieStore' in window) await cookieStore.delete('logged_in'); diff --git a/src/lib/components/recipe-details/columns.ts b/src/lib/components/recipe-details/columns.ts index c679d24..db81e79 100644 --- a/src/lib/components/recipe-details/columns.ts +++ b/src/lib/components/recipe-details/columns.ts @@ -112,9 +112,11 @@ export const columns: ColumnDef[] = [ row_uid: row.original.id, mat_id: row.original.material_id, onEditValue: (changes: any) => { + // console.log('triggered on edit value', changes); + recipeDataEvent.set({ event_type: 'edit_change_value_rpl', - payload: changes, + payload: JSON.parse(changes), index: row.original.id }); diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte index 3d80f8f..445d547 100644 --- a/src/lib/components/recipe-details/recipe-detail.svelte +++ b/src/lib/components/recipe-details/recipe-detail.svelte @@ -11,6 +11,7 @@ import { columns, type RecipelistMaterial } from './columns'; import { get, readable, writable } from 'svelte/store'; import { + currentEditingRecipeProductCode, latestRecipeToppingData, materialFromMachineQuery, materialFromServerQuery @@ -29,8 +30,9 @@ let { recipeData, onPendingChange, + productCode, refPage - }: { recipeData: any; onPendingChange: any; refPage: string } = $props(); + }: { recipeData: any; onPendingChange: any; productCode: string; refPage: string } = $props(); let menuName: string = $state(''); @@ -126,7 +128,9 @@ } } - async function saveRecipe() {} + async function saveRecipe() { + recipeDetailDispatch('saveRecipe'); + } async function sendTriggerBrewNow() { // check queue ready @@ -155,8 +159,8 @@ } } - async function checkChanges(original: any) { - console.log('old', original, 'updated', recipeListMatState); + async function checkChanges(productCode: string, original: any) { + // console.log('old', original, 'updated', recipeListMatState); if (recipeListOriginal.length == 0) { recipeListOriginal = original; } @@ -164,6 +168,7 @@ if (original !== recipeListMatState) { await onPendingChange({ target: 'recipeList', + ref_pd: productCode, value: original }); } @@ -187,6 +192,8 @@ latestRecipeToppingData.set(toppingSlotState); + currentEditingRecipeProductCode.set(productCode); + // save old value\ } }); @@ -237,7 +244,12 @@ - + diff --git a/src/lib/components/recipe-details/recipelist-table.svelte b/src/lib/components/recipe-details/recipelist-table.svelte index ad6445a..caf08f3 100644 --- a/src/lib/components/recipe-details/recipelist-table.svelte +++ b/src/lib/components/recipe-details/recipelist-table.svelte @@ -42,11 +42,13 @@ let { data, columns, - onStateChange + onStateChange, + productCode }: { data: RecipelistMaterial[]; columns: ColumnDef[]; onStateChange: any; + productCode: string; } = $props(); let sorting = $state([]); @@ -75,7 +77,10 @@ getFacetedRowModel: getFacetedRowModel(), onStateChange: async (updater) => { console.log('table state change', data); - await onStateChange(table.getRowModel().rows.map((x) => x.original)); + await onStateChange( + productCode, + table.getRowModel().rows.map((x) => x.original) + ); }, onSortingChange: async (updater) => { console.log('triggering sorting'); @@ -84,7 +89,10 @@ } else { sorting = updater; } - await onStateChange(table.getRowModel().rows.map((x) => x.original)); + await onStateChange( + productCode, + table.getRowModel().rows.map((x) => x.original) + ); }, onRowSelectionChange: async (updater) => { // table.getRowModel().rows.find((x) => x.original.id == ) @@ -92,11 +100,14 @@ rowSelection = updater(rowSelection); let rows = table.getRowModel().rows; - console.log('state size', data, rows); + // console.log('state size', data, rows); } else { rowSelection = updater; } - await onStateChange(table.getRowModel().rows.map((x) => x.original)); + await onStateChange( + productCode, + table.getRowModel().rows.map((x) => x.original) + ); } }); diff --git a/src/lib/components/recipe-details/recipelist-value-editor.svelte b/src/lib/components/recipe-details/recipelist-value-editor.svelte index 994144e..14f728d 100644 --- a/src/lib/components/recipe-details/recipelist-value-editor.svelte +++ b/src/lib/components/recipe-details/recipelist-value-editor.svelte @@ -176,7 +176,7 @@ function saveEditingValue() { console.log('saving value ...', value_event_state); - if (value_event_state === ValueEvent.EDITED) { + if (value_event_state === ValueEvent.EDITED || value_event_state === ValueEvent.SAVED) { let payload = { source: current_editing_data, change: changed_data @@ -209,28 +209,36 @@ function handleToppingGroupChange(v: any) { console.log('change topping group'); + + // TODO: clear topping list, otherwise, it will continue to append filter to list by each navigate back and forth + selected_category_id = v.groupID; // get default selected_topping_list_id = v.idDefault ?? 0; // set to edit state even if selected same group again value_event_state = ValueEvent.EDITED; - if (current_editing_data['toppings'] == undefined || current_editing_data['toppings'] == null) { + if ( + current_editing_data['toppings'] !== undefined || + current_editing_data['toppings'] !== null + ) { let toppings_length = current_editing_data['toppings'].length; if (changed_data['toppings'] == undefined || changed_data['toppings'] == null) { changed_data['toppings'] = new Array(toppings_length); - // console.log('filling change topping', JSON.stringify(changed_data)); + console.log('filling change topping', JSON.stringify(changed_data)); } } + console.log('current editing data', current_editing_data['toppings']); + let idx = getToppingSlotIndex(current_editing_data['mat_id']); // get old state topping idx let current_selection = current_editing_data['toppings'][idx]; // let default_from_recipe = current_editing_data['toppings'][idx]['ListGroupID'][0]; - // console.log(`Current TG: `, JSON.stringify(current_selection)); + console.log(`Current TG: `, JSON.stringify(current_selection)); if (current_selection['groupID'] !== undefined || current_selection['groupID'] !== null) { try { changed_data['toppings'][idx] = current_selection; @@ -250,7 +258,10 @@ // set to edit state even if selected same group again value_event_state = ValueEvent.EDITED; let idx = getToppingSlotIndex(current_editing_data['mat_id']); - if (current_editing_data['toppings'] == undefined || current_editing_data['toppings'] == null) { + if ( + current_editing_data['toppings'] !== undefined || + current_editing_data['toppings'] !== null + ) { let toppings_length = current_editing_data['toppings'].length; // case: topping not init if (changed_data['toppings'] == null || changed_data['toppings'] == undefined) { @@ -590,12 +601,12 @@ - + > --> + diff --git a/src/lib/components/recipe-details/recipelist-value.svelte b/src/lib/components/recipe-details/recipelist-value.svelte index ba31fa2..0c7a0c6 100644 --- a/src/lib/components/recipe-details/recipelist-value.svelte +++ b/src/lib/components/recipe-details/recipelist-value.svelte @@ -10,7 +10,7 @@ import Input from '../ui/input/input.svelte'; import Separator from '$lib/components/ui/separator/separator.svelte'; import Button from '../ui/button/button.svelte'; - import { PencilIcon } from '@lucide/svelte/icons'; + import { PencilIcon, Undo } from '@lucide/svelte/icons'; import * as Tooltip from '../ui/tooltip/index'; import { latestRecipeToppingData, @@ -22,6 +22,9 @@ referenceFromPage } from '$lib/core/stores/recipeStore'; import { get } from 'svelte/store'; + import { sendMessage } from '$lib/core/handlers/ws_messageSender'; + import { auth } from '$lib/core/stores/auth'; + import { departmentStore } from '$lib/core/stores/departments'; let { row_uid, @@ -51,11 +54,16 @@ // toppings let currentToppings: any = $state([]); + let oldToppings: any = $state(null); // current topping of this row let currentToppingInRow: any = $state(); let selectableToppingInGroup: any[] = $state([]); + let currentToppingNamesOnly = $state(''); + let oldToppingNamesOnly = $state(null); + let unsubRecipeDataEvent: any; + let unsubRecipeTopping: any; function isToppingId(mat_id: string): boolean { let mat_num = extractMaterialIdFromDisplay(mat_id); @@ -118,21 +126,54 @@ refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery ); - // console.log(JSON.stringify(groupQuery[0])); - // console.log(JSON.stringify(listQuery[0])); + console.log('old topping', oldToppings[getToppingSlot()]); + + console.log('current topping', current_row_topping); + oldToppingNamesOnly = null; // reset first + if (oldToppings[getToppingSlot()] != current_row_topping) { + console.log('detect change on topping row', row_uid); + + let groupIDchange = + oldToppings[getToppingSlot()]['groupID'] !== current_row_topping['groupID']; + let defaultIDchange = + oldToppings[getToppingSlot()]['defaultIDSelect'] !== current_row_topping['defaultIDSelect']; + + console.log('group change', groupIDchange, 'default id change', defaultIDchange); + + if (!groupIDchange && !defaultIDchange) { + oldToppingNamesOnly = null; + } else { + // has change, display old name + let oldGroupData = groupQuery.find( + (x: any) => x.groupID.toString() === oldToppings[getToppingSlot()]['groupID'] + ); + let oldListData = listQuery.filter( + (x: any) => + oldGroupData != undefined && + Object.keys(oldGroupData).includes('idInGroup') && + oldGroupData['idInGroup'].split(',').includes(x.id.toString()) + ); + oldToppingNamesOnly = oldGroupData['otherName']; + } + } else { + oldToppingNamesOnly = null; + } let groupData = groupQuery.find( (x: any) => x.groupID.toString() === current_row_topping['groupID'] ); let listData = listQuery.filter( (x: any) => - groupData && + groupData != undefined && Object.keys(groupData).includes('idInGroup') && groupData['idInGroup'].split(',').includes(x.id.toString()) ); console.log('topping data', JSON.stringify(groupData), 'list', listData.length); + currentToppingNamesOnly = groupData ? (groupData['otherName'] ?? '-') : '-'; + + // NOTE: send topping data to value editor currentToppingInRow = current_row_topping['groupID'] === 0 || current_row_topping['groupID'] === '0' ? { @@ -178,13 +219,20 @@ } function initialize() { - currentToppings = get(latestRecipeToppingData); - hasMixOrder = mix_order == 1; extractStringParam(); if (isToppingId(mat_id) && onDetectToppingSlot) { + // FIXME: this should not be updated yet, must be updated after user pressed save only + let latest_topping_data_snapshot = $state.snapshot(get(latestRecipeToppingData)); + currentToppings = latest_topping_data_snapshot; + // console.log('current topping in initialize', currentToppings); + if (oldToppings == null) { + // console.log('saving original topping', oldToppings); + oldToppings = $state.snapshot(currentToppings); + } + currentMaterialType = 'topping'; getToppingDisplay(); @@ -200,9 +248,9 @@ // console.log('type get', mat_type_t1, mat_type_t2); currentMaterialType = mat_type_t1 === mat_type_t2 ? mat_type_t1 : mat_type_t2; - if (hasMixOrder) { - console.log('detect mix order', mat_num); - } + // if (hasMixOrder) { + // console.log('detect mix order', mat_num); + // } // if (feed.parameter > 0 || feed.pattern > 0) { // console.log('has feed fields', JSON.stringify(feed)); @@ -210,6 +258,19 @@ } } + function applyChanges(value: any) { + let keys = Object.keys(value); + + for (const key of keys) { + if (key == 'toppings' && currentMaterialType == 'topping') { + let topping_change = value[key][getToppingSlot()]; + console.log('topping applying', topping_change); + currentToppings[getToppingSlot()] = topping_change; + } else { + } + } + } + function handleEvents(event: { event_type: string; payload: any; index: number | undefined }) { // console.log('triggered event', event.event_type, JSON.stringify(event.payload)); if (event.event_type === 'mat_change') { @@ -217,6 +278,10 @@ initialize(); } else if (event.event_type === 'edit_mat_field') { console.log('request edit mat'); + + // fix bug: topping instant change by unlink topping + let _current_toppings = $state.snapshot(currentToppings); + // pack all shown data recipeDataEvent.set({ event_type: 'edit_mat_field_prep', @@ -225,7 +290,7 @@ mat_type: currentMaterialType, mat_name: mat_id, params: currentStringParams, - toppings: currentToppings, + toppings: _current_toppings, current_topping_group: currentToppingInRow, current_topping_list: selectableToppingInGroup, has_mix_ord: hasMixOrder, @@ -244,6 +309,23 @@ // apply now let keys = Object.keys(change_values); console.log('change keys', JSON.stringify(keys)); + let _current_toppings = $state.snapshot(currentToppings); + + // initialize(); + applyChanges(change_values); + + event.payload = { + current_toppings: _current_toppings, + ...event.payload + }; + + // console.log('check on change before trigger', change_values); + + if (onEditValue) onEditValue(JSON.stringify(event.payload)); + } else if (event.event_type === 'revert_change') { + // console.log('revert back ...', row_uid); + + currentToppings = oldToppings; } } @@ -255,11 +337,23 @@ handleEvents(event); } }); + + unsubRecipeTopping = latestRecipeToppingData.subscribe((payload) => { + // console.log('topping data subscribe', payload); + }); }); onDestroy(() => { if (unsubRecipeDataEvent) { + // do last event before destroy + // console.log('trigger last event before destroy', get(latestRecipeToppingData)); + handleEvents(get(recipeDataEvent) ?? { event_type: '', payload: {}, index: -1 }); + unsubRecipeDataEvent(); + // console.log('destroy recipe data event listener'); + } + if (unsubRecipeTopping) { + unsubRecipeTopping(); } }); @@ -275,6 +369,14 @@ {getCurrentSelectedToppingGroup()} , {getCurrentSelectedToppingList()}

+ {#if oldToppingNamesOnly != null} +
+ + {/if} {:else} diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index 06b6457..f5b3986 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -1,5 +1,11 @@ {#if isDesktop.current} - + onCloseDialog()}> e.preventDefault()} >View @@ -126,8 +207,16 @@ sendBrewNow()} + on:saveRecipe={async () => { + save_change = true; + + console.log('save change, check state', callback_revert_value_if_not_save); + + addNotification('INFO:Save recipe'); + }} /> diff --git a/src/lib/core/stores/recipeStore.ts b/src/lib/core/stores/recipeStore.ts index f35ec51..e7d9340 100644 --- a/src/lib/core/stores/recipeStore.ts +++ b/src/lib/core/stores/recipeStore.ts @@ -44,6 +44,7 @@ export const recipeFromMachineQuery = writable({}); export const materialFromMachineQuery = writable({}); export const referenceFromPage = writable(''); +export const currentEditingRecipeProductCode = writable(''); let worker: Worker | null = null; let initialized = false; diff --git a/src/lib/core/stores/websocketStore.ts b/src/lib/core/stores/websocketStore.ts index 3cc983b..49c3efb 100644 --- a/src/lib/core/stores/websocketStore.ts +++ b/src/lib/core/stores/websocketStore.ts @@ -2,7 +2,7 @@ import { browser } from '$app/environment'; import { env } from '$env/dynamic/public'; import { get, writable } from 'svelte/store'; import { handleIncomingMessages } from '../handlers/messageHandler'; -import { queue as msgQueue } from '../handlers/ws_messageSender'; +import { queue as msgQueue, sendMessage } from '../handlers/ws_messageSender'; import { auth } from '../client/firebase'; import { addNotification } from './noti'; @@ -31,6 +31,16 @@ export function connectToWebsocket() { msgQueue.set(queue); } } + + // heartbeat 10s + setInterval(() => { + if (socket) { + sendMessage({ + type: 'heartbeat', + payload: {} + }); + } + }, 10000); }); socket.addEventListener('message', (event) => { diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts index 138e41a..e40c0e0 100644 --- a/src/lib/core/types/outMessage.ts +++ b/src/lib/core/types/outMessage.ts @@ -32,6 +32,14 @@ export type OutMessage = values: any; }; } + | { + type: 'save_recipe'; + payload: { + user: string; + country: string; + values: any; + }; + } | { type: 'heartbeat'; payload: {};