refact & cleanup : Sidebar, Button, card, EmptyState, Loader, ServiceObject, StatusBadge, UptameObject, BackupWidget, SecurityWidget, ServicesWidget, SupportCtaWidget, UptimeWidget, DashboardLayout
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/utils/utils';
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
children, variant = 'primary', size = 'md', isLoading, className, disabled, ...props
|
||||
}) => {
|
||||
const baseStyles = "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2";
|
||||
|
||||
const variants = {
|
||||
primary: "bg-blue-900 text-white hover:bg-blue-800 focus:ring-blue-900 shadow-sm",
|
||||
outline: "border border-slate-200 text-slate-900 hover:border-blue-900 hover:bg-blue-50 focus:ring-blue-900",
|
||||
ghost: "text-slate-600 hover:text-slate-900 hover:bg-slate-100 focus:ring-slate-500",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "px-3 py-1.5 text-sm",
|
||||
md: "px-4 py-2 text-base",
|
||||
lg: "w-full py-3 text-base",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={disabled || isLoading}
|
||||
className={cn(baseStyles, variants[variant], sizes[size], (disabled || isLoading) && "opacity-50 cursor-not-allowed", className)}
|
||||
{...props}
|
||||
>
|
||||
{isLoading && <span className="mr-2 w-4 h-4 rounded-full border-2 border-current border-b-transparent animate-spin" />}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/utils/utils';
|
||||
|
||||
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
Card.displayName = "Card";
|
||||
|
||||
export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("px-6 py-5 border-b border-slate-100 flex flex-col space-y-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 flex-1", className)} {...props} />
|
||||
)
|
||||
);
|
||||
CardContent.displayName = "CardContent";
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@utils/utils';
|
||||
|
||||
interface EmptyStateProps {
|
||||
message: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const EmptyState: React.FC<EmptyStateProps> = ({ message, className }) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
"flex-1 flex justify-center items-center py-8 text-sm text-slate-500 italic",
|
||||
className
|
||||
)}>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@utils/utils';
|
||||
|
||||
interface LoaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Loader: React.FC<LoaderProps> = ({ className }) => {
|
||||
return (
|
||||
<div className={cn("flex-1 flex justify-center items-center py-8", className)}>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-slate-900"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import type { ManagedContract } from '@/types/ManagedContract';
|
||||
import { StatusBadge } from '@/components/ui/StatusBadge';
|
||||
|
||||
interface ServiceObjectProps {
|
||||
contract: ManagedContract;
|
||||
}
|
||||
|
||||
export const ServiceObject: React.FC<ServiceObjectProps> = ({ contract }) => {
|
||||
return (
|
||||
<div className="p-4 bg-slate-50 rounded-lg border border-slate-100 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-slate-900">{contract.service_name}</h3>
|
||||
{contract.description && (
|
||||
<p className="text-xs text-slate-500 mt-1">{contract.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Fonctionne instantanément avec 'Actif', 'En déploiement', 'Suspendu' */}
|
||||
<StatusBadge status={contract.status} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
// Fichier : src/components/ui/StatusBadge.tsx
|
||||
|
||||
import React from 'react';
|
||||
import { cn } from '@/utils/utils';
|
||||
import type { AegisStatus } from '@/types/Status';
|
||||
|
||||
interface StatusBadgeProps {
|
||||
status: AegisStatus;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Configuration visuelle unique pour chaque statut
|
||||
const STATUS_CONFIG: Record<AegisStatus, { bg: string; dot: string }> = {
|
||||
// Statuts Infrastructure & Contrats Positifs
|
||||
'Opérationnel': {
|
||||
bg: 'bg-emerald-50 text-emerald-700 border-emerald-200/60',
|
||||
dot: 'bg-emerald-500',
|
||||
},
|
||||
'Actif': {
|
||||
bg: 'bg-emerald-50 text-emerald-700 border-emerald-200/60',
|
||||
dot: 'bg-emerald-500',
|
||||
},
|
||||
|
||||
// Statuts Intermédiaires / En cours
|
||||
'Dégradé': {
|
||||
bg: 'bg-amber-50 text-amber-700 border-amber-200/60',
|
||||
dot: 'bg-amber-500',
|
||||
},
|
||||
'En déploiement': {
|
||||
bg: 'bg-blue-50 text-blue-700 border-blue-200/60',
|
||||
dot: 'bg-blue-500',
|
||||
},
|
||||
|
||||
// Statuts Alertes / Inactifs
|
||||
'Hors Ligne': {
|
||||
bg: 'bg-red-50 text-red-700 border-red-200/60',
|
||||
dot: 'bg-red-500',
|
||||
},
|
||||
'Suspendu': {
|
||||
bg: 'bg-slate-100 text-slate-700 border-slate-200',
|
||||
dot: 'bg-slate-400',
|
||||
},
|
||||
};
|
||||
|
||||
export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className }) => {
|
||||
// Récupération de la configuration ou fallback de sécurité
|
||||
const config = STATUS_CONFIG[status] || {
|
||||
bg: 'bg-slate-50 text-slate-600 border-slate-200',
|
||||
dot: 'bg-slate-400',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"px-2.5 py-1 rounded-full text-xs font-semibold inline-flex items-center border transition-colors",
|
||||
config.bg,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full mr-2 shrink-0", config.dot)} />
|
||||
{status}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusBadge;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import type { InfrastructureNode } from '@/types/InfrastructureNode';
|
||||
import { StatusBadge } from '@ui/StatusBadge';
|
||||
|
||||
interface UptimeObjectProps {
|
||||
node: InfrastructureNode;
|
||||
}
|
||||
|
||||
export const UptimeObject: React.FC<UptimeObjectProps> = ({ node }) => {
|
||||
return (
|
||||
// La "key" a été retirée d'ici, elle sera gérée par le parent
|
||||
<div className="py-4 flex items-center justify-between first:pt-0 last:pb-0">
|
||||
|
||||
{/* Infos Serveur */}
|
||||
<div>
|
||||
<p className="font-medium text-slate-900">{node.label}</p>
|
||||
<p className="text-xs text-slate-500 mt-0.5">{node.type}</p>
|
||||
</div>
|
||||
|
||||
{/* Métriques & Badge */}
|
||||
<div className="flex items-center space-x-6">
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-slate-500">SLA</p>
|
||||
<p className="text-sm font-semibold text-slate-900">{node.uptime_sla}</p>
|
||||
</div>
|
||||
|
||||
<StatusBadge status={node.status} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user