mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-02 05:43:40 +00:00
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Button, Divider, Drawer, NumberInput, Stack, Text, TextInput } from '@mantine/core';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { BilingualInput, ModalFooter } from '@ema-platform/ui';
|
|
import type { FormSchemaPalette, FormSectionConfig } from '@ema-platform/api';
|
|
import { ConditionBuilder, type ConditionValue } from './ConditionBuilder';
|
|
import type { ConditionTarget } from '../config/schema-paths';
|
|
|
|
const KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
|
|
function emptySection(): FormSectionConfig {
|
|
return { key: '', title: { en: '', am: '' }, fields: [] };
|
|
}
|
|
|
|
/** Adds/edits one section's own metadata — its fields are managed on the list, not here. */
|
|
export function SectionEditorDrawer({
|
|
opened,
|
|
onClose,
|
|
section,
|
|
onSave,
|
|
palette,
|
|
conditionTargets,
|
|
}: {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
/** Null = adding a new section. */
|
|
section: FormSectionConfig | null;
|
|
onSave: (section: Omit<FormSectionConfig, 'fields'>) => void;
|
|
palette: FormSchemaPalette | undefined;
|
|
conditionTargets: ConditionTarget[];
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const [draft, setDraft] = useState<FormSectionConfig>(emptySection());
|
|
const [keyError, setKeyError] = useState<string | null>(null);
|
|
const isNew = !section;
|
|
|
|
useEffect(() => {
|
|
if (opened) {
|
|
setDraft(section ? { ...section, title: { ...section.title } } : emptySection());
|
|
setKeyError(null);
|
|
}
|
|
}, [opened, section]);
|
|
|
|
function save() {
|
|
if (!draft.key.trim() || !KEY_PATTERN.test(draft.key.trim())) {
|
|
setKeyError(t('certReq.section.keyInvalid', 'Key must start with a letter and contain only letters, numbers, underscores'));
|
|
return;
|
|
}
|
|
if (!draft.title.en?.trim()) return;
|
|
const { fields: _fields, ...meta } = draft;
|
|
onSave({ ...meta, key: draft.key.trim() });
|
|
}
|
|
|
|
return (
|
|
<Drawer
|
|
opened={opened}
|
|
onClose={onClose}
|
|
position="right"
|
|
size="md"
|
|
title={<Text fw={700}>{isNew ? t('certReq.section.add', 'Add section') : t('certReq.section.edit', 'Edit section')}</Text>}
|
|
>
|
|
<Stack gap="md">
|
|
<TextInput
|
|
label={t('certReq.section.key', 'Section key')}
|
|
placeholder="certificate"
|
|
required
|
|
value={draft.key}
|
|
error={keyError}
|
|
disabled={!isNew}
|
|
description={
|
|
isNew
|
|
? t('certReq.section.keyHelp', 'Letters, numbers and underscores only')
|
|
: t('certReq.section.keyLocked', 'Key cannot change once created')
|
|
}
|
|
onChange={(e) => {
|
|
const { value } = e.currentTarget;
|
|
setDraft((d) => ({ ...d, key: value }));
|
|
}}
|
|
/>
|
|
|
|
<BilingualInput
|
|
label={t('certReq.section.title', 'Title')}
|
|
required
|
|
value={{ en: draft.title.en ?? '', am: draft.title.am ?? '' }}
|
|
onChange={(v) => setDraft((d) => ({ ...d, title: v }))}
|
|
/>
|
|
|
|
<BilingualInput
|
|
label={t('certReq.section.description', 'Description')}
|
|
value={{ en: draft.description?.en ?? '', am: draft.description?.am ?? '' }}
|
|
onChange={(v) => setDraft((d) => ({ ...d, description: v }))}
|
|
/>
|
|
|
|
<TextInput
|
|
label={t('certReq.section.group', 'Wizard step group')}
|
|
description={t(
|
|
'certReq.section.groupHelp',
|
|
'Sections sharing the same group render together on one step',
|
|
)}
|
|
value={draft.group ?? ''}
|
|
onChange={(e) => {
|
|
const { value } = e.currentTarget;
|
|
setDraft((d) => ({ ...d, group: value || undefined }));
|
|
}}
|
|
/>
|
|
|
|
<NumberInput
|
|
label={t('certReq.section.groupOrder', 'Group order')}
|
|
value={draft.groupOrder ?? ''}
|
|
onChange={(v) => setDraft((d) => ({ ...d, groupOrder: typeof v === 'number' ? v : undefined }))}
|
|
/>
|
|
|
|
<NumberInput
|
|
label={t('certReq.section.sortOrder', 'Sort order')}
|
|
value={draft.sortOrder ?? ''}
|
|
onChange={(v) => setDraft((d) => ({ ...d, sortOrder: typeof v === 'number' ? v : undefined }))}
|
|
/>
|
|
|
|
<Divider label={t('certReq.condition.title', 'Visibility condition')} labelPosition="left" />
|
|
<ConditionBuilder
|
|
value={(draft.showWhen ?? null) as ConditionValue | null}
|
|
onChange={(v) => setDraft((d) => ({ ...d, showWhen: v ?? undefined }))}
|
|
targets={conditionTargets}
|
|
palette={palette}
|
|
/>
|
|
|
|
<ModalFooter>
|
|
<Button variant="default" onClick={onClose}>{t('certReq.cancel', 'Cancel')}</Button>
|
|
<Button color="teal" onClick={save}>
|
|
{isNew ? t('certReq.section.add', 'Add section') : t('certReq.saveChanges', 'Save changes')}
|
|
</Button>
|
|
</ModalFooter>
|
|
</Stack>
|
|
</Drawer>
|
|
);
|
|
}
|