113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
|
||
import {
|
||
DINGTALK_APPROVAL_NO_MAX_LENGTH,
|
||
getDingtalkApprovalNoError,
|
||
} from '@/lib/dingtalk-approval-no'
|
||
|
||
interface TaskReferenceFieldsProps {
|
||
idPrefix: string
|
||
taskNo: string
|
||
onTaskNoChange: (value: string) => void
|
||
dingtalkApprovalNo: string
|
||
onDingtalkApprovalNoChange: (value: string) => void
|
||
taskNoRequired?: boolean
|
||
disabled?: boolean
|
||
}
|
||
|
||
function getTaskNoError(value: string): string | null {
|
||
const normalized = value.trim().toUpperCase()
|
||
if (!normalized) {
|
||
return null
|
||
}
|
||
|
||
return /^TASK\d{12,18}(-\d{2})*$/.test(normalized)
|
||
? null
|
||
: '任务号格式错误'
|
||
}
|
||
|
||
export default function TaskReferenceFields({
|
||
idPrefix,
|
||
taskNo,
|
||
onTaskNoChange,
|
||
dingtalkApprovalNo,
|
||
onDingtalkApprovalNoChange,
|
||
taskNoRequired = false,
|
||
disabled = false,
|
||
}: TaskReferenceFieldsProps) {
|
||
const [taskNoTouched, setTaskNoTouched] = useState(false)
|
||
const [approvalNoTouched, setApprovalNoTouched] = useState(false)
|
||
const taskNoId = `${idPrefix}-task-no`
|
||
const taskNoErrorId = `${taskNoId}-error`
|
||
const approvalNoId = `${idPrefix}-dingtalk-approval-no`
|
||
const approvalNoErrorId = `${approvalNoId}-error`
|
||
const taskNoError = getTaskNoError(taskNo)
|
||
const approvalNoError = getDingtalkApprovalNoError(dingtalkApprovalNo)
|
||
const visibleTaskNoError = taskNoTouched ? taskNoError : null
|
||
const visibleApprovalNoError = approvalNoTouched ? approvalNoError : null
|
||
const inputClassName =
|
||
'w-full rounded-apple border border-gray-300 px-4 py-2 outline-none focus:border-gray-900 focus:ring-2 focus:ring-gray-200 disabled:cursor-not-allowed disabled:opacity-60 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-gray-400 dark:focus:ring-gray-600'
|
||
|
||
return (
|
||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||
<div className="min-w-0">
|
||
<label
|
||
htmlFor={taskNoId}
|
||
className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||
>
|
||
任务号{taskNoRequired ? ' *' : ''}
|
||
</label>
|
||
<input
|
||
id={taskNoId}
|
||
type="text"
|
||
value={taskNo}
|
||
onChange={(event) => onTaskNoChange(event.target.value.toUpperCase())}
|
||
onBlur={() => setTaskNoTouched(true)}
|
||
placeholder="例如:TASK202601030184"
|
||
className={inputClassName}
|
||
disabled={disabled}
|
||
required={taskNoRequired}
|
||
aria-invalid={Boolean(visibleTaskNoError)}
|
||
aria-describedby={visibleTaskNoError ? taskNoErrorId : undefined}
|
||
/>
|
||
{visibleTaskNoError && (
|
||
<p id={taskNoErrorId} className="mt-1 text-sm text-red-600 dark:text-red-400">
|
||
{visibleTaskNoError}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="min-w-0">
|
||
<label
|
||
htmlFor={approvalNoId}
|
||
className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||
>
|
||
钉钉审批编号
|
||
</label>
|
||
<input
|
||
id={approvalNoId}
|
||
type="text"
|
||
inputMode="numeric"
|
||
autoComplete="off"
|
||
maxLength={DINGTALK_APPROVAL_NO_MAX_LENGTH}
|
||
value={dingtalkApprovalNo}
|
||
onChange={(event) => onDingtalkApprovalNoChange(event.target.value)}
|
||
onBlur={() => setApprovalNoTouched(true)}
|
||
placeholder="例如:202508211445000157693"
|
||
className={inputClassName}
|
||
disabled={disabled}
|
||
aria-invalid={Boolean(visibleApprovalNoError)}
|
||
aria-describedby={visibleApprovalNoError ? approvalNoErrorId : undefined}
|
||
/>
|
||
{visibleApprovalNoError && (
|
||
<p id={approvalNoErrorId} className="mt-1 text-sm text-red-600 dark:text-red-400">
|
||
{visibleApprovalNoError}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|