2025-09-17 18:43:43 +09:00
|
|
|
/**
|
|
|
|
|
* Dashboard Utilities
|
|
|
|
|
* Helper functions for dashboard data processing and formatting
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-27 15:47:50 +09:00
|
|
|
import {
|
|
|
|
|
invoiceActivityMetadataSchema,
|
|
|
|
|
serviceActivityMetadataSchema,
|
2025-11-26 16:36:06 +09:00
|
|
|
type Activity,
|
|
|
|
|
// Re-export business logic from domain
|
|
|
|
|
ACTIVITY_FILTERS,
|
|
|
|
|
filterActivities,
|
|
|
|
|
isActivityClickable,
|
2025-12-23 17:53:08 +09:00
|
|
|
generateQuickActions,
|
|
|
|
|
type QuickActionTask,
|
2025-11-26 16:36:06 +09:00
|
|
|
type DashboardTaskSummary,
|
2025-10-21 13:44:14 +09:00
|
|
|
} from "@customer-portal/domain/dashboard";
|
2025-12-26 14:53:03 +09:00
|
|
|
import { Formatting } from "@customer-portal/domain/toolkit";
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-11-26 16:36:06 +09:00
|
|
|
// Re-export domain business logic for backward compatibility
|
|
|
|
|
export {
|
|
|
|
|
ACTIVITY_FILTERS,
|
|
|
|
|
filterActivities,
|
|
|
|
|
isActivityClickable,
|
2025-12-23 17:53:08 +09:00
|
|
|
generateQuickActions,
|
|
|
|
|
type QuickActionTask,
|
2025-11-26 16:36:06 +09:00
|
|
|
type DashboardTaskSummary,
|
|
|
|
|
};
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-26 14:53:03 +09:00
|
|
|
const formatCurrencyUtil = Formatting.formatCurrency;
|
|
|
|
|
|
2025-09-17 18:43:43 +09:00
|
|
|
/**
|
|
|
|
|
* 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":
|
2025-12-17 15:44:46 +09:00
|
|
|
return `/account/billing/invoices/${activity.relatedId}`;
|
2025-09-17 18:43:43 +09:00
|
|
|
case "service_activated":
|
2025-12-25 13:20:45 +09:00
|
|
|
return `/account/subscriptions/${activity.relatedId}`;
|
2025-09-17 18:43:43 +09:00
|
|
|
case "case_created":
|
|
|
|
|
case "case_closed":
|
2025-12-17 15:44:46 +09:00
|
|
|
return `/account/support/${activity.relatedId}`;
|
2025-09-17 18:43:43 +09:00
|
|
|
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",
|
2026-01-15 11:28:25 +09:00
|
|
|
year: activityDate.getFullYear() === now.getFullYear() ? undefined : "numeric",
|
2025-09-17 18:43:43 +09:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
return "Unknown date";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-19 15:14:39 +09:00
|
|
|
function formatInvoiceActivity(activity: Activity): string | null {
|
|
|
|
|
const parsed = invoiceActivityMetadataSchema.safeParse(activity.metadata ?? {});
|
|
|
|
|
if (!parsed.success || typeof parsed.data.amount !== "number") {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formattedAmount = formatCurrencyUtil(parsed.data.amount, parsed.data.currency);
|
|
|
|
|
if (!formattedAmount) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return activity.type === "invoice_paid"
|
|
|
|
|
? `${formattedAmount} payment completed`
|
|
|
|
|
: `${formattedAmount} invoice generated`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatServiceActivity(activity: Activity): string | null {
|
|
|
|
|
const parsed = serviceActivityMetadataSchema.safeParse(activity.metadata ?? {});
|
|
|
|
|
if (!parsed.success || !parsed.data.productName) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return `${parsed.data.productName} is now active`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-21 13:44:14 +09:00
|
|
|
export function formatActivityDescription(activity: Activity): string {
|
2026-01-19 15:14:39 +09:00
|
|
|
const fallback = activity.description ?? "";
|
|
|
|
|
|
2025-10-21 13:44:14 +09:00
|
|
|
switch (activity.type) {
|
|
|
|
|
case "invoice_created":
|
2026-01-19 15:14:39 +09:00
|
|
|
case "invoice_paid":
|
|
|
|
|
return formatInvoiceActivity(activity) ?? fallback;
|
|
|
|
|
case "service_activated":
|
|
|
|
|
return formatServiceActivity(activity) ?? fallback;
|
2025-10-21 13:44:14 +09:00
|
|
|
default:
|
2026-01-19 15:14:39 +09:00
|
|
|
return fallback;
|
2025-10-21 13:44:14 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 18:43:43 +09:00
|
|
|
/**
|
|
|
|
|
* 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)) + "…";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate dashboard loading progress
|
|
|
|
|
*/
|
|
|
|
|
export function calculateLoadingProgress(loadingStates: Record<string, boolean>): number {
|
|
|
|
|
const states = Object.values(loadingStates);
|
|
|
|
|
const completedCount = states.filter(loading => !loading).length;
|
|
|
|
|
return Math.round((completedCount / states.length) * 100);
|
|
|
|
|
}
|