|
|
@@ -1,78 +1,118 @@
|
|
|
import { useEffect, useState } from 'react'
|
|
|
import { Link, useParams } from 'react-router-dom'
|
|
|
import { Helmet } from 'react-helmet-async'
|
|
|
-import type { TemplateDetailStats, TemplateEntity } from '../../../services/api/contracts'
|
|
|
-import { getTemplateById, getTemplateStats, getTemplateUsageResults, updateTemplate, type TemplateUsageResult } from '../../../services/api/templates'
|
|
|
+import type { TemplateDetailStats } from '../../../services/api/contracts'
|
|
|
+import {
|
|
|
+ getTemplateBasicInfo,
|
|
|
+ getTemplateStats,
|
|
|
+ getTemplateUsageResults,
|
|
|
+ type TemplateBasicInfo,
|
|
|
+ type TemplateUsageResult,
|
|
|
+} from '../../../services/api/templates'
|
|
|
import { usePageContext } from '../../../shared/pageContext'
|
|
|
import { DashboardLayout } from '../../../layouts/DashboardLayout'
|
|
|
|
|
|
-function getAssetLabelFromUrl(url?: string) {
|
|
|
- if (!url) return '-'
|
|
|
- const lastSegment = url.split('/').pop()
|
|
|
- return lastSegment || url
|
|
|
+const FALLBACK_PAGE_COPY = {
|
|
|
+ title: '发型模板详情',
|
|
|
+ description: '查看模板封面、基本信息与使用情况。',
|
|
|
+ back: '返回发型模板列表',
|
|
|
+ badge: '模板详情',
|
|
|
+ lead: '确认模板封面、基础信息和顾客使用结果。',
|
|
|
+ preview: {
|
|
|
+ badge: '预览',
|
|
|
+ title: '模板预览',
|
|
|
+ imageAlt: '模板封面',
|
|
|
+ downloadBundle: '下载模板包',
|
|
|
+ bundleUnavailable: '模板包尚未生成',
|
|
|
+ },
|
|
|
+ info: {
|
|
|
+ badge: '模板信息',
|
|
|
+ title: '发型模板基本信息',
|
|
|
+ name: '模板名称',
|
|
|
+ status: '状态',
|
|
|
+ category: '分类',
|
|
|
+ tags: '标签',
|
|
|
+ description: '说明',
|
|
|
+ },
|
|
|
+ stats: {
|
|
|
+ customers: '使用顾客数',
|
|
|
+ saveRate: '提案后保存率',
|
|
|
+ monthlyGenerated: '本月生成次数',
|
|
|
+ },
|
|
|
+ results: {
|
|
|
+ badge: '顾客生成结果',
|
|
|
+ title: '使用该模板的顾客',
|
|
|
+ action: '查看全部',
|
|
|
+ empty: '暂无顾客生成结果',
|
|
|
+ },
|
|
|
+ states: {
|
|
|
+ loading: '正在加载模板详情...',
|
|
|
+ notFound: '模板不存在或已删除',
|
|
|
+ },
|
|
|
+ statusOptions: {
|
|
|
+ DRAFT: '草稿',
|
|
|
+ PUBLIC: '公开中',
|
|
|
+ },
|
|
|
+ categoryOptions: {
|
|
|
+ LONG: '长发',
|
|
|
+ MEDIUM: '中长发',
|
|
|
+ SHORT: '短发',
|
|
|
+ DYE: '染发',
|
|
|
+ },
|
|
|
+} as const
|
|
|
+
|
|
|
+function renderValue(value?: string) {
|
|
|
+ return value && value.trim() ? value : '-'
|
|
|
}
|
|
|
|
|
|
export function TemplateDetailPage() {
|
|
|
const { locale, path, copy } = usePageContext()
|
|
|
- const params = useParams()
|
|
|
- const [template, setTemplate] = useState<TemplateEntity | null>(null)
|
|
|
+ const params = useParams<{ templateId: string }>()
|
|
|
+ const pageCopy = copy.templateDetailPage ?? FALLBACK_PAGE_COPY
|
|
|
+ const [template, setTemplate] = useState<TemplateBasicInfo | null>(null)
|
|
|
const [loading, setLoading] = useState(true)
|
|
|
const [notFound, setNotFound] = useState(false)
|
|
|
- const [name, setName] = useState('')
|
|
|
- const [status, setStatus] = useState('Draft')
|
|
|
- const [category, setCategory] = useState('Long')
|
|
|
- const [tags, setTags] = useState('')
|
|
|
- const [description, setDescription] = useState('')
|
|
|
const [usageResults, setUsageResults] = useState<TemplateUsageResult[]>([])
|
|
|
const [stats, setStats] = useState<TemplateDetailStats>({
|
|
|
customerCount: 0,
|
|
|
saveRate: 0,
|
|
|
monthlyGeneratedCount: 0,
|
|
|
})
|
|
|
- const [saving, setSaving] = useState(false)
|
|
|
- const [error, setError] = useState('')
|
|
|
|
|
|
- const pageCopy = copy.templateDetailPage
|
|
|
useEffect(() => {
|
|
|
let cancelled = false
|
|
|
- let timer: number | undefined
|
|
|
|
|
|
- const loadTemplate = async (showLoading: boolean) => {
|
|
|
- if (showLoading) {
|
|
|
- setLoading(true)
|
|
|
+ const load = async () => {
|
|
|
+ if (!params.templateId) {
|
|
|
+ setNotFound(true)
|
|
|
+ setLoading(false)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
+ setLoading(true)
|
|
|
setNotFound(false)
|
|
|
|
|
|
try {
|
|
|
- const item = await getTemplateById({ templateId: params.templateId })
|
|
|
+ const [templateData, statsData, usageData] = await Promise.all([
|
|
|
+ getTemplateBasicInfo(params.templateId),
|
|
|
+ getTemplateStats(params.templateId).catch(() => ({
|
|
|
+ customerCount: 0,
|
|
|
+ saveRate: 0,
|
|
|
+ monthlyGeneratedCount: 0,
|
|
|
+ })),
|
|
|
+ getTemplateUsageResults(params.templateId).catch(() => []),
|
|
|
+ ])
|
|
|
+
|
|
|
if (cancelled) return
|
|
|
|
|
|
- if (!item) {
|
|
|
+ setTemplate(templateData)
|
|
|
+ setStats(statsData)
|
|
|
+ setUsageResults(usageData)
|
|
|
+ } catch {
|
|
|
+ if (!cancelled) {
|
|
|
setTemplate(null)
|
|
|
setNotFound(true)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- setTemplate(item)
|
|
|
- setName(item.name)
|
|
|
- setStatus(item.status)
|
|
|
- setCategory(item.category)
|
|
|
- setTags((item.tags || []).join(', '))
|
|
|
- setDescription(item.description)
|
|
|
-
|
|
|
- if (
|
|
|
- item.generationStatus === 'pending' ||
|
|
|
- item.generationStatus === 'processing'
|
|
|
- ) {
|
|
|
- timer = window.setTimeout(() => {
|
|
|
- void loadTemplate(false)
|
|
|
- }, 1500)
|
|
|
}
|
|
|
- } catch {
|
|
|
- if (cancelled) return
|
|
|
- setTemplate(null)
|
|
|
- setNotFound(true)
|
|
|
} finally {
|
|
|
if (!cancelled) {
|
|
|
setLoading(false)
|
|
|
@@ -80,56 +120,20 @@ export function TemplateDetailPage() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- void loadTemplate(true)
|
|
|
-
|
|
|
- if (params.templateId) {
|
|
|
- getTemplateUsageResults(params.templateId).then(setUsageResults).catch(() => setUsageResults([]))
|
|
|
- getTemplateStats(params.templateId)
|
|
|
- .then(setStats)
|
|
|
- .catch(() =>
|
|
|
- setStats({
|
|
|
- customerCount: 0,
|
|
|
- saveRate: 0,
|
|
|
- monthlyGeneratedCount: 0,
|
|
|
- }),
|
|
|
- )
|
|
|
- }
|
|
|
+ void load()
|
|
|
|
|
|
return () => {
|
|
|
cancelled = true
|
|
|
- if (timer) {
|
|
|
- window.clearTimeout(timer)
|
|
|
- }
|
|
|
}
|
|
|
}, [params.templateId])
|
|
|
|
|
|
- const handleSave = async () => {
|
|
|
- if (!template || saving) return
|
|
|
- setError('')
|
|
|
- setSaving(true)
|
|
|
- try {
|
|
|
- const updated = await updateTemplate(template.id, {
|
|
|
- name,
|
|
|
- status,
|
|
|
- category,
|
|
|
- description,
|
|
|
- tags: tags.split(',').map((tag) => tag.trim()).filter(Boolean),
|
|
|
- })
|
|
|
- setTemplate(updated)
|
|
|
- } catch (err) {
|
|
|
- setError(err instanceof Error ? err.message : '保存失败')
|
|
|
- } finally {
|
|
|
- setSaving(false)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
if (loading) {
|
|
|
return (
|
|
|
<DashboardLayout>
|
|
|
- <section className="pt-0">
|
|
|
+ <section className="stylist-workbench-top ipad-top-space-margin mb-2">
|
|
|
<div className="container">
|
|
|
<p className="fs-14 text-medium-gray mt-4" role="status">
|
|
|
- {pageCopy.states?.loading || '正在加载模板详情...'}
|
|
|
+ {pageCopy.states.loading}
|
|
|
</p>
|
|
|
</div>
|
|
|
</section>
|
|
|
@@ -140,10 +144,10 @@ export function TemplateDetailPage() {
|
|
|
if (notFound || !template) {
|
|
|
return (
|
|
|
<DashboardLayout>
|
|
|
- <section className="pt-0">
|
|
|
+ <section className="stylist-workbench-top ipad-top-space-margin mb-2">
|
|
|
<div className="container">
|
|
|
<p className="fs-14 text-red mt-4" role="alert">
|
|
|
- {pageCopy.states?.notFound || '模板不存在或已删除'}
|
|
|
+ {pageCopy.states.notFound}
|
|
|
</p>
|
|
|
</div>
|
|
|
</section>
|
|
|
@@ -161,14 +165,25 @@ export function TemplateDetailPage() {
|
|
|
<section className="stylist-workbench-top ipad-top-space-margin mb-2">
|
|
|
<div className="container">
|
|
|
<div className="d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
|
|
- <Link to={path('/templates')} className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"><i className="feather icon-feather-arrow-left me-6px"></i>{pageCopy.back}</Link>
|
|
|
- <div className="d-flex flex-wrap gap-2">
|
|
|
- <Link to={path('/templates')} className="btn btn-large btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{copy.common.cancel}</Link>
|
|
|
- <button type="button" onClick={handleSave} disabled={saving} className="btn btn-large btn-rounded btn-base-color btn-box-shadow fw-700"><i className="feather icon-feather-save me-10px"></i>{copy.common.save}</button>
|
|
|
- </div>
|
|
|
+ <Link
|
|
|
+ to={path('/templates')}
|
|
|
+ className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"
|
|
|
+ >
|
|
|
+ <i className="feather icon-feather-arrow-left me-6px"></i>
|
|
|
+ {pageCopy.back}
|
|
|
+ </Link>
|
|
|
+ <Link
|
|
|
+ to={path(`/templates/${template.id}/edit`)}
|
|
|
+ className="btn btn-large btn-rounded btn-base-color btn-box-shadow fw-700"
|
|
|
+ >
|
|
|
+ <i className="feather icon-feather-edit me-10px"></i>
|
|
|
+ {copy.common.edit}
|
|
|
+ </Link>
|
|
|
</div>
|
|
|
<div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px">
|
|
|
- <span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">{pageCopy.badge}</span>
|
|
|
+ <span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">
|
|
|
+ {pageCopy.badge}
|
|
|
+ </span>
|
|
|
<h1 className="fw-700 text-dark-gray ls-minus-1px mb-10px">{pageCopy.title}</h1>
|
|
|
<p className="fs-18 lh-30 mb-0">{pageCopy.lead}</p>
|
|
|
</div>
|
|
|
@@ -177,86 +192,150 @@ export function TemplateDetailPage() {
|
|
|
|
|
|
<section className="pt-0">
|
|
|
<div className="container">
|
|
|
- {error ? <p className="fs-14 text-red mb-3" role="alert">{error}</p> : null}
|
|
|
- <form className="customer-edit-form">
|
|
|
- <div className="row g-4">
|
|
|
- <div className="col-xl-5 col-lg-6">
|
|
|
- <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
- <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-25px">
|
|
|
- <div><span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.preview.badge}</span><h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.preview.title}</h2></div>
|
|
|
- <span className="template-status-badge">{pageCopy.statusOptions[status as keyof typeof pageCopy.statusOptions] || status}</span>
|
|
|
- </div>
|
|
|
- <div className="template-detail-preview"><img src={template.image || '/images/aitori-simulation-result.png'} alt={pageCopy.preview.imageAlt} /></div>
|
|
|
- <div className="template-detail-summary mt-20px">
|
|
|
- <div><span>{pageCopy.preview.generationStatus}</span><strong>{template.generationStatus || pageCopy.preview.pending}</strong></div>
|
|
|
- <div><span>{pageCopy.preview.progress}</span><strong>{template.generationProgressMessage || pageCopy.preview.progressFallback}</strong></div>
|
|
|
- <div><span>{pageCopy.preview.createdAt}</span><strong>{getAssetLabelFromUrl(template.modelImageUrl)}</strong></div>
|
|
|
- </div>
|
|
|
- <div className="d-flex flex-wrap gap-2 mt-20px">
|
|
|
- {template.generationStatus === 'succeeded' && template.templateUrl ? (
|
|
|
- <a
|
|
|
- href={template.templateUrl}
|
|
|
- target="_blank"
|
|
|
- rel="noreferrer"
|
|
|
- className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"
|
|
|
- >
|
|
|
- {pageCopy.preview.downloadBundle}
|
|
|
- </a>
|
|
|
- ) : (
|
|
|
- <span className="fs-14 text-medium-gray">{pageCopy.preview.bundleUnavailable}</span>
|
|
|
- )}
|
|
|
+ <div className="row g-4">
|
|
|
+ <div className="col-xl-5 col-lg-6">
|
|
|
+ <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
+ <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-25px">
|
|
|
+ <div>
|
|
|
+ <span className="fs-14 fw-700 text-base-color text-uppercase">
|
|
|
+ {pageCopy.preview.badge}
|
|
|
+ </span>
|
|
|
+ <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.preview.title}</h2>
|
|
|
</div>
|
|
|
+ <span className="template-status-badge">
|
|
|
+ {pageCopy.statusOptions[template.status] ?? template.status}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="template-detail-preview">
|
|
|
+ <img
|
|
|
+ src={template.coverUrl || '/images/aitori-simulation-result.png'}
|
|
|
+ alt={pageCopy.preview.imageAlt}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="d-flex flex-wrap gap-2 mt-20px">
|
|
|
+ {template.templateUrl ? (
|
|
|
+ <a
|
|
|
+ href={template.templateUrl}
|
|
|
+ target="_blank"
|
|
|
+ rel="noreferrer"
|
|
|
+ className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"
|
|
|
+ >
|
|
|
+ {pageCopy.preview.downloadBundle}
|
|
|
+ </a>
|
|
|
+ ) : (
|
|
|
+ <span className="fs-14 text-medium-gray">
|
|
|
+ {pageCopy.preview.bundleUnavailable}
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div className="col-xl-7 col-lg-6">
|
|
|
- <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
- <span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.info.badge}</span>
|
|
|
- <h2 className="fw-700 text-dark-gray fs-26 mb-25px">{pageCopy.info.title}</h2>
|
|
|
- <div className="row g-3">
|
|
|
- <div className="col-md-7"><label htmlFor="template-name-detail" className="customer-edit-label">{pageCopy.info.name} <span className="text-base-color">*</span></label><input id="template-name-detail" type="text" className="form-control customer-edit-control" value={name} onChange={(event) => setName(event.target.value)} required /></div>
|
|
|
- <div className="col-md-5"><label htmlFor="template-visibility-detail" className="customer-edit-label">{pageCopy.info.status}</label><select id="template-visibility-detail" className="form-select customer-edit-control" value={status} onChange={(event) => setStatus(event.target.value)}><option value="Draft">{pageCopy.statusOptions.Draft}</option><option value="Published">{pageCopy.statusOptions.Published}</option></select></div>
|
|
|
- <div className="col-md-6"><label htmlFor="template-category-detail" className="customer-edit-label">{pageCopy.info.category}</label><select id="template-category-detail" className="form-select customer-edit-control" value={category} onChange={(event) => setCategory(event.target.value)}><option value="Long">{pageCopy.categoryOptions.Long}</option><option value="Medium">{pageCopy.categoryOptions.Medium}</option><option value="Short">{pageCopy.categoryOptions.Short}</option><option value="Color">{pageCopy.categoryOptions.Color}</option></select></div>
|
|
|
- <div className="col-md-6"><label htmlFor="template-tags-detail" className="customer-edit-label">{pageCopy.info.tags}</label><input id="template-tags-detail" type="text" className="form-control customer-edit-control" value={tags} onChange={(event) => setTags(event.target.value)} /></div>
|
|
|
- <div className="col-12"><label htmlFor="template-description-detail" className="customer-edit-label">{pageCopy.info.description}</label><textarea id="template-description-detail" className="form-control customer-edit-control" rows={6} value={description} onChange={(event) => setDescription(event.target.value)}></textarea></div>
|
|
|
+ <div className="col-xl-7 col-lg-6">
|
|
|
+ <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100">
|
|
|
+ <span className="fs-14 fw-700 text-base-color text-uppercase">
|
|
|
+ {pageCopy.info.badge}
|
|
|
+ </span>
|
|
|
+ <h2 className="fw-700 text-dark-gray fs-26 mb-25px">{pageCopy.info.title}</h2>
|
|
|
+ <div className="row g-3">
|
|
|
+ <div className="col-md-7">
|
|
|
+ <span className="customer-edit-label">{pageCopy.info.name}</span>
|
|
|
+ <div className="form-control customer-edit-control d-flex align-items-center">
|
|
|
+ {renderValue(template.templateName)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="col-md-5">
|
|
|
+ <span className="customer-edit-label">{pageCopy.info.status}</span>
|
|
|
+ <div className="form-control customer-edit-control d-flex align-items-center">
|
|
|
+ {pageCopy.statusOptions[template.status] ?? template.status}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="col-md-6">
|
|
|
+ <span className="customer-edit-label">{pageCopy.info.category}</span>
|
|
|
+ <div className="form-control customer-edit-control d-flex align-items-center">
|
|
|
+ {pageCopy.categoryOptions[template.category] ?? template.category}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="col-md-6">
|
|
|
+ <span className="customer-edit-label">{pageCopy.info.tags}</span>
|
|
|
+ <div className="form-control customer-edit-control d-flex align-items-center">
|
|
|
+ {renderValue(template.tags.join(', '))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="col-12">
|
|
|
+ <span className="customer-edit-label">{pageCopy.info.description}</span>
|
|
|
+ <div className="form-control customer-edit-control min-height-180px">
|
|
|
+ {renderValue(template.description)}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div className="row g-4 mt-1">
|
|
|
- <div className="col-lg-4"><div className="workbench-stat bg-white border-radius-8px p-20px h-100"><span className="d-block fs-26 fw-800 text-dark-gray">{stats.customerCount}</span><span className="fs-14">{pageCopy.stats.customers}</span></div></div>
|
|
|
- <div className="col-lg-4"><div className="workbench-stat bg-white border-radius-8px p-20px h-100"><span className="d-block fs-26 fw-800 text-dark-gray">{stats.saveRate}%</span><span className="fs-14">{pageCopy.stats.saveRate}</span></div></div>
|
|
|
- <div className="col-lg-4"><div className="workbench-stat bg-white border-radius-8px p-20px h-100"><span className="d-block fs-26 fw-800 text-dark-gray">{stats.monthlyGeneratedCount}</span><span className="fs-14">{pageCopy.stats.monthlyGenerated}</span></div></div>
|
|
|
+ <div className="row g-4 mt-1">
|
|
|
+ <div className="col-lg-4">
|
|
|
+ <div className="workbench-stat bg-white border-radius-8px p-20px h-100">
|
|
|
+ <span className="d-block fs-26 fw-800 text-dark-gray">{stats.customerCount}</span>
|
|
|
+ <span className="fs-14">{pageCopy.stats.customers}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
-
|
|
|
- <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px mt-4">
|
|
|
- <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-25px">
|
|
|
- <div><span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.results.badge}</span><h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.results.title}</h2></div>
|
|
|
- <a href="#" className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{pageCopy.results.action}</a>
|
|
|
+ <div className="col-lg-4">
|
|
|
+ <div className="workbench-stat bg-white border-radius-8px p-20px h-100">
|
|
|
+ <span className="d-block fs-26 fw-800 text-dark-gray">{stats.saveRate}%</span>
|
|
|
+ <span className="fs-14">{pageCopy.stats.saveRate}</span>
|
|
|
</div>
|
|
|
-
|
|
|
- {usageResults.length === 0 ? (
|
|
|
- <p className="fs-14 text-medium-gray" role="status">
|
|
|
- {pageCopy.results.empty}
|
|
|
- </p>
|
|
|
- ) : (
|
|
|
- <div className="template-result-grid">
|
|
|
- {usageResults.map((item, index) => (
|
|
|
- <article key={`${item.date}-${index}`} className="template-result-card"><div className="template-result-images">{item.beforeImage ? <img src={item.beforeImage} alt="before" /> : null}{item.afterImage ? <img src={item.afterImage} alt="after" /> : null}</div><div className="template-result-body"><div><span className="template-status-badge">{item.status}</span><span className="fs-13 text-medium-gray">{item.date}</span></div><p>{item.note}</p></div></article>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- )}
|
|
|
</div>
|
|
|
+ <div className="col-lg-4">
|
|
|
+ <div className="workbench-stat bg-white border-radius-8px p-20px h-100">
|
|
|
+ <span className="d-block fs-26 fw-800 text-dark-gray">
|
|
|
+ {stats.monthlyGeneratedCount}
|
|
|
+ </span>
|
|
|
+ <span className="fs-14">{pageCopy.stats.monthlyGenerated}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div className="customer-edit-actions workbench-panel border-radius-8px p-25px mt-4">
|
|
|
- <div><strong>{pageCopy.submitTitle}</strong><span>{pageCopy.submitLead}</span></div>
|
|
|
- <div className="d-flex flex-wrap gap-2">
|
|
|
- <Link to={path('/templates')} className="btn btn-large btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700">{copy.common.cancel}</Link>
|
|
|
- <button type="button" onClick={handleSave} disabled={saving} className="btn btn-large btn-rounded btn-base-color btn-box-shadow fw-700"><i className="feather icon-feather-save me-10px"></i>{copy.common.save}</button>
|
|
|
+ <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px mt-4">
|
|
|
+ <div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-25px">
|
|
|
+ <div>
|
|
|
+ <span className="fs-14 fw-700 text-base-color text-uppercase">
|
|
|
+ {pageCopy.results.badge}
|
|
|
+ </span>
|
|
|
+ <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.results.title}</h2>
|
|
|
</div>
|
|
|
+ <a
|
|
|
+ href="#"
|
|
|
+ className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700"
|
|
|
+ >
|
|
|
+ {pageCopy.results.action}
|
|
|
+ </a>
|
|
|
</div>
|
|
|
- </form>
|
|
|
+
|
|
|
+ {usageResults.length === 0 ? (
|
|
|
+ <p className="fs-14 text-medium-gray" role="status">
|
|
|
+ {pageCopy.results.empty}
|
|
|
+ </p>
|
|
|
+ ) : (
|
|
|
+ <div className="template-result-grid">
|
|
|
+ {usageResults.map((item, index) => (
|
|
|
+ <article key={`${item.date}-${index}`} className="template-result-card">
|
|
|
+ <div className="template-result-images">
|
|
|
+ {item.beforeImage ? <img src={item.beforeImage} alt="before" /> : null}
|
|
|
+ {item.afterImage ? <img src={item.afterImage} alt="after" /> : null}
|
|
|
+ </div>
|
|
|
+ <div className="template-result-body">
|
|
|
+ <div>
|
|
|
+ <span className="template-status-badge">{item.status}</span>
|
|
|
+ <span className="fs-13 text-medium-gray">{item.date}</span>
|
|
|
+ </div>
|
|
|
+ <p>{item.note}</p>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</section>
|
|
|
</DashboardLayout>
|