652 lines
26 KiB
TypeScript
652 lines
26 KiB
TypeScript
import {
|
|
ApprovalStatus,
|
|
ContractStatus,
|
|
ActivityStatus,
|
|
Priority,
|
|
OrganisationType,
|
|
IndustrySector,
|
|
ProjectType,
|
|
UnitOfMeasure,
|
|
EvidenceType,
|
|
RiskLikelihood,
|
|
RiskImpact,
|
|
NotificationPlatform,
|
|
NotificationStatus,
|
|
EmailStatuses,
|
|
UserRole,
|
|
} 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 | string | boolean | null | undefined) => {
|
|
if (status === null || status === undefined) {
|
|
return {
|
|
label: 'Active',
|
|
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
|
};
|
|
}
|
|
|
|
if (typeof status === 'boolean') {
|
|
return status
|
|
? { label: 'Active', className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold' }
|
|
: { label: 'Inactive', className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold' };
|
|
}
|
|
|
|
const str = String(status).trim().toLowerCase();
|
|
if (str === '1' || str === 'active' || str === 'enabled') {
|
|
return {
|
|
label: 'Active',
|
|
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
|
};
|
|
}
|
|
if (str === '2' || str === 'inactive') {
|
|
return {
|
|
label: 'Inactive',
|
|
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold',
|
|
};
|
|
}
|
|
if (str === '3' || str === 'disabled' || str === 'suspended' || str === 'terminated') {
|
|
return {
|
|
label: str === 'suspended' ? 'Suspended' : 'Disabled',
|
|
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
|
};
|
|
}
|
|
if (str === 'approved') {
|
|
return {
|
|
label: 'Approved',
|
|
className: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold',
|
|
};
|
|
}
|
|
if (str === 'pending' || str === 'review') {
|
|
return {
|
|
label: 'Pending',
|
|
className: 'bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold',
|
|
};
|
|
}
|
|
|
|
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:
|
|
return {
|
|
label: 'Disabled',
|
|
className: 'bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold',
|
|
};
|
|
default:
|
|
return {
|
|
label: String(status),
|
|
className: 'bg-slate-800/80 text-slate-300 border-slate-700/80 font-semibold',
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Robust Status Badge resolver for Organisation Directory.
|
|
* Inspects status, Status, activityStatus, approvalStatus, statusId, isActive, or state,
|
|
* parsing string, boolean, or numeric database representations.
|
|
*/
|
|
export const getOrganisationStatusBadge = (statusOrOrg: any) => {
|
|
let val: any = statusOrOrg;
|
|
if (statusOrOrg !== null && typeof statusOrOrg === 'object') {
|
|
val =
|
|
statusOrOrg.status ??
|
|
statusOrOrg.Status ??
|
|
statusOrOrg.activityStatus ??
|
|
statusOrOrg.ActivityStatus ??
|
|
statusOrOrg.approvalStatus ??
|
|
statusOrOrg.ApprovalStatus ??
|
|
statusOrOrg.statusId ??
|
|
statusOrOrg.StatusId ??
|
|
statusOrOrg.state ??
|
|
statusOrOrg.State ??
|
|
(statusOrOrg.isActive !== undefined ? (statusOrOrg.isActive ? 1 : 2) : undefined) ??
|
|
(statusOrOrg.IsActive !== undefined ? (statusOrOrg.IsActive ? 1 : 2) : undefined);
|
|
}
|
|
|
|
return getActivityStatusBadge(val);
|
|
};
|
|
|
|
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) || 1) * (Number(impact) || 1);
|
|
if (score >= 15) {
|
|
return { level: 'Critical', color: 'bg-rose-600 text-white', badge: 'bg-rose-500/20 text-rose-300 border border-rose-500/40 font-bold' };
|
|
}
|
|
if (score >= 10) {
|
|
return { level: 'High', color: 'bg-orange-500 text-white', badge: 'bg-orange-500/20 text-orange-300 border border-orange-500/40 font-bold' };
|
|
}
|
|
if (score >= 5) {
|
|
return { level: 'Moderate', color: 'bg-amber-500 text-white', badge: 'bg-amber-500/20 text-amber-300 border border-amber-500/40 font-semibold' };
|
|
}
|
|
return { level: 'Low', color: 'bg-emerald-600 text-white', badge: 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 font-semibold' };
|
|
};
|
|
|
|
// ==========================================
|
|
// User to Email Resolver (UI shows only Email)
|
|
// ==========================================
|
|
|
|
const USER_EMAIL_CACHE: Record<string, string> = {};
|
|
const EMAIL_TO_USER_ID_CACHE: Record<string, string> = {};
|
|
|
|
export const registerUserEmail = (userId: string | number, email: string) => {
|
|
if (userId && email && typeof email === 'string' && email.trim()) {
|
|
const cleanId = String(userId).trim();
|
|
const cleanEmail = email.trim();
|
|
USER_EMAIL_CACHE[cleanId] = cleanEmail;
|
|
USER_EMAIL_CACHE[cleanId.toLowerCase()] = cleanEmail;
|
|
EMAIL_TO_USER_ID_CACHE[cleanEmail.toLowerCase()] = cleanId;
|
|
}
|
|
};
|
|
|
|
export const registerUserEmails = (users: Array<{ id: string | number; email?: string | null }>) => {
|
|
if (!Array.isArray(users)) return;
|
|
for (const u of users) {
|
|
if (u && u.id && u.email) {
|
|
registerUserEmail(u.id, u.email);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const getUserIdByEmail = (email?: string | null): string | undefined => {
|
|
if (!email || typeof email !== 'string') return undefined;
|
|
return EMAIL_TO_USER_ID_CACHE[email.trim().toLowerCase()];
|
|
};
|
|
|
|
export const getUserEmail = (userIdOrEmail?: string | number | null): string => {
|
|
if (!userIdOrEmail) return '—';
|
|
const str = String(userIdOrEmail).trim();
|
|
if (!str) return '—';
|
|
if (str.includes('@')) return str;
|
|
if (USER_EMAIL_CACHE[str]) return USER_EMAIL_CACHE[str];
|
|
if (USER_EMAIL_CACHE[str.toLowerCase()]) return USER_EMAIL_CACHE[str.toLowerCase()];
|
|
|
|
// Synchronous fallback from stored auth session in browser
|
|
try {
|
|
const storedUserJson = localStorage.getItem('pfm_auth_user');
|
|
const storedSub = localStorage.getItem('pfm_user_sub');
|
|
if (storedUserJson) {
|
|
const parsed = JSON.parse(storedUserJson);
|
|
if (parsed?.email) {
|
|
const email = String(parsed.email).trim();
|
|
if (
|
|
(parsed.id && String(parsed.id).toLowerCase() === str.toLowerCase()) ||
|
|
(parsed.sub && String(parsed.sub).toLowerCase() === str.toLowerCase()) ||
|
|
(storedSub && String(storedSub).toLowerCase() === str.toLowerCase())
|
|
) {
|
|
registerUserEmail(str, email);
|
|
return email;
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore storage parse errors
|
|
}
|
|
|
|
return str;
|
|
};
|
|
|
|
// ==========================================
|
|
// User Role Badges & Formatter
|
|
// ==========================================
|
|
|
|
export const USER_ROLE_OPTIONS = [
|
|
{ value: 'Admin', label: 'Admin' },
|
|
{ value: 'Executive', label: 'Executive' },
|
|
{ value: 'Supervisor', label: 'Supervisor' },
|
|
{ value: 'Manager', label: 'Manager' },
|
|
{ value: 'Contributor', label: 'Contributor' },
|
|
{ value: 'Unassigned', label: 'Unassigned' },
|
|
];
|
|
|
|
export const getUserRoleBadge = (role?: string | number | UserRole | null) => {
|
|
if (role === null || role === undefined || role === '' || String(role).trim().toLowerCase() === 'null' || String(role).trim().toLowerCase() === 'unassigned') {
|
|
return {
|
|
label: 'Unassigned',
|
|
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 shadow-xs',
|
|
dot: 'bg-slate-500',
|
|
};
|
|
}
|
|
|
|
const roleStr = String(role).trim().toLowerCase();
|
|
|
|
// 1. Admin - High-privilege Crimson/Rose
|
|
if (roleStr === 'admin' || roleStr === 'superadmin' || roleStr === '1') {
|
|
return {
|
|
label: 'Admin',
|
|
className: 'bg-rose-500/15 text-rose-300 border-rose-500/30 shadow-xs shadow-rose-500/10 font-semibold',
|
|
dot: 'bg-rose-400',
|
|
};
|
|
}
|
|
|
|
// 2. Executive - Strategic Amber/Gold
|
|
if (roleStr === 'executive' || roleStr === '2') {
|
|
return {
|
|
label: 'Executive',
|
|
className: 'bg-amber-500/15 text-amber-300 border-amber-500/30 shadow-xs shadow-amber-500/10 font-semibold',
|
|
dot: 'bg-amber-400',
|
|
};
|
|
}
|
|
|
|
// 3. Supervisor - Operational Governance Purple/Violet
|
|
if (roleStr === 'supervisor' || roleStr === '3') {
|
|
return {
|
|
label: 'Supervisor',
|
|
className: 'bg-purple-500/15 text-purple-300 border-purple-500/30 shadow-xs shadow-purple-500/10 font-semibold',
|
|
dot: 'bg-purple-400',
|
|
};
|
|
}
|
|
|
|
// 4. Manager - Programme & Project Management Blue/Sky
|
|
if (roleStr === 'manager' || roleStr === 'programmemanager' || roleStr === 'projectmanager' || roleStr === '4') {
|
|
return {
|
|
label: 'Manager',
|
|
className: 'bg-blue-500/15 text-blue-300 border-blue-500/30 shadow-xs shadow-blue-500/10 font-semibold',
|
|
dot: 'bg-blue-400',
|
|
};
|
|
}
|
|
|
|
// 5. Contributor - Execution & Outreach Emerald/Mint
|
|
if (roleStr === 'contributor' || roleStr === 'viewer' || roleStr === 'recipientviewer' || roleStr === '5') {
|
|
return {
|
|
label: 'Contributor',
|
|
className: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30 shadow-xs shadow-emerald-500/10 font-semibold',
|
|
dot: 'bg-emerald-400',
|
|
};
|
|
}
|
|
|
|
// Fallback for custom formatted role strings
|
|
const formatted = roleStr.charAt(0).toUpperCase() + roleStr.slice(1);
|
|
return {
|
|
label: formatted,
|
|
className: 'bg-cyan-500/15 text-cyan-300 border-cyan-500/30',
|
|
dot: 'bg-cyan-400',
|
|
};
|
|
};
|