245 lines
8.4 KiB
JavaScript
245 lines
8.4 KiB
JavaScript
'use client';
|
|
|
|
import { useRouter } from 'src/routes/hooks';
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
import Box from '@mui/material/Box';
|
|
import Card from '@mui/material/Card';
|
|
import Grid from '@mui/material/Grid';
|
|
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 TextField from '@mui/material/TextField';
|
|
import Typography from '@mui/material/Typography';
|
|
import CardContent from '@mui/material/CardContent';
|
|
import CardActions from '@mui/material/CardActions';
|
|
import DialogTitle from '@mui/material/DialogTitle';
|
|
import DialogContent from '@mui/material/DialogContent';
|
|
import DialogActions from '@mui/material/DialogActions';
|
|
import CircularProgress from '@mui/material/CircularProgress';
|
|
import InputAdornment from '@mui/material/InputAdornment';
|
|
|
|
import { paths } from 'src/routes/paths';
|
|
|
|
import axios from 'src/utils/axios';
|
|
|
|
import { DashboardContent } from 'src/layouts/dashboard';
|
|
|
|
import { Iconify } from 'src/components/iconify';
|
|
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
async function getChildren() {
|
|
const res = await axios.get('/parent/dashboard/');
|
|
const raw = res.data?.children ?? [];
|
|
return raw.map((item) => {
|
|
const c = item.child ?? item;
|
|
const [first_name = '', ...rest] = (c.name || '').split(' ');
|
|
return { id: c.id, first_name, last_name: rest.join(' '), email: c.email };
|
|
});
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
function AddChildDialog({ open, onClose, onSuccess }) {
|
|
const [code, setCode] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
const handleClose = () => {
|
|
setCode('');
|
|
setError('');
|
|
onClose();
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
const trimmed = code.trim().toUpperCase();
|
|
if (!trimmed) { setError('Введите код'); return; }
|
|
if (trimmed.length !== 8) { setError('Код должен содержать ровно 8 символов'); return; }
|
|
|
|
try {
|
|
setLoading(true);
|
|
setError('');
|
|
await axios.post('/manage/parents/add_child/', { universal_code: trimmed });
|
|
handleClose();
|
|
onSuccess();
|
|
} catch (e) {
|
|
const msg = e?.response?.data?.error
|
|
|| e?.response?.data?.detail
|
|
|| e?.message
|
|
|| 'Ошибка при добавлении';
|
|
setError(msg);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
|
<DialogTitle>Добавить ребёнка по коду</DialogTitle>
|
|
<DialogContent>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
Введите 8-значный код из профиля ребёнка. Код содержит буквы и цифры.
|
|
</Typography>
|
|
<TextField
|
|
autoFocus
|
|
fullWidth
|
|
label="Код ребёнка"
|
|
placeholder="Например: 8XW4EIVL"
|
|
value={code}
|
|
onChange={(e) => {
|
|
setCode(e.target.value.toUpperCase());
|
|
setError('');
|
|
}}
|
|
onKeyDown={(e) => e.key === 'Enter' && handleSubmit()}
|
|
inputProps={{ maxLength: 8, style: { letterSpacing: 4, fontWeight: 600, fontSize: 18 } }}
|
|
InputProps={{
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Iconify icon="solar:key-bold" width={20} />
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
error={!!error}
|
|
helperText={error || ' '}
|
|
/>
|
|
</DialogContent>
|
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
|
<Button onClick={handleClose} color="inherit" disabled={loading}>
|
|
Отмена
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSubmit}
|
|
disabled={loading || code.trim().length !== 8}
|
|
startIcon={loading ? <CircularProgress size={16} /> : <Iconify icon="solar:user-plus-bold" width={18} />}
|
|
>
|
|
Добавить
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
export function ChildrenView() {
|
|
const router = useRouter();
|
|
const [children, setChildren] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [addOpen, setAddOpen] = useState(false);
|
|
|
|
const load = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const list = await getChildren();
|
|
setChildren(list);
|
|
} catch (e) {
|
|
setError(e?.response?.data?.detail || e?.message || 'Ошибка загрузки');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
return (
|
|
<DashboardContent>
|
|
<CustomBreadcrumbs
|
|
heading="Мои дети"
|
|
links={[{ name: 'Главная', href: paths.dashboard.root }, { name: 'Мои дети' }]}
|
|
action={
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<Iconify icon="solar:user-plus-bold" width={18} />}
|
|
onClick={() => setAddOpen(true)}
|
|
>
|
|
Добавить ребёнка
|
|
</Button>
|
|
}
|
|
sx={{ mb: 3 }}
|
|
/>
|
|
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 3 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{loading ? (
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
) : children.length === 0 ? (
|
|
<Box sx={{ textAlign: 'center', py: 8 }}>
|
|
<Iconify icon="eva:people-outline" width={64} color="text.disabled" />
|
|
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
|
|
Нет привязанных детей
|
|
</Typography>
|
|
<Button
|
|
variant="outlined"
|
|
sx={{ mt: 2 }}
|
|
startIcon={<Iconify icon="solar:user-plus-bold" width={18} />}
|
|
onClick={() => setAddOpen(true)}
|
|
>
|
|
Добавить ребёнка по коду
|
|
</Button>
|
|
</Box>
|
|
) : (
|
|
<Grid container spacing={2}>
|
|
{children.map((child) => {
|
|
const name = `${child.first_name || ''} ${child.last_name || ''}`.trim() || child.email;
|
|
return (
|
|
<Grid key={child.id} item xs={12} sm={6} md={4}>
|
|
<Card variant="outlined">
|
|
<CardContent>
|
|
<Stack direction="row" spacing={2} alignItems="center">
|
|
<Avatar sx={{ width: 48, height: 48, bgcolor: 'primary.lighter', color: 'primary.main' }}>
|
|
{name[0]?.toUpperCase()}
|
|
</Avatar>
|
|
<Box>
|
|
<Typography variant="subtitle1">{name}</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{child.email}
|
|
</Typography>
|
|
</Box>
|
|
</Stack>
|
|
</CardContent>
|
|
<CardActions sx={{ px: 2, pb: 2 }}>
|
|
<Button
|
|
variant="contained"
|
|
fullWidth
|
|
startIcon={<Iconify icon="solar:chart-square-bold" />}
|
|
onClick={() => {
|
|
localStorage.setItem('selected_child', JSON.stringify({ id: child.id, name }));
|
|
window.dispatchEvent(new Event('child-changed'));
|
|
router.push(paths.dashboard.root);
|
|
}}
|
|
>
|
|
Дашборд
|
|
</Button>
|
|
</CardActions>
|
|
</Card>
|
|
</Grid>
|
|
);
|
|
})}
|
|
</Grid>
|
|
)}
|
|
|
|
<AddChildDialog
|
|
open={addOpen}
|
|
onClose={() => setAddOpen(false)}
|
|
onSuccess={() => {
|
|
load();
|
|
window.dispatchEvent(new Event('child-changed'));
|
|
}}
|
|
/>
|
|
</DashboardContent>
|
|
);
|
|
}
|