Шаг 2.2 (часть 2) плана production-готовности: - 14 файлов в components/task-detail/ (типы, вкладки, диалоги, шапка, таб-бар, мобильный accordion, хук условий полей) - посимвольная сверка: все несовпадения объяснены (export-префиксы/импорты/пропсы) - check/lint/build чисто, vitest 104/104
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
||
import { FieldValueRow } from '@/components/task-fields/FieldValueRow';
|
||
import type { FormFieldData, TaskData, TaskFieldValueData } from '@/components/task-fields/FieldValueRow';
|
||
import { useOfflineSyncContext } from '@/contexts/OfflineSyncContext';
|
||
import type { UserSummary } from './types';
|
||
|
||
interface TabFieldsContentProps {
|
||
tabId?: number;
|
||
hasTabs: boolean;
|
||
fields: FormFieldData[];
|
||
fieldValues: TaskFieldValueData[];
|
||
taskIdNumber: number;
|
||
formId?: number;
|
||
task?: TaskData;
|
||
users: UserSummary[];
|
||
roles: Array<{ id: number; name: string }>;
|
||
fieldValuesMap: Record<string, unknown>;
|
||
conditionContextStatuses: Array<{ id: number; name?: string; position: number; isFinal?: boolean | null }>;
|
||
isTaskFinal: boolean;
|
||
isFieldVisibleForTask: (field: FormFieldData) => boolean;
|
||
isFieldImmutableForTask: (field: FormFieldData) => boolean;
|
||
fieldErrors: Record<number, string>;
|
||
setFieldErrors: React.Dispatch<React.SetStateAction<Record<number, string>>>;
|
||
}
|
||
|
||
export const TabFieldsContent = ({
|
||
tabId,
|
||
hasTabs,
|
||
fields,
|
||
fieldValues,
|
||
taskIdNumber,
|
||
formId,
|
||
task,
|
||
users,
|
||
roles,
|
||
fieldValuesMap,
|
||
conditionContextStatuses,
|
||
isTaskFinal,
|
||
isFieldVisibleForTask,
|
||
isFieldImmutableForTask,
|
||
fieldErrors,
|
||
setFieldErrors,
|
||
}: TabFieldsContentProps) => {
|
||
const { isPending: isUrlPending, getPendingBody } = useOfflineSyncContext();
|
||
const visibleFields = fields.filter(
|
||
f => f.type !== 'company' && (tabId === undefined || f.tabId === tabId) && isFieldVisibleForTask(f)
|
||
);
|
||
return (
|
||
<div className="space-y-2 text-sm">
|
||
{visibleFields && visibleFields.length > 0 ? (
|
||
visibleFields.map((field) => {
|
||
const fieldValue = fieldValues.find((fv: TaskFieldValueData) => fv.fieldId === field.id);
|
||
const fieldUrl = `/api/tasks/${taskIdNumber}/field-values/${field.id}`;
|
||
const pendingBody = isUrlPending(fieldUrl) ? getPendingBody(fieldUrl) : null;
|
||
const effectiveValue = (pendingBody && 'value' in pendingBody)
|
||
? (pendingBody as { value: string | number | boolean | null | undefined }).value
|
||
: fieldValue?.value;
|
||
return (
|
||
<ErrorBoundary key={`eb-${field.id}`}>
|
||
<FieldValueRow
|
||
key={field.id}
|
||
field={field}
|
||
task={task}
|
||
value={effectiveValue}
|
||
taskId={taskIdNumber}
|
||
formId={formId}
|
||
users={users}
|
||
roles={roles}
|
||
allFieldValues={fieldValuesMap}
|
||
allFields={fields}
|
||
statuses={conditionContextStatuses}
|
||
isPending={isUrlPending}
|
||
readOnly={isTaskFinal}
|
||
errorMessage={fieldErrors[field.id]}
|
||
onErrorClear={() => setFieldErrors((prev) => { const next = { ...prev }; delete next[field.id]; return next; })}
|
||
/>
|
||
</ErrorBoundary>
|
||
);
|
||
})
|
||
) : (
|
||
hasTabs && tabId !== undefined ? (
|
||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||
<p className="text-xs">На этой вкладке нет полей</p>
|
||
<p className="text-[10px] mt-1">Назначьте поля на вкладку в редакторе формы</p>
|
||
</div>
|
||
) : null
|
||
)}
|
||
</div>
|
||
);
|
||
};
|