krona-front/src/shared/ui/StatCard.tsx

85 lines
2.9 KiB
TypeScript

import React from 'react';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { TrendingUp, TrendingDown, Minus } from 'lucide-react';
export interface StatCardProps {
title: string;
value: string | number;
change?: {
value: string;
trend: 'up' | 'down' | 'neutral';
label?: string;
};
subtitle?: string;
icon?: React.ReactNode;
actionText?: string;
onAction?: () => void;
className?: string;
}
export const StatCard: React.FC<StatCardProps> = ({
title,
value,
change,
subtitle,
icon,
actionText,
onAction,
className,
}) => {
return (
<div
className={twMerge(
clsx(
'bg-krona-surface border border-krona-border rounded-lg p-4 shadow-krona-soft flex flex-col justify-between transition-all duration-150 hover:border-krona-border-strong',
className
)
)}
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-krona-secondary truncate">{title}</p>
<div className="mt-1.5 flex items-baseline gap-2">
<span className="text-2xl font-semibold text-krona-primary tracking-tight">{value}</span>
{change && (
<span
className={clsx(
'inline-flex items-center text-xs font-medium px-1.5 py-0.5 rounded',
change.trend === 'up' && 'text-status-success-text bg-status-success-soft',
change.trend === 'down' && 'text-status-danger-text bg-status-danger-soft',
change.trend === 'neutral' && 'text-krona-secondary bg-krona-muted'
)}
>
{change.trend === 'up' && <TrendingUp className="w-3 h-3 mr-0.5" aria-hidden="true" />}
{change.trend === 'down' && <TrendingDown className="w-3 h-3 mr-0.5" aria-hidden="true" />}
{change.trend === 'neutral' && <Minus className="w-3 h-3 mr-0.5" aria-hidden="true" />}
{change.value}
</span>
)}
</div>
</div>
{icon && (
<div className="p-2.5 rounded-lg bg-krona-workspace border border-krona-border/60 text-krona-secondary shrink-0">
{icon}
</div>
)}
</div>
{(subtitle || actionText) && (
<div className="mt-3 pt-2.5 border-t border-krona-border/40 flex items-center justify-between text-xs text-krona-secondary">
{subtitle && <span>{subtitle}</span>}
{actionText && (
<button
type="button"
onClick={onAction}
className="text-krona-green hover:text-krona-green-hover font-medium hover:underline focus:outline-none ml-auto cursor-pointer"
>
{actionText} →
</button>
)}
</div>
)}
</div>
);
};