import { ActionIcon, Autocomplete, Checkbox, Group, Paper, Select, Stack, Switch, Text, TextInput } from '@mantine/core'; import { IconPlus, IconTrash } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; import type { FieldCondition, FormSchemaPalette } from '@ema-platform/api'; import { useLocalized } from '@ema-platform/api'; import type { ConditionTarget } from '../config/schema-paths'; /** `FieldCondition` plus the renewal-only extra `DocumentRequirement.conditionExpression` carries. */ export type ConditionValue = FieldCondition & { previousDocExpired?: string }; /** One editable `anyOf` arm — a single-field condition, same shape a plain condition holds. */ type ConditionArm = Omit; type Operator = 'equals' | 'notEquals' | 'in' | 'isSet'; function operatorOf(condition: ConditionValue | undefined): Operator | null { if (!condition) return null; if (condition.isSet !== undefined) return 'isSet'; if (condition.equals !== undefined) return 'equals'; if (condition.notEquals !== undefined) return 'notEquals'; if (condition.in !== undefined) return 'in'; return null; } /** Best-effort type for a raw stored value, so re-editing an existing * condition renders a number input for a numeric value rather than text. */ function coerce(raw: string, targetType: string | undefined): string | number | boolean { if (targetType === 'BOOLEAN') return raw === 'true'; if (targetType === 'NUMBER' || targetType === 'MONEY') { const n = Number(raw); return Number.isFinite(n) && raw.trim() !== '' ? n : raw; } return raw; } /** One-line summary of a condition for read-only chips ("when X = Y", "when X = Y or W = Z"). */ export function describeCondition( condition: ConditionValue, t: (key: string, fallback: string) => string, ): string { if (condition.anyOf?.length) { return condition.anyOf.map((arm) => describeCondition(arm, t)).join(` ${t('certReq.condition.or', 'or')} `); } if (!condition.field) return ''; const parts = [condition.field]; if (condition.equals !== undefined) parts.push(`= ${condition.equals}`); if (condition.notEquals !== undefined) parts.push(`≠ ${condition.notEquals}`); if (condition.in !== undefined) parts.push(`∈ [${condition.in.join(', ')}]`); if (condition.isSet !== undefined) { parts.push(condition.isSet ? t('certReq.condition.isSet', 'is set') : t('certReq.condition.isNotSet', 'is not set')); } return parts.join(' '); } /** * The field/operator/value trio for one condition — a plain condition, or one * arm of an `anyOf`. No enable switch of its own; the caller owns whether * this row exists at all. */ function ConditionArmFields({ value, onChange, targets, palette, }: { value: ConditionArm; onChange: (value: ConditionArm) => void; targets: ConditionTarget[]; palette: FormSchemaPalette | undefined; }) { const { t } = useTranslation(); const localized = useLocalized(); const operator = operatorOf(value) ?? 'equals'; const target = targets.find((c) => c.path === value.field); const operators = palette?.conditionOperators ?? ['equals', 'notEquals', 'in', 'isSet']; function setField(field: string) { onChange({ field, ...(operator === 'isSet' ? { isSet: true } : { equals: '' }) }); } function setOperator(next: Operator) { if (!value.field) return; const base: ConditionArm = { field: value.field }; if (next === 'isSet') base.isSet = true; else if (next === 'in') base.in = []; else if (next === 'notEquals') base.notEquals = ''; else base.equals = ''; onChange(base); } function setValueRaw(raw: string) { if (!value.field) return; const coerced = coerce(raw, target?.field.type); if (operator === 'equals') onChange({ field: value.field, equals: coerced }); else if (operator === 'notEquals') onChange({ field: value.field, notEquals: coerced }); } function setInValues(raws: string[]) { if (!value.field) return; onChange({ field: value.field, in: raws.map((r) => coerce(r, target?.field.type)) as (string | number)[], }); } return ( c.path)} value={value.field ?? ''} onChange={setField} /> ({ value: o.value, label: localized(o.label) || o.value, }))} value={String(value.equals ?? value.notEquals ?? '')} onChange={(v) => v !== null && setValueRaw(v)} /> )} {operator !== 'isSet' && operator !== 'in' && target?.field.type !== 'SELECT' && ( target?.field.type === 'BOOLEAN' ? ( setValueRaw(String(e.currentTarget.checked))} /> ) : ( setValueRaw(e.currentTarget.value)} /> ) )} {operator === 'in' && target?.field.type === 'SELECT' && (