From c721e828b16fd1f7b74d48469570f5937ffc60c3 Mon Sep 17 00:00:00 2001 From: wyna Date: Tue, 17 Sep 2024 10:29:01 +0200 Subject: [PATCH 1/6] indicators next to listItems --- src/api/firebase.js | 4 ++++ src/components/ListItem.jsx | 35 ++++++++++++++++++++++++++++++++++- src/components/SingleList.css | 11 +++++++++++ src/views/List.jsx | 5 +++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/api/firebase.js b/src/api/firebase.js index 6f650de..1399687 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -239,3 +239,7 @@ export async function deleteItem() { * this function must accept! */ } + +export function comparePurchaseUrgency(arr) { + return arr.sort(); +} diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index 051e336..dab1385 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -1,7 +1,7 @@ import './ListItem.css'; import { updateItem } from '../api'; import { useEffect } from 'react'; -import { ONE_DAY_IN_MILLISECONDS } from '../utils/dates'; +import { ONE_DAY_IN_MILLISECONDS, getDaysBetweenDates } from '../utils/dates'; export function ListItem({ listPath, @@ -9,6 +9,7 @@ export function ListItem({ id, isChecked, dateLastPurchased, + dateNextPurchased, totalPurchases, dayInterval, dateCreated, @@ -26,6 +27,36 @@ export function ListItem({ }); }; + //We are repeating logic form firebase file, maybe we should extract this into a function? + + const dateLastPurchasedJavaScriptObject = dateLastPurchased + ? dateLastPurchased.toDate() + : dateCreated.toDate(); + + const daysSinceLastPurchase = getDaysBetweenDates( + new Date(), + dateLastPurchasedJavaScriptObject, + ); + + const daysUntilNextPurchase = getDaysBetweenDates( + dateNextPurchased.toDate(), + new Date(), + ); + + //_________ + + const getIndicator = () => { + if (daysSinceLastPurchase > 60) { + return 'Inactive'; + } else if (daysUntilNextPurchase <= 7) { + return 'Soon'; + } else if (daysUntilNextPurchase > 7 && daysUntilNextPurchase < 30) { + return 'Kind of soon'; + } else if (daysUntilNextPurchase >= 30) { + return 'Not soon'; + } + }; + useEffect(() => { const today = new Date().getTime(); const datePurchasedInMillis = dateLastPurchased?.toMillis(); @@ -51,6 +82,8 @@ export function ListItem({ disabled={isChecked} /> + {/* Add CSS to dynamically change bg-color for badges? */} +

{getIndicator()}

); } diff --git a/src/components/SingleList.css b/src/components/SingleList.css index 1e528f0..ce9c3d4 100644 --- a/src/components/SingleList.css +++ b/src/components/SingleList.css @@ -8,3 +8,14 @@ .SingleList-label { margin-left: 0.2em; } + +.ListItem { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.TimeBadge { + margin: 8px; + font-size: small; +} diff --git a/src/views/List.jsx b/src/views/List.jsx index 58bbdde..dcc91f2 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { ListItem, SearchBar } from '../components'; import { useNavigate } from 'react-router-dom'; +import { comparePurchaseUrgency } from '../api/firebase'; export function List({ data, listPath }) { const [search, setSearch] = useState(''); @@ -11,6 +12,9 @@ export function List({ data, listPath }) { setDisplayData([...data]); }, [data]); + const test = comparePurchaseUrgency([2, 8, 1, 10, 300, 6, 4]); + console.log('test', test); + return ( <> ))} From 1565048cb10d7319f0e08c27ab80f88008205f67 Mon Sep 17 00:00:00 2001 From: Jonathan O'Riordan Date: Wed, 18 Sep 2024 09:33:11 +0100 Subject: [PATCH 2/6] Sorting implemented --- src/api/firebase.js | 19 ++++++++++++++++++- src/components/ListItem.jsx | 36 +++-------------------------------- src/views/List.jsx | 38 +++++++++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/api/firebase.js b/src/api/firebase.js index 1399687..cddc884 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -241,5 +241,22 @@ export async function deleteItem() { } export function comparePurchaseUrgency(arr) { - return arr.sort(); + const soonArray = arr + .filter((item) => item.indicator === 'Soon') + .sort((a, b) => (a.name > b.name ? 1 : -1)); + const kindOfSoon = arr + .filter((item) => item.indicator === 'Kind of soon') + .sort((a, b) => (a.name > b.name ? 1 : -1)); + const notSoon = arr + .filter((item) => item.indicator === 'Not soon') + .sort((a, b) => (a.name > b.name ? 1 : -1)); + const inactive = arr + .filter((item) => item.indicator === 'Inactive') + .sort((a, b) => (a.name > b.name ? 1 : -1)); + const overdue = arr + .filter((item) => item.indicator === 'Overdue') + .sort((a, b) => (a.name > b.name ? 1 : -1)); + return overdue.length > 0 + ? overdue.concat(soonArray, kindOfSoon, notSoon, inactive) + : soonArray.concat(kindOfSoon, notSoon, inactive); } diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index dab1385..82ed5ba 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -1,7 +1,7 @@ import './ListItem.css'; import { updateItem } from '../api'; import { useEffect } from 'react'; -import { ONE_DAY_IN_MILLISECONDS, getDaysBetweenDates } from '../utils/dates'; +import { ONE_DAY_IN_MILLISECONDS } from '../utils/dates'; export function ListItem({ listPath, @@ -9,10 +9,10 @@ export function ListItem({ id, isChecked, dateLastPurchased, - dateNextPurchased, totalPurchases, dayInterval, dateCreated, + indicator, }) { const handleOnChange = async (event) => { let { checked } = event.target; @@ -27,36 +27,6 @@ export function ListItem({ }); }; - //We are repeating logic form firebase file, maybe we should extract this into a function? - - const dateLastPurchasedJavaScriptObject = dateLastPurchased - ? dateLastPurchased.toDate() - : dateCreated.toDate(); - - const daysSinceLastPurchase = getDaysBetweenDates( - new Date(), - dateLastPurchasedJavaScriptObject, - ); - - const daysUntilNextPurchase = getDaysBetweenDates( - dateNextPurchased.toDate(), - new Date(), - ); - - //_________ - - const getIndicator = () => { - if (daysSinceLastPurchase > 60) { - return 'Inactive'; - } else if (daysUntilNextPurchase <= 7) { - return 'Soon'; - } else if (daysUntilNextPurchase > 7 && daysUntilNextPurchase < 30) { - return 'Kind of soon'; - } else if (daysUntilNextPurchase >= 30) { - return 'Not soon'; - } - }; - useEffect(() => { const today = new Date().getTime(); const datePurchasedInMillis = dateLastPurchased?.toMillis(); @@ -83,7 +53,7 @@ export function ListItem({ /> {/* Add CSS to dynamically change bg-color for badges? */} -

{getIndicator()}

+

{indicator}

); } diff --git a/src/views/List.jsx b/src/views/List.jsx index dcc91f2..37983c6 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { ListItem, SearchBar } from '../components'; import { useNavigate } from 'react-router-dom'; import { comparePurchaseUrgency } from '../api/firebase'; +import { getDaysBetweenDates } from '../utils/dates'; export function List({ data, listPath }) { const [search, setSearch] = useState(''); @@ -9,11 +10,39 @@ export function List({ data, listPath }) { const navigate = useNavigate(); useEffect(() => { - setDisplayData([...data]); - }, [data]); + const getIndicator = (item) => { + const dateLastPurchasedJavaScriptObject = item.dateLastPurchased + ? item.dateLastPurchased.toDate() + : item.dateCreated.toDate(); + + const daysSinceLastPurchase = getDaysBetweenDates( + new Date(), + dateLastPurchasedJavaScriptObject, + ); - const test = comparePurchaseUrgency([2, 8, 1, 10, 300, 6, 4]); - console.log('test', test); + const daysUntilNextPurchase = getDaysBetweenDates( + item.dateNextPurchased.toDate(), + new Date(), + ); + + if (daysSinceLastPurchase > 60) { + return 'Inactive'; + } else if (daysSinceLastPurchase > 30 && daysSinceLastPurchase < 60) { + return 'Overdue'; + } else if (daysUntilNextPurchase <= 7) { + return 'Soon'; + } else if (daysUntilNextPurchase > 7 && daysUntilNextPurchase < 30) { + return 'Kind of soon'; + } else if (daysUntilNextPurchase >= 30) { + return 'Not soon'; + } + }; + const arrayWithIndicator = data.map((item) => ({ + ...item, + indicator: getIndicator(item), + })); + setDisplayData([...comparePurchaseUrgency(arrayWithIndicator)]); + }, [data]); return ( <> @@ -36,6 +65,7 @@ export function List({ data, listPath }) { dayInterval={item.dayInterval} dateCreated={item.dateCreated} dateNextPurchased={item.dateNextPurchased} + indicator={item.indicator} /> ))} From dd69ac8fe216d68dcb0e5abc8272699a2a5a981c Mon Sep 17 00:00:00 2001 From: wyna Date: Thu, 19 Sep 2024 11:53:03 +0200 Subject: [PATCH 3/6] fixed searchbar bug + refactored comparePurchaseUrgency & extracted getIndicator --- src/api/firebase.js | 46 +++++++++++++++++++++--------------- src/components/SearchBar.jsx | 10 ++++---- src/utils/helpers.js | 29 +++++++++++++++++++++++ src/views/List.jsx | 33 ++++---------------------- 4 files changed, 65 insertions(+), 53 deletions(-) create mode 100644 src/utils/helpers.js diff --git a/src/api/firebase.js b/src/api/firebase.js index cddc884..8820133 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -7,7 +7,6 @@ import { onSnapshot, updateDoc, addDoc, - Timestamp, } from 'firebase/firestore'; import { useEffect, useState } from 'react'; import { db } from './config'; @@ -241,22 +240,31 @@ export async function deleteItem() { } export function comparePurchaseUrgency(arr) { - const soonArray = arr - .filter((item) => item.indicator === 'Soon') - .sort((a, b) => (a.name > b.name ? 1 : -1)); - const kindOfSoon = arr - .filter((item) => item.indicator === 'Kind of soon') - .sort((a, b) => (a.name > b.name ? 1 : -1)); - const notSoon = arr - .filter((item) => item.indicator === 'Not soon') - .sort((a, b) => (a.name > b.name ? 1 : -1)); - const inactive = arr - .filter((item) => item.indicator === 'Inactive') - .sort((a, b) => (a.name > b.name ? 1 : -1)); - const overdue = arr - .filter((item) => item.indicator === 'Overdue') - .sort((a, b) => (a.name > b.name ? 1 : -1)); - return overdue.length > 0 - ? overdue.concat(soonArray, kindOfSoon, notSoon, inactive) - : soonArray.concat(kindOfSoon, notSoon, inactive); + const groupedItems = { + Overdue: [], + Soon: [], + 'Kind of soon': [], + 'Not soon': [], + Inactive: [], + }; + arr.forEach((item) => { + if (groupedItems[item.indicator]) { + groupedItems[item.indicator].push(item); + } + }); + Object.keys(groupedItems).forEach((key) => { + groupedItems[key].sort((a, b) => (a.name > b.name ? 1 : -1)); + }); + return groupedItems['Overdue'].length > 0 + ? groupedItems['Overdue'].concat( + groupedItems['Soon'], + groupedItems['Kind of soon'], + groupedItems['Not soon'], + groupedItems['Inactive'], + ) + : groupedItems['Soon'].concat( + groupedItems['Kind of soon'], + groupedItems['Not soon'], + groupedItems['Inactive'], + ); } diff --git a/src/components/SearchBar.jsx b/src/components/SearchBar.jsx index 6657ce1..d380b23 100644 --- a/src/components/SearchBar.jsx +++ b/src/components/SearchBar.jsx @@ -1,16 +1,16 @@ -export function SearchBar({ data, setDisplayData, setSearch, search }) { +export function SearchBar({ allData, setDisplayData, setSearch, search }) { const handleInputChange = (e) => { const searchTerm = e.target.value; setSearch(searchTerm); - const filteredUsers = data.filter((item) => + const filteredData = allData.filter((item) => item.name.toLowerCase().includes(searchTerm.trim().toLowerCase()), ); - setDisplayData(filteredUsers); - }; + setDisplayData(filteredData); + }; const handleClear = () => { - setDisplayData(data); + setDisplayData(allData); setSearch(''); }; diff --git a/src/utils/helpers.js b/src/utils/helpers.js new file mode 100644 index 0000000..a2364aa --- /dev/null +++ b/src/utils/helpers.js @@ -0,0 +1,29 @@ +import { getDaysBetweenDates } from './dates'; + +export function getIndicator(item) { + const dateLastPurchasedJavaScriptObject = item.dateLastPurchased + ? item.dateLastPurchased.toDate() + : item.dateCreated.toDate(); + + const daysSinceLastPurchase = getDaysBetweenDates( + new Date(), + dateLastPurchasedJavaScriptObject, + ); + + const daysUntilNextPurchase = getDaysBetweenDates( + item.dateNextPurchased.toDate(), + new Date(), + ); + + if (daysSinceLastPurchase > 60) { + return 'Inactive'; + } else if (daysSinceLastPurchase > 30 && daysSinceLastPurchase < 60) { + return 'Overdue'; + } else if (daysUntilNextPurchase <= 7) { + return 'Soon'; + } else if (daysUntilNextPurchase > 7 && daysUntilNextPurchase < 30) { + return 'Kind of soon'; + } else if (daysUntilNextPurchase >= 30) { + return 'Not soon'; + } +} diff --git a/src/views/List.jsx b/src/views/List.jsx index 37983c6..6e3804d 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -2,45 +2,20 @@ import { useEffect, useState } from 'react'; import { ListItem, SearchBar } from '../components'; import { useNavigate } from 'react-router-dom'; import { comparePurchaseUrgency } from '../api/firebase'; -import { getDaysBetweenDates } from '../utils/dates'; +import { getIndicator } from '../utils/helpers'; export function List({ data, listPath }) { const [search, setSearch] = useState(''); + const [allData, setAllData] = useState([]); const [displayData, setDisplayData] = useState([]); const navigate = useNavigate(); useEffect(() => { - const getIndicator = (item) => { - const dateLastPurchasedJavaScriptObject = item.dateLastPurchased - ? item.dateLastPurchased.toDate() - : item.dateCreated.toDate(); - - const daysSinceLastPurchase = getDaysBetweenDates( - new Date(), - dateLastPurchasedJavaScriptObject, - ); - - const daysUntilNextPurchase = getDaysBetweenDates( - item.dateNextPurchased.toDate(), - new Date(), - ); - - if (daysSinceLastPurchase > 60) { - return 'Inactive'; - } else if (daysSinceLastPurchase > 30 && daysSinceLastPurchase < 60) { - return 'Overdue'; - } else if (daysUntilNextPurchase <= 7) { - return 'Soon'; - } else if (daysUntilNextPurchase > 7 && daysUntilNextPurchase < 30) { - return 'Kind of soon'; - } else if (daysUntilNextPurchase >= 30) { - return 'Not soon'; - } - }; const arrayWithIndicator = data.map((item) => ({ ...item, indicator: getIndicator(item), })); + setAllData([...comparePurchaseUrgency(arrayWithIndicator)]); setDisplayData([...comparePurchaseUrgency(arrayWithIndicator)]); }, [data]); @@ -48,7 +23,7 @@ export function List({ data, listPath }) { <> From f749273cf129b5c3ce573e5c127baa9917e1a6b6 Mon Sep 17 00:00:00 2001 From: wyna Date: Thu, 19 Sep 2024 11:57:23 +0200 Subject: [PATCH 4/6] extracted daysSinceLastPurchase logic --- src/api/firebase.js | 10 ++-------- src/utils/helpers.js | 11 ++++++----- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/api/firebase.js b/src/api/firebase.js index 8820133..e3ca21a 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -13,7 +13,7 @@ import { db } from './config'; import { getFutureDate } from '../utils'; import toast from 'react-hot-toast'; import { calculateEstimate } from '@the-collab-lab/shopping-list-utils'; -import { getDaysBetweenDates } from '../utils/dates'; +import { getDaysSinceLastPurchase } from '../utils/helpers'; /** * A custom hook that subscribes to the user's shopping lists in our Firestore * database and returns new data whenever the lists change. @@ -198,14 +198,8 @@ export async function updateItem(listPath, checked, itemData) { const today = new Date(); const currentTotalPurchases = itemData.totalPurchases; const currentDayInterval = itemData.dayInterval; - const dateLastPurchasedJavaScriptObject = itemData.dateLastPurchased - ? itemData.dateLastPurchased.toDate() - : itemData.dateCreated.toDate(); - const daysSinceLastPurchase = getDaysBetweenDates( - today, - dateLastPurchasedJavaScriptObject, - ); + const daysSinceLastPurchase = getDaysSinceLastPurchase(itemData); const estimate = calculateEstimate( currentDayInterval, daysSinceLastPurchase, diff --git a/src/utils/helpers.js b/src/utils/helpers.js index a2364aa..1b3a549 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,14 +1,15 @@ import { getDaysBetweenDates } from './dates'; -export function getIndicator(item) { +export function getDaysSinceLastPurchase(item) { const dateLastPurchasedJavaScriptObject = item.dateLastPurchased ? item.dateLastPurchased.toDate() : item.dateCreated.toDate(); - const daysSinceLastPurchase = getDaysBetweenDates( - new Date(), - dateLastPurchasedJavaScriptObject, - ); + return getDaysBetweenDates(new Date(), dateLastPurchasedJavaScriptObject); +} + +export function getIndicator(item) { + const daysSinceLastPurchase = getDaysSinceLastPurchase(item); const daysUntilNextPurchase = getDaysBetweenDates( item.dateNextPurchased.toDate(), From 673ed4ac2eed327cb6c9be60a9ff58a09a972d2f Mon Sep 17 00:00:00 2001 From: Jonathan O'Riordan Date: Fri, 20 Sep 2024 16:10:01 +0100 Subject: [PATCH 5/6] Fixed overdue purchase issus and added variable in useEffect --- src/utils/helpers.js | 2 +- src/views/List.jsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 1b3a549..d66c6f1 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -18,7 +18,7 @@ export function getIndicator(item) { if (daysSinceLastPurchase > 60) { return 'Inactive'; - } else if (daysSinceLastPurchase > 30 && daysSinceLastPurchase < 60) { + } else if (daysUntilNextPurchase < 0 && daysSinceLastPurchase < 60) { return 'Overdue'; } else if (daysUntilNextPurchase <= 7) { return 'Soon'; diff --git a/src/views/List.jsx b/src/views/List.jsx index 6e3804d..3a63ed1 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -15,8 +15,9 @@ export function List({ data, listPath }) { ...item, indicator: getIndicator(item), })); - setAllData([...comparePurchaseUrgency(arrayWithIndicator)]); - setDisplayData([...comparePurchaseUrgency(arrayWithIndicator)]); + const urgencyData = [...comparePurchaseUrgency(arrayWithIndicator)]; + setAllData(urgencyData); + setDisplayData(urgencyData); }, [data]); return ( From 31235471cc95ffec29370131de703883858e5548 Mon Sep 17 00:00:00 2001 From: Jonathan O'Riordan Date: Sun, 22 Sep 2024 09:26:05 +0100 Subject: [PATCH 6/6] Fix: conflicts resolved --- src/api/firebase.js | 16 ++++++++++------ src/components/ListItem.jsx | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/api/firebase.js b/src/api/firebase.js index e3ca21a..02fa26b 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -7,6 +7,8 @@ import { onSnapshot, updateDoc, addDoc, + Timestamp, + deleteDoc, } from 'firebase/firestore'; import { useEffect, useState } from 'react'; import { db } from './config'; @@ -225,12 +227,14 @@ export async function updateItem(listPath, checked, itemData) { } } -export async function deleteItem() { - /** - * TODO: Fill this out so that it uses the correct Firestore function - * to delete an existing item. You'll need to figure out what arguments - * this function must accept! - */ +export async function deleteItem(listPath, id) { + const listCollectionRef = collection(db, listPath, 'items'); + const itemRef = doc(listCollectionRef, id); + try { + await deleteDoc(itemRef); + } catch (error) { + console.error('Error deleting your item', error); + } } export function comparePurchaseUrgency(arr) { diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index 82ed5ba..a528b6f 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -1,7 +1,8 @@ import './ListItem.css'; -import { updateItem } from '../api'; +import { updateItem, deleteItem } from '../api'; import { useEffect } from 'react'; import { ONE_DAY_IN_MILLISECONDS } from '../utils/dates'; +import toast from 'react-hot-toast'; export function ListItem({ listPath, @@ -27,6 +28,16 @@ export function ListItem({ }); }; + const handleDelete = async () => { + const confirm = window.confirm(`are you sure you want to delete ${name}?`); + if (confirm) { + await deleteItem(listPath, id); + toast.success(`${name} was deleted from the list`); + } else { + toast.error('Deletion canceled'); + } + }; + useEffect(() => { const today = new Date().getTime(); const datePurchasedInMillis = dateLastPurchased?.toMillis(); @@ -54,6 +65,9 @@ export function ListItem({ {/* Add CSS to dynamically change bg-color for badges? */}

{indicator}

+ ); }