From 807db79fb030c79e40e8e1af48b339702413f767 Mon Sep 17 00:00:00 2001 From: Brendan Lieu Date: Tue, 4 Feb 2025 16:28:28 -0800 Subject: [PATCH] Implemented filter modal --- client/src/assets/{logo => }/filter.svg | 0 client/src/assets/person.svg | 3 + .../components/invoices/InvoiceComponents.jsx | 106 ++++++++++++++++ .../components/invoices/InvoicesDashboard.jsx | 113 +++++++++++------- server/routes/invoicesAssignments.js | 5 +- 5 files changed, 180 insertions(+), 47 deletions(-) rename client/src/assets/{logo => }/filter.svg (100%) create mode 100644 client/src/assets/person.svg create mode 100644 client/src/components/invoices/InvoiceComponents.jsx diff --git a/client/src/assets/logo/filter.svg b/client/src/assets/filter.svg similarity index 100% rename from client/src/assets/logo/filter.svg rename to client/src/assets/filter.svg diff --git a/client/src/assets/person.svg b/client/src/assets/person.svg new file mode 100644 index 0000000..dd428c8 --- /dev/null +++ b/client/src/assets/person.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/src/components/invoices/InvoiceComponents.jsx b/client/src/components/invoices/InvoiceComponents.jsx new file mode 100644 index 0000000..6db369e --- /dev/null +++ b/client/src/components/invoices/InvoiceComponents.jsx @@ -0,0 +1,106 @@ +import {Menu, MenuButton, MenuList, MenuItem, Image, Button, Flex, Heading, PopoverTrigger, Popover, PopoverContent, Input, Text, Select} from '@chakra-ui/react' +import filterIcon from "../../assets/filter.svg"; +import { useEffect, useState } from 'react'; +import { CalendarIcon } from '@chakra-ui/icons'; +import personIcon from "../../assets/person.svg" + +function InvoiceFilter({invoices, filter, setFilter}) { + + + useEffect(() => { + console.log(filter); + console.log(invoices); + }, [invoices, filter]) + + return ( + <> + + + + + + + + + Date Range + + + setFilter({...filter, startDate: e.target.value})}/> + to + setFilter({...filter, endDate: e.target.value})}/> + + + Status + + {['All', 'Paid', 'Not Paid', 'Past Due'].map((label, index) => ( + + ))} + + + + + Instructor + Instructor(s) + + + + + + + Payee + Payee(s) + + + + + + + + + ) +} + +export { InvoiceFilter } \ No newline at end of file diff --git a/client/src/components/invoices/InvoicesDashboard.jsx b/client/src/components/invoices/InvoicesDashboard.jsx index 477a357..78dd1ce 100644 --- a/client/src/components/invoices/InvoicesDashboard.jsx +++ b/client/src/components/invoices/InvoicesDashboard.jsx @@ -1,12 +1,11 @@ -import { Text, Box, Stack, Table, HStack, Tr, Thead, Th, TableContainer, Tbody, Td, Flex, Button, Input, IconButton, Image, InputGroup, InputRightElement, Heading} from "@chakra-ui/react"; -import filterIcon from "../../assets/logo/filter.svg"; +import { Text, Box, Stack, Table, HStack, Tr, Thead, Th, TableContainer, Tbody, Td, Flex, Button, Input, IconButton, Image, InputGroup, InputRightElement, Heading, useToast } from "@chakra-ui/react"; import { DownloadIcon, SearchIcon } from "@chakra-ui/icons"; import { useBackendContext } from "../../contexts/hooks/useBackendContext"; import Navbar from "../Navbar"; -import { useToast } from '@chakra-ui/react' import { useEffect, useState, useRef } from "react"; import { useNavigate } from 'react-router-dom' import PDFButtonInvoice from "./PDFButtonInvoice" +import { InvoiceFilter } from "./InvoiceComponents"; function InvoicesDashboard(){ @@ -16,6 +15,13 @@ function InvoicesDashboard(){ const [invoices, setInvoices] = useState([]); const [query, setQuery] = useState(''); const hasShownToast = useRef(false); + const [filter, setFilter] = useState({ + startDate : "", + endDate : "", + status : "all", + instructor : "all", + payee : "all" + }) const isPaidColor = (invoice) => { if (invoice.isSent && invoice.paymentStatus === "full") { @@ -53,29 +59,6 @@ function InvoicesDashboard(){ useEffect(() => { if (invoices.length === 0 || hasShownToast.current) return; - - const callToast = () => { - toast({ - title: "Unpaid Invoices", - description: notifCounter > 1 - ? `You have ${notifCounter} past due invoices` - : `${pastDueInvoices[0].name} - ${pastDueInvoices[0].endDate.split("T")[0]}`, - status: "error", - duration: 9000, - position: "bottom-right", - isClosable: true, - render: () => ( - navigate("/notification")}> - Unpaid Invoices - {notifCounter > 1 - ? `You have ${notifCounter} past due invoices` - : `${pastDueInvoices[0].name} - - ${new Date(pastDueInvoices[0].endDate).toLocaleDateString("en-US", {month: "2-digit", day: "2-digit", year: "2-digit",})}` - } - - ), - }); - } const getUnpaidInvoices = () => { const pastDueInvoices = invoices.filter(invoice => isPaid(invoice) === "Past Due"); @@ -83,23 +66,74 @@ function InvoicesDashboard(){ if (notifCounter > 0) { hasShownToast.current = true; // Set ref to true to prevent multiple toasts - - callToast(); + toast({ + title: "Unpaid Invoices", + description: notifCounter > 1 + ? `You have ${notifCounter} past due invoices` + : `${pastDueInvoices[0].name} - ${pastDueInvoices[0].endDate.split("T")[0]}`, + status: "error", + duration: 9000, + position: "bottom-right", + isClosable: true, + render: () => ( + navigate("/notification")}> + Unpaid Invoices + {notifCounter > 1 + ? `You have ${notifCounter} past due invoices` + : `${pastDueInvoices[0].name} - + ${new Date(pastDueInvoices[0].endDate).toLocaleDateString("en-US", {month: "2-digit", day: "2-digit", year: "2-digit",})}` + } + + ), + }); } }; getUnpaidInvoices(); + console.log(filter); }, [invoices]); + const filteredInvoices = invoices + .filter(invoice => invoice.eventName.toLowerCase().includes(query.toLowerCase())) + .filter(invoice => { + + // Exclude invoices where the role is "instructor" + if (invoice.role === "instructor") return false; + + // Status filter + if (filter.status !== 'all' && isPaid(invoice).toLowerCase() !== filter.status.toLowerCase()) return false; + + + // Instructor filter + // Filters for events that have an instructor, and gets the event while ensuring only showing a single events even if they have both instructor and payee + if (filter.instructor.toLowerCase() !== 'all' && invoice.role !== "instructor" && !invoices.some(inv => inv.eventName === invoice.eventName && inv.role === "instructor" && inv.name.toLowerCase() === filter.instructor.toLowerCase())) + return false; + + + //Payee filter + if (filter.payee.toLowerCase() !== 'all' && invoice.role === "payee" && invoice.name.toLowerCase() !== filter.payee.toLowerCase()) return false; + + // Date range filters + if (filter.startDate && new Date(invoice.endDate) < new Date(filter.startDate)) return false; + if (filter.endDate && new Date(invoice.endDate) > new Date(new Date(filter.endDate).setDate(new Date(filter.endDate).getDate() + 1))) return false; //to make date range inclusive + + + + return true; + }); + + + + return( - + {/* */} @@ -127,27 +161,18 @@ function InvoicesDashboard(){ - {invoices - .filter(invoice => invoice.eventName.toLowerCase().includes(query.toLowerCase())) - .map((invoice, index) => ( + {filteredInvoices.map((invoice, index) => ( {invoice.eventName} - - {isPaid(invoice)} - + {isPaid(invoice)} {invoice.name} {new Date(invoice.endDate).toLocaleDateString("en-US", { - month: "2-digit", - day: "2-digit", - year: "numeric", + month: "2-digit", day: "2-digit", year: "numeric" })} - + diff --git a/server/routes/invoicesAssignments.js b/server/routes/invoicesAssignments.js index e6ed379..2cda42b 100644 --- a/server/routes/invoicesAssignments.js +++ b/server/routes/invoicesAssignments.js @@ -8,12 +8,11 @@ invoicesAssignments.use(express.json()); invoicesAssignments.get("/", async (req, res) => { try { const invoices = await db.query( - `SELECT events.name as event_name, invoices.is_sent, clients.name, invoices.end_date + `SELECT events.name as event_name, invoices.is_sent, invoices.payment_status, clients.name, invoices.end_date, assignments.role FROM events JOIN invoices ON events.id = invoices.event_id JOIN assignments ON assignments.event_id = events.id - JOIN clients ON clients.id = assignments.client_id - WHERE assignments.role = 'payee';`, + JOIN clients ON clients.id = assignments.client_id;`, );