mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-07 14:15:45 +00:00
added location registeration
This commit is contained in:
240
apps/backoffice/src/app/features/location/pages/LocationPage.tsx
Normal file
240
apps/backoffice/src/app/features/location/pages/LocationPage.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user