106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
/**
|
|
* Dashboard для ментора (iOS 26).
|
|
* Без календаря. Переиспользуемые секции в dashboard/mentor.
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { getMentorDashboard, getMentorIncome } from '@/api/dashboard';
|
|
import type { MentorDashboardResponse, MentorIncomeResponse } from '@/api/dashboard';
|
|
import type { IncomePeriod } from './mentor';
|
|
import { DashboardLayout } from './ui';
|
|
import {
|
|
StatsSection,
|
|
IncomeSection,
|
|
ExtraStatsSection,
|
|
UpcomingLessonsSection,
|
|
RecentSubmissionsSection,
|
|
} from './mentor';
|
|
import { ErrorDisplay } from '@/components/common/ErrorDisplay';
|
|
|
|
const isAbortError = (e: unknown): boolean =>
|
|
(e as any)?.name === 'AbortError' ||
|
|
(e as any)?.name === 'CanceledError' ||
|
|
(e as any)?.code === 'ERR_CANCELED';
|
|
|
|
export const MentorDashboard: React.FC = () => {
|
|
const [stats, setStats] = useState<MentorDashboardResponse | null>(null);
|
|
const [incomeStats, setIncomeStats] = useState<MentorIncomeResponse | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [incomeLoading, setIncomeLoading] = useState(false);
|
|
const [incomePeriod, setIncomePeriod] = useState<IncomePeriod>('week');
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const loadDashboard = useCallback(async (signal?: AbortSignal) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const data = await getMentorDashboard(signal ? { signal } : undefined);
|
|
if (signal?.aborted) return;
|
|
setStats(data);
|
|
} catch (err: unknown) {
|
|
if (isAbortError(err)) return;
|
|
setError(err instanceof Error ? err.message : 'Ошибка загрузки данных');
|
|
} finally {
|
|
if (!signal?.aborted) setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const loadIncome = useCallback(
|
|
async (signal?: AbortSignal) => {
|
|
try {
|
|
setIncomeLoading(true);
|
|
const data = await getMentorIncome(
|
|
incomePeriod,
|
|
undefined,
|
|
undefined,
|
|
signal ? { signal } : undefined
|
|
);
|
|
if (signal?.aborted) return;
|
|
setIncomeStats(data);
|
|
} catch (err: unknown) {
|
|
if (isAbortError(err)) return;
|
|
// игнор прочих
|
|
} finally {
|
|
if (!signal?.aborted) setIncomeLoading(false);
|
|
}
|
|
},
|
|
[incomePeriod]
|
|
);
|
|
|
|
useEffect(() => {
|
|
const c = new AbortController();
|
|
loadDashboard(c.signal);
|
|
return () => c.abort();
|
|
}, [loadDashboard]);
|
|
|
|
useEffect(() => {
|
|
const c = new AbortController();
|
|
loadIncome(c.signal);
|
|
return () => c.abort();
|
|
}, [loadIncome]);
|
|
|
|
// if (error && !stats) {
|
|
// return (
|
|
// <DashboardLayout>
|
|
// <ErrorDisplay error={error} onRetry={loadDashboard} />
|
|
// </DashboardLayout>
|
|
// );
|
|
// }
|
|
|
|
return (
|
|
<DashboardLayout className="ios26-dashboard-grid">
|
|
<IncomeSection
|
|
data={incomeStats}
|
|
period={incomePeriod}
|
|
onPeriodChange={setIncomePeriod}
|
|
loading={incomeLoading}
|
|
/>
|
|
<ExtraStatsSection stats={stats} loading={loading} />
|
|
<UpcomingLessonsSection data={stats} loading={loading} />
|
|
<RecentSubmissionsSection data={stats} loading={loading} />
|
|
</DashboardLayout>
|
|
);
|
|
};
|