Skip to content

Commit

Permalink
Merge pull request #37 from the-collab-lab/ec-hm-issue-13
Browse files Browse the repository at this point in the history
13. As a user, I want to view a list of my shopping list items in order of how soon I am likely to need to buy each of them again so that it’s clear what I need to buy soon
  • Loading branch information
3campos authored Mar 14, 2024
2 parents e69acd4 + dde5e5a commit 0cb000d
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 22 deletions.
57 changes: 56 additions & 1 deletion src/api/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'firebase/firestore';
import { useEffect, useState } from 'react';
import { db } from './config';
import { getFutureDate } from '../utils';
import { getFutureDate, getDifferenceBetweenDates, todaysDate } from '../utils';

/**
* A custom hook that subscribes to the user's shopping lists in our Firestore
Expand Down Expand Up @@ -208,3 +208,58 @@ export async function deleteItem(listPath, itemId) {
const itemDocRef = doc(listCollectionRef, itemId);
return deleteDoc(itemDocRef);
}

/**
* Compare and sort list items by time urgency and alphabetical order
* @param {array} array The list of items to sort
*/
export function comparePurchaseUrgency(array) {
return array.sort((a, b) => {
// getDifferenceBetweenDates gets the average number of days between the next purchase date and today's date for each shopping item
const dateA = Math.floor(
getDifferenceBetweenDates(a.dateNextPurchased.toDate(), todaysDate),
);
const dateB = Math.floor(
getDifferenceBetweenDates(b.dateNextPurchased.toDate(), todaysDate),
);

const itemA = a.name.toLowerCase();

const itemB = b.name.toLowerCase();

// get the average number of days between today's date and the date last purchased or the date created if
// dateLastPurchased does not exist yet
const daysSinceLastPurchaseA = Math.floor(
getDifferenceBetweenDates(
todaysDate,
a.dateLastPurchased
? a.dateLastPurchased.toDate()
: a.dateCreated.toDate(),
),
);

const daysSinceLastPurchaseB = Math.floor(
getDifferenceBetweenDates(
todaysDate,
b.dateLastPurchased
? b.dateLastPurchased.toDate()
: b.dateCreated.toDate(),
),
);

// sort by value of days since last purchased
if (daysSinceLastPurchaseA >= 60 && daysSinceLastPurchaseB < 60) {
return 1;
} else if (daysSinceLastPurchaseA < 60 && daysSinceLastPurchaseB >= 60) {
return -1;
}

// if dates are not equal sort by difference of dates
if (dateA !== dateB) {
return dateA - dateB;
}

// if dates are equal sort by character value
return itemA.localeCompare(itemB);
});
}
49 changes: 35 additions & 14 deletions src/components/ListItem.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getDifferenceBetweenDates, todaysDate, subtractDates } from '../utils';
import { colorPicker, calculateUrgency } from '../utils/helpers';
import { updateItem, deleteItem } from '../api';
import { subtractDates } from '../utils';
import { Timestamp } from 'firebase/firestore';
import { calculateEstimate } from '@the-collab-lab/shopping-list-utils';
import './ListItem.css';
Expand All @@ -13,36 +14,56 @@ export function ListItem({
totalPurchases,
dateCreated,
}) {
const todaysDate = Timestamp.now();

// if dateLastPurchased is true subtract it from dateNextPurchased, else subtract dateCreated from dateNextPurchased to get the estimated number of days till next purchase
const previousEstimate = Math.ceil(
(dateNextPurchased.toDate() -
(dateLastPurchased ? dateLastPurchased.toDate() : dateCreated.toDate())) /
(24 * 60 * 60 * 1000),
getDifferenceBetweenDates(
dateNextPurchased.toDate(),
dateLastPurchased ? dateLastPurchased.toDate() : dateCreated.toDate(),
),
);

// if dateLastPurchased is true subtract it from todaysDate, else subtract dateCreated from todaysDate to get the number of days since the last transaction
const daysSinceLastTransaction = Math.floor(
(todaysDate.toDate() -
(dateLastPurchased ? dateLastPurchased.toDate() : dateCreated.toDate())) /
(24 * 60 * 60 * 1000),
const daysSinceLastPurchase = Math.floor(
getDifferenceBetweenDates(
todaysDate,
dateLastPurchased ? dateLastPurchased.toDate() : dateCreated.toDate(),
),
);

const daysTillNextPurchase = Math.floor(
Math.floor(
getDifferenceBetweenDates(dateNextPurchased.toDate(), todaysDate),
),
);

const nextPurchaseEstimate = calculateEstimate(
previousEstimate,
daysSinceLastTransaction,
daysSinceLastPurchase,
totalPurchases,
);

const handleChecked = async () => {
const todaysDateTimestamp = Timestamp.now();
try {
await updateItem(listPath, id, todaysDate, nextPurchaseEstimate);
await updateItem(listPath, id, todaysDateTimestamp, nextPurchaseEstimate);
} catch (err) {
console.error(err);
}
};

const isChecked = () => {
if (dateLastPurchased) {
return (
getDifferenceBetweenDates(todaysDate, dateLastPurchased.toDate()) < 1
);
}

return false;
};

let urgency = calculateUrgency(daysTillNextPurchase, daysSinceLastPurchase);
let textColor = colorPicker(urgency);

const handleDelete = async () => {
try {
if (window.confirm('Are you sure you want to delete this item?')) {
Expand All @@ -58,13 +79,13 @@ export function ListItem({
return (
<li className="ListItem">
<label>
{name}
{name} <span style={{ color: textColor }}>{urgency}</span>
<input
type="checkbox"
id={`checkbox-${id}`} // Unique identifier
name={name}
onChange={handleChecked}
checked={subtractDates(todaysDate, dateLastPurchased)}
checked={isChecked()}
></input>
</label>
<button onClick={handleDelete} className="delete-button">
Expand Down
10 changes: 4 additions & 6 deletions src/utils/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ export function getFutureDate(offset) {
return new Date(Date.now() + offset * ONE_DAY_IN_MILLISECONDS);
}

export function subtractDates(todaysDate, dateLastPurchased) {
if (dateLastPurchased) {
return (todaysDate - dateLastPurchased) * 1000 < ONE_DAY_IN_MILLISECONDS;
}

return false;
export function getDifferenceBetweenDates(date1, date2) {
return (date1 - date2) / (24 * 60 * 60 * 1000);
}

export const todaysDate = new Date();
46 changes: 46 additions & 0 deletions src/utils/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export function colorPicker(text) {
let color;
switch (text) {
case 'overdue':
color = 'red';
break;
case 'soon':
color = 'orange';
break;
case 'kind of soon':
color = 'yellow';
break;
case 'not so soon':
color = 'green';
break;
case 'inactive':
color = 'grey';
break;
default:
color = 'black';
}
return color;
}

export function calculateUrgency(daysTillNextPurchase, daysSinceLastPurchase) {
let urgency;

if (
daysTillNextPurchase < 0 &&
daysSinceLastPurchase > daysTillNextPurchase
) {
urgency = 'overdue';
} else if (daysTillNextPurchase <= 7) {
urgency = 'soon';
} else if (daysTillNextPurchase < 30) {
urgency = 'kind of soon';
} else if (daysTillNextPurchase >= 30) {
urgency = 'not so soon';
}

if (daysSinceLastPurchase >= 60) {
urgency = 'inactive';
}

return urgency;
}
5 changes: 4 additions & 1 deletion src/views/List.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from 'react-router-dom';
import { useState } from 'react';
import { ListItem } from '../components/ListItem';
import { comparePurchaseUrgency } from '../api/firebase';

export function List({ data, listPath, loading }) {
const [search, setSearch] = useState('');
Expand All @@ -22,6 +23,8 @@ export function List({ data, listPath, loading }) {
item.name.toLowerCase().includes(search.toLowerCase()),
);

const sortedItems = comparePurchaseUrgency(filteredData);

return (
<>
<p>
Expand All @@ -45,7 +48,7 @@ export function List({ data, listPath, loading }) {
</button>
</form>
<ul>
{filteredData.map((item) => (
{sortedItems.map((item) => (
<ListItem
key={item.id}
name={item.name}
Expand Down

0 comments on commit 0cb000d

Please sign in to comment.