Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #13: Sort Items by Purchase Urgency #30

Merged
merged 8 commits into from
Sep 21, 2024
6 changes: 3 additions & 3 deletions 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, getDaysBetweenDates } from '../utils';
import { getFutureDate, calculateDaysDifferenceFromNow } from '../utils';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice update from office hours! 👍

import { calculateEstimate } from '@the-collab-lab/shopping-list-utils';
/**
* A custom hook that subscribes to the user's shopping lists in our Firestore
Expand Down Expand Up @@ -210,9 +210,9 @@ export async function updateItem(
let daysSinceLastPurchase;

if (dateLastPurchased) {
daysSinceLastPurchase = getDaysBetweenDates(dateLastPurchased);
daysSinceLastPurchase = calculateDaysDifferenceFromNow(dateLastPurchased);
} else {
daysSinceLastPurchase = getDaysBetweenDates(dateCreated);
daysSinceLastPurchase = calculateDaysDifferenceFromNow(dateCreated);
}

// Calculate days until next purchase
Expand Down
41 changes: 26 additions & 15 deletions src/components/ListItem.css
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
.ListItem {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 8px;
border-bottom: 1px solid var(--color-border);
font-size: 1.2em;
td {
border-bottom: 1px solid whitesmoke;
}

.ListItem-checkbox {
accent-color: var(--color-accent);
th {
background-color: #3e27ed;
border-bottom: 2px solid #ddd;
}

.ListItem-label {
margin-left: 0.2em;
.duesoon {
color: orange;
font-weight: bold;
}

.item-name {
flex-grow: 1;
margin-right: 10px;
.duekindofsoon {
color: yellow;
font-weight: bold;
}

.notduesoon {
color: green;
font-weight: bold;
}

.nolongeractive {
color: gray;
font-weight: bold;
}

.overdue {
color: red;
font-weight: bold;
}
41 changes: 26 additions & 15 deletions src/components/ListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function ListItem({
dateLastPurchased,
purchaseInterval,
dateCreated,
sortCriteria,
setMessage,
}) {
const [purchased, setPurchased] = useToggle(false);
Expand Down Expand Up @@ -60,10 +61,11 @@ export function ListItem({
}
}
};



// handleDelete Function
const handleDelete = async () => {
const deleteConfirm = window.confirm(
const deleteConfirm = window.confirm(
`Are you sure you want to delete ${name}?`,
);

Expand All @@ -77,23 +79,32 @@ export function ListItem({
}
};

const urgencyClass = sortCriteria.tag.toLowerCase().replace(/\s/g, '');
dterceroparker marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
<li className="ListItem">
<div className="item-name">{name}</div>
<Toggle
toggle={handleToggle}
on={purchased}
name={name}
isDisabled={isDisabled}
dateLastPurchased={dateLastPurchased}
/>

{dateLastPurchased ? dateLastPurchased.toDate().toLocaleString() : ''}
<button onClick={handleDelete} aria-label={`Delete ${name}`}>
<tr className="ListItem">
<td>{name}
<button onClick={handleDelete} aria-label={`Delete ${name}`}>
Delete
</button>
</li>
</td>

<td>
<Toggle
toggle={handleToggle}
on={purchased}
name={name}
isDisabled={isDisabled}
dateLastPurchased={dateLastPurchased}
/>
</td>
<td>
{dateLastPurchased ? dateLastPurchased.toDate().toLocaleString() : ''}
</td>
<td className={urgencyClass}>{sortCriteria.tag}</td>
</tr>

</>
);
}
68 changes: 65 additions & 3 deletions src/utils/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,71 @@ const ONE_DAY_IN_MILLISECONDS = 86400000;
export function getFutureDate(offset) {
return new Date(Date.now() + offset * ONE_DAY_IN_MILLISECONDS);
}
export function getDaysBetweenDates(previousPurchaseDate) {
const pastDate = previousPurchaseDate.toDate();
export function calculateDaysDifferenceFromNow(dateToCompare) {
const comparisonDate = dateToCompare.toDate();
const presentDate = new Date();
const diffInMilliseconds = presentDate.getTime() - pastDate.getTime();
const diffInMilliseconds = presentDate.getTime() - comparisonDate.getTime();
return Math.round(diffInMilliseconds / ONE_DAY_IN_MILLISECONDS);
}

export function comparePurchaseUrgency(list) {
const inactive = [];
const overdue = [];
const future = [];
//iterate through the list and categorize each item
list.forEach((item) => {
//positive numbers represent the past, negative numbers represent the future
const days = calculateDaysDifferenceFromNow(item.dateNextPurchased);
if (days >= 60) {
/*
-If sixty or more days have passed since the last purchase, we consider the item inactive.
-We flip the days to negative* to represent inactivity because inactive items should be sorted in reverse order from overdue and future items.
dterceroparker marked this conversation as resolved.
Show resolved Hide resolved
-For instance, an item that is 62 days overdue will be placed below an item that is 61 days overdue. It is less relevant to the user because more time has elapsed since they engaged with it.
*/
item.sortCriteria = {
tag: 'No longer active',
daysUntilNextPurchase: -days, // * flip the days to negative
};
inactive.push(item);
} else if (days < 60 && days > 0) {
item.sortCriteria = { tag: 'Overdue', daysUntilNextPurchase: days };
overdue.push(item);
} else if (days <= 0 && days >= -7) {
item.sortCriteria = { tag: 'Due soon', daysUntilNextPurchase: days };
future.push(item);
} else if (days < -7 && days >= -30) {
item.sortCriteria = {
tag: 'Due kind of soon',
daysUntilNextPurchase: days,
};
future.push(item);
} else if (days < -30) {
item.sortCriteria = { tag: 'Not due soon', daysUntilNextPurchase: days };
future.push(item);
}
});
//function to sort lists by days until next purchase and alphabetically if days are equal
const sortList = (list) => {
const sortedList = [...list].sort((a, b) => {
if (
a.sortCriteria.daysUntilNextPurchase ===
b.sortCriteria.daysUntilNextPurchase
) {
//sorts alphabetically if days are the same
return a.name.localeCompare(b.name);
}
return (
//sort by days until next purchase
b.sortCriteria.daysUntilNextPurchase -
a.sortCriteria.daysUntilNextPurchase
);
});
return sortedList;
};

const sortedOverdue = sortList(overdue);
const sortedFuture = sortList(future);
const sortedInactive = sortList(inactive);

return sortedOverdue.concat(sortedFuture).concat(sortedInactive);
dterceroparker marked this conversation as resolved.
Show resolved Hide resolved
}
40 changes: 25 additions & 15 deletions src/views/List.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { ListItem } from '../components';
import { NavLink } from 'react-router-dom';
import { comparePurchaseUrgency } from '../utils/dates.js';

export function List({ data, listPath }) {
const [searchInput, setSearchInput] = useState('');
Expand All @@ -15,7 +16,9 @@ export function List({ data, listPath }) {
setSearchInput('');
};

const filterList = data.filter((item) => {
const sortedByUrgency = comparePurchaseUrgency(data);

const filterList = sortedByUrgency.filter((item) => {
return searchInput
? item.name.toLowerCase().includes(searchInput.toLowerCase())
: item;
Expand Down Expand Up @@ -60,10 +63,18 @@ export function List({ data, listPath }) {
</button>
)}
</div>
<ul>
{filterList.length ? (
filterList.map((item) => {
return (
{filterList.length ? (
<table>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Buy Now</th>
<th scope="col">Last Purchase Date</th>
<th scope="col">Urgency</th>
</tr>
</thead>
<tbody>
{filterList.map((item) => (
<ListItem
key={item.id}
name={item.name}
Expand All @@ -73,19 +84,18 @@ export function List({ data, listPath }) {
dateLastPurchased={item.dateLastPurchased}
purchaseInterval={item.purchaseInterval}
dateCreated={item.dateCreated}
setMessage={setMessage}
setMessage={setMessage}
sortCriteria={item.sortCriteria}
/>
);
})
) : (
<li>
{' '}
No items found! <NavLink to="/manage-list"> Add item</NavLink>
</li>
)}
))}
</tbody>
</table>
) : (
<p>No items to display</p>
)}
<br />
<span>{message}</span>
</ul>

</>
);
}