Обновлено получение данных в компонентах: AgentsBarChart, AgentsTable, BillingPayoutsTable, BillingPieChart, BillingStatChart, PayoutsTransactionsTable, ReferralsTable, RevenueChart и SalesTable. Теперь данные извлекаются из свойства items ответа API, что улучшает обработку данных и предотвращает возможные ошибки.

This commit is contained in:
Redsandyg 2025-06-07 14:15:13 +03:00
parent 410410bc85
commit 14921074a5
9 changed files with 100 additions and 22 deletions

View File

@ -47,7 +47,7 @@ const AgentsBarChart: React.FC = () => {
return res.json(); return res.json();
}) })
.then((data) => { .then((data) => {
setData(data); setData(data.items);
setLoading(false); setLoading(false);
}) })
.catch((err) => { .catch((err) => {

View File

@ -35,7 +35,7 @@ export default function AgentsTable({ filters, reloadKey }: { filters: { dateSta
} }
}) })
.then(res => res.json()) .then(res => res.json())
.then(setData) .then(apiData => setData(apiData.items))
.catch(() => setData([])); .catch(() => setData([]));
}, [filters.dateStart, filters.dateEnd, reloadKey]); }, [filters.dateStart, filters.dateEnd, reloadKey]);

View File

@ -1,9 +1,10 @@
import { useMemo } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { MaterialReactTable, MRT_ColumnDef, MRT_Row, useMaterialReactTable } from 'material-react-table'; import { MaterialReactTable, MRT_ColumnDef, MRT_Row, useMaterialReactTable } from 'material-react-table';
import mockData from '../data/mockData'; import mockData from '../data/mockData';
import { Box, Button } from '@mui/material'; import { Box, Button } from '@mui/material';
import FileDownloadIcon from '@mui/icons-material/FileDownload'; import FileDownloadIcon from '@mui/icons-material/FileDownload';
import { mkConfig, generateCsv, download } from 'export-to-csv'; import { mkConfig, generateCsv, download } from 'export-to-csv';
import Cookies from 'js-cookie';
function formatCurrency(amount: number) { function formatCurrency(amount: number) {
return amount?.toLocaleString('ru-RU', { return amount?.toLocaleString('ru-RU', {
@ -14,24 +15,67 @@ function formatCurrency(amount: number) {
} }
const statusColor: Record<string, string> = { const statusColor: Record<string, string> = {
'Завершена': '#4caf50', 'done': '#10B981', // green
'Ожидается': '#ff9800', 'waiting': '#F59E0B', // orange
'Ошибка': '#f44336', 'error': '#EF4444', // red
'process': '#3B82F6', // blue
'reject': '#800080', // purple
'new': '#E30B5C', // pink
}; };
const csvConfig = mkConfig({ const csvConfig = mkConfig({
fieldSeparator: ',', fieldSeparator: ',',
decimalSeparator: '.', decimalSeparator: '.', // This should be '.' for CSV regardless of locale for parsing.
useKeysAsHeaders: true, useKeysAsHeaders: true,
}); });
export default function BillingPayoutsTable() { export default function BillingPayoutsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const params = new URLSearchParams();
if (filters.dateStart) params.append('date_start', filters.dateStart);
if (filters.dateEnd) params.append('date_end', filters.dateEnd);
const token = Cookies.get("access_token");
if (!token) {
console.warn("Токен авторизации не найден.");
setError("Токен авторизации не найден.");
setLoading(false);
setData([]);
return;
}
fetch(`/api/billing/payouts/transactions?${params.toString()}`, {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
if (!res.ok) {
throw new Error(`Ошибка загрузки данных: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(apiData => {
setData(apiData.items);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
setData([]);
});
}, [filters.dateStart, filters.dateEnd, reloadKey]);
const columns = useMemo<MRT_ColumnDef<any>[]>( const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [ () => [
{ accessorKey: 'id', header: 'ID' }, { accessorKey: 'id', header: 'ID' },
{ accessorKey: 'amount', header: 'Сумма', { accessorKey: 'amount', header: 'Сумма',
Cell: ({ cell }) => formatCurrency(cell.getValue() as number) }, Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
{ accessorKey: 'date', header: 'Дата' }, { accessorKey: 'agent', header: 'Агент' }, // Добавлено поле для имени агента
{ accessorKey: 'status', header: 'Статус', { accessorKey: 'status', header: 'Статус',
Cell: ({ cell }) => ( Cell: ({ cell }) => (
<span style={{ color: statusColor[cell.getValue() as string] || '#333', fontWeight: 600 }}> <span style={{ color: statusColor[cell.getValue() as string] || '#333', fontWeight: 600 }}>
@ -39,14 +83,17 @@ export default function BillingPayoutsTable() {
</span> </span>
) )
}, },
{ accessorKey: 'method', header: 'Способ' }, { accessorKey: 'create_dttm', header: 'Дата создания',
Cell: ({ cell }) => new Date(cell.getValue() as string).toLocaleDateString("ru-RU") },
{ accessorKey: 'update_dttm', header: 'Дата обновления',
Cell: ({ cell }) => new Date(cell.getValue() as string).toLocaleDateString("ru-RU") },
], ],
[] []
); );
const table = useMaterialReactTable({ const table = useMaterialReactTable({
columns, columns,
data: mockData.payouts, data,
enableRowSelection: false, enableRowSelection: false,
renderTopToolbarCustomActions: ({ table }) => ( renderTopToolbarCustomActions: ({ table }) => (
<Box sx={{ display: 'flex', gap: 2, p: 1, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 2, p: 1, flexWrap: 'wrap' }}>
@ -64,6 +111,10 @@ export default function BillingPayoutsTable() {
muiTableBodyCellProps: { sx: { fontSize: 14 } }, muiTableBodyCellProps: { sx: { fontSize: 14 } },
muiTableHeadCellProps: { sx: { fontWeight: 700 } }, muiTableHeadCellProps: { sx: { fontWeight: 700 } },
initialState: { pagination: { pageSize: 10, pageIndex: 0 } }, initialState: { pagination: { pageSize: 10, pageIndex: 0 } },
state: { isLoading: loading, showAlertBanner: error !== null, showProgressBars: loading },
renderEmptyRowsFallback: () => (
<div>{error ? `Ошибка: ${error}` : (loading ? 'Загрузка данных...' : 'Данные не найдены.')}</div>
)
}); });
const handleExportRows = (rows: MRT_Row<any>[]) => { const handleExportRows = (rows: MRT_Row<any>[]) => {
@ -73,7 +124,7 @@ export default function BillingPayoutsTable() {
}; };
const handleExportData = () => { const handleExportData = () => {
const csv = generateCsv(csvConfig)(mockData.payouts); const csv = generateCsv(csvConfig)(data);
download(csvConfig)(csv); download(csvConfig)(csv);
}; };

View File

@ -29,7 +29,7 @@ const BillingPieChart: React.FC = () => {
}) })
.then((res) => res.json()) .then((res) => res.json())
.then((apiData) => { .then((apiData) => {
const mapped = apiData.map((item: { status: string; count: number }) => ({ const mapped = apiData.items.map((item: { status: string; count: number }) => ({
name: item.status, name: item.status,
value: item.count, value: item.count,
fill: STATUS_COLORS[item.status] || "#A3A3A3", fill: STATUS_COLORS[item.status] || "#A3A3A3",

View File

@ -36,7 +36,7 @@ const BillingStatChart: React.FC = () => {
// Собираем все уникальные даты и статусы // Собираем все уникальные даты и статусы
const allDates = new Set<string>(); const allDates = new Set<string>();
const allStatuses = new Set<string>(); const allStatuses = new Set<string>();
apiData.forEach((item: any) => { apiData.items.forEach((item: any) => {
allDates.add(item.date); allDates.add(item.date);
allStatuses.add(item.status); allStatuses.add(item.status);
}); });
@ -50,7 +50,7 @@ const BillingStatChart: React.FC = () => {
grouped[date][status] = 0; grouped[date][status] = 0;
}); });
}); });
apiData.forEach((item: any) => { apiData.items.forEach((item: any) => {
grouped[item.date][item.status] = item.count; grouped[item.date][item.status] = item.count;
}); });
const sorted = sortedDates.map(date => grouped[date]); const sorted = sortedDates.map(date => grouped[date]);

View File

@ -26,6 +26,9 @@ const statusColor: Record<string, string> = {
export default function PayoutsTransactionsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) { export default function PayoutsTransactionsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (filters.dateStart) params.append('date_start', filters.dateStart); if (filters.dateStart) params.append('date_start', filters.dateStart);
@ -34,20 +37,40 @@ export default function PayoutsTransactionsTable({ filters, reloadKey }: { filte
const token = Cookies.get("access_token"); const token = Cookies.get("access_token");
if (!token) { if (!token) {
console.warn("Токен авторизации не найден."); console.warn("Токен авторизации не найден.");
setData([]); // Очистить данные, если токен отсутствует setError("Токен авторизации не найден.");
setLoading(false);
setData([]);
return; return;
} }
setLoading(true);
setError(null);
fetch(`/api/billing/payouts/transactions?${params.toString()}`, { fetch(`/api/billing/payouts/transactions?${params.toString()}`, {
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}) })
.then(res => res.json()) .then(res => {
.then(setData) if (!res.ok) {
.catch(() => setData([])); throw new Error(`Ошибка загрузки данных: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(apiData => {
setData(apiData.items);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
setData([]);
});
}, [filters.dateStart, filters.dateEnd, reloadKey]); }, [filters.dateStart, filters.dateEnd, reloadKey]);
useEffect(() => {
}, [data]);
const columns = useMemo<MRT_ColumnDef<any>[]>( const columns = useMemo<MRT_ColumnDef<any>[]>(
() => [ () => [
{ accessorKey: 'id', header: 'ID' }, { accessorKey: 'id', header: 'ID' },
@ -95,6 +118,10 @@ export default function PayoutsTransactionsTable({ filters, reloadKey }: { filte
muiTableBodyCellProps: { sx: { fontSize: 14 } }, muiTableBodyCellProps: { sx: { fontSize: 14 } },
muiTableHeadCellProps: { sx: { fontWeight: 700 } }, muiTableHeadCellProps: { sx: { fontWeight: 700 } },
initialState: { pagination: { pageSize: 10, pageIndex: 0 } }, initialState: { pagination: { pageSize: 10, pageIndex: 0 } },
state: { isLoading: loading, showAlertBanner: error !== null, showProgressBars: loading },
renderEmptyRowsFallback: () => (
<div>{error ? `Ошибка: ${error}` : (loading ? 'Загрузка данных...' : 'Данные не найдены.')}</div>
)
}); });
const handleExportRows = (rows: MRT_Row<any>[]) => { const handleExportRows = (rows: MRT_Row<any>[]) => {

View File

@ -35,7 +35,7 @@ export default function ReferralsTable({ filters, reloadKey }: { filters: { date
} }
}) })
.then(res => res.json()) .then(res => res.json())
.then(setData) .then(apiData => setData(apiData.items))
.catch(() => setData([])); .catch(() => setData([]));
}, [filters.dateStart, filters.dateEnd, reloadKey]); }, [filters.dateStart, filters.dateEnd, reloadKey]);

View File

@ -41,7 +41,7 @@ const RevenueChart: React.FC = () => {
return res.json(); return res.json();
}) })
.then((data) => { .then((data) => {
setData(data); setData(data.items);
setLoading(false); setLoading(false);
}) })
.catch((err) => { .catch((err) => {

View File

@ -35,7 +35,7 @@ export default function SalesTable({ filters, reloadKey }: { filters: { dateStar
} }
}) })
.then(res => res.json()) .then(res => res.json())
.then(setData) .then(apiData => setData(apiData.items))
.catch(() => setData([])); .catch(() => setData([]));
}, [filters.dateStart, filters.dateEnd, reloadKey]); }, [filters.dateStart, filters.dateEnd, reloadKey]);