2026-02-17 14:30:02 +07:00
|
|
|
<script lang="ts">
|
2026-03-04 13:28:14 +07:00
|
|
|
import { onDestroy, onMount } from 'svelte';
|
2026-02-17 14:30:02 +07:00
|
|
|
import type { RecipelistMaterial } from './columns';
|
2026-03-04 13:28:14 +07:00
|
|
|
import {
|
|
|
|
|
convertFromInterProductCode,
|
|
|
|
|
extractMaterialIdFromDisplay,
|
|
|
|
|
getMaterialType
|
|
|
|
|
} from '$lib/data/recipeService';
|
|
|
|
|
import Label from '../ui/label/label.svelte';
|
|
|
|
|
import Input from '../ui/input/input.svelte';
|
|
|
|
|
import Separator from '$lib/components/ui/separator/separator.svelte';
|
|
|
|
|
import Button from '../ui/button/button.svelte';
|
|
|
|
|
import { PencilIcon } from '@lucide/svelte/icons';
|
|
|
|
|
import * as Tooltip from '../ui/tooltip/index';
|
|
|
|
|
import {
|
|
|
|
|
latestRecipeToppingData,
|
|
|
|
|
recipeDataEvent,
|
|
|
|
|
toppingGroupFromServerQuery,
|
|
|
|
|
toppingListFromServerQuery
|
|
|
|
|
} from '$lib/core/stores/recipeStore';
|
|
|
|
|
import { get } from 'svelte/store';
|
2026-02-17 14:30:02 +07:00
|
|
|
|
2026-03-04 13:28:14 +07:00
|
|
|
let {
|
|
|
|
|
row_uid,
|
|
|
|
|
mat_id,
|
|
|
|
|
onDetectToppingSlot,
|
|
|
|
|
onDetectMixOrder,
|
|
|
|
|
onEditValue,
|
|
|
|
|
string_param,
|
|
|
|
|
mix_order,
|
|
|
|
|
feed,
|
|
|
|
|
water,
|
|
|
|
|
powder,
|
|
|
|
|
syrup,
|
|
|
|
|
stir_time
|
|
|
|
|
}: {
|
|
|
|
|
row_uid: number;
|
|
|
|
|
mat_id: string;
|
|
|
|
|
onDetectToppingSlot: any;
|
|
|
|
|
onDetectMixOrder: any;
|
|
|
|
|
onEditValue: any;
|
|
|
|
|
} & RecipelistMaterial['values'] = $props();
|
|
|
|
|
|
|
|
|
|
let currentMaterialType = $state('');
|
|
|
|
|
let hasMixOrder = $state(false);
|
|
|
|
|
let currentStringParams: any = $state({});
|
|
|
|
|
let currentMaterialInt: number = $state(0);
|
|
|
|
|
|
|
|
|
|
// All ui states
|
|
|
|
|
let showBlenderParam = $state(false);
|
|
|
|
|
let showMixParam = $state(false);
|
|
|
|
|
let showSyrupParam = $state(false);
|
|
|
|
|
let showWaterParam = $state(false);
|
|
|
|
|
let showPowderParam = $state(false);
|
|
|
|
|
let showIceParam = $state(false);
|
|
|
|
|
let showAdjustGrinder = $state(false);
|
|
|
|
|
let showEspressoParam = $state(false);
|
|
|
|
|
let showToppingIdx = $state(false);
|
|
|
|
|
let showEquipmentLayout = $state(false);
|
|
|
|
|
let showToppingParam = $state(false);
|
|
|
|
|
let showSubtractPreWater = $state(false);
|
|
|
|
|
let showCleanOptionParam = $state(false);
|
|
|
|
|
|
|
|
|
|
// toppings
|
|
|
|
|
let currentToppings: any = $state([]);
|
|
|
|
|
// current topping of this row
|
|
|
|
|
let currentToppingInRow: any = $state();
|
|
|
|
|
let selectableToppingInGroup: any[] = $state([]);
|
|
|
|
|
|
|
|
|
|
let unsubRecipeDataEvent: any;
|
|
|
|
|
|
|
|
|
|
function isToppingId(mat_id: string): boolean {
|
|
|
|
|
let mat_num = extractMaterialIdFromDisplay(mat_id);
|
|
|
|
|
currentMaterialInt = mat_num;
|
|
|
|
|
return mat_num > 8110 && mat_num < 8131;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getToppingSlot() {
|
|
|
|
|
return currentMaterialInt - 8110 - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractStringParam() {
|
|
|
|
|
if (string_param && string_param !== '') {
|
|
|
|
|
let plist = string_param.split(',');
|
|
|
|
|
|
|
|
|
|
for (let param of plist) {
|
|
|
|
|
if (param !== '') {
|
|
|
|
|
let kv = param.split('=');
|
|
|
|
|
let key = kv[0];
|
|
|
|
|
let value = kv[1] ?? '';
|
|
|
|
|
|
|
|
|
|
console.log('key', key, 'value', value);
|
|
|
|
|
currentStringParams[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPowderSyrupValue() {
|
|
|
|
|
if (currentMaterialType === 'powder' || currentMaterialType === 'bean') {
|
|
|
|
|
return powder.gram;
|
|
|
|
|
} else if (currentMaterialType === 'syrup') {
|
|
|
|
|
return syrup.gram;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStirTimeName() {
|
|
|
|
|
switch (currentMaterialType) {
|
|
|
|
|
case 'whipper':
|
|
|
|
|
return 'Mix';
|
|
|
|
|
case 'bean':
|
|
|
|
|
return 'Grinder';
|
|
|
|
|
case 'others':
|
|
|
|
|
if (currentMaterialInt == 8001 || currentMaterialInt == 8002) {
|
|
|
|
|
return 'Clean';
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getToppingDisplay() {
|
|
|
|
|
let current_row_topping = currentToppings[getToppingSlot()];
|
|
|
|
|
let groupQuery = get(toppingGroupFromServerQuery);
|
|
|
|
|
let listQuery = get(toppingListFromServerQuery);
|
|
|
|
|
|
|
|
|
|
// console.log(JSON.stringify(groupQuery[0]));
|
|
|
|
|
// console.log(JSON.stringify(listQuery[0]));
|
|
|
|
|
|
|
|
|
|
let groupData = groupQuery.find(
|
|
|
|
|
(x: any) => x.groupID.toString() === current_row_topping['groupID']
|
|
|
|
|
);
|
|
|
|
|
let listData = listQuery.filter(
|
|
|
|
|
(x: any) =>
|
|
|
|
|
groupData &&
|
|
|
|
|
Object.keys(groupData).includes('idInGroup') &&
|
|
|
|
|
groupData['idInGroup'].split(',').includes(x.id.toString())
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log('topping data', JSON.stringify(groupData), 'list', listData.length);
|
|
|
|
|
|
|
|
|
|
currentToppingInRow =
|
|
|
|
|
current_row_topping['groupID'] === 0 || current_row_topping['groupID'] === '0'
|
|
|
|
|
? {
|
|
|
|
|
name: 'Empty',
|
|
|
|
|
otherName: 'Empty',
|
|
|
|
|
id: 0
|
|
|
|
|
}
|
|
|
|
|
: groupData;
|
|
|
|
|
selectableToppingInGroup =
|
|
|
|
|
currentToppingInRow.id === 0
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
name: 'Empty',
|
|
|
|
|
otherName: 'Empty',
|
|
|
|
|
id: 0
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
: listData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCurrentSelectedToppingGroup() {
|
|
|
|
|
if (currentToppingInRow) {
|
|
|
|
|
return currentToppingInRow['otherName'] ?? currentToppingInRow['name'];
|
|
|
|
|
} else {
|
|
|
|
|
return 'Unknown';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCurrentSelectedToppingList() {
|
|
|
|
|
let current_selected = selectableToppingInGroup.find(
|
|
|
|
|
(x) => x.id === currentToppings[getToppingSlot()]['ListGroupID'][0]
|
|
|
|
|
);
|
|
|
|
|
if (currentToppings[getToppingSlot()]['ListGroupID'][0] === 0) {
|
|
|
|
|
return 'Empty';
|
|
|
|
|
}
|
|
|
|
|
if (!current_selected) {
|
|
|
|
|
return 'Unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return current_selected.otherName ?? current_selected.name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initialize() {
|
|
|
|
|
currentToppings = get(latestRecipeToppingData);
|
|
|
|
|
|
|
|
|
|
hasMixOrder = mix_order == 1;
|
|
|
|
|
|
|
|
|
|
extractStringParam();
|
|
|
|
|
|
|
|
|
|
if (isToppingId(mat_id) && onDetectToppingSlot) {
|
|
|
|
|
currentMaterialType = 'topping';
|
|
|
|
|
|
|
|
|
|
getToppingDisplay();
|
|
|
|
|
onDetectToppingSlot();
|
|
|
|
|
} else {
|
|
|
|
|
let mat_num = extractMaterialIdFromDisplay(mat_id);
|
|
|
|
|
|
|
|
|
|
currentMaterialInt = mat_num;
|
|
|
|
|
|
|
|
|
|
let mat_type_t1 = getMaterialType(mat_num);
|
|
|
|
|
let mat_type_t2 = getMaterialType(convertFromInterProductCode(mat_num));
|
|
|
|
|
|
|
|
|
|
// console.log('type get', mat_type_t1, mat_type_t2);
|
|
|
|
|
currentMaterialType = mat_type_t1 === mat_type_t2 ? mat_type_t1 : mat_type_t2;
|
|
|
|
|
|
|
|
|
|
if (hasMixOrder) {
|
|
|
|
|
console.log('detect mix order', mat_num);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (feed.parameter > 0 || feed.pattern > 0) {
|
|
|
|
|
console.log('has feed fields', JSON.stringify(feed));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleEvents(event: { event_type: string; payload: any; index: number | undefined }) {
|
|
|
|
|
console.log('triggered event', event.event_type, JSON.stringify(event.payload));
|
|
|
|
|
if (event.event_type === 'mat_change') {
|
|
|
|
|
// update value, do re-render
|
|
|
|
|
initialize();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function triggerEditChange(value: any) {
|
|
|
|
|
console.log('triggered on change editing', JSON.stringify(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
initialize();
|
|
|
|
|
unsubRecipeDataEvent = recipeDataEvent.subscribe((event) => {
|
|
|
|
|
if (event && event.index && event.index === row_uid) {
|
|
|
|
|
// has some event
|
|
|
|
|
handleEvents(event);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
if (unsubRecipeDataEvent) {
|
|
|
|
|
unsubRecipeDataEvent();
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-02-17 14:30:02 +07:00
|
|
|
</script>
|
2026-03-04 13:28:14 +07:00
|
|
|
|
|
|
|
|
{#if currentMaterialType === 'topping'}
|
|
|
|
|
<!-- do topping layout -->
|
|
|
|
|
<div>
|
|
|
|
|
<!-- get name of topping -->
|
|
|
|
|
<div class="mx-auto my-4 flex flex-row gap-8">
|
|
|
|
|
<!-- popup selector for topping group -->
|
|
|
|
|
<Button variant="default">{getCurrentSelectedToppingGroup()}</Button>
|
|
|
|
|
<!-- popup selector for topping list -->
|
|
|
|
|
<Button variant="default">{getCurrentSelectedToppingList()}</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex w-full flex-row gap-4">
|
|
|
|
|
<!-- string param -->
|
|
|
|
|
{#if !hasMixOrder}
|
|
|
|
|
<!-- check if param is esp-v2-press-value -->
|
|
|
|
|
|
|
|
|
|
{#if currentStringParams['esp-v2-press-value']}
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<Label class="font-bold" for={`esp_v2_press_${row_uid}`}>Press</Label>
|
|
|
|
|
<div class="flex flex-row items-center space-x-2 text-center">
|
|
|
|
|
<Input
|
|
|
|
|
class="w-16"
|
|
|
|
|
id={`esp_v2_press_${row_uid}`}
|
|
|
|
|
type="number"
|
|
|
|
|
value={currentStringParams['esp-v2-press-value']}
|
|
|
|
|
onchangecapture={(v) =>
|
|
|
|
|
triggerEditChange(`${v.currentTarget.id}=${v.currentTarget.value}`)}
|
|
|
|
|
/>
|
|
|
|
|
<p>mA</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if water.yield > 0}
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<Label class="font-bold" for={`water_yield_volume_${row_uid}`}>Hot</Label>
|
|
|
|
|
<div class="flex flex-row items-center space-x-2 text-center">
|
|
|
|
|
<Input
|
|
|
|
|
class="w-16"
|
|
|
|
|
type="number"
|
|
|
|
|
id={`water_yield_volume_${row_uid}`}
|
|
|
|
|
value={water.yield}
|
|
|
|
|
onchangecapture={(v) =>
|
|
|
|
|
triggerEditChange(`${v.currentTarget.id}=${v.currentTarget.value}`)}
|
|
|
|
|
/>
|
|
|
|
|
<p>ml</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if water.cold > 0}
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<Label class="font-bold" for={`water_cold_volume_${row_uid}`}>Cold</Label>
|
|
|
|
|
<div class="flex flex-row items-center space-x-2 text-center">
|
|
|
|
|
<Input
|
|
|
|
|
class="w-16"
|
|
|
|
|
type="number"
|
|
|
|
|
id={`water_cold_volume_${row_uid}`}
|
|
|
|
|
value={water.cold}
|
|
|
|
|
onchangecapture={(v) =>
|
|
|
|
|
triggerEditChange(`${v.currentTarget.id}=${v.currentTarget.value}`)}
|
|
|
|
|
/>
|
|
|
|
|
<p>ml</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if currentMaterialType !== 'cup' && currentMaterialType !== 'topping' && stir_time > 0}
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<Label class="font-bold" for={`stir_time_${row_uid}`}>{getStirTimeName()}</Label>
|
|
|
|
|
<div class="flex flex-row items-center space-x-2 text-center">
|
|
|
|
|
<Input
|
|
|
|
|
class="w-16"
|
|
|
|
|
type="number"
|
|
|
|
|
id={`stir_time_${row_uid}`}
|
|
|
|
|
value={stir_time / 10}
|
|
|
|
|
onchangecapture={(v) =>
|
|
|
|
|
triggerEditChange(`${v.currentTarget.id}=${v.currentTarget.value}`)}
|
|
|
|
|
/>
|
|
|
|
|
<p>sec</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- display powder/syrup -->
|
|
|
|
|
{#if currentMaterialType === 'syrup' || currentMaterialType === 'powder' || currentMaterialType === 'bean'}
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<Label class="font-bold" for={`powder_syrup_volume_${row_uid}`}>Volume</Label>
|
|
|
|
|
<div class="flex flex-row items-center space-x-2 text-center">
|
|
|
|
|
<Input
|
|
|
|
|
class="w-16"
|
|
|
|
|
type="number"
|
|
|
|
|
id={`powder_syrup_volume_${row_uid}`}
|
|
|
|
|
value={getPowderSyrupValue()}
|
|
|
|
|
onchangecapture={(v) =>
|
|
|
|
|
triggerEditChange(`${v.currentTarget.id}=${v.currentTarget.value}`)}
|
|
|
|
|
/>
|
|
|
|
|
<p>gram</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
<!-- feed pattern -->
|
|
|
|
|
{#if feed.parameter > 0 || feed.pattern > 0}
|
|
|
|
|
<div class="mx-auto my-4 flex items-center gap-8 text-center">
|
|
|
|
|
<Button variant="outline">Style {feed.pattern}</Button>
|
|
|
|
|
<Button variant="outline">Level {feed.parameter}</Button>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|