diff --git a/src/components/AgentsBarChart.tsx b/src/components/AgentsBarChart.tsx index d53128d..bb47dd8 100644 --- a/src/components/AgentsBarChart.tsx +++ b/src/components/AgentsBarChart.tsx @@ -47,7 +47,7 @@ const AgentsBarChart: React.FC = () => { return res.json(); }) .then((data) => { - setData(data); + setData(data.items); setLoading(false); }) .catch((err) => { diff --git a/src/components/AgentsTable.tsx b/src/components/AgentsTable.tsx index bf0875f..0d4a06d 100644 --- a/src/components/AgentsTable.tsx +++ b/src/components/AgentsTable.tsx @@ -35,7 +35,7 @@ export default function AgentsTable({ filters, reloadKey }: { filters: { dateSta } }) .then(res => res.json()) - .then(setData) + .then(apiData => setData(apiData.items)) .catch(() => setData([])); }, [filters.dateStart, filters.dateEnd, reloadKey]); diff --git a/src/components/BillingPayoutsTable.tsx b/src/components/BillingPayoutsTable.tsx index 7431d4b..b0044fa 100644 --- a/src/components/BillingPayoutsTable.tsx +++ b/src/components/BillingPayoutsTable.tsx @@ -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 mockData from '../data/mockData'; import { Box, Button } from '@mui/material'; import FileDownloadIcon from '@mui/icons-material/FileDownload'; import { mkConfig, generateCsv, download } from 'export-to-csv'; +import Cookies from 'js-cookie'; function formatCurrency(amount: number) { return amount?.toLocaleString('ru-RU', { @@ -14,24 +15,67 @@ function formatCurrency(amount: number) { } const statusColor: Record = { - 'Завершена': '#4caf50', - 'Ожидается': '#ff9800', - 'Ошибка': '#f44336', + 'done': '#10B981', // green + 'waiting': '#F59E0B', // orange + 'error': '#EF4444', // red + 'process': '#3B82F6', // blue + 'reject': '#800080', // purple + 'new': '#E30B5C', // pink }; const csvConfig = mkConfig({ fieldSeparator: ',', - decimalSeparator: '.', + decimalSeparator: '.', // This should be '.' for CSV regardless of locale for parsing. useKeysAsHeaders: true, }); -export default function BillingPayoutsTable() { +export default function BillingPayoutsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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[]>( () => [ { accessorKey: 'id', header: 'ID' }, { accessorKey: 'amount', header: 'Сумма', Cell: ({ cell }) => formatCurrency(cell.getValue() as number) }, - { accessorKey: 'date', header: 'Дата' }, + { accessorKey: 'agent', header: 'Агент' }, // Добавлено поле для имени агента { accessorKey: 'status', header: 'Статус', Cell: ({ cell }) => ( @@ -39,14 +83,17 @@ export default function BillingPayoutsTable() { ) }, - { 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({ columns, - data: mockData.payouts, + data, enableRowSelection: false, renderTopToolbarCustomActions: ({ table }) => ( @@ -64,6 +111,10 @@ export default function BillingPayoutsTable() { muiTableBodyCellProps: { sx: { fontSize: 14 } }, muiTableHeadCellProps: { sx: { fontWeight: 700 } }, initialState: { pagination: { pageSize: 10, pageIndex: 0 } }, + state: { isLoading: loading, showAlertBanner: error !== null, showProgressBars: loading }, + renderEmptyRowsFallback: () => ( +
{error ? `Ошибка: ${error}` : (loading ? 'Загрузка данных...' : 'Данные не найдены.')}
+ ) }); const handleExportRows = (rows: MRT_Row[]) => { @@ -73,7 +124,7 @@ export default function BillingPayoutsTable() { }; const handleExportData = () => { - const csv = generateCsv(csvConfig)(mockData.payouts); + const csv = generateCsv(csvConfig)(data); download(csvConfig)(csv); }; diff --git a/src/components/BillingPieChart.tsx b/src/components/BillingPieChart.tsx index 942591c..f228376 100644 --- a/src/components/BillingPieChart.tsx +++ b/src/components/BillingPieChart.tsx @@ -29,7 +29,7 @@ const BillingPieChart: React.FC = () => { }) .then((res) => res.json()) .then((apiData) => { - const mapped = apiData.map((item: { status: string; count: number }) => ({ + const mapped = apiData.items.map((item: { status: string; count: number }) => ({ name: item.status, value: item.count, fill: STATUS_COLORS[item.status] || "#A3A3A3", diff --git a/src/components/BillingStatChart.tsx b/src/components/BillingStatChart.tsx index 188ec11..bcd217b 100644 --- a/src/components/BillingStatChart.tsx +++ b/src/components/BillingStatChart.tsx @@ -36,7 +36,7 @@ const BillingStatChart: React.FC = () => { // Собираем все уникальные даты и статусы const allDates = new Set(); const allStatuses = new Set(); - apiData.forEach((item: any) => { + apiData.items.forEach((item: any) => { allDates.add(item.date); allStatuses.add(item.status); }); @@ -50,7 +50,7 @@ const BillingStatChart: React.FC = () => { grouped[date][status] = 0; }); }); - apiData.forEach((item: any) => { + apiData.items.forEach((item: any) => { grouped[item.date][item.status] = item.count; }); const sorted = sortedDates.map(date => grouped[date]); diff --git a/src/components/PayoutsTransactionsTable.tsx b/src/components/PayoutsTransactionsTable.tsx index 8b14f06..1aa31d7 100644 --- a/src/components/PayoutsTransactionsTable.tsx +++ b/src/components/PayoutsTransactionsTable.tsx @@ -26,6 +26,9 @@ const statusColor: Record = { export default function PayoutsTransactionsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) { const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + useEffect(() => { const params = new URLSearchParams(); 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"); if (!token) { console.warn("Токен авторизации не найден."); - setData([]); // Очистить данные, если токен отсутствует + setError("Токен авторизации не найден."); + setLoading(false); + setData([]); return; } + setLoading(true); + setError(null); + fetch(`/api/billing/payouts/transactions?${params.toString()}`, { headers: { 'Authorization': `Bearer ${token}` } }) - .then(res => res.json()) - .then(setData) - .catch(() => setData([])); + .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]); + useEffect(() => { + }, [data]); + const columns = useMemo[]>( () => [ { accessorKey: 'id', header: 'ID' }, @@ -95,6 +118,10 @@ export default function PayoutsTransactionsTable({ filters, reloadKey }: { filte muiTableBodyCellProps: { sx: { fontSize: 14 } }, muiTableHeadCellProps: { sx: { fontWeight: 700 } }, initialState: { pagination: { pageSize: 10, pageIndex: 0 } }, + state: { isLoading: loading, showAlertBanner: error !== null, showProgressBars: loading }, + renderEmptyRowsFallback: () => ( +
{error ? `Ошибка: ${error}` : (loading ? 'Загрузка данных...' : 'Данные не найдены.')}
+ ) }); const handleExportRows = (rows: MRT_Row[]) => { diff --git a/src/components/ReferralsTable.tsx b/src/components/ReferralsTable.tsx index 1ba5e9d..c749f55 100644 --- a/src/components/ReferralsTable.tsx +++ b/src/components/ReferralsTable.tsx @@ -35,7 +35,7 @@ export default function ReferralsTable({ filters, reloadKey }: { filters: { date } }) .then(res => res.json()) - .then(setData) + .then(apiData => setData(apiData.items)) .catch(() => setData([])); }, [filters.dateStart, filters.dateEnd, reloadKey]); diff --git a/src/components/RevenueChart.tsx b/src/components/RevenueChart.tsx index e2930cf..3b75f4e 100644 --- a/src/components/RevenueChart.tsx +++ b/src/components/RevenueChart.tsx @@ -41,7 +41,7 @@ const RevenueChart: React.FC = () => { return res.json(); }) .then((data) => { - setData(data); + setData(data.items); setLoading(false); }) .catch((err) => { diff --git a/src/components/SalesTable.tsx b/src/components/SalesTable.tsx index 4d71306..b66b099 100644 --- a/src/components/SalesTable.tsx +++ b/src/components/SalesTable.tsx @@ -35,7 +35,7 @@ export default function SalesTable({ filters, reloadKey }: { filters: { dateStar } }) .then(res => res.json()) - .then(setData) + .then(apiData => setData(apiData.items)) .catch(() => setData([])); }, [filters.dateStart, filters.dateEnd, reloadKey]);