mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-30 15:48:12 +00:00
Merge branch 'dev' of github.com:Tria-plc/emaui into Refactor
Resolved by unifying on the lib/data AdvancedTable (PR #10) as the canonical table component: kept its API plus teammate i18n/feature work, kept the folder-per-table structure (index.tsx + columns.tsx + actions.tsx) across all 27 tables, removed the parallel lib/table implementation, and fixed pre-existing compile breaks in ExamDetailPage/QuestionAssigner/RecordResultModal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,38 +1,40 @@
|
||||
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>, void>({
|
||||
query: () => '/professions',
|
||||
providesTags: ['Api'],
|
||||
getProfessions: builder.query<ListResponse<Profession>, string>({
|
||||
query: (params) => ({
|
||||
url: `/professions?q=${encodeURIComponent(params)}`,
|
||||
}),
|
||||
providesTags: ["Api", "backOfficeApi", "ProfessionApi"],
|
||||
}),
|
||||
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,
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import { IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { AdvancedTableAction } from '@ema-platform/ui';
|
||||
import type { Profession } from '../../types/configuration';
|
||||
import { ActionIcon, Group } from "@mantine/core";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import type { AdvancedColumn } from "@ema-platform/ui";
|
||||
import type { Profession } from "../../types/configuration";
|
||||
|
||||
export function professionColumnActions(
|
||||
t: TFunction,
|
||||
handlers: {
|
||||
onEdit: (prof: Profession) => void;
|
||||
onDelete: (prof: Profession) => void;
|
||||
},
|
||||
): AdvancedTableAction<Profession>[] {
|
||||
return [
|
||||
{
|
||||
key: 'edit',
|
||||
label: t('configuration.edit'),
|
||||
color: 'blue',
|
||||
icon: <IconEdit size={14} />,
|
||||
onClick: handlers.onEdit,
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: t('configuration.delete'),
|
||||
color: 'red',
|
||||
icon: <IconTrash size={14} />,
|
||||
onClick: handlers.onDelete,
|
||||
},
|
||||
];
|
||||
export function professionActionsColumn(handlers: {
|
||||
onEdit: (prof: Profession) => void;
|
||||
onDelete: (prof: Profession) => void;
|
||||
}): AdvancedColumn<Profession> {
|
||||
return {
|
||||
header: "actions",
|
||||
size: 90,
|
||||
cell: ({ row }) => (
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
size="sm"
|
||||
onClick={() => handlers.onEdit(row.original)}
|
||||
>
|
||||
<IconEdit size={14} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => handlers.onDelete(row.original)}
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { AdvancedTableColumn } from '@ema-platform/ui';
|
||||
import type { Profession } from '../../types/configuration';
|
||||
import { Text } from "@mantine/core";
|
||||
import type { TFunction } from "i18next";
|
||||
import type { AdvancedColumn } from "@ema-platform/ui";
|
||||
import type { Profession } from "../../types/configuration";
|
||||
|
||||
export function professionColumns(
|
||||
t: TFunction,
|
||||
locale: 'en' | 'am',
|
||||
locale: "en" | "am",
|
||||
getDeptName: (deptId: string) => string,
|
||||
): AdvancedTableColumn<Profession>[] {
|
||||
): AdvancedColumn<Profession>[] {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
header: t('configuration.name'),
|
||||
render: (prof) => prof.name[locale],
|
||||
header: t("configuration.name"),
|
||||
cell: ({ row }) => row.original.name[locale],
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
header: t('configuration.description'),
|
||||
render: (prof) => (
|
||||
<Text size="sm" lineClamp={2} maw={200}>{prof.description[locale]}</Text>
|
||||
header: t("configuration.description"),
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" lineClamp={2} maw={200}>
|
||||
{row.original.description[locale]}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'department',
|
||||
header: t('configuration.department'),
|
||||
render: (prof) => getDeptName(prof.departmentId),
|
||||
header: t("configuration.department"),
|
||||
cell: ({ row }) => getDeptName(row.original.departmentId),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Title,
|
||||
@@ -10,28 +10,39 @@ import {
|
||||
Modal,
|
||||
Text,
|
||||
Select,
|
||||
Paper,
|
||||
Loader,
|
||||
Center,
|
||||
Alert,
|
||||
} from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconPlus, IconBriefcase, IconMap, IconCertificate, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AdvancedTable, notify } from '@ema-platform/ui';
|
||||
import { professionColumns } from './columns';
|
||||
import { professionColumnActions } from './actions';
|
||||
import { LocationPage } from '../../../location/pages/LocationPage';
|
||||
import { CertificationPage } from '../../../certification/pages/CertificationPage';
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import {
|
||||
IconPlus,
|
||||
IconBriefcase,
|
||||
IconMap,
|
||||
IconCertificate,
|
||||
IconInfoCircle,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
notify,
|
||||
useErrorHandler,
|
||||
AdvancedTable,
|
||||
useServerTable,
|
||||
ModalFooter,
|
||||
} 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";
|
||||
import { professionColumns } from "./columns";
|
||||
import { professionActionsColumn } from "./actions";
|
||||
|
||||
interface ProfFormValues {
|
||||
nameEn: string;
|
||||
@@ -49,14 +60,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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -65,90 +89,125 @@ 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">
|
||||
<Modal
|
||||
opened
|
||||
onClose={onCancel}
|
||||
title={
|
||||
editingProf
|
||||
? t("configuration.update")
|
||||
: t("configuration.addProfession")
|
||||
}
|
||||
size="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">
|
||||
<ModalFooter>
|
||||
<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>
|
||||
</ModalFooter>
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
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 { data: profRes, isLoading, isError, refetch } = useGetProfessionsQuery();
|
||||
const [createProfession, { isLoading: isCreating }] = useCreateProfessionMutation();
|
||||
const [updateProfession, { isLoading: isUpdating }] = useUpdateProfessionMutation();
|
||||
const { pageIndex, setPageIndex, setQ, pageSize, setPageSize, skip, take } =
|
||||
useServerTable({
|
||||
pageSize: 10,
|
||||
});
|
||||
const {
|
||||
data: profRes,
|
||||
isLoading,
|
||||
isFetching,
|
||||
isError,
|
||||
refetch,
|
||||
} = useGetProfessionsQuery(
|
||||
`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 ?? []);
|
||||
// Server now paginates, so the previous client-side `isActive` filter is
|
||||
// dropped (it would hide rows outside just this page). Restore it via a
|
||||
// server-side `q` filter once the backend field name is confirmed.
|
||||
const professions = profRes?.items ?? [];
|
||||
const totalCount = profRes?.total ?? profRes?.count ?? professions.length;
|
||||
|
||||
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);
|
||||
@@ -160,67 +219,100 @@ 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 {
|
||||
notify.error(t('configuration.error'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}, [deleteTarget, deleteProfession, closeDelete, t]);
|
||||
}, [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 {
|
||||
notify.error(t('configuration.error'));
|
||||
}
|
||||
}, [editingProf, createProfession, updateProfession, resetProfForm, t]);
|
||||
},
|
||||
[
|
||||
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 columns = [
|
||||
...professionColumns(t, locale, getDeptName),
|
||||
professionActionsColumn({
|
||||
onEdit: handleEditProf,
|
||||
onDelete: handleDeleteProf,
|
||||
}),
|
||||
];
|
||||
|
||||
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"
|
||||
@@ -228,7 +320,7 @@ function ProfessionTab() {
|
||||
onClick={() => setShowProfForm(true)}
|
||||
size="sm"
|
||||
>
|
||||
{t('configuration.addProfession')}
|
||||
{t("configuration.addProfession")}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
@@ -244,25 +336,39 @@ function ProfessionTab() {
|
||||
)}
|
||||
|
||||
<AdvancedTable
|
||||
columns={professionColumns(t, locale, getDeptName)}
|
||||
data={professions.filter((p) => p.isActive)}
|
||||
rowKey={(prof) => prof.id}
|
||||
actions={professionColumnActions(t, {
|
||||
onEdit: handleEditProf,
|
||||
onDelete: handleDeleteProf,
|
||||
})}
|
||||
onRefresh={refetch}
|
||||
emptyTitle={t('configuration.noProfessions')}
|
||||
columns={columns}
|
||||
data={professions}
|
||||
tableName={t("configuration.professionsList")}
|
||||
itemCount={totalCount}
|
||||
pageIndex={pageIndex}
|
||||
onPageChange={setPageIndex}
|
||||
pageSize={pageSize}
|
||||
onPageSizeChange={setPageSize}
|
||||
refresh={refetch}
|
||||
onSearchChange={setQ}
|
||||
isLoading={isFetching}
|
||||
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>
|
||||
</Group>
|
||||
<ModalFooter>
|
||||
<Button variant="default" onClick={closeDelete} size="sm">
|
||||
{t("configuration.cancel")}
|
||||
</Button>
|
||||
<Button color="red" onClick={confirmDeleteProf} size="sm">
|
||||
{t("configuration.delete")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
@@ -273,18 +379,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>
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ export interface Profession {
|
||||
}
|
||||
|
||||
export interface ListResponse<T> {
|
||||
count: number;
|
||||
count?: number;
|
||||
total?: number;
|
||||
items: T[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user