Supra_App/src/lib/components/recipe-details/recipe-detail.svelte
pakintada@gmail.com a29ff0be1a feat: recipe version selector
- fix recipe not show on overview
- fix recipe show late after select country
- disable queue message on no connection ws
- fix infinite topping(s) list if moving between pages

Signed-off-by: pakintada@gmail.com <Pakin>
2026-05-04 16:50:15 +07:00

272 lines
7.4 KiB
Svelte

<script lang="ts">
import * as Tabs from '$lib/components/ui/tabs/index';
import * as Card from '$lib/components/ui/card/index';
import Label from '$lib/components/ui/label/label.svelte';
import Input from '$lib/components/ui/input/input.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
import { Badge } from '$lib/components/ui/badge/index';
import * as Item from '$lib/components/ui/item/index';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import RecipelistTable from './recipelist-table.svelte';
import * as adb from '$lib/core/adb/adb';
import { columns, type RecipelistMaterial } from './columns';
import { get, readable, writable } from 'svelte/store';
import {
currentEditingRecipeProductCode,
latestRecipeToppingData,
materialFromMachineQuery,
materialFromServerQuery
} from '$lib/core/stores/recipeStore';
import { generateIcing } from '$lib/helpers/icingGen';
import { getMachineStatus, machineInfoStore } from '$lib/core/stores/machineInfoStore';
import MachineInfo from '../machine-info.svelte';
import { v4 as uuidv4 } from 'uuid';
import { addNotification } from '$lib/core/stores/noti';
import { env } from '$env/dynamic/public';
import { sendCommand, sendReset } from '$lib/core/brew/command';
import { isAdbWriterAvailable } from '$lib/core/stores/adbWriter';
import { sendToAndroid } from '$lib/core/stores/adbWriter';
//
let {
recipeData,
onPendingChange,
productCode,
refPage
}: { recipeData: any; onPendingChange: any; productCode: string; refPage: string } = $props();
let menuName: string = $state('');
let materialSnapshot: any = $state();
let machineInfoSnapshot: any = $state();
let recipeListMatState: RecipelistMaterial[] = $state([]);
let recipeListOriginal: RecipelistMaterial[] = $state([]);
let toppingSlotState: any = $state([]);
const recipeDetailDispatch = createEventDispatcher();
function remappingToColumn(data: any[]): RecipelistMaterial[] {
let ret: RecipelistMaterial[] = [];
// expect recipelist
if (materialSnapshot) {
let d_cnt = 0;
for (let rpl of data) {
let mat = materialSnapshot.filter(
(x: any) => x['id'].toString() === rpl['materialPathId'].toString()
)[0];
// console.log('mat filter get', Object(mat), Object.keys(mat));
let name = mat ? mat['materialOtherName'] : rpl['materialPathId'];
if (rpl['materialPathId'] === 0) {
name = '-';
}
// let gen_id = generateRowId();
// console.log(`generated for ${rpl['materialPathId']} = ${gen_id}`);
ret.push({
id: d_cnt,
material_id: `${name} (${rpl['materialPathId']})`,
is_use: rpl['isUse'],
values: {
string_param: rpl['StringParam'],
mix_order: rpl['MixOrder'],
stir_time: rpl['stirTime'],
feed: {
pattern: rpl['FeedPattern'],
parameter: rpl['FeedParameter']
},
powder: {
gram: rpl['powderGram'],
time: rpl['powderTime']
},
syrup: {
gram: rpl['syrupGram'],
time: rpl['syrupTime']
},
water: {
cold: rpl['waterCold'],
yield: rpl['waterYield']
}
}
});
d_cnt++;
}
}
return ret;
}
async function getCurrentQueue() {
let inst = adb.getAdbInstance();
if (inst) {
let current_brewing = await adb.pull(env.PUBLIC_BREW_CURRENT_RECIPE);
// console.log(`current brewing queue: ${current_brewing}`);
if (current_brewing === '') {
current_brewing = '{}';
}
return JSON.parse(current_brewing ?? '{}');
}
return {
error: 'instance lost'
};
}
async function resetAllPendingCmds() {
// send reset to brew
try {
await sendReset();
addNotification(`INFO:Reset completed!`);
} catch (e) {
addNotification(`ERR:${e}`);
}
}
async function saveRecipe() {
recipeDetailDispatch('saveRecipe');
}
async function sendTriggerBrewNow() {
// check queue ready
// let currentBrewingQueue = await getCurrentQueue();
// console.log('checking queue ... ', Object.keys(currentBrewingQueue).length);
await sendToAndroid({
type: 'brew_prep',
payload: {
start: new Date().toLocaleTimeString()
}
});
// if (Object.keys(currentBrewingQueue).length != 0) {
// addNotification('ERR:Brewing queue is full, please check machine or press `reset`');
// return;
// }
//
let inst = adb.getAdbInstance();
if (inst) {
console.log('check adb writer', isAdbWriterAvailable());
recipeDetailDispatch('brewNow');
} else {
console.log('result check fail');
}
}
async function checkChanges(productCode: string, original: any) {
// console.log('old', original, 'updated', recipeListMatState);
if (recipeListOriginal.length == 0) {
recipeListOriginal = original;
}
if (original !== recipeListMatState) {
await onPendingChange({
target: 'recipeList',
ref_pd: productCode,
value: original
});
}
}
onMount(() => {
machineInfoSnapshot = get(machineInfoStore);
if (recipeData) {
menuName =
recipeData['name'] ?? (recipeData['otherName'] ? recipeData['otherName'] : 'Not set');
if (refPage == 'overview') {
materialSnapshot = get(materialFromServerQuery);
} else if (refPage == 'brew') {
materialSnapshot = get(materialFromMachineQuery);
}
recipeListMatState = remappingToColumn(recipeData['recipes']);
toppingSlotState = recipeData['ToppingSet'];
latestRecipeToppingData.set(toppingSlotState);
currentEditingRecipeProductCode.set(productCode);
// save old value\
}
});
</script>
<!-- show info -->
<!-- latest edit date -->
<!-- Menu Status -->
<div class="-mb-4 flex w-full flex-col gap-6">
<Tabs.Root value="info">
<div class="flex flex-row justify-between">
<Tabs.List>
<Tabs.Trigger value="info">Info</Tabs.Trigger>
<Tabs.Trigger value="details">Details</Tabs.Trigger>
</Tabs.List>
{#if refPage === 'brew'}
<div>
<Button type="button" variant="default" onclick={() => saveRecipe()}>Save</Button>
{#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == ''}
<Button type="button" variant="default" onclick={async () => sendTriggerBrewNow()}
>Test Brew</Button
>
{/if}
</div>
{/if}
</div>
<Tabs.Content value="info">
<Card.Root>
<Card.Header>
<Card.Title>Info</Card.Title>
<Card.Description>Info about this menu</Card.Description>
</Card.Header>
<Card.Content class="grid gap-6">
<div class="grid grid-flow-row gap-3">
<Label for="tabs-menu-name">Name</Label>
<Input id="tabs-menu-name" value={recipeData['name'] ?? ''} />
<Label for="tabs-menu-other-name">Other Name</Label>
<Input id="tabs-menu-other-name" value={recipeData['otherName'] ?? ''} />
</div>
<div class="grid gap-3"></div>
</Card.Content>
</Card.Root>
</Tabs.Content>
<Tabs.Content value="details">
<!-- hide by machine state -->
{#if $machineInfoStore?.status == 'IDLE' || $machineInfoStore?.status == '' || refPage == 'overview'}
<RecipelistTable
data={recipeListMatState}
{columns}
onStateChange={checkChanges}
{productCode}
/>
{:else}
<Card.Root>
<Card.Content>
<div class="grid h-[60vh] w-full place-items-center rounded-md border">
<div class="m-4 flex flex-row gap-2">
<Spinner />Machine is working. Please wait for a moment.
</div>
</div>
</Card.Content>
</Card.Root>
{/if}
</Tabs.Content>
</Tabs.Root>
</div>