'use client';
import { useState, useEffect, useCallback } from 'react';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Chip from '@mui/material/Chip';
import Grid from '@mui/material/Grid';
import Tabs from '@mui/material/Tabs';
import List from '@mui/material/List';
import Stack from '@mui/material/Stack';
import Alert from '@mui/material/Alert';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import Divider from '@mui/material/Divider';
import Tooltip from '@mui/material/Tooltip';
import ListItem from '@mui/material/ListItem';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import CardHeader from '@mui/material/CardHeader';
import IconButton from '@mui/material/IconButton';
import DialogTitle from '@mui/material/DialogTitle';
import CardContent from '@mui/material/CardContent';
import ListItemText from '@mui/material/ListItemText';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import InputAdornment from '@mui/material/InputAdornment';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import CircularProgress from '@mui/material/CircularProgress';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { resolveMediaUrl } from 'src/utils/axios';
import {
getStudents,
getMyMentors,
getMyRequests,
getMyInvitations,
addStudentInvitation,
generateInvitationLink,
sendMentorshipRequest,
acceptMentorshipRequest,
rejectMentorshipRequest,
rejectInvitationAsStudent,
confirmInvitationAsStudent,
getMentorshipRequestsPending,
removeStudent,
removeMentor,
} from 'src/utils/students-api';
import { DashboardContent } from 'src/layouts/dashboard';
import { Iconify } from 'src/components/iconify';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { useAuthContext } from 'src/auth/hooks';
// ----------------------------------------------------------------------
const avatarUrl = (href) => resolveMediaUrl(href) || null;
function initials(firstName, lastName) {
return `${(firstName || '')[0] || ''}${(lastName || '')[0] || ''}`.toUpperCase();
}
// ----------------------------------------------------------------------
function RemoveConnectionDialog({ open, onClose, onConfirm, name, type, loading }) {
return (
);
}
// ----------------------------------------------------------------------
// MENTOR VIEWS
function MentorStudentList({ onRefresh }) {
const router = useRouter();
const [students, setStudents] = useState([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
const [error, setError] = useState(null);
const [removeTarget, setRemoveTarget] = useState(null); // { id, name }
const [removing, setRemoving] = useState(false);
const handleRemove = async () => {
try {
setRemoving(true);
await removeStudent(removeTarget.id);
setRemoveTarget(null);
load();
onRefresh?.();
} catch (e) {
setError(e?.response?.data?.error?.message || e?.message || 'Ошибка удаления');
setRemoveTarget(null);
} finally {
setRemoving(false);
}
};
const load = useCallback(async () => {
try {
setLoading(true);
const res = await getStudents({ page_size: 200 });
setStudents(res.results);
} catch (e) {
setError(e?.response?.data?.detail || e?.message || 'Ошибка загрузки');
} finally {
setLoading(false);
}
}, []);
useEffect(() => { load(); }, [load]);
const filtered = students.filter((s) => {
if (!search.trim()) return true;
const q = search.toLowerCase();
const u = s.user || {};
return (
(u.first_name || '').toLowerCase().includes(q) ||
(u.last_name || '').toLowerCase().includes(q) ||
(u.email || '').toLowerCase().includes(q)
);
});
if (loading) return ;
return (
setSearch(e.target.value)}
size="small"
InputProps={{
startAdornment: ,
}}
/>
{error && {error}}
{filtered.length === 0 ? (
{search ? 'Ничего не найдено' : 'Нет учеников'}
) : (
{filtered.map((s) => {
const u = s.user || {};
const name = `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || '—';
return (
t.customShadows?.z16 || '0 8px 24px rgba(0,0,0,0.12)',
},
}}
>
{ e.stopPropagation(); setRemoveTarget({ id: s.id, name }); }}
sx={{ position: 'absolute', top: 8, right: 8 }}
>
router.push(paths.dashboard.studentDetail(s.id))} sx={{ cursor: 'pointer' }}>
{initials(u.first_name, u.last_name)}
{name}
{u.email || ''}
{s.total_lessons != null && (
} />
)}
{s.subject && }
);
})}
)}
setRemoveTarget(null)}
onConfirm={handleRemove}
name={removeTarget?.name || ''}
type="student"
loading={removing}
/>
);
}
function MentorRequests({ onRefresh }) {
const [requests, setRequests] = useState([]);
const [loading, setLoading] = useState(true);
const [processing, setProcessing] = useState(null);
const [error, setError] = useState(null);
const load = useCallback(async () => {
try {
setLoading(true);
const res = await getMentorshipRequestsPending();
setRequests(Array.isArray(res) ? res : []);
} catch (e) {
setError(e?.response?.data?.detail || e?.message || 'Ошибка загрузки');
} finally {
setLoading(false);
}
}, []);
useEffect(() => { load(); }, [load]);
const handle = async (id, action) => {
try {
setProcessing(id);
if (action === 'accept') await acceptMentorshipRequest(id);
else await rejectMentorshipRequest(id);
await load();
onRefresh();
} catch (e) {
setError(e?.response?.data?.detail || e?.message || 'Ошибка');
} finally {
setProcessing(null);
}
};
if (loading) return ;
return (
{error && {error}}
{requests.length === 0 ? (
Нет входящих заявок
) : (
{requests.map((r) => {
const s = r.student || {};
return (
}
>
{initials(s.first_name, s.last_name)}
);
})}
)}
);
}
// 8 отдельных полей для ввода кода
function CodeInput({ value, onChange, disabled }) {
const handleChange = (e) => {
const v = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 8);
onChange(v);
};
return (
);
}
function InviteDialog({ open, onClose, onSuccess }) {
const [mode, setMode] = useState('email'); // 'email' | 'code' | 'link'
const [emailValue, setEmailValue] = useState('');
const [codeValue, setCodeValue] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [successMsg, setSuccessMsg] = useState(null);
const [linkData, setLinkData] = useState(null); // { invitation_link, expires_at }
const [linkLoading, setLinkLoading] = useState(false);
const [copied, setCopied] = useState(false);
const reset = () => {
setEmailValue(''); setCodeValue(''); setError(null); setSuccessMsg(null);
setLinkData(null); setCopied(false);
};
const handleClose = () => { if (!loading && !linkLoading) { reset(); onClose(); } };
const handleSend = async () => {
const val = mode === 'email' ? emailValue.trim() : codeValue.trim();
if (!val) return;
try {
setLoading(true);
setError(null);
const payload = mode === 'email' ? { email: val } : { universal_code: val };
const res = await addStudentInvitation(payload);
setSuccessMsg(res?.message || 'Приглашение отправлено');
onSuccess();
} catch (e) {
const msg = e?.response?.data?.error?.message
|| e?.response?.data?.detail
|| e?.response?.data?.email?.[0]
|| e?.message || 'Ошибка';
setError(msg);
} finally {
setLoading(false);
}
};
const handleGenerateLink = async () => {
try {
setLinkLoading(true);
setError(null);
const res = await generateInvitationLink();
setLinkData(res);
setCopied(false);
} catch (e) {
setError(e?.response?.data?.error?.message || e?.message || 'Ошибка генерации ссылки');
} finally {
setLinkLoading(false);
}
};
const handleCopyLink = () => {
navigator.clipboard.writeText(linkData.invitation_link);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const expiresText = linkData?.expires_at
? `Действует до ${new Date(linkData.expires_at).toLocaleString('ru-RU', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: '2-digit' })}`
: '';
const canSend = mode === 'email' ? !!emailValue.trim() : codeValue.length === 8;
return (
);
}
// ----------------------------------------------------------------------
// CLIENT VIEWS
function ClientMentorList() {
const [mentors, setMentors] = useState([]);
const [loading, setLoading] = useState(true);
const [removeTarget, setRemoveTarget] = useState(null);
const [removing, setRemoving] = useState(false);
const load = useCallback(() => {
setLoading(true);
getMyMentors()
.then((list) => setMentors(Array.isArray(list) ? list : []))
.catch(() => setMentors([]))
.finally(() => setLoading(false));
}, []);
useEffect(() => { load(); }, [load]);
const handleRemove = async () => {
try {
setRemoving(true);
await removeMentor(removeTarget.id);
setRemoveTarget(null);
load();
} catch (e) {
setRemoveTarget(null);
} finally {
setRemoving(false);
}
};
if (loading) return ;
if (mentors.length === 0) {
return (
Нет подключённых менторов
);
}
return (
<>
{mentors.map((m) => {
const name = `${m.first_name || ''} ${m.last_name || ''}`.trim() || m.email || '—';
return (
t.customShadows?.z16 || '0 8px 24px rgba(0,0,0,0.12)',
},
}}
>
setRemoveTarget({ id: m.id, name })}
sx={{ position: 'absolute', top: 8, right: 8 }}
>
{initials(m.first_name, m.last_name)}
{name}
{m.email || ''}
);
})}
setRemoveTarget(null)}
onConfirm={handleRemove}
name={removeTarget?.name || ''}
type="mentor"
loading={removing}
/>
>
);
}
function ClientInvitations({ onRefresh }) {
const [invitations, setInvitations] = useState([]);
const [loading, setLoading] = useState(true);
const [processing, setProcessing] = useState(null);
const [error, setError] = useState(null);
const load = useCallback(async () => {
try {
setLoading(true);
const res = await getMyInvitations();
setInvitations(Array.isArray(res) ? res.filter((i) => ['pending', 'pending_student', 'pending_parent'].includes(i.status)) : []);
} catch {
setInvitations([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => { load(); }, [load]);
const handle = async (id, action) => {
try {
setProcessing(id);
if (action === 'confirm') await confirmInvitationAsStudent(id);
else await rejectInvitationAsStudent(id);
await load();
onRefresh();
} catch (e) {
setError(e?.response?.data?.detail || e?.message || 'Ошибка');
} finally {
setProcessing(null);
}
};
if (loading) return ;
if (invitations.length === 0) return (
Нет входящих приглашений
);
return (
{error && {error}}
{invitations.map((inv) => {
const m = inv.mentor || {};
return (
}
>
{initials(m.first_name, m.last_name)}
);
})}
);
}
function SendRequestDialog({ open, onClose, onSuccess }) {
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [successMsg, setSuccessMsg] = useState(null);
const handleSend = async () => {
if (!code.trim()) return;
try {
setLoading(true);
setError(null);
const res = await sendMentorshipRequest(code.trim());
setSuccessMsg(res?.message || 'Заявка отправлена');
onSuccess();
} catch (e) {
setError(e?.response?.data?.detail || e?.response?.data?.mentor_code?.[0] || e?.message || 'Ошибка');
} finally {
setLoading(false);
}
};
return (
);
}
const STATUS_LABELS = {
pending_mentor: { label: 'Ожидает ментора', color: 'warning' },
pending_student: { label: 'Ожидает вас', color: 'info' },
accepted: { label: 'Принято', color: 'success' },
rejected: { label: 'Отклонено', color: 'error' },
removed: { label: 'Удалено', color: 'default' },
};
function ClientOutgoingRequests() {
const [requests, setRequests] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getMyRequests()
.then((data) => setRequests(Array.isArray(data) ? data : []))
.catch(() => setRequests([]))
.finally(() => setLoading(false));
}, []);
if (loading) return ;
if (requests.length === 0) return (
Нет исходящих заявок
);
return (
{requests.map((req) => {
const m = req.mentor || {};
const name = `${m.first_name || ''} ${m.last_name || ''}`.trim() || m.email || '—';
const statusInfo = STATUS_LABELS[req.status] || { label: req.status, color: 'default' };
return (
{initials(m.first_name, m.last_name)}
);
})}
);
}
// ----------------------------------------------------------------------
export function StudentsView() {
const { user } = useAuthContext();
const isMentor = user?.role === 'mentor';
const [tab, setTab] = useState(0);
const [inviteOpen, setInviteOpen] = useState(false);
const [requestOpen, setRequestOpen] = useState(false);
const [refreshKey, setRefreshKey] = useState(0);
const refresh = () => setRefreshKey((k) => k + 1);
return (
} onClick={() => setInviteOpen(true)}>
Пригласить ученика
) : (
} onClick={() => setRequestOpen(true)}>
Найти ментора
)
}
sx={{ mb: 3 }}
/>
{isMentor ? (
<>
setTab(v)} sx={{ mb: 3 }}>
{tab === 0 && }
{tab === 1 && }
setInviteOpen(false)} onSuccess={refresh} />
>
) : (
<>
setTab(v)} sx={{ mb: 3 }}>
{tab === 0 && }
{tab === 1 && }
{tab === 2 && }
setRequestOpen(false)} onSuccess={refresh} />
>
)}
);
}