import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { Helmet } from 'react-helmet-async' import type { TemplateEntity } from '../../../services/api/contracts' import { deleteTemplate, deleteTemplates, getTemplateDashboardSummary, listTemplates, type TemplateDashboardSummary, } from '../../../services/api/templates' import { usePageContext } from '../../../shared/pageContext' import { DashboardLayout } from '../../../layouts/DashboardLayout' const initialSummary: TemplateDashboardSummary = { total: 0, published: 0, weeklyUsed: 0, draft: 0, } const FALLBACK_TEMPLATE_IMAGE = '/images/aitori-simulation-result.png' const PAGE_SIZE = 8 export function TemplateListPage() { const { locale, path, copy } = usePageContext() const [items, setItems] = useState([]) const [summary, setSummary] = useState(initialSummary) const [selectedIds, setSelectedIds] = useState([]) const [query, setQuery] = useState('') const [category, setCategory] = useState('') const [status, setStatus] = useState('') const [page, setPage] = useState(1) const [pageSize] = useState(PAGE_SIZE) const [total, setTotal] = useState(0) const [loading, setLoading] = useState(false) const pageCopy = copy.templateListPage useEffect(() => { getTemplateDashboardSummary().then(setSummary).catch(() => setSummary(initialSummary)) }, []) useEffect(() => { let cancelled = false const load = async () => { setLoading(true) try { const result = await listTemplates({ q: query.trim(), category, status, page, pageSize, }) if (cancelled) return setItems(result.items) setTotal(result.total) setSelectedIds((current) => current.filter((id) => result.items.some((item) => item.id === id))) } catch { if (cancelled) return setItems([]) setTotal(0) setSelectedIds([]) } finally { if (!cancelled) { setLoading(false) } } } void load() return () => { cancelled = true } }, [category, page, pageSize, query, status]) const toggleSelect = (templateId: string) => { setSelectedIds((current) => current.includes(templateId) ? current.filter((id) => id !== templateId) : [...current, templateId], ) } const allCurrentPageSelected = items.length > 0 && items.every((item) => selectedIds.includes(item.id)) const handleToggleSelectAllCurrentPage = () => { const currentPageIds = items.map((item) => item.id) setSelectedIds((current) => { if (allCurrentPageSelected) { return current.filter((id) => !currentPageIds.includes(id)) } return Array.from(new Set([...current, ...currentPageIds])) }) } const handleDelete = async (templateId: string) => { const template = items.find((item) => item.id === templateId) if (!window.confirm(`确认删除模板「${template?.name || ''}」吗?`)) return await deleteTemplate(templateId) setItems((current) => current.filter((item) => item.id !== templateId)) setSelectedIds((current) => current.filter((id) => id !== templateId)) setTotal((current) => Math.max(0, current - 1)) } const handleBulkDelete = async () => { if (selectedIds.length === 0) return if (!window.confirm(`确认删除已选中的 ${selectedIds.length} 个模板吗?`)) return const deletingIds = [...selectedIds] await deleteTemplates(deletingIds) setItems((current) => current.filter((item) => !deletingIds.includes(item.id))) setSelectedIds([]) setTotal((current) => Math.max(0, current - deletingIds.length)) } const getGenerationBadge = (template: TemplateEntity) => { if (template.makeTaskStatus === 'WAIT' || template.generationStatus === 'pending') { return pageCopy.statusBadgeMap?.pending || '排队中' } if ( template.makeTaskStatus === 'PROCESSING' || template.makeTaskStatus === 'STEP1_SUCCESS' || template.generationStatus === 'processing' ) { return pageCopy.statusBadgeMap?.processing || '生成中' } if (template.makeTaskStatus === 'FAILED' || template.generationStatus === 'failed') { return pageCopy.statusBadgeMap?.failed || '生成失败' } return null } const getTemplateTarget = (template: TemplateEntity) => { if (template.makeTaskId) { return path(`/templates/tasks/${template.makeTaskId}`) } return null } const getCardImage = (template: TemplateEntity) => { const isProcessing = template.makeTaskStatus === 'WAIT' || template.makeTaskStatus === 'PROCESSING' || template.makeTaskStatus === 'STEP1_SUCCESS' || template.generationStatus === 'pending' || template.generationStatus === 'processing' if (isProcessing) { return template.referenceImage || template.image || FALLBACK_TEMPLATE_IMAGE } return template.image || FALLBACK_TEMPLATE_IMAGE } const totalPages = Math.max(1, Math.ceil(total / pageSize)) const pages = Array.from({ length: totalPages }, (_, index) => index + 1) return ( {`Aitori Stylist | ${pageCopy.title}`}
{pageCopy.badge}

{pageCopy.title}

{pageCopy.lead}

{pageCopy.newTemplate}
{ setQuery(event.target.value) setPage(1) }} />
{summary.total}{pageCopy.stats.total}
{summary.published}{pageCopy.stats.published}
{summary.weeklyUsed}{pageCopy.stats.weeklyUsed}
{summary.draft}{pageCopy.stats.draft}
{pageCopy.resultBadge}

{pageCopy.resultTitle.replace('{{count}}', String(total))}

{pageCopy.sortRecent}
{items.map((template) => { const target = getTemplateTarget(template) const generationBadge = getGenerationBadge(template) const cardContent = ( <>
{target ? ( {template.name} ) : (
{template.name}
)}
toggleSelect(template.id)} className="template-card-checkbox" />
{generationBadge ? (
{generationBadge}
) : null}
{pageCopy.categoryMap[template.category as keyof typeof pageCopy.categoryMap] || template.category} {pageCopy.statusMap[template.status as keyof typeof pageCopy.statusMap] || template.status}
{target ? (

{template.name}

) : (

{template.name}

)}

{template.description}

{pageCopy.usedCount.replace('{{count}}', String(template.usedCount))}
) return (
{cardContent}
) })}
{!loading && items.length === 0 ?

{copy.common.empty}

: null}
{pages.map((pageNumber) => ( ))}
) }