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

151 lines
5.3 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 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>
);
};