fix(sample-progress): align board with current OA stage
This commit is contained in:
parent
1433428cf7
commit
5318a30346
@ -3308,13 +3308,13 @@ export default function OaBoardClient({
|
||||
dashboard,
|
||||
loadErrorMessage,
|
||||
sampleProgressItems = [],
|
||||
currentSampleStageCount = 0,
|
||||
currentSampleStageOpeningCodes = [],
|
||||
currentUserName = '',
|
||||
}: {
|
||||
dashboard: OaBoardDashboardDTO
|
||||
loadErrorMessage?: string | null
|
||||
sampleProgressItems?: SampleProgressDTO[]
|
||||
currentSampleStageCount?: number
|
||||
currentSampleStageOpeningCodes?: string[]
|
||||
currentUserName?: string
|
||||
}) {
|
||||
const { items: allItems, stageGraph, dailyReport, monthlyReport } = dashboard
|
||||
@ -4475,7 +4475,7 @@ export default function OaBoardClient({
|
||||
) : activeTab === 'brand-planning' ? (
|
||||
<BrandPlanningBoardPanel />
|
||||
) : activeTab === 'sample-progress' ? (
|
||||
<SampleProgressClient initialItems={sampleProgressItems} currentUserName={currentUserName} currentSampleStageCount={currentSampleStageCount} embedded />
|
||||
<SampleProgressClient initialItems={sampleProgressItems} currentUserName={currentUserName} currentSampleStageOpeningCodes={currentSampleStageOpeningCodes} embedded />
|
||||
) : activeTab === 'timeout-analysis' ? (
|
||||
<TimeoutAnalysis
|
||||
items={items}
|
||||
|
||||
@ -130,7 +130,7 @@ export default async function OaBoardPage({
|
||||
: { ...result.dashboard, dailyReport: undefined }
|
||||
await sampleProgressService.syncFromOaDashboard()
|
||||
const sampleProgressItems = await sampleProgressService.listStored()
|
||||
const currentSampleStageCount = await sampleProgressService.countCurrentSampleStage()
|
||||
const currentSampleStageOpeningCodes = await sampleProgressService.listCurrentSampleStageOpeningCodes()
|
||||
|
||||
return (
|
||||
<OaBoardClient
|
||||
@ -138,7 +138,7 @@ export default async function OaBoardPage({
|
||||
dashboard={dashboard}
|
||||
loadErrorMessage={result.loadErrorMessage}
|
||||
sampleProgressItems={sampleProgressItems}
|
||||
currentSampleStageCount={currentSampleStageCount}
|
||||
currentSampleStageOpeningCodes={currentSampleStageOpeningCodes}
|
||||
currentUserName={session?.user.name ?? ''}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -17,10 +17,10 @@ const inputDate = (value: string | Date) => {
|
||||
return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
export default function SampleProgressClient({ initialItems, currentUserName, currentSampleStageCount = 0, embedded = false }: { initialItems: SampleProgressDTO[]; currentUserName: string; currentSampleStageCount?: number; embedded?: boolean }) {
|
||||
export default function SampleProgressClient({ initialItems, currentUserName, currentSampleStageOpeningCodes = [], embedded = false }: { initialItems: SampleProgressDTO[]; currentUserName: string; currentSampleStageOpeningCodes?: string[]; embedded?: boolean }) {
|
||||
const [items, setItems] = useState(initialItems)
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [status, setStatus] = useState('ACTIVE')
|
||||
const [status, setStatus] = useState('CURRENT_STAGE')
|
||||
const [page, setPage] = useState(1)
|
||||
const [editing, setEditing] = useState<SampleProgressDTO | null | 'new'>(null)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@ -29,15 +29,17 @@ export default function SampleProgressClient({ initialItems, currentUserName, cu
|
||||
const [previewImage, setPreviewImage] = useState<SampleProgressDTO | null>(null)
|
||||
const canEdit = (item: SampleProgressDTO) => canEditSampleProgress(currentUserName, item)
|
||||
|
||||
const currentStageCodes = useMemo(() => new Set(currentSampleStageOpeningCodes.map((code) => code.trim().toUpperCase())), [currentSampleStageOpeningCodes])
|
||||
const currentStageItems = useMemo(() => items.filter((item) => currentStageCodes.has(item.openingCode.trim().toUpperCase())), [currentStageCodes, items])
|
||||
const filtered = useMemo(() => items.filter((item) => {
|
||||
const haystack = [item.openingCode, item.brand, item.supplier, item.designer, item.purchaseOwner, item.color, item.material].join(' ').toLowerCase()
|
||||
const statusMatches = status === 'ALL' || (status === 'ACTIVE' ? !hiddenTerminalStatuses.has(item.status) : item.status === status)
|
||||
const statusMatches = status === 'ALL' || (status === 'CURRENT_STAGE' ? currentStageCodes.has(item.openingCode.trim().toUpperCase()) : item.status === status)
|
||||
return (!keyword || haystack.includes(keyword.toLowerCase())) && statusMatches
|
||||
}), [items, keyword, status])
|
||||
}), [currentStageCodes, items, keyword, status])
|
||||
const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE))
|
||||
const rows = filtered.slice((Math.min(page, totalPages) - 1) * PAGE_SIZE, Math.min(page, totalPages) * PAGE_SIZE)
|
||||
const overdueCount = items.filter(isOverdue).length
|
||||
const statusCounts = SAMPLE_PROGRESS_STATUSES.map((value) => ({ value, count: items.filter((item) => item.status === value).length }))
|
||||
const overdueCount = currentStageItems.filter(isOverdue).length
|
||||
const statusCounts = SAMPLE_PROGRESS_STATUSES.map((value) => ({ value, count: currentStageItems.filter((item) => item.status === value).length }))
|
||||
|
||||
async function remove(item: SampleProgressDTO) {
|
||||
if (!window.confirm(`确认删除 ${item.openingCode} 的打样记录?`)) return
|
||||
@ -53,12 +55,13 @@ export default function SampleProgressClient({ initialItems, currentUserName, cu
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="grid border-b border-black/[0.06] bg-white sm:grid-cols-2">
|
||||
<Metric icon={<CalendarClock size={18}/>} label="当前打样节点" value={`${currentSampleStageCount} 款`} tone="plain" />
|
||||
<section className="grid border-b border-black/[0.06] bg-white sm:grid-cols-3">
|
||||
<Metric icon={<CalendarClock size={18}/>} label="当前打样节点" value={`${currentStageItems.length} 款`} tone="plain" />
|
||||
<Metric icon={<AlertTriangle size={18}/>} label="交期已超期" value={`${overdueCount} 款`} tone={overdueCount ? 'danger' : 'plain'} />
|
||||
<Metric icon={<Link2 size={18}/>} label="OA 台账匹配" value={`${currentStageItems.length} / ${currentStageCodes.size}`} tone={currentStageItems.length === currentStageCodes.size ? 'success' : 'danger'} />
|
||||
</section>
|
||||
|
||||
<section className="grid border-b border-black/[0.06] bg-white sm:grid-cols-2 lg:grid-cols-4 2xl:grid-cols-7">
|
||||
<section className="grid grid-cols-3 border-b border-black/[0.06] bg-white md:grid-cols-5 xl:grid-cols-9">
|
||||
{statusCounts.map(({ value, count }) => (
|
||||
<button key={value} type="button" onClick={() => { setStatus(value); setPage(1) }} className={`h-[68px] border-b border-r border-black/[0.05] px-4 text-left transition hover:bg-[#f7f9f8] ${status === value ? 'bg-[#edf4f1]' : ''}`}>
|
||||
<div className="text-xs text-[#788083]">{SAMPLE_PROGRESS_STATUS_LABELS[value]}</div>
|
||||
@ -70,7 +73,7 @@ export default function SampleProgressClient({ initialItems, currentUserName, cu
|
||||
<section className="p-4 sm:p-6">
|
||||
<div className="mb-3 flex flex-col gap-2 md:flex-row">
|
||||
<label className="relative min-w-0 flex-1 md:max-w-[420px]"><Search className="absolute left-3 top-2.5 text-[#8c9496]" size={16}/><input value={keyword} onChange={(e) => { setKeyword(e.target.value); setPage(1) }} placeholder="搜索开款编号、品牌、供应商、设计师或采购负责人" className="h-10 w-full rounded-[6px] border border-black/[0.1] bg-white pl-9 pr-3 text-sm outline-none focus:border-[#26756b]"/></label>
|
||||
<select aria-label="打样状态" value={status} onChange={(e) => { setStatus(e.target.value); setPage(1) }} className="h-10 rounded-[6px] border border-black/[0.1] bg-white px-3 text-sm"><option value="ACTIVE">打样状态</option><option value="ALL">全部打样状态</option>{SAMPLE_PROGRESS_STATUSES.map((value) => <option key={value} value={value}>{SAMPLE_PROGRESS_STATUS_LABELS[value]}</option>)}</select>
|
||||
<select aria-label="打样状态" value={status} onChange={(e) => { setStatus(e.target.value); setPage(1) }} className="h-10 rounded-[6px] border border-black/[0.1] bg-white px-3 text-sm"><option value="CURRENT_STAGE">当前打样节点</option><option value="ALL">全部打样台账({items.length})</option>{SAMPLE_PROGRESS_STATUSES.map((value) => <option key={value} value={value}>{SAMPLE_PROGRESS_STATUS_LABELS[value]}</option>)}</select>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-[6px] border border-black/[0.08] bg-white shadow-[0_8px_24px_rgba(21,31,33,0.05)]">
|
||||
|
||||
@ -6,6 +6,6 @@ export const dynamic = 'force-dynamic'
|
||||
export default async function SampleProgressPage() {
|
||||
const { user } = await requireSession('/login')
|
||||
const items = await sampleProgressService.list()
|
||||
const currentSampleStageCount = await sampleProgressService.countCurrentSampleStage()
|
||||
return <SampleProgressClient initialItems={items} currentUserName={user.name} currentSampleStageCount={currentSampleStageCount} />
|
||||
const currentSampleStageOpeningCodes = await sampleProgressService.listCurrentSampleStageOpeningCodes()
|
||||
return <SampleProgressClient initialItems={items} currentUserName={user.name} currentSampleStageOpeningCodes={currentSampleStageOpeningCodes} />
|
||||
}
|
||||
|
||||
@ -8,4 +8,4 @@ const samples: SampleProgressDTO[] = [
|
||||
{ id: 'preview-3', openingCode: 'XP202607220009', productDevelopmentFlowUrl: 'https://oa.example.com/product-development/process/XP202607220009', orderDate: '2026-07-22T00:00:00.000Z', sampleDueDate: '2026-08-06T00:00:00.000Z', oaReachedAt: '2026-07-23T01:00:00.000Z', anomaly: 'NONE', imageUrl: null, productSize: '1800 x 400 x 450 mm', color: '深灰', material: '实木多层板 + 岩板', sampleQuantity: 1, brand: '索菲亚', supplier: '嘉兴木业', finalSampleFactory: '嘉兴木业', status: 'COMPLETED', designer: '张凯', purchaseOwner: '孙丽', merchandiserOwner: '方敏', progressDescription: '实物审样通过,打样已完结', requestedCost: 2200, actualCost: 2180, followUpOwner: '孙丽', createdBy: null, updatedBy: null, createdAt: now, updatedAt: now },
|
||||
]
|
||||
|
||||
export default function SampleProgressPreviewPage() { return <SampleProgressClient initialItems={samples} currentUserName="荆川" currentSampleStageCount={2} /> }
|
||||
export default function SampleProgressPreviewPage() { return <SampleProgressClient initialItems={samples} currentUserName="荆川" currentSampleStageOpeningCodes={samples.slice(0, 2).map((item) => item.openingCode)} /> }
|
||||
|
||||
@ -213,11 +213,14 @@ export const sampleProgressService = {
|
||||
})
|
||||
await syncFromOaItems(dashboard.items)
|
||||
},
|
||||
async countCurrentSampleStage() {
|
||||
async listCurrentSampleStageOpeningCodes() {
|
||||
const dashboard = await oaBoardService.getDashboard({
|
||||
overallStatus: SAMPLE_PROGRESS_SYNC_FLOW_STATUSES,
|
||||
})
|
||||
return dashboard.items.filter(isCurrentSampleStage).length
|
||||
return dashboard.items
|
||||
.filter(isCurrentSampleStage)
|
||||
.map((item) => (item.formSummary.openingCode || item.openingCode || '').trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
},
|
||||
async listStored() { return (await sampleProgressRepository.list()).map(serialize) },
|
||||
async list() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user