feat: implement ModalFooter component for consistent modal action layout

This commit is contained in:
estifanos
2026-08-05 08:50:38 +00:00
parent c8df8e1578
commit bbae230ad5
21 changed files with 152 additions and 74 deletions

View File

@@ -44,7 +44,7 @@ import {
useUpdateLicenseTemplateMutation,
type LicenseTemplate,
} from '@ema-platform/api';
import { EmptyState, ErrorState, PageHeader } from '@ema-platform/ui';
import { EmptyState, ErrorState, ModalFooter, PageHeader } from '@ema-platform/ui';
import { authStorage, usePermissions } from '@ema-platform/auth';
import { PERMISSIONS } from '../../../layouts/nav-config';
@@ -529,7 +529,7 @@ export function CertificateDesignerPage() {
'Starts from the live design, or the built-in layout if this type has none.',
)}
</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={() => setNewOpen(false)}>
{t('common.cancel', 'Cancel')}
</Button>
@@ -550,7 +550,7 @@ export function CertificateDesignerPage() {
>
{t('designer.create', 'Create')}
</Button>
</Group>
</ModalFooter>
</Stack>
</Modal>
</Container>

View File

@@ -17,7 +17,7 @@ import {
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCertificate } from '@tabler/icons-react';
import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui';
import {
useGetCertificationsQuery,
useCreateCertificationMutation,
@@ -199,10 +199,10 @@ export function CertificationPage() {
<Modal opened={deleteOpened} onClose={closeDelete} title={t('certification.confirmDelete')} size="sm">
<Text mb="md">{t('certification.deleteConfirmText', { name: deleteTarget?.name?.[locale] })}</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">{t('certification.cancel')}</Button>
<Button color="red" onClick={handleDelete} size="sm">{t('certification.delete')}</Button>
</Group>
</ModalFooter>
</Modal>
</Stack>
);

View File

@@ -33,6 +33,7 @@ import {
useErrorHandler,
AdvancedTable,
useServerTable,
ModalFooter,
type AdvancedColumn,
} from "@ema-platform/ui";
import { LocationPage } from "../../location/pages/LocationPage";
@@ -388,14 +389,14 @@ function ProfessionTab() {
name: deleteTarget?.name?.[locale] ?? "",
})}
</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">
{t("configuration.cancel")}
</Button>
<Button color="red" onClick={confirmDeleteProf} size="sm">
{t("configuration.delete")}
</Button>
</Group>
</ModalFooter>
</Modal>
</>
);

View File

@@ -40,7 +40,7 @@ import {
IconX,
IconDetails
} from "@tabler/icons-react";
import { notify, useErrorHandler } from "@ema-platform/ui";
import { notify, useErrorHandler, ModalFooter } from "@ema-platform/ui";
import {
useGetExamQuery,
useUpdateExamMutation,
@@ -494,14 +494,14 @@ export function ExamDetailPage() {
mode="manual"
actions={setWhatAction}
/>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeAssign} size="sm">
{t("exam.cancel")}
</Button>
<Button onClick={handleAssign} size="sm" loading={isAssigning}>
{t("exam.saveAssignments")}
</Button>
</Group>
</ModalFooter>
</>
) : (
<>
@@ -531,14 +531,14 @@ export function ExamDetailPage() {
onChange={setDraftQuestions}
mode="random"
/>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeAssign} size="sm">
{t("exam.cancel")}
</Button>
<Button onClick={handleAssign} size="sm" loading={isAssigning}>
{t("exam.saveAssignments")}
</Button>
</Group>
</ModalFooter>
</>
)}
</Stack>

View File

@@ -31,7 +31,7 @@ import {
IconClipboardList,
IconDetails,
} from "@tabler/icons-react";
import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from "@ema-platform/ui";
import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from "@ema-platform/ui";
import { useGetCertificationsQuery } from "../../certification/api/certification-api";
import {
useGetExamsQuery,
@@ -600,14 +600,14 @@ export function ExamPage() {
name: deleteTarget?.title?.[locale] ?? "",
})}
</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">
{t("exam.cancel")}
</Button>
<Button color="red" onClick={handleDelete} size="sm">
{t("exam.delete")}
</Button>
</Group>
</ModalFooter>
</Modal>
</Stack>
);

View File

@@ -3,7 +3,6 @@ import {
Alert,
Button,
Checkbox,
Group,
Modal,
Select,
Stack,
@@ -13,6 +12,7 @@ import {
} from '@mantine/core';
import { IconAlertTriangle } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { ModalFooter } from '@ema-platform/ui';
import type { ResolvedAction } from '../config/actions';
/** Reason codes offered per action. Free text is always available too. */
@@ -290,7 +290,7 @@ export function DecisionConfirmModal({
/>
)}
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={onClose}>
{t('common.cancel', 'Cancel')}
</Button>
@@ -310,7 +310,7 @@ export function DecisionConfirmModal({
>
{t(action.labelKey)}
</Button>
</Group>
</ModalFooter>
</Stack>
</Modal>
);

View File

@@ -57,7 +57,7 @@ import {
useScheduleInspectionMutation,
type RemarkTargetType,
} from '@ema-platform/api';
import { ErrorState } from '@ema-platform/ui';
import { ErrorState, ModalFooter } from '@ema-platform/ui';
import { usePermissions } from '@ema-platform/auth';
import { useAppSelector } from '../../../store/hooks';
import { DecisionBar } from '../components/DecisionBar';
@@ -894,7 +894,7 @@ export function LicenseReviewPage() {
value={inspectionDate}
onChange={(e) => setInspectionDate(e.currentTarget.value)}
/>
<Group justify="flex-end">
<ModalFooter>
<Tooltip
label={t('review.pickDate', 'Pick a date and time first')}
disabled={Boolean(inspectionDate)}
@@ -924,7 +924,7 @@ export function LicenseReviewPage() {
>
<IconCheck size={18} />
</ActionIcon>
</Group>
</ModalFooter>
</Stack>
</Modal>
@@ -942,7 +942,7 @@ export function LicenseReviewPage() {
autosize
minRows={3}
/>
<Group grow>
<ModalFooter grow>
<ActionIcon
variant="light"
color="teal"
@@ -989,7 +989,7 @@ export function LicenseReviewPage() {
>
<IconX size={18} />
</ActionIcon>
</Group>
</ModalFooter>
</Stack>
</Modal>
</Container>

View File

@@ -17,7 +17,7 @@ import {
import { useDisclosure } from '@mantine/hooks';
import { IconSettings, IconMap, IconInfoCircle, IconPlus } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { notify, useErrorHandler } from '@ema-platform/ui';
import { notify, useErrorHandler, ModalFooter } from '@ema-platform/ui';
import { LocationTree } from '../components/LocationTree';
import { LocationDetail } from '../components/LocationDetail';
import { LocationForm } from '../components/LocationForm';
@@ -226,14 +226,14 @@ export function LocationPage() {
name: selectedLocation?.names[locale] ?? '',
})}
</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDeleteModal} size="sm">
{t('location.cancel')}
</Button>
<Button color="red" onClick={handleDeleteConfirm} size="sm">
{t('location.delete')}
</Button>
</Group>
</ModalFooter>
</Modal>
<LocationTypeModal opened={typeModalOpened} onClose={closeTypeModal} />

View File

@@ -26,7 +26,7 @@ import {
IconInfoCircle,
IconLock,
} from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { notify, ModalFooter } from '@ema-platform/ui';
import {
extractErrorMessage,
localized,
@@ -364,14 +364,14 @@ function FeeEditModal({
</>
)}
<Group justify="flex-end" mt="xs">
<ModalFooter mt="xs">
<Button variant="default" onClick={onClose} disabled={isLoading}>
Cancel
</Button>
<Button onClick={save} loading={isLoading}>
Save fees
</Button>
</Group>
</ModalFooter>
</Stack>
)}
</Modal>

View File

@@ -18,7 +18,7 @@ import {
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { IconEdit, IconTrash, IconPlus, IconInfoCircle } from '@tabler/icons-react';
import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui';
import { useGetCertificationsQuery } from '../../certification/api/certification-api';
import {
useGetQuestionsQuery,
@@ -255,10 +255,10 @@ export function QuestionPage() {
<Modal opened={deleteOpened} onClose={closeDelete} title={t('question.confirmDelete')} size="sm">
<Text mb="md">{t('question.deleteConfirmText')}</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">{t('question.cancel')}</Button>
<Button color="red" onClick={handleDelete} size="sm">{t('question.delete')}</Button>
</Group>
</ModalFooter>
</Modal>
</Stack>
);

View File

@@ -17,7 +17,7 @@ import {
Alert,
} from '@mantine/core';
import { IconInfoCircle, IconCheck, IconX } from '@tabler/icons-react';
import { notify, useErrorHandler } from '@ema-platform/ui';
import { notify, useErrorHandler, ModalFooter } from '@ema-platform/ui';
import { useApiQuery } from '@ema-platform/api';
import { useCreateResultMutation, useUpdateResultMutation } from '../api/result-api';
import type { Exam } from '../../exam/types/exam';
@@ -195,12 +195,12 @@ export function RecordResultModal({
size="sm"
/>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={onClose} size="sm">{t('result.cancel')}</Button>
<Button onClick={handleSave} size="sm" loading={isSaving}>
{t('result.saveResult')}
</Button>
</Group>
</ModalFooter>
</>
)}

View File

@@ -36,7 +36,7 @@ import {
IconChartBar,
IconSearch,
} from '@tabler/icons-react';
import { notify, BilingualInput, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import { notify, BilingualInput, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui';
import type { BilingualValue } from '@ema-platform/ui';
import { useGetResultsQuery, useLazyGetResultQuery, useDeleteResultMutation, useUpdateResultMutation } from '../api/result-api';
import { useGetExamsQuery } from '../../exam/api/exam-api';
@@ -462,7 +462,7 @@ export function ResultPage() {
</>
)}
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={handleDetailClose} size="sm">{t('result.close')}</Button>
<Button
onClick={handleDetailSave}
@@ -472,7 +472,7 @@ export function ResultPage() {
>
{t('result.save')}
</Button>
</Group>
</ModalFooter>
</Stack>
) : (
<Text c="dimmed" ta="center" py="xl">{t('result.noData')}</Text>
@@ -481,10 +481,10 @@ export function ResultPage() {
<Modal opened={deleteOpened} onClose={closeDelete} title={t('result.confirmDelete')} size="sm">
<Text mb="md">{t('result.deleteConfirmText')}</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">{t('result.cancel')}</Button>
<Button color="red" onClick={handleDelete} size="sm">{t('result.delete')}</Button>
</Group>
</ModalFooter>
</Modal>
{/* Choose exam, then record */}
@@ -501,10 +501,10 @@ export function ResultPage() {
searchable
required
/>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={closePicker} size="sm">{t('result.cancel')}</Button>
<Button onClick={startRecord} size="sm" disabled={!pickerExamId}>{t('result.continue')}</Button>
</Group>
</ModalFooter>
</Stack>
</Modal>

View File

@@ -30,7 +30,7 @@ import {
IconSettings,
IconTrash,
} from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { notify, ModalFooter } from '@ema-platform/ui';
// ---------------------------------------------------------------------------
// Types
@@ -495,12 +495,12 @@ export function VesselRegistrationFormBuilderPage() {
onChange={(e) => setDraftRequired(e.currentTarget.checked)}
/>
<Divider />
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={() => setDrawerOpen(false)}>Cancel</Button>
<Button color="teal" onClick={saveField}>
{isNew ? 'Add Field' : 'Save Changes'}
</Button>
</Group>
</ModalFooter>
</Stack>
</Drawer>
@@ -516,10 +516,10 @@ export function VesselRegistrationFormBuilderPage() {
Are you sure you want to remove <strong>{deleteTarget?.label}</strong> from the form?
This cannot be undone.
</Text>
<Group justify="flex-end">
<ModalFooter>
<Button variant="default" onClick={() => setDeleteTarget(null)}>Cancel</Button>
<Button color="red" onClick={confirmDelete}>Delete Field</Button>
</Group>
</ModalFooter>
</Stack>
</Modal>
</Stack>