init
This commit is contained in:
commit
451223816b
338 changed files with 9938 additions and 0 deletions
114
src/lib/components/recipe-details/columns.ts
Normal file
114
src/lib/components/recipe-details/columns.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// {
|
||||
// "MixOrder": 0,
|
||||
// "StringParam": "",
|
||||
// "FeedParameter": 0,
|
||||
// "FeedPattern": 0,
|
||||
// "isUse": true,
|
||||
// "materialPathId": 599501,
|
||||
// "powderGram": 0,
|
||||
// "powderTime": 0,
|
||||
// "stirTime": 110,
|
||||
// "syrupGram": 0,
|
||||
// "syrupTime": 0,
|
||||
// "waterCold": 0,
|
||||
// "waterYield": 0
|
||||
// }
|
||||
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent, renderSnippet } from '../ui/data-table';
|
||||
import RecipelistIsuse from './recipelist-isuse.svelte';
|
||||
import RecipelistValue from './recipelist-value.svelte';
|
||||
import { DragHandle } from './recipelist-table.svelte';
|
||||
import { createRawSnippet } from 'svelte';
|
||||
import RecipelistMatSelect from './recipelist-mat-select.svelte';
|
||||
|
||||
export type RecipelistMaterial = {
|
||||
id: number;
|
||||
material_id: string;
|
||||
is_use: boolean;
|
||||
values: {
|
||||
string_param: string;
|
||||
mix_order: number;
|
||||
feed: {
|
||||
pattern: number;
|
||||
parameter: number;
|
||||
};
|
||||
powder: {
|
||||
gram: number;
|
||||
time: number;
|
||||
};
|
||||
syrup: {
|
||||
gram: number;
|
||||
time: number;
|
||||
};
|
||||
water: {
|
||||
cold: number;
|
||||
yield: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const columns: ColumnDef<RecipelistMaterial>[] = [
|
||||
{
|
||||
id: 'id',
|
||||
accessorKey: 'id',
|
||||
header: () => null,
|
||||
cell: () => null,
|
||||
enableSorting: true
|
||||
},
|
||||
{
|
||||
id: 'drag',
|
||||
header: () =>
|
||||
renderSnippet(
|
||||
createRawSnippet(() => ({
|
||||
render: () => '<div class="w-1 text-start"></div>'
|
||||
}))
|
||||
),
|
||||
cell: () => renderSnippet(DragHandle)
|
||||
},
|
||||
{
|
||||
accessorKey: 'is_use',
|
||||
id: 'is_use',
|
||||
header: ({ column }) =>
|
||||
renderSnippet(
|
||||
createRawSnippet(() => ({
|
||||
render: () => '<div class="w-0.5">Enable</div>'
|
||||
}))
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(RecipelistIsuse, {
|
||||
checked: row.original.is_use,
|
||||
onCheckedChange: (value) => {
|
||||
row.original.is_use = !!value;
|
||||
row.toggleSelected(row.original.is_use);
|
||||
}
|
||||
});
|
||||
},
|
||||
enableSorting: false,
|
||||
enableHiding: false
|
||||
},
|
||||
{
|
||||
id: 'material_id',
|
||||
header: ({ column }) => 'Material',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(RecipelistMatSelect, {
|
||||
currentMat: row.original.material_id,
|
||||
onMatChange: (value: any) => {
|
||||
row.original.material_id = value;
|
||||
console.log('change mat', value);
|
||||
row.toggleSelected(row.original.is_use);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'values',
|
||||
id: 'values',
|
||||
header: ({ column }) => 'Values',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(RecipelistValue, {
|
||||
...row.original.values
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
145
src/lib/components/recipe-details/recipe-detail.svelte
Normal file
145
src/lib/components/recipe-details/recipe-detail.svelte
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<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 { onDestroy, onMount } from 'svelte';
|
||||
import RecipelistTable from './recipelist-table.svelte';
|
||||
|
||||
import { columns, type RecipelistMaterial } from './columns';
|
||||
import { get, readable, writable } from 'svelte/store';
|
||||
import { materialFromMachineQuery } from '$lib/core/stores/recipeStore';
|
||||
import { generateIcing } from '$lib/helpers/icingGen';
|
||||
import { machineInfoStore } from '$lib/core/stores/machineInfoStore';
|
||||
import MachineInfo from '../machine-info.svelte';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
//
|
||||
let { recipeData, onPendingChange }: { recipeData: any; onPendingChange: any } = $props();
|
||||
|
||||
let menuName: string = $state('');
|
||||
|
||||
let materialSnapshot: any = $state();
|
||||
let machineInfoSnapshot: any = $state();
|
||||
|
||||
let recipeListMatState: RecipelistMaterial[] = $state([]);
|
||||
let recipeListOriginal: RecipelistMaterial[] = $state([]);
|
||||
|
||||
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'],
|
||||
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 checkChanges(original: any) {
|
||||
console.log('old', original, 'updated', recipeListMatState);
|
||||
if (recipeListOriginal.length == 0) {
|
||||
recipeListOriginal = original;
|
||||
}
|
||||
|
||||
if (original !== recipeListMatState) {
|
||||
await onPendingChange({
|
||||
target: 'recipeList',
|
||||
value: original
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
machineInfoSnapshot = get(machineInfoStore);
|
||||
|
||||
if (recipeData) {
|
||||
menuName =
|
||||
recipeData['name'] ?? (recipeData['otherName'] ? recipeData['otherName'] : 'Not set');
|
||||
materialSnapshot = get(materialFromMachineQuery);
|
||||
|
||||
recipeListMatState = remappingToColumn(recipeData['recipes']);
|
||||
// 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">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="info">Info</Tabs.Trigger>
|
||||
<Tabs.Trigger value="details">Details</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<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">
|
||||
<RecipelistTable data={recipeListMatState} {columns} onStateChange={checkChanges} />
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
14
src/lib/components/recipe-details/recipelist-isuse.svelte
Normal file
14
src/lib/components/recipe-details/recipelist-isuse.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">
|
||||
import Checkbox from '../ui/checkbox/checkbox.svelte';
|
||||
import { onMount, type ComponentProps } from 'svelte';
|
||||
|
||||
let {
|
||||
checked,
|
||||
onCheckedChange = (v) => (checked = v),
|
||||
...restProps
|
||||
}: ComponentProps<typeof Checkbox> = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex w-1.5 items-center justify-center">
|
||||
<Checkbox {checked} onCheckedChange={(e) => onCheckedChange(e)} {...restProps} />
|
||||
</div>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Button } from '../ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
materialData,
|
||||
materialFromMachineQuery,
|
||||
referenceFromPage
|
||||
} from '$lib/core/stores/recipeStore';
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
|
||||
import { SearchIcon, PlusIcon, BeanIcon, StarIcon } from '@lucide/svelte/icons';
|
||||
|
||||
let { currentMat, onMatChange } = $props();
|
||||
let allMatData: any = $state();
|
||||
let refPage: string | undefined = $state();
|
||||
|
||||
function changeMat(mat_id: string) {
|
||||
currentMat = mat_id;
|
||||
onMatChange(mat_id);
|
||||
}
|
||||
|
||||
function getMaterialTypeIcon(mat_id: number) {}
|
||||
|
||||
onMount(() => {
|
||||
refPage = get(referenceFromPage);
|
||||
if (refPage === 'brew') allMatData = get(materialFromMachineQuery);
|
||||
else if (refPage === 'overview') allMatData = get(materialData);
|
||||
});
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="ghost" class="text-muted-foreground hover:bg-transparent">
|
||||
{currentMat}
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<div class="sticky top-0 z-10 my-4 flex items-center gap-2 bg-accent">
|
||||
<SearchIcon />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search by mat id or name"
|
||||
onchange={(e) => {}}
|
||||
oninput={(e) => {}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- permission create mat -->
|
||||
<DropdownMenu.Item>
|
||||
<div class="flex gap-2">
|
||||
<PlusIcon />
|
||||
<p>Create Material</p>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
{#each allMatData as mat}
|
||||
<DropdownMenu.Item onclick={() => changeMat(`${mat.materialOtherName} (${mat.id})`)}>
|
||||
<div class="flex gap-2">
|
||||
{#if mat.BeanChannel}
|
||||
<BeanIcon />
|
||||
{:else if mat.id > 8110 && mat.id < 8131}
|
||||
<StarIcon />
|
||||
{/if}
|
||||
|
||||
<p>{mat.materialOtherName} ({mat.id})</p>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{:else}
|
||||
<DropdownMenu.Item>
|
||||
<p class="text-muted-foreground">No materials available</p>
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
196
src/lib/components/recipe-details/recipelist-table.svelte
Normal file
196
src/lib/components/recipe-details/recipelist-table.svelte
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<script module>
|
||||
export { DragHandle };
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
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 RecipeDetail from './recipe-detail.svelte';
|
||||
import {
|
||||
createTable,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getSortedRowModel,
|
||||
type ColumnDef,
|
||||
type Row,
|
||||
type RowSelectionState,
|
||||
type SortingState
|
||||
} from '@tanstack/table-core';
|
||||
import { createSvelteTable } from '../ui/data-table';
|
||||
import * as Table from '$lib/components/ui/table/index';
|
||||
import FlexRender from '../ui/data-table/flex-render.svelte';
|
||||
import ScrollArea from '../ui/scroll-area/scroll-area.svelte';
|
||||
import { type Attachment } from 'svelte/attachments';
|
||||
import Button from '../ui/button/button.svelte';
|
||||
import { GripVerticalIcon } from '@lucide/svelte/icons';
|
||||
import { DragDropProvider } from '@dnd-kit-svelte/svelte';
|
||||
import { move } from '@dnd-kit/helpers';
|
||||
import { RestrictToVerticalAxis } from '@dnd-kit/abstract/modifiers';
|
||||
import { type UniqueIdentifier } from '@dnd-kit/abstract';
|
||||
import { type RecipelistMaterial } from './columns';
|
||||
import { useSortable } from '@dnd-kit-svelte/svelte/sortable';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { get, writable, type Writable } from 'svelte/store';
|
||||
|
||||
// type DataTableProps<TData, TValue> = {
|
||||
// columns: ColumnDef<TData, TValue>[];
|
||||
// data: TData[];
|
||||
// };
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
onStateChange
|
||||
}: {
|
||||
data: RecipelistMaterial[];
|
||||
columns: ColumnDef<any, any>[];
|
||||
onStateChange: any;
|
||||
} = $props();
|
||||
|
||||
let sorting = $state<SortingState>([]);
|
||||
let rowSelection = $state<RowSelectionState>({});
|
||||
|
||||
// let recipeMatState = $state<RecipelistMaterial[]>(data);
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: true,
|
||||
getRowId: (row: any) => row.id.toString(),
|
||||
state: {
|
||||
get sorting() {
|
||||
return sorting;
|
||||
},
|
||||
get rowSelection() {
|
||||
return rowSelection;
|
||||
}
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
onStateChange: async (updater) => {
|
||||
console.log('table state change', data);
|
||||
await onStateChange(table.getRowModel().rows.map((x) => x.original));
|
||||
},
|
||||
onSortingChange: async (updater) => {
|
||||
console.log('triggering sorting');
|
||||
if (typeof updater === 'function') {
|
||||
sorting = updater(sorting);
|
||||
} else {
|
||||
sorting = updater;
|
||||
}
|
||||
await onStateChange(table.getRowModel().rows.map((x) => x.original));
|
||||
},
|
||||
onRowSelectionChange: async (updater) => {
|
||||
// table.getRowModel().rows.find((x) => x.original.id == )
|
||||
if (typeof updater === 'function') {
|
||||
rowSelection = updater(rowSelection);
|
||||
let rows = table.getRowModel().rows;
|
||||
|
||||
console.log('state size', data, rows);
|
||||
} else {
|
||||
rowSelection = updater;
|
||||
}
|
||||
await onStateChange(table.getRowModel().rows.map((x) => x.original));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- use card -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Recipe List</Card.Title>
|
||||
<Card.Description>Material used in this menu's brewing process</Card.Description>
|
||||
|
||||
<Card.Content>
|
||||
<!-- table -->
|
||||
|
||||
<DragDropProvider
|
||||
modifiers={[
|
||||
// @ts-expect-error @dnd-kit/adbstract types are botched atm
|
||||
RestrictToVerticalAxis
|
||||
]}
|
||||
onDragEnd={(e) => {
|
||||
// snap
|
||||
data = move(data as any, e as any);
|
||||
//
|
||||
}}
|
||||
>
|
||||
<ScrollArea class="h-[60vh] w-full rounded-md border" type="always">
|
||||
<Table.Root class="relative w-full">
|
||||
<Table.Header class="sticky top-0 z-10">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head colspan={header.colSpan}>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body class="**:data-[slot=table-cell]:first:w-8">
|
||||
{#if table.getRowModel().rows?.length}
|
||||
{#each table.getRowModel().rows as row, index (row.id)}
|
||||
{@render DraggableRow({ row, index })}
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center"
|
||||
>Empty recipe.</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</ScrollArea>
|
||||
</DragDropProvider>
|
||||
</Card.Content>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
|
||||
{#snippet DraggableRow({ row, index }: { row: Row<any>; index: number })}
|
||||
{@const { ref, isDragging, handleRef } = useSortable({
|
||||
id: row.original.id,
|
||||
index: () => index
|
||||
})}
|
||||
|
||||
<Table.Row
|
||||
data-state={row.original.is_use ? 'selected' : undefined}
|
||||
data-dragging={isDragging.current}
|
||||
class="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||
{@attach ref}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
attach={handleRef}
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/snippet}
|
||||
|
||||
{#snippet DragHandle({ attach }: { attach: Attachment })}
|
||||
<Button
|
||||
{@attach attach}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-7 text-muted-foreground hover:bg-transparent"
|
||||
>
|
||||
<GripVerticalIcon class="size-3 text-muted-foreground" />
|
||||
<span class="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
{/snippet}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { RecipelistMaterial } from './columns';
|
||||
|
||||
let { string_param, mix_order, feed, water, powder, syrup }: RecipelistMaterial['values'] =
|
||||
$props();
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue