feat: save recipe in progress

Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
pakintada@gmail.com 2026-04-20 10:37:02 +07:00
parent 916e056389
commit 230d4abe0c
11 changed files with 310 additions and 41 deletions

20
ISSUES.txt Normal file
View file

@ -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

View file

@ -47,9 +47,12 @@
authStore.set(null); authStore.set(null);
let socket = get(socketStore); let socket = get(socketStore);
if (socket) {
socket.close(1000, 'logout'); try {
} if (socket) {
socket.close(1000, 'logout');
}
} catch (e) {}
socketStore.set(null); socketStore.set(null);
if (browser && 'cookieStore' in window) await cookieStore.delete('logged_in'); if (browser && 'cookieStore' in window) await cookieStore.delete('logged_in');

View file

@ -112,9 +112,11 @@ export const columns: ColumnDef<RecipelistMaterial>[] = [
row_uid: row.original.id, row_uid: row.original.id,
mat_id: row.original.material_id, mat_id: row.original.material_id,
onEditValue: (changes: any) => { onEditValue: (changes: any) => {
// console.log('triggered on edit value', changes);
recipeDataEvent.set({ recipeDataEvent.set({
event_type: 'edit_change_value_rpl', event_type: 'edit_change_value_rpl',
payload: changes, payload: JSON.parse(changes),
index: row.original.id index: row.original.id
}); });

View file

@ -11,6 +11,7 @@
import { columns, type RecipelistMaterial } from './columns'; import { columns, type RecipelistMaterial } from './columns';
import { get, readable, writable } from 'svelte/store'; import { get, readable, writable } from 'svelte/store';
import { import {
currentEditingRecipeProductCode,
latestRecipeToppingData, latestRecipeToppingData,
materialFromMachineQuery, materialFromMachineQuery,
materialFromServerQuery materialFromServerQuery
@ -29,8 +30,9 @@
let { let {
recipeData, recipeData,
onPendingChange, onPendingChange,
productCode,
refPage refPage
}: { recipeData: any; onPendingChange: any; refPage: string } = $props(); }: { recipeData: any; onPendingChange: any; productCode: string; refPage: string } = $props();
let menuName: string = $state(''); let menuName: string = $state('');
@ -126,7 +128,9 @@
} }
} }
async function saveRecipe() {} async function saveRecipe() {
recipeDetailDispatch('saveRecipe');
}
async function sendTriggerBrewNow() { async function sendTriggerBrewNow() {
// check queue ready // check queue ready
@ -155,8 +159,8 @@
} }
} }
async function checkChanges(original: any) { async function checkChanges(productCode: string, original: any) {
console.log('old', original, 'updated', recipeListMatState); // console.log('old', original, 'updated', recipeListMatState);
if (recipeListOriginal.length == 0) { if (recipeListOriginal.length == 0) {
recipeListOriginal = original; recipeListOriginal = original;
} }
@ -164,6 +168,7 @@
if (original !== recipeListMatState) { if (original !== recipeListMatState) {
await onPendingChange({ await onPendingChange({
target: 'recipeList', target: 'recipeList',
ref_pd: productCode,
value: original value: original
}); });
} }
@ -187,6 +192,8 @@
latestRecipeToppingData.set(toppingSlotState); latestRecipeToppingData.set(toppingSlotState);
currentEditingRecipeProductCode.set(productCode);
// save old value\ // save old value\
} }
}); });
@ -237,7 +244,12 @@
</Tabs.Content> </Tabs.Content>
<Tabs.Content value="details"> <Tabs.Content value="details">
<RecipelistTable data={recipeListMatState} {columns} onStateChange={checkChanges} /> <RecipelistTable
data={recipeListMatState}
{columns}
onStateChange={checkChanges}
{productCode}
/>
</Tabs.Content> </Tabs.Content>
</Tabs.Root> </Tabs.Root>
</div> </div>

View file

@ -42,11 +42,13 @@
let { let {
data, data,
columns, columns,
onStateChange onStateChange,
productCode
}: { }: {
data: RecipelistMaterial[]; data: RecipelistMaterial[];
columns: ColumnDef<any, any>[]; columns: ColumnDef<any, any>[];
onStateChange: any; onStateChange: any;
productCode: string;
} = $props(); } = $props();
let sorting = $state<SortingState>([]); let sorting = $state<SortingState>([]);
@ -75,7 +77,10 @@
getFacetedRowModel: getFacetedRowModel(), getFacetedRowModel: getFacetedRowModel(),
onStateChange: async (updater) => { onStateChange: async (updater) => {
console.log('table state change', data); 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) => { onSortingChange: async (updater) => {
console.log('triggering sorting'); console.log('triggering sorting');
@ -84,7 +89,10 @@
} else { } else {
sorting = updater; sorting = updater;
} }
await onStateChange(table.getRowModel().rows.map((x) => x.original)); await onStateChange(
productCode,
table.getRowModel().rows.map((x) => x.original)
);
}, },
onRowSelectionChange: async (updater) => { onRowSelectionChange: async (updater) => {
// table.getRowModel().rows.find((x) => x.original.id == ) // table.getRowModel().rows.find((x) => x.original.id == )
@ -92,11 +100,14 @@
rowSelection = updater(rowSelection); rowSelection = updater(rowSelection);
let rows = table.getRowModel().rows; let rows = table.getRowModel().rows;
console.log('state size', data, rows); // console.log('state size', data, rows);
} else { } else {
rowSelection = updater; rowSelection = updater;
} }
await onStateChange(table.getRowModel().rows.map((x) => x.original)); await onStateChange(
productCode,
table.getRowModel().rows.map((x) => x.original)
);
} }
}); });
</script> </script>

View file

@ -176,7 +176,7 @@
function saveEditingValue() { function saveEditingValue() {
console.log('saving value ...', value_event_state); 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 = { let payload = {
source: current_editing_data, source: current_editing_data,
change: changed_data change: changed_data
@ -209,28 +209,36 @@
function handleToppingGroupChange(v: any) { function handleToppingGroupChange(v: any) {
console.log('change topping group'); 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; selected_category_id = v.groupID;
// get default // get default
selected_topping_list_id = v.idDefault ?? 0; selected_topping_list_id = v.idDefault ?? 0;
// set to edit state even if selected same group again // set to edit state even if selected same group again
value_event_state = ValueEvent.EDITED; 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; let toppings_length = current_editing_data['toppings'].length;
if (changed_data['toppings'] == undefined || changed_data['toppings'] == null) { if (changed_data['toppings'] == undefined || changed_data['toppings'] == null) {
changed_data['toppings'] = new Array<any>(toppings_length); changed_data['toppings'] = new Array<any>(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']); let idx = getToppingSlotIndex(current_editing_data['mat_id']);
// get old state topping idx // get old state topping idx
let current_selection = current_editing_data['toppings'][idx]; let current_selection = current_editing_data['toppings'][idx];
// let default_from_recipe = current_editing_data['toppings'][idx]['ListGroupID'][0]; // 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) { if (current_selection['groupID'] !== undefined || current_selection['groupID'] !== null) {
try { try {
changed_data['toppings'][idx] = current_selection; changed_data['toppings'][idx] = current_selection;
@ -250,7 +258,10 @@
// set to edit state even if selected same group again // set to edit state even if selected same group again
value_event_state = ValueEvent.EDITED; value_event_state = ValueEvent.EDITED;
let idx = getToppingSlotIndex(current_editing_data['mat_id']); 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; let toppings_length = current_editing_data['toppings'].length;
// case: topping not init // case: topping not init
if (changed_data['toppings'] == null || changed_data['toppings'] == undefined) { if (changed_data['toppings'] == null || changed_data['toppings'] == undefined) {
@ -590,12 +601,12 @@
</ScrollArea> </ScrollArea>
<!-- final --> <!-- final -->
<!-- <Field.Field orientation="horizontal"> <Field.Field orientation="horizontal">
<Button type="button" onclick={() => saveEditingValue()}>Save</Button> <Button type="button" onclick={() => saveEditingValue()}>Apply</Button>
<Button variant="outline" type="button" onclick={() => (sheetOpenState = false)} <!-- <Button variant="outline" type="button" onclick={() => (sheetOpenState = false)}
>{warnUserNotSaveChange ? 'Discard Changes' : 'Cancel'}</Button >{warnUserNotSaveChange ? 'Discard Changes' : 'Cancel'}</Button
> > -->
</Field.Field> --> </Field.Field>
</Field.Group> </Field.Group>
</form> </form>
</div> </div>

View file

@ -10,7 +10,7 @@
import Input from '../ui/input/input.svelte'; import Input from '../ui/input/input.svelte';
import Separator from '$lib/components/ui/separator/separator.svelte'; import Separator from '$lib/components/ui/separator/separator.svelte';
import Button from '../ui/button/button.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 * as Tooltip from '../ui/tooltip/index';
import { import {
latestRecipeToppingData, latestRecipeToppingData,
@ -22,6 +22,9 @@
referenceFromPage referenceFromPage
} from '$lib/core/stores/recipeStore'; } from '$lib/core/stores/recipeStore';
import { get } from 'svelte/store'; 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 { let {
row_uid, row_uid,
@ -51,11 +54,16 @@
// toppings // toppings
let currentToppings: any = $state([]); let currentToppings: any = $state([]);
let oldToppings: any = $state(null);
// current topping of this row // current topping of this row
let currentToppingInRow: any = $state(); let currentToppingInRow: any = $state();
let selectableToppingInGroup: any[] = $state([]); let selectableToppingInGroup: any[] = $state([]);
let currentToppingNamesOnly = $state('');
let oldToppingNamesOnly = $state(null);
let unsubRecipeDataEvent: any; let unsubRecipeDataEvent: any;
let unsubRecipeTopping: any;
function isToppingId(mat_id: string): boolean { function isToppingId(mat_id: string): boolean {
let mat_num = extractMaterialIdFromDisplay(mat_id); let mat_num = extractMaterialIdFromDisplay(mat_id);
@ -118,21 +126,54 @@
refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery
); );
// console.log(JSON.stringify(groupQuery[0])); console.log('old topping', oldToppings[getToppingSlot()]);
// console.log(JSON.stringify(listQuery[0]));
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( let groupData = groupQuery.find(
(x: any) => x.groupID.toString() === current_row_topping['groupID'] (x: any) => x.groupID.toString() === current_row_topping['groupID']
); );
let listData = listQuery.filter( let listData = listQuery.filter(
(x: any) => (x: any) =>
groupData && groupData != undefined &&
Object.keys(groupData).includes('idInGroup') && Object.keys(groupData).includes('idInGroup') &&
groupData['idInGroup'].split(',').includes(x.id.toString()) groupData['idInGroup'].split(',').includes(x.id.toString())
); );
console.log('topping data', JSON.stringify(groupData), 'list', listData.length); console.log('topping data', JSON.stringify(groupData), 'list', listData.length);
currentToppingNamesOnly = groupData ? (groupData['otherName'] ?? '-') : '-';
// NOTE: send topping data to value editor
currentToppingInRow = currentToppingInRow =
current_row_topping['groupID'] === 0 || current_row_topping['groupID'] === '0' current_row_topping['groupID'] === 0 || current_row_topping['groupID'] === '0'
? { ? {
@ -178,13 +219,20 @@
} }
function initialize() { function initialize() {
currentToppings = get(latestRecipeToppingData);
hasMixOrder = mix_order == 1; hasMixOrder = mix_order == 1;
extractStringParam(); extractStringParam();
if (isToppingId(mat_id) && onDetectToppingSlot) { 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'; currentMaterialType = 'topping';
getToppingDisplay(); getToppingDisplay();
@ -200,9 +248,9 @@
// console.log('type get', mat_type_t1, mat_type_t2); // console.log('type get', mat_type_t1, mat_type_t2);
currentMaterialType = mat_type_t1 === mat_type_t2 ? mat_type_t1 : mat_type_t2; currentMaterialType = mat_type_t1 === mat_type_t2 ? mat_type_t1 : mat_type_t2;
if (hasMixOrder) { // if (hasMixOrder) {
console.log('detect mix order', mat_num); // console.log('detect mix order', mat_num);
} // }
// if (feed.parameter > 0 || feed.pattern > 0) { // if (feed.parameter > 0 || feed.pattern > 0) {
// console.log('has feed fields', JSON.stringify(feed)); // 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 }) { function handleEvents(event: { event_type: string; payload: any; index: number | undefined }) {
// console.log('triggered event', event.event_type, JSON.stringify(event.payload)); // console.log('triggered event', event.event_type, JSON.stringify(event.payload));
if (event.event_type === 'mat_change') { if (event.event_type === 'mat_change') {
@ -217,6 +278,10 @@
initialize(); initialize();
} else if (event.event_type === 'edit_mat_field') { } else if (event.event_type === 'edit_mat_field') {
console.log('request edit mat'); console.log('request edit mat');
// fix bug: topping instant change by unlink topping
let _current_toppings = $state.snapshot(currentToppings);
// pack all shown data // pack all shown data
recipeDataEvent.set({ recipeDataEvent.set({
event_type: 'edit_mat_field_prep', event_type: 'edit_mat_field_prep',
@ -225,7 +290,7 @@
mat_type: currentMaterialType, mat_type: currentMaterialType,
mat_name: mat_id, mat_name: mat_id,
params: currentStringParams, params: currentStringParams,
toppings: currentToppings, toppings: _current_toppings,
current_topping_group: currentToppingInRow, current_topping_group: currentToppingInRow,
current_topping_list: selectableToppingInGroup, current_topping_list: selectableToppingInGroup,
has_mix_ord: hasMixOrder, has_mix_ord: hasMixOrder,
@ -244,6 +309,23 @@
// apply now // apply now
let keys = Object.keys(change_values); let keys = Object.keys(change_values);
console.log('change keys', JSON.stringify(keys)); 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); handleEvents(event);
} }
}); });
unsubRecipeTopping = latestRecipeToppingData.subscribe((payload) => {
// console.log('topping data subscribe', payload);
});
}); });
onDestroy(() => { onDestroy(() => {
if (unsubRecipeDataEvent) { 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(); unsubRecipeDataEvent();
// console.log('destroy recipe data event listener');
}
if (unsubRecipeTopping) {
unsubRecipeTopping();
} }
}); });
</script> </script>
@ -275,6 +369,14 @@
{getCurrentSelectedToppingGroup()} {getCurrentSelectedToppingGroup()}
</b>, {getCurrentSelectedToppingList()} </b>, {getCurrentSelectedToppingList()}
</p> </p>
{#if oldToppingNamesOnly != null}
<br />
<Button variant="outline" class="font-bold text-red-500">
<!-- old topping -->
<Undo />
<!-- {oldToppingNamesOnly} -->
</Button>
{/if}
</div> </div>
</div> </div>
{:else} {:else}

View file

@ -1,5 +1,11 @@
<script lang="ts"> <script lang="ts">
import { recipeFromMachineQuery, recipeFromServerQuery } from '$lib/core/stores/recipeStore'; import {
currentEditingRecipeProductCode,
latestRecipeToppingData,
recipeFromMachineQuery,
recipeFromServerQuery,
referenceFromPage
} from '$lib/core/stores/recipeStore';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { MediaQuery } from 'svelte/reactivity'; import { MediaQuery } from 'svelte/reactivity';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
@ -12,6 +18,12 @@
import { addNotification } from '$lib/core/stores/noti'; import { addNotification } from '$lib/core/stores/noti';
import { sendToAndroid } from '$lib/core/stores/adbWriter'; import { sendToAndroid } from '$lib/core/stores/adbWriter';
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
import { recipeDataEvent } from '$lib/core/stores/recipeStore';
import { sendMessage } from '$lib/core/handlers/ws_messageSender';
import { auth } from '$lib/core/stores/auth';
import { departmentStore } from '$lib/core/stores/departments';
const isDesktop = new MediaQuery('(min-width: 768px)'); const isDesktop = new MediaQuery('(min-width: 768px)');
let currentData: any = $state(); let currentData: any = $state();
@ -30,7 +42,11 @@
let ready_to_send_brew: any[] = $state([]); let ready_to_send_brew: any[] = $state([]);
async function onPendingChange(newChange: { target: string; value: any }) { let change_value_rpl: any = $state(null);
let callback_revert_value_if_not_save: any = $state(() => {});
let save_change: boolean = $state(false);
async function onPendingChange(newChange: { target: string; value: any; ref_pd: string }) {
console.log('detect pending change', matchMenuStatus(currentData.MenuStatus)); console.log('detect pending change', matchMenuStatus(currentData.MenuStatus));
hasPendingChange = true; hasPendingChange = true;
@ -52,10 +68,65 @@
// //
// TODO: build into structure, flatten fields into 1 layer, strip off `id` (row id) // TODO: build into structure, flatten fields into 1 layer, strip off `id` (row id)
console.log('pending change recipe list', newChange); console.log('pending change recipe list', newChange);
}
ready_to_send_brew = []; let isMatchedProduct = newChange.ref_pd == productCode;
ready_to_send_brew.push([env.PUBLIC_BREW_CURRENT_RECIPE, JSON.stringify(currentData)]);
// get latest edit
let latest_event = get(recipeDataEvent);
// expect edit_change_value_rpl
console.log('latest data event', latest_event);
let topping_value_for_revert = latest_event?.payload.current_toppings;
let new_topping_value_for_save = latest_event?.payload.source.toppings;
// console.log(
// 'topping_data_latest',
// get(latestRecipeToppingData),
// 'topping_old',
// topping_value_for_revert,
// 'current product code',
// get(currentEditingRecipeProductCode)
// );
callback_revert_value_if_not_save = (save: any) => {
if (!save) {
latestRecipeToppingData.set(topping_value_for_revert);
console.log('revert change', get(latestRecipeToppingData));
recipeDataEvent.set({
event_type: 'revert_change',
payload: {},
index: -1
});
} else {
// topping part
latestRecipeToppingData.set(new_topping_value_for_save);
console.log('save change', get(latestRecipeToppingData));
currentData['ToppingSet'] = latestRecipeToppingData;
console.log('current data', currentData);
if (get(referenceFromPage) == 'brew') {
// send change to machine
sendToAndroid({
type: 'save_recipe_machine',
payload: {
time: new Date().toLocaleTimeString(),
data: currentData
}
});
} else if (get(referenceFromPage) == 'overview') {
sendMessage({
type: 'save_recipe',
payload: {
user: get(auth)?.displayName ?? 'unknown',
country: get(departmentStore) ?? 'unknown',
values: currentData
}
});
}
}
};
}
// await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData)); // await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData));
// //
@ -82,7 +153,17 @@
} }
} }
function onCloseDialog() {
currentEditingRecipeProductCode.set('');
callback_revert_value_if_not_save(save_change);
// reset back
save_change = false;
callback_revert_value_if_not_save = () => {};
}
onMount(() => { onMount(() => {
save_change = false;
// //
if (refPage === 'brew') { if (refPage === 'brew') {
// fetch from store // fetch from store
@ -109,7 +190,7 @@
</script> </script>
{#if isDesktop.current} {#if isDesktop.current}
<Dialog.Root> <Dialog.Root onOpenChangeComplete={(_cc) => onCloseDialog()}>
<Dialog.Trigger class="w-full text-start" onselect={(e) => e.preventDefault()} <Dialog.Trigger class="w-full text-start" onselect={(e) => e.preventDefault()}
>View</Dialog.Trigger >View</Dialog.Trigger
> >
@ -126,8 +207,16 @@
<RecipeDetail <RecipeDetail
recipeData={currentData} recipeData={currentData}
{onPendingChange} {onPendingChange}
{productCode}
{refPage} {refPage}
on:brewNow={async () => sendBrewNow()} on:brewNow={async () => sendBrewNow()}
on:saveRecipe={async () => {
save_change = true;
console.log('save change, check state', callback_revert_value_if_not_save);
addNotification('INFO:Save recipe');
}}
/> />
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>

View file

@ -44,6 +44,7 @@ export const recipeFromMachineQuery = writable<any>({});
export const materialFromMachineQuery = writable<any>({}); export const materialFromMachineQuery = writable<any>({});
export const referenceFromPage = writable<string>(''); export const referenceFromPage = writable<string>('');
export const currentEditingRecipeProductCode = writable<string>('');
let worker: Worker | null = null; let worker: Worker | null = null;
let initialized = false; let initialized = false;

View file

@ -2,7 +2,7 @@ import { browser } from '$app/environment';
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
import { get, writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import { handleIncomingMessages } from '../handlers/messageHandler'; 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 { auth } from '../client/firebase';
import { addNotification } from './noti'; import { addNotification } from './noti';
@ -31,6 +31,16 @@ export function connectToWebsocket() {
msgQueue.set(queue); msgQueue.set(queue);
} }
} }
// heartbeat 10s
setInterval(() => {
if (socket) {
sendMessage({
type: 'heartbeat',
payload: {}
});
}
}, 10000);
}); });
socket.addEventListener('message', (event) => { socket.addEventListener('message', (event) => {

View file

@ -32,6 +32,14 @@ export type OutMessage =
values: any; values: any;
}; };
} }
| {
type: 'save_recipe';
payload: {
user: string;
country: string;
values: any;
};
}
| { | {
type: 'heartbeat'; type: 'heartbeat';
payload: {}; payload: {};