Files
ccs/ui/src/components/account/continuity-overview.tsx
T
Tam Nhu Tran d9f78ad490 refactor(accounts): consolidate continuity guidance layout
- replace separate readiness and learning cards with one continuity overview surface

- simplify row labels and dialog controls around continuity mode changes

- keep localized continuity guidance aligned with the new component structure

Refs #904
2026-04-04 12:17:32 -04:00

239 lines
9.4 KiB
TypeScript

import {
AlertTriangle,
CheckCircle2,
Info,
Link2,
ShieldAlert,
Unlink,
Waves,
ArrowRight,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
interface ContinuityOverviewProps {
totalAccounts: number;
isolatedCount: number;
sharedStandardCount: number;
deeperSharedCount: number;
sharedAloneCount: number;
sharedPeerAccountCount: number; // The accounts in a viable group
deeperReadyAccountCount: number;
sharedGroups: string[];
sharedPeerGroups: string[];
deeperReadyGroups: string[];
legacyTargetCount: number;
cliproxyCount: number;
}
type ReadinessState =
| 'single'
| 'isolated'
| 'shared-alone'
| 'shared-standard'
| 'partial'
| 'ready';
const InfoTooltip = ({ titleKey, descKey }: { titleKey: string; descKey: string }) => {
const { t } = useTranslation();
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-5 w-5 rounded-full hover:bg-muted text-muted-foreground/70 transition-colors"
>
<Info className="h-3 w-3" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-72 p-4 rounded-xl shadow-lg border-border/50 text-sm"
side="top"
align="center"
>
<p className="font-semibold tracking-tight">{t(titleKey)}</p>
<p className="mt-1.5 text-muted-foreground leading-relaxed">{t(descKey)}</p>
</PopoverContent>
</Popover>
);
};
export function ContinuityOverview({
totalAccounts,
isolatedCount,
sharedStandardCount,
deeperSharedCount,
sharedAloneCount,
sharedPeerAccountCount,
deeperReadyAccountCount,
sharedPeerGroups,
deeperReadyGroups,
legacyTargetCount,
cliproxyCount,
}: ContinuityOverviewProps) {
const { t } = useTranslation();
const readiness: ReadinessState =
totalAccounts < 2
? 'single'
: sharedPeerGroups.length === 0
? isolatedCount === totalAccounts
? 'isolated'
: 'shared-alone'
: deeperReadyGroups.length === 0
? 'shared-standard'
: isolatedCount > 0 ||
sharedAloneCount > 0 ||
deeperReadyAccountCount < sharedPeerAccountCount ||
deeperReadyGroups.length < sharedPeerGroups.length
? 'partial'
: 'ready';
const highlightGroup = deeperReadyGroups[0] || sharedPeerGroups[0] || 'default';
const hasMixedState =
deeperReadyGroups.length > 0 &&
(isolatedCount > 0 ||
sharedStandardCount > 0 ||
sharedPeerGroups.length > deeperReadyGroups.length);
const iconMap = {
ready: <CheckCircle2 className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />,
'shared-standard': <Link2 className="h-6 w-6 text-blue-600 dark:text-blue-400" />,
single: <ShieldAlert className="h-6 w-6 text-stone-400" />,
isolated: <Unlink className="h-6 w-6 text-amber-600 dark:text-amber-400" />,
'shared-alone': <AlertTriangle className="h-6 w-6 text-amber-600 dark:text-amber-400" />,
partial: <AlertTriangle className="h-6 w-6 text-amber-600 dark:text-amber-400" />,
};
const currentIcon = iconMap[readiness];
return (
<div className="flex flex-col gap-4">
{/* Primary Status Bento Box */}
<Card className="flex flex-col justify-between overflow-hidden relative group p-0 border-border bg-card shadow-sm hover:shadow-md transition-shadow duration-300">
<CardContent className="p-6 flex flex-col h-full bg-gradient-to-br from-card to-muted/20 space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="flex flex-col sm:flex-row sm:items-start gap-4">
<div className="rounded-2xl bg-background p-3 shadow-sm ring-1 ring-border/50 self-start shrink-0">
{currentIcon}
</div>
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-lg font-semibold tracking-tight">
{t(`continuityReadiness.messages.${readiness}.title`, {
group: highlightGroup,
})}
</h3>
<Badge
variant={readiness === 'ready' ? 'default' : 'secondary'}
className="rounded-full px-2.5 py-0.5 font-medium shadow-sm"
>
{t(`continuityReadiness.state.${readiness}`)}
</Badge>
</div>
<p className="text-sm text-muted-foreground max-w-lg leading-relaxed">
{t(`continuityReadiness.messages.${readiness}.description`, {
group: highlightGroup,
count: sharedAloneCount,
})}
</p>
</div>
</div>
</div>
<div className="mt-auto pt-4 flex flex-wrap items-center gap-2">
{cliproxyCount > 0 && (
<Badge
variant="outline"
className="text-blue-700 bg-blue-50/50 border-blue-200/60 dark:border-blue-900/40 dark:bg-blue-900/20 dark:text-blue-300"
>
{t('historySyncLearningMap.cliproxyManaged', { count: cliproxyCount })}
</Badge>
)}
{legacyTargetCount > 0 && (
<Badge
variant="outline"
className="text-amber-700 bg-amber-50/50 border-amber-200/60 dark:border-amber-900/40 dark:bg-amber-900/20 dark:text-amber-300"
>
{t('historySyncLearningMap.legacyConfirmation', { count: legacyTargetCount })}
</Badge>
)}
{sharedPeerGroups.length > 0 && deeperReadyGroups.length === 0 && (
<Badge
variant="secondary"
className="font-mono text-[11px] px-2 bg-muted/50 text-muted-foreground border-transparent"
>
Recommend: {highlightGroup}
</Badge>
)}
{hasMixedState && (
<Badge
variant="secondary"
className="font-mono text-[11px] px-2 bg-muted/50 text-muted-foreground border-transparent"
>
Partial sync ({highlightGroup})
</Badge>
)}
</div>
</CardContent>
</Card>
{/* Horizontal Progression Chain */}
<div className="flex flex-col md:flex-row items-center gap-3">
<div className="flex-1 w-full flex items-center justify-between p-3.5 rounded-xl border border-blue-300/40 bg-blue-50/50 dark:border-blue-900/30 dark:bg-blue-900/10 shadow-sm transition-colors hover:bg-blue-100/40 dark:hover:bg-blue-900/20">
<div className="flex items-center gap-2">
<Unlink className="h-4 w-4 text-blue-700/80 dark:text-blue-400/80" />
<span className="text-xs font-semibold uppercase tracking-wider text-blue-900 dark:text-blue-200">
{t('historySyncLearningMap.isolated')}
</span>
<InfoTooltip titleKey="accountsPage.isolated" descKey="accountsPage.isolatedDesc" />
</div>
<span className="text-lg font-mono font-bold text-blue-900 dark:text-blue-200">
{isolatedCount}
</span>
</div>
<ArrowRight className="hidden md:block h-4 w-4 text-muted-foreground/40 shrink-0" />
<div className="flex-1 w-full flex items-center justify-between p-3.5 rounded-xl border border-emerald-300/40 bg-emerald-50/50 dark:border-emerald-900/30 dark:bg-emerald-900/10 shadow-sm transition-colors hover:bg-emerald-100/40 dark:hover:bg-emerald-900/20">
<div className="flex items-center gap-2">
<Link2 className="h-4 w-4 text-emerald-700/80 dark:text-emerald-400/80" />
<span className="text-xs font-semibold uppercase tracking-wider text-emerald-900 dark:text-emerald-200">
{t('historySyncLearningMap.shared')}
</span>
<InfoTooltip
titleKey="accountsPage.sharedStandard"
descKey="accountsPage.sharedStandardDesc"
/>
</div>
<span className="text-lg font-mono font-bold text-emerald-900 dark:text-emerald-200">
{sharedStandardCount}
</span>
</div>
<ArrowRight className="hidden md:block h-4 w-4 text-muted-foreground/40 shrink-0" />
<div className="flex-1 w-full flex items-center justify-between p-3.5 rounded-xl border border-indigo-300/40 bg-indigo-50/50 dark:border-indigo-900/30 dark:bg-indigo-900/10 shadow-sm transition-colors hover:bg-indigo-100/40 dark:hover:bg-indigo-900/20">
<div className="flex items-center gap-2">
<Waves className="h-4 w-4 text-indigo-700/80 dark:text-indigo-400/80" />
<span className="text-xs font-semibold uppercase tracking-wider text-indigo-900 dark:text-indigo-200">
{t('historySyncLearningMap.deeper')}
</span>
<InfoTooltip
titleKey="accountsPage.sharedDeeper"
descKey="accountsPage.sharedDeeperDesc"
/>
</div>
<span className="text-lg font-mono font-bold text-indigo-900 dark:text-indigo-200">
{deeperSharedCount}
</span>
</div>
</div>
</div>
);
}