sending params on configation api call on professions

This commit is contained in:
Estifo77
2026-07-25 11:20:18 +03:00
parent 86163fa7eb
commit 4c412a9fba
2 changed files with 202 additions and 114 deletions

View File

@@ -1,41 +1,41 @@
import { baseApi } from '@ema-platform/api'; import { baseApi } from "@ema-platform/api";
import type { import type {
Organization, Organization,
Profession, Profession,
ListResponse, ListResponse,
CreateProfessionPayload, CreateProfessionPayload,
UpdateProfessionPayload, UpdateProfessionPayload,
} from '../types/configuration'; } from "../types/configuration";
const configurationApi = baseApi.injectEndpoints({ const configurationApi = baseApi.injectEndpoints({
endpoints: (builder) => ({ endpoints: (builder) => ({
getOrganizations: builder.query<ListResponse<Organization>, void>({ getOrganizations: builder.query<ListResponse<Organization>, void>({
query: () => '/organizations', query: () => "/organizations",
providesTags: ['Api'], providesTags: ["Api"],
}), }),
getProfessions: builder.query< getProfessions: builder.query<ListResponse<Profession>, { q?: string }>({
ListResponse<Profession>, query: (params) => ({
{ skip?: number; take?: number; q?: string; orderBy?: string } | void url: "/professions",
>({ params,
query: (arg) => ({ url: '/professions', params: arg ?? {} }), }),
providesTags: ['Api'], providesTags: ["Api"],
}), }),
createProfession: builder.mutation<Profession, CreateProfessionPayload>({ createProfession: builder.mutation<Profession, CreateProfessionPayload>({
query: (body) => ({ url: '/professions', method: 'POST', body }), query: (body) => ({ url: "/professions", method: "POST", body }),
invalidatesTags: ['Api'], invalidatesTags: ["Api"],
}), }),
updateProfession: builder.mutation<Profession, UpdateProfessionPayload>({ updateProfession: builder.mutation<Profession, UpdateProfessionPayload>({
query: ({ id, ...body }) => ({ query: ({ id, ...body }) => ({
url: `/professions/${id}`, url: `/professions/${id}`,
method: 'PUT', method: "PUT",
body, body,
}), }),
invalidatesTags: ['Api'], invalidatesTags: ["Api"],
}), }),
deleteProfession: builder.mutation<void, string>({ deleteProfession: builder.mutation<void, string>({
query: (id) => ({ url: `/professions/${id}`, method: 'DELETE' }), query: (id) => ({ url: `/professions/${id}`, method: "DELETE" }),
invalidatesTags: ['Api'], invalidatesTags: ["Api"],
}), }),
}), }),
overrideExisting: true, overrideExisting: true,

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from "react";
import { import {
Stack, Stack,
Title, Title,
@@ -15,28 +15,36 @@ import {
Loader, Loader,
Center, Center,
Alert, Alert,
} from '@mantine/core'; } from "@mantine/core";
import { useForm } from '@mantine/form'; import { useForm } from "@mantine/form";
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from "@mantine/hooks";
import { IconEdit, IconTrash, IconPlus, IconBriefcase, IconMap, IconCertificate, IconInfoCircle } from '@tabler/icons-react'; import {
import { useTranslation } from 'react-i18next'; IconEdit,
IconTrash,
IconPlus,
IconBriefcase,
IconMap,
IconCertificate,
IconInfoCircle,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { import {
notify, notify,
useErrorHandler, useErrorHandler,
AdvancedTable, AdvancedTable,
useServerTable, useServerTable,
type AdvancedColumn, type AdvancedColumn,
} from '@ema-platform/ui'; } from "@ema-platform/ui";
import { LocationPage } from '../../location/pages/LocationPage'; import { LocationPage } from "../../location/pages/LocationPage";
import { CertificationPage } from '../../certification/pages/CertificationPage'; import { CertificationPage } from "../../certification/pages/CertificationPage";
import { import {
useGetOrganizationsQuery, useGetOrganizationsQuery,
useGetProfessionsQuery, useGetProfessionsQuery,
useCreateProfessionMutation, useCreateProfessionMutation,
useUpdateProfessionMutation, useUpdateProfessionMutation,
useDeleteProfessionMutation, useDeleteProfessionMutation,
} from '../api/configuration-api'; } from "../api/configuration-api";
import type { Profession } from '../types/configuration'; import type { Profession } from "../types/configuration";
interface ProfFormValues { interface ProfFormValues {
nameEn: string; nameEn: string;
@@ -54,14 +62,27 @@ interface ProfFormProps {
onCancel: () => void; onCancel: () => void;
} }
function ProfessionForm({ editingProf, deptOptions, isSubmitting, onSubmit, onCancel }: ProfFormProps) { function ProfessionForm({
editingProf,
deptOptions,
isSubmitting,
onSubmit,
onCancel,
}: ProfFormProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useForm<ProfFormValues>({ const form = useForm<ProfFormValues>({
initialValues: { nameEn: '', nameAm: '', descEn: '', descAm: '', departmentId: '' }, initialValues: {
nameEn: "",
nameAm: "",
descEn: "",
descAm: "",
departmentId: "",
},
validate: { validate: {
nameEn: (v) => (!v ? t('configuration.validation.nameEnRequired') : null), nameEn: (v) => (!v ? t("configuration.validation.nameEnRequired") : null),
nameAm: (v) => (!v ? t('configuration.validation.nameAmRequired') : null), nameAm: (v) => (!v ? t("configuration.validation.nameAmRequired") : null),
departmentId: (v) => (!v ? t('configuration.validation.departmentRequired') : null), departmentId: (v) =>
!v ? t("configuration.validation.departmentRequired") : null,
}, },
}); });
@@ -70,61 +91,65 @@ function ProfessionForm({ editingProf, deptOptions, isSubmitting, onSubmit, onCa
form.setValues({ form.setValues({
nameEn: editingProf.name.en, nameEn: editingProf.name.en,
nameAm: editingProf.name.am, nameAm: editingProf.name.am,
descEn: editingProf.description.en ?? '', descEn: editingProf.description.en ?? "",
descAm: editingProf.description.am ?? '', descAm: editingProf.description.am ?? "",
departmentId: editingProf.departmentId, departmentId: editingProf.departmentId,
}); });
} }
}, [editingProf]); }, [editingProf]);
const handleSubmit = form.onSubmit((values) => onSubmit(values, !!editingProf)); const handleSubmit = form.onSubmit((values) =>
onSubmit(values, !!editingProf),
);
return ( return (
<Paper p="md" withBorder mb="md" radius="md"> <Paper p="md" withBorder mb="md" radius="md">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<Stack gap="sm"> <Stack gap="sm">
<TextInput <TextInput
label={t('configuration.nameEn')} label={t("configuration.nameEn")}
placeholder="English name" placeholder="English name"
{...form.getInputProps('nameEn')} {...form.getInputProps("nameEn")}
size="sm" size="sm"
/> />
<TextInput <TextInput
label={t('configuration.nameAm')} label={t("configuration.nameAm")}
placeholder="የአማርኛ ስም" placeholder="የአማርኛ ስም"
{...form.getInputProps('nameAm')} {...form.getInputProps("nameAm")}
size="sm" size="sm"
/> />
<Textarea <Textarea
label={t('configuration.descEn')} label={t("configuration.descEn")}
placeholder="English description" placeholder="English description"
{...form.getInputProps('descEn')} {...form.getInputProps("descEn")}
size="sm" size="sm"
autosize autosize
minRows={2} minRows={2}
/> />
<Textarea <Textarea
label={t('configuration.descAm')} label={t("configuration.descAm")}
placeholder="የአማርኛ መግለጫ" placeholder="የአማርኛ መግለጫ"
{...form.getInputProps('descAm')} {...form.getInputProps("descAm")}
size="sm" size="sm"
autosize autosize
minRows={2} minRows={2}
/> />
<Select <Select
label={t('configuration.department')} label={t("configuration.department")}
placeholder={t('configuration.selectDepartment')} placeholder={t("configuration.selectDepartment")}
data={deptOptions} data={deptOptions}
{...form.getInputProps('departmentId')} {...form.getInputProps("departmentId")}
size="sm" size="sm"
searchable searchable
/> />
<Group justify="flex-end"> <Group justify="flex-end">
<Button variant="default" onClick={onCancel} size="sm"> <Button variant="default" onClick={onCancel} size="sm">
{t('configuration.cancel')} {t("configuration.cancel")}
</Button> </Button>
<Button type="submit" size="sm" loading={isSubmitting}> <Button type="submit" size="sm" loading={isSubmitting}>
{editingProf ? t('configuration.update') : t('configuration.create')} {editingProf
? t("configuration.update")
: t("configuration.create")}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
@@ -135,19 +160,25 @@ function ProfessionForm({ editingProf, deptOptions, isSubmitting, onSubmit, onCa
function ProfessionTab() { function ProfessionTab() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am'; const locale = i18n.language as "en" | "am";
const { handleError } = useErrorHandler(); const { handleError } = useErrorHandler();
const { data: deptRes } = useGetOrganizationsQuery(); const { data: deptRes } = useGetOrganizationsQuery();
const { pageIndex, setPageIndex, q, setQ, skip, take } = useServerTable({ pageSize: 10 }); const { pageIndex, setPageIndex, setQ, skip, take } = useServerTable({
pageSize: 10,
});
const { const {
data: profRes, data: profRes,
isLoading, isLoading,
isFetching, isFetching,
isError, isError,
refetch, refetch,
} = useGetProfessionsQuery({ skip, take, q: q || undefined, orderBy: 'CreatedAt:DESC' }); } = useGetProfessionsQuery({
const [createProfession, { isLoading: isCreating }] = useCreateProfessionMutation(); q: `skip:${skip},take:${take},orderBy:createdAt:DESC`,
const [updateProfession, { isLoading: isUpdating }] = useUpdateProfessionMutation(); });
const [createProfession, { isLoading: isCreating }] =
useCreateProfessionMutation();
const [updateProfession, { isLoading: isUpdating }] =
useUpdateProfessionMutation();
const [deleteProfession] = useDeleteProfessionMutation(); const [deleteProfession] = useDeleteProfessionMutation();
const departments = Array.isArray(deptRes) ? deptRes : (deptRes?.items ?? []); const departments = Array.isArray(deptRes) ? deptRes : (deptRes?.items ?? []);
@@ -160,12 +191,15 @@ function ProfessionTab() {
const [editingProf, setEditingProf] = useState<Profession | null>(null); const [editingProf, setEditingProf] = useState<Profession | null>(null);
const [showProfForm, setShowProfForm] = useState(false); const [showProfForm, setShowProfForm] = useState(false);
const [deleteTarget, setDeleteTarget] = useState<Profession | null>(null); const [deleteTarget, setDeleteTarget] = useState<Profession | null>(null);
const [deleteOpened, { open: openDelete, close: closeDelete }] = useDisclosure(false); const [deleteOpened, { open: openDelete, close: closeDelete }] =
useDisclosure(false);
const deptOptions = departments.filter((d) => d?.status?.toLowerCase() === 'active').map((d) => ({ const deptOptions = departments
value: d.id, .filter((d) => d?.status?.toLowerCase() === "active")
label: d.name?.[locale] ?? d.name ?? '', .map((d) => ({
})); value: d.id,
label: d.name?.[locale] ?? d.name ?? "",
}));
const resetProfForm = useCallback(() => { const resetProfForm = useCallback(() => {
setEditingProf(null); setEditingProf(null);
@@ -177,16 +211,19 @@ function ProfessionTab() {
setShowProfForm(true); setShowProfForm(true);
}, []); }, []);
const handleDeleteProf = useCallback((prof: Profession) => { const handleDeleteProf = useCallback(
setDeleteTarget(prof); (prof: Profession) => {
openDelete(); setDeleteTarget(prof);
}, [openDelete]); openDelete();
},
[openDelete],
);
const confirmDeleteProf = useCallback(async () => { const confirmDeleteProf = useCallback(async () => {
if (!deleteTarget) return; if (!deleteTarget) return;
try { try {
await deleteProfession(deleteTarget.id).unwrap(); await deleteProfession(deleteTarget.id).unwrap();
notify.success(t('configuration.deleted')); notify.success(t("configuration.deleted"));
closeDelete(); closeDelete();
setDeleteTarget(null); setDeleteTarget(null);
} catch (e) { } catch (e) {
@@ -194,62 +231,86 @@ function ProfessionTab() {
} }
}, [deleteTarget, deleteProfession, closeDelete, handleError]); }, [deleteTarget, deleteProfession, closeDelete, handleError]);
const handleProfSubmit = useCallback(async (values: ProfFormValues) => { const handleProfSubmit = useCallback(
const name = { en: values.nameEn, am: values.nameAm }; async (values: ProfFormValues) => {
const description = { en: values.descEn, am: values.descAm }; const name = { en: values.nameEn, am: values.nameAm };
const description = { en: values.descEn, am: values.descAm };
try { try {
if (editingProf) { if (editingProf) {
await updateProfession({ await updateProfession({
id: editingProf.id, id: editingProf.id,
name, name,
description, description,
departmentId: values.departmentId, departmentId: values.departmentId,
}).unwrap(); }).unwrap();
notify.success(t('configuration.updated')); notify.success(t("configuration.updated"));
} else { } else {
await createProfession({ await createProfession({
departmentId: values.departmentId, departmentId: values.departmentId,
name, name,
description, description,
}).unwrap(); }).unwrap();
notify.success(t('configuration.created')); notify.success(t("configuration.created"));
}
resetProfForm();
} catch (e) {
handleError(e);
} }
resetProfForm(); },
} catch (e) { [
handleError(e); editingProf,
} createProfession,
}, [editingProf, createProfession, updateProfession, resetProfForm, handleError]); updateProfession,
resetProfForm,
handleError,
],
);
const getDeptName = useCallback((deptId: string) => { const getDeptName = useCallback(
const dept = departments.find((d) => d.id === deptId); (deptId: string) => {
return dept ? (dept.name?.[locale] ?? dept.name?.en ?? '-') : '-'; const dept = departments.find((d) => d.id === deptId);
}, [departments, locale]); return dept ? (dept.name?.[locale] ?? dept.name?.en ?? "-") : "-";
},
[departments, locale],
);
const professionColumns: AdvancedColumn<Profession>[] = [ const professionColumns: AdvancedColumn<Profession>[] = [
{ {
header: t('configuration.name'), header: t("configuration.name"),
cell: ({ row }) => row.original.name[locale], cell: ({ row }) => row.original.name[locale],
}, },
{ {
header: t('configuration.description'), header: t("configuration.description"),
cell: ({ row }) => ( cell: ({ row }) => (
<Text size="sm" lineClamp={2} maw={200}>{row.original.description[locale]}</Text> <Text size="sm" lineClamp={2} maw={200}>
{row.original.description[locale]}
</Text>
), ),
}, },
{ {
header: t('configuration.department'), header: t("configuration.department"),
cell: ({ row }) => getDeptName(row.original.departmentId), cell: ({ row }) => getDeptName(row.original.departmentId),
}, },
{ {
header: '', header: "",
size: 90, size: 90,
cell: ({ row }) => ( cell: ({ row }) => (
<Group gap="xs"> <Group gap="xs">
<ActionIcon variant="subtle" color="blue" size="sm" onClick={() => handleEditProf(row.original)}> <ActionIcon
variant="subtle"
color="blue"
size="sm"
onClick={() => handleEditProf(row.original)}
>
<IconEdit size={14} /> <IconEdit size={14} />
</ActionIcon> </ActionIcon>
<ActionIcon variant="subtle" color="red" size="sm" onClick={() => handleDeleteProf(row.original)}> <ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => handleDeleteProf(row.original)}
>
<IconTrash size={14} /> <IconTrash size={14} />
</ActionIcon> </ActionIcon>
</Group> </Group>
@@ -258,17 +319,27 @@ function ProfessionTab() {
]; ];
if (isLoading) { if (isLoading) {
return <Center py="xl"><Loader /></Center>; return (
<Center py="xl">
<Loader />
</Center>
);
} }
if (isError) { if (isError) {
return <Alert icon={<IconInfoCircle size={16} />} color="red" title={t('configuration.error')} />; return (
<Alert
icon={<IconInfoCircle size={16} />}
color="red"
title={t("configuration.error")}
/>
);
} }
return ( return (
<> <>
<Group justify="space-between" align="flex-end" mb="md"> <Group justify="space-between" align="flex-end" mb="md">
<Title order={2}>{t('configuration.professionsList')}</Title> <Title order={2}>{t("configuration.professionsList")}</Title>
{!showProfForm && ( {!showProfForm && (
<Button <Button
variant="light" variant="light"
@@ -276,7 +347,7 @@ function ProfessionTab() {
onClick={() => setShowProfForm(true)} onClick={() => setShowProfForm(true)}
size="sm" size="sm"
> >
{t('configuration.addProfession')} {t("configuration.addProfession")}
</Button> </Button>
)} )}
</Group> </Group>
@@ -294,7 +365,7 @@ function ProfessionTab() {
<AdvancedTable <AdvancedTable
columns={professionColumns} columns={professionColumns}
data={professions} data={professions}
tableName={t('configuration.professionsList')} tableName={t("configuration.professionsList")}
itemCount={totalCount} itemCount={totalCount}
pageIndex={pageIndex} pageIndex={pageIndex}
onPageChange={setPageIndex} onPageChange={setPageIndex}
@@ -302,16 +373,27 @@ function ProfessionTab() {
refresh={refetch} refresh={refetch}
onSearchChange={setQ} onSearchChange={setQ}
isLoading={isFetching} isLoading={isFetching}
emptyText={t('configuration.noProfessions')} emptyText={t("configuration.noProfessions")}
/> />
<Modal opened={deleteOpened} onClose={closeDelete} title={t('configuration.confirmDelete')} size="sm"> <Modal
opened={deleteOpened}
onClose={closeDelete}
title={t("configuration.confirmDelete")}
size="sm"
>
<Text mb="md"> <Text mb="md">
{t('configuration.deleteConfirmText', { name: deleteTarget?.name?.[locale] ?? '' })} {t("configuration.deleteConfirmText", {
name: deleteTarget?.name?.[locale] ?? "",
})}
</Text> </Text>
<Group justify="flex-end"> <Group justify="flex-end">
<Button variant="default" onClick={closeDelete} size="sm">{t('configuration.cancel')}</Button> <Button variant="default" onClick={closeDelete} size="sm">
<Button color="red" onClick={confirmDeleteProf} size="sm">{t('configuration.delete')}</Button> {t("configuration.cancel")}
</Button>
<Button color="red" onClick={confirmDeleteProf} size="sm">
{t("configuration.delete")}
</Button>
</Group> </Group>
</Modal> </Modal>
</> </>
@@ -323,18 +405,24 @@ export function ConfigurationPage() {
return ( return (
<Stack gap="lg"> <Stack gap="lg">
<Title order={2}>{t('configuration.title')}</Title> <Title order={2}>{t("configuration.title")}</Title>
<Tabs defaultValue="professions"> <Tabs defaultValue="professions">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="professions" leftSection={<IconBriefcase size={16} />}> <Tabs.Tab
{t('configuration.professions')} value="professions"
leftSection={<IconBriefcase size={16} />}
>
{t("configuration.professions")}
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="locations" leftSection={<IconMap size={16} />}> <Tabs.Tab value="locations" leftSection={<IconMap size={16} />}>
{t('location.title')} {t("location.title")}
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="certifications" leftSection={<IconCertificate size={16} />}> <Tabs.Tab
{t('certification.title')} value="certifications"
leftSection={<IconCertificate size={16} />}
>
{t("certification.title")}
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>