mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
115 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
};
|