diff --git a/package-lock.json b/package-lock.json index 898f3f5..b3bd10f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "smart-shopping-list-next", "dependencies": { + "@the-collab-lab/shopping-list-utils": "^2.2.0", "firebase": "^10.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3994,6 +3995,15 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@the-collab-lab/shopping-list-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@the-collab-lab/shopping-list-utils/-/shopping-list-utils-2.2.0.tgz", + "integrity": "sha512-nEN1z/SEOIWO+8JWIgPDNUkrXmXqNomSh5aiZDNdiaUH/JYqyNeXmEOldtMqAfLicSRiFkapcAIlrUUnPzNaog==", + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", diff --git a/package.json b/package.json index c4a636a..1ac60de 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "npm": ">=8.19.0" }, "dependencies": { + "@the-collab-lab/shopping-list-utils": "^2.2.0", "firebase": "^10.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/api/firebase.js b/src/api/firebase.js index 360664d..41b07b1 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -11,7 +11,9 @@ import { } from 'firebase/firestore'; import { useEffect, useState } from 'react'; import { db } from './config'; -import { getFutureDate } from '../utils'; +import { getFutureDate, getDaysBetweenDates } from '../utils'; +import { calculateEstimate } from '@the-collab-lab/shopping-list-utils'; +// calculateEstimate takes 3 arguments - previousEstimate (number), daysSinceLastPurchase (number), totalPurchases (number) /** * A custom hook that subscribes to the user's shopping lists in our Firestore @@ -179,7 +181,7 @@ export async function addItem(listPath, { itemName, daysUntilNextPurchase }) { dateCreated: new Date(), // NOTE: This is null because the item has just been created. // We'll use updateItem to put a Date here when the item is purchased! - dateLastPurchased: null, + dateLastPurchased: [new Date()], dateNextPurchased: getFutureDate(daysUntilNextPurchase), name: itemName, totalPurchases: 0, @@ -187,13 +189,54 @@ export async function addItem(listPath, { itemName, daysUntilNextPurchase }) { return newItem; } +//Helper function to handle estimation logic +async function handleCalculateEstimate(listRef) { + const selectedItem = await getDoc(listRef); + + //access array of dateLastPurchased for the selected item + const selectedLastPurchase = selectedItem.data().dateLastPurchased; + + //retrieve the most recent purchase date from the array and convert to a date + const lastPurchased = + selectedLastPurchase[selectedLastPurchase.length - 1].toDate(); + + //calculates the previously estimated days until next purchase + const previousEstimate = getDaysBetweenDates( + lastPurchased, + selectedItem.data().dateNextPurchased.toDate(), + ); + const daysSincePrevPurchase = getDaysBetweenDates(lastPurchased, new Date()); + + //calculates a new estimate based on the previous estimate, days since last purchased, and total purchases + const newEstimate = calculateEstimate( + previousEstimate, + daysSincePrevPurchase, + selectedItem.data().totalPurchases, + ); + + return { newEstimate, selectedLastPurchase }; +} + export async function updateItem(listPath, itemID, isChecked) { const listRef = doc(db, listPath, 'items', itemID); - await updateDoc(listRef, { - dateLastPurchased: isChecked ? new Date() : null, - totalPurchases: isChecked ? increment(1) : increment(-1), - }); + const { newEstimate, selectedLastPurchase } = + await handleCalculateEstimate(listRef); + + //checks to see if checkbox is checked in front end, updates selected item + if (isChecked) { + await updateDoc(listRef, { + dateLastPurchased: [...selectedLastPurchase, new Date()], + dateNextPurchased: getFutureDate(newEstimate), + totalPurchases: increment(1), + }); + } else { + selectedLastPurchase.pop(); + await updateDoc(listRef, { + dateLastPurchased: [...selectedLastPurchase], + totalPurchases: increment(-1), + }); + } } export async function deleteItem() { diff --git a/src/utils/dates.js b/src/utils/dates.js index dc66d95..ca68674 100644 --- a/src/utils/dates.js +++ b/src/utils/dates.js @@ -10,3 +10,15 @@ export const ONE_DAY_IN_MILLISECONDS = 86400000; export function getFutureDate(offset) { return new Date(Date.now() + offset * ONE_DAY_IN_MILLISECONDS); } + +// This function will enable us to convert JS dates into Firebase-friendly timestamps, calculate the difference, and then return the difference between them (as a number). + +export function getDaysBetweenDates(firstDate, secondDate) { + // Takes two JS Date objects, converts to MS, calculates MS difference, then converts to days as a number (rounded up or down). + const firstTime = firstDate.getTime(); + const secondTime = secondDate.getTime(); + const secondsBetween = secondTime - firstTime; + const daysBetween = secondsBetween / ONE_DAY_IN_MILLISECONDS; + // Round number here + return daysBetween.toPrecision(3); +}