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

View File

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