175 lines
6.6 KiB
TypeScript
175 lines
6.6 KiB
TypeScript
import React, { useEffect } from 'react';
|
||
import { CatalogModuleItem, READINESS_CONFIG, AVAILABILITY_CONFIG } from './catalogData';
|
||
import { Button } from '../../shared/ui/Button';
|
||
import { Badge } from '../../shared/ui/Badge';
|
||
import { MaturityBadge } from '../../shared/ui/MaturityBadge';
|
||
import {
|
||
X,
|
||
ArrowRight,
|
||
CheckCircle2,
|
||
Layers,
|
||
Link2,
|
||
ShieldCheck,
|
||
Target,
|
||
} from 'lucide-react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
|
||
export interface ModuleInfoModalProps {
|
||
module: CatalogModuleItem | null;
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export const ModuleInfoModal: React.FC<ModuleInfoModalProps> = ({ module, isOpen, onClose }) => {
|
||
const navigate = useNavigate();
|
||
|
||
// Escape key handler
|
||
useEffect(() => {
|
||
if (!isOpen) return;
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === 'Escape') onClose();
|
||
};
|
||
window.addEventListener('keydown', handleKeyDown);
|
||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
}, [isOpen, onClose]);
|
||
|
||
if (!isOpen || !module) return null;
|
||
|
||
const readinessInfo = READINESS_CONFIG[module.readiness];
|
||
const availabilityInfo = AVAILABILITY_CONFIG[module.availability];
|
||
|
||
const handleOpenModule = () => {
|
||
onClose();
|
||
navigate(module.route);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="module-info-title"
|
||
data-testid="module-info-modal"
|
||
>
|
||
{/* Backdrop */}
|
||
<div
|
||
className="fixed inset-0 bg-black/60 backdrop-blur-xs animate-fade-in"
|
||
onClick={onClose}
|
||
/>
|
||
|
||
{/* Modal Card */}
|
||
<div className="relative bg-krona-surface border border-krona-border rounded-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-krona-modal z-10 animate-scale-in p-6 space-y-6">
|
||
{/* Header */}
|
||
<div className="flex items-start justify-between gap-4 border-b border-krona-border/70 pb-4">
|
||
<div className="space-y-1.5">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<span className="text-xs uppercase font-bold tracking-wider text-krona-gold">
|
||
Информация о модуле
|
||
</span>
|
||
<MaturityBadge level={readinessInfo.maturity} size="sm" />
|
||
<Badge variant={availabilityInfo.variant} size="sm">
|
||
{availabilityInfo.label}
|
||
</Badge>
|
||
</div>
|
||
<h2 id="module-info-title" className="text-xl font-bold text-krona-primary">
|
||
{module.title}
|
||
</h2>
|
||
<p className="text-xs text-krona-secondary">
|
||
{module.description}
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={onClose}
|
||
className="p-1.5 rounded-lg text-krona-secondary hover:text-krona-primary hover:bg-krona-workspace transition-colors"
|
||
title="Закрыть (Esc)"
|
||
data-testid="close-module-info-modal"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Section 1: Purpose */}
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2 text-xs font-bold text-krona-primary uppercase tracking-wider">
|
||
<Target className="w-4 h-4 text-krona-green" />
|
||
<span>1. Назначение модуля</span>
|
||
</div>
|
||
<div className="p-3 bg-krona-workspace rounded-xl border border-krona-border text-xs text-krona-primary leading-relaxed">
|
||
{module.details.purpose}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section 2: Expected Capabilities */}
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2 text-xs font-bold text-krona-primary uppercase tracking-wider">
|
||
<Layers className="w-4 h-4 text-krona-gold" />
|
||
<span>2. Ключевые функциональные возможности</span>
|
||
</div>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||
{module.details.expectedCapabilities.map((cap, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="p-2.5 rounded-xl border border-krona-border bg-krona-surface text-xs text-krona-primary flex items-start gap-2"
|
||
>
|
||
<CheckCircle2 className="w-4 h-4 text-krona-green shrink-0 mt-0.5" />
|
||
<span>{cap}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section 3: Current Status */}
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2 text-xs font-bold text-krona-primary uppercase tracking-wider">
|
||
<ShieldCheck className="w-4 h-4 text-sky-500" />
|
||
<span>3. Статус реализации</span>
|
||
</div>
|
||
<div className="p-3 rounded-xl border border-sky-500/30 bg-sky-500/10 text-xs text-sky-900 dark:text-sky-200 leading-relaxed">
|
||
<strong>{readinessInfo.label}:</strong> {module.details.currentStatus}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section 4: Planned Integrations */}
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2 text-xs font-bold text-krona-primary uppercase tracking-wider">
|
||
<Link2 className="w-4 h-4 text-purple-500" />
|
||
<span>4. Запланированные интеграции</span>
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
{module.details.plannedIntegrations.map((integ, idx) => (
|
||
<span
|
||
key={idx}
|
||
className="px-2.5 py-1 text-xs rounded-lg border border-krona-border bg-krona-workspace text-krona-primary font-medium"
|
||
>
|
||
{integ}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer Actions */}
|
||
<div className="flex items-center justify-between pt-4 border-t border-krona-border/70">
|
||
<span className="text-[11px] text-krona-secondary">
|
||
Маршрут в системе: <code className="font-mono text-krona-primary">{module.route}</code>
|
||
</span>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<Button variant="secondary" size="sm" onClick={onClose}>
|
||
Закрыть
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
onClick={handleOpenModule}
|
||
data-testid={`modal-open-module-${module.id}`}
|
||
>
|
||
<span>Перейти в модуль</span>
|
||
<ArrowRight className="w-3.5 h-3.5 ml-1" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|