feat: add pickup appointment scheduling and management features

- Introduced new pickup appointment functionalities in the licensing API, including scheduling, rescheduling, and managing pickup offices.
- Added UI components for the pickup desk, allowing officers to check in, issue documents, and manage no-show appointments.
- Implemented a new page for managing pickup offices with CRUD operations.
- Enhanced internationalization support for new pickup-related terms and messages.
- Updated licensing types to include new application kinds and issuance periods.
- Created a read-only panel for displaying scheduled pickup appointments in the licensing component.
This commit is contained in:
fitse-yotor
2026-08-28 15:49:05 +03:00
parent 12e7927d01
commit 687e3df3c2
27 changed files with 1233 additions and 30 deletions

View File

@@ -1,6 +1,7 @@
import { isValidPhoneNumber } from 'libphonenumber-js';
import { resolveTokenFromStorage } from '../../session';
import type {
ApplicationKind,
Bilingual,
FamilyKind,
FormFieldConfig,
@@ -10,6 +11,11 @@ import type {
ValidationIssue,
} from './licensing.types';
/** A section with no `applicationKinds` applies to every kind, as before that field existed. */
function sectionAppliesToKind(section: FormSectionConfig, kind: ApplicationKind): boolean {
return !section.applicationKinds?.length || section.applicationKinds.includes(kind);
}
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000/api';
@@ -437,12 +443,28 @@ export function buildWizardSteps(
* instead of showing an empty page.
*/
hasStaff?: boolean;
/**
* Whether this application has any document requirements to upload.
* False for a Damaged/Reissue application, which asks nothing beyond the
* Damage Information step — showing an empty Documents page would be a
* page to click past for nothing.
*/
hasDocuments?: boolean;
/** Active UI language. Components get this from `useLocalized`; this is a
* pure function, so the caller passes `i18n.language` through. */
language?: string;
/**
* The application's kind — NEW unless the caller is renewing or
* reissuing. A section scoped to a different kind via
* `applicationKinds` is left out entirely, the same as a `showWhen`
* that never holds.
*/
applicationKind?: ApplicationKind;
},
): WizardStep[] {
const kind = options?.applicationKind ?? 'NEW';
const visible = [...sections]
.filter((section) => sectionAppliesToKind(section, kind))
.filter((section) => conditionHolds(section.showWhen, formData))
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
@@ -497,7 +519,9 @@ export function buildWizardSteps(
...(options?.hasStaff === false
? []
: [{ key: 'staff', label: 'Staff', kind: 'staff', sections: [] } as WizardStep]),
{ key: 'documents', label: 'Documents', kind: 'documents', sections: [] },
...(options?.hasDocuments === false
? []
: [{ key: 'documents', label: 'Documents', kind: 'documents', sections: [] } as WizardStep]),
{ key: 'review', label: 'Review', kind: 'review', sections: reviewSections },
];
}