feat: add ExamStageActions component for exam fee handling

- Implemented ExamStageActions component to manage actions related to exam booking and payment based on application status.
- Added mock-base-query for development, providing a partial mock backend for various API endpoints.
- Introduced mock-data for simulating responses in the mock-base-query, covering profiles, vessels, applications, licenses, exams, and notifications.
This commit is contained in:
fitse-yotor
2026-08-15 11:51:14 +03:00
parent d8b01a2003
commit 0ac75669d4
34 changed files with 2638 additions and 472 deletions

View File

@@ -0,0 +1,84 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import type { LicenseTemplate } from '@ema-platform/api';
/**
* Editor state for the selected version.
*
* Selection defaults to the live design because that is the one staff usually
* open, and the fields reset whenever the selection changes so an edit can
* never leak from one version into another.
*/
export function useTemplateDraft(templates: LicenseTemplate[]) {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [source, setSource] = useState('');
const [name, setName] = useState('');
const [landscape, setLandscape] = useState(true);
const [backgroundUrl, setBackgroundUrl] = useState('');
const editorRef = useRef<HTMLTextAreaElement>(null);
const selected = useMemo(
() => templates.find((tpl) => tpl.id === selectedId) ?? null,
[templates, selectedId],
);
useEffect(() => {
if (!templates.length) {
setSelectedId(null);
return;
}
if (selectedId && templates.some((tpl) => tpl.id === selectedId)) return;
const published = templates.find((tpl) => tpl.status === 'PUBLISHED');
setSelectedId((published ?? templates[0]).id);
}, [templates, selectedId]);
useEffect(() => {
if (!selected) return;
setSource(selected.hbsSource);
setName(selected.name);
setLandscape(selected.pageOptions?.landscape ?? true);
setBackgroundUrl(selected.backgroundUrl ?? '');
}, [selected]);
const isPublished = selected?.status === 'PUBLISHED';
const dirty =
Boolean(selected) &&
(source !== selected?.hbsSource ||
name !== selected?.name ||
landscape !== (selected?.pageOptions?.landscape ?? true) ||
backgroundUrl !== (selected?.backgroundUrl ?? ''));
/** Inserts a placeholder where the caret is, rather than at the end. */
function insertVariable(key: string) {
const el = editorRef.current;
const token = `{{${key}}}`;
if (!el) {
setSource((prev) => prev + token);
return;
}
const start = el.selectionStart ?? source.length;
const end = el.selectionEnd ?? start;
setSource(source.slice(0, start) + token + source.slice(end));
requestAnimationFrame(() => {
el.focus();
el.setSelectionRange(start + token.length, start + token.length);
});
}
return {
selected,
selectedId,
setSelectedId,
source,
setSource,
name,
setName,
landscape,
setLandscape,
backgroundUrl,
setBackgroundUrl,
editorRef,
isPublished,
dirty,
insertVariable,
};
}