From ddd91b6125e8169c508e7d531975bee2c35aaca5 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Fri, 19 Jun 2026 15:24:37 +0300 Subject: [PATCH] added configuration page --- .../configuration/api/configuration-api.ts | 68 +++ .../configuration/pages/ConfigurationPage.tsx | 479 ++++++++++++++++++ .../configuration/types/configuration.ts | 50 ++ apps/backoffice/src/app/i18n/locales/am.ts | 35 ++ apps/backoffice/src/app/i18n/locales/en.ts | 35 ++ .../src/app/layouts/BackofficeLayout.tsx | 4 +- apps/backoffice/src/app/router/index.tsx | 4 +- 7 files changed, 671 insertions(+), 4 deletions(-) create mode 100644 apps/backoffice/src/app/features/configuration/api/configuration-api.ts create mode 100644 apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx create mode 100644 apps/backoffice/src/app/features/configuration/types/configuration.ts diff --git a/apps/backoffice/src/app/features/configuration/api/configuration-api.ts b/apps/backoffice/src/app/features/configuration/api/configuration-api.ts new file mode 100644 index 000000000..6189684f0 --- /dev/null +++ b/apps/backoffice/src/app/features/configuration/api/configuration-api.ts @@ -0,0 +1,68 @@ +import { baseApi } from '@ema-platform/api'; +import type { + Department, + Profession, + ListResponse, + CreateDepartmentPayload, + UpdateDepartmentPayload, + CreateProfessionPayload, + UpdateProfessionPayload, +} from '../types/configuration'; + +const configurationApi = baseApi.injectEndpoints({ + endpoints: (builder) => ({ + getDepartments: builder.query, void>({ + query: () => '/departments', + providesTags: ['Api'], + }), + createDepartment: builder.mutation({ + query: (body) => ({ url: '/departments', method: 'POST', body }), + invalidatesTags: ['Api'], + }), + updateDepartment: builder.mutation({ + query: ({ id, ...body }) => ({ + url: `/departments/${id}`, + method: 'PUT', + body, + }), + invalidatesTags: ['Api'], + }), + deleteDepartment: builder.mutation({ + query: (id) => ({ url: `/departments/${id}`, method: 'DELETE' }), + invalidatesTags: ['Api'], + }), + + getProfessions: builder.query, void>({ + query: () => '/professions', + providesTags: ['Api'], + }), + createProfession: builder.mutation({ + query: (body) => ({ url: '/professions', method: 'POST', body }), + invalidatesTags: ['Api'], + }), + updateProfession: builder.mutation({ + query: ({ id, ...body }) => ({ + url: `/professions/${id}`, + method: 'PUT', + body, + }), + invalidatesTags: ['Api'], + }), + deleteProfession: builder.mutation({ + query: (id) => ({ url: `/professions/${id}`, method: 'DELETE' }), + invalidatesTags: ['Api'], + }), + }), + overrideExisting: false, +}); + +export const { + useGetDepartmentsQuery, + useCreateDepartmentMutation, + useUpdateDepartmentMutation, + useDeleteDepartmentMutation, + useGetProfessionsQuery, + useCreateProfessionMutation, + useUpdateProfessionMutation, + useDeleteProfessionMutation, +} = configurationApi; diff --git a/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx new file mode 100644 index 000000000..83a73271e --- /dev/null +++ b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx @@ -0,0 +1,479 @@ +import { useState } from 'react'; +import { + Stack, + Title, + Tabs, + Group, + Button, + TextInput, + Textarea, + Table, + ActionIcon, + Modal, + Text, + Select, + Badge, + Paper, +} from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { useDisclosure } from '@mantine/hooks'; +import { IconEdit, IconTrash, IconPlus, IconBuilding, IconBriefcase, IconMap } from '@tabler/icons-react'; +import { useTranslation } from 'react-i18next'; +import { notify } from '@ema-platform/ui'; +import { LocationPage } from '../../location/pages/LocationPage'; +import type { Department, Profession } from '../types/configuration'; + +let nextId = 1; +const uid = () => String(nextId++); + +const MOCK_DEPARTMENTS: Department[] = [ + { id: uid(), code: 'IT', names: { en: 'Information Technology', am: 'ኢንፎርሜሽን ቴክኖሎጂ' }, description: 'Handles all IT infrastructure and systems.', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { id: uid(), code: 'HR', names: { en: 'Human Resources', am: 'የሰው ኃይል ሀብት' }, description: 'Manages personnel and recruitment.', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { id: uid(), code: 'FIN', names: { en: 'Finance', am: 'ፋይናንስ' }, description: 'Oversees budgeting and accounting.', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, +]; + +const MOCK_PROFESSIONS: Profession[] = [ + { id: uid(), code: 'SWE', names: { en: 'Software Engineer', am: 'የሶፍትዌር መሐንዲስ' }, description: 'Develops and maintains software applications.', departmentId: '1', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { id: uid(), code: 'SRE', names: { en: 'Site Reliability Engineer', am: 'የጣቢያ አስተማማኝነት መሐንዲስ' }, description: 'Ensures system reliability and uptime.', departmentId: '1', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { id: uid(), code: 'HRM', names: { en: 'HR Manager', am: 'የሰው ኃይል አስተዳዳሪ' }, description: 'Leads the HR team and strategy.', departmentId: '2', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, +]; + +function DepartmentTab() { + const { t } = useTranslation(); + const [departments, setDepartments] = useState(MOCK_DEPARTMENTS); + const [editingDept, setEditingDept] = useState(null); + const [showDeptForm, setShowDeptForm] = useState(false); + const [deleteTarget, setDeleteTarget] = useState(null); + const [deleteOpened, { open: openDelete, close: closeDelete }] = useDisclosure(false); + + const deptForm = useForm({ + initialValues: { code: '', nameEn: '', nameAm: '', description: '' }, + validate: { + code: (v) => (!v ? t('configuration.validation.codeRequired') : null), + nameEn: (v) => (!v ? t('configuration.validation.nameEnRequired') : null), + nameAm: (v) => (!v ? t('configuration.validation.nameAmRequired') : null), + }, + }); + + const resetDeptForm = () => { + deptForm.reset(); + setEditingDept(null); + setShowDeptForm(false); + }; + + const handleEditDept = (dept: Department) => { + setEditingDept(dept); + deptForm.setValues({ + code: dept.code, + nameEn: dept.names.en, + nameAm: dept.names.am, + description: dept.description, + }); + setShowDeptForm(true); + }; + + const handleDeleteDept = (dept: Department) => { + setDeleteTarget(dept); + openDelete(); + }; + + const confirmDeleteDept = () => { + if (!deleteTarget) return; + setDepartments((prev) => prev.filter((d) => d.id !== deleteTarget.id)); + notify.success(t('configuration.deleted')); + closeDelete(); + setDeleteTarget(null); + }; + + const handleDeptSubmit = deptForm.onSubmit((values) => { + const now = new Date().toISOString(); + if (editingDept) { + setDepartments((prev) => + prev.map((d) => + d.id === editingDept.id + ? { ...d, code: values.code, names: { en: values.nameEn, am: values.nameAm }, description: values.description, updatedAt: now } + : d + ) + ); + notify.success(t('configuration.updated')); + } else { + const newDept: Department = { + id: uid(), + code: values.code, + names: { en: values.nameEn, am: values.nameAm }, + description: values.description, + createdAt: now, + updatedAt: now, + }; + setDepartments((prev) => [...prev, newDept]); + notify.success(t('configuration.created')); + } + resetDeptForm(); + }); + + return ( + <> + + {t('configuration.departmentsList')} + {!showDeptForm && ( + + )} + + + {showDeptForm && ( + +
+ + + + +