diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js
index 88527ff..2f81849 100644
--- a/backend/src/db/seeders/20231127130745-sample-data.js
+++ b/backend/src/db/seeders/20231127130745-sample-data.js
@@ -36,6 +36,8 @@ const Departments = db.departments;
const RolePermissions = db.role_permissions;
+const Permissions = db.permissions;
+
const ApprovalWorkflows = db.approval_workflows;
const ApprovalSteps = db.approval_steps;
diff --git a/backend/src/index.js b/backend/src/index.js
index c405d31..3e08ae0 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -121,6 +121,8 @@ const documentsRoutes = require('./routes/documents');
const compliance_alertsRoutes = require('./routes/compliance_alerts');
+const executive_summaryRoutes = require('./routes/executive_summary');
+
const getBaseUrl = (url) => {
if (!url) return '';
@@ -277,6 +279,8 @@ app.use('/api/documents', passport.authenticate('jwt', {session: false}), docume
app.use('/api/compliance_alerts', passport.authenticate('jwt', {session: false}), compliance_alertsRoutes);
+app.use('/api/executive-summary', passport.authenticate('jwt', {session: false}), executive_summaryRoutes);
+
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
diff --git a/backend/src/routes/executive_summary.js b/backend/src/routes/executive_summary.js
new file mode 100644
index 0000000..1e1c70f
--- /dev/null
+++ b/backend/src/routes/executive_summary.js
@@ -0,0 +1,969 @@
+const express = require('express');
+const db = require('../db/models');
+const wrapAsync = require('../helpers').wrapAsync;
+
+const router = express.Router();
+const { Op } = db.Sequelize;
+
+const scopeByOrganization = (currentUser, fieldName = 'organizationId') => {
+ if (currentUser?.app_role?.globalAccess) {
+ return {};
+ }
+
+ const organizationId = currentUser?.organization?.id || currentUser?.organizationId || currentUser?.organizationsId;
+
+ if (!organizationId) {
+ return {};
+ }
+
+ return {
+ [fieldName]: organizationId,
+ };
+};
+
+const toNumber = (value) => {
+ const parsedValue = Number(value || 0);
+
+ if (Number.isNaN(parsedValue)) {
+ return 0;
+ }
+
+ return parsedValue;
+};
+
+const sumCurrency = async (model, field, currency, where, organizationField = 'organizationId') => {
+ const value = await model.sum(field, {
+ where: {
+ ...scopeByOrganization(where.currentUser, organizationField),
+ ...where.filters,
+ currency,
+ },
+ });
+
+ return toNumber(value);
+};
+
+const WORKSPACE_ROLES = {
+ superAdmin: 'Super Administrator',
+ administrator: 'Administrator',
+ directorGeneral: 'Director General',
+ financeDirector: 'Finance Director',
+ procurementLead: 'Procurement Lead',
+ complianceAuditLead: 'Compliance and Audit Lead',
+ projectDeliveryLead: 'Project Delivery Lead',
+};
+
+const formatCurrencyValue = (value, currency) =>
+ new Intl.NumberFormat(currency === 'CDF' ? 'fr-CD' : 'en-US', {
+ style: 'currency',
+ currency,
+ maximumFractionDigits: 0,
+ }).format(toNumber(value));
+
+const formatMetricValue = (summary, metricKey) => {
+ switch (metricKey) {
+ case 'approvedBudget':
+ case 'committedBudget':
+ case 'disbursedBudget':
+ case 'budgetVariance':
+ return `${formatCurrencyValue(summary[metricKey]?.USD, 'USD')} / ${formatCurrencyValue(summary[metricKey]?.CDF, 'CDF')}`;
+ case 'averageProjectProgress':
+ return `${toNumber(summary[metricKey])}%`;
+ default:
+ return `${toNumber(summary[metricKey])}`;
+ }
+};
+
+const getCollectionCount = (datasets, collectionKey) => {
+ const collection = datasets[collectionKey];
+
+ if (Array.isArray(collection)) {
+ return collection.length;
+ }
+
+ return 0;
+};
+
+const workspacePayloadConfigs = {
+ [WORKSPACE_ROLES.superAdmin]: {
+ summaryMetricKeys: ['activeProjects', 'pendingApprovals', 'openRiskAlerts', 'contractsNearingExpiry', 'unreadNotifications', 'vendorComplianceAlerts'],
+ focusCards: [
+ {
+ key: 'super-admin-approvals',
+ title: 'Workflow backlog requiring oversight',
+ metricKey: 'pendingApprovals',
+ note: 'Pending approvals across the accessible ERP scope that may require escalation or reassignment.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'super-admin-alerts',
+ title: 'Open risk exposure',
+ metricKey: 'openRiskAlerts',
+ note: 'High and critical alerts still unresolved across contracts, vendors, and operational records.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'super-admin-projects',
+ title: 'Live execution footprint',
+ metricKey: 'activeProjects',
+ note: 'Projects currently moving through the institution and contributing to workload volume.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'super-admin-notifications',
+ title: 'Unread notices',
+ metricKey: 'unreadNotifications',
+ note: 'Unread workflow, control, and system notices that may hide operational friction.',
+ href: '/notifications/notifications-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'super-admin-approval-queue',
+ title: 'Escalation approval inbox',
+ collectionKey: 'approvalQueue',
+ note: 'Oldest pending decisions visible from the platform oversight layer.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'super-admin-risk-watch',
+ title: 'Platform red-flag watchlist',
+ collectionKey: 'riskPanel',
+ note: 'The most severe open alerts currently demanding follow-up.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'super-admin-notification-watch',
+ title: 'Unread institutional notices',
+ collectionKey: 'recentNotifications',
+ note: 'Recent notifications that may indicate configuration or workflow issues.',
+ href: '/notifications/notifications-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.administrator]: {
+ summaryMetricKeys: ['pendingApprovals', 'unreadNotifications', 'contractsNearingExpiry', 'openRiskAlerts', 'activeProjects', 'procurementPipeline'],
+ focusCards: [
+ {
+ key: 'admin-approvals',
+ title: 'Approvals awaiting operational follow-through',
+ metricKey: 'pendingApprovals',
+ note: 'Work items that often surface workflow setup gaps, delegation issues, or stalled administrative action.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'admin-notifications',
+ title: 'Unread admin-facing notices',
+ metricKey: 'unreadNotifications',
+ note: 'Recent notifications that can point to user support, assignment, or setup problems.',
+ href: '/notifications/notifications-list',
+ },
+ {
+ key: 'admin-contracts',
+ title: 'Contracts nearing renewal attention',
+ metricKey: 'contractsNearingExpiry',
+ note: 'Active contracts nearing expiry and likely to trigger administrative coordination.',
+ href: '/contracts/contracts-list',
+ },
+ {
+ key: 'admin-pipeline',
+ title: 'Procurement workload in motion',
+ metricKey: 'procurementPipeline',
+ note: 'Requisitions advancing through review, tendering, or award that can pressure operations teams.',
+ href: '/requisitions/requisitions-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'admin-approval-queue',
+ title: 'Operational approval queue',
+ collectionKey: 'approvalQueue',
+ note: 'A quick read on which approvals need routing or user follow-up.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'admin-procurement-queue',
+ title: 'Live procurement queue',
+ collectionKey: 'procurementQueue',
+ note: 'Requisitions that may be blocked by setup, documentation, or coordination issues.',
+ href: '/requisitions/requisitions-list',
+ },
+ {
+ key: 'admin-notification-watch',
+ title: 'Recent administrative notices',
+ collectionKey: 'recentNotifications',
+ note: 'Recent events to help admins spot support and process issues early.',
+ href: '/notifications/notifications-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.directorGeneral]: {
+ summaryMetricKeys: ['approvedBudget', 'committedBudget', 'disbursedBudget', 'pendingApprovals', 'highRiskProjects', 'contractsNearingExpiry'],
+ focusCards: [
+ {
+ key: 'dg-budget',
+ title: 'Approved budget envelope',
+ metricKey: 'approvedBudget',
+ note: 'Current allocation envelope available for strategic oversight and institutional steering.',
+ href: '/allocations/allocations-list',
+ },
+ {
+ key: 'dg-disbursed',
+ title: 'Disbursements already released',
+ metricKey: 'disbursedBudget',
+ note: 'Payments processed so far, useful for comparing commitment and delivery momentum.',
+ href: '/payments/payments-list',
+ },
+ {
+ key: 'dg-risk-projects',
+ title: 'Projects under elevated risk',
+ metricKey: 'highRiskProjects',
+ note: 'Projects flagged high or critical risk and likely to require executive attention.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'dg-contracts',
+ title: 'Contracts nearing decision point',
+ metricKey: 'contractsNearingExpiry',
+ note: 'Active contracts approaching expiry that may affect program continuity or exposure.',
+ href: '/contracts/contracts-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'dg-contract-watch',
+ title: 'Executive contract watchlist',
+ collectionKey: 'contractWatchlist',
+ note: 'Contracts closest to expiry or requiring strategic follow-up.',
+ href: '/contracts/contracts-list',
+ },
+ {
+ key: 'dg-risk-watch',
+ title: 'Strategic risk watchlist',
+ collectionKey: 'riskPanel',
+ note: 'High-severity alerts that can affect institutional delivery credibility.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'dg-rollout-watch',
+ title: 'Province rollout overview',
+ collectionKey: 'provinceRollout',
+ note: 'Delivery footprint by province to help spot uneven execution patterns.',
+ href: '/projects/projects-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.financeDirector]: {
+ summaryMetricKeys: ['approvedBudget', 'committedBudget', 'disbursedBudget', 'overduePayments', 'pendingApprovals', 'unreadNotifications'],
+ focusCards: [
+ {
+ key: 'finance-approved',
+ title: 'Approved budget base',
+ metricKey: 'approvedBudget',
+ note: 'The fiscal envelope currently approved and available for expenditure control.',
+ href: '/allocations/allocations-list',
+ },
+ {
+ key: 'finance-committed',
+ title: 'Committed demand',
+ metricKey: 'committedBudget',
+ note: 'Requisition value moving toward tender, award, or contract conversion.',
+ href: '/requisitions/requisitions-list',
+ },
+ {
+ key: 'finance-disbursed',
+ title: 'Disbursed amount',
+ metricKey: 'disbursedBudget',
+ note: 'Processed payments already released, useful for cash-control tracking.',
+ href: '/payments/payments-list',
+ },
+ {
+ key: 'finance-overdue',
+ title: 'Aging payment requests',
+ metricKey: 'overduePayments',
+ note: 'Submitted or approved requests older than 30 days and likely creating vendor pressure.',
+ href: '/payment_requests/payment_requests-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'finance-payment-watch',
+ title: 'Payment control watchlist',
+ collectionKey: 'recentNotifications',
+ note: 'Recent notices that may affect settlement timing or finance follow-through.',
+ href: '/notifications/notifications-list',
+ },
+ {
+ key: 'finance-approval-watch',
+ title: 'Approvals blocking spend',
+ collectionKey: 'approvalQueue',
+ note: 'Pending approvals that can delay commitments, invoicing, or disbursement.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'finance-contract-watch',
+ title: 'Contract value watchlist',
+ collectionKey: 'topContracts',
+ note: 'Largest contracts in scope for exposure review and budget tracking.',
+ href: '/contracts/contracts-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.procurementLead]: {
+ summaryMetricKeys: ['procurementPipeline', 'pendingApprovals', 'contractsNearingExpiry', 'vendorComplianceAlerts', 'openRiskAlerts', 'activeProjects'],
+ focusCards: [
+ {
+ key: 'procurement-pipeline',
+ title: 'Live requisition pipeline',
+ metricKey: 'procurementPipeline',
+ note: 'Requisitions actively progressing through review, tender, and award stages.',
+ href: '/requisitions/requisitions-list',
+ },
+ {
+ key: 'procurement-approvals',
+ title: 'Approvals slowing procurement',
+ metricKey: 'pendingApprovals',
+ note: 'Pending workflow actions that can delay conversion from request to tender or contract.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'procurement-contracts',
+ title: 'Contracts close to expiry',
+ metricKey: 'contractsNearingExpiry',
+ note: 'Active contracts approaching expiry and likely to require extension or replacement planning.',
+ href: '/contracts/contracts-list',
+ },
+ {
+ key: 'procurement-vendor-alerts',
+ title: 'Vendor compliance issues',
+ metricKey: 'vendorComplianceAlerts',
+ note: 'Open missing-document or expiry alerts that may block award or execution readiness.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'procurement-queue',
+ title: 'Priority procurement queue',
+ collectionKey: 'procurementQueue',
+ note: 'Newest or most urgent requisitions requiring action from the procurement desk.',
+ href: '/requisitions/requisitions-list',
+ },
+ {
+ key: 'procurement-contract-watch',
+ title: 'Expiring contract watchlist',
+ collectionKey: 'contractWatchlist',
+ note: 'Contracts most likely to create continuity or sourcing pressure soon.',
+ href: '/contracts/contracts-list',
+ },
+ {
+ key: 'procurement-approval-watch',
+ title: 'Approval blockers to clear',
+ collectionKey: 'approvalQueue',
+ note: 'Pending approvals tied to the demand-to-award flow.',
+ href: '/approvals/approvals-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.complianceAuditLead]: {
+ summaryMetricKeys: ['openRiskAlerts', 'vendorComplianceAlerts', 'contractsNearingExpiry', 'pendingApprovals', 'overduePayments', 'unreadNotifications'],
+ focusCards: [
+ {
+ key: 'compliance-risks',
+ title: 'Open control alerts',
+ metricKey: 'openRiskAlerts',
+ note: 'High and critical alerts requiring audit or compliance follow-up.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'compliance-vendors',
+ title: 'Vendor evidence gaps',
+ metricKey: 'vendorComplianceAlerts',
+ note: 'Vendor-related missing-document and expiry issues that can undermine compliance posture.',
+ href: '/vendors/vendors-list',
+ },
+ {
+ key: 'compliance-contracts',
+ title: 'Contracts nearing obligation review',
+ metricKey: 'contractsNearingExpiry',
+ note: 'Expiring contracts that may require evidence review, amendments, or closure checks.',
+ href: '/contracts/contracts-list',
+ },
+ {
+ key: 'compliance-payments',
+ title: 'Aging payment exposure',
+ metricKey: 'overduePayments',
+ note: 'Older payment requests that can indicate weak controls or missing approvals.',
+ href: '/payment_requests/payment_requests-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'compliance-risk-watch',
+ title: 'Highest-severity red flags',
+ collectionKey: 'riskPanel',
+ note: 'Open alerts with the strongest likelihood of control failure or audit finding.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'compliance-approval-watch',
+ title: 'Approvals with control exposure',
+ collectionKey: 'approvalQueue',
+ note: 'Pending approvals where evidence, delegation, or routing should be verified.',
+ href: '/approvals/approvals-list',
+ },
+ {
+ key: 'compliance-notification-watch',
+ title: 'Recent control notices',
+ collectionKey: 'recentNotifications',
+ note: 'Notifications that can help trace exceptions and follow-up points.',
+ href: '/notifications/notifications-list',
+ },
+ ],
+ },
+ [WORKSPACE_ROLES.projectDeliveryLead]: {
+ summaryMetricKeys: ['activeProjects', 'averageProjectProgress', 'highRiskProjects', 'pendingApprovals', 'overduePayments', 'unreadNotifications'],
+ focusCards: [
+ {
+ key: 'delivery-projects',
+ title: 'Projects in execution',
+ metricKey: 'activeProjects',
+ note: 'Active projects currently moving through implementation and field follow-through.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'delivery-progress',
+ title: 'Average execution progress',
+ metricKey: 'averageProjectProgress',
+ note: 'Average completion across approved and active projects in the accessible delivery scope.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'delivery-risks',
+ title: 'Projects under elevated risk',
+ metricKey: 'highRiskProjects',
+ note: 'Projects marked high or critical risk and likely to affect delivery commitments.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'delivery-payments',
+ title: 'Payment requests creating delivery lag',
+ metricKey: 'overduePayments',
+ note: 'Aging payment requests that can block contractors, milestones, or field mobilization.',
+ href: '/payment_requests/payment_requests-list',
+ },
+ ],
+ watchlistCards: [
+ {
+ key: 'delivery-rollout-watch',
+ title: 'Province rollout snapshot',
+ collectionKey: 'provinceRollout',
+ note: 'Provincial delivery concentration and completion spread across the current portfolio.',
+ href: '/projects/projects-list',
+ },
+ {
+ key: 'delivery-risk-watch',
+ title: 'Delivery risk watchlist',
+ collectionKey: 'riskPanel',
+ note: 'Open alerts most likely to disrupt project execution.',
+ href: '/compliance_alerts/compliance_alerts-list',
+ },
+ {
+ key: 'delivery-approval-watch',
+ title: 'Approvals affecting execution',
+ collectionKey: 'approvalQueue',
+ note: 'Pending approvals that can delay works, procurement, or milestone completion.',
+ href: '/approvals/approvals-list',
+ },
+ ],
+ },
+};
+
+const buildWorkspacePayload = (roleName, summary, datasets) => {
+ const roleConfig = workspacePayloadConfigs[roleName] || workspacePayloadConfigs[WORKSPACE_ROLES.projectDeliveryLead];
+
+ return {
+ roleName: roleName || WORKSPACE_ROLES.projectDeliveryLead,
+ summaryMetricKeys: roleConfig.summaryMetricKeys,
+ focusCards: roleConfig.focusCards.map((card) => ({
+ key: card.key,
+ title: card.title,
+ value: formatMetricValue(summary, card.metricKey),
+ note: card.note,
+ href: card.href,
+ })),
+ watchlistCards: roleConfig.watchlistCards.map((card) => ({
+ key: card.key,
+ title: card.title,
+ count: getCollectionCount(datasets, card.collectionKey),
+ note: card.note,
+ href: card.href,
+ })),
+ };
+};
+
+router.get(
+ '/',
+ wrapAsync(async (req, res) => {
+ const currentUser = req.currentUser;
+ const now = new Date();
+ const contractExpiryWindow = new Date(now);
+ const overduePaymentThreshold = new Date(now);
+
+ contractExpiryWindow.setDate(contractExpiryWindow.getDate() + 60);
+ overduePaymentThreshold.setDate(overduePaymentThreshold.getDate() - 30);
+
+ const allocationFilters = {
+ currentUser,
+ filters: {
+ status: {
+ [Op.in]: ['approved', 'active'],
+ },
+ },
+ };
+
+ const requisitionFilters = {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['submitted', 'under_review', 'approved', 'in_tender', 'awarded', 'converted_to_contract'],
+ },
+ };
+
+ const paymentFilters = {
+ ...scopeByOrganization(currentUser),
+ status: 'processed',
+ };
+
+ const [
+ approvedBudgetUSD,
+ approvedBudgetCDF,
+ committedBudgetUSD,
+ committedBudgetCDF,
+ disbursedBudgetUSD,
+ disbursedBudgetCDF,
+ activeProjects,
+ procurementPipeline,
+ pendingApprovals,
+ contractsNearingExpiry,
+ overduePayments,
+ vendorComplianceAlerts,
+ openRiskAlerts,
+ unreadNotifications,
+ averageProjectProgress,
+ highRiskProjects,
+ topContracts,
+ contractWatchlist,
+ approvalQueue,
+ procurementQueue,
+ riskPanel,
+ rolloutProjects,
+ recentNotifications,
+ ] = await Promise.all([
+ sumCurrency(db.allocations, 'amount', 'USD', allocationFilters, 'organizationsId'),
+ sumCurrency(db.allocations, 'amount', 'CDF', allocationFilters, 'organizationsId'),
+ sumCurrency(db.requisitions, 'estimated_amount', 'USD', { currentUser, filters: requisitionFilters }),
+ sumCurrency(db.requisitions, 'estimated_amount', 'CDF', { currentUser, filters: requisitionFilters }),
+ sumCurrency(db.payments, 'amount', 'USD', { currentUser, filters: paymentFilters }),
+ sumCurrency(db.payments, 'amount', 'CDF', { currentUser, filters: paymentFilters }),
+ db.projects.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: 'active',
+ },
+ }),
+ db.requisitions.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['submitted', 'under_review', 'approved', 'in_tender', 'awarded'],
+ },
+ },
+ }),
+ db.approvals.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: 'pending',
+ },
+ }),
+ db.contracts.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: 'active',
+ end_date: {
+ [Op.between]: [now, contractExpiryWindow],
+ },
+ },
+ }),
+ db.payment_requests.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['submitted', 'under_review', 'approved', 'batched'],
+ },
+ requested_at: {
+ [Op.lte]: overduePaymentThreshold,
+ },
+ },
+ }),
+ db.compliance_alerts.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['open', 'acknowledged'],
+ },
+ alert_type: {
+ [Op.in]: ['vendor_document_expiry', 'missing_document'],
+ },
+ },
+ }),
+ db.compliance_alerts.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['open', 'acknowledged'],
+ },
+ severity: {
+ [Op.in]: ['high', 'critical'],
+ },
+ },
+ }),
+ db.notifications.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ read: false,
+ },
+ }),
+ db.projects.aggregate('completion_percent', 'avg', {
+ plain: true,
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['approved', 'active', 'on_hold', 'completed'],
+ },
+ },
+ }),
+ db.projects.count({
+ where: {
+ ...scopeByOrganization(currentUser),
+ risk_level: {
+ [Op.in]: ['high', 'critical'],
+ },
+ status: {
+ [Op.in]: ['approved', 'active', 'on_hold'],
+ },
+ },
+ }),
+ db.contracts.findAll({
+ attributes: ['id', 'contract_number', 'title', 'contract_value', 'currency', 'end_date', 'status'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['active', 'suspended', 'completed', 'expired'],
+ },
+ },
+ include: [
+ {
+ model: db.vendors,
+ as: 'vendor',
+ attributes: ['name'],
+ },
+ {
+ model: db.projects,
+ as: 'project',
+ attributes: ['name'],
+ },
+ ],
+ order: [['contract_value', 'DESC']],
+ limit: 5,
+ }),
+ db.contracts.findAll({
+ attributes: ['id', 'contract_number', 'title', 'contract_value', 'currency', 'end_date', 'status'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: 'active',
+ end_date: {
+ [Op.between]: [now, contractExpiryWindow],
+ },
+ },
+ include: [
+ {
+ model: db.vendors,
+ as: 'vendor',
+ attributes: ['name'],
+ },
+ {
+ model: db.projects,
+ as: 'project',
+ attributes: ['name'],
+ },
+ ],
+ order: [['end_date', 'ASC']],
+ limit: 6,
+ }),
+ db.approvals.findAll({
+ attributes: ['id', 'record_type', 'record_key', 'requested_at', 'status'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: 'pending',
+ },
+ include: [
+ {
+ model: db.approval_workflows,
+ as: 'workflow',
+ attributes: ['name', 'module'],
+ },
+ {
+ model: db.approval_steps,
+ as: 'step',
+ attributes: ['name', 'step_order'],
+ },
+ {
+ model: db.users,
+ as: 'requested_by_user',
+ attributes: ['firstName', 'lastName', 'email'],
+ },
+ {
+ model: db.users,
+ as: 'assigned_to_user',
+ attributes: ['firstName', 'lastName', 'email'],
+ },
+ ],
+ order: [['requested_at', 'ASC']],
+ limit: 8,
+ }),
+ db.requisitions.findAll({
+ attributes: [
+ 'id',
+ 'requisition_number',
+ 'title',
+ 'procurement_method',
+ 'estimated_amount',
+ 'currency',
+ 'needed_by_date',
+ 'status',
+ ],
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['submitted', 'under_review', 'approved', 'in_tender', 'awarded'],
+ },
+ },
+ include: [
+ {
+ model: db.provinces,
+ as: 'province',
+ attributes: ['name', 'code'],
+ },
+ ],
+ order: [['updatedAt', 'DESC']],
+ limit: 8,
+ }),
+ db.compliance_alerts.findAll({
+ attributes: ['id', 'alert_type', 'severity', 'title', 'details', 'record_type', 'record_key', 'due_at', 'status'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ status: {
+ [Op.in]: ['open', 'acknowledged'],
+ },
+ },
+ include: [
+ {
+ model: db.users,
+ as: 'assigned_to_user',
+ attributes: ['firstName', 'lastName', 'email'],
+ },
+ ],
+ order: [
+ ['severity', 'DESC'],
+ ['due_at', 'ASC'],
+ ['createdAt', 'DESC'],
+ ],
+ limit: 8,
+ }),
+ db.projects.findAll({
+ attributes: ['id', 'status', 'completion_percent'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ },
+ include: [
+ {
+ model: db.provinces,
+ as: 'province',
+ attributes: ['name'],
+ },
+ ],
+ }),
+ db.notifications.findAll({
+ attributes: ['id', 'type', 'title', 'message', 'read', 'sent_at', 'record_type', 'record_key'],
+ where: {
+ ...scopeByOrganization(currentUser),
+ },
+ order: [['sent_at', 'DESC'], ['createdAt', 'DESC']],
+ limit: 6,
+ }),
+ ]);
+
+ const provinceRolloutMap = rolloutProjects.reduce((accumulator, project) => {
+ const provinceName = project.province?.name || 'Unassigned province';
+
+ if (!accumulator[provinceName]) {
+ accumulator[provinceName] = {
+ provinceName,
+ totalProjects: 0,
+ activeProjects: 0,
+ completionTotal: 0,
+ };
+ }
+
+ accumulator[provinceName].totalProjects += 1;
+ accumulator[provinceName].completionTotal += toNumber(project.completion_percent);
+
+ if (project.status === 'active') {
+ accumulator[provinceName].activeProjects += 1;
+ }
+
+ return accumulator;
+ }, {});
+
+ const provinceRollout = Object.values(provinceRolloutMap)
+ .map((province) => ({
+ provinceName: province.provinceName,
+ totalProjects: province.totalProjects,
+ activeProjects: province.activeProjects,
+ averageCompletion: province.totalProjects
+ ? Math.round(province.completionTotal / province.totalProjects)
+ : 0,
+ }))
+ .sort((left, right) => right.totalProjects - left.totalProjects)
+ .slice(0, 6);
+
+ const formattedApprovalQueue = approvalQueue.map((approval) => ({
+ id: approval.id,
+ recordType: approval.record_type,
+ recordKey: approval.record_key,
+ status: approval.status,
+ requestedAt: approval.requested_at,
+ workflowName: approval.workflow?.name || 'Workflow not assigned',
+ stepName: approval.step?.name || 'No active step',
+ stepOrder: approval.step?.step_order || null,
+ requestedBy: approval.requested_by_user
+ ? `${approval.requested_by_user.firstName || ''} ${approval.requested_by_user.lastName || ''}`.trim() || approval.requested_by_user.email
+ : 'Unknown requester',
+ assignedTo: approval.assigned_to_user
+ ? `${approval.assigned_to_user.firstName || ''} ${approval.assigned_to_user.lastName || ''}`.trim() || approval.assigned_to_user.email
+ : 'Unassigned',
+ }));
+
+ const formattedProcurementQueue = procurementQueue.map((requisition) => ({
+ id: requisition.id,
+ requisitionNumber: requisition.requisition_number,
+ title: requisition.title,
+ procurementMethod: requisition.procurement_method,
+ estimatedAmount: toNumber(requisition.estimated_amount),
+ currency: requisition.currency,
+ neededByDate: requisition.needed_by_date,
+ status: requisition.status,
+ provinceName: requisition.province?.name || 'Unassigned province',
+ }));
+
+ const formattedContractWatchlist = contractWatchlist.map((contract) => ({
+ id: contract.id,
+ contractNumber: contract.contract_number,
+ title: contract.title,
+ contractValue: toNumber(contract.contract_value),
+ currency: contract.currency,
+ endDate: contract.end_date,
+ status: contract.status,
+ vendorName: contract.vendor?.name || 'Vendor not linked',
+ projectName: contract.project?.name || 'Project not linked',
+ daysToExpiry: contract.end_date
+ ? Math.ceil((new Date(contract.end_date).getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
+ : null,
+ }));
+
+ const formattedTopContracts = topContracts.map((contract) => ({
+ id: contract.id,
+ contractNumber: contract.contract_number,
+ title: contract.title,
+ contractValue: toNumber(contract.contract_value),
+ currency: contract.currency,
+ endDate: contract.end_date,
+ status: contract.status,
+ vendorName: contract.vendor?.name || 'Vendor not linked',
+ projectName: contract.project?.name || 'Project not linked',
+ }));
+
+ const formattedRiskPanel = riskPanel.map((alert) => ({
+ id: alert.id,
+ alertType: alert.alert_type,
+ severity: alert.severity,
+ title: alert.title,
+ details: alert.details,
+ recordType: alert.record_type,
+ recordKey: alert.record_key,
+ dueAt: alert.due_at,
+ status: alert.status,
+ assignedTo: alert.assigned_to_user
+ ? `${alert.assigned_to_user.firstName || ''} ${alert.assigned_to_user.lastName || ''}`.trim() || alert.assigned_to_user.email
+ : 'Unassigned',
+ }));
+
+ const formattedNotifications = recentNotifications.map((notification) => ({
+ id: notification.id,
+ type: notification.type,
+ title: notification.title,
+ message: notification.message,
+ read: notification.read,
+ sentAt: notification.sent_at,
+ recordType: notification.record_type,
+ recordKey: notification.record_key,
+ }));
+
+ const summary = {
+ approvedBudget: {
+ USD: approvedBudgetUSD,
+ CDF: approvedBudgetCDF,
+ },
+ committedBudget: {
+ USD: committedBudgetUSD,
+ CDF: committedBudgetCDF,
+ },
+ disbursedBudget: {
+ USD: disbursedBudgetUSD,
+ CDF: disbursedBudgetCDF,
+ },
+ budgetVariance: {
+ USD: approvedBudgetUSD - committedBudgetUSD,
+ CDF: approvedBudgetCDF - committedBudgetCDF,
+ },
+ activeProjects,
+ procurementPipeline,
+ pendingApprovals,
+ contractsNearingExpiry,
+ overduePayments,
+ vendorComplianceAlerts,
+ openRiskAlerts,
+ unreadNotifications,
+ averageProjectProgress: Math.round(toNumber(averageProjectProgress) || 0),
+ highRiskProjects,
+ };
+
+ const datasets = {
+ approvalQueue: formattedApprovalQueue,
+ procurementQueue: formattedProcurementQueue,
+ contractWatchlist: formattedContractWatchlist,
+ topContracts: formattedTopContracts,
+ riskPanel: formattedRiskPanel,
+ provinceRollout,
+ recentNotifications: formattedNotifications,
+ };
+
+ res.status(200).json({
+ workspace: buildWorkspacePayload(currentUser?.app_role?.name, summary, datasets),
+ summary,
+ ...datasets,
+ });
+ }),
+);
+
+module.exports = router;
diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx
index c645325..5dd780a 100644
--- a/frontend/src/components/AsideMenuLayer.tsx
+++ b/frontend/src/components/AsideMenuLayer.tsx
@@ -3,10 +3,8 @@ import { mdiLogout, mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
-import { useAppSelector } from '../stores/hooks'
+import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Link from 'next/link';
-
-import { useAppDispatch } from '../stores/hooks';
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
diff --git a/frontend/src/components/AsideMenuList.tsx b/frontend/src/components/AsideMenuList.tsx
index 9e33ea1..9ef6d84 100644
--- a/frontend/src/components/AsideMenuList.tsx
+++ b/frontend/src/components/AsideMenuList.tsx
@@ -1,8 +1,9 @@
import React from 'react'
import { MenuAsideItem } from '../interfaces'
import AsideMenuItem from './AsideMenuItem'
-import {useAppSelector} from "../stores/hooks";
-import {hasPermission} from "../helpers/userPermissions";
+import { useAppSelector } from '../stores/hooks';
+import { hasPermission } from '../helpers/userPermissions';
+import { getWorkspaceConfig, itemVisibleForRole } from '../helpers/workspace';
type Props = {
menu: MenuAsideItem[]
@@ -15,16 +16,28 @@ export default function AsideMenuList({ menu, isDropdownList = false, className
if (!currentUser) return null;
+ const roleName = currentUser?.app_role?.name;
+ const workspaceConfig = getWorkspaceConfig(roleName);
+
return (
{menu.map((item, index) => {
-
- if (!hasPermission(currentUser, item.permissions)) return null;
-
+ if (!itemVisibleForRole(item.roles, roleName)) return null;
+ if (!hasPermission(currentUser, item.permissions)) return null;
+
+ const displayItem: MenuAsideItem = {
+ ...item,
+ label: item.labelByRole?.[roleName] || item.label,
+ };
+
+ if (displayItem.label === 'Role Workspace') {
+ displayItem.label = workspaceConfig.sidebarLabel;
+ }
+
return (
diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index 72935e6..fcbd9b9 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef} from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
diff --git a/frontend/src/helpers/workspace.ts b/frontend/src/helpers/workspace.ts
new file mode 100644
index 0000000..2bde41c
--- /dev/null
+++ b/frontend/src/helpers/workspace.ts
@@ -0,0 +1,629 @@
+export const WORKSPACE_ROLES = {
+ superAdmin: 'Super Administrator',
+ administrator: 'Administrator',
+ directorGeneral: 'Director General',
+ financeDirector: 'Finance Director',
+ procurementLead: 'Procurement Lead',
+ complianceAuditLead: 'Compliance and Audit Lead',
+ projectDeliveryLead: 'Project Delivery Lead',
+ public: 'Public',
+} as const;
+
+export type WorkspaceMetricKey =
+ | 'approvedBudget'
+ | 'committedBudget'
+ | 'disbursedBudget'
+ | 'pendingApprovals'
+ | 'contractsNearingExpiry'
+ | 'vendorComplianceAlerts'
+ | 'procurementPipeline'
+ | 'openRiskAlerts'
+ | 'averageProjectProgress'
+ | 'highRiskProjects'
+ | 'overduePayments'
+ | 'activeProjects'
+ | 'unreadNotifications';
+
+export type WorkspaceDetailBlockKey =
+ | 'focus'
+ | 'summary'
+ | 'watchlist'
+ | 'approvalRisk'
+ | 'operations'
+ | 'delivery'
+ | 'actions';
+
+export type WorkspaceSectionKey =
+ | 'approvalQueue'
+ | 'riskPanel'
+ | 'procurementQueue'
+ | 'contractWatchlist'
+ | 'recentNotifications'
+ | 'provinceRollout'
+ | 'topContracts'
+ | 'quickActions';
+
+export type WorkspaceQuickLinkIconKey =
+ | 'organizations'
+ | 'users'
+ | 'approvals'
+ | 'notifications'
+ | 'projects'
+ | 'contracts'
+ | 'payments'
+ | 'allocations'
+ | 'requisitions'
+ | 'tenders'
+ | 'compliance'
+ | 'audit'
+ | 'milestones'
+ | 'vendors';
+
+export interface WorkspaceAction {
+ href: string;
+ label: string;
+}
+
+export interface WorkspaceQuickLink {
+ href: string;
+ label: string;
+ description: string;
+ icon: WorkspaceQuickLinkIconKey;
+}
+
+export interface WorkspaceSectionCopy {
+ eyebrow: string;
+ title: string;
+ actionLabel?: string;
+}
+
+export interface WorkspaceConfig {
+ sidebarLabel: string;
+ pageTitle: string;
+ eyebrow: string;
+ heroTitle: string;
+ heroDescription: string;
+ primaryAction: WorkspaceAction;
+ secondaryAction: WorkspaceAction;
+ highlightedMetricKeys: WorkspaceMetricKey[];
+ heroMetricKeys: WorkspaceMetricKey[];
+ blockOrder: WorkspaceDetailBlockKey[];
+ sectionCopy: Record;
+ quickLinks: WorkspaceQuickLink[];
+}
+
+type WorkspaceConfigInput = Omit & {
+ heroMetricKeys?: WorkspaceMetricKey[];
+ blockOrder?: WorkspaceDetailBlockKey[];
+ sectionCopy?: Partial>;
+ quickLinks?: WorkspaceQuickLink[];
+};
+
+const defaultBlockOrder: WorkspaceDetailBlockKey[] = [
+ 'focus',
+ 'summary',
+ 'watchlist',
+ 'approvalRisk',
+ 'operations',
+ 'delivery',
+ 'actions',
+];
+
+const defaultSectionCopy: Record = {
+ approvalQueue: {
+ eyebrow: 'Approval inbox',
+ title: 'Pending institutional decisions',
+ actionLabel: 'Open approvals',
+ },
+ riskPanel: {
+ eyebrow: 'Risk and red-flag panel',
+ title: 'Compliance, audit, and payment exposure',
+ },
+ procurementQueue: {
+ eyebrow: 'Procurement workflow',
+ title: 'Live procurement pipeline',
+ actionLabel: 'All requisitions',
+ },
+ contractWatchlist: {
+ eyebrow: 'Contract watchlist',
+ title: 'Expiring commitments',
+ actionLabel: 'Contract register',
+ },
+ recentNotifications: {
+ eyebrow: 'Notifications',
+ title: 'Recent operational signals',
+ actionLabel: 'Notification center',
+ },
+ provinceRollout: {
+ eyebrow: 'Geographic rollout',
+ title: 'Projects by province',
+ actionLabel: 'Projects register',
+ },
+ topContracts: {
+ eyebrow: 'Top-value contracts',
+ title: 'Largest commitments currently tracked',
+ actionLabel: 'Vendor master',
+ },
+ quickActions: {
+ eyebrow: 'Action launcher',
+ title: 'Move from oversight to execution',
+ },
+};
+
+const defaultQuickLinks: WorkspaceQuickLink[] = [
+ {
+ href: '/requisitions/requisitions-new',
+ label: 'Create requisition',
+ description: 'Start the procurement workflow.',
+ icon: 'requisitions',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Approval inbox',
+ description: 'Review pending decisions and escalations.',
+ icon: 'approvals',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Contract register',
+ description: 'Inspect value, milestones, and expiry status.',
+ icon: 'contracts',
+ },
+ {
+ href: '/audit_logs/audit_logs-list',
+ label: 'Audit center',
+ description: 'Trace actions, timestamps, and record history.',
+ icon: 'audit',
+ },
+];
+
+const createWorkspaceConfig = ({
+ heroMetricKeys,
+ blockOrder,
+ sectionCopy,
+ quickLinks,
+ ...config
+}: WorkspaceConfigInput): WorkspaceConfig => ({
+ ...config,
+ heroMetricKeys: heroMetricKeys || config.highlightedMetricKeys.slice(0, 4),
+ blockOrder: blockOrder || defaultBlockOrder,
+ sectionCopy: {
+ ...defaultSectionCopy,
+ ...sectionCopy,
+ },
+ quickLinks: quickLinks || defaultQuickLinks,
+});
+
+const workspaceConfigs: Record = {
+ [WORKSPACE_ROLES.superAdmin]: createWorkspaceConfig({
+ sidebarLabel: 'Platform Command',
+ pageTitle: 'Super Administrator Workspace',
+ eyebrow: 'FDSU ERP · Super Administrator workspace',
+ heroTitle: 'Oversee tenants, access control, institutional activity, and platform-level risk from one command surface.',
+ heroDescription:
+ 'This workspace prioritizes cross-organization visibility, access governance, auditability, and system stewardship while keeping drill-down access into the same ERP records used by operating teams.',
+ primaryAction: { href: '/organizations/organizations-list', label: 'Review organizations' },
+ secondaryAction: { href: '/users/users-list', label: 'Manage users' },
+ highlightedMetricKeys: ['activeProjects', 'pendingApprovals', 'openRiskAlerts', 'contractsNearingExpiry', 'unreadNotifications', 'vendorComplianceAlerts'],
+ heroMetricKeys: ['activeProjects', 'pendingApprovals', 'openRiskAlerts', 'unreadNotifications'],
+ blockOrder: ['focus', 'watchlist', 'summary', 'approvalRisk', 'operations', 'delivery', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Governance queue',
+ title: 'Approvals and escalations needing platform attention',
+ actionLabel: 'Open governance queue',
+ },
+ riskPanel: {
+ eyebrow: 'Cross-cutting control flags',
+ title: 'Platform-wide exceptions and institutional exposure',
+ },
+ recentNotifications: {
+ eyebrow: 'Platform activity',
+ title: 'Recent operational signals across tenants',
+ actionLabel: 'Open activity feed',
+ },
+ quickActions: {
+ eyebrow: 'Platform actions',
+ title: 'Jump directly into oversight and access-control work',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/organizations/organizations-list',
+ label: 'Organization registry',
+ description: 'Review tenant setup and coverage.',
+ icon: 'organizations',
+ },
+ {
+ href: '/users/users-list',
+ label: 'User access',
+ description: 'Manage accounts, roles, and ownership.',
+ icon: 'users',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Escalation queue',
+ description: 'Follow stalled approvals and control breaks.',
+ icon: 'approvals',
+ },
+ {
+ href: '/audit_logs/audit_logs-list',
+ label: 'Audit trail',
+ description: 'Trace administrative and ERP record changes.',
+ icon: 'audit',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.administrator]: createWorkspaceConfig({
+ sidebarLabel: 'Operations Command',
+ pageTitle: 'Administrator Workspace',
+ eyebrow: 'FDSU ERP · Administrator workspace',
+ heroTitle: 'Run tenant operations, workflow readiness, user support, and master data hygiene without losing ERP traceability.',
+ heroDescription:
+ 'This workspace is tuned for organization-level administration: approval setup, departments, notifications, operational bottlenecks, and administrative follow-through across the shared ERP.',
+ primaryAction: { href: '/approval_workflows/approval_workflows-list', label: 'Review workflows' },
+ secondaryAction: { href: '/notifications/notifications-list', label: 'Check notifications' },
+ highlightedMetricKeys: ['pendingApprovals', 'unreadNotifications', 'contractsNearingExpiry', 'openRiskAlerts', 'activeProjects', 'procurementPipeline'],
+ heroMetricKeys: ['pendingApprovals', 'unreadNotifications', 'procurementPipeline', 'contractsNearingExpiry'],
+ blockOrder: ['focus', 'summary', 'approvalRisk', 'watchlist', 'operations', 'delivery', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Operations queue',
+ title: 'Approvals waiting on workflow administration',
+ actionLabel: 'Open workflow queue',
+ },
+ procurementQueue: {
+ eyebrow: 'Operational throughput',
+ title: 'Requisitions that may require admin unblock',
+ actionLabel: 'Open requisitions',
+ },
+ quickActions: {
+ eyebrow: 'Administrative actions',
+ title: 'Go straight to configuration and operational follow-through',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/approval_workflows/approval_workflows-list',
+ label: 'Workflow setup',
+ description: 'Review approval design and routing.',
+ icon: 'approvals',
+ },
+ {
+ href: '/notifications/notifications-list',
+ label: 'Notification center',
+ description: 'Resolve unread system and user signals.',
+ icon: 'notifications',
+ },
+ {
+ href: '/users/users-list',
+ label: 'User support',
+ description: 'Update access and account records.',
+ icon: 'users',
+ },
+ {
+ href: '/projects/projects-list',
+ label: 'Project register',
+ description: 'Verify record coverage and ownership.',
+ icon: 'projects',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.directorGeneral]: createWorkspaceConfig({
+ sidebarLabel: 'Executive Command',
+ pageTitle: 'Director General Workspace',
+ eyebrow: 'FDSU ERP · Director General workspace',
+ heroTitle: 'Track strategic budget use, delivery momentum, risk concentration, and contract exposure across the institution.',
+ heroDescription:
+ 'This workspace emphasizes executive oversight over budgets, projects, approvals, and risk hot spots while staying connected to the underlying contracts, requisitions, and implementation records.',
+ primaryAction: { href: '/projects/projects-list', label: 'Review projects' },
+ secondaryAction: { href: '/contracts/contracts-list', label: 'Review contracts' },
+ highlightedMetricKeys: ['approvedBudget', 'committedBudget', 'disbursedBudget', 'pendingApprovals', 'highRiskProjects', 'contractsNearingExpiry'],
+ heroMetricKeys: ['approvedBudget', 'disbursedBudget', 'pendingApprovals', 'highRiskProjects'],
+ blockOrder: ['summary', 'focus', 'watchlist', 'delivery', 'approvalRisk', 'operations', 'actions'],
+ sectionCopy: {
+ riskPanel: {
+ eyebrow: 'Executive risk watch',
+ title: 'Strategic risks, bottlenecks, and control exposure',
+ },
+ provinceRollout: {
+ eyebrow: 'Delivery coverage',
+ title: 'Provincial rollout and execution pace',
+ actionLabel: 'Open projects',
+ },
+ topContracts: {
+ eyebrow: 'Contract exposure',
+ title: 'Largest commitments shaping institutional delivery',
+ actionLabel: 'Vendor and contract view',
+ },
+ quickActions: {
+ eyebrow: 'Executive actions',
+ title: 'Jump into the records most relevant for strategic follow-up',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/projects/projects-list',
+ label: 'Project portfolio',
+ description: 'Review delivery status and ownership.',
+ icon: 'projects',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Contract exposure',
+ description: 'Inspect major commitments and expiry dates.',
+ icon: 'contracts',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Decision queue',
+ description: 'Track items waiting for institutional action.',
+ icon: 'approvals',
+ },
+ {
+ href: '/notifications/notifications-list',
+ label: 'Executive signals',
+ description: 'Review the latest alerts and escalations.',
+ icon: 'notifications',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.financeDirector]: createWorkspaceConfig({
+ sidebarLabel: 'Financial Control',
+ pageTitle: 'Finance Director Workspace',
+ eyebrow: 'FDSU ERP · Finance Director workspace',
+ heroTitle: 'Monitor allocations, commitments, invoices, payments, and approval pressure with finance-first operational context.',
+ heroDescription:
+ 'This workspace focuses on fiscal control across allocations, commitments, disbursements, overdue payment requests, and approvals while remaining linked to the procurement and project records driving spend.',
+ primaryAction: { href: '/payment_requests/payment_requests-list', label: 'Review payment requests' },
+ secondaryAction: { href: '/allocations/allocations-list', label: 'Review allocations' },
+ highlightedMetricKeys: ['approvedBudget', 'committedBudget', 'disbursedBudget', 'overduePayments', 'pendingApprovals', 'unreadNotifications'],
+ heroMetricKeys: ['approvedBudget', 'committedBudget', 'disbursedBudget', 'overduePayments'],
+ blockOrder: ['summary', 'watchlist', 'focus', 'approvalRisk', 'delivery', 'operations', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Finance queue',
+ title: 'Approvals affecting commitments and disbursements',
+ actionLabel: 'Open finance approvals',
+ },
+ riskPanel: {
+ eyebrow: 'Fiscal control panel',
+ title: 'Budget pressure, overdue payments, and control exceptions',
+ },
+ topContracts: {
+ eyebrow: 'High-value commitments',
+ title: 'Largest contracts influencing current financial exposure',
+ actionLabel: 'Open vendor master',
+ },
+ quickActions: {
+ eyebrow: 'Finance actions',
+ title: 'Move from budget control into payment and allocation follow-up',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/payment_requests/payment_requests-list',
+ label: 'Payment requests',
+ description: 'Review overdue and pending disbursements.',
+ icon: 'payments',
+ },
+ {
+ href: '/allocations/allocations-list',
+ label: 'Allocations',
+ description: 'Check available headroom and funding coverage.',
+ icon: 'allocations',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Commitment register',
+ description: 'Inspect the contracts driving spend.',
+ icon: 'contracts',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Approval pressure',
+ description: 'Track bottlenecks holding back finance action.',
+ icon: 'approvals',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.procurementLead]: createWorkspaceConfig({
+ sidebarLabel: 'Procurement Desk',
+ pageTitle: 'Procurement Lead Workspace',
+ eyebrow: 'FDSU ERP · Procurement Lead workspace',
+ heroTitle: 'Stay on top of requisitions, tender flow, contract deadlines, and approval backlog across the procurement chain.',
+ heroDescription:
+ 'This workspace keeps procurement leadership focused on live demand, tender progression, expiring contracts, and workflow bottlenecks while preserving drill-down into vendors, awards, and originating requisitions.',
+ primaryAction: { href: '/requisitions/requisitions-list', label: 'Review requisitions' },
+ secondaryAction: { href: '/tenders/tenders-list', label: 'Open tender pipeline' },
+ highlightedMetricKeys: ['procurementPipeline', 'pendingApprovals', 'contractsNearingExpiry', 'vendorComplianceAlerts', 'openRiskAlerts', 'activeProjects'],
+ heroMetricKeys: ['procurementPipeline', 'pendingApprovals', 'contractsNearingExpiry', 'vendorComplianceAlerts'],
+ blockOrder: ['focus', 'summary', 'operations', 'watchlist', 'approvalRisk', 'delivery', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Procurement approvals',
+ title: 'Decisions blocking sourcing and award progress',
+ actionLabel: 'Open approval backlog',
+ },
+ procurementQueue: {
+ eyebrow: 'Sourcing pipeline',
+ title: 'Requisitions and tenders requiring procurement action',
+ actionLabel: 'Open sourcing queue',
+ },
+ contractWatchlist: {
+ eyebrow: 'Contract renewals',
+ title: 'Supplier commitments nearing expiry or follow-up',
+ actionLabel: 'Open contracts',
+ },
+ quickActions: {
+ eyebrow: 'Procurement actions',
+ title: 'Jump into the next sourcing and contract-management tasks',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/requisitions/requisitions-list',
+ label: 'Requisition queue',
+ description: 'Review demand and readiness to source.',
+ icon: 'requisitions',
+ },
+ {
+ href: '/tenders/tenders-list',
+ label: 'Tender pipeline',
+ description: 'Follow active tenders and consultations.',
+ icon: 'tenders',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Contract deadlines',
+ description: 'Track expiring commitments and supplier action.',
+ icon: 'contracts',
+ },
+ {
+ href: '/vendors/vendors-list',
+ label: 'Vendor register',
+ description: 'Inspect supplier readiness and history.',
+ icon: 'vendors',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.complianceAuditLead]: createWorkspaceConfig({
+ sidebarLabel: 'Compliance Desk',
+ pageTitle: 'Compliance & Audit Workspace',
+ eyebrow: 'FDSU ERP · Compliance & Audit workspace',
+ heroTitle: 'Surface red flags, document gaps, control exceptions, and approval exposure before they become institutional failures.',
+ heroDescription:
+ 'This workspace is optimized for compliance and audit follow-up: open alerts, expiring obligations, evidence gaps, approvals awaiting control review, and links into the exact underlying ERP records.',
+ primaryAction: { href: '/compliance_alerts/compliance_alerts-list', label: 'Review compliance alerts' },
+ secondaryAction: { href: '/audit_logs/audit_logs-list', label: 'Open audit logs' },
+ highlightedMetricKeys: ['openRiskAlerts', 'vendorComplianceAlerts', 'contractsNearingExpiry', 'pendingApprovals', 'overduePayments', 'unreadNotifications'],
+ heroMetricKeys: ['openRiskAlerts', 'vendorComplianceAlerts', 'contractsNearingExpiry', 'pendingApprovals'],
+ blockOrder: ['watchlist', 'focus', 'summary', 'approvalRisk', 'operations', 'delivery', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Control review queue',
+ title: 'Approvals and cases requiring audit follow-through',
+ actionLabel: 'Open control queue',
+ },
+ riskPanel: {
+ eyebrow: 'Compliance watch',
+ title: 'Exceptions, overdue items, and evidence gaps',
+ },
+ recentNotifications: {
+ eyebrow: 'Alert traffic',
+ title: 'Recent system signals and policy-relevant activity',
+ actionLabel: 'Open alert feed',
+ },
+ quickActions: {
+ eyebrow: 'Audit actions',
+ title: 'Go straight to alerts, logs, and records needing review',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/compliance_alerts/compliance_alerts-list',
+ label: 'Compliance alerts',
+ description: 'Review open red flags and follow-up status.',
+ icon: 'compliance',
+ },
+ {
+ href: '/audit_logs/audit_logs-list',
+ label: 'Audit logs',
+ description: 'Trace evidence across users and records.',
+ icon: 'audit',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Control queue',
+ description: 'Inspect approvals needing governance review.',
+ icon: 'approvals',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Contract obligations',
+ description: 'Check commitments nearing expiry or breach.',
+ icon: 'contracts',
+ },
+ ],
+ }),
+ [WORKSPACE_ROLES.projectDeliveryLead]: createWorkspaceConfig({
+ sidebarLabel: 'Delivery Command',
+ pageTitle: 'Project Delivery Workspace',
+ eyebrow: 'FDSU ERP · Project Delivery workspace',
+ heroTitle: 'Keep execution moving by watching project progress, blocked approvals, payment lag, and field-level delivery risk.',
+ heroDescription:
+ 'This workspace centers on implementation performance: active projects, milestone progress, approvals affecting execution, and risks that can delay delivery across provinces and programs.',
+ primaryAction: { href: '/projects/projects-list', label: 'Review projects' },
+ secondaryAction: { href: '/project_milestones/project_milestones-list', label: 'Review milestones' },
+ highlightedMetricKeys: ['activeProjects', 'averageProjectProgress', 'highRiskProjects', 'pendingApprovals', 'overduePayments', 'unreadNotifications'],
+ heroMetricKeys: ['activeProjects', 'averageProjectProgress', 'highRiskProjects', 'pendingApprovals'],
+ blockOrder: ['summary', 'focus', 'delivery', 'approvalRisk', 'watchlist', 'operations', 'actions'],
+ sectionCopy: {
+ approvalQueue: {
+ eyebrow: 'Execution blockers',
+ title: 'Approvals holding back project delivery',
+ actionLabel: 'Open delivery blockers',
+ },
+ provinceRollout: {
+ eyebrow: 'Delivery heatmap',
+ title: 'Progress by province and execution footprint',
+ actionLabel: 'Open project register',
+ },
+ topContracts: {
+ eyebrow: 'Major delivery contracts',
+ title: 'Largest commitments underpinning project execution',
+ actionLabel: 'Open suppliers',
+ },
+ quickActions: {
+ eyebrow: 'Delivery actions',
+ title: 'Move into the projects, milestones, and blockers that need attention',
+ },
+ },
+ quickLinks: [
+ {
+ href: '/projects/projects-list',
+ label: 'Project portfolio',
+ description: 'Review implementation status and ownership.',
+ icon: 'projects',
+ },
+ {
+ href: '/project_milestones/project_milestones-list',
+ label: 'Milestones',
+ description: 'Track critical delivery checkpoints.',
+ icon: 'milestones',
+ },
+ {
+ href: '/approvals/approvals-list',
+ label: 'Delivery blockers',
+ description: 'See approvals slowing field execution.',
+ icon: 'approvals',
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Supporting contracts',
+ description: 'Inspect contracts affecting delivery scope.',
+ icon: 'contracts',
+ },
+ ],
+ }),
+};
+
+export function getWorkspaceConfig(roleName?: string | null): WorkspaceConfig {
+ if (!roleName) {
+ return workspaceConfigs[WORKSPACE_ROLES.projectDeliveryLead];
+ }
+
+ return workspaceConfigs[roleName] || workspaceConfigs[WORKSPACE_ROLES.projectDeliveryLead];
+}
+
+export function itemVisibleForRole(itemRoles?: string[], roleName?: string | null) {
+ if (!itemRoles?.length) {
+ return true;
+ }
+
+ if (!roleName) {
+ return false;
+ }
+
+ return itemRoles.includes(roleName);
+}
diff --git a/frontend/src/interfaces/index.ts b/frontend/src/interfaces/index.ts
index 0c7dd74..be1b067 100644
--- a/frontend/src/interfaces/index.ts
+++ b/frontend/src/interfaces/index.ts
@@ -14,6 +14,8 @@ export type MenuAsideItem = {
withDevider?: boolean;
menu?: MenuAsideItem[]
permissions?: string | string[]
+ roles?: string[]
+ labelByRole?: Record
}
export type MenuNavBarItem = {
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 1b9907d..73d8391 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,5 +1,4 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index fbbd590..6b5a8e3 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -1,11 +1,132 @@
import * as icon from '@mdi/js';
import { MenuAsideItem } from './interfaces'
+import { WORKSPACE_ROLES } from './helpers/workspace'
const menuAside: MenuAsideItem[] = [
+ {
+ href: '/executive-summary',
+ icon: icon.mdiBankOutline,
+ label: 'Role Workspace',
+ labelByRole: {
+ [WORKSPACE_ROLES.superAdmin]: 'Platform Command',
+ [WORKSPACE_ROLES.administrator]: 'Operations Command',
+ [WORKSPACE_ROLES.directorGeneral]: 'Executive Command',
+ [WORKSPACE_ROLES.financeDirector]: 'Financial Control',
+ [WORKSPACE_ROLES.procurementLead]: 'Procurement Desk',
+ [WORKSPACE_ROLES.complianceAuditLead]: 'Compliance Desk',
+ [WORKSPACE_ROLES.projectDeliveryLead]: 'Delivery Command',
+ },
+ },
{
href: '/dashboard',
icon: icon.mdiViewDashboardOutline,
- label: 'Dashboard',
+ label: 'Platform Widgets',
+ roles: [WORKSPACE_ROLES.superAdmin, WORKSPACE_ROLES.administrator],
+ },
+ {
+ href: '/organizations/organizations-list',
+ label: 'Tenant Oversight',
+ icon: icon.mdiDomain,
+ permissions: 'READ_ORGANIZATIONS',
+ roles: [WORKSPACE_ROLES.superAdmin],
+ withDevider: true,
+ },
+ {
+ href: '/users/users-list',
+ label: 'Access Control',
+ icon: icon.mdiAccountGroup,
+ permissions: 'READ_USERS',
+ roles: [WORKSPACE_ROLES.superAdmin],
+ },
+ {
+ href: '/approval_workflows/approval_workflows-list',
+ label: 'Workflow Readiness',
+ icon: icon.mdiSitemap,
+ permissions: 'READ_APPROVAL_WORKFLOWS',
+ roles: [WORKSPACE_ROLES.administrator],
+ withDevider: true,
+ },
+ {
+ href: '/notifications/notifications-list',
+ label: 'Operational Notices',
+ icon: icon.mdiBellOutline,
+ permissions: 'READ_NOTIFICATIONS',
+ roles: [WORKSPACE_ROLES.administrator],
+ },
+ {
+ href: '/projects/projects-list',
+ label: 'Strategic Portfolio',
+ icon: icon.mdiChartTimelineVariant,
+ permissions: 'READ_PROJECTS',
+ roles: [WORKSPACE_ROLES.directorGeneral],
+ withDevider: true,
+ },
+ {
+ href: '/contracts/contracts-list',
+ label: 'Contract Exposure',
+ icon: icon.mdiFileDocumentOutline,
+ permissions: 'READ_CONTRACTS',
+ roles: [WORKSPACE_ROLES.directorGeneral],
+ },
+ {
+ href: '/payment_requests/payment_requests-list',
+ label: 'Payment Control',
+ icon: icon.mdiCashCheck,
+ permissions: 'READ_PAYMENT_REQUESTS',
+ roles: [WORKSPACE_ROLES.financeDirector],
+ withDevider: true,
+ },
+ {
+ href: '/allocations/allocations-list',
+ label: 'Allocation Register',
+ icon: icon.mdiWalletOutline,
+ permissions: 'READ_ALLOCATIONS',
+ roles: [WORKSPACE_ROLES.financeDirector],
+ },
+ {
+ href: '/requisitions/requisitions-list',
+ label: 'Requisition Queue',
+ icon: icon.mdiClipboardListOutline,
+ permissions: 'READ_REQUISITIONS',
+ roles: [WORKSPACE_ROLES.procurementLead],
+ withDevider: true,
+ },
+ {
+ href: '/tenders/tenders-list',
+ label: 'Tender Pipeline',
+ icon: icon.mdiGavel,
+ permissions: 'READ_TENDERS',
+ roles: [WORKSPACE_ROLES.procurementLead],
+ },
+ {
+ href: '/compliance_alerts/compliance_alerts-list',
+ label: 'Control Exceptions',
+ icon: icon.mdiShieldAlertOutline,
+ permissions: 'READ_COMPLIANCE_ALERTS',
+ roles: [WORKSPACE_ROLES.complianceAuditLead],
+ withDevider: true,
+ },
+ {
+ href: '/audit_logs/audit_logs-list',
+ label: 'Audit Trail',
+ icon: icon.mdiClipboardTextClock,
+ permissions: 'READ_AUDIT_LOGS',
+ roles: [WORKSPACE_ROLES.complianceAuditLead],
+ },
+ {
+ href: '/projects/projects-list',
+ label: 'Delivery Portfolio',
+ icon: icon.mdiChartTimelineVariant,
+ permissions: 'READ_PROJECTS',
+ roles: [WORKSPACE_ROLES.projectDeliveryLead],
+ withDevider: true,
+ },
+ {
+ href: '/project_milestones/project_milestones-list',
+ label: 'Milestone Tracker',
+ icon: icon.mdiFlagCheckered,
+ permissions: 'READ_PROJECT_MILESTONES',
+ roles: [WORKSPACE_ROLES.projectDeliveryLead],
},
{
diff --git a/frontend/src/pages/executive-summary.tsx b/frontend/src/pages/executive-summary.tsx
new file mode 100644
index 0000000..b87f24a
--- /dev/null
+++ b/frontend/src/pages/executive-summary.tsx
@@ -0,0 +1,997 @@
+import {
+ mdiBankOutline,
+ mdiBellOutline,
+ mdiCheckDecagramOutline,
+ mdiChartTimelineVariant,
+ mdiClipboardClockOutline,
+ mdiClipboardListOutline,
+ mdiFileDocumentOutline,
+ mdiPlus,
+ mdiShieldAlertOutline,
+ mdiWalletOutline,
+} from '@mdi/js';
+import axios from 'axios';
+import Head from 'next/head';
+import Link from 'next/link';
+import React, { ReactElement, ReactNode, useEffect, useMemo, useState } from 'react';
+import BaseButton from '../components/BaseButton';
+import BaseButtons from '../components/BaseButtons';
+import BaseIcon from '../components/BaseIcon';
+import CardBox from '../components/CardBox';
+import NotificationBar from '../components/NotificationBar';
+import SectionMain from '../components/SectionMain';
+import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
+import {
+ getWorkspaceConfig,
+ type WorkspaceDetailBlockKey,
+ type WorkspaceMetricKey,
+ type WorkspaceQuickLinkIconKey,
+} from '../helpers/workspace';
+import LayoutAuthenticated from '../layouts/Authenticated';
+import { getPageTitle } from '../config';
+import { useAppSelector } from '../stores/hooks';
+
+interface BudgetByCurrency {
+ USD: number;
+ CDF: number;
+}
+
+interface Summary {
+ approvedBudget: BudgetByCurrency;
+ committedBudget: BudgetByCurrency;
+ disbursedBudget: BudgetByCurrency;
+ budgetVariance: BudgetByCurrency;
+ activeProjects: number;
+ procurementPipeline: number;
+ pendingApprovals: number;
+ contractsNearingExpiry: number;
+ overduePayments: number;
+ vendorComplianceAlerts: number;
+ openRiskAlerts: number;
+ unreadNotifications: number;
+ averageProjectProgress: number;
+ highRiskProjects: number;
+}
+
+interface ApprovalItem {
+ id: string;
+ recordType: string;
+ recordKey: string;
+ status: string;
+ requestedAt: string;
+ workflowName: string;
+ stepName: string;
+ stepOrder: number | null;
+ requestedBy: string;
+ assignedTo: string;
+}
+
+interface ProcurementItem {
+ id: string;
+ requisitionNumber: string;
+ title: string;
+ procurementMethod: string;
+ estimatedAmount: number;
+ currency: 'USD' | 'CDF';
+ neededByDate: string;
+ status: string;
+ provinceName: string;
+}
+
+interface ContractItem {
+ id: string;
+ contractNumber: string;
+ title: string;
+ contractValue: number;
+ currency: 'USD' | 'CDF';
+ endDate: string;
+ status: string;
+ vendorName: string;
+ projectName: string;
+ daysToExpiry?: number | null;
+}
+
+interface RiskItem {
+ id: string;
+ alertType: string;
+ severity: string;
+ title: string;
+ details: string;
+ recordType: string;
+ recordKey: string;
+ dueAt: string;
+ status: string;
+ assignedTo: string;
+}
+
+interface ProvinceItem {
+ provinceName: string;
+ totalProjects: number;
+ activeProjects: number;
+ averageCompletion: number;
+}
+
+interface NotificationItem {
+ id: string;
+ type: string;
+ title: string;
+ message: string;
+ read: boolean;
+ sentAt: string;
+ recordType: string;
+ recordKey: string;
+}
+
+interface WorkspaceFocusCard {
+ key: string;
+ title: string;
+ value: string;
+ note: string;
+ href: string;
+}
+
+interface WorkspaceWatchlistCard {
+ key: string;
+ title: string;
+ count: number;
+ note: string;
+ href: string;
+}
+
+interface WorkspacePayload {
+ roleName: string;
+ summaryMetricKeys: Array;
+ focusCards: WorkspaceFocusCard[];
+ watchlistCards: WorkspaceWatchlistCard[];
+}
+
+interface ExecutiveSummaryResponse {
+ workspace?: WorkspacePayload;
+ summary: Summary;
+ approvalQueue: ApprovalItem[];
+ procurementQueue: ProcurementItem[];
+ contractWatchlist: ContractItem[];
+ topContracts: ContractItem[];
+ riskPanel: RiskItem[];
+ provinceRollout: ProvinceItem[];
+ recentNotifications: NotificationItem[];
+}
+
+interface MetricDefinition {
+ title: string;
+ value: string;
+ note: string;
+ icon: string;
+}
+
+const defaultResponse: ExecutiveSummaryResponse = {
+ workspace: {
+ roleName: '',
+ summaryMetricKeys: [],
+ focusCards: [],
+ watchlistCards: [],
+ },
+ summary: {
+ approvedBudget: { USD: 0, CDF: 0 },
+ committedBudget: { USD: 0, CDF: 0 },
+ disbursedBudget: { USD: 0, CDF: 0 },
+ budgetVariance: { USD: 0, CDF: 0 },
+ activeProjects: 0,
+ procurementPipeline: 0,
+ pendingApprovals: 0,
+ contractsNearingExpiry: 0,
+ overduePayments: 0,
+ vendorComplianceAlerts: 0,
+ openRiskAlerts: 0,
+ unreadNotifications: 0,
+ averageProjectProgress: 0,
+ highRiskProjects: 0,
+ },
+ approvalQueue: [],
+ procurementQueue: [],
+ contractWatchlist: [],
+ topContracts: [],
+ riskPanel: [],
+ provinceRollout: [],
+ recentNotifications: [],
+};
+
+const actionIconMap: Record = {
+ organizations: mdiBankOutline,
+ users: mdiCheckDecagramOutline,
+ approvals: mdiClipboardClockOutline,
+ notifications: mdiBellOutline,
+ projects: mdiChartTimelineVariant,
+ contracts: mdiFileDocumentOutline,
+ payments: mdiWalletOutline,
+ allocations: mdiBankOutline,
+ requisitions: mdiClipboardListOutline,
+ tenders: mdiClipboardListOutline,
+ compliance: mdiShieldAlertOutline,
+ audit: mdiShieldAlertOutline,
+ milestones: mdiChartTimelineVariant,
+ vendors: mdiCheckDecagramOutline,
+};
+
+const formatCurrency = (value: number, currency: 'USD' | 'CDF') =>
+ new Intl.NumberFormat(currency === 'CDF' ? 'fr-CD' : 'en-US', {
+ style: 'currency',
+ currency,
+ maximumFractionDigits: 0,
+ }).format(value || 0);
+
+const formatDate = (value?: string | null) => {
+ if (!value) {
+ return '—';
+ }
+
+ return new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit',
+ month: 'short',
+ year: 'numeric',
+ }).format(new Date(value));
+};
+
+const humanize = (value?: string | null) =>
+ (value || '—')
+ .replace(/_/g, ' ')
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
+
+const statusBadgeClass = (status?: string) => {
+ switch (status) {
+ case 'approved':
+ case 'active':
+ case 'processed':
+ return 'bg-emerald-50 text-emerald-700 border-emerald-200';
+ case 'pending':
+ case 'submitted':
+ case 'under_review':
+ case 'in_tender':
+ case 'awarded':
+ case 'batched':
+ return 'bg-amber-50 text-amber-700 border-amber-200';
+ case 'rejected':
+ case 'cancelled':
+ case 'expired':
+ case 'terminated':
+ case 'failed':
+ return 'bg-red-50 text-red-700 border-red-200';
+ default:
+ return 'bg-slate-100 text-slate-700 border-slate-200';
+ }
+};
+
+const severityBadgeClass = (severity?: string) => {
+ switch (severity) {
+ case 'critical':
+ return 'bg-red-100 text-red-800 border-red-200';
+ case 'high':
+ return 'bg-rose-50 text-rose-700 border-rose-200';
+ case 'warning':
+ return 'bg-amber-50 text-amber-700 border-amber-200';
+ default:
+ return 'bg-slate-100 text-slate-700 border-slate-200';
+ }
+};
+
+const getRecordLink = (recordType?: string, recordKey?: string) => {
+ if (!recordType || !recordKey) {
+ return '/approvals/approvals-list';
+ }
+
+ const allowedDetailPages = new Set([
+ 'requisitions',
+ 'contracts',
+ 'projects',
+ 'vendors',
+ 'tenders',
+ 'awards',
+ 'invoices',
+ 'payment_requests',
+ 'budget_reallocations',
+ 'grants',
+ ]);
+
+ if (!allowedDetailPages.has(recordType)) {
+ return '/approvals/approvals-list';
+ }
+
+ return `/${recordType}/${recordType}-view?id=${recordKey}`;
+};
+
+const SectionHeader = ({
+ eyebrow,
+ title,
+ action,
+}: {
+ eyebrow: string;
+ title: string;
+ action?: ReactNode;
+}) => (
+
+);
+
+const SummaryMetric = ({
+ title,
+ value,
+ note,
+ icon,
+}: {
+ title: string;
+ value: string;
+ note: string;
+ icon: string;
+}) => (
+
+
+
+
{title}
+
{value}
+
{note}
+
+
+
+
+
+
+);
+
+const ExecutiveSummaryPage = () => {
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const [data, setData] = useState(defaultResponse);
+ const [loading, setLoading] = useState(true);
+ const [errorMessage, setErrorMessage] = useState('');
+ const workspaceConfig = useMemo(() => getWorkspaceConfig(currentUser?.app_role?.name), [currentUser?.app_role?.name]);
+
+ useEffect(() => {
+ const fetchSummary = async () => {
+ try {
+ setLoading(true);
+ setErrorMessage('');
+ const response = await axios.get('/executive-summary');
+ setData(response.data);
+ } catch (error: any) {
+ console.error('Failed to load executive summary', error);
+ setErrorMessage(error?.response?.data?.message || 'Unable to load the executive summary right now.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchSummary();
+ }, []);
+
+ const metricDefinitions = useMemo>(
+ () => ({
+ approvedBudget: {
+ title: 'Approved annual budget',
+ value: `${formatCurrency(data.summary.approvedBudget.USD, 'USD')} / ${formatCurrency(data.summary.approvedBudget.CDF, 'CDF')}`,
+ note: 'Approved and active allocation envelope across the institution.',
+ icon: mdiBankOutline,
+ },
+ committedBudget: {
+ title: 'Committed budget',
+ value: `${formatCurrency(data.summary.committedBudget.USD, 'USD')} / ${formatCurrency(data.summary.committedBudget.CDF, 'CDF')}`,
+ note: 'Open requisition exposure awaiting tendering, award, or conversion.',
+ icon: mdiClipboardListOutline,
+ },
+ disbursedBudget: {
+ title: 'Disbursed budget',
+ value: `${formatCurrency(data.summary.disbursedBudget.USD, 'USD')} / ${formatCurrency(data.summary.disbursedBudget.CDF, 'CDF')}`,
+ note: 'Processed payments already released to counterparties.',
+ icon: mdiWalletOutline,
+ },
+ pendingApprovals: {
+ title: 'Pending approvals',
+ value: `${data.summary.pendingApprovals}`,
+ note: 'Approval actions currently sitting in the institutional queue.',
+ icon: mdiClipboardClockOutline,
+ },
+ contractsNearingExpiry: {
+ title: 'Contracts nearing expiry',
+ value: `${data.summary.contractsNearingExpiry}`,
+ note: 'Active contracts due to expire within the next 60 days.',
+ icon: mdiFileDocumentOutline,
+ },
+ vendorComplianceAlerts: {
+ title: 'Vendor compliance alerts',
+ value: `${data.summary.vendorComplianceAlerts}`,
+ note: 'Open compliance and missing-document issues requiring follow-up.',
+ icon: mdiCheckDecagramOutline,
+ },
+ procurementPipeline: {
+ title: 'Procurement pipeline',
+ value: `${data.summary.procurementPipeline}`,
+ note: 'Live requisitions progressing through review, tender, and award.',
+ icon: mdiClipboardListOutline,
+ },
+ openRiskAlerts: {
+ title: 'Open risk alerts',
+ value: `${data.summary.openRiskAlerts}`,
+ note: 'High-priority compliance or control issues requiring intervention.',
+ icon: mdiShieldAlertOutline,
+ },
+ averageProjectProgress: {
+ title: 'Average project progress',
+ value: `${data.summary.averageProjectProgress}%`,
+ note: 'Average completion level across active and approved projects.',
+ icon: mdiChartTimelineVariant,
+ },
+ highRiskProjects: {
+ title: 'High-risk projects',
+ value: `${data.summary.highRiskProjects}`,
+ note: 'Projects flagged high or critical risk in active delivery.',
+ icon: mdiShieldAlertOutline,
+ },
+ overduePayments: {
+ title: 'Overdue payment requests',
+ value: `${data.summary.overduePayments}`,
+ note: 'Submitted, approved, or batched requests older than 30 days.',
+ icon: mdiWalletOutline,
+ },
+ activeProjects: {
+ title: 'Active projects',
+ value: `${data.summary.activeProjects}`,
+ note: 'Projects currently being executed across the organization.',
+ icon: mdiChartTimelineVariant,
+ },
+ unreadNotifications: {
+ title: 'Unread notifications',
+ value: `${data.summary.unreadNotifications}`,
+ note: 'System notices, workflow events, and alerts awaiting attention.',
+ icon: mdiBellOutline,
+ },
+ }),
+ [data.summary],
+ );
+
+ const summaryCards = useMemo(() => {
+ const summaryMetricKeys = data.workspace?.summaryMetricKeys?.length
+ ? (data.workspace.summaryMetricKeys as WorkspaceMetricKey[])
+ : workspaceConfig.highlightedMetricKeys;
+
+ return summaryMetricKeys.map((metricKey) => ({
+ key: metricKey,
+ ...metricDefinitions[metricKey],
+ }));
+ }, [data.workspace?.summaryMetricKeys, metricDefinitions, workspaceConfig.highlightedMetricKeys]);
+
+ const heroMetricChips = useMemo(
+ () => workspaceConfig.heroMetricKeys.slice(0, 4).map((metricKey) => metricDefinitions[metricKey]),
+ [metricDefinitions, workspaceConfig.heroMetricKeys],
+ );
+
+ const focusBlock = Boolean(data.workspace?.focusCards?.length) && (
+
+ {data.workspace?.focusCards.map((card) => (
+
+
+
+
Role focus
+
{card.title}
+
{card.value}
+
{card.note}
+
+
+
+
+
+
+ ))}
+
+ );
+
+ const summaryBlock = (
+
+ {summaryCards.map((card) => (
+
+ ))}
+
+ );
+
+ const watchlistBlock = Boolean(data.workspace?.watchlistCards?.length) && (
+
+ {data.workspace?.watchlistCards.map((card) => (
+
+
+
+
Watchlist
+
{card.title}
+
+
{card.count}
+
records in view
+
+
{card.note}
+
+
+
+
+
+
+ ))}
+
+ );
+
+ const approvalRiskBlock = (
+
+
+
+ }
+ />
+
+ {loading ? (
+ Loading approval workload…
+ ) : data.approvalQueue.length ? (
+
+
+
+
+ Workflow
+ Step
+ Requested by
+ Assigned to
+ Requested
+ Record
+
+
+
+ {data.approvalQueue.map((item) => (
+
+
+ {item.workflowName}
+
+
+ {humanize(item.status)}
+
+
+
+
+ {item.stepOrder ? `Step ${item.stepOrder}` : '—'} · {item.stepName}
+
+ {item.requestedBy}
+ {item.assignedTo}
+ {formatDate(item.requestedAt)}
+
+
+
+ Open approval
+
+
+ {humanize(item.recordType)} record
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+ No pending approvals are queued right now. Start a new requisition to test the workflow end to end.
+
+ )}
+
+
+
+
+ {data.summary.openRiskAlerts} open alerts
+
+ }
+ />
+
+
+
+
Budget headroom
+
+ {formatCurrency(data.summary.budgetVariance.USD, 'USD')} / {formatCurrency(data.summary.budgetVariance.CDF, 'CDF')}
+
+
Remaining variance between approved allocations and current commitments.
+
+
+
+
{data.summary.overduePayments}
+
Overdue payment requests
+
+
+
{data.summary.highRiskProjects}
+
High-risk projects
+
+
+
+
+
+ {data.riskPanel.length ? (
+ data.riskPanel.slice(0, 4).map((risk) => (
+
+
+
+
{risk.title}
+
{risk.details || humanize(risk.alertType)}
+
Assigned to {risk.assignedTo}
+
+
+ {humanize(risk.severity)}
+
+
+
+ ))
+ ) : (
+
+ No open compliance alerts are currently active.
+
+ )}
+
+
+
+ );
+
+ const operationsBlock = (
+
+
+
+ }
+ />
+
+
+
+
+ 1
+ Requisition created
+
+
+ 2
+ Internal approval
+
+
+ 3
+ Tender / consultation
+
+
+ 4
+ Award and contract
+
+
+ 5
+ Invoice and payment
+
+
+
+ {data.procurementQueue.length ? (
+ data.procurementQueue.map((item) => (
+
+
+
+
{item.requisitionNumber || 'Requisition'}
+
{item.title}
+
+ {humanize(item.procurementMethod)} · {item.provinceName}
+
+
+
+
{formatCurrency(item.estimatedAmount, item.currency)}
+
+ {humanize(item.status)}
+
+
+
+
+ Needed by {formatDate(item.neededByDate)}
+ Open record
+
+
+ ))
+ ) : (
+
+ No live requisitions were found. Use “New requisition” to begin the procurement chain.
+
+ )}
+
+
+
+
+
+ }
+ />
+
+ {data.contractWatchlist.length ? (
+ data.contractWatchlist.map((contract) => (
+
+
+
+
{contract.contractNumber || 'Contract record'}
+
{contract.title}
+
{contract.vendorName}
+
+
+ {humanize(contract.status)}
+
+
+
+
+
End date
+
{formatDate(contract.endDate)}
+
+
+
Days to expiry
+
{contract.daysToExpiry ?? '—'}
+
+
+
+ ))
+ ) : (
+
+ No active contracts are expiring within the next 60 days.
+
+ )}
+
+
+
+
+
+ }
+ />
+
+ {data.recentNotifications.length ? (
+ data.recentNotifications.map((notification) => (
+
+
+
+
{notification.title}
+
{notification.message}
+
+
+
+
+ {humanize(notification.type)}
+ {formatDate(notification.sentAt)}
+
+
+ ))
+ ) : (
+
+ No recent notifications were returned for this user.
+
+ )}
+
+
+
+ );
+
+ const deliveryBlock = (
+
+
+
+ }
+ />
+
+ {data.provinceRollout.length ? (
+ data.provinceRollout.map((province) => (
+
+
+
+
{province.provinceName}
+
+ {province.activeProjects} active of {province.totalProjects} total projects
+
+
+
+
{province.averageCompletion}%
+
Average completion
+
+
+
+
+ ))
+ ) : (
+
+ No provincial rollout data is available yet.
+
+ )}
+
+
+
+
+
+ }
+ />
+
+ {data.topContracts.length ? (
+
+
+
+ Contract
+ Vendor
+ Project
+ Value
+
+
+
+ {data.topContracts.map((contract) => (
+
+
+
+ {contract.contractNumber || contract.title}
+
+ {contract.title}
+
+ {contract.vendorName}
+ {contract.projectName}
+ {formatCurrency(contract.contractValue, contract.currency)}
+
+ ))}
+
+
+ ) : (
+
+ No contract commitments are available yet.
+
+ )}
+
+
+
+ );
+
+ const actionsBlock = (
+
+
+
{workspaceConfig.sectionCopy.quickActions.eyebrow}
+
{workspaceConfig.sectionCopy.quickActions.title}
+
+
+ {workspaceConfig.quickLinks.map((link) => (
+
+
+
+
+
{link.label}
+
{link.description}
+
+
+
+ ))}
+
+
+ );
+
+ const blockMap: Partial> = {
+ focus: focusBlock,
+ summary: summaryBlock,
+ watchlist: watchlistBlock,
+ approvalRisk: approvalRiskBlock,
+ operations: operationsBlock,
+ delivery: deliveryBlock,
+ actions: actionsBlock,
+ };
+
+ return (
+ <>
+
+ {getPageTitle(workspaceConfig.pageTitle)}
+
+
+
+
+
+
+
+
+
+
+
+
+
{workspaceConfig.eyebrow}
+
{workspaceConfig.heroTitle}
+
{workspaceConfig.heroDescription}
+
+ {heroMetricChips.map((metric) => (
+
+ {metric.title}: {metric.value}
+
+ ))}
+
+
+
+
+
User context
+
{currentUser?.firstName || currentUser?.email || 'Authenticated user'}
+
{data.workspace?.roleName || currentUser?.app_role?.name || 'Operational access'}
+
+
+
Control indicators
+
+
+
{data.summary.overduePayments}
+
Overdue payment requests
+
+
+
{data.summary.unreadNotifications}
+
Unread notifications
+
+
+
+
+
+
+
+ {errorMessage && {errorMessage} }
+
+ {workspaceConfig.blockOrder.map((blockKey) => {
+ const block = blockMap[blockKey];
+
+ if (!block) {
+ return null;
+ }
+
+ return {block} ;
+ })}
+
+ >
+ );
+};
+
+ExecutiveSummaryPage.getLayout = function getLayout(page: ReactElement) {
+ return {page} ;
+};
+
+export default ExecutiveSummaryPage;
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index f032efe..35df893 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,190 @@
-
-import React, { useEffect, useState } from 'react';
+import {
+ mdiBankOutline,
+ mdiCheckDecagramOutline,
+ mdiClipboardClockOutline,
+ mdiFileDocumentOutline,
+ mdiLogin,
+ mdiMapMarkerRadiusOutline,
+ mdiOfficeBuildingCogOutline,
+ mdiShieldCheckOutline,
+ mdiWalletOutline,
+} from '@mdi/js';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
+import React from 'react';
import BaseButton from '../components/BaseButton';
-import CardBox from '../components/CardBox';
-import SectionFullScreen from '../components/SectionFullScreen';
-import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
+import BaseIcon from '../components/BaseIcon';
+import CardBox from '../components/CardBox';
+import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
-import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
+const moduleCards = [
+ {
+ title: 'Budgeting and fund allocation',
+ description: 'Approved allocations, commitment exposure, reallocations, and fiscal-year control across funding programs.',
+ icon: mdiBankOutline,
+ },
+ {
+ title: 'Procurement and approvals',
+ description: 'Requisition-to-award workflow with internal approvals, tender visibility, and contract handoff.',
+ icon: mdiClipboardClockOutline,
+ },
+ {
+ title: 'Contracts and vendors',
+ description: 'Vendor qualification, compliance review, contract expiry monitoring, and payment readiness.',
+ icon: mdiFileDocumentOutline,
+ },
+ {
+ title: 'Projects and rollout oversight',
+ description: 'Province-level implementation tracking for school broadband, digital centers, rural connectivity, and ICT programs.',
+ icon: mdiMapMarkerRadiusOutline,
+ },
+];
-export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('background');
- const textColor = useAppSelector((state) => state.style.linkColor);
-
- const title = 'FDSU ERP'
-
- // Fetch Pexels image/video
- useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
- }
- fetchData();
- }, []);
-
- const imageBlock = (image) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
- Your browser does not support the video tag.
-
-
-
)
- }
- };
+const controlPoints = [
+ 'Approval-aware procurement workflow from requisition to payment',
+ 'Vendor compliance, document expiry, and audit-ready record trails',
+ 'Executive summary with budget, contract, project, and risk visibility',
+ 'Operational links into the existing admin interface and CRUD work areas',
+];
+export default function HomePage() {
return (
-
+ <>
-
{getPageTitle('Starter Page')}
+
{getPageTitle('FDSU ERP')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
This is a React.js/Node.js app generated by the Flatlogic Web App Generator
-
For guides and documentation please check
- your local README.md and the Flatlogic documentation
+
+
+
+
+
National public fund authority platform
+
FDSU ERP
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
© 2026 {title} . All rights reserved
-
- Privacy Policy
-
-
+
+
+
+
+
Institutional ERP foundation
+
+ Operational control for budgeting, procurement, contract management, compliance, and digital inclusion rollout.
+
+
+ FDSU ERP is designed as a serious working environment for finance, procurement, project delivery, internal control, audit, and executive leadership.
+ It centralizes recordkeeping, approvals, payment oversight, vendor qualification, contract obligations, and province-level implementation in one platform.
+
-
+
+
+
+
+
+
+
+
+
+ What the first delivery includes
+
+ {controlPoints.map((item) => (
+
+ ))}
+
+
+
+
+
Interface style
+
Dense, audit-conscious, line-of-business layout
+
+
+
Access
+
Public landing with links into the secured admin workspace
+
+
+
+
+
+
+ {moduleCards.map((card) => (
+
+
+
+
ERP module
+
{card.title}
+
{card.description}
+
+
+
+
+
+
+ ))}
+
+
+
+
+ Operational workflow
+ Thin-slice journey now wired into the app
+
+ {[
+ ['1', 'Create requisition', 'Capture procurement demand and route it into the formal approval chain.'],
+ ['2', 'Review approvals', 'Use the new executive summary and approval inbox to identify pending actions.'],
+ ['3', 'Inspect contracts', 'Track contracts nearing expiry and top-value commitments in one place.'],
+ ['4', 'Monitor rollout', 'Review project concentration by province and outstanding operational risk.'],
+ ].map(([step, title, text]) => (
+
+
{step}
+
{title}
+
{text}
+
+ ))}
+
+
+
+
+ Quick access
+
+ {[
+ { href: '/executive-summary', icon: mdiWalletOutline, title: 'Executive summary', text: 'Operational overview, approval queue, risk panel, and rollout indicators.' },
+ { href: '/requisitions/requisitions-list', icon: mdiClipboardClockOutline, title: 'Requisitions', text: 'Create, list, and review procurement requests.' },
+ { href: '/contracts/contracts-list', icon: mdiFileDocumentOutline, title: 'Contracts', text: 'Open the contract register and milestone detail views.' },
+ { href: '/vendors/vendors-list', icon: mdiCheckDecagramOutline, title: 'Vendor master', text: 'Access qualification, banking data, compliance, and related history.' },
+ ].map((item) => (
+
+
+
+
+
+
+
{item.title}
+
{item.text}
+
+
+
+ ))}
+
+
+
+
+
+ >
);
}
-Starter.getLayout = function getLayout(page: ReactElement) {
+HomePage.getLayout = function getLayout(page: ReactElement) {
return {page} ;
};
-
diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx
index 00f5168..005eb07 100644
--- a/frontend/src/pages/search.tsx
+++ b/frontend/src/pages/search.tsx
@@ -1,9 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
-import { useAppDispatch } from '../stores/hooks';
-
-import { useAppSelector } from '../stores/hooks';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';