Files
emaui/apps/backoffice/src/app/features/location/pages/LocationPage.tsx
2026-06-17 16:25:58 +03:00

241 lines
7.4 KiB
TypeScript

import { useState, useCallback } from 'react';
import {
Stack,
Title,
Group,
Button,
Paper,
Text,
Grid,
Modal,
ActionIcon,
Tooltip,
Loader,
Center,
Alert,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconSettings, IconMap, IconInfoCircle, IconPlus } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { notify } from '@ema-platform/ui';
import { LocationTree } from '../components/LocationTree';
import { LocationDetail } from '../components/LocationDetail';
import { LocationForm } from '../components/LocationForm';
import { LocationTypeModal } from '../components/LocationTypeModal';
import {
useGetLocationTypesQuery,
useCreateLocationMutation,
useUpdateLocationMutation,
useDeleteLocationMutation,
} from '../api/location-api';
import type { Location } from '../types/location';
export function LocationPage() {
const { t } = useTranslation();
const { data: locationTypesRes, isLoading: typesLoading } = useGetLocationTypesQuery();
const [createLocation, { isLoading: isCreating }] = useCreateLocationMutation();
const [updateLocation, { isLoading: isUpdating }] = useUpdateLocationMutation();
const [deleteLocation] = useDeleteLocationMutation();
const locationTypes = locationTypesRes?.items ?? [];
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null);
const [typeModalOpened, { open: openTypeModal, close: closeTypeModal }] = useDisclosure(false);
const [formModalOpened, { open: openFormModal, close: closeFormModal }] = useDisclosure(false);
const [deleteModalOpened, { open: openDeleteModal, close: closeDeleteModal }] = useDisclosure(false);
const [editingLocation, setEditingLocation] = useState<Location | null>(null);
const [parentLocation, setParentLocation] = useState<Location | null>(null);
const handleSelect = useCallback((loc: Location) => {
setSelectedLocation(loc);
}, []);
const handleAddChild = useCallback(() => {
setEditingLocation(null);
setParentLocation(selectedLocation);
openFormModal();
}, [selectedLocation, openFormModal]);
const handleEdit = useCallback(() => {
if (!selectedLocation) return;
setEditingLocation(selectedLocation);
setParentLocation(null);
openFormModal();
}, [selectedLocation, openFormModal]);
const handleFormSubmit = useCallback(
async (values: {
code: string;
names: { en: string; am: string };
locationTypeId: string;
parentId?: string | null;
}) => {
try {
if (editingLocation) {
await updateLocation({ id: editingLocation.id, ...values }).unwrap();
notify.success(t('location.updated'));
} else {
await createLocation(values).unwrap();
notify.success(t('location.created'));
}
closeFormModal();
setEditingLocation(null);
setParentLocation(null);
} catch {
notify.error(t('location.error'));
}
},
[editingLocation, createLocation, updateLocation, closeFormModal, t],
);
const handleDeleteConfirm = useCallback(async () => {
if (!selectedLocation) return;
try {
await deleteLocation(selectedLocation.id).unwrap();
notify.success(t('location.deleted'));
setSelectedLocation(null);
closeDeleteModal();
} catch {
notify.error(t('location.deleteError'));
}
}, [selectedLocation, deleteLocation, closeDeleteModal, t]);
if (typesLoading) {
return (
<Center py="xl">
<Loader />
</Center>
);
}
return (
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>{t('location.title')}</Title>
<Group gap="sm">
{locationTypes.length > 0 && (
<Button
variant="light"
leftSection={<IconPlus size={16} />}
onClick={() => {
setEditingLocation(null);
setParentLocation(null);
openFormModal();
}}
size="sm"
>
{t('location.addRoot')}
</Button>
)}
<Tooltip label={t('location.manageTypes')}>
<ActionIcon
variant="subtle"
color="gray"
size="lg"
onClick={openTypeModal}
>
<IconSettings size={20} />
</ActionIcon>
</Tooltip>
</Group>
</Group>
{locationTypes.length === 0 && (
<Alert
icon={<IconInfoCircle size={16} />}
title={t('location.setupRequired')}
color="blue"
>
<Text size="sm" mb="sm">
{t('location.setupHint')}
</Text>
<Button variant="light" size="sm" onClick={openTypeModal}>
{t('location.configureTypes')}
</Button>
</Alert>
)}
{locationTypes.length > 0 && (
<Grid gutter="md">
<Grid.Col span={{ base: 12, md: 5 }}>
<Paper p="md" radius="md" withBorder style={{ maxHeight: 'calc(100vh - 260px)', overflow: 'auto' }}>
<Text fw={600} size="sm" mb="sm">
{t('location.hierarchy')}
</Text>
<LocationTree
selectedId={selectedLocation?.id ?? null}
onSelect={handleSelect}
/>
</Paper>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 7 }}>
{selectedLocation ? (
<LocationDetail
location={selectedLocation}
locationTypes={locationTypes}
onAddChild={handleAddChild}
onEdit={handleEdit}
onDelete={openDeleteModal}
/>
) : (
<Paper p="xl" radius="md" withBorder>
<Center py="xl">
<Stack align="center" gap="sm">
<IconMap size={48} stroke={1} color="var(--mantine-color-gray-4)" />
<Text c="dimmed" size="sm">
{t('location.selectHint')}
</Text>
</Stack>
</Center>
</Paper>
)}
</Grid.Col>
</Grid>
)}
<Modal
opened={formModalOpened}
onClose={closeFormModal}
title={editingLocation ? t('location.editTitle') : t('location.addTitle')}
size="md"
>
<LocationForm
locationTypes={locationTypes}
parentLocation={parentLocation}
editingLocation={editingLocation}
onSubmit={handleFormSubmit}
onCancel={() => {
closeFormModal();
setEditingLocation(null);
setParentLocation(null);
}}
isSubmitting={isCreating || isUpdating}
/>
</Modal>
<Modal
opened={deleteModalOpened}
onClose={closeDeleteModal}
title={t('location.confirmDelete')}
size="sm"
>
<Text mb="md">
{t('location.deleteConfirmText', {
name: selectedLocation?.names.en ?? '',
})}
</Text>
<Group justify="flex-end">
<Button variant="default" onClick={closeDeleteModal} size="sm">
{t('location.cancel')}
</Button>
<Button color="red" onClick={handleDeleteConfirm} size="sm">
{t('location.delete')}
</Button>
</Group>
</Modal>
<LocationTypeModal opened={typeModalOpened} onClose={closeTypeModal} />
</Stack>
);
}