feat: refactor personal document management to use server-side pagination, grouping, and filtering

This commit is contained in:
estifanos
2026-08-28 11:17:25 +00:00
parent 6f9986acef
commit bf083bff82
5 changed files with 299 additions and 101 deletions

View File

@@ -37,6 +37,8 @@ import type {
TemplateLogoPlacement,
TemplatePageOptions,
TemplateVariable,
PersonalDocumentFilter,
PersonalDocumentGroup,
} from './licensing.types';
/**
@@ -63,6 +65,16 @@ function serialiseQueueFilter(
return params;
}
/** Sends only the facets that are set; `search=` would match nothing. */
function dropEmpty(filter: object): Record<string, unknown> {
const params: Record<string, unknown> = {};
for (const [key, value] of Object.entries(filter)) {
if (value === undefined || value === null || value === '' || value === false) continue;
params[key] = value;
}
return params;
}
const TAGS = [
'LicenseType',
'OperatorType',
@@ -245,6 +257,25 @@ export const licensingApi = baseApi
providesTags: () => [listTag('DocumentRequirement')],
}),
/**
* Personal document slots, grouped by key and paged by the server.
*
* Its own endpoint rather than filtering `getDocumentRequirements` in the
* browser: one document can be configured against several licence types,
* so a page of rows would split a document in half and misreport its
* scope. The server groups first, then pages.
*/
getPersonalDocuments: builder.query<
Paginated<PersonalDocumentGroup>,
PersonalDocumentFilter | void
>({
query: (filter) => ({
url: '/document-requirements/personal',
params: dropEmpty(filter ?? {}),
}),
providesTags: () => [listTag('DocumentRequirement')],
}),
createDocumentRequirement: builder.mutation<
DocumentRequirement,
// No `licenseTypeId` means a personal document, required for every
@@ -1109,6 +1140,7 @@ export const {
useValidateFormSchemaMutation,
useGetFormSchemaPaletteQuery,
useGetDocumentRequirementsQuery,
useGetPersonalDocumentsQuery,
useCreateDocumentRequirementMutation,
useUpdateDocumentRequirementMutation,
useDeleteDocumentRequirementMutation,

View File

@@ -761,6 +761,31 @@ export interface IssuedLicense {
certificateFileKey: string | null;
}
/**
* One personal document as the backoffice manages it: every configured row
* sharing a key, which is one slot in the applicant's vault. Several rows mean
* the document is scoped to several licence types.
*/
export interface PersonalDocumentGroup {
key: string;
rows: DocumentRequirement[];
}
export interface PersonalDocumentFilter {
/** Matches the key and the name in either locale. */
search?: string;
/** A licence type also matches the documents every licence asks for. */
licenseTypeId?: string;
/** Narrows to the documents configured against no licence type at all. */
globalOnly?: boolean;
sortBy?: 'sortOrder' | 'key' | 'name';
sortDir?: 'ASC' | 'DESC';
take?: number;
skip?: number;
/** Which locale `sortBy: "name"` sorts on. */
locale?: 'en' | 'am';
}
/** One sitting offered to a claimed examined-certificate application, scoped to its rank. */
export interface EligibleExam {
id: string;