krona-front/src/features/catalog/ModuleInfoModal.tsx

175 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
};