Added PFM Console UI project
This commit is contained in:
@@ -0,0 +1,898 @@
|
||||
import {
|
||||
ApprovalStatus,
|
||||
ContractStatus,
|
||||
ActivityStatus,
|
||||
Priority,
|
||||
OrganisationType,
|
||||
IndustrySector,
|
||||
ProjectType,
|
||||
UnitOfMeasure,
|
||||
EvidenceType,
|
||||
RiskLikelihood,
|
||||
RiskImpact,
|
||||
NotificationPlatform,
|
||||
NotificationStatus,
|
||||
EmailStatuses,
|
||||
} from '../types';
|
||||
|
||||
export const formatZAR = (amount: number): string => {
|
||||
return new Intl.NumberFormat('en-ZA', {
|
||||
style: 'currency',
|
||||
currency: 'ZAR',
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
export const formatCompactZAR = (amount: number): string => {
|
||||
if (amount >= 1_000_000_000) {
|
||||
return `R ${(amount / 1_000_000_000).toFixed(2)}bn`;
|
||||
}
|
||||
if (amount >= 1_000_000) {
|
||||
return `R ${(amount / 1_000_000).toFixed(1)}m`;
|
||||
}
|
||||
if (amount >= 1_000) {
|
||||
return `R ${(amount / 1_000).toFixed(0)}k`;
|
||||
}
|
||||
return formatZAR(amount);
|
||||
};
|
||||
|
||||
export const formatDate = (dateStr?: string | null): string => {
|
||||
if (!dateStr) return '—';
|
||||
try {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleDateString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Dropdown Select Options with Integer Values
|
||||
// ==========================================
|
||||
|
||||
export const EMAIL_STATUS_OPTIONS = [
|
||||
{ value: EmailStatuses.GeneralError, label: 'General Error' },
|
||||
{ value: EmailStatuses.AuthenticationError, label: 'Authentication Error' },
|
||||
{ value: EmailStatuses.ProtocolError, label: 'Protocol Error' },
|
||||
{ value: EmailStatuses.Connected, label: 'Connected' },
|
||||
{ value: EmailStatuses.Disconnected, label: 'Disconnected' },
|
||||
{ value: EmailStatuses.TooManyConnections, label: 'Too Many Connections' },
|
||||
{ value: EmailStatuses.ConnectionAborted, label: 'Connection Aborted' },
|
||||
{ value: EmailStatuses.Success, label: 'Success' },
|
||||
];
|
||||
|
||||
export const EMAIL_STATUSES_OPTIONS = EMAIL_STATUS_OPTIONS;
|
||||
|
||||
export const NOTIFICATION_STATUS_OPTIONS = [
|
||||
{ value: NotificationStatus.Pending, label: 'Pending' },
|
||||
{ value: NotificationStatus.Queued, label: 'Queued' },
|
||||
{ value: NotificationStatus.Processing, label: 'Processing' },
|
||||
{ value: NotificationStatus.Sent, label: 'Sent' },
|
||||
{ value: NotificationStatus.Delivered, label: 'Delivered' },
|
||||
{ value: NotificationStatus.Read, label: 'Read' },
|
||||
{ value: NotificationStatus.Failed, label: 'Failed' },
|
||||
{ value: NotificationStatus.Cancelled, label: 'Cancelled' },
|
||||
{ value: NotificationStatus.Bounced, label: 'Bounced' },
|
||||
];
|
||||
|
||||
export const NOTIFICATION_PLATFORM_OPTIONS = [
|
||||
{ value: NotificationPlatform.Email, label: 'Email' },
|
||||
{ value: NotificationPlatform.SMS, label: 'SMS' },
|
||||
{ value: NotificationPlatform.WhatsApp, label: 'WhatsApp' },
|
||||
{ value: NotificationPlatform.PushNotification, label: 'Push Notification' },
|
||||
{ value: NotificationPlatform.InApp, label: 'In App' },
|
||||
{ value: NotificationPlatform.Teams, label: 'Teams' },
|
||||
{ value: NotificationPlatform.Slack, label: 'Slack' },
|
||||
{ value: NotificationPlatform.Webhook, label: 'Webhook' },
|
||||
{ value: NotificationPlatform.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const EVIDENCE_TYPE_OPTIONS = [
|
||||
{ value: EvidenceType.AttendanceRegister, label: 'Attendance Register' },
|
||||
{ value: EvidenceType.BankStatement, label: 'Bank Statement' },
|
||||
{ value: EvidenceType.CompletionCertificate, label: 'Completion Certificate' },
|
||||
{ value: EvidenceType.FinancialInvoiceOrReceipt, label: 'Financial Invoice Or Receipt' },
|
||||
{ value: EvidenceType.GeotaggedPhoto, label: 'Geotagged Photo' },
|
||||
{ value: EvidenceType.InspectionReport, label: 'Inspection Report' },
|
||||
{ value: EvidenceType.MeetingMinutes, label: 'Meeting Minutes' },
|
||||
{ value: EvidenceType.PayrollOrStipendRegister, label: 'Payroll Or Stipend Register' },
|
||||
{ value: EvidenceType.ProcurementDocument, label: 'Procurement Document' },
|
||||
{ value: EvidenceType.TechnicalSpecificationOrDesign, label: 'Technical Specification Or Design' },
|
||||
{ value: EvidenceType.VideoRecording, label: 'Video Recording' },
|
||||
{ value: EvidenceType.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const PROJECT_TYPE_OPTIONS = [
|
||||
{ value: ProjectType.CapacityBuildingAndTraining, label: 'Capacity Building And Training' },
|
||||
{ value: ProjectType.CapitalExpenditureAndInfrastructure, label: 'Capital Expenditure And Infrastructure' },
|
||||
{ value: ProjectType.CommercializationAndScaling, label: 'Commercialization And Scaling' },
|
||||
{ value: ProjectType.FeasibilityAndTechnicalAssistance, label: 'Feasibility And Technical Assistance' },
|
||||
{ value: ProjectType.OperationalGrantAndSubsidy, label: 'Operational Grant And Subsidy' },
|
||||
{ value: ProjectType.ProductDevelopmentAndPrototyping, label: 'Product Development And Prototyping' },
|
||||
{ value: ProjectType.ResearchAndDevelopment, label: 'Research And Development' },
|
||||
{ value: ProjectType.SocialImpactAndCommunityDevelopment, label: 'Social Impact And Community Development' },
|
||||
{ value: ProjectType.TechnologyTransferAndAdoption, label: 'Technology Transfer And Adoption' },
|
||||
{ value: ProjectType.Innovation, label: 'Innovation' },
|
||||
{ value: ProjectType.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const INDUSTRY_SECTOR_OPTIONS = [
|
||||
{ value: IndustrySector.AgricultureAndAgroProcessing, label: 'Agriculture And Agro Processing' },
|
||||
{ value: IndustrySector.AutomotiveAndTransportEquipment, label: 'Automotive And Transport Equipment' },
|
||||
{ value: IndustrySector.ChemicalsAndPharmaceuticals, label: 'Chemicals And Pharmaceuticals' },
|
||||
{ value: IndustrySector.ConstructionAndInfrastructure, label: 'Construction And Infrastructure' },
|
||||
{ value: IndustrySector.CreativeIndustriesAndMedia, label: 'Creative Industries And Media' },
|
||||
{ value: IndustrySector.DefenseAndAerospace, label: 'Defense And Aerospace' },
|
||||
{ value: IndustrySector.EducationAndSkillsDevelopment, label: 'Education And Skills Development' },
|
||||
{ value: IndustrySector.EnergyAndUtilities, label: 'Energy And Utilities' },
|
||||
{ value: IndustrySector.FinancialServices, label: 'Financial Services' },
|
||||
{ value: IndustrySector.HealthcareAndLifeSciences, label: 'Healthcare And Life Sciences' },
|
||||
{ value: IndustrySector.InformationCommunicationTechnology, label: 'Information Communication Technology' },
|
||||
{ value: IndustrySector.ManufacturingAndIndustrial, label: 'Manufacturing And Industrial' },
|
||||
{ value: IndustrySector.MiningAndMetals, label: 'Mining And Metals' },
|
||||
{ value: IndustrySector.RealEstateAndHousing, label: 'Real Estate And Housing' },
|
||||
{ value: IndustrySector.RetailAndConsumerGoods, label: 'Retail And Consumer Goods' },
|
||||
{ value: IndustrySector.TourismAndHospitality, label: 'Tourism And Hospitality' },
|
||||
{ value: IndustrySector.WaterAndSanitation, label: 'Water And Sanitation' },
|
||||
{ value: IndustrySector.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const ORGANISATION_TYPE_OPTIONS = [
|
||||
{ value: OrganisationType.NonGovernmentalOrganisation, label: 'Non-Governmental Organisation' },
|
||||
{ value: OrganisationType.CivilSocietyOrganisation, label: 'Civil Society Organisation' },
|
||||
{ value: OrganisationType.Trust, label: 'Trust' },
|
||||
{ value: OrganisationType.FaithBasedOrganisation, label: 'Faith-Based Organisation' },
|
||||
{ value: OrganisationType.PrivateLimitedCompany, label: 'Private Limited Company' },
|
||||
{ value: OrganisationType.PublicLimitedCompany, label: 'Public Limited Company' },
|
||||
{ value: OrganisationType.SoleProprietorship, label: 'Sole Proprietorship' },
|
||||
{ value: OrganisationType.Partnership, label: 'Partnership' },
|
||||
{ value: OrganisationType.SocialEnterprise, label: 'Social Enterprise' },
|
||||
{ value: OrganisationType.NationalGovernmentMinistry, label: 'National Government Ministry' },
|
||||
{ value: OrganisationType.ProvincialGovernmentEntity, label: 'Provincial Government Entity' },
|
||||
{ value: OrganisationType.LocalGovernmentMunicipality, label: 'Local Government Municipality' },
|
||||
{ value: OrganisationType.StateOwnedEnterprise, label: 'State-Owned Enterprise' },
|
||||
{ value: OrganisationType.PublicAgency, label: 'Public Agency' },
|
||||
{ value: OrganisationType.PublicUniversity, label: 'Public University' },
|
||||
{ value: OrganisationType.PrivateUniversity, label: 'Private University' },
|
||||
{ value: OrganisationType.ResearchInstitute, label: 'Research Institute' },
|
||||
{ value: OrganisationType.TechnicalVocationalCollege, label: 'Technical Vocational College' },
|
||||
{ value: OrganisationType.Cooperative, label: 'Cooperative' },
|
||||
{ value: OrganisationType.MicrofinanceInstitution, label: 'Microfinance Institution' },
|
||||
{ value: OrganisationType.CommercialBank, label: 'Commercial Bank' },
|
||||
{ value: OrganisationType.DevelopmentFinanceInstitution, label: 'Development Finance Institution' },
|
||||
{ value: OrganisationType.UnitedNationsAgency, label: 'United Nations Agency' },
|
||||
{ value: OrganisationType.IntergovernmentalOrganisation, label: 'Intergovernmental Organisation' },
|
||||
{ value: OrganisationType.InternationalNGO, label: 'International NGO' },
|
||||
{ value: OrganisationType.Consortium, label: 'Consortium' },
|
||||
{ value: OrganisationType.SpecialPurposeVehicle, label: 'Special Purpose Vehicle' },
|
||||
];
|
||||
|
||||
export const PRIORITY_OPTIONS = [
|
||||
{ value: Priority.Low, label: 'Low' },
|
||||
{ value: Priority.Medium, label: 'Medium' },
|
||||
{ value: Priority.High, label: 'High' },
|
||||
{ value: Priority.Critical, label: 'Critical' },
|
||||
];
|
||||
|
||||
export const UNIT_OF_MEASURE_OPTIONS = [
|
||||
{ value: UnitOfMeasure.Count, label: 'Count' },
|
||||
{ value: UnitOfMeasure.Percentage, label: 'Percentage' },
|
||||
{ value: UnitOfMeasure.Currency, label: 'Currency' },
|
||||
{ value: UnitOfMeasure.Area, label: 'Area' },
|
||||
{ value: UnitOfMeasure.Weight, label: 'Weight' },
|
||||
{ value: UnitOfMeasure.Volume, label: 'Volume' },
|
||||
{ value: UnitOfMeasure.DurationDays, label: 'Duration (Days)' },
|
||||
{ value: UnitOfMeasure.DurationMonths, label: 'Duration (Months)' },
|
||||
{ value: UnitOfMeasure.Ratio, label: 'Ratio' },
|
||||
];
|
||||
|
||||
export const CONTRACT_STATUS_OPTIONS = [
|
||||
{ value: ContractStatus.Draft, label: 'Draft' },
|
||||
{ value: ContractStatus.PendingApproval, label: 'Pending Approval' },
|
||||
{ value: ContractStatus.Active, label: 'Active' },
|
||||
{ value: ContractStatus.Expired, label: 'Expired' },
|
||||
{ value: ContractStatus.Terminated, label: 'Terminated' },
|
||||
{ value: ContractStatus.Amended, label: 'Amended' },
|
||||
];
|
||||
|
||||
export const APPROVAL_STATUS_OPTIONS = [
|
||||
{ value: ApprovalStatus.Pending, label: 'Pending' },
|
||||
{ value: ApprovalStatus.Review, label: 'Review' },
|
||||
{ value: ApprovalStatus.Approved, label: 'Approved' },
|
||||
{ value: ApprovalStatus.Rejected, label: 'Rejected' },
|
||||
{ value: ApprovalStatus.Removed, label: 'Removed' },
|
||||
];
|
||||
|
||||
export const ACTIVITY_STATUS_OPTIONS = [
|
||||
{ value: ActivityStatus.Active, label: 'Active' },
|
||||
{ value: ActivityStatus.Inactive, label: 'Inactive' },
|
||||
{ value: ActivityStatus.Disabled, label: 'Disabled' },
|
||||
];
|
||||
|
||||
export const RISK_LIKELIHOOD_OPTIONS = [
|
||||
{ value: RiskLikelihood.Rare, label: 'Rare' },
|
||||
{ value: RiskLikelihood.Unlikely, label: 'Unlikely' },
|
||||
{ value: RiskLikelihood.Possible, label: 'Possible' },
|
||||
{ value: RiskLikelihood.Likely, label: 'Likely' },
|
||||
{ value: RiskLikelihood.AlmostCertain, label: 'Almost Certain' },
|
||||
];
|
||||
|
||||
export const RISK_IMPACT_OPTIONS = [
|
||||
{ value: RiskImpact.Minor, label: 'Minor' },
|
||||
{ value: RiskImpact.Moderate, label: 'Moderate' },
|
||||
{ value: RiskImpact.Major, label: 'Major' },
|
||||
{ value: RiskImpact.Critical, label: 'Critical' },
|
||||
{ value: RiskImpact.Severe, label: 'Severe' },
|
||||
];
|
||||
|
||||
// ==========================================
|
||||
// Status Badges & Labels
|
||||
// ==========================================
|
||||
|
||||
export const getApprovalStatusBadge = (status: ApprovalStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ApprovalStatus.Approved:
|
||||
return {
|
||||
label: 'Approved',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
dot: 'bg-emerald-500',
|
||||
};
|
||||
case ApprovalStatus.Review:
|
||||
return {
|
||||
label: 'Review',
|
||||
className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold',
|
||||
dot: 'bg-amber-500',
|
||||
};
|
||||
case ApprovalStatus.Rejected:
|
||||
return {
|
||||
label: 'Rejected',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
dot: 'bg-rose-500',
|
||||
};
|
||||
case ApprovalStatus.Removed:
|
||||
return {
|
||||
label: 'Removed',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
dot: 'bg-slate-500',
|
||||
};
|
||||
case ApprovalStatus.Pending:
|
||||
default:
|
||||
return {
|
||||
label: 'Pending',
|
||||
className: 'bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold',
|
||||
dot: 'bg-blue-500',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getContractStatusBadge = (status: ContractStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ContractStatus.Active:
|
||||
return {
|
||||
label: 'Active',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.PendingApproval:
|
||||
return {
|
||||
label: 'Pending Approval',
|
||||
className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Amended:
|
||||
return {
|
||||
label: 'Amended',
|
||||
className: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Expired:
|
||||
return {
|
||||
label: 'Expired',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
};
|
||||
case ContractStatus.Terminated:
|
||||
return {
|
||||
label: 'Terminated',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Draft:
|
||||
default:
|
||||
return {
|
||||
label: 'Draft',
|
||||
className: 'bg-slate-800/80 text-slate-300 border-slate-700/80 font-semibold',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getActivityStatusBadge = (status: ActivityStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ActivityStatus.Active:
|
||||
return {
|
||||
label: 'Active',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
};
|
||||
case ActivityStatus.Inactive:
|
||||
return {
|
||||
label: 'Inactive',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
};
|
||||
case ActivityStatus.Disabled:
|
||||
default:
|
||||
return {
|
||||
label: 'Disabled',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getPriorityBadge = (priority: Priority | number) => {
|
||||
const p = Number(priority);
|
||||
switch (p) {
|
||||
case Priority.Critical:
|
||||
return { label: 'Critical', className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold' };
|
||||
case Priority.High:
|
||||
return { label: 'High', className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold' };
|
||||
case Priority.Medium:
|
||||
return { label: 'Medium', className: 'bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold' };
|
||||
case Priority.Low:
|
||||
default:
|
||||
return { label: 'Low', className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganisationTypeLabel = (type: OrganisationType | number): string => {
|
||||
const opt = ORGANISATION_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Organisation';
|
||||
};
|
||||
|
||||
export const getIndustrySectorLabel = (sector: IndustrySector | number): string => {
|
||||
const opt = INDUSTRY_SECTOR_OPTIONS.find((o) => o.value === Number(sector));
|
||||
return opt ? opt.label : 'Industry Sector';
|
||||
};
|
||||
|
||||
export const getProjectTypeLabel = (type: ProjectType | number): string => {
|
||||
const opt = PROJECT_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Project Type';
|
||||
};
|
||||
|
||||
export const getEvidenceTypeLabel = (type: EvidenceType | number): string => {
|
||||
const opt = EVIDENCE_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Evidence';
|
||||
};
|
||||
|
||||
export const getUnitOfMeasureLabel = (uom?: UnitOfMeasure | number | null): string => {
|
||||
if (uom === null || uom === undefined) return 'Qty';
|
||||
const opt = UNIT_OF_MEASURE_OPTIONS.find((o) => o.value === Number(uom));
|
||||
return opt ? opt.label : 'Qty';
|
||||
};
|
||||
|
||||
export const getNotificationPlatformLabel = (platform?: NotificationPlatform | number | null): string => {
|
||||
const opt = NOTIFICATION_PLATFORM_OPTIONS.find((o) => o.value === Number(platform));
|
||||
return opt ? opt.label : 'Platform';
|
||||
};
|
||||
|
||||
export const getNotificationStatusLabel = (status?: NotificationStatus | number | null): string => {
|
||||
const opt = NOTIFICATION_STATUS_OPTIONS.find((o) => o.value === Number(status));
|
||||
return opt ? opt.label : 'Status';
|
||||
};
|
||||
|
||||
export const getEmailStatusLabel = (status?: EmailStatuses | number | null): string => {
|
||||
const opt = EMAIL_STATUS_OPTIONS.find((o) => o.value === Number(status));
|
||||
return opt ? opt.label : 'Status';
|
||||
};
|
||||
|
||||
export const getEmailStatusBadge = (status?: EmailStatuses | number | null) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case EmailStatuses.Connected:
|
||||
case EmailStatuses.Success:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
case EmailStatuses.Disconnected:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-slate-100 text-slate-700 border-slate-300 font-semibold' };
|
||||
default:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getNotificationStatusBadge = (status?: NotificationStatus | number | null) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case NotificationStatus.Delivered:
|
||||
case NotificationStatus.Read:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
case NotificationStatus.Sent:
|
||||
case NotificationStatus.Queued:
|
||||
case NotificationStatus.Processing:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-sky-50 text-sky-800 border-sky-300 font-semibold' };
|
||||
case NotificationStatus.Pending:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-amber-50 text-amber-900 border-amber-300 font-semibold' };
|
||||
case NotificationStatus.Failed:
|
||||
case NotificationStatus.Cancelled:
|
||||
case NotificationStatus.Bounced:
|
||||
default:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getRiskScoreColor = (likelihood: RiskLikelihood | number, impact: RiskImpact | number) => {
|
||||
const score = Number(likelihood) * Number(impact);
|
||||
if (score >= 15) {
|
||||
return { level: 'High', color: 'bg-rose-600 text-white', badge: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
if (score >= 8) {
|
||||
return { level: 'Medium', color: 'bg-amber-500 text-white', badge: 'bg-amber-50 text-amber-900 border-amber-300 font-semibold' };
|
||||
}
|
||||
return { level: 'Low', color: 'bg-emerald-600 text-white', badge: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// User to Email Resolver (UI shows only Email)
|
||||
// ==========================================
|
||||
|
||||
const USER_EMAIL_CACHE: Record<string, string> = {};
|
||||
|
||||
export const registerUserEmail = (userId: string, email: string) => {
|
||||
if (userId && email) {
|
||||
USER_EMAIL_CACHE[userId] = email;
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserEmail = (userIdOrEmail?: string | null): string => {
|
||||
if (!userIdOrEmail) return '—';
|
||||
if (userIdOrEmail.includes('@')) return userIdOrEmail;
|
||||
if (USER_EMAIL_CACHE[userIdOrEmail]) return USER_EMAIL_CACHE[userIdOrEmail];
|
||||
return userIdOrEmail;
|
||||
};
|
||||
import {
|
||||
ApprovalStatus,
|
||||
ContractStatus,
|
||||
ActivityStatus,
|
||||
Priority,
|
||||
OrganisationType,
|
||||
IndustrySector,
|
||||
ProjectType,
|
||||
UnitOfMeasure,
|
||||
EvidenceType,
|
||||
RiskLikelihood,
|
||||
RiskImpact,
|
||||
NotificationPlatform,
|
||||
NotificationStatus,
|
||||
EmailStatuses,
|
||||
} from '../types';
|
||||
|
||||
export const formatZAR = (amount: number): string => {
|
||||
return new Intl.NumberFormat('en-ZA', {
|
||||
style: 'currency',
|
||||
currency: 'ZAR',
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
export const formatCompactZAR = (amount: number): string => {
|
||||
if (amount >= 1_000_000_000) {
|
||||
return `R ${(amount / 1_000_000_000).toFixed(2)}bn`;
|
||||
}
|
||||
if (amount >= 1_000_000) {
|
||||
return `R ${(amount / 1_000_000).toFixed(1)}m`;
|
||||
}
|
||||
if (amount >= 1_000) {
|
||||
return `R ${(amount / 1_000).toFixed(0)}k`;
|
||||
}
|
||||
return formatZAR(amount);
|
||||
};
|
||||
|
||||
export const formatDate = (dateStr?: string | null): string => {
|
||||
if (!dateStr) return '—';
|
||||
try {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleDateString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Dropdown Select Options with Integer Values
|
||||
// ==========================================
|
||||
|
||||
export const EMAIL_STATUS_OPTIONS = [
|
||||
{ value: EmailStatuses.GeneralError, label: 'General Error' },
|
||||
{ value: EmailStatuses.AuthenticationError, label: 'Authentication Error' },
|
||||
{ value: EmailStatuses.ProtocolError, label: 'Protocol Error' },
|
||||
{ value: EmailStatuses.Connected, label: 'Connected' },
|
||||
{ value: EmailStatuses.Disconnected, label: 'Disconnected' },
|
||||
{ value: EmailStatuses.TooManyConnections, label: 'Too Many Connections' },
|
||||
{ value: EmailStatuses.ConnectionAborted, label: 'Connection Aborted' },
|
||||
{ value: EmailStatuses.Success, label: 'Success' },
|
||||
];
|
||||
|
||||
export const EMAIL_STATUSES_OPTIONS = EMAIL_STATUS_OPTIONS;
|
||||
|
||||
export const NOTIFICATION_STATUS_OPTIONS = [
|
||||
{ value: NotificationStatus.Pending, label: 'Pending' },
|
||||
{ value: NotificationStatus.Queued, label: 'Queued' },
|
||||
{ value: NotificationStatus.Processing, label: 'Processing' },
|
||||
{ value: NotificationStatus.Sent, label: 'Sent' },
|
||||
{ value: NotificationStatus.Delivered, label: 'Delivered' },
|
||||
{ value: NotificationStatus.Read, label: 'Read' },
|
||||
{ value: NotificationStatus.Failed, label: 'Failed' },
|
||||
{ value: NotificationStatus.Cancelled, label: 'Cancelled' },
|
||||
{ value: NotificationStatus.Bounced, label: 'Bounced' },
|
||||
];
|
||||
|
||||
export const NOTIFICATION_PLATFORM_OPTIONS = [
|
||||
{ value: NotificationPlatform.Email, label: 'Email' },
|
||||
{ value: NotificationPlatform.SMS, label: 'SMS' },
|
||||
{ value: NotificationPlatform.WhatsApp, label: 'WhatsApp' },
|
||||
{ value: NotificationPlatform.PushNotification, label: 'Push Notification' },
|
||||
{ value: NotificationPlatform.InApp, label: 'In App' },
|
||||
{ value: NotificationPlatform.Teams, label: 'Teams' },
|
||||
{ value: NotificationPlatform.Slack, label: 'Slack' },
|
||||
{ value: NotificationPlatform.Webhook, label: 'Webhook' },
|
||||
{ value: NotificationPlatform.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const EVIDENCE_TYPE_OPTIONS = [
|
||||
{ value: EvidenceType.AttendanceRegister, label: 'Attendance Register' },
|
||||
{ value: EvidenceType.BankStatement, label: 'Bank Statement' },
|
||||
{ value: EvidenceType.CompletionCertificate, label: 'Completion Certificate' },
|
||||
{ value: EvidenceType.FinancialInvoiceOrReceipt, label: 'Financial Invoice Or Receipt' },
|
||||
{ value: EvidenceType.GeotaggedPhoto, label: 'Geotagged Photo' },
|
||||
{ value: EvidenceType.InspectionReport, label: 'Inspection Report' },
|
||||
{ value: EvidenceType.MeetingMinutes, label: 'Meeting Minutes' },
|
||||
{ value: EvidenceType.PayrollOrStipendRegister, label: 'Payroll Or Stipend Register' },
|
||||
{ value: EvidenceType.ProcurementDocument, label: 'Procurement Document' },
|
||||
{ value: EvidenceType.TechnicalSpecificationOrDesign, label: 'Technical Specification Or Design' },
|
||||
{ value: EvidenceType.VideoRecording, label: 'Video Recording' },
|
||||
{ value: EvidenceType.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const PROJECT_TYPE_OPTIONS = [
|
||||
{ value: ProjectType.CapacityBuildingAndTraining, label: 'Capacity Building And Training' },
|
||||
{ value: ProjectType.CapitalExpenditureAndInfrastructure, label: 'Capital Expenditure And Infrastructure' },
|
||||
{ value: ProjectType.CommercializationAndScaling, label: 'Commercialization And Scaling' },
|
||||
{ value: ProjectType.FeasibilityAndTechnicalAssistance, label: 'Feasibility And Technical Assistance' },
|
||||
{ value: ProjectType.OperationalGrantAndSubsidy, label: 'Operational Grant And Subsidy' },
|
||||
{ value: ProjectType.ProductDevelopmentAndPrototyping, label: 'Product Development And Prototyping' },
|
||||
{ value: ProjectType.ResearchAndDevelopment, label: 'Research And Development' },
|
||||
{ value: ProjectType.SocialImpactAndCommunityDevelopment, label: 'Social Impact And Community Development' },
|
||||
{ value: ProjectType.TechnologyTransferAndAdoption, label: 'Technology Transfer And Adoption' },
|
||||
{ value: ProjectType.Innovation, label: 'Innovation' },
|
||||
{ value: ProjectType.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const INDUSTRY_SECTOR_OPTIONS = [
|
||||
{ value: IndustrySector.AgricultureAndAgroProcessing, label: 'Agriculture And Agro Processing' },
|
||||
{ value: IndustrySector.AutomotiveAndTransportEquipment, label: 'Automotive And Transport Equipment' },
|
||||
{ value: IndustrySector.ChemicalsAndPharmaceuticals, label: 'Chemicals And Pharmaceuticals' },
|
||||
{ value: IndustrySector.ConstructionAndInfrastructure, label: 'Construction And Infrastructure' },
|
||||
{ value: IndustrySector.CreativeIndustriesAndMedia, label: 'Creative Industries And Media' },
|
||||
{ value: IndustrySector.DefenseAndAerospace, label: 'Defense And Aerospace' },
|
||||
{ value: IndustrySector.EducationAndSkillsDevelopment, label: 'Education And Skills Development' },
|
||||
{ value: IndustrySector.EnergyAndUtilities, label: 'Energy And Utilities' },
|
||||
{ value: IndustrySector.FinancialServices, label: 'Financial Services' },
|
||||
{ value: IndustrySector.HealthcareAndLifeSciences, label: 'Healthcare And Life Sciences' },
|
||||
{ value: IndustrySector.InformationCommunicationTechnology, label: 'Information Communication Technology' },
|
||||
{ value: IndustrySector.ManufacturingAndIndustrial, label: 'Manufacturing And Industrial' },
|
||||
{ value: IndustrySector.MiningAndMetals, label: 'Mining And Metals' },
|
||||
{ value: IndustrySector.RealEstateAndHousing, label: 'Real Estate And Housing' },
|
||||
{ value: IndustrySector.RetailAndConsumerGoods, label: 'Retail And Consumer Goods' },
|
||||
{ value: IndustrySector.TourismAndHospitality, label: 'Tourism And Hospitality' },
|
||||
{ value: IndustrySector.WaterAndSanitation, label: 'Water And Sanitation' },
|
||||
{ value: IndustrySector.Other, label: 'Other' },
|
||||
];
|
||||
|
||||
export const ORGANISATION_TYPE_OPTIONS = [
|
||||
{ value: OrganisationType.NonGovernmentalOrganisation, label: 'Non-Governmental Organisation' },
|
||||
{ value: OrganisationType.CivilSocietyOrganisation, label: 'Civil Society Organisation' },
|
||||
{ value: OrganisationType.Trust, label: 'Trust' },
|
||||
{ value: OrganisationType.FaithBasedOrganisation, label: 'Faith-Based Organisation' },
|
||||
{ value: OrganisationType.PrivateLimitedCompany, label: 'Private Limited Company' },
|
||||
{ value: OrganisationType.PublicLimitedCompany, label: 'Public Limited Company' },
|
||||
{ value: OrganisationType.SoleProprietorship, label: 'Sole Proprietorship' },
|
||||
{ value: OrganisationType.Partnership, label: 'Partnership' },
|
||||
{ value: OrganisationType.SocialEnterprise, label: 'Social Enterprise' },
|
||||
{ value: OrganisationType.NationalGovernmentMinistry, label: 'National Government Ministry' },
|
||||
{ value: OrganisationType.ProvincialGovernmentEntity, label: 'Provincial Government Entity' },
|
||||
{ value: OrganisationType.LocalGovernmentMunicipality, label: 'Local Government Municipality' },
|
||||
{ value: OrganisationType.StateOwnedEnterprise, label: 'State-Owned Enterprise' },
|
||||
{ value: OrganisationType.PublicAgency, label: 'Public Agency' },
|
||||
{ value: OrganisationType.PublicUniversity, label: 'Public University' },
|
||||
{ value: OrganisationType.PrivateUniversity, label: 'Private University' },
|
||||
{ value: OrganisationType.ResearchInstitute, label: 'Research Institute' },
|
||||
{ value: OrganisationType.TechnicalVocationalCollege, label: 'Technical Vocational College' },
|
||||
{ value: OrganisationType.Cooperative, label: 'Cooperative' },
|
||||
{ value: OrganisationType.MicrofinanceInstitution, label: 'Microfinance Institution' },
|
||||
{ value: OrganisationType.CommercialBank, label: 'Commercial Bank' },
|
||||
{ value: OrganisationType.DevelopmentFinanceInstitution, label: 'Development Finance Institution' },
|
||||
{ value: OrganisationType.UnitedNationsAgency, label: 'United Nations Agency' },
|
||||
{ value: OrganisationType.IntergovernmentalOrganisation, label: 'Intergovernmental Organisation' },
|
||||
{ value: OrganisationType.InternationalNGO, label: 'International NGO' },
|
||||
{ value: OrganisationType.Consortium, label: 'Consortium' },
|
||||
{ value: OrganisationType.SpecialPurposeVehicle, label: 'Special Purpose Vehicle' },
|
||||
];
|
||||
|
||||
export const PRIORITY_OPTIONS = [
|
||||
{ value: Priority.Low, label: 'Low' },
|
||||
{ value: Priority.Medium, label: 'Medium' },
|
||||
{ value: Priority.High, label: 'High' },
|
||||
{ value: Priority.Critical, label: 'Critical' },
|
||||
];
|
||||
|
||||
export const UNIT_OF_MEASURE_OPTIONS = [
|
||||
{ value: UnitOfMeasure.Count, label: 'Count' },
|
||||
{ value: UnitOfMeasure.Percentage, label: 'Percentage' },
|
||||
{ value: UnitOfMeasure.Currency, label: 'Currency' },
|
||||
{ value: UnitOfMeasure.Area, label: 'Area' },
|
||||
{ value: UnitOfMeasure.Weight, label: 'Weight' },
|
||||
{ value: UnitOfMeasure.Volume, label: 'Volume' },
|
||||
{ value: UnitOfMeasure.DurationDays, label: 'Duration (Days)' },
|
||||
{ value: UnitOfMeasure.DurationMonths, label: 'Duration (Months)' },
|
||||
{ value: UnitOfMeasure.Ratio, label: 'Ratio' },
|
||||
];
|
||||
|
||||
export const CONTRACT_STATUS_OPTIONS = [
|
||||
{ value: ContractStatus.Draft, label: 'Draft' },
|
||||
{ value: ContractStatus.PendingApproval, label: 'Pending Approval' },
|
||||
{ value: ContractStatus.Active, label: 'Active' },
|
||||
{ value: ContractStatus.Expired, label: 'Expired' },
|
||||
{ value: ContractStatus.Terminated, label: 'Terminated' },
|
||||
{ value: ContractStatus.Amended, label: 'Amended' },
|
||||
];
|
||||
|
||||
export const APPROVAL_STATUS_OPTIONS = [
|
||||
{ value: ApprovalStatus.Pending, label: 'Pending' },
|
||||
{ value: ApprovalStatus.Review, label: 'Review' },
|
||||
{ value: ApprovalStatus.Approved, label: 'Approved' },
|
||||
{ value: ApprovalStatus.Rejected, label: 'Rejected' },
|
||||
{ value: ApprovalStatus.Removed, label: 'Removed' },
|
||||
];
|
||||
|
||||
export const ACTIVITY_STATUS_OPTIONS = [
|
||||
{ value: ActivityStatus.Active, label: 'Active' },
|
||||
{ value: ActivityStatus.Inactive, label: 'Inactive' },
|
||||
{ value: ActivityStatus.Disabled, label: 'Disabled' },
|
||||
];
|
||||
|
||||
export const RISK_LIKELIHOOD_OPTIONS = [
|
||||
{ value: RiskLikelihood.Rare, label: 'Rare' },
|
||||
{ value: RiskLikelihood.Unlikely, label: 'Unlikely' },
|
||||
{ value: RiskLikelihood.Possible, label: 'Possible' },
|
||||
{ value: RiskLikelihood.Likely, label: 'Likely' },
|
||||
{ value: RiskLikelihood.AlmostCertain, label: 'Almost Certain' },
|
||||
];
|
||||
|
||||
export const RISK_IMPACT_OPTIONS = [
|
||||
{ value: RiskImpact.Minor, label: 'Minor' },
|
||||
{ value: RiskImpact.Moderate, label: 'Moderate' },
|
||||
{ value: RiskImpact.Major, label: 'Major' },
|
||||
{ value: RiskImpact.Critical, label: 'Critical' },
|
||||
{ value: RiskImpact.Severe, label: 'Severe' },
|
||||
];
|
||||
|
||||
// ==========================================
|
||||
// Status Badges & Labels
|
||||
// ==========================================
|
||||
|
||||
export const getApprovalStatusBadge = (status: ApprovalStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ApprovalStatus.Approved:
|
||||
return {
|
||||
label: 'Approved',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
dot: 'bg-emerald-500',
|
||||
};
|
||||
case ApprovalStatus.Review:
|
||||
return {
|
||||
label: 'Review',
|
||||
className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold',
|
||||
dot: 'bg-amber-500',
|
||||
};
|
||||
case ApprovalStatus.Rejected:
|
||||
return {
|
||||
label: 'Rejected',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
dot: 'bg-rose-500',
|
||||
};
|
||||
case ApprovalStatus.Removed:
|
||||
return {
|
||||
label: 'Removed',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
dot: 'bg-slate-500',
|
||||
};
|
||||
case ApprovalStatus.Pending:
|
||||
default:
|
||||
return {
|
||||
label: 'Pending',
|
||||
className: 'bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold',
|
||||
dot: 'bg-blue-500',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getContractStatusBadge = (status: ContractStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ContractStatus.Active:
|
||||
return {
|
||||
label: 'Active',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.PendingApproval:
|
||||
return {
|
||||
label: 'Pending Approval',
|
||||
className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Amended:
|
||||
return {
|
||||
label: 'Amended',
|
||||
className: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Expired:
|
||||
return {
|
||||
label: 'Expired',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
};
|
||||
case ContractStatus.Terminated:
|
||||
return {
|
||||
label: 'Terminated',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
};
|
||||
case ContractStatus.Draft:
|
||||
default:
|
||||
return {
|
||||
label: 'Draft',
|
||||
className: 'bg-slate-800/80 text-slate-300 border-slate-700/80 font-semibold',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getActivityStatusBadge = (status: ActivityStatus | number) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case ActivityStatus.Active:
|
||||
return {
|
||||
label: 'Active',
|
||||
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
||||
};
|
||||
case ActivityStatus.Inactive:
|
||||
return {
|
||||
label: 'Inactive',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
||||
};
|
||||
case ActivityStatus.Disabled:
|
||||
default:
|
||||
return {
|
||||
label: 'Disabled',
|
||||
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getPriorityBadge = (priority: Priority | number) => {
|
||||
const p = Number(priority);
|
||||
switch (p) {
|
||||
case Priority.Critical:
|
||||
return { label: 'Critical', className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold' };
|
||||
case Priority.High:
|
||||
return { label: 'High', className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold' };
|
||||
case Priority.Medium:
|
||||
return { label: 'Medium', className: 'bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold' };
|
||||
case Priority.Low:
|
||||
default:
|
||||
return { label: 'Low', className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganisationTypeLabel = (type: OrganisationType | number): string => {
|
||||
const opt = ORGANISATION_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Organisation';
|
||||
};
|
||||
|
||||
export const getIndustrySectorLabel = (sector: IndustrySector | number): string => {
|
||||
const opt = INDUSTRY_SECTOR_OPTIONS.find((o) => o.value === Number(sector));
|
||||
return opt ? opt.label : 'Industry Sector';
|
||||
};
|
||||
|
||||
export const getProjectTypeLabel = (type: ProjectType | number): string => {
|
||||
const opt = PROJECT_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Project Type';
|
||||
};
|
||||
|
||||
export const getEvidenceTypeLabel = (type: EvidenceType | number): string => {
|
||||
const opt = EVIDENCE_TYPE_OPTIONS.find((o) => o.value === Number(type));
|
||||
return opt ? opt.label : 'Evidence';
|
||||
};
|
||||
|
||||
export const getUnitOfMeasureLabel = (uom?: UnitOfMeasure | number | null): string => {
|
||||
if (uom === null || uom === undefined) return 'Qty';
|
||||
const opt = UNIT_OF_MEASURE_OPTIONS.find((o) => o.value === Number(uom));
|
||||
return opt ? opt.label : 'Qty';
|
||||
};
|
||||
|
||||
export const getNotificationPlatformLabel = (platform?: NotificationPlatform | number | null): string => {
|
||||
const opt = NOTIFICATION_PLATFORM_OPTIONS.find((o) => o.value === Number(platform));
|
||||
return opt ? opt.label : 'Platform';
|
||||
};
|
||||
|
||||
export const getNotificationStatusLabel = (status?: NotificationStatus | number | null): string => {
|
||||
const opt = NOTIFICATION_STATUS_OPTIONS.find((o) => o.value === Number(status));
|
||||
return opt ? opt.label : 'Status';
|
||||
};
|
||||
|
||||
export const getEmailStatusLabel = (status?: EmailStatuses | number | null): string => {
|
||||
const opt = EMAIL_STATUS_OPTIONS.find((o) => o.value === Number(status));
|
||||
return opt ? opt.label : 'Status';
|
||||
};
|
||||
|
||||
export const getEmailStatusBadge = (status?: EmailStatuses | number | null) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case EmailStatuses.Connected:
|
||||
case EmailStatuses.Success:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
case EmailStatuses.Disconnected:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-slate-100 text-slate-700 border-slate-300 font-semibold' };
|
||||
default:
|
||||
return { label: getEmailStatusLabel(s), className: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getNotificationStatusBadge = (status?: NotificationStatus | number | null) => {
|
||||
const s = Number(status);
|
||||
switch (s) {
|
||||
case NotificationStatus.Delivered:
|
||||
case NotificationStatus.Read:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
case NotificationStatus.Sent:
|
||||
case NotificationStatus.Queued:
|
||||
case NotificationStatus.Processing:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-sky-50 text-sky-800 border-sky-300 font-semibold' };
|
||||
case NotificationStatus.Pending:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-amber-50 text-amber-900 border-amber-300 font-semibold' };
|
||||
case NotificationStatus.Failed:
|
||||
case NotificationStatus.Cancelled:
|
||||
case NotificationStatus.Bounced:
|
||||
default:
|
||||
return { label: getNotificationStatusLabel(s), className: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
};
|
||||
|
||||
export const getRiskScoreColor = (likelihood: RiskLikelihood | number, impact: RiskImpact | number) => {
|
||||
const score = Number(likelihood) * Number(impact);
|
||||
if (score >= 15) {
|
||||
return { level: 'High', color: 'bg-rose-600 text-white', badge: 'bg-rose-50 text-rose-800 border-rose-300 font-semibold' };
|
||||
}
|
||||
if (score >= 8) {
|
||||
return { level: 'Medium', color: 'bg-amber-500 text-white', badge: 'bg-amber-50 text-amber-900 border-amber-300 font-semibold' };
|
||||
}
|
||||
return { level: 'Low', color: 'bg-emerald-600 text-white', badge: 'bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold' };
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// User to Email Resolver (UI shows only Email)
|
||||
// ==========================================
|
||||
|
||||
const USER_EMAIL_CACHE: Record<string, string> = {};
|
||||
|
||||
export const registerUserEmail = (userId: string, email: string) => {
|
||||
if (userId && email) {
|
||||
USER_EMAIL_CACHE[userId] = email;
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserEmail = (userIdOrEmail?: string | null): string => {
|
||||
if (!userIdOrEmail) return '—';
|
||||
if (userIdOrEmail.includes('@')) return userIdOrEmail;
|
||||
if (USER_EMAIL_CACHE[userIdOrEmail]) return USER_EMAIL_CACHE[userIdOrEmail];
|
||||
return userIdOrEmail;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 3, // 3 minutes default cache
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user