Files
diploma-client/src/modules/admin/components/leads/dashboard-leads.tsx
Evgenii Saenko 4f6700e0e2 Add docker
2025-12-17 11:51:25 +03:00

161 lines
5.1 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.
import React, { useEffect, useMemo, useState } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Pagination from '@mui/material/Pagination';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import { leadsApi, type LeadListItem } from '../../../../api/index.ts';
import { ApiError } from '../../../../api/httpClient.ts';
const pageSize = 10;
const formatDateTime = (value: string) => {
const timestamp = new Date(value);
if (Number.isNaN(timestamp.getTime())) {
return '—';
}
return new Intl.DateTimeFormat('ru-RU', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(timestamp);
};
export const AdminDashboardLeads: React.FC = () => {
const [items, setItems] = useState<LeadListItem[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [limit, setLimit] = useState(pageSize);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
const load = async () => {
setIsLoading(true);
setError(null);
try {
const response = await leadsApi.list({
limit: pageSize,
page,
});
if (!isMounted) {
return;
}
setItems(response.items);
setTotal(response.total);
setLimit(response.limit || pageSize);
} catch (err) {
if (!isMounted) {
return;
}
const message = err instanceof ApiError ? err.message : 'Не удалось загрузить лиды. Попробуйте позже.';
setError(message);
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
void load();
return () => {
isMounted = false;
};
}, [page]);
const totalPages = useMemo(() => {
if (total > 0 && limit > 0) {
return Math.max(Math.ceil(total / limit), 1);
}
return items.length > 0 ? page : 0;
}, [items.length, limit, page, total]);
useEffect(() => {
if (!isLoading && totalPages > 0 && page > totalPages) {
setPage(totalPages);
}
}, [isLoading, page, totalPages]);
return (
<Stack spacing={3}>
<div>
<Typography variant="h5">Лиды и заявки</Typography>
<Typography color="text.secondary">Просматривайте обращения клиентов и назначайте ответственных.</Typography>
</div>
{isLoading && (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress size={28} />
</Box>
)}
{error && <Alert severity="error">{error}</Alert>}
{!isLoading && !error && items.length === 0 && (
<Typography color="text.secondary" textAlign="center">
Пока нет новых заявок. Как только клиенты оставят контакты, они появятся здесь.
</Typography>
)}
{!isLoading && !error && items.length > 0 && (
<Table
size="small"
sx={{
border: 1,
borderColor: 'divider',
borderRadius: 1,
overflow: 'hidden',
'& th, & td': {
borderRight: '1px solid',
borderColor: 'divider',
'&:last-of-type': {
borderRight: 'none',
},
},
}}
>
<TableHead>
<TableRow sx={{ backgroundColor: 'action.hover' }}>
<TableCell sx={{ fontWeight: 600 }}>Дата и время</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Имя</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Email</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Телефон</TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => (
<TableRow key={item.id} hover sx={{ '& td': { borderBottom: '1px solid', borderColor: 'divider' } }}>
<TableCell>{formatDateTime(item.createdAt ?? '')}</TableCell>
<TableCell>
<Typography fontWeight={600}>{item.fullName}</Typography>
</TableCell>
<TableCell>{item.email}</TableCell>
<TableCell>{item.phone ?? '—'}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{!isLoading && !error && totalPages > 1 && (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Pagination page={page} onChange={(_, value) => setPage(value)} count={totalPages} color="primary" showFirstButton showLastButton />
</Box>
)}
</Stack>
);
};