mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-28 18:20:59 +00:00
203 lines
6.0 KiB
TypeScript
203 lines
6.0 KiB
TypeScript
import { useEffect, useMemo } from 'react';
|
|
import {
|
|
TextInput,
|
|
Button,
|
|
Group,
|
|
Stack,
|
|
Paper,
|
|
Title,
|
|
Text,
|
|
Badge,
|
|
Select,
|
|
} from '@mantine/core';
|
|
import { useForm } from '@mantine/form';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { notify } from '@ema-platform/ui';
|
|
import type { Location, LocationType } from '../types/location';
|
|
|
|
interface LocationFormValues {
|
|
code: string;
|
|
namesEn: string;
|
|
namesAm: string;
|
|
locationTypeId: string;
|
|
}
|
|
|
|
interface LocationFormProps {
|
|
locationTypes: LocationType[];
|
|
parentLocation?: Location | null;
|
|
editingLocation?: Location | null;
|
|
onSubmit: (values: {
|
|
code: string;
|
|
names: { en: string; am: string };
|
|
locationTypeId: string;
|
|
parentId?: string | null;
|
|
}) => void;
|
|
onCancel: () => void;
|
|
isSubmitting?: boolean;
|
|
}
|
|
|
|
export function LocationForm({
|
|
locationTypes,
|
|
parentLocation,
|
|
editingLocation,
|
|
onSubmit,
|
|
onCancel,
|
|
isSubmitting,
|
|
}: LocationFormProps) {
|
|
const { t, i18n } = useTranslation();
|
|
const locale = i18n.language as 'en' | 'am';
|
|
|
|
const isEditing = !!editingLocation;
|
|
|
|
const { type, allAtLevel, typesAvailable } = useMemo(() => {
|
|
if (isEditing) {
|
|
const lt = locationTypes.find((t) => t.id === editingLocation.locationTypeId);
|
|
return { type: lt ?? null, allAtLevel: [lt].filter(Boolean) as LocationType[], typesAvailable: false };
|
|
}
|
|
|
|
if (parentLocation) {
|
|
const parentLevel =
|
|
locationTypes.find((lt) => lt.id === parentLocation.locationTypeId)?.level ?? 0;
|
|
const nextLevel = locationTypes
|
|
.filter((lt) => lt.level === parentLevel + 1)
|
|
.sort((a, b) => a.level - b.level);
|
|
return { type: nextLevel[0] ?? null, allAtLevel: nextLevel, typesAvailable: nextLevel.length > 1 };
|
|
}
|
|
|
|
const minLevel = Math.min(...locationTypes.map((lt) => lt.level));
|
|
const roots = locationTypes
|
|
.filter((lt) => lt.level === minLevel)
|
|
.sort((a, b) => a.level - b.level);
|
|
return { type: roots[0] ?? null, allAtLevel: roots, typesAvailable: roots.length > 1 };
|
|
}, [locationTypes, parentLocation, editingLocation, isEditing]);
|
|
|
|
const form = useForm<LocationFormValues>({
|
|
initialValues: {
|
|
code: '',
|
|
namesEn: '',
|
|
namesAm: '',
|
|
locationTypeId: typesAvailable ? '' : (type?.id ?? ''),
|
|
},
|
|
validate: {
|
|
code: (v) => (!v ? t('location.validation.codeRequired') : null),
|
|
namesEn: (v) => (!v ? t('location.validation.nameEnRequired') : null),
|
|
namesAm: (v) => (!v ? t('location.validation.nameAmRequired') : null),
|
|
locationTypeId: (v) => {
|
|
if (typesAvailable && !v) return t('location.validation.typeRequired');
|
|
return null;
|
|
},
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (editingLocation) {
|
|
form.setValues({
|
|
code: editingLocation.code,
|
|
namesEn: editingLocation.names.en,
|
|
namesAm: editingLocation.names.am,
|
|
locationTypeId: editingLocation.locationTypeId,
|
|
});
|
|
}
|
|
}, [editingLocation]);
|
|
|
|
const handleSubmit = form.onSubmit((values) => {
|
|
const resolvedTypeId = typesAvailable ? values.locationTypeId : type?.id;
|
|
if (!resolvedTypeId) {
|
|
notify.error(t('location.noTypesAvailable'));
|
|
return;
|
|
}
|
|
onSubmit({
|
|
code: values.code,
|
|
names: { en: values.namesEn, am: values.namesAm },
|
|
locationTypeId: resolvedTypeId,
|
|
parentId: editingLocation ? editingLocation.parentId : parentLocation?.id ?? null,
|
|
});
|
|
});
|
|
|
|
const title = isEditing
|
|
? t('location.editTitle')
|
|
: t('location.addTitle');
|
|
|
|
return (
|
|
<Paper p="lg" radius="md" withBorder>
|
|
<Title order={5} mb="md">
|
|
{title}
|
|
</Title>
|
|
{parentLocation && !isEditing && (
|
|
<TextInput
|
|
label={t('location.parent')}
|
|
value={parentLocation.names[locale]}
|
|
disabled
|
|
mb="sm"
|
|
size="sm"
|
|
/>
|
|
)}
|
|
<form onSubmit={handleSubmit}>
|
|
<Stack gap="sm">
|
|
{typesAvailable ? (
|
|
<Select
|
|
label={t('location.type')}
|
|
placeholder={t('location.selectType')}
|
|
data={allAtLevel.map((lt) => ({
|
|
value: lt.id,
|
|
// Not every locale is filled in on every row, and an option
|
|
// with no label is unpickable — fall back to English, then the
|
|
// code, which always exists.
|
|
label: lt.names[locale] || lt.names.en || lt.code,
|
|
}))}
|
|
{...form.getInputProps('locationTypeId')}
|
|
size="sm"
|
|
required
|
|
nothingFoundMessage={t('location.noTypesAvailable')}
|
|
/>
|
|
) : (
|
|
<div>
|
|
<Text size="xs" c="dimmed" fw={500} mb={4}>
|
|
{t('location.type')}
|
|
</Text>
|
|
{type ? (
|
|
<Badge size="lg" variant="light" color="blue">
|
|
{type.names[locale]}
|
|
</Badge>
|
|
) : (
|
|
<Text size="sm" c="red">
|
|
{t('location.noTypesAvailable')}
|
|
</Text>
|
|
)}
|
|
</div>
|
|
)}
|
|
<TextInput
|
|
label={t('location.code')}
|
|
placeholder="e.g., ETH, AA, KK"
|
|
{...form.getInputProps('code')}
|
|
size="sm"
|
|
required
|
|
/>
|
|
<TextInput
|
|
label={t('location.nameEn')}
|
|
placeholder="English name"
|
|
{...form.getInputProps('namesEn')}
|
|
size="sm"
|
|
required
|
|
/>
|
|
<TextInput
|
|
label={t('location.nameAm')}
|
|
placeholder="የአማርኛ ስም"
|
|
{...form.getInputProps('namesAm')}
|
|
size="sm"
|
|
required
|
|
/>
|
|
<Group justify="flex-end" mt="md">
|
|
<Button variant="default" onClick={onCancel} size="sm">
|
|
{t('location.cancel')}
|
|
</Button>
|
|
<Button type="submit" loading={isSubmitting} size="sm">
|
|
{isEditing ? t('location.update') : t('location.create')}
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</form>
|
|
</Paper>
|
|
);
|
|
}
|