Skip to content

Commit

Permalink
Implemented filter modal
Browse files Browse the repository at this point in the history
  • Loading branch information
brelieu05 committed Feb 5, 2025
1 parent 2b28a0e commit 807db79
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 47 deletions.
File renamed without changes
3 changes: 3 additions & 0 deletions client/src/assets/person.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions client/src/components/invoices/InvoiceComponents.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Popover placement='bottom-start'>
<PopoverTrigger>

<Button
backgroundColor="transparent"
border="1px solid rgba(71, 72, 73, 0.20)"
borderRadius="15px"
h="48px"
px="12px"
>
<Image src={filterIcon} alt="Filter" boxSize="20px" mr="4px" /> Filter
</Button>
</PopoverTrigger>
<PopoverContent h='auto' w='sm' borderRadius='15px' box-shadow='0px 4px 4px 0px rgba(0, 0, 0, 0.25)' border='1px solid #D2D2D2' padding='16px' display='inline-flex' flexDirection='column' gap='16px'>
<Flex alignItems='center' gap='8px'>
<CalendarIcon color='#767778'/>
<Text size='sm' as='b' color='#767778'>Date Range</Text>
</Flex>
<Flex justifyContent='space-evenly' alignItems='center'>
<Input type='date' w='150px' backgroundColor='#F6F6F6' value={filter.startDate} onChange={(e) => setFilter({...filter, startDate: e.target.value})}/>
to
<Input type='date' w='150px' backgroundColor='#F6F6F6' value={filter.endDate} onChange={(e) => setFilter({...filter, endDate: e.target.value})}/>
</Flex>

<Text size='sm' as='b' color='#767778'>Status</Text>
<Flex justifyContent='space-between' padding='4px 12px'>
{['All', 'Paid', 'Not Paid', 'Past Due'].map((label, index) => (
<Button
key={index}
borderRadius="30px"
background="#F6F6F6"
border={filter.status === label.toLowerCase() ? '1px solid var(--indigo, #4E4AE7)' : '1px solid transparent'}
onClick={() => setFilter(filter => ({...filter, status: label.toLowerCase()}))
}
>
{label}
</Button>
))}
</Flex>

<Flex justifyContent="space-between" alignItems="center" mb={4}>
<Flex alignItems="center">
<Image src={personIcon} alt="Instructor" boxSize="20px" mr="4px" />
<Text as="b" color="#767778">Instructor(s)</Text>
</Flex>
<Select w="50%" backgroundColor='#F6F6F6' value={filter.instructor} onChange={(e) => setFilter({...filter, instructor : e.target.value})}>
<option>All</option>
{invoices
.filter(invoice => invoice.role === "instructor")
.reduce((uniqueNames, invoice) => {
if (!uniqueNames.includes(invoice.name)) {
uniqueNames.push(invoice.name);
}
return uniqueNames;
}, [])
.map((name, index) => (
<option key={index}>{name}</option>
))}
</Select>
</Flex>

<Flex justifyContent="space-between" alignItems="center">
<Flex alignItems="center">
<Image src={personIcon} alt="Payee" boxSize="20px" mr="4px" />
<Text as="b" color="#767778">Payee(s)</Text>
</Flex>
<Select w="50%" backgroundColor='#F6F6F6' value={filter.payee} onChange={(e) => setFilter({...filter, payee : e.target.value})}>
<option>All</option>
{invoices
.filter(invoice => invoice.role === "payee")
.reduce((uniqueNames, invoice) => {
if (!uniqueNames.includes(invoice.name)) {
uniqueNames.push(invoice.name);
}
return uniqueNames;
}, [])
.map((name, index) => (
<option key={index}>{name}</option>
))}
</Select>
</Flex>


</PopoverContent>
</Popover>
</>
)
}

export { InvoiceFilter }
113 changes: 69 additions & 44 deletions client/src/components/invoices/InvoicesDashboard.jsx
Original file line number Diff line number Diff line change
@@ -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(){
Expand All @@ -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") {
Expand Down Expand Up @@ -53,53 +59,81 @@ 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: () => (
<Box p={3} bg="#FED7D7" borderTop="4px solid" borderTopColor="red.500" onClick={() => navigate("/notification")}>
<Heading size="sm">Unpaid Invoices</Heading>
{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",})}`
}
</Box>
),
});
}

const getUnpaidInvoices = () => {
const pastDueInvoices = invoices.filter(invoice => isPaid(invoice) === "Past Due");
const notifCounter = pastDueInvoices.length;

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: () => (
<Box p={3} bg="#FED7D7" borderTop="4px solid" borderTopColor="red.500" onClick={() => navigate("/notification")}>
<Heading size="sm">Unpaid Invoices</Heading>
{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",})}`
}
</Box>
),
});
}
};

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(
<Flex minH="100vh">
<Navbar style={{display: 'flex', h: "100vh"}}/>
<Flex w='100vw' m='120px 40px' flexDirection='column' padding="48px">
<Flex justifyContent='space-between' mb='40px'>
<Button backgroundColor='transparent' border="1px solid rgba(71, 72, 73, 0.20)" borderRadius='15px' h='48px'>
<Image src={filterIcon} alt="Filter" boxSize="24px" mt='4px'/>Filter
</Button>
{/* <Button backgroundColor='transparent' border="1px solid rgba(71, 72, 73, 0.20)" borderRadius='15px' h='48px'> */}
<InvoiceFilter filter={filter} setFilter={setFilter} invoices={invoices} />
{/* </Button> */}

<InputGroup w='400px' borderColor='transparent' >
<InputRightElement pointerEvents='none' bgColor="rgba(71, 72, 73, 0.20)" borderRadius='0px 15px 15px 0px'>
Expand Down Expand Up @@ -127,27 +161,18 @@ function InvoicesDashboard(){
</Tr>
</Thead>
<Tbody>
{invoices
.filter(invoice => invoice.eventName.toLowerCase().includes(query.toLowerCase()))
.map((invoice, index) => (
{filteredInvoices.map((invoice, index) => (
<Tr key={index}>
<Td textAlign='center'>{invoice.eventName}</Td>
<Td
textAlign='center'
bg = {isPaidColor(invoice)}
>
{isPaid(invoice)}
</Td>
<Td textAlign='center' bg={isPaidColor(invoice)}>{isPaid(invoice)}</Td>
<Td textAlign='center'>{invoice.name}</Td>
<Td textAlign="center">
{new Date(invoice.endDate).toLocaleDateString("en-US", {
month: "2-digit",
day: "2-digit",
year: "numeric",
month: "2-digit", day: "2-digit", year: "numeric"
})}
</Td>
<Td>
<Flex justifyContent = "center">
<Flex justifyContent="center">
<PDFButtonInvoice invoice={invoice} />
</Flex>
</Td>
Expand Down
5 changes: 2 additions & 3 deletions server/routes/invoicesAssignments.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;`,
);


Expand Down

0 comments on commit 807db79

Please sign in to comment.