146 lines
3.5 KiB
TypeScript
146 lines
3.5 KiB
TypeScript
/**
|
||
* Карточка со статистикой для Dashboard (iOS 26).
|
||
* Переиспользуется для ментора, клиента, родителя и др. ролей.
|
||
*/
|
||
|
||
'use client';
|
||
|
||
import React from 'react';
|
||
import { Panel } from './ui/Panel';
|
||
|
||
export interface StatCardProps {
|
||
title: string;
|
||
value: string | number;
|
||
icon?: React.ReactNode;
|
||
trend?: {
|
||
value: number;
|
||
isPositive: boolean;
|
||
};
|
||
loading?: boolean;
|
||
subtitle?: string;
|
||
}
|
||
|
||
export const StatCard: React.FC<StatCardProps> = ({
|
||
title,
|
||
value,
|
||
icon,
|
||
trend,
|
||
loading = false,
|
||
subtitle,
|
||
}) => {
|
||
if (loading) {
|
||
return (
|
||
<Panel padding="md" interactive={false}>
|
||
<div
|
||
style={{
|
||
minHeight: 100,
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
justifyContent: 'space-between',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
height: 14,
|
||
background: 'var(--md-sys-color-surface-variant)',
|
||
borderRadius: 8,
|
||
width: '60%',
|
||
marginBottom: 12,
|
||
opacity: 0.6,
|
||
}}
|
||
/>
|
||
<div
|
||
style={{
|
||
height: 28,
|
||
background: 'var(--md-sys-color-surface-variant)',
|
||
borderRadius: 10,
|
||
width: '45%',
|
||
opacity: 0.6,
|
||
}}
|
||
/>
|
||
</div>
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Panel padding="lg" interactive>
|
||
<div
|
||
style={{
|
||
minHeight: 140,
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
justifyContent: 'space-between',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'flex-start',
|
||
marginBottom: 12,
|
||
}}
|
||
>
|
||
<div style={{ flex: 1 }}>
|
||
<p
|
||
style={{
|
||
fontSize: 18,
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
margin: '0 0 4px 0',
|
||
fontWeight: 400,
|
||
}}
|
||
>
|
||
{title}
|
||
</p>
|
||
{subtitle && (
|
||
<p
|
||
style={{
|
||
fontSize: 12,
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
margin: 0,
|
||
opacity: 0.8,
|
||
}}
|
||
>
|
||
{subtitle}
|
||
</p>
|
||
)}
|
||
</div>
|
||
{icon && (
|
||
<div style={{ color: 'var(--md-sys-color-primary)', opacity: 0.85 }}>
|
||
{icon}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div>
|
||
<p
|
||
style={{
|
||
fontSize: 40,
|
||
fontWeight: 600,
|
||
color: 'var(--md-sys-color-primary)',
|
||
margin: '0 0 6px 0',
|
||
lineHeight: 1.2,
|
||
letterSpacing: '-0.02em',
|
||
}}
|
||
>
|
||
{value}
|
||
</p>
|
||
{trend && (
|
||
<span
|
||
style={{
|
||
fontSize: 12,
|
||
fontWeight: 500,
|
||
color: trend.isPositive
|
||
? 'var(--md-sys-color-primary)'
|
||
: 'var(--md-sys-color-error)',
|
||
}}
|
||
>
|
||
{trend.isPositive ? '+' : '-'}
|
||
{Math.abs(trend.value)}%
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Panel>
|
||
);
|
||
};
|