151 lines
5.3 KiB
TypeScript
151 lines
5.3 KiB
TypeScript
import React from 'react';
|
||
import { CatalogModuleItem, READINESS_CONFIG, AVAILABILITY_CONFIG } from './catalogData';
|
||
import { MaturityBadge } from '../../shared/ui/MaturityBadge';
|
||
import { Badge } from '../../shared/ui/Badge';
|
||
import { Button } from '../../shared/ui/Button';
|
||
import {
|
||
Users,
|
||
ShoppingBag,
|
||
Boxes,
|
||
Truck,
|
||
Workflow,
|
||
UserCheck,
|
||
MessageSquareText,
|
||
Headset,
|
||
CheckSquare,
|
||
BadgeDollarSign,
|
||
Megaphone,
|
||
BarChart3,
|
||
Cpu,
|
||
Sparkles,
|
||
Globe,
|
||
Settings,
|
||
Grid,
|
||
ArrowRight,
|
||
Info,
|
||
Check,
|
||
} from 'lucide-react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
|
||
const ICON_COMPONENTS: Record<string, React.ReactNode> = {
|
||
Users: <Users className="w-5 h-5 text-emerald-500" />,
|
||
ShoppingBag: <ShoppingBag className="w-5 h-5 text-sky-500" />,
|
||
Boxes: <Boxes className="w-5 h-5 text-amber-500" />,
|
||
Truck: <Truck className="w-5 h-5 text-orange-500" />,
|
||
Workflow: <Workflow className="w-5 h-5 text-krona-gold" />,
|
||
UserCheck: <UserCheck className="w-5 h-5 text-indigo-500" />,
|
||
MessageSquareText: <MessageSquareText className="w-5 h-5 text-teal-500" />,
|
||
Headset: <Headset className="w-5 h-5 text-rose-500" />,
|
||
CheckSquare: <CheckSquare className="w-5 h-5 text-blue-500" />,
|
||
BadgeDollarSign: <BadgeDollarSign className="w-5 h-5 text-emerald-600" />,
|
||
Megaphone: <Megaphone className="w-5 h-5 text-pink-500" />,
|
||
BarChart3: <BarChart3 className="w-5 h-5 text-violet-500" />,
|
||
Cpu: <Cpu className="w-5 h-5 text-cyan-500" />,
|
||
Sparkles: <Sparkles className="w-5 h-5 text-krona-gold" />,
|
||
Globe: <Globe className="w-5 h-5 text-emerald-400" />,
|
||
Settings: <Settings className="w-5 h-5 text-slate-400" />,
|
||
Grid: <Grid className="w-5 h-5 text-amber-400" />,
|
||
};
|
||
|
||
export interface ModuleCardProps {
|
||
module: CatalogModuleItem;
|
||
onOpenDetails: (module: CatalogModuleItem) => void;
|
||
isRecommended?: boolean;
|
||
}
|
||
|
||
export const ModuleCard: React.FC<ModuleCardProps> = ({
|
||
module,
|
||
onOpenDetails,
|
||
isRecommended = false,
|
||
}) => {
|
||
const navigate = useNavigate();
|
||
const readiness = READINESS_CONFIG[module.readiness];
|
||
const availability = AVAILABILITY_CONFIG[module.availability];
|
||
|
||
const handleOpen = () => {
|
||
navigate(module.route);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
data-testid={`module-card-${module.id}`}
|
||
className={`bg-krona-surface rounded-2xl border transition-all duration-200 flex flex-col justify-between hover:shadow-krona-card hover:border-krona-gold/60 p-5 ${
|
||
isRecommended
|
||
? 'border-krona-gold/50 bg-gradient-to-b from-krona-gold/5 to-transparent'
|
||
: 'border-krona-border'
|
||
}`}
|
||
>
|
||
<div className="space-y-3.5">
|
||
{/* Top meta bar: category + badges */}
|
||
<div className="flex items-center justify-between gap-2 flex-wrap">
|
||
<div className="flex items-center gap-2">
|
||
<div className="p-2 rounded-xl bg-krona-workspace border border-krona-border/60 shadow-xs">
|
||
{ICON_COMPONENTS[module.iconName] || <Grid className="w-5 h-5 text-krona-secondary" />}
|
||
</div>
|
||
{isRecommended && (
|
||
<span className="text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full bg-krona-gold/20 text-krona-gold-text border border-krona-gold/40">
|
||
★ Рекомендовано
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex items-center gap-1.5 flex-wrap">
|
||
<MaturityBadge level={readiness.maturity} size="sm" />
|
||
<Badge variant={availability.variant} size="sm">
|
||
{availability.label}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Title and description */}
|
||
<div>
|
||
<h3 className="text-base font-bold text-krona-primary leading-tight">
|
||
{module.title}
|
||
</h3>
|
||
<p className="text-xs text-krona-secondary mt-1.5 line-clamp-2 leading-relaxed">
|
||
{module.description}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Features list */}
|
||
<div className="space-y-1.5 pt-1 border-t border-krona-border/50">
|
||
<span className="text-[10px] uppercase font-semibold tracking-wider text-krona-secondary block mb-1">
|
||
Ключевые возможности:
|
||
</span>
|
||
{module.features.slice(0, 3).map((f, i) => (
|
||
<div key={i} className="flex items-start gap-1.5 text-xs text-krona-primary">
|
||
<Check className="w-3.5 h-3.5 text-krona-green shrink-0 mt-0.5" />
|
||
<span className="line-clamp-1">{f}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Action buttons */}
|
||
<div className="flex items-center gap-2 pt-4 mt-4 border-t border-krona-border/60">
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={() => onOpenDetails(module)}
|
||
className="text-xs px-2.5"
|
||
title="Подробная информация о модуле"
|
||
data-testid={`module-details-btn-${module.id}`}
|
||
>
|
||
<Info className="w-3.5 h-3.5 mr-1" />
|
||
<span>О модуле</span>
|
||
</Button>
|
||
|
||
<Button
|
||
variant={isRecommended ? 'primary' : 'secondary'}
|
||
size="sm"
|
||
onClick={handleOpen}
|
||
className="flex-1 text-xs justify-center"
|
||
data-testid={`module-open-btn-${module.id}`}
|
||
>
|
||
<span>Открыть</span>
|
||
<ArrowRight className="w-3.5 h-3.5 ml-1" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|