63 lines
5.4 KiB
TypeScript
63 lines
5.4 KiB
TypeScript
'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>
|
||
}
|