Merge branch 'master' of https://pakin-inspiron-15-3530.tail110d9.ts.net/pakin/Supra_App
This commit is contained in:
commit
f4b8df2c27
29 changed files with 693 additions and 97 deletions
BIN
src/lib/assets/modules/sheet_btn.png
Normal file
BIN
src/lib/assets/modules/sheet_btn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5 KiB |
|
|
@ -11,7 +11,8 @@
|
||||||
DiamondIcon,
|
DiamondIcon,
|
||||||
BugIcon,
|
BugIcon,
|
||||||
CupSodaIcon,
|
CupSodaIcon,
|
||||||
Shield
|
Shield,
|
||||||
|
FileSpreadsheet
|
||||||
} from '@lucide/svelte/icons';
|
} from '@lucide/svelte/icons';
|
||||||
import TaobinLogo from '$lib/assets/logo.svelte';
|
import TaobinLogo from '$lib/assets/logo.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
import { sidebarStore } from '$lib/core/stores/sidebar';
|
import { sidebarStore } from '$lib/core/stores/sidebar';
|
||||||
import { auth } from '$lib/core/stores/auth';
|
import { auth } from '$lib/core/stores/auth';
|
||||||
import { isUserAdmin } from '$lib/core/admin/adminService';
|
import { isUserAdmin } from '$lib/core/admin/adminService';
|
||||||
|
import { referenceFromPage } from '$lib/core/stores/recipeStore';
|
||||||
|
|
||||||
let sideBar: HTMLElement | null = $state(null);
|
let sideBar: HTMLElement | null = $state(null);
|
||||||
let isSideBarOpen: boolean = $state(true);
|
let isSideBarOpen: boolean = $state(true);
|
||||||
|
|
@ -70,6 +72,17 @@
|
||||||
icon: BugIcon
|
icon: BugIcon
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sheet',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Overview',
|
||||||
|
url: '/departments',
|
||||||
|
icon: FileSpreadsheet
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
@ -138,7 +151,17 @@
|
||||||
<Sidebar.MenuItem>
|
<Sidebar.MenuItem>
|
||||||
<Sidebar.MenuButton>
|
<Sidebar.MenuButton>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<a href={sub.url} {...props}>
|
<a
|
||||||
|
href={sub.url}
|
||||||
|
{...props}
|
||||||
|
onclick={(e) => {
|
||||||
|
if (nav.title === 'Sheet') {
|
||||||
|
e.preventDefault();
|
||||||
|
referenceFromPage.set('sheet');
|
||||||
|
goto(sub.url);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#if sub.icon}
|
{#if sub.icon}
|
||||||
<sub.icon />
|
<sub.icon />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -162,7 +185,17 @@
|
||||||
<Sidebar.MenuItem>
|
<Sidebar.MenuItem>
|
||||||
<Sidebar.MenuButton>
|
<Sidebar.MenuButton>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<a href={sub.url} {...props}>
|
<a
|
||||||
|
href={sub.url}
|
||||||
|
{...props}
|
||||||
|
onclick={(e) => {
|
||||||
|
if (nav.title === 'Sheet') {
|
||||||
|
e.preventDefault();
|
||||||
|
referenceFromPage.set('sheet');
|
||||||
|
goto(sub.url);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#if sub.icon}
|
{#if sub.icon}
|
||||||
<sub.icon />
|
<sub.icon />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,14 @@ 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) => {
|
||||||
|
recipeDataEvent.set({
|
||||||
|
event_type: 'edit_change_value_rpl',
|
||||||
|
payload: changes,
|
||||||
|
index: row.original.id
|
||||||
|
});
|
||||||
|
|
||||||
// get change parameters
|
// get change parameters
|
||||||
|
row.toggleSelected(row.original.is_use);
|
||||||
},
|
},
|
||||||
onDetectMixOrder: () => {
|
onDetectMixOrder: () => {
|
||||||
// set next
|
// set next
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,23 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { addNotification } from '$lib/core/stores/noti';
|
import { addNotification } from '$lib/core/stores/noti';
|
||||||
import { get } from 'svelte/store';
|
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 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 { row_id }: { row_id: number } = $props();
|
||||||
|
|
||||||
let current_editing_data: any = $state();
|
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
|
// update value, do re-render
|
||||||
if (event.payload) {
|
if (event.payload) {
|
||||||
current_editing_data = 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
|
// default topping
|
||||||
if (
|
if (
|
||||||
|
|
@ -105,11 +115,15 @@
|
||||||
current_editing_data.water.yield > 0 || current_editing_data.water.cold > 0;
|
current_editing_data.water.yield > 0 || current_editing_data.water.cold > 0;
|
||||||
toggledOpenFeed =
|
toggledOpenFeed =
|
||||||
current_editing_data.feed.pattern > 0 || current_editing_data.feed.parameter > 0;
|
current_editing_data.feed.pattern > 0 || current_editing_data.feed.parameter > 0;
|
||||||
|
|
||||||
|
sheetOpenState = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestDataFromDisplay() {
|
function requestDataFromDisplay() {
|
||||||
|
// pause show until have data
|
||||||
|
sheetOpenState = false;
|
||||||
console.log('sending request edit', row_id);
|
console.log('sending request edit', row_id);
|
||||||
recipeDataEvent.set({
|
recipeDataEvent.set({
|
||||||
event_type: 'edit_mat_field',
|
event_type: 'edit_mat_field',
|
||||||
|
|
@ -120,13 +134,12 @@
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (current_editing_data === undefined) {
|
if (current_editing_data === undefined) {
|
||||||
addNotification('ERR:Unable to edit');
|
addNotification('ERR:Unable to edit');
|
||||||
|
sheetOpenState = false;
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFieldValueChange(field_name: string[], new_value: any) {
|
function onFieldValueChange(field_name: string[], new_value: any) {
|
||||||
console.log('change on', JSON.stringify(field_name), new_value);
|
|
||||||
|
|
||||||
// validate if field value changes
|
// validate if field value changes
|
||||||
let curr_val;
|
let curr_val;
|
||||||
for (let field of field_name) {
|
for (let field of field_name) {
|
||||||
|
|
@ -147,43 +160,133 @@
|
||||||
if (has_changed) {
|
if (has_changed) {
|
||||||
if (value_event_state === ValueEvent.NONE) {
|
if (value_event_state === ValueEvent.NONE) {
|
||||||
value_event_state = ValueEvent.EDITED;
|
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 {
|
} else {
|
||||||
// revert value in key
|
// revert value in key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEditingValue() {
|
function saveEditingValue() {
|
||||||
console.log('saving value ...');
|
console.log('saving value ...', value_event_state);
|
||||||
if (value_event_state === ValueEvent.EDITED) {
|
if (value_event_state === ValueEvent.EDITED) {
|
||||||
|
let payload = {
|
||||||
|
source: current_editing_data,
|
||||||
|
change: changed_data
|
||||||
|
};
|
||||||
recipeDataEvent.set({
|
recipeDataEvent.set({
|
||||||
event_type: 'save_mat_field',
|
event_type: 'save_mat_field',
|
||||||
payload: current_editing_data,
|
payload,
|
||||||
index: row_id
|
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) {
|
function handleToppingGroupChange(v: any) {
|
||||||
console.log('change topping group', JSON.stringify(v));
|
console.log('change topping group');
|
||||||
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
|
||||||
|
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<any>(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) {
|
function handleToppingListChange(v: any) {
|
||||||
console.log('Topping list chose: ', JSON.stringify(v));
|
console.log('Topping list chose ');
|
||||||
selected_topping_list_id = v.id;
|
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<any>(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}
|
// 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}
|
// 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(() => {
|
onMount(() => {
|
||||||
|
sheetOpenState = false;
|
||||||
|
console.log('sheet open? ', sheetOpenState);
|
||||||
|
|
||||||
let refFrom = get(referenceFromPage);
|
let refFrom = get(referenceFromPage);
|
||||||
categories = get(
|
categories = get(
|
||||||
refFrom === 'overview' ? toppingGroupFromServerQuery : toppingGroupFromMachineQuery
|
refFrom === 'overview' ? toppingGroupFromServerQuery : toppingGroupFromMachineQuery
|
||||||
|
|
@ -200,6 +306,9 @@
|
||||||
refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery
|
refFrom === 'overview' ? toppingListFromServerQuery : toppingListFromMachineQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// save ref
|
||||||
|
currentRef = refFrom;
|
||||||
|
|
||||||
return recipeDataEvent.subscribe((event) => {
|
return recipeDataEvent.subscribe((event) => {
|
||||||
if (event !== null && event.index !== undefined && event.index === row_id) {
|
if (event !== null && event.index !== undefined && event.index === row_id) {
|
||||||
handleEvents(event);
|
handleEvents(event);
|
||||||
|
|
@ -208,7 +317,16 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sheet.Root>
|
<Sheet.Root
|
||||||
|
bind:open={sheetOpenState}
|
||||||
|
onOpenChange={(next) => {
|
||||||
|
if (!next) {
|
||||||
|
beforeClosing();
|
||||||
|
} else {
|
||||||
|
sheetOpenState = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Sheet.Trigger>
|
<Sheet.Trigger>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|
@ -256,6 +374,15 @@
|
||||||
</Field.Set>
|
</Field.Set>
|
||||||
|
|
||||||
<ScrollArea class="h-[60vh] w-full" type="always">
|
<ScrollArea class="h-[60vh] w-full" type="always">
|
||||||
|
{#if warnUserNotSaveChange}
|
||||||
|
<div class="flex flex-col items-center gap-2 text-center">
|
||||||
|
<h1 class="text-lg font-bold text-red-600">Unsaved changes may be lost.</h1>
|
||||||
|
<p class="text-balance text-red-700">
|
||||||
|
Click on "Discard Changes" to revert back values
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- topping layout -->
|
<!-- topping layout -->
|
||||||
<div
|
<div
|
||||||
class={current_editing_data.mat_type === 'topping'
|
class={current_editing_data.mat_type === 'topping'
|
||||||
|
|
@ -487,10 +614,13 @@
|
||||||
</Field.Group>
|
</Field.Group>
|
||||||
</Field.Set>
|
</Field.Set>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
<!-- final -->
|
<!-- final -->
|
||||||
<Field.Field orientation="horizontal">
|
<Field.Field orientation="horizontal">
|
||||||
<Button type="button" onclick={() => saveEditingValue()}>Save</Button>
|
<Button type="button" onclick={() => saveEditingValue()}>Save</Button>
|
||||||
<Button variant="outline" type="button">Cancel</Button>
|
<Button variant="outline" type="button" onclick={() => beforeClosing()}
|
||||||
|
>{warnUserNotSaveChange ? 'Discard Changes' : 'Cancel'}</Button
|
||||||
|
>
|
||||||
</Field.Field>
|
</Field.Field>
|
||||||
</Field.Group>
|
</Field.Group>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
if (currentToppings[getToppingSlot()]['ListGroupID'][0] === 0) {
|
if (currentToppings[getToppingSlot()]['ListGroupID'][0] === 0) {
|
||||||
return 'Empty';
|
return 'Empty';
|
||||||
}
|
}
|
||||||
if (!current_selected) {
|
if (current_selected === undefined || current_selected === null) {
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,11 +237,14 @@
|
||||||
},
|
},
|
||||||
index: row_uid
|
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) {
|
// apply now
|
||||||
console.log('triggered on change editing', JSON.stringify(value));
|
let keys = Object.keys(change_values);
|
||||||
|
console.log('change keys', JSON.stringify(keys));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
enum ValueEvent {
|
||||||
NONE,
|
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 };
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
// currentData.recipes = newChange.value;
|
// currentData.recipes = newChange.value;
|
||||||
//
|
//
|
||||||
// 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(newChange);
|
console.log('pending change recipe list', newChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData));
|
// await adb.push('/sdcard/coffeevending/.curr.brewing.json', JSON.stringify(currentData));
|
||||||
|
|
@ -103,4 +103,6 @@
|
||||||
<RecipeDetail recipeData={currentData} {onPendingChange} {refPage} />
|
<RecipeDetail recipeData={currentData} {onPendingChange} {refPage} />
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
{:else}{/if}
|
{:else}
|
||||||
|
<!-- TODO: handle case open on mobile? -->
|
||||||
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.ActionProps & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-action"
|
||||||
|
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-action", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
variant = "outline",
|
||||||
|
size = "default",
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.CancelProps & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-cancel"
|
||||||
|
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-cancel", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import AlertDialogPortal from "./alert-dialog-portal.svelte";
|
||||||
|
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
||||||
|
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
size = "default",
|
||||||
|
portalProps,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
||||||
|
size?: "default" | "sm";
|
||||||
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof AlertDialogPortal>>;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPortal {...portalProps}>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-content"
|
||||||
|
data-size={size}
|
||||||
|
class={cn(
|
||||||
|
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.DescriptionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-description"
|
||||||
|
class={cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-footer"
|
||||||
|
class={cn(
|
||||||
|
"bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-header"
|
||||||
|
class={cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
src/lib/components/ui/alert-dialog/alert-dialog-media.svelte
Normal file
20
src/lib/components/ui/alert-dialog/alert-dialog-media.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-media"
|
||||||
|
class={cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.OverlayProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-overlay"
|
||||||
|
class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ...restProps }: AlertDialogPrimitive.PortalProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Portal {...restProps} />
|
||||||
17
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
17
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-title"
|
||||||
|
class={cn("text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />
|
||||||
7
src/lib/components/ui/alert-dialog/alert-dialog.svelte
Normal file
7
src/lib/components/ui/alert-dialog/alert-dialog.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { open = $bindable(false), ...restProps }: AlertDialogPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Root bind:open {...restProps} />
|
||||||
40
src/lib/components/ui/alert-dialog/index.ts
Normal file
40
src/lib/components/ui/alert-dialog/index.ts
Normal file
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
@ -4,25 +4,25 @@
|
||||||
import { type VariantProps, tv } from "tailwind-variants";
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
export const buttonVariants = tv({
|
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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs",
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
destructive:
|
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",
|
||||||
"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",
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||||
outline:
|
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||||
"bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs",
|
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",
|
||||||
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",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
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",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
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",
|
||||||
icon: "size-9",
|
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
||||||
"icon-sm": "size-8",
|
icon: "size-8",
|
||||||
"icon-lg": "size-10",
|
"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: {
|
defaultVariants: {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
||||||
|
|
||||||
if (stream_id) {
|
if (stream_id) {
|
||||||
addNotification('INFO:Start streaming data');
|
addNotification('INFO:Start streaming data');
|
||||||
recipeLoading.set(true);
|
// recipeLoading.set(true);
|
||||||
recipeStreamMeta.set({
|
recipeStreamMeta.set({
|
||||||
id: stream_id,
|
id: stream_id,
|
||||||
total_size: total_size,
|
total_size: total_size,
|
||||||
|
|
@ -82,6 +82,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
||||||
if (percent == 100) {
|
if (percent == 100) {
|
||||||
addNotification(`INFO:Current progress ${percent}%`);
|
addNotification(`INFO:Current progress ${percent}%`);
|
||||||
}
|
}
|
||||||
|
buildOverviewFromServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ export function sendMessage(msg: OutMessage): boolean {
|
||||||
const socket = get(socketStore);
|
const socket = get(socketStore);
|
||||||
const data = JSON.stringify(msg);
|
const data = JSON.stringify(msg);
|
||||||
|
|
||||||
|
// console.log('try sending ', data);
|
||||||
|
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||||
console.warn('WebSocket not connected, put to queue');
|
console.warn('WebSocket not connected, put to queue');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,49 +7,56 @@ import { auth } from '../client/firebase';
|
||||||
import { addNotification } from './noti';
|
import { addNotification } from './noti';
|
||||||
|
|
||||||
let socket: WebSocket | null = null;
|
let socket: WebSocket | null = null;
|
||||||
|
const ENABLE_WS_DEBUG: boolean = false;
|
||||||
|
|
||||||
export const socketStore = writable<WebSocket | null>(null);
|
export const socketStore = writable<WebSocket | null>(null);
|
||||||
|
|
||||||
export function connectToWebsocket() {
|
export function connectToWebsocket() {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
console.log('connecting to ', env.PUBLIC_WSS);
|
// console.log('connecting to ', env.PUBLIC_WSS);
|
||||||
socket = new WebSocket(`${env.PUBLIC_WSS}`);
|
try {
|
||||||
|
socket = new WebSocket(`${env.PUBLIC_WSS}`);
|
||||||
|
|
||||||
socket.addEventListener('open', () => {
|
socket.addEventListener('open', () => {
|
||||||
socketStore.set(socket);
|
socketStore.set(socket);
|
||||||
addNotification('INFO:Connected!');
|
addNotification('INFO:Connected!');
|
||||||
|
|
||||||
// recover messages on connect, flushing
|
// recover messages on connect, flushing
|
||||||
while (get(msgQueue).length) {
|
while (get(msgQueue).length) {
|
||||||
let queue = get(msgQueue);
|
let queue = get(msgQueue);
|
||||||
let current = queue.shift();
|
let current = queue.shift();
|
||||||
if (current && socket) {
|
if (current && socket) {
|
||||||
socket.send(current);
|
socket.send(current);
|
||||||
// set next
|
// set next
|
||||||
msgQueue.set(queue);
|
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 () => {
|
return () => {
|
||||||
if (socket?.readyState === WebSocket.OPEN) {
|
if (socket?.readyState === WebSocket.OPEN) {
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,13 @@ export type OutMessage =
|
||||||
permissions: string;
|
permissions: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'log_report';
|
||||||
|
payload: {
|
||||||
|
user: string;
|
||||||
|
action: string;
|
||||||
|
country: string;
|
||||||
|
values: any;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { asset } from '$app/paths';
|
import { asset } from '$app/paths';
|
||||||
import { permission as currentPerms } from '$lib/core/stores/permissions';
|
import { permission as currentPerms } from '$lib/core/stores/permissions';
|
||||||
|
import { page } from '$app/stores';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
|
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
|
||||||
import { departmentStore } from '$lib/core/stores/departments';
|
import { departmentStore } from '$lib/core/stores/departments';
|
||||||
|
|
@ -9,9 +10,12 @@
|
||||||
import { addNotification } from '$lib/core/stores/noti';
|
import { addNotification } from '$lib/core/stores/noti';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { setCookieOnNonBrowser } from '$lib/helpers/cookie';
|
import { setCookieOnNonBrowser } from '$lib/helpers/cookie';
|
||||||
|
import { referenceFromPage} from '$lib/core/stores/recipeStore';
|
||||||
|
|
||||||
let enabledAccessibleCountries: string[] = $state([]);
|
let enabledAccessibleCountries: string[] = $state([]);
|
||||||
|
|
||||||
|
const refPage = get(referenceFromPage);
|
||||||
|
|
||||||
function onCountrySelected(cnt: string) {
|
function onCountrySelected(cnt: string) {
|
||||||
departmentStore.set(cnt);
|
departmentStore.set(cnt);
|
||||||
if (browser && 'cookieStore' in window) cookieStore.set('department', cnt);
|
if (browser && 'cookieStore' in window) cookieStore.set('department', cnt);
|
||||||
|
|
@ -19,30 +23,37 @@
|
||||||
addNotification(`INFO:Selected ${cnt}`);
|
addNotification(`INFO:Selected ${cnt}`);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
console.log(get(departmentStore));
|
console.log(get(departmentStore));
|
||||||
await goto('/recipe/overview');
|
|
||||||
|
if (refPage === 'sheet') {
|
||||||
|
await goto('/sheet/overview');
|
||||||
|
} else {
|
||||||
|
await goto('/recipe/overview');
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read or write permission
|
// read or write permission
|
||||||
let userCurrentPerms = get(currentPerms).filter(
|
let userCurrentPerms = get(currentPerms).filter((x) => {
|
||||||
(x) => x.startsWith('document.read') || x.startsWith('document.write')
|
if (refPage === 'sheet') {
|
||||||
);
|
return x.startsWith('document.write');
|
||||||
|
}
|
||||||
|
return x.startsWith('document.read');
|
||||||
|
});
|
||||||
// show country by enabled `document.read.{country}`
|
// show country by enabled `document.read.{country}`
|
||||||
enabledAccessibleCountries = userCurrentPerms
|
enabledAccessibleCountries = userCurrentPerms.map((x) => x.split('.')[2]);
|
||||||
.filter((x) => x.startsWith('document.read'))
|
|
||||||
.map((x) => x.split('.')[2]);
|
|
||||||
|
|
||||||
// update every 30s
|
// update every 30s
|
||||||
if (enabledAccessibleCountries.length == 0) {
|
if (enabledAccessibleCountries.length == 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// read or write permission
|
// read or write permission
|
||||||
let userCurrentPerms = get(currentPerms).filter(
|
let userCurrentPerms = get(currentPerms).filter((x) => {
|
||||||
(x) => x.startsWith('document.read') || x.startsWith('document.write')
|
if (refPage === 'sheet') {
|
||||||
);
|
return x.startsWith('document.write');
|
||||||
|
}
|
||||||
|
return x.startsWith('document.read');
|
||||||
|
});
|
||||||
// show country by enabled `document.read.{country}`
|
// show country by enabled `document.read.{country}`
|
||||||
enabledAccessibleCountries = userCurrentPerms
|
enabledAccessibleCountries = userCurrentPerms.map((x) => x.split('.')[2]);
|
||||||
.filter((x) => x.startsWith('document.read'))
|
|
||||||
.map((x) => x.split('.')[2]);
|
|
||||||
|
|
||||||
if (enabledAccessibleCountries.length == 1) {
|
if (enabledAccessibleCountries.length == 1) {
|
||||||
onCountrySelected(enabledAccessibleCountries[0]);
|
onCountrySelected(enabledAccessibleCountries[0]);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import RecipeModuleBtn from '$lib/assets/modules/recipe_btn.png';
|
import RecipeModuleBtn from '$lib/assets/modules/recipe_btn.png';
|
||||||
import MachineInspectBtn from '$lib/assets/modules/monitoring_btn.png';
|
import MachineInspectBtn from '$lib/assets/modules/monitoring_btn.png';
|
||||||
|
import SheetModuleBtn from '$lib/assets/modules/sheet_btn.png';
|
||||||
import { animate, JSAnimation, remove as removeAnime, stagger } from 'animejs';
|
import { animate, JSAnimation, remove as removeAnime, stagger } from 'animejs';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
|
|
@ -9,9 +10,11 @@
|
||||||
import { permission as currentPermissions } from '$lib/core/stores/permissions';
|
import { permission as currentPermissions } from '$lib/core/stores/permissions';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { referenceFromPage} from '$lib/core/stores/recipeStore';
|
||||||
|
|
||||||
let recipeModBtn = $state<HTMLElement | null>(null);
|
let recipeModBtn = $state<HTMLElement | null>(null);
|
||||||
let monitorModBtn = $state<HTMLElement | null>(null);
|
let monitorModBtn = $state<HTMLElement | null>(null);
|
||||||
|
let sheetModBtn = $state<HTMLElement | null>(null);
|
||||||
|
|
||||||
let gotoDashboardBtn = $state<HTMLElement | null>(null);
|
let gotoDashboardBtn = $state<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
|
@ -78,6 +81,36 @@
|
||||||
>
|
>
|
||||||
<img src={RecipeModuleBtn} alt="Recipes" loading="lazy" />
|
<img src={RecipeModuleBtn} alt="Recipes" loading="lazy" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
id="sheet_mod_btn"
|
||||||
|
bind:this={sheetModBtn}
|
||||||
|
onclick={() => {
|
||||||
|
referenceFromPage.set('sheet');
|
||||||
|
goto('/departments');
|
||||||
|
}}
|
||||||
|
onmouseenter={() => {
|
||||||
|
if (sheetModBtn) {
|
||||||
|
animate(sheetModBtn, {
|
||||||
|
scale: 1.1,
|
||||||
|
duration: 300,
|
||||||
|
ease: 'inOutSine'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onmouseleave={() => {
|
||||||
|
if (sheetModBtn) {
|
||||||
|
animate(sheetModBtn, {
|
||||||
|
scale: 1.0,
|
||||||
|
duration: 200,
|
||||||
|
ease: 'inOutSine'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={SheetModuleBtn} alt="Sheets" loading="lazy" />
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- need permission `tools` -->
|
<!-- need permission `tools` -->
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@
|
||||||
// schedule check if recipe is empty
|
// schedule check if recipe is empty
|
||||||
if (data.recipes.length == 0) {
|
if (data.recipes.length == 0) {
|
||||||
console.log('loading recipe ....');
|
console.log('loading recipe ....');
|
||||||
recipeLoading.set(true);
|
// recipeLoading.set(true);
|
||||||
// empty
|
// empty
|
||||||
await getRecipes();
|
await getRecipes();
|
||||||
|
|
||||||
setTimeout(() => recipeLoading.set(false), 3000);
|
// setTimeout(() => recipeLoading.set(false), 3000);
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
||||||
62
src/routes/(authed)/sheet/overview/+page.svelte
Normal file
62
src/routes/(authed)/sheet/overview/+page.svelte
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
|
import Input from '$lib/components/ui/input/input.svelte';
|
||||||
|
import { SearchIcon } from '@lucide/svelte/icons';
|
||||||
|
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import {
|
||||||
|
loadRecipe,
|
||||||
|
recipeData,
|
||||||
|
recipeFromServerQuery,
|
||||||
|
recipeOverviewData,
|
||||||
|
referenceFromPage
|
||||||
|
} from '$lib/core/stores/recipeStore.js';
|
||||||
|
import { sendMessage } from '$lib/core/handlers/ws_messageSender.js';
|
||||||
|
import { auth } from '$lib/core/stores/auth.js';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { getRecipes } from '$lib/core/client/server.js';
|
||||||
|
import { departmentStore } from '$lib/core/stores/departments';
|
||||||
|
|
||||||
|
let refDepartment: string | undefined = $state();
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// do load recipe
|
||||||
|
// loadRecipe();
|
||||||
|
refDepartment = get(departmentStore);
|
||||||
|
referenceFromPage.set('overview');
|
||||||
|
// await getRecipes();
|
||||||
|
});
|
||||||
|
|
||||||
|
// onDestroy(() => {
|
||||||
|
// unsubRecipeData();
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-8 flex">
|
||||||
|
<!-- header -->
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 class="m-8 text-4xl font-bold">Layout overview [ {refDepartment} ]</h1>
|
||||||
|
<p class="mx-8 my-0 text-muted-foreground">
|
||||||
|
Display menus from the spreadsheet current selected country
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mx-8 my-4 flex gap-2">
|
||||||
|
<Button variant="default">+ Create Menu</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- search bar -->
|
||||||
|
<!-- <div class="mx-4 my-8 flex w-full items-center justify-center gap-2">
|
||||||
|
<SearchIcon />
|
||||||
|
<Input type="text" placeholder="Search by id, product code, name or material" class="" />
|
||||||
|
</div> -->
|
||||||
|
<!-- filter -->
|
||||||
|
|
||||||
|
<!-- table -->
|
||||||
|
|
||||||
|
<!-- <div class="w-full overflow-auto">
|
||||||
|
<DataTable data={data.recipes} refPage="overview" {columns} />
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue