From 6158f8f3717f286f802c68de9c3e1a88b1ea8cc8 Mon Sep 17 00:00:00 2001 From: ahajdarevi5 Date: Sat, 24 May 2025 23:18:07 +0200 Subject: [PATCH] analitika za prodavnice --- src/api/api.js | 33 ++++++++++ src/components/StoreEarningsTable.jsx | 88 +++++++++++++++++++++++++++ src/pages/AnalyticsPage.jsx | 52 ++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 src/components/StoreEarningsTable.jsx diff --git a/src/api/api.js b/src/api/api.js index a3d1b2c..f871023 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1670,3 +1670,36 @@ export const apiGetAllRoutesAsync = async () => { apiSetAuthHeader(); return axios.get(`${baseApiUrl}/api/Delivery/routes`); }; + + +export const apiGetStoreIncomeAsync = async (storeId, from, to) => { + const fromDate = from.toISOString(); + const toDate = to.toISOString(); + const response = await axios.get( + `${baseApiUrl}/api/Admin/store/${storeId}/income`, + { params: { from: fromDate, to: toDate } } + ); + return response.data; +}; + + + +export const apiGetAdminProfitAsync = async (from, to, storeIds) => { + const response = await axios.get(`${baseApiUrl}/api/loyalty/admin/profit`, { + params: { + from: from?.toISOString(), + to: to?.toISOString(), + storeIds, + }, + paramsSerializer: (params) => { + const query = new URLSearchParams(); + if (params.from) query.append('from', params.from); + if (params.to) query.append('to', params.to); + if (params.storeIds) { + params.storeIds.forEach((id) => query.append('storeIds', id)); + } + return query.toString(); + }, + }); + return response.data; +}; \ No newline at end of file diff --git a/src/components/StoreEarningsTable.jsx b/src/components/StoreEarningsTable.jsx new file mode 100644 index 0000000..020fd58 --- /dev/null +++ b/src/components/StoreEarningsTable.jsx @@ -0,0 +1,88 @@ +import React, { useState, useMemo } from 'react'; +import { + Table, TableHead, TableRow, TableCell, TableBody, + TablePagination, TableSortLabel, Paper, Box +} from '@mui/material'; + +const StoreEarningsTable = ({ data }) => { + const [page, setPage] = useState(0); + const rowsPerPage = 5; + const [orderBy, setOrderBy] = useState('storeRevenue'); + const [order, setOrder] = useState('desc'); + + const handleSort = (property) => { + const isAsc = orderBy === property && order === 'asc'; + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(property); + }; + + const sortedData = useMemo(() => { + return [...data].sort((a, b) => + (order === 'asc' ? a[orderBy] - b[orderBy] : b[orderBy] - a[orderBy]) + ); + }, [data, order, orderBy]); + + const paginatedData = sortedData.slice(page * rowsPerPage, (page + 1) * rowsPerPage); + + return ( + + + + + + Store Name + + handleSort('storeRevenue')} + > + Store Revenue + + + + handleSort('adminProfit')} + > + Admin Profit + + + + handleSort('taxRate')} + > + Tax Rate (%) + + + + + + {paginatedData.map((row) => ( + + {row.name} + {(row.storeRevenue ?? 0).toFixed(2)} $ + {(row.adminProfit ?? 0).toFixed(2)} $ + {(row.taxRate ?? 0).toFixed(2)} % + + + ))} + +
+
+ setPage(newPage)} + /> +
+ ); +}; + +export default StoreEarningsTable; diff --git a/src/pages/AnalyticsPage.jsx b/src/pages/AnalyticsPage.jsx index 4e92b1f..e72978b 100644 --- a/src/pages/AnalyticsPage.jsx +++ b/src/pages/AnalyticsPage.jsx @@ -16,6 +16,7 @@ import Calendar from '@components/Calendar'; // From develop import DealsChart from '@components/DealsChart'; // From develop import SalesChart from '@components/SalesChart'; // From develop import { useState, useEffect, useRef } from 'react'; // useRef from develop +import StoreEarningsTable from '@components/StoreEarningsTable'; import { apiGetAllAdsAsync, @@ -27,6 +28,8 @@ import { apiFetchAdClicksAsync, apiFetchAdViewsAsync, apiFetchAdConversionsAsync, // From HEAD, for ProductsSummary + apiGetStoreIncomeAsync, + apiGetAdminProfitAsync } from '../api/api.js'; // format and parseISO were in develop but not used in the conflicting part, subMonths is used by both import { subMonths, format, parseISO } from 'date-fns'; // Added format, parseISO from develop imports @@ -40,6 +43,7 @@ const HUB_URL = `${baseUrl}${HUB_ENDPOINT_PATH}`; const AnalyticsPage = () => { // --- State from develop --- + const [totalAdminProfit, setTotalAdminProfit] = useState(0); const [ads, setAds] = useState([]); // For general ad data, updated by SignalR const [kpi, setKpi] = useState({ totalViews: 0, @@ -85,6 +89,9 @@ const AnalyticsPage = () => { const [storeSpecificConversionData, setStoreSpecificConversionData] = useState([]); + const [storeStats, setStoreStats] = useState([]); + + // --- Pagination Logic (from HEAD) --- const handlePageChange = (event, value) => { setCurrentProductPage(value); @@ -321,6 +328,44 @@ const AnalyticsPage = () => { calculatedProductsChange ); + const adsWithProfitResp = await apiFetchAdsWithProfitAsync(); + const adsWithProfit = adsWithProfitResp?.data || []; + + const noww= new Date(); + + const from = new Date(noww.getFullYear(), noww.getMonth(), 1); + const to = new Date(noww.getFullYear(), noww.getMonth() + 1, 0); + + const earningsStats = await Promise.all( + stores.map(async (store) => { + try { + const income = await apiGetStoreIncomeAsync(store.id, from, to); + return { + storeId: income.StoreId, + name: income.StoreName ?? store.name, + storeRevenue: income.TotalIncome , + adminProfit: income.TaxedIncome, + taxRate: (store.tax) * 100, + }; + } catch (err) { + console.error(`❌ Error fetching income for store ${store.id}`, err); + return { + storeId: store.id, + name: store.name, + storeRevenue: 0, + adminProfit: 0, + taxRate: (store.tax) * 100, + }; + } + }) + ); + + setStoreStats(earningsStats); + + + const adminProfit = await apiGetAdminProfitAsync(from, to, stores.map(s => s.id)); + setTotalAdminProfit(adminProfit); + // Other initial data if needed (orders, users - not directly used for KPIs in develop's version) // const ordersData = await apiFetchOrdersAsync(); // const usersResponse = await apiFetchAllUsersAsync(); @@ -816,6 +861,13 @@ const AnalyticsPage = () => { )} + + + Store Earnings (Past Month) + + + + {/* //jel ovo ima smisla ovd? (Comment from HEAD)