/** * Dashboard Utilities * Helper functions for dashboard data processing and formatting */ import type { Activity, ActivityFilter, ActivityFilterConfig } from "@customer-portal/domain/dashboard"; /** * Activity filter configurations */ export const ACTIVITY_FILTERS: ActivityFilterConfig[] = [ { key: "all", label: "All" }, { key: "billing", label: "Billing", types: ["invoice_created", "invoice_paid"], }, { key: "orders", label: "Orders", types: ["service_activated"], }, { key: "support", label: "Support", types: ["case_created", "case_closed"], }, ]; /** * Filter activities by type */ export function filterActivities(activities: Activity[], filter: ActivityFilter): Activity[] { if (filter === "all") { return activities; } const filterConfig = ACTIVITY_FILTERS.find(f => f.key === filter); if (!filterConfig?.types) { return activities; } return activities.filter(activity => filterConfig.types!.includes(activity.type)); } /** * Check if an activity is clickable (navigable) */ export function isActivityClickable(activity: Activity): boolean { const clickableTypes: Activity["type"][] = ["invoice_created", "invoice_paid"]; return clickableTypes.includes(activity.type) && !!activity.relatedId; } /** * Get navigation path for an activity */ export function getActivityNavigationPath(activity: Activity): string | null { if (!isActivityClickable(activity) || !activity.relatedId) { return null; } switch (activity.type) { case "invoice_created": case "invoice_paid": return `/billing/invoices/${activity.relatedId}`; case "service_activated": return `/subscriptions/${activity.relatedId}`; case "case_created": case "case_closed": return `/support/cases/${activity.relatedId}`; default: return null; } } /** * Format activity date for display */ export function formatActivityDate(date: string): string { try { const activityDate = new Date(date); const now = new Date(); const diffInHours = (now.getTime() - activityDate.getTime()) / (1000 * 60 * 60); if (diffInHours < 1) { const diffInMinutes = Math.floor(diffInHours * 60); return diffInMinutes <= 1 ? "Just now" : `${diffInMinutes} minutes ago`; } else if (diffInHours < 24) { const hours = Math.floor(diffInHours); return `${hours} hour${hours === 1 ? "" : "s"} ago`; } else if (diffInHours < 48) { return "Yesterday"; } else { return activityDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: activityDate.getFullYear() !== now.getFullYear() ? "numeric" : undefined, }); } } catch { return "Unknown date"; } } /** * Get activity icon gradient class */ export function getActivityIconGradient(activityType: Activity["type"]): string { const gradientMap: Record = { invoice_created: "from-blue-500 to-cyan-500", invoice_paid: "from-green-500 to-emerald-500", service_activated: "from-purple-500 to-pink-500", case_created: "from-yellow-500 to-orange-500", case_closed: "from-green-500 to-emerald-500", }; return gradientMap[activityType] || "from-gray-500 to-slate-500"; } /** * Truncate text to specified length */ export function truncateText(text: string, maxLength = 28): string { if (text.length <= maxLength) { return text; } return text.slice(0, Math.max(0, maxLength - 1)) + "…"; } /** * Generate dashboard task suggestions based on summary data */ export function generateDashboardTasks(summary: { nextInvoice?: { id: number } | null; stats?: { unpaidInvoices?: number; openCases?: number }; }): Array<{ label: string; href: string }> { const tasks: Array<{ label: string; href: string }> = []; if (summary.nextInvoice) { tasks.push({ label: "Pay upcoming invoice", href: "#attention", }); } if (summary.stats?.unpaidInvoices && summary.stats.unpaidInvoices > 0) { tasks.push({ label: "Review unpaid invoices", href: "/billing/invoices", }); } if (summary.stats?.openCases && summary.stats.openCases > 0) { tasks.push({ label: "Check support cases", href: "/support/cases", }); } return tasks; } /** * Calculate dashboard loading progress */ export function calculateLoadingProgress(loadingStates: Record): number { const states = Object.values(loadingStates); const completedCount = states.filter(loading => !loading).length; return Math.round((completedCount / states.length) * 100); }