Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1685,9 +1685,44 @@ export const apiGetAllRoutesAsync = async () => {
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;
};

export const apiFetchDeliveryAddressByIdAsync = async (addressId) => {
const res = await axios.get(
`${baseApiUrl}/api/user-profile/address/${addressId}`
);
return res.data; // Vraća objekat adrese
};

88 changes: 88 additions & 0 deletions src/components/StoreEarningsTable.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Paper sx={{ mt: 5, p: 2 }}>
<Box sx={{ overflowX: 'auto' }}>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: '#ffffba' }}>
<TableCell sx={{ fontWeight: 'bold' }}>Store Name</TableCell>
<TableCell sortDirection={orderBy === 'storeRevenue' ? order : false} sx={{ fontWeight: 'bold' }}>
<TableSortLabel
active={orderBy === 'storeRevenue'}
direction={order}
onClick={() => handleSort('storeRevenue')}
>
Store Revenue
</TableSortLabel>
</TableCell>
<TableCell sortDirection={orderBy === 'adminProfit' ? order : false} sx={{ fontWeight: 'bold' }}>
<TableSortLabel
active={orderBy === 'adminProfit'}
direction={order}
onClick={() => handleSort('adminProfit')}
>
Admin Profit
</TableSortLabel>
</TableCell>
<TableCell sortDirection={orderBy === 'taxRate' ? order : false} sx={{ fontWeight: 'bold' }}>
<TableSortLabel
active={orderBy === 'taxRate'}
direction={order}
onClick={() => handleSort('taxRate')}
>
Tax Rate (%)
</TableSortLabel>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paginatedData.map((row) => (
<TableRow key={row.storeId}>
<TableCell>{row.name}</TableCell>
<TableCell>{(row.storeRevenue ?? 0).toFixed(2)} $</TableCell>
<TableCell>{(row.adminProfit ?? 0).toFixed(2)} $</TableCell>
<TableCell>{(row.taxRate ?? 0).toFixed(2)} %</TableCell>

</TableRow>
))}
</TableBody>
</Table>
</Box>
<TablePagination
rowsPerPageOptions={[]}
component='div'
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={(e, newPage) => setPage(newPage)}
/>
</Paper>
);
};

export default StoreEarningsTable;
52 changes: 52 additions & 0 deletions src/pages/AnalyticsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -85,6 +89,9 @@ const AnalyticsPage = () => {
const [storeSpecificConversionData, setStoreSpecificConversionData] =
useState([]);

const [storeStats, setStoreStats] = useState([]);


// --- Pagination Logic (from HEAD) ---
const handlePageChange = (event, value) => {
setCurrentProductPage(value);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -816,6 +861,13 @@ const AnalyticsPage = () => {
)}
</Box>

<Box sx={{ px: 4, pb: 10 }}>
<Typography variant='h5' sx={{ mb: 2, color: 'primary.dark' }}>
Store Earnings (Past Month)
</Typography>
<StoreEarningsTable data={storeStats} />
</Box>

<Box sx={{ width: '100%', mt: 6 }} id='seller-section'></Box>
<Box sx={{ width: '100%', mt: 6 }} id='revenue-section'>
{/* //jel ovo ima smisla ovd? (Comment from HEAD)
Expand Down