Update: Something spicy for recipe viewing only but called it editor lol
This commit is contained in:
parent
72187f348b
commit
7dd075dd59
8 changed files with 450 additions and 346 deletions
|
|
@ -1,173 +1,21 @@
|
|||
import { Input } from '@/components/ui/input'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { type MaterialSetting, type Recipe01, type Recipes } from '@/models/recipe/schema'
|
||||
import { memo, useEffect, useMemo, useState } from 'react'
|
||||
import { type Recipes } from '@/models/recipe/schema'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Search, CupSoda, Wheat, Dessert, Cherry, WineOff, Server, Loader2 } from 'lucide-react'
|
||||
import Nav from './nav'
|
||||
import RecipeList from './recipe-list'
|
||||
import { format, isBefore, isToday } from 'date-fns'
|
||||
import { format } from 'date-fns'
|
||||
import RecipeDisplay from './recipe-display'
|
||||
import MaterialList from './material-list'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import taoAxios from '@/lib/taoAxios'
|
||||
|
||||
interface RecipeMenuProps {
|
||||
recipes: Recipes
|
||||
recipe01: Recipe01[]
|
||||
defaultSize?: number
|
||||
isDevBranch: boolean
|
||||
}
|
||||
const RecipeMenu: React.FC<RecipeMenuProps> = memo(({ recipes, recipe01, defaultSize, isDevBranch }) => {
|
||||
const [recipeList, setRecipeList] = useState<Recipe01[]>(recipe01)
|
||||
|
||||
const sortedRecipe01 = useMemo(() => {
|
||||
return recipeList.sort((a, b) => (a.LastChange && b.LastChange && isBefore(a.LastChange, b.LastChange) ? 1 : -1))
|
||||
}, [recipeList])
|
||||
|
||||
const { isPending, isSuccess, isError, mutate } = useMutation({
|
||||
mutationFn: async () => {
|
||||
return taoAxios.post('/v2/recipes/', recipes, {
|
||||
params: {
|
||||
country_id: 'tha'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const recipesFiltered = recipe01.filter(
|
||||
item =>
|
||||
item.productCode.toLowerCase().includes(search.toLowerCase()) ||
|
||||
(item.name && item.name.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
setRecipeList(recipesFiltered)
|
||||
} else {
|
||||
setRecipeList(recipe01)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResizablePanel id="recipe-panel" defaultSize={defaultSize} minSize={30}>
|
||||
<Tabs defaultValue="all">
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">
|
||||
Recipe Version: {recipes.MachineSetting.configNumber} {isDevBranch ? '(Dev)' : ''}
|
||||
</h1>
|
||||
<TabsList className="ml-auto">
|
||||
<TabsTrigger value="all" className="text-zinc-600 dark:text-zinc-200">
|
||||
All Menu
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="today" className="text-zinc-600 dark:text-zinc-200">
|
||||
Today
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="pb-3 flex justify-end items-end">
|
||||
<Button className="bg-primary text-white" onClick={() => mutate()}>
|
||||
{isPending ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
<span>Updating...</span>
|
||||
</div>
|
||||
) : isSuccess ? (
|
||||
'Updated'
|
||||
) : isError ? (
|
||||
'Error'
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Server size={20} />
|
||||
Up Recipe to Server
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search" className="pl-8" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<TabsContent value="all" className="m-0">
|
||||
<RecipeList items={sortedRecipe01} />
|
||||
</TabsContent>
|
||||
<TabsContent value="today" className="m-0">
|
||||
<RecipeList items={sortedRecipe01.filter(item => item.LastChange && isToday(item.LastChange))} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={defaultSize}>
|
||||
<RecipeDisplay recipes={recipes} />
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
interface MaterialsProps {
|
||||
recipes: Recipes
|
||||
defaultSize?: number
|
||||
isDevBranch: boolean
|
||||
}
|
||||
const Materials: React.FC<MaterialsProps> = memo(({ recipes, defaultSize, isDevBranch }) => {
|
||||
const [materialSettingList, setMaterialSettingList] = useState<MaterialSetting[]>(recipes.MaterialSetting)
|
||||
|
||||
const sortedMaterialSettingList = useMemo(() => {
|
||||
return materialSettingList.sort((a, b) => (a.id < b.id ? 1 : -1))
|
||||
}, [materialSettingList])
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const materialSettingsFiltered = recipes.MaterialSetting.filter(
|
||||
item =>
|
||||
item.materialName.toLowerCase().includes(search.toLowerCase()) ||
|
||||
item.id.toString().includes(search.toLowerCase())
|
||||
)
|
||||
setMaterialSettingList(materialSettingsFiltered)
|
||||
} else {
|
||||
setMaterialSettingList(recipes.MaterialSetting)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResizablePanel id="material-panel" defaultSize={defaultSize} minSize={30}>
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">
|
||||
Recipe Version: {recipes.MachineSetting.configNumber} {isDevBranch ? '(Dev)' : ''}
|
||||
</h1>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search" className="pl-8" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<MaterialList items={sortedMaterialSettingList} />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={defaultSize}>
|
||||
<RecipeDisplay recipes={recipes} />
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)
|
||||
})
|
||||
import type { ItemMetadata, ListMetadata } from '@/hooks/recipe-dashboard'
|
||||
import useRecipeDashboard, { EditorShowStateEnum } from '@/hooks/recipe-dashboard'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface RecipeEditorProps {
|
||||
isDevBranch: boolean
|
||||
|
|
@ -186,15 +34,118 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
}) => {
|
||||
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed)
|
||||
|
||||
const [showListIndex, setShowListIndex] = useState(0)
|
||||
const [editorShowState, setEditorShowState] = useState(EditorShowStateEnum.RECIPES_IN_USE)
|
||||
|
||||
const { recipesEnable, recipeDisable } = useMemo(() => {
|
||||
const { recipesEnable, recipesDisable } = useMemo<{
|
||||
recipesEnable: ItemMetadata[]
|
||||
recipesDisable: ItemMetadata[]
|
||||
}>(() => {
|
||||
return {
|
||||
recipesEnable: recipes.Recipe01.filter(r => r.isUse),
|
||||
recipeDisable: recipes.Recipe01.filter(r => !r.isUse)
|
||||
recipesEnable: recipes.Recipe01.filter(r => r.isUse).map(x => ({
|
||||
id: x.productCode,
|
||||
name: x.name,
|
||||
lastChange: x.LastChange
|
||||
})),
|
||||
recipesDisable: recipes.Recipe01.filter(r => !r.isUse).map(x => ({
|
||||
id: x.productCode,
|
||||
name: x.name,
|
||||
lastChange: x.LastChange
|
||||
}))
|
||||
}
|
||||
}, [recipes])
|
||||
|
||||
const [currentItems, setCurrentItems] = useState<ItemMetadata[]>(recipesEnable)
|
||||
|
||||
const [listMetadata, setListMetadata] = useState<ListMetadata>({
|
||||
items: recipesEnable,
|
||||
currentSelectedId: undefined,
|
||||
onSelectFn: id => setSelectedRecipe(id.toString())
|
||||
})
|
||||
|
||||
const { selectedMaterial, setSelectedMaterial, selectedRecipe, setSelectedRecipe } = useRecipeDashboard(
|
||||
useShallow(state => ({
|
||||
selectedMaterial: state.selectedMaterial,
|
||||
setSelectedMaterial: state.setSelectedMaterial,
|
||||
selectedRecipe: state.selectedRecipe,
|
||||
setSelectedRecipe: state.setSelectedRecipe
|
||||
}))
|
||||
)
|
||||
|
||||
// user click button from nav
|
||||
useEffect(() => {
|
||||
let list: ItemMetadata[] = []
|
||||
let currentSelectId: string | number | undefined
|
||||
let onSelectedFn: (id: string | number) => void = () => {}
|
||||
|
||||
if (editorShowState === EditorShowStateEnum.RECIPES_IN_USE) {
|
||||
list = recipesEnable
|
||||
currentSelectId = selectedRecipe
|
||||
onSelectedFn = id => setSelectedRecipe(id.toString())
|
||||
} else if (editorShowState === EditorShowStateEnum.RECIPES_NOT_IN_USE) {
|
||||
list = recipesDisable
|
||||
currentSelectId = selectedRecipe
|
||||
onSelectedFn = id => setSelectedRecipe(id.toString())
|
||||
} else if (editorShowState === EditorShowStateEnum.MATERIALS_SETTING) {
|
||||
list = recipes.MaterialSetting.map(x => ({
|
||||
id: x.id,
|
||||
name: x.materialName
|
||||
}))
|
||||
currentSelectId = selectedMaterial
|
||||
onSelectedFn = id => setSelectedMaterial(Number(id))
|
||||
} else if (editorShowState === EditorShowStateEnum.TOPPING_GROUPS) {
|
||||
list = recipes.Topping.ToppingGroup.map(x => ({
|
||||
id: x.groupID,
|
||||
name: x.name
|
||||
}))
|
||||
currentSelectId = selectedMaterial
|
||||
onSelectedFn = id => setSelectedMaterial(Number(id))
|
||||
} else if (editorShowState === EditorShowStateEnum.TOPPING_LIST) {
|
||||
list = recipes.Topping.ToppingList.map(x => ({
|
||||
id: x.id,
|
||||
name: x.name
|
||||
}))
|
||||
}
|
||||
|
||||
setListMetadata({
|
||||
items: list,
|
||||
currentSelectedId: currentSelectId,
|
||||
onSelectFn: onSelectedFn
|
||||
})
|
||||
setCurrentItems(list)
|
||||
setSearch('')
|
||||
}, [editorShowState])
|
||||
|
||||
const saveRecipeState = useMutation({
|
||||
mutationFn: async () => {
|
||||
return taoAxios.post('/v2/recipes/', recipes, {
|
||||
params: {
|
||||
country_id: 'tha'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const recipesFiltered = currentItems.filter(
|
||||
item =>
|
||||
item.id.toString().toLowerCase().includes(search.toLowerCase()) ||
|
||||
(item.name && item.name.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
setListMetadata({
|
||||
...listMetadata,
|
||||
items: recipesFiltered
|
||||
})
|
||||
} else {
|
||||
setListMetadata({
|
||||
...listMetadata,
|
||||
items: currentItems
|
||||
})
|
||||
}
|
||||
}, [search])
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup
|
||||
|
|
@ -228,19 +179,19 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
<Separator />
|
||||
<Nav
|
||||
isCollapsed={isCollapsed}
|
||||
showListIndex={showListIndex}
|
||||
setShowListIndex={setShowListIndex}
|
||||
showListIndex={editorShowState}
|
||||
setShowListIndex={setEditorShowState}
|
||||
links={[
|
||||
{
|
||||
index: 0,
|
||||
index: EditorShowStateEnum.RECIPES_IN_USE,
|
||||
title: 'Menu (Enabled)',
|
||||
label: recipesEnable.length.toString(),
|
||||
icon: CupSoda
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
index: EditorShowStateEnum.RECIPES_NOT_IN_USE,
|
||||
title: 'Menu (Disabled)',
|
||||
label: recipeDisable.length.toString(),
|
||||
label: recipesDisable.length.toString(),
|
||||
icon: WineOff
|
||||
}
|
||||
]}
|
||||
|
|
@ -248,23 +199,23 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
<Separator />
|
||||
<Nav
|
||||
isCollapsed={isCollapsed}
|
||||
showListIndex={showListIndex}
|
||||
setShowListIndex={setShowListIndex}
|
||||
showListIndex={editorShowState}
|
||||
setShowListIndex={setEditorShowState}
|
||||
links={[
|
||||
{
|
||||
index: 2,
|
||||
index: EditorShowStateEnum.MATERIALS_SETTING,
|
||||
title: 'Materials',
|
||||
label: recipes.MaterialSetting.length.toString(),
|
||||
icon: Wheat
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
index: EditorShowStateEnum.TOPPING_GROUPS,
|
||||
title: 'ToppingsGroups',
|
||||
label: recipes.Topping.ToppingGroup.length.toString(),
|
||||
icon: Dessert
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
index: EditorShowStateEnum.TOPPING_LIST,
|
||||
title: 'ToppingsList',
|
||||
label: recipes.Topping.ToppingList.length.toString(),
|
||||
icon: Cherry
|
||||
|
|
@ -273,23 +224,52 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
{showListIndex === 0 ? (
|
||||
<RecipeMenu
|
||||
recipes={recipes}
|
||||
recipe01={recipesEnable}
|
||||
defaultSize={defaultLayout[1]}
|
||||
isDevBranch={isDevBranch}
|
||||
/>
|
||||
) : showListIndex === 1 ? (
|
||||
<RecipeMenu
|
||||
recipes={recipes}
|
||||
recipe01={recipeDisable}
|
||||
defaultSize={defaultLayout[1]}
|
||||
isDevBranch={isDevBranch}
|
||||
/>
|
||||
) : showListIndex === 2 ? (
|
||||
<Materials recipes={recipes} defaultSize={defaultLayout[1]} isDevBranch={isDevBranch} />
|
||||
) : null}
|
||||
<ResizablePanel id="recipe-panel" defaultSize={defaultLayout[1]} minSize={30}>
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">
|
||||
Recipe Version: {recipes.MachineSetting.configNumber} {isDevBranch ? '(Dev)' : ''}
|
||||
</h1>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="pb-3 flex justify-end items-end">
|
||||
<Button className="bg-primary text-white" onClick={() => saveRecipeState.mutate()}>
|
||||
{saveRecipeState.isPending ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
<span>Updating...</span>
|
||||
</div>
|
||||
) : saveRecipeState.isSuccess ? (
|
||||
'Updated'
|
||||
) : saveRecipeState.isError ? (
|
||||
'Error'
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Server size={20} />
|
||||
Up Recipe to Server
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search" className="pl-8" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{listMetadata ? (
|
||||
<RecipeList
|
||||
items={listMetadata.items}
|
||||
onSelect={listMetadata.onSelectFn}
|
||||
currentSelectId={listMetadata.currentSelectedId}
|
||||
/>
|
||||
) : null}
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={defaultLayout[1]}>
|
||||
<RecipeDisplay recipes={recipes} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue