From 3420b05cd9f73c5a24ca6f2a9bd88f42de93e288 Mon Sep 17 00:00:00 2001 From: NIMENDRA Date: Mon, 14 Oct 2024 11:52:17 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Update=20Finance=20Pages=20(#116?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: DasunDhananjaya-git< it22190420@my.sliit.lk> --- backend/controllers/orderController.js | 50 ++ backend/routes/orderRoute.js | 2 + frontend/src/App.jsx | 2 + frontend/src/Pages/Admin/Afinance.jsx | 426 ++++++------------ frontend/src/Pages/Admin/ManageShopIncome.jsx | 292 ++++++++++++ 5 files changed, 492 insertions(+), 280 deletions(-) create mode 100644 frontend/src/Pages/Admin/ManageShopIncome.jsx diff --git a/backend/controllers/orderController.js b/backend/controllers/orderController.js index ba9d6df6..f06373dc 100644 --- a/backend/controllers/orderController.js +++ b/backend/controllers/orderController.js @@ -248,3 +248,53 @@ export const getTotalSales = async (req, res) => { return res.status(500).json({ message: 'Error fetching total sales' }) } } + + +export const getShopTotalIncome = async (req, res) => { + try { + const totalIncome = await Order.aggregate([ + { + $group: { + _id: '$farmer.shopId', // Group by shop ID + totalIncome: { $sum: '$totalPrice' }, // Sum totalPrice for each shop + }, + }, + { + $lookup: { + from: 'shops', // Name of your shop collection + localField: '_id', // The shop ID from the previous group stage + foreignField: '_id', // The shop ID in the shops collection + as: 'shopDetails', // Output array field for joined shop details + }, + }, + { + $unwind: '$shopDetails', // Deconstruct the shopDetails array + }, + { + $lookup: { + from: 'farmers', // Assuming the farmers collection holds owner information + localField: 'shopDetails.farmer', // Reference to farmer ID in the shopDetails + foreignField: '_id', // The farmer ID in the farmers collection + as: 'farmerDetails', // Output array field for joined farmer details + }, + }, + { + $unwind: '$farmerDetails', // Deconstruct the farmerDetails array + }, + { + $project: { + _id: 0, // Exclude the default _id field + shopId: '$_id', // Include the shop ID + shopName: '$shopDetails.name', // Include the shop name + totalIncome: 1, // Include total income + ownerName: '$farmerDetails.name', // Include the owner's name + }, + }, + ]); + + res.status(200).json(totalIncome); // Send the total income response + } catch (error) { + console.error('Error fetching shop total income with owner:', error); + res.status(500).json({ message: 'Error fetching total income for shops with owner' }); + } +} diff --git a/backend/routes/orderRoute.js b/backend/routes/orderRoute.js index 81e5a321..7f489ef7 100644 --- a/backend/routes/orderRoute.js +++ b/backend/routes/orderRoute.js @@ -10,6 +10,7 @@ import { getOrderById, getDailyOrders, getTotalSales, + getShopTotalIncome, } from '../controllers/orderController.js' const orderRouter = express.Router() @@ -22,6 +23,7 @@ orderRouter.get('/get-user-orders/:id', getOrdersByUserId) orderRouter.put('/:id', updateOrderStatus) orderRouter.delete('/:id', DeleteOrder) orderRouter.get('/get-shop/:id', getShopByFarmerId) +orderRouter.get('/shop-total-income', getShopTotalIncome); orderRouter.get('/:id', getOrderById) // Fetch order details by ID with populated farmer and user details diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 208dfffb..042f9471 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -110,6 +110,7 @@ import HelpLayout from './Layouts/HelpLayout' import Help from './Pages/Help/Help' import SupportTicket from './Pages/Help/SupportTicket' import Feedback from './Pages/Help/Feedback' +import ManageShopIncome from './Pages/Admin/ManageShopIncome' // Define all routes in a single Router const router = createBrowserRouter( @@ -218,6 +219,7 @@ const router = createBrowserRouter( /> } /> } /> + } /> {/* diff --git a/frontend/src/Pages/Admin/Afinance.jsx b/frontend/src/Pages/Admin/Afinance.jsx index 7f4a57c4..74b0b45f 100644 --- a/frontend/src/Pages/Admin/Afinance.jsx +++ b/frontend/src/Pages/Admin/Afinance.jsx @@ -1,302 +1,168 @@ -import { useEffect, useMemo, useState } from 'react' -import axios from '../../axios' -import Sidebar from '../../Components/Admin/AsideBar.jsx' -import { useNavigate } from 'react-router-dom' -import { useDisclosure } from '@nextui-org/react' -import { - Input, - Pagination, - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, - Tooltip, - Button, -} from '@nextui-org/react' -import { FaRegEye } from 'react-icons/fa' -import { MdDeleteSweep } from 'react-icons/md' -import Loading from '../../Components/Loading' -import Swal from 'sweetalert2' -import jsPDF from 'jspdf' -import 'jspdf-autotable' -import logo from '../../assets/logo.png' -import { FaDownload } from 'react-icons/fa' +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Sidebar from '../../Components/Admin/AsideBar.jsx'; +import { Button } from '@nextui-org/react'; +import axios from 'axios'; +import jsPDF from 'jspdf'; +import 'jspdf-autotable'; const Finance = () => { - const [staffMembers, setStaffMembers] = useState([]) - const [loading, setLoading] = useState(true) - const [refetch, setRefetch] = useState(false) - const [page, setPage] = useState(1) - const [search, setSearch] = useState('') - - const rowsPerPage = 4 - const navigate = useNavigate() - - // Fetch staff members - useEffect(() => { - const fetchStaffMembers = async () => { - setLoading(true) - try { - const { data } = await axios.get('/staff') - setStaffMembers(data) - } catch (error) { - console.error('Error fetching staff members:', error) - } finally { - setLoading(false) - } + const navigate = useNavigate(); + const [incomeData, setIncomeData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [sortOrder, setSortOrder] = useState('asc'); // 'asc' for ascending, 'desc' for descending + + // Function to fetch shop income data + const fetchShopIncomeData = async () => { + try { + const response = await axios.get('/api/orders/shop-total-income'); + setIncomeData(response.data); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); } + }; - fetchStaffMembers() - }, [refetch]) - - // Search functionality - const filteredStaff = useMemo(() => { - return staffMembers.filter((member) => - `${member.firstName} ${member.lastName}` - .toLowerCase() - .includes(search.toLowerCase()) - ) - }, [search, staffMembers]) - - // Pagination - const items = useMemo(() => { - const start = (page - 1) * rowsPerPage - const end = start + rowsPerPage - return filteredStaff.slice(start, end) - }, [page, filteredStaff]) - - const pages = Math.ceil(filteredStaff.length / rowsPerPage) - - const handleDeleteStaff = async (staffId) => { - const result = await Swal.fire({ - title: 'Are you sure?', - text: 'Do you really want to delete this staff member? This process cannot be undone.', - icon: 'warning', - showCancelButton: true, - confirmButtonText: 'Yes', - cancelButtonText: 'Cancel', - }) - - if (result.isConfirmed) { - try { - await axios.delete(`/staff/${staffId}`) - Swal.fire( - 'Deleted!', - 'Staff member has been deleted.', - 'success' - ) - setRefetch((prev) => !prev) - } catch (error) { - console.error('Error deleting staff:', error) - Swal.fire( - 'Error!', - 'There was an error deleting the staff member.', - 'error' - ) - } - } - } - - const handleAddNewStaff = () => { - navigate('/AddStaff') - } - - const handleEditStaff = (memberId) => { - navigate(`/updatestaff/${memberId}`) - } - - // PDF Generation + useEffect(() => { + fetchShopIncomeData(); + }, []); + + // Handle search input change + const handleSearchChange = (e) => { + setSearchTerm(e.target.value); + }; + + // Sort the income data based on total income + const handleSort = () => { + const sortedData = [...incomeData].sort((a, b) => { + return sortOrder === 'asc' + ? a.totalIncome - b.totalIncome + : b.totalIncome - a.totalIncome; + }); + setIncomeData(sortedData); + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + }; + + // Generate PDF report const generatePDF = () => { - const doc = new jsPDF() - const img = new Image() - img.src = logo - // Add logo - doc.addImage(img, 'PNG', 20, 35, 30, 5) // Adjust the X, Y, width, and height as needed // Adjust x, y, width, height as needed - - // Add title below the logo - doc.setFontSize(15) - doc.text('Staff List', 105, 40, null, null, 'center') // Centered below logo - - // Prepare the table data - const tableColumn = [ - 'Id', - 'Full Name', - 'NIC', - 'Email', - 'Birth Day', - 'Address', - 'Role', - ] - const tableRows = [] - - filteredStaff.forEach((member, index) => { - const memberData = [ - index + 1, - `${member.firstName} ${member.lastName}`, - member.nic, - member.email, - member.birthday.split('T')[0], - `${member.Address.home} ${member.Address.street} ${member.Address.city}`, - member.role, - ] - tableRows.push(memberData) - }) - - // Add table to PDF + const doc = new jsPDF(); + const title = 'Shop Income Report'; + + // Set font size and style for title + doc.setFontSize(16); + doc.text(title, 14, 16); + + // Set font size for table headers + doc.setFontSize(12); + + // Create the table doc.autoTable({ - head: [tableColumn], - body: tableRows, - startY: 50, // Positioning the table after the logo and title + head: [['Shop Name', 'Owner Name', 'Total Income']], + body: incomeData + .filter(shop => shop.shopName.toLowerCase().includes(searchTerm.toLowerCase())) + .map(shop => [ + shop.shopName, + shop.ownerName, + `Rs. ${shop.totalIncome.toFixed(2)}` + ]), + margin: { top: 24 }, // Margin from the top styles: { - fontSize: 9, // Adjust this value to make the table content smaller + fontSize: 10, + cellPadding: 5, + halign: 'left', // Align text to the right in cells + valign: 'middle' // Vertically center the text }, - }) - - doc.save('staff-list.pdf') - } - - if (loading) { - return ( -
- -
- ) - } + headStyles: { + fillColor: [22, 160, 133], // Header background color + textColor: [255, 255, 255], // Header text color + fontStyle: 'bold' // Bold font style for headers + }, + theme: 'striped', // Use striped theme for better readability + }); + + doc.save('shop_monthly_payment_report.pdf'); + }; + + // Filter data based on search term + const filteredData = incomeData.filter(shop => + shop.shopName.toLowerCase().includes(searchTerm.toLowerCase()) + ); return ( -
+
-
- ) -} + ); +}; -export default Finance +export default Finance; diff --git a/frontend/src/Pages/Admin/ManageShopIncome.jsx b/frontend/src/Pages/Admin/ManageShopIncome.jsx new file mode 100644 index 00000000..b4303b14 --- /dev/null +++ b/frontend/src/Pages/Admin/ManageShopIncome.jsx @@ -0,0 +1,292 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Sidebar from '../../Components/Admin/AsideBar.jsx'; +import { Button } from '@nextui-org/react'; +import axios from 'axios'; +import jsPDF from 'jspdf'; +import 'jspdf-autotable'; + +const ManageShopIncome = () => { + const navigate = useNavigate(); + const [shopIncomeData, setShopIncomeData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [sortOrder, setSortOrder] = useState('asc'); + const [taxRate, setTaxRate] = useState(20); + const [paymentStatus, setPaymentStatus] = useState({}); // State to store payment statuses + + // Function to fetch shop income data + const fetchShopIncomeData = async () => { + try { + const response = await axios.get('/api/orders/shop-total-income'); // Adjust endpoint as necessary + setShopIncomeData(response.data); + // Initialize payment statuses with default values (e.g., 'Pending') + const initialStatus = {}; + response.data.forEach(shop => { + initialStatus[shop.shopId] = 'Pending'; // Default to 'Pending' + }); + setPaymentStatus(initialStatus); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchShopIncomeData(); + }, []); + + // Handle search input change + const handleSearchChange = (e) => { + setSearchTerm(e.target.value); + }; + + // Sort the shop income data based on total income + const handleSort = () => { + const sortedData = [...shopIncomeData].sort((a, b) => { + return sortOrder === 'asc' + ? a.totalIncome - b.totalIncome + : b.totalIncome - a.totalIncome; + }); + setShopIncomeData(sortedData); + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + }; + + const generatePDF = () => { + const doc = new jsPDF(); + const title = 'Shop Income Report'; + + // Set font size and style for title + doc.setFontSize(16); + doc.text(title, 14, 16); + + // Create the table + doc.autoTable({ + head: [ + [ + 'Shop Name', + 'Owner Name', + 'Total Income', + `Tax (${taxRate}%)`, + 'Final Shop Income', + 'FarmCart Company Income', + 'Payment Status' // New header for payment status + ], + ], + body: shopIncomeData + .filter((shop) => + shop.shopName.toLowerCase().includes(searchTerm.toLowerCase()) + ) + .map((shop) => { + const tax = (shop.totalIncome * (taxRate / 100)).toFixed(2); + const finalIncome = (shop.totalIncome - tax).toFixed(2); + const farmCartIncome = tax; // FarmCart company income is the same as the tax amount + return [ + shop.shopName, + shop.ownerName, + `Rs. ${shop.totalIncome.toFixed(2)}`, + `Rs. ${tax}`, + `Rs. ${finalIncome}`, + `Rs. ${farmCartIncome}`, + paymentStatus[shop.shopId] // Add payment status to the PDF + ]; + }), + margin: { top: 24 }, + styles: { + fontSize: 10, + cellPadding: 5, + halign: 'left', + valign: 'middle', + }, + headStyles: { + fillColor: [22, 160, 133], + textColor: [255, 255, 255], + fontStyle: 'bold', + }, + theme: 'striped', + }); + + doc.save('shop_income_report.pdf'); + }; + + // Filter data based on search term + const filteredData = shopIncomeData.filter((shop) => + shop.shopName.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Handle payment status button click + const handlePaymentStatusChange = (shopId, status) => { + setPaymentStatus((prevStatus) => ({ + ...prevStatus, + [shopId]: status // Update the payment status for the specific shop + })); + + // Optionally, send the new payment status to the backend + axios.post('/api/payment-status', { shopId, status }) + .then(response => console.log("Payment status updated", response)) + .catch(err => console.error("Error updating payment status", err)); + }; + + return ( +
+
+ + +
+
+

+ Manage Shop Income +

+
+ +
+ + + { + const value = parseFloat(e.target.value); + if (!isNaN(value) && value >= 0) { + setTaxRate(value); + } + }} + className="border p-2 w-1/12 rounded-md shadow-md border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-400" + /> +

Adjust the tax rate

+ + + + + + +
+ +
+ {loading ? ( +
Loading...
+ ) : error ? ( +
+ Error: {error} +
+ ) : ( + + + + + + + + + + + + + + {filteredData.map((shop) => { + const tax = (shop.totalIncome * (taxRate / 100)).toFixed(2); + const finalIncome = (shop.totalIncome - tax).toFixed(2); + + // Debugging: Log values to check calculations + console.log(`Shop: ${shop.shopName}, Total Income: ${shop.totalIncome}, Tax: ${tax}, Final Income: ${finalIncome}`); + + return ( + + + + + + + + + + ); + })} + +
+ Shop Name + + Owner Name + + Total Income + + FarmCart Tax ({taxRate}%) + + Final Shop Income + + FarmCart Income + + Payment Status +
+ {shop.shopName} + + {shop.ownerName} + + Rs.{shop.totalIncome.toFixed(2)} + + Rs.{tax} + + Rs.{finalIncome} + + Rs.{tax} + + + + +
+ )} +
+
+
+
+ ); +}; + +export default ManageShopIncome;