63 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { LoaderCircle, Plus } from 'lucide-react'
import { useState } from 'react'
import dynamic from 'next/dynamic'
import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench'
import { useDiyCabinetThumbnails } from '../hooks/useDiyCabinetThumbnails'
import { findDiyColorForMaterial } from '@/lib/diy/materials'
import DiyMaterialSwatch from './DiyMaterialSwatch'
const DiyCabinetThumbnailRenderer = dynamic(() => import('./DiyCabinetThumbnailRenderer'), { ssr: false })
export default function DiyCabinetLibrary({ controller, disabled,onAddColor }: { controller: DiyWorkbenchController; disabled: boolean;onAddColor:(styleCode:string,materialCode:string)=>void }) {
const { getCatalogStyle } = controller.catalogAccess
const [search,setSearch]=useState('')
const styles=controller.catalog.styles.filter(style=>(style.spu||'SJ0380')===controller.seriesSpu)
const series=controller.catalog.series||[{spu:controller.catalog.spu,name:controller.catalog.name,version:1}]
const colors=controller.catalog.materials.filter(material=>material.kind==='main'&&!material.deletedAt)
const visibleStyles=styles.filter(style=>`${style.code} ${getCatalogStyle(style.code).name}`.toLowerCase().includes(search.toLowerCase()))
const {previews,task,complete,fail}=useDiyCabinetThumbnails(controller.catalog,visibleStyles,controller.familyCode)
return <div className="flex min-h-0 flex-col">
{task&&<DiyCabinetThumbnailRenderer catalog={controller.catalog} task={task} onComplete={complete} onError={fail}/>}
<div className="border-b border-zinc-200 p-4">
<label className="text-xs font-medium text-zinc-500" htmlFor="diy-series">柜体系列</label>
<select id="diy-series" value={controller.seriesSpu} onChange={event=>{controller.setSeriesSpu(event.target.value);setSearch('')}} className="mt-2 h-10 w-full rounded-md border border-zinc-300 bg-white px-2 text-sm text-zinc-800">
{series.map(item=><option key={item.spu} value={item.spu}>{item.name}</option>)}
</select>
<input aria-label="搜索柜体款式" value={search} onChange={event=>setSearch(event.target.value)} placeholder="搜索款式 / 编码" className="mt-3 h-9 w-full rounded-md border border-zinc-200 px-2 text-xs outline-none focus:border-blue-400"/>
<p className="mt-3 text-xs font-medium text-zinc-500">共用颜色</p>
<div className="mt-2 flex max-h-28 flex-wrap gap-2 overflow-y-auto">
{colors.map(color => <button key={color.itemCode} type="button" title={color.name} aria-label={`选择主色 ${color.name}`}
aria-pressed={controller.familyCode === color.itemCode} onClick={() => controller.setFamilyCode(color.itemCode)} disabled={disabled}
className={`rounded-md p-0.5 ${controller.familyCode === color.itemCode ? 'ring-2 ring-blue-600' : 'ring-1 ring-zinc-200'}`}>
<DiyMaterialSwatch main={color}/>
</button>)}
</div>
<p className="mt-2 text-xs text-zinc-500">{controller.family}</p>
{!colors.length&&<p className="mt-2 text-xs text-zinc-500">暂无可用颜色,请在素材管理中添加或从回收站恢复。</p>}
</div>
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto p-3">
{visibleStyles.map(style => {
const entry = getCatalogStyle(style.code), count = controller.snapshot.items.filter(item => item.code === style.code).length
const color=findDiyColorForMaterial(controller.catalog,style.code,controller.familyCode)
const preview=previews[style.code]
return <button type="button" key={style.code} onClick={() => color?controller.add(style.code):onAddColor(style.code,controller.familyCode)} disabled={disabled||!controller.familyCode}
aria-label={`添加${entry.name}`} className="group flex w-full items-center gap-3 rounded-lg border border-zinc-200 bg-white p-2 text-left transition hover:border-blue-300 hover:bg-blue-50/30 disabled:opacity-50">
<span className="flex h-16 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md bg-[#f2f4f4]" aria-busy={Boolean(controller.familyCode&&!preview?.image&&!preview?.error)}>
{!controller.familyCode?<span className="text-[10px] text-zinc-500">暂无颜色</span>:preview?.image ? /* eslint-disable-next-line @next/next/no-img-element */
<img src={preview.image} alt={entry.name} className="h-full w-full object-contain" /> : preview?.error ?
<span className="text-[10px] text-zinc-500" title={preview.error}>预览失败</span> : <LoaderCircle size={16} className="animate-spin text-zinc-400" aria-label="正在生成款式预览"/>}
</span>
<span className="min-w-0 flex-1"><span className="block text-sm font-medium text-zinc-800">{entry.name}</span>
<span className="mt-1 block text-[11px] text-zinc-500">{Math.round(style.realSize.width)}×{Math.round(style.realSize.height)}×{Math.round(style.realSize.depth)} mm</span>
<span className="mt-1 block text-[11px] text-zinc-500">{style.cellCount} 格{count ? ` · 已选 ${count} 件` : ''}</span>
{!color&&controller.familyCode&&<span className="mt-1 block text-[11px] text-blue-600">添加此颜色款</span>}
</span><Plus size={16} className="shrink-0 text-zinc-400 group-hover:text-blue-600" />
</button>
})}
{!styles.length&&<p className="px-1 py-6 text-xs text-zinc-500">此系列尚无款式,可通过顶部素材管理添加。</p>}
</div>
</div>
}