74 lines
5.9 KiB
TypeScript

'use client'
import { Trash2 } from 'lucide-react'
import {
SAMPLE_PROGRESS_STATUS_LABELS,
SAMPLE_PROGRESS_STATUSES,
type SampleProgressSupplierInput,
} from '@/lib/sample-progress'
const inputClass = 'h-10 w-full rounded-[5px] border border-black/[0.12] bg-white px-3 text-sm text-[#252b2d] outline-none transition focus:border-[#26756b] focus:ring-2 focus:ring-[#26756b]/10'
const labelClass = 'space-y-1.5 text-xs font-bold text-[#5f696c]'
function inputDate(value: string | Date) {
const date = new Date(value)
return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)
}
function inputDateTime(value: string | Date | null | undefined) {
if (!value) return ''
const date = new Date(value)
if (Number.isNaN(date.getTime())) return ''
const local = new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000)
return local.toISOString().slice(0, 16)
}
const parseDateInput = (value: string) => new Date(`${value}T12:00:00.000Z`)
type Props = {
index: number
value: SampleProgressSupplierInput
dueDateChanged: boolean
canRemove: boolean
readOnly?: boolean
onChange: (value: SampleProgressSupplierInput) => void
onRemove: () => void
}
export default function SampleProgressSupplierEditor({ index, value, dueDateChanged, canRemove, readOnly = false, onChange, onRemove }: Props) {
const setField = <K extends keyof SampleProgressSupplierInput>(key: K, nextValue: SampleProgressSupplierInput[K]) => {
onChange({ ...value, [key]: nextValue })
}
const fieldLabel = (label: string) => index === 0 ? label : `供应商 ${index + 1} · ${label}`
return <section className="w-full shrink-0 border-t border-black/[0.08] pt-3 first:border-t-0 first:pt-0 md:min-w-[260px] md:flex-1 md:border-l md:border-t-0 md:px-4 md:pt-0 md:first:border-l-0 md:first:pl-0 md:last:pr-0">
<header className="mb-3 flex h-8 items-center justify-between gap-2">
<h4 className="text-xs font-black text-[#252b2d]">供应商 {String.fromCharCode(65 + index)}</h4>
{canRemove && !readOnly && <button type="button" title={`删除供应商 ${String.fromCharCode(65 + index)}`} aria-label={`删除供应商 ${String.fromCharCode(65 + index)}`} onClick={onRemove} className="inline-flex h-8 w-8 items-center justify-center rounded-[4px] text-[#9a4a45] hover:bg-[#fff0ef]"><Trash2 size={15}/></button>}
</header>
<div className="space-y-3">
<TextField label="供应商" ariaLabel={fieldLabel('供应商')} disabled={readOnly} value={value.supplier} onChange={(nextValue) => setField('supplier', nextValue)}/>
<DateTimeField label="通知打样时间" ariaLabel={fieldLabel('通知打样时间')} disabled={readOnly} value={value.sampleNotifiedAt} onChange={(nextValue) => setField('sampleNotifiedAt', nextValue)}/>
<label className={labelClass}>打样交期<input aria-label={fieldLabel('打样交期')} disabled={readOnly} type="date" value={inputDate(value.sampleDueDate)} onChange={(event) => { if (event.target.value) setField('sampleDueDate', parseDateInput(event.target.value)) }} className={inputClass}/></label>
{dueDateChanged && <TextField label="交期变更原因" ariaLabel={fieldLabel('交期变更原因')} disabled={readOnly} value={value.deliveryDateChangeReason || ''} onChange={(nextValue) => setField('deliveryDateChangeReason', nextValue)}/>}
<TextField label="寄出快递单号" ariaLabel={fieldLabel('寄出快递单号')} disabled={readOnly} value={value.courierTrackingNumber || ''} onChange={(nextValue) => setField('courierTrackingNumber', nextValue)}/>
<label className={labelClass}>打样状态<select aria-label={fieldLabel('打样状态')} disabled={readOnly} value={value.status} onChange={(event) => setField('status', event.target.value as SampleProgressSupplierInput['status'])} className={inputClass}>{SAMPLE_PROGRESS_STATUSES.map((status) => <option key={status} value={status}>{SAMPLE_PROGRESS_STATUS_LABELS[status]}</option>)}</select></label>
<MoneyField label="需求成本" ariaLabel={fieldLabel('需求成本')} disabled={readOnly} value={value.requestedCost} onChange={(nextValue) => setField('requestedCost', nextValue)}/>
<MoneyField label="实际成本" ariaLabel={fieldLabel('实际成本')} disabled={readOnly} value={value.actualCost} onChange={(nextValue) => setField('actualCost', nextValue)}/>
<label className={labelClass}>进度说明<textarea aria-label={fieldLabel('进度说明')} disabled={readOnly} rows={4} value={value.progressDescription || ''} onChange={(event) => setField('progressDescription', event.target.value)} className={`${inputClass} h-auto min-h-[92px] resize-y py-2.5`}/></label>
</div>
</section>
}
function TextField({ label, ariaLabel, value, disabled = false, onChange }: { label: string; ariaLabel: string; value: string; disabled?: boolean; onChange: (value: string) => void }) {
return <label className={labelClass}>{label}<input aria-label={ariaLabel} disabled={disabled} value={value} onChange={(event) => onChange(event.target.value)} className={inputClass}/></label>
}
function DateTimeField({ label, ariaLabel, value, disabled = false, onChange }: { label: string; ariaLabel: string; value: Date | null | undefined; disabled?: boolean; onChange: (value: Date | null) => void }) {
return <label className={labelClass}>{label}<input aria-label={ariaLabel} disabled={disabled} type="datetime-local" value={inputDateTime(value)} onChange={(event) => onChange(event.target.value ? new Date(event.target.value) : null)} className={inputClass}/></label>
}
function MoneyField({ label, ariaLabel, value, disabled = false, onChange }: { label: string; ariaLabel: string; value: number | null | undefined; disabled?: boolean; onChange: (value: number | null) => void }) {
return <label className={labelClass}>{label}<input aria-label={ariaLabel} disabled={disabled} min="0" step="0.01" type="number" value={value ?? ''} onChange={(event) => onChange(event.target.value ? Number(event.target.value) : null)} className={inputClass}/></label>
}