gongxiangp2/components/department-dashboard/MonthlyLaunchesInsights.tsx
2026-09-01 12:09:08 +08:00

139 lines
7.0 KiB
TypeScript
Raw Permalink 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 { useMemo, useState } from 'react'
import {
CheckCircle2,
ChevronDown,
Clock3,
PackageOpen,
} from 'lucide-react'
import type {
MonthlyLaunchBrandSummary,
MonthlyLaunchInsights,
MonthlyLaunchWorkflowStatus,
} from '@/lib/ding-workflow-sync'
import MonthlyLaunchPeriodFilter from './MonthlyLaunchPeriodFilter'
import { formatMonthlyLaunchPeriod } from './monthly-launch-period'
import WorkflowDetailTable from './WorkflowDetailTable'
function statusLabel(status: MonthlyLaunchWorkflowStatus, period: string) {
return status === 'completed' ? `${period}已完结新品` : '当前进行中新品'
}
export default function MonthlyLaunchesInsights({ insights }: { insights: MonthlyLaunchInsights }) {
const [selectedBrand, setSelectedBrand] = useState(insights.brands[0]?.brand || '亿家达')
const [selectedStatus, setSelectedStatus] = useState<MonthlyLaunchWorkflowStatus>('completed')
const [visibleCount, setVisibleCount] = useState(30)
const period = formatMonthlyLaunchPeriod(insights.summary.monthStart, insights.summary.monthEnd)
const monthOptions = insights.availableMonths || []
const selectedItems = useMemo(() => insights.items.filter((item) => (
item.brand === selectedBrand && item.launchStatus === selectedStatus
)), [insights.items, selectedBrand, selectedStatus])
const selectGroup = (brand: MonthlyLaunchBrandSummary['brand'], status: MonthlyLaunchWorkflowStatus) => {
setSelectedBrand(brand)
setSelectedStatus(status)
setVisibleCount(30)
}
return (
<section aria-labelledby="monthly-launch-title" className="mt-10">
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div>
<h2 id="monthly-launch-title" className="text-xl font-bold tracking-[-0.02em] sm:text-2xl">六品牌{period}新品流程</h2>
<p className="mt-1 max-w-3xl text-sm leading-6 text-slate-600">
已完结按所选日期范围内整条美术设计流程的完结时间统计;进行中复用“进行中项目”的当前品牌中心在办美术流程口径。流程自 2026 年 4 月 1 日起计入。
</p>
</div>
<p className="text-sm font-semibold tabular-nums text-slate-600">
{period}已完结 {insights.summary.completed} · 当前进行中 {insights.summary.ongoing}
</p>
</div>
<MonthlyLaunchPeriodFilter summary={insights.summary} options={monthOptions} />
<div className="mt-5 grid gap-3 lg:grid-cols-2" aria-label="六品牌新品流程数量">
{insights.brands.map((brand) => (
<article key={brand.brand} className="min-w-0 overflow-hidden rounded-xl border border-slate-200 bg-white">
<div className="flex items-center justify-between gap-4 border-b border-slate-200 px-5 py-4">
<h3 className="min-w-0 truncate text-lg font-bold text-slate-950">{brand.brand}</h3>
<p className="shrink-0 text-sm font-semibold tabular-nums text-slate-500">合计 {brand.total}</p>
</div>
<div className="grid grid-cols-2 gap-px bg-slate-200">
{([
{ status: 'completed' as const, label: '本月已完结', count: brand.completed, icon: CheckCircle2 },
{ status: 'ongoing' as const, label: '当前进行中', count: brand.ongoing, icon: Clock3 },
]).map((group) => {
const Icon = group.icon
const groupLabel = group.status === 'completed' ? `${period}已完结` : group.label
const selected = selectedBrand === brand.brand && selectedStatus === group.status
return (
<button
key={group.status}
type="button"
aria-pressed={selected}
aria-label={`查看${brand.brand}${groupLabel}明细,共${group.count}条`}
onClick={() => selectGroup(brand.brand, group.status)}
className={`group min-w-0 px-5 py-4 text-left transition-colors focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#5a9ce6] ${selected ? 'bg-[#e5f2ff] text-[#075fab]' : 'bg-white hover:bg-slate-50'}`}
>
<div className="flex items-start justify-between gap-3">
<span className="inline-flex min-w-0 items-center gap-2 text-sm font-bold">
<Icon className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="truncate">{groupLabel}</span>
</span>
<span className="shrink-0 text-2xl font-bold tabular-nums">{group.count}</span>
</div>
</button>
)
})}
</div>
</article>
))}
</div>
<section aria-labelledby="monthly-launch-detail-title" className="mt-6 overflow-hidden rounded-xl border border-slate-200 bg-white">
<div className="flex flex-col gap-2 border-b border-slate-200 px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 id="monthly-launch-detail-title" className="text-lg font-bold text-slate-950">
{selectedBrand} · {statusLabel(selectedStatus, period)}
</h3>
<p className="mt-1 text-sm text-slate-500">明细字段与“进行中项目”一致,完整展示表单信息、当前节点与业务时间。</p>
</div>
<p className="text-sm font-semibold tabular-nums text-slate-600">共 {selectedItems.length} 条</p>
</div>
{selectedItems.length === 0 ? (
<div role="status" className="px-5 py-14 text-center">
<PackageOpen className="mx-auto h-8 w-8 text-slate-400" aria-hidden="true" />
<h4 className="mt-4 font-bold text-slate-950">当前分组暂无流程</h4>
<p className="mt-2 text-sm text-slate-500">切换品牌或流程状态查看其他明细。</p>
</div>
) : (
<>
<div className="p-4 sm:p-5">
<WorkflowDetailTable
items={selectedItems.slice(0, visibleCount)}
tableLabel={`${selectedBrand}${statusLabel(selectedStatus, period)}流程明细横向滚动表格`}
mobileListLabel={`${selectedBrand}${statusLabel(selectedStatus, period)}流程列表`}
/>
</div>
</>
)}
{visibleCount < selectedItems.length && (
<div className="border-t border-slate-200 p-4 text-center">
<button
type="button"
onClick={() => setVisibleCount((count) => count + 30)}
className="inline-flex min-h-10 items-center gap-2 rounded-xl bg-slate-100 px-4 text-sm font-bold text-slate-700 transition-colors hover:bg-slate-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400"
>
展开更多<ChevronDown className="h-4 w-4" aria-hidden="true" />
</button>
</div>
)}
</section>
</section>
)
}