diff --git a/src/lib/assets/modules/sheet_btn.png b/src/lib/assets/modules/sheet_btn.png new file mode 100644 index 0000000..e63d948 Binary files /dev/null and b/src/lib/assets/modules/sheet_btn.png differ diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 8b46cd7..3eba3f1 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -11,7 +11,8 @@ DiamondIcon, BugIcon, CupSodaIcon, - Shield + Shield, + FileSpreadsheet } from '@lucide/svelte/icons'; import TaobinLogo from '$lib/assets/logo.svelte'; import { goto } from '$app/navigation'; @@ -19,6 +20,7 @@ import { sidebarStore } from '$lib/core/stores/sidebar'; import { auth } from '$lib/core/stores/auth'; import { isUserAdmin } from '$lib/core/admin/adminService'; + import { referenceFromPage } from '$lib/core/stores/recipeStore'; let sideBar: HTMLElement | null = $state(null); let isSideBarOpen: boolean = $state(true); @@ -70,6 +72,17 @@ icon: BugIcon } ] + }, + { + title: 'Sheet', + items: [ + { + title: 'Overview', + url: '/departments', + icon: FileSpreadsheet + + } + ] } ] }; @@ -138,7 +151,17 @@ {#snippet child({ props })} - + { + if (nav.title === 'Sheet') { + e.preventDefault(); + referenceFromPage.set('sheet'); + goto(sub.url); + } + }} + > {#if sub.icon} {/if} @@ -162,7 +185,17 @@ {#snippet child({ props })} - + { + if (nav.title === 'Sheet') { + e.preventDefault(); + referenceFromPage.set('sheet'); + goto(sub.url); + } + }} + > {#if sub.icon} {/if} 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)/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 +

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