227 lines
7 KiB
TypeScript
227 lines
7 KiB
TypeScript
import React from 'react';
|
||
import {
|
||
CheckCircle2,
|
||
AlertTriangle,
|
||
XCircle,
|
||
Clock,
|
||
Radio,
|
||
ShieldAlert,
|
||
Smartphone,
|
||
Server,
|
||
Zap,
|
||
} from 'lucide-react';
|
||
|
||
export type StatusBadgeVariant =
|
||
| 'active'
|
||
| 'trial'
|
||
| 'grace'
|
||
| 'suspended'
|
||
| 'closed'
|
||
| 'normal'
|
||
| 'overdue'
|
||
| 'expiring_7d'
|
||
| 'expiring_30d'
|
||
| 'healthy'
|
||
| 'degraded'
|
||
| 'warning'
|
||
| 'critical'
|
||
| 'offline'
|
||
| 'unknown'
|
||
| 'supported'
|
||
| 'outdated'
|
||
| 'blocked'
|
||
| 'revoked'
|
||
| 'pilot'
|
||
| 'draft'
|
||
| 'percentage'
|
||
| 'general_availability'
|
||
| 'paused'
|
||
| 'rolled_back'
|
||
| 'open'
|
||
| 'mitigating'
|
||
| 'resolved';
|
||
|
||
interface StatusBadgeProps {
|
||
status: StatusBadgeVariant;
|
||
customLabel?: string;
|
||
size?: 'sm' | 'md';
|
||
}
|
||
|
||
interface BadgeConfig {
|
||
label: string;
|
||
className: string;
|
||
icon: React.ReactNode;
|
||
}
|
||
|
||
export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, customLabel, size = 'md' }) => {
|
||
const isSmall = size === 'sm';
|
||
const iconSize = isSmall ? 'w-3 h-3' : 'w-3.5 h-3.5';
|
||
|
||
const configs: Record<StatusBadgeVariant, BadgeConfig> = {
|
||
// Platform & tenant
|
||
active: {
|
||
label: 'Активна',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
trial: {
|
||
label: 'Trial (Пробный)',
|
||
className: 'bg-amber-950/80 text-amber-300 border-amber-700/50',
|
||
icon: <Clock className={iconSize} aria-hidden="true" />,
|
||
},
|
||
grace: {
|
||
label: 'Grace Period',
|
||
className: 'bg-orange-950/80 text-orange-400 border-orange-700/50',
|
||
icon: <AlertTriangle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
suspended: {
|
||
label: 'Приостановлена',
|
||
className: 'bg-rose-950/80 text-rose-400 border-rose-700/50',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
closed: {
|
||
label: 'Закрыта',
|
||
className: 'bg-zinc-900 text-zinc-400 border-zinc-700/50',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
normal: {
|
||
label: 'Оплачена',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
overdue: {
|
||
label: 'Просрочена',
|
||
className: 'bg-rose-950/80 text-rose-400 border-rose-700/50',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
expiring_7d: {
|
||
label: 'Истекает (< 7 дн.)',
|
||
className: 'bg-amber-950/80 text-amber-300 border-amber-700/50 font-semibold',
|
||
icon: <Clock className={iconSize} aria-hidden="true" />,
|
||
},
|
||
expiring_30d: {
|
||
label: 'Истекает (< 30 дн.)',
|
||
className: 'bg-sky-950/80 text-sky-300 border-sky-700/50',
|
||
icon: <Clock className={iconSize} aria-hidden="true" />,
|
||
},
|
||
|
||
// Health
|
||
healthy: {
|
||
label: 'В норме',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
degraded: {
|
||
label: 'Деградация',
|
||
className: 'bg-amber-950/80 text-amber-400 border-amber-700/50',
|
||
icon: <AlertTriangle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
warning: {
|
||
label: 'Предупреждение',
|
||
className: 'bg-amber-950/80 text-amber-400 border-amber-700/50',
|
||
icon: <AlertTriangle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
critical: {
|
||
label: 'Критический',
|
||
className: 'bg-red-950/80 text-red-400 border-red-700/50 font-semibold animate-pulse',
|
||
icon: <ShieldAlert className={iconSize} aria-hidden="true" />,
|
||
},
|
||
offline: {
|
||
label: 'Offline / Сбой',
|
||
className: 'bg-zinc-900 text-zinc-400 border-zinc-700/60',
|
||
icon: <Radio className={iconSize} aria-hidden="true" />,
|
||
},
|
||
unknown: {
|
||
label: 'Неизвестно',
|
||
className: 'bg-zinc-900 text-zinc-500 border-zinc-800',
|
||
icon: <Radio className={iconSize} aria-hidden="true" />,
|
||
},
|
||
|
||
// Installations
|
||
supported: {
|
||
label: 'Поддерживается',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
outdated: {
|
||
label: 'Устарела',
|
||
className: 'bg-amber-950/80 text-amber-300 border-amber-700/50',
|
||
icon: <Smartphone className={iconSize} aria-hidden="true" />,
|
||
},
|
||
blocked: {
|
||
label: 'Заблокирована',
|
||
className: 'bg-rose-950/80 text-rose-400 border-rose-700/50',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
revoked: {
|
||
label: 'Отозвана',
|
||
className: 'bg-zinc-900 text-zinc-400 border-zinc-700',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
|
||
// Rollout
|
||
draft: {
|
||
label: 'Черновик',
|
||
className: 'bg-zinc-900 text-zinc-400 border-zinc-700',
|
||
icon: <Clock className={iconSize} aria-hidden="true" />,
|
||
},
|
||
pilot: {
|
||
label: 'Пилот (Pilot)',
|
||
className: 'bg-indigo-950/80 text-indigo-300 border-indigo-700/50',
|
||
icon: <Zap className={iconSize} aria-hidden="true" />,
|
||
},
|
||
percentage: {
|
||
label: 'Поэтапная',
|
||
className: 'bg-sky-950/80 text-sky-300 border-sky-700/50',
|
||
icon: <Server className={iconSize} aria-hidden="true" />,
|
||
},
|
||
general_availability: {
|
||
label: 'Релиз (GA)',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
paused: {
|
||
label: 'На паузе',
|
||
className: 'bg-amber-950/80 text-amber-300 border-amber-700/50',
|
||
icon: <AlertTriangle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
rolled_back: {
|
||
label: 'Откатан (Rollback)',
|
||
className: 'bg-rose-950/80 text-rose-400 border-rose-700/50',
|
||
icon: <XCircle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
|
||
// Incidents
|
||
open: {
|
||
label: 'Открыт',
|
||
className: 'bg-rose-950/80 text-rose-400 border-rose-700/50 font-semibold',
|
||
icon: <AlertTriangle className={iconSize} aria-hidden="true" />,
|
||
},
|
||
mitigating: {
|
||
label: 'Митигация',
|
||
className: 'bg-amber-950/80 text-amber-300 border-amber-700/50',
|
||
icon: <Clock className={iconSize} aria-hidden="true" />,
|
||
},
|
||
resolved: {
|
||
label: 'Разрешен',
|
||
className: 'bg-emerald-950/80 text-emerald-400 border-emerald-700/50',
|
||
icon: <CheckCircle2 className={iconSize} aria-hidden="true" />,
|
||
},
|
||
};
|
||
|
||
const config = configs[status] || configs.unknown;
|
||
const label = customLabel || config.label;
|
||
|
||
return (
|
||
<span
|
||
className={`inline-flex items-center gap-1.5 rounded-md border font-medium tabular-nums tracking-wide transition-colors ${
|
||
isSmall ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-xs'
|
||
} ${config.className}`}
|
||
data-status={status}
|
||
data-testid="status-badge"
|
||
>
|
||
{config.icon}
|
||
<span>{label}</span>
|
||
</span>
|
||
);
|
||
};
|