Files
helix-engage/src/components/admin/integration-health.tsx

115 lines
4.5 KiB
TypeScript

import { BadgeWithDot } from '@/components/base/badges/badges';
import { cx } from '@/utils/cx';
import type { IntegrationStatus, AuthStatus, LeadIngestionSource } from '@/types/entities';
interface IntegrationHealthProps {
sources: LeadIngestionSource[];
}
const statusBorderMap: Record<IntegrationStatus, string> = {
ACTIVE: 'border-secondary',
WARNING: 'border-warning',
ERROR: 'border-error',
DISABLED: 'border-secondary',
};
const statusBadgeColorMap: Record<IntegrationStatus, 'success' | 'warning' | 'error' | 'gray'> = {
ACTIVE: 'success',
WARNING: 'warning',
ERROR: 'error',
DISABLED: 'gray',
};
const authBadgeColorMap: Record<AuthStatus, 'success' | 'warning' | 'error' | 'gray'> = {
VALID: 'success',
EXPIRING_SOON: 'warning',
EXPIRED: 'error',
NOT_CONFIGURED: 'gray',
};
function formatRelativeTime(isoString: string): string {
const diffMs = Date.now() - new Date(isoString).getTime();
const diffMinutes = Math.floor(diffMs / (1000 * 60));
if (diffMinutes < 1) return 'just now';
if (diffMinutes < 60) return `${diffMinutes} min ago`;
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
}
export const IntegrationHealth = ({ sources }: IntegrationHealthProps) => {
return (
<div className="rounded-2xl border border-secondary bg-primary p-5">
<h3 className="text-sm font-bold text-primary">Integration Health</h3>
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
{sources.map((source) => {
const status = source.integrationStatus ?? 'DISABLED';
const authStatus = source.authStatus ?? 'NOT_CONFIGURED';
const showAuthBadge = authStatus !== 'VALID';
return (
<div
key={source.id}
className={cx(
'rounded-xl border bg-primary p-4',
statusBorderMap[status],
)}
>
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-primary">
{source.sourceName}
</span>
<BadgeWithDot
size="sm"
type="pill-color"
color={statusBadgeColorMap[status]}
>
{status}
</BadgeWithDot>
</div>
<p className="mt-2 text-xs text-tertiary">
{source.leadsReceivedLast24h ?? 0} leads in 24h
</p>
{source.lastSuccessfulSyncAt && (
<p className="mt-0.5 text-xs text-quaternary">
Last sync: {formatRelativeTime(source.lastSuccessfulSyncAt)}
</p>
)}
{showAuthBadge && (
<div className="mt-2">
<BadgeWithDot
size="sm"
type="pill-color"
color={authBadgeColorMap[authStatus]}
>
Auth: {authStatus.replace(/_/g, ' ')}
</BadgeWithDot>
</div>
)}
{(status === 'WARNING' || status === 'ERROR') && source.lastErrorMessage && (
<p
className={cx(
'mt-2 text-xs',
status === 'ERROR' ? 'text-error-primary' : 'text-warning-primary',
)}
>
{source.lastErrorMessage}
</p>
)}
</div>
);
})}
</div>
</div>
);
};