From e9192c8607b7df9ae00b84e70493f5bbb9282abe Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Tue, 24 Mar 2026 17:52:53 +0700 Subject: [PATCH 1/2] change: remove loading while request recipe progress: WIP editing flow Signed-off-by: pakintada@gmail.com --- src/lib/components/recipe-details/columns.ts | 7 + .../recipelist-value-editor.svelte | 172 +++++++++++++++--- .../recipe-details/recipelist-value.svelte | 13 +- .../components/recipe-details/value_event.ts | 39 +++- .../components/recipe-editor-dialog.svelte | 6 +- .../alert-dialog/alert-dialog-action.svelte | 27 +++ .../alert-dialog/alert-dialog-cancel.svelte | 27 +++ .../alert-dialog/alert-dialog-content.svelte | 32 ++++ .../alert-dialog-description.svelte | 17 ++ .../alert-dialog/alert-dialog-footer.svelte | 23 +++ .../alert-dialog/alert-dialog-header.svelte | 20 ++ .../ui/alert-dialog/alert-dialog-media.svelte | 20 ++ .../alert-dialog/alert-dialog-overlay.svelte | 17 ++ .../alert-dialog/alert-dialog-portal.svelte | 7 + .../ui/alert-dialog/alert-dialog-title.svelte | 17 ++ .../alert-dialog/alert-dialog-trigger.svelte | 7 + .../ui/alert-dialog/alert-dialog.svelte | 7 + src/lib/components/ui/alert-dialog/index.ts | 40 ++++ src/lib/components/ui/button/button.svelte | 28 +-- src/lib/core/handlers/messageHandler.ts | 3 +- src/lib/core/handlers/ws_messageSender.ts | 2 + src/lib/core/stores/websocketStore.ts | 75 ++++---- src/lib/core/types/outMessage.ts | 9 + .../(authed)/recipe/overview/+page.svelte | 4 +- 24 files changed, 538 insertions(+), 81 deletions(-) create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-media.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog.svelte create mode 100644 src/lib/components/ui/alert-dialog/index.ts diff --git a/src/lib/components/recipe-details/columns.ts b/src/lib/components/recipe-details/columns.ts index 54d9fa0..c679d24 100644 --- a/src/lib/components/recipe-details/columns.ts +++ b/src/lib/components/recipe-details/columns.ts @@ -112,7 +112,14 @@ export const columns: ColumnDef[] = [ row_uid: row.original.id, mat_id: row.original.material_id, onEditValue: (changes: any) => { + recipeDataEvent.set({ + event_type: 'edit_change_value_rpl', + payload: changes, + index: row.original.id + }); + // get change parameters + row.toggleSelected(row.original.is_use); }, onDetectMixOrder: () => { // set next diff --git a/src/lib/components/recipe-details/recipelist-value-editor.svelte b/src/lib/components/recipe-details/recipelist-value-editor.svelte index 41754f9..a786a7e 100644 --- a/src/lib/components/recipe-details/recipelist-value-editor.svelte +++ b/src/lib/components/recipe-details/recipelist-value-editor.svelte @@ -20,13 +20,23 @@ import { onMount } from 'svelte'; import { addNotification } from '$lib/core/stores/noti'; import { get } from 'svelte/store'; - import { ValueEvent } from './value_event'; + import { actionReport, ValueEvent } from './value_event'; import ScrollArea from '../ui/scroll-area/scroll-area.svelte'; + import { sendMessage } from '$lib/core/handlers/ws_messageSender'; + import { auth } from '$lib/core/stores/auth'; + import { departmentStore } from '$lib/core/stores/departments'; + import { machineInfoStore } from '$lib/core/stores/machineInfoStore'; + import type { setDefaultAutoSelectFamily } from 'node:net'; let { row_id }: { row_id: number } = $props(); let current_editing_data: any = $state(); - let changed_data: any = $state(); + let changed_data: any = $state({}); + + let sheetOpenState = $state(false); + let currentRef = $state(''); + + let warnUserNotSaveChange = $state(false); // -------------------------------------------------- @@ -76,7 +86,7 @@ // update value, do re-render if (event.payload) { current_editing_data = event.payload; - console.log(`GET requested data: ${JSON.stringify(current_editing_data)}`); + // console.log(`GET requested data: ${JSON.stringify(current_editing_data)}`); // default topping if ( @@ -105,11 +115,15 @@ current_editing_data.water.yield > 0 || current_editing_data.water.cold > 0; toggledOpenFeed = current_editing_data.feed.pattern > 0 || current_editing_data.feed.parameter > 0; + + sheetOpenState = true; } } } function requestDataFromDisplay() { + // pause show until have data + sheetOpenState = false; console.log('sending request edit', row_id); recipeDataEvent.set({ event_type: 'edit_mat_field', @@ -120,13 +134,12 @@ setTimeout(() => { if (current_editing_data === undefined) { addNotification('ERR:Unable to edit'); + sheetOpenState = false; } }, 5000); } function onFieldValueChange(field_name: string[], new_value: any) { - console.log('change on', JSON.stringify(field_name), new_value); - // validate if field value changes let curr_val; for (let field of field_name) { @@ -147,43 +160,133 @@ if (has_changed) { if (value_event_state === ValueEvent.NONE) { value_event_state = ValueEvent.EDITED; - - // save change - let single_key = ''; - for (let field of field_name) { - single_key += field + '_'; - } - single_key = single_key.slice(0, single_key.length - 1); - - console.log('save to key', single_key); - changed_data[single_key] = new_value; } + + // save change + let single_key = ''; + for (let field of field_name) { + single_key += field + '_'; + } + single_key = single_key.slice(0, single_key.length - 1); + changed_data[single_key] = new_value; } else { // revert value in key } } function saveEditingValue() { - console.log('saving value ...'); + console.log('saving value ...', value_event_state); if (value_event_state === ValueEvent.EDITED) { + let payload = { + source: current_editing_data, + change: changed_data + }; recipeDataEvent.set({ event_type: 'save_mat_field', - payload: current_editing_data, + payload, index: row_id }); + + value_event_state = ValueEvent.SAVED; + + actionReport( + 'change_mat_field', + { + index: row_id, + ...payload + }, + currentRef + ); + + sheetOpenState = false; + } else { + // set noti + addNotification(`WARN:Cannot save, please retry ...`); } } function handleToppingGroupChange(v: any) { - console.log('change topping group', JSON.stringify(v)); + console.log('change topping group'); 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']) { + let toppings_length = current_editing_data['toppings'].length; + if (!changed_data['toppings']) { + changed_data['toppings'] = new Array(toppings_length); + + // console.log('filling change topping', JSON.stringify(changed_data)); + } + } + + 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)); + if (current_selection['groupID'] !== undefined || current_selection['groupID'] !== null) { + try { + changed_data['toppings'][idx] = current_selection; + changed_data['toppings'][idx]['groupID'] = `${selected_category_id}`; + changed_data['toppings'][idx]['defaultIDSelect'] = selected_topping_list_id; + changed_data['toppings'][idx]['ListGroupID'][0] = selected_topping_list_id; + } catch (topping_group_exception) { + console.error('Error on topping group select', topping_group_exception); + } + } } function handleToppingListChange(v: any) { - console.log('Topping list chose: ', JSON.stringify(v)); + console.log('Topping list chose '); selected_topping_list_id = v.id; + + // 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']) { + let toppings_length = current_editing_data['toppings'].length; + // case: topping not init + if (!changed_data['toppings']) { + changed_data['toppings'] = new Array(toppings_length); + } + } + + let current_selection = current_editing_data['toppings'][idx]; + + if (current_selection['groupID'] !== undefined || current_selection['groupID'] !== null) { + try { + changed_data['toppings'][idx] = current_selection; + // changed_data['toppings'][idx]['groupID'] = `${selected_category_id}`; + changed_data['toppings'][idx]['defaultIDSelect'] = selected_topping_list_id; + changed_data['toppings'][idx]['ListGroupID'][0] = selected_topping_list_id; + } catch (topping_list_exception) { + console.error('Error on topping group select', topping_list_exception); + } + } + } + + function beforeClosing() { + if (value_event_state === ValueEvent.EDITED) { + if (warnUserNotSaveChange) { + // Discard all + + warnUserNotSaveChange = false; + // sheetOpenState = false; + return true; + } else { + // show no save warning + warnUserNotSaveChange = true; + return false; + } + } else { + sheetOpenState = false; + return true; + } } // recipelist-value-editor.svelte?t=1773541064272:57 GET requested data: {"mat_id":1002,"mat_type":"bean","mat_name":"medium-roasts (1002)","params":{"esp-v2-press-value":"24"},"toppings":[{"ListGroupID":[1,0,0,0],"defaultIDSelect":1,"groupID":"1","isUse":true},{"ListGroupID":[6,0,0,0],"defaultIDSelect":31,"groupID":"6","isUse":true},{"ListGroupID":[7,0,0,0],"defaultIDSelect":33,"groupID":"7","isUse":true},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[530,0,0,0],"defaultIDSelect":532,"groupID":"530","isUse":true},{"ListGroupID":[500,0,0,0],"defaultIDSelect":502,"groupID":"500","isUse":true}],"current_topping_list":[],"has_mix_ord":false,"feed":{"pattern":0,"parameter":0},"water":{"cold":0,"yield":30},"powder":{"gram":7,"time":9},"syrup":{"gram":0,"time":0},"stir_time":120} @@ -192,6 +295,9 @@ // recipelist-value-editor.svelte?t=1773541064272:57 GET requested data: {"mat_id":9501,"mat_type":"cup","mat_name":"CUP paper (9501)","params":{},"toppings":[{"ListGroupID":[1,0,0,0],"defaultIDSelect":1,"groupID":"1","isUse":true},{"ListGroupID":[6,0,0,0],"defaultIDSelect":31,"groupID":"6","isUse":true},{"ListGroupID":[7,0,0,0],"defaultIDSelect":33,"groupID":"7","isUse":true},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[0,0,0,0],"defaultIDSelect":0,"groupID":"0","isUse":false},{"ListGroupID":[530,0,0,0],"defaultIDSelect":532,"groupID":"530","isUse":true},{"ListGroupID":[500,0,0,0],"defaultIDSelect":502,"groupID":"500","isUse":true}],"current_topping_list":[],"has_mix_ord":false,"feed":{"pattern":0,"parameter":0},"water":{"cold":0,"yield":0},"powder":{"gram":0,"time":0},"syrup":{"gram":0,"time":0},"stir_time":0} onMount(() => { + sheetOpenState = false; + console.log('sheet open? ', sheetOpenState); + let refFrom = get(referenceFromPage); categories = get( refFrom === 'overview' ? toppingGroupFromServerQuery : toppingGroupFromMachineQuery @@ -200,6 +306,9 @@ refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery ); + // save ref + currentRef = refFrom; + return recipeDataEvent.subscribe((event) => { if (event !== null && event.index !== undefined && event.index === row_id) { handleEvents(event); @@ -208,7 +317,16 @@ }); - + { + if (!next) { + beforeClosing(); + } else { + sheetOpenState = true; + } + }} +> - + diff --git a/src/lib/components/recipe-details/recipelist-value.svelte b/src/lib/components/recipe-details/recipelist-value.svelte index 3557b18..6df6aca 100644 --- a/src/lib/components/recipe-details/recipelist-value.svelte +++ b/src/lib/components/recipe-details/recipelist-value.svelte @@ -170,7 +170,7 @@ if (currentToppings[getToppingSlot()]['ListGroupID'][0] === 0) { return 'Empty'; } - if (!current_selected) { + if (current_selected === undefined || current_selected === null) { return 'Unknown'; } @@ -237,11 +237,14 @@ }, index: row_uid }); - } - } + } else if (event.event_type === 'save_mat_field') { + console.log('receive saving process mat, do refresh...'); + let change_values = event.payload.change; - function triggerEditChange(value: any) { - console.log('triggered on change editing', JSON.stringify(value)); + // apply now + let keys = Object.keys(change_values); + console.log('change keys', JSON.stringify(keys)); + } } onMount(() => { diff --git a/src/lib/components/recipe-details/value_event.ts b/src/lib/components/recipe-details/value_event.ts index 831edea..81653d5 100644 --- a/src/lib/components/recipe-details/value_event.ts +++ b/src/lib/components/recipe-details/value_event.ts @@ -1,6 +1,41 @@ +import { get } from 'svelte/store'; +import { auth } from '$lib/core/stores/auth'; +import { departmentStore } from '$lib/core/stores/departments'; +import { machineInfoStore } from '$lib/core/stores/machineInfoStore'; +import { addNotification } from '$lib/core/stores/noti'; +import { sendMessage } from '$lib/core/handlers/ws_messageSender'; + enum ValueEvent { NONE, - EDITED + EDITED, + SAVED } -export { ValueEvent }; +function actionReport(action_name: string, values: any, currentRef: string) { + let country = get(departmentStore) ?? 'unknown dep'; + + if (currentRef === 'brew') { + // from machine + let machine_info = get(machineInfoStore); + if (machine_info) { + let bid = machine_info.boxId ?? 'BOX_UNK'; + let cc = machine_info.country; + + country = `${bid}-${cc}`; + } else { + addNotification('WARN:Saving as unknown department, please check setting on machine'); + } + } + + sendMessage({ + type: 'log_report', + payload: { + user: get(auth)?.email ?? 'unknown', + action: action_name, + country, + values + } + }); +} + +export { ValueEvent, actionReport }; diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index ff5b982..e02f6b5 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -48,7 +48,7 @@ // currentData.recipes = newChange.value; // // TODO: build into structure, flatten fields into 1 layer, strip off `id` (row id) - console.log(newChange); + console.log('pending change recipe list', newChange); } // await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData)); @@ -103,4 +103,6 @@ -{:else}{/if} +{:else} + +{/if} diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..7e63004 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..a4ce03c --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..251e852 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,32 @@ + + + + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..5024f3f --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..f4214db --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..d6df4d3 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte new file mode 100644 index 0000000..3f634f3 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..9ffcc85 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..f0a19a8 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..652da33 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..b22d1d5 --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/src/lib/components/ui/alert-dialog/alert-dialog.svelte new file mode 100644 index 0000000..7ea78bb --- /dev/null +++ b/src/lib/components/ui/alert-dialog/alert-dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/alert-dialog/index.ts b/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..ca81c2a --- /dev/null +++ b/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,40 @@ +import Root from "./alert-dialog.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Trigger from "./alert-dialog-trigger.svelte"; +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; +import Media from "./alert-dialog-media.svelte"; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Media, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, + Media as AlertDialogMedia, +}; diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte index a8296ae..b6f80ef 100644 --- a/src/lib/components/ui/button/button.svelte +++ b/src/lib/components/ui/button/button.svelte @@ -4,25 +4,25 @@ import { type VariantProps, tv } from "tailwind-variants"; export const buttonVariants = tv({ - base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs", - destructive: - "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs", - outline: - "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs", - secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground", + destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", + default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + icon: "size-8", + "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", }, }, defaultVariants: { diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index db1846a..b06fd7b 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -35,7 +35,7 @@ const handlers: Record void> = { if (stream_id) { addNotification('INFO:Start streaming data'); - recipeLoading.set(true); + // recipeLoading.set(true); recipeStreamMeta.set({ id: stream_id, total_size: total_size, @@ -82,6 +82,7 @@ const handlers: Record void> = { if (percent == 100) { addNotification(`INFO:Current progress ${percent}%`); } + buildOverviewFromServer(); } } }, diff --git a/src/lib/core/handlers/ws_messageSender.ts b/src/lib/core/handlers/ws_messageSender.ts index ce86af6..0ab7c8a 100644 --- a/src/lib/core/handlers/ws_messageSender.ts +++ b/src/lib/core/handlers/ws_messageSender.ts @@ -9,6 +9,8 @@ export function sendMessage(msg: OutMessage): boolean { const socket = get(socketStore); const data = JSON.stringify(msg); + // console.log('try sending ', data); + if (!socket || socket.readyState !== WebSocket.OPEN) { console.warn('WebSocket not connected, put to queue'); diff --git a/src/lib/core/stores/websocketStore.ts b/src/lib/core/stores/websocketStore.ts index 2fa53bf..3cc983b 100644 --- a/src/lib/core/stores/websocketStore.ts +++ b/src/lib/core/stores/websocketStore.ts @@ -7,49 +7,56 @@ import { auth } from '../client/firebase'; import { addNotification } from './noti'; let socket: WebSocket | null = null; +const ENABLE_WS_DEBUG: boolean = false; export const socketStore = writable(null); export function connectToWebsocket() { if (browser) { - console.log('connecting to ', env.PUBLIC_WSS); - socket = new WebSocket(`${env.PUBLIC_WSS}`); + // console.log('connecting to ', env.PUBLIC_WSS); + try { + socket = new WebSocket(`${env.PUBLIC_WSS}`); - socket.addEventListener('open', () => { - socketStore.set(socket); - addNotification('INFO:Connected!'); + socket.addEventListener('open', () => { + socketStore.set(socket); + addNotification('INFO:Connected!'); - // recover messages on connect, flushing - while (get(msgQueue).length) { - let queue = get(msgQueue); - let current = queue.shift(); - if (current && socket) { - socket.send(current); - // set next - msgQueue.set(queue); + // recover messages on connect, flushing + while (get(msgQueue).length) { + let queue = get(msgQueue); + let current = queue.shift(); + if (current && socket) { + socket.send(current); + // set next + msgQueue.set(queue); + } } + }); + + socket.addEventListener('message', (event) => { + handleIncomingMessages(event.data); + }); + + socket.addEventListener('close', () => { + socketStore.set(null); + socket = null; + + if (auth.currentUser) { + // console.log('try reconnect websocket ...'); + // retry again + setTimeout(() => connectToWebsocket(), 5000); + } + }); + + socket.addEventListener('error', (e) => { + // console.log('WebSocket error: ', e); + socketStore.set(null); + }); + } catch (socket_error: any) { + if (ENABLE_WS_DEBUG) { + console.error('WS_ERR', socket_error); } - }); - - socket.addEventListener('message', (event) => { - handleIncomingMessages(event.data); - }); - - socket.addEventListener('close', () => { - socketStore.set(null); - socket = null; - - if (auth.currentUser) { - console.log('try reconnect websocket ...'); - // retry again - setTimeout(() => connectToWebsocket(), 5000); - } - }); - - socket.addEventListener('error', (e) => { - console.log('WebSocket error: ', e); - socketStore.set(null); - }); + } return () => { if (socket?.readyState === WebSocket.OPEN) { diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts index d41e0c0..e11d183 100644 --- a/src/lib/core/types/outMessage.ts +++ b/src/lib/core/types/outMessage.ts @@ -22,4 +22,13 @@ export type OutMessage = permissions: string; }; }; + } + | { + type: 'log_report'; + payload: { + user: string; + action: string; + country: string; + values: any; + }; }; diff --git a/src/routes/(authed)/recipe/overview/+page.svelte b/src/routes/(authed)/recipe/overview/+page.svelte index 5289ea5..e3f0814 100644 --- a/src/routes/(authed)/recipe/overview/+page.svelte +++ b/src/routes/(authed)/recipe/overview/+page.svelte @@ -50,11 +50,11 @@ // schedule check if recipe is empty if (data.recipes.length == 0) { console.log('loading recipe ....'); - recipeLoading.set(true); + // recipeLoading.set(true); // empty await getRecipes(); - setTimeout(() => recipeLoading.set(false), 3000); + // setTimeout(() => recipeLoading.set(false), 3000); } }, 30000); return () => { From b1dd9de062be58fae7157d0d1d9eceb0f709f5e4 Mon Sep 17 00:00:00 2001 From: Ittipat Lusuk Date: Thu, 26 Mar 2026 14:23:12 +0700 Subject: [PATCH 2/2] update: sheet routes --- src/lib/assets/modules/sheet_btn.png | Bin 0 -> 5129 bytes src/lib/components/app-sidebar.svelte | 27 +++++++- src/routes/(authed)/departments/+page.svelte | 37 +++++++---- src/routes/(authed)/entry/+page.svelte | 33 ++++++++++ .../(authed)/sheet/overview/+page.svelte | 62 ++++++++++++++++++ 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 src/lib/assets/modules/sheet_btn.png create mode 100644 src/routes/(authed)/sheet/overview/+page.svelte diff --git a/src/lib/assets/modules/sheet_btn.png b/src/lib/assets/modules/sheet_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..e63d948b9c2757f5fe971f3ee1e7f123e915805e GIT binary patch literal 5129 zcmcgwXE+;*- zMuUhL3G(m%ez_m-bD#U%|A%wV^PcnNe0bmYdCp0)G&f>p0y0rhP_UX98{Gd}zy6Ii z2D-ob9f|npZ($5Hb_k)MVCMXHP*P-n;`!U847qQlM^Q7zxBVB;cvn9tp4 z|6)L>p?#=zfJZ3AE!dsnj<=_uTV7qp6AB8>mnH_fHsO@mCFVdrLKOYM%3iLEJzM0L ztPHpPBYmWjOkiY9=+SX$7GDjWdMw@uSAPE` z{skjLB6Xc?CBG=`cbOZ{XPQa=G%}Pd~XrI z$)^mMk|JT9llRe+leoHFt@zE+Y};^5Q>L#7+Fk?RSc(fPtq682|5SJwEB&;RY&=WR z>|XKMKM<81pRX?R=(8;aN;H=Z4u$AVQa-9#&1Rt3Y9{Dv>u0eJ*O3)KyE}j{5&B zI=Nn)C65nzeE(?GcvNJe_1XVa_?GFfhL~Vh=k~*q^fbuwqiQ!a4IIhx2D33qo1bL_ z64x$CBhL-GYt1QF4P?4F5Ck_N2szinnl|{p<83@ucF1Vh)Bg1xt!urMS01tb03yD} z?6vni;LMB&TRIbuG_SQ<3M54kz%_0kLvd3+!=PIGlU>T9ml}WCrkS>jL|rXeB|k9r z4O=0TE6tR~anKJLJSt_SuJ&Wj0H9AUrNL{2VYo=;dA(u(hgWVnr3gD&D=VwC*d1~6 zJKx0bG72GDH&p|OrWXwetC{nBC3L8mS^}9eQ38F2F=Za^W>M40UdyJUq+}NoqM)Lo z(TRMX zmPRB>D41EhE}kEor5k7o?NN1W3;KbJk&!DUn;F8Phfl>FGCmM!U%IN$KZ!759L;nq0=nouS}m61Bc(#hkKLCh-!2NQ-7dp(GN99h{|VGk+3k0 zP?R*{h9Eov6uE8rr1(;8XDzT*Ke!wxkDYIT6-iA|t`#m`Q*xZ04sj$$%l7ur-b@p7 zEIrAQ9_w25@Wj7g*^c5ee7{%XgkTR13!7~F&5|YCx*iOAx`0L-^(7~%~m9I6gxKDi4-tEZhtE=#5kJL{{G<Pf8qkTujse7)c9olVt^xt%2}KH%MuvDm5W5%(JQbaueG?FRQnAF!#BO~ zY+Yr{qV(>!HZU0Lm>6x)MMM5{4vCGuL{En%1T$8co%3L7eyS;{EMaw5i%`K7m38+N zFUer6ep{UuiM)8SbMsUwzw~a}@H^LEtMnb+%#>GJwYSnoedkbcrvFk!TwQcs+)DX` z4WHz#BU*7VEMO=QQLHN(-ggmVYDa<)c-EdUV~66(4;bb#d z0P=YGWybP$Io4w4xF*i2yFkSRnLAF`*uW9fhGPB zkEae88yPyI=)1At`EhESCqHfQ=tTU8yyOXAS5(C%g>dM%yfRF;sN&p?-J$~LkrY9= zv&+28dDQfac>0Q#mVHyoUqd6~BgK8AGDI!6D=-UWgPU3USNf9Hl8>!TO+ z1jC3wUq0P#D0$i*?QC^*TZ=&jvN_iFk%^g^u#Z_6$;fbbx7j+=vR308>ynWERC+w= zc?DkH?(g5|Jq0>hCil~$>4fV!TS1*CX36I2g?VdZ|aR9Q@LWcc|Zq82+8j@ z{yfWu^c~=%Ib*N{0$nv+6eKO4-?a@{G`(3vW<~}SNgo#5@4*-AH$5Ed!>?oKJ7C#P z(MGwsxe=<*0{fHdIa*4?1rK5klYo;sDiDCo15!a})%URct>e)mkd^Vauw8>^<}Ner z4o>uLc*$@;qtq}p_j@*DctJ1KWE=;@|Cir?Z4Y?h>+4gLkPtPqJgMuP>r7<1FFG(l z)PUTqlp>6$f+CMg&s`qg*R%bAXDE^cQ{8uJiO>6aDWasre~gTpzIJ(+{1{Pld1EQ$ zM5Us$>NZvji{%gPdsk^)Xt);6d(rE2&6gahfmHO35 zcknnd;D(TFA%H=p^1QdltYQNpY5Twt0e+{Cm>+o6Q*BMK*z4T&Um%RAQ!#Mt#0KW~ zfj{ghYPREa`BP^|m+8uyUlcal>9~OYMQe$Sx_Z3qHI(P-zfTZ=@H&eZ80)H_X`dW% zo5-0&ZpfnIWWk-*9oJ}ESc-zcb*E@7w>NIxFkNz)ZZ2XE<5HRp)f`sN#>xXAe%KT% zzvL6Na@dzgnp2nodJasil{4~tOa36t$(8&DsG#}W!&MbF>d8{m_3R(_>|gT1waGQ^3_m%EP&6Wv(%ey-tll`4>j>r)~IWh}sX- zIor+QjVVTJi{U80g+u$*<|TS|=T#6XS>yP*AnIcOQUf&vD4w9}ugzF3{v}4B3pSc& zLj_gZ%}#jHzUu1{exeDyI+L$f;=>|v70$%<3%Cc4fp$negwS>+G#64P93Q7#X$gD1 z^Q5Vzh29_Bm~rV4c8B`LFWk>G(MHAXhfW9I#E_?3rzbn3IP=ro>gHyE0v}*+dT-swYKfnk8KRB>$i5a2K(R$DK_+$?(k$ha)_fM(1Sg@r z`*9XyrCc}dCNB_ls_1UjQ6B%ffGFRFuBxti)VAfx_msw6W#4cK89wmb@||iU!1f4f zpMMs8bS)6j#yyv^9C(0_h=N27H+R1J!0dyW2w6ohFRT>EebBl{#@NVgEp~NAw92ii z2-OpCGQ{Yy70piEi$>B6@lTpOBSGRCcM1W)v{J=n4yUv2KI_G9zW11l4^(`S0|NM$ z+9JA=AvRT$ieBAD=R`Xfv$hoU22ArP7Sj&24IBx@uC59lOwLt^LY>0KOMY0M6Op6p z%Re|~by2=Osfu$!(+%dc6kuDyw`!D!yNWo!=%yDC9Rn3R5dE8;7w{($ZLo$IXgTP+ z9?Bl)VjI{7Pj|Om&CNQ}w)NXK&Ea264|(0!^QtFuX8(_AwPdSh1nq3Z!=K1k+n+OC z)$k3jtUQe}W%Iw3f0U-2Gyzf=O85JWY-DOS?)@YQIn-TCX6ClTEwZiZeN#%9 zN!o~ej#4M$_4*4oQ}rEFdTABBu-xDS$@Hc$y|lg%_283R@H_cKYZOW}xA~E7pqbn4 zvve+^*Y!!J}UiGP! zFkj#K6kh~DguZ0Q?MmgCDa(+8`oNiTjHNpM@Py3kBi zEZGeexlo6jqn~$%hNV*`Go8yJH$TI~q9r`_Ada5}da=p<^8byWgXYdO~C3UU`X;P6!6>l>t zPCmSO=*7h?5kMXYh)SDTg9KDYy&dI?mK}R`ycNB)oLc6 z3I+=3I`~A@f^PeATJ7i_&wqyF^k;a%O&4<_ZDtc4wc0X81w_}gQTc1k30})>A>wF#>A=~E=s^X6b?^AF& z{i?ly;kgPpo=HO+Jp}9zo?J}+x)kQ*i}rR}garlqHAi13IYy*|PNTLtH!DO2Ayx$Obgma05|4Ltt6OKpQpSM6=~o|cfF6XrlC zQeFBA)L|Ul;mV;u0BbT=qJ@sV%z5d?y(9L3-6aF0dN@_HFd<4Dm6Ma>pSw~yxw@IL z$6J9J3NKjo&3B!%M8#uXs&}XlHOhFRf>6R~ep^|D!lAI^kWxV%-VY|ekKLEJhw9c; zyw`n_CV5;GG>$QLnn$0*F}+^QC#eJ0A>_P!r#n{vt2@$k&u5v@agZgj*)snBeuS>d zOJJ)!J$BB13~qn&qHycBn(+Rj#@O60V%8@QL##-5Rbz@dPX%ZbrE6}guk7-$FjbiH z+$pX=z#%Ms#;JZ*Xs5^|vIU(FM`Cu*u`Q-f+1cc0g5Q8I>BOVAfNb-^_@@+4HEXT4 zE2O2Jmx{`&=x(RCWsC)PQ|BcI&LRJsGD)UBPlcYT$`$VYyt{6DU(N8f%c6-%6w^cN zd^+wX#-}p2&24W(R8x~^l=d&Va@BEKSz?({JEzAZ`zw-;2pOUUWSV^GBhC*xSZT+WwDXtQ-032(y%x;- zA&M$0x1{7@&mWrO8taVRly)|smXj`9w@3%fifr}jKa&r)-1$x4zOuA% z03;AXIWzZL|Iw(9{(1!LA(jQ`nD&9*@ykB zBVxar#D_>6GHn=Yeurh-yTo1$!34F<;`NbzckkU}R(5f2Ygh4(x<^=-vuVK~N`iih zooGC#9E>k~?VFb1Ct|}$E)dFs%oUShoj(^A7A*8fMNx?Bm)rj+mPS<)54lX@yPl>I zyqjJ~h3)fDK4k^Gp7%(*K=aJkaziXCmk7Pg;?K(c8QX{)WHun*N_IB)+1B4r9fgUZ Lxj~KIqqu(oOlyMf literal 0 HcmV?d00001 diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index e4d0917..3f2a832 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -10,13 +10,15 @@ CherryIcon, DiamondIcon, BugIcon, - CupSodaIcon + CupSodaIcon, + FileSpreadsheet } from '@lucide/svelte/icons'; import TaobinLogo from '$lib/assets/logo.svelte'; import { goto } from '$app/navigation'; import Button from '$lib/components/ui/button/button.svelte'; import { get } from 'svelte/store'; import { sidebarStore } from '$lib/core/stores/sidebar'; + import { referenceFromPage } from '$lib/core/stores/recipeStore'; let sideBar: HTMLElement | null = $state(null); let isSideBarOpen: boolean = $state(true); @@ -67,6 +69,17 @@ icon: BugIcon } ] + }, + { + title: 'Sheet', + items: [ + { + title: 'Overview', + url: '/departments', + icon: FileSpreadsheet + + } + ] } // more to add here ] @@ -109,7 +122,17 @@ {#snippet child({ props })} - + { + if (nav.title === 'Sheet') { + e.preventDefault(); + referenceFromPage.set('sheet'); + goto(sub.url); + } + }} + > {#if sub.icon} {/if} diff --git a/src/routes/(authed)/departments/+page.svelte b/src/routes/(authed)/departments/+page.svelte index aed44df..4c2a9db 100644 --- a/src/routes/(authed)/departments/+page.svelte +++ b/src/routes/(authed)/departments/+page.svelte @@ -1,6 +1,7 @@ + +
+ +
+
+
+

Layout overview [ {refDepartment} ]

+

+ Display menus from the spreadsheet current selected country +

+
+
+ +
+
+ + + + + + + +
+