uchill/front_material/components/dashboard/ClientDashboard.tsx

253 lines
9.0 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.

/**
* Dashboard для студента (роль client) или для выбранного ребёнка (роль parent)
*/
'use client';
import React, { useState, useEffect } from 'react';
import { getClientDashboard, getChildDashboard, DashboardStats } from '@/api/dashboard';
import { StatCard } from './StatCard';
import { LessonCard } from './LessonCard';
import { HomeworkCard } from './HomeworkCard';
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
export interface ClientDashboardProps {
/** Для родителя: id выбранного ребёнка (user_id) — данные загружаются как для этого ребёнка */
childId?: string | null;
/** Для родителя: имя ребёнка для приветствия */
childName?: string | null;
}
export const ClientDashboard: React.FC<ClientDashboardProps> = ({ childId }) => {
const [stats, setStats] = useState<DashboardStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const isParentView = Boolean(childId);
useEffect(() => {
loadDashboard();
}, [childId]);
const loadDashboard = async () => {
try {
setLoading(true);
setError(null);
const data = isParentView && childId
? await getChildDashboard(childId)
: await getClientDashboard();
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',
minHeight: '100vh'
}}>
{/* Статистика студента */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
gap: '16px',
marginBottom: '24px'
}}>
<StatCard
title="Занятий всего"
value={loading ? '—' : (stats?.total_lessons || 0)}
loading={loading}
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
}
/>
<StatCard
title="Пройдено"
value={loading ? '—' : (stats?.completed_lessons || 0)}
loading={loading}
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
}
/>
<StatCard
title="ДЗ к выполнению"
value={loading ? '—' : (stats?.homework_pending || 0)}
loading={loading}
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
</svg>
}
/>
<StatCard
title="Средняя оценка"
value={loading ? '—' : (stats?.average_grade != null ? String(parseFloat(Number(stats.average_grade).toFixed(2))) : '-')}
loading={loading}
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
}
/>
</div>
{/* Следующее занятие */}
{stats?.next_lesson && (
<div style={{
background: 'var(--md-sys-color-surface)',
borderRadius: '20px',
padding: '24px',
border: '1px solid var(--md-sys-color-outline-variant)',
marginBottom: '24px',
borderLeft: '4px solid var(--md-sys-color-primary)'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '500',
color: 'var(--md-sys-color-on-surface)',
margin: '0 0 16px 0'
}}>
Ближайшее занятие
</h3>
<LessonCard lesson={stats.next_lesson} showMentor />
</div>
)}
{/* Домашние задания и расписание */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
gap: '16px',
marginBottom: '24px'
}}>
{/* Домашние задания */}
<div style={{
background: 'var(--md-sys-color-surface)',
borderRadius: '20px',
padding: '24px',
border: '1px solid var(--md-sys-color-outline-variant)'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '500',
color: 'var(--md-sys-color-on-surface)',
margin: '0 0 20px 0'
}}>
Ваши домашние задания
</h3>
{loading ? (
<LoadingSpinner size="medium" />
) : stats?.recent_homework && stats.recent_homework.length > 0 ? (
<div>
{stats.recent_homework.slice(0, 3).map((homework) => (
<HomeworkCard key={homework.id} homework={homework} />
))}
</div>
) : (
<div style={{
textAlign: 'center',
padding: '32px',
color: 'var(--md-sys-color-on-surface-variant)'
}}>
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ margin: '0 auto 16px', opacity: 0.3 }}>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<p style={{ margin: 0 }}>Нет домашних заданий</p>
</div>
)}
</div>
{/* Ближайшие занятия */}
<div style={{
background: 'var(--md-sys-color-surface)',
borderRadius: '20px',
padding: '24px',
border: '1px solid var(--md-sys-color-outline-variant)'
}}>
<h3 style={{
fontSize: '20px',
fontWeight: '500',
color: 'var(--md-sys-color-on-surface)',
margin: '0 0 20px 0'
}}>
Ваши занятия
</h3>
{loading ? (
<LoadingSpinner size="medium" />
) : stats?.upcoming_lessons && stats.upcoming_lessons.length > 0 ? (
<div>
{stats.upcoming_lessons.slice(0, 3).map((lesson) => (
<LessonCard key={lesson.id} lesson={lesson} showMentor />
))}
</div>
) : (
<div style={{
textAlign: 'center',
padding: '32px',
color: 'var(--md-sys-color-on-surface-variant)'
}}>
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ margin: '0 auto 16px', opacity: 0.3 }}>
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
<p style={{ margin: 0 }}>Нет запланированных занятий</p>
</div>
)}
</div>
</div>
</div>
);
};