257 lines
8.4 KiB
TypeScript
257 lines
8.4 KiB
TypeScript
/**
|
||
* Dashboard для родителя
|
||
*/
|
||
|
||
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { getParentDashboard, DashboardStats } from '@/api/dashboard';
|
||
import { StatCard } from './StatCard';
|
||
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
|
||
|
||
export const ParentDashboard: React.FC = () => {
|
||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
loadDashboard();
|
||
}, []);
|
||
|
||
const loadDashboard = async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const data = await getParentDashboard();
|
||
setStats(data);
|
||
} catch (err: any) {
|
||
console.error('Error loading dashboard:', err);
|
||
setError(err.message || 'Ошибка загрузки данных');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (error && !stats) {
|
||
return (
|
||
<div style={{
|
||
padding: '24px',
|
||
textAlign: 'center',
|
||
color: 'var(--md-sys-color-error)'
|
||
}}>
|
||
<p>{error}</p>
|
||
<button
|
||
onClick={loadDashboard}
|
||
style={{
|
||
marginTop: '16px',
|
||
padding: '12px 24px',
|
||
background: 'var(--md-sys-color-primary)',
|
||
color: 'var(--md-sys-color-on-primary)',
|
||
border: 'none',
|
||
borderRadius: '20px',
|
||
cursor: 'pointer',
|
||
fontSize: '14px',
|
||
fontWeight: '500'
|
||
}}
|
||
>
|
||
Попробовать снова
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{
|
||
width: '100%',
|
||
maxWidth: '100%',
|
||
padding: '16px',
|
||
background: 'var(--md-sys-color-background)',
|
||
}}>
|
||
{/* Приветствие */}
|
||
<div style={{
|
||
background: 'linear-gradient(135deg, var(--md-sys-color-primary) 0%, var(--md-sys-color-tertiary) 100%)',
|
||
borderRadius: '20px',
|
||
padding: '32px',
|
||
marginBottom: '24px',
|
||
color: 'var(--md-sys-color-on-primary)'
|
||
}}>
|
||
<h1 className="parent-dashboard-title" style={{
|
||
fontSize: '28px',
|
||
fontWeight: '500',
|
||
margin: '0 0 8px 0'
|
||
}}>
|
||
Добро пожаловать! 👨👩👧👦
|
||
</h1>
|
||
<p style={{
|
||
fontSize: '16px',
|
||
margin: 0,
|
||
opacity: 0.9
|
||
}}>
|
||
Отслеживайте прогресс ваших детей и их успехи в обучении
|
||
</p>
|
||
</div>
|
||
|
||
{/* Статистика по детям */}
|
||
{stats?.children_stats && stats.children_stats.length > 0 && (
|
||
<div className="parent-children-grid" style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: 'repeat(auto-fit, minmax(min(300px, 100%), 1fr))',
|
||
gap: '16px',
|
||
marginBottom: '24px'
|
||
}}>
|
||
{stats.children_stats.map((child) => (
|
||
<div
|
||
key={child.id}
|
||
style={{
|
||
background: 'var(--md-sys-color-surface)',
|
||
borderRadius: '20px',
|
||
padding: '24px',
|
||
border: '1px solid var(--md-sys-color-outline-variant)'
|
||
}}
|
||
>
|
||
<div style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '16px',
|
||
marginBottom: '20px'
|
||
}}>
|
||
{child.avatar_url ? (
|
||
<img
|
||
src={child.avatar_url}
|
||
alt={child.name}
|
||
style={{
|
||
width: '56px',
|
||
height: '56px',
|
||
borderRadius: '50%',
|
||
objectFit: 'cover'
|
||
}}
|
||
/>
|
||
) : (
|
||
<div style={{
|
||
width: '56px',
|
||
height: '56px',
|
||
borderRadius: '50%',
|
||
background: 'var(--md-sys-color-primary-container)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
color: 'var(--md-sys-color-on-primary-container)',
|
||
fontSize: '24px',
|
||
fontWeight: '500'
|
||
}}>
|
||
{child.name.charAt(0).toUpperCase()}
|
||
</div>
|
||
)}
|
||
<div>
|
||
<h3 style={{
|
||
fontSize: '18px',
|
||
fontWeight: '500',
|
||
color: 'var(--md-sys-color-on-surface)',
|
||
margin: '0 0 4px 0'
|
||
}}>
|
||
{child.name}
|
||
</h3>
|
||
<p style={{
|
||
fontSize: '14px',
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
margin: 0
|
||
}}>
|
||
{child.completed_lessons} / {child.total_lessons} занятий
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '12px'
|
||
}}>
|
||
<div>
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
marginBottom: '4px',
|
||
fontSize: '12px',
|
||
color: 'var(--md-sys-color-on-surface-variant)'
|
||
}}>
|
||
<span>Прогресс занятий</span>
|
||
<span>{Math.round((child.completed_lessons / child.total_lessons) * 100)}%</span>
|
||
</div>
|
||
<div style={{
|
||
width: '100%',
|
||
height: '6px',
|
||
background: 'var(--md-sys-color-surface-variant)',
|
||
borderRadius: '3px',
|
||
overflow: 'hidden'
|
||
}}>
|
||
<div style={{
|
||
height: '100%',
|
||
width: `${(child.completed_lessons / child.total_lessons) * 100}%`,
|
||
background: 'var(--md-sys-color-primary)',
|
||
transition: 'width 0.3s ease'
|
||
}}></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
fontSize: '14px'
|
||
}}>
|
||
<span style={{ color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
Средняя оценка:
|
||
</span>
|
||
<span style={{
|
||
fontWeight: '500',
|
||
color: 'var(--md-sys-color-on-surface)'
|
||
}}>
|
||
{child.average_grade != null ? String(parseFloat(Number(child.average_grade).toFixed(2))) : '—'}
|
||
</span>
|
||
</div>
|
||
|
||
<div style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
fontSize: '14px'
|
||
}}>
|
||
<span style={{ color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
Домашних заданий:
|
||
</span>
|
||
<span style={{
|
||
fontWeight: '500',
|
||
color: child.homework_pending > 0 ? 'var(--md-sys-color-error)' : 'var(--md-sys-color-tertiary)'
|
||
}}>
|
||
{child.homework_pending}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Общая статистика */}
|
||
<div style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
|
||
gap: '16px',
|
||
marginBottom: '24px'
|
||
}}>
|
||
<StatCard
|
||
title="Всего детей"
|
||
value={loading ? '—' : (stats?.children_count || 0)}
|
||
loading={loading}
|
||
icon={
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="9" cy="7" r="4"></circle>
|
||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
||
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
||
</svg>
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|