feat: implement comprehensive dashboard UI components, analytics integration, and internationalization support for portal and backoffice apps.

This commit is contained in:
Estifo77
2026-09-03 14:40:55 +03:00
parent 92f2f16b80
commit d66484921b
14 changed files with 3225 additions and 312 deletions

View File

@@ -1,5 +1,7 @@
import { baseApi } from '../../base-api';
import type {
AdminDashboardAnalytics,
ApplicantDashboardSummary,
ExamStateView,
AppNotification,
ApplicationDetail,
@@ -492,6 +494,11 @@ export const licensingApi = baseApi
providesTags: () => [listTag('LicenseApplication')],
}),
getMyDashboardSummary: builder.query<ApplicantDashboardSummary, void>({
query: () => ({ url: '/license-applications/mine/summary' }),
providesTags: () => [listTag('LicenseApplication'), listTag('License')],
}),
getApplication: builder.query<ApplicationDetail, string>({
query: (id) => ({ url: `/license-applications/${id}` }),
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
@@ -746,6 +753,17 @@ export const licensingApi = baseApi
providesTags: () => [listTag('ApplicationQueue')],
}),
getAdminDashboardAnalytics: builder.query<
AdminDashboardAnalytics,
{ period?: string } | void
>({
query: (args) => ({
url: '/license-application-review/dashboard-analytics',
params: args?.period ? { period: args.period } : undefined,
}),
providesTags: () => [listTag('ApplicationQueue'), listTag('License')],
}),
getApplicationForReview: builder.query<ApplicationDetail, string>({
query: (id) => ({ url: `/license-application-review/${id}` }),
providesTags: (_r, _e, id) => [itemTag('LicenseApplication', id)],
@@ -1386,6 +1404,7 @@ export const {
useGetExamStateForApplicationQuery,
useDiscardApplicationMutation,
useGetMyApplicationsQuery,
useGetMyDashboardSummaryQuery,
useGetApplicationQuery,
useInitiatePaymentMutation,
useBypassPaymentMutation,
@@ -1409,6 +1428,7 @@ export const {
useGetAssignedToMeQuery,
useGetAllApplicationsQuery,
useGetQueueCountsQuery,
useGetAdminDashboardAnalyticsQuery,
useGetLicenseTemplatesQuery,
useGetTemplateVariablesQuery,
useGetBuiltInTemplateQuery,

View File

@@ -947,3 +947,121 @@ export interface ExamStateView {
/** Whether the exam engine produced the mark, or a person did. */
autoGraded: boolean | null;
}
export interface ApplicantDashboardSummary {
counts: {
totalApplications: number;
activeLicenses: number;
expiringSoonLicenses: number;
pendingApplications: number;
approvedApplications: number;
draftApplications: number;
actionRequiredApplications: number;
};
statusDistribution: Array<{
name: string;
value: number;
color: string;
}>;
monthlyTrend: Array<{
month: string;
year: number;
submitted: number;
approved: number;
}>;
categoryBreakdown: Array<{
category: string;
count: number;
}>;
}
export interface AdminDashboardAnalytics {
kpis: {
totalApplications: number;
unclaimedQueue: number;
assignedToMe: number;
inProgressTotal: number;
needsApplicant: number;
awaitingPayment: number;
approved: number;
rejected: number;
overdueSla: number;
withinSla: number;
slaComplianceRate: number;
activeLicenses: number;
revenue: {
totalCollected: number;
pendingAmount: number;
currency: string;
};
};
statusDistribution: Array<{
name: string;
value: number;
color: string;
}>;
monthlyTrend: Array<{
month: string;
year: number;
submitted: number;
approved: number;
rejected: number;
revenue: number;
}>;
categoryBreakdown: Array<{
category: string;
count: number;
percentage: number;
}>;
departmentSummary: {
seafarerRegistrations: {
total: number;
submitted: number;
approved: number;
};
seamanBooks: {
total: number;
pending: number;
issued: number;
};
btc: {
total: number;
pending: number;
issued: number;
};
vessels: {
total: number;
active: number;
pending: number;
};
};
oldestUnclaimed: Array<{
id: string;
applicationNumber: string;
companyName: string;
licenseTypeName: string;
category: string;
status: string;
submittedAt: string;
slaHours: number | null;
}>;
urgentApplications: Array<{
id: string;
applicationNumber: string;
companyName: string | null;
licenseTypeName: string;
category: string;
submittedAt: string | null;
hoursLeft: number;
isOverdue: boolean;
}>;
officerWorkload: Array<{
officerId: string;
officerName: string;
activeCount: number;
overdueCount: number;
onScheduleCount: number;
}>;
period?: string;
}