Skip to content

Commit f5cde9a

Browse files
bbland1zahrafalak
andauthored
Issue #13: Sort item list by when to buy urgency (#35)
* created comparePurchaseUrgency function Co-authored-by: Falak <[email protected]> * added urgency status to list item check box Co-authored-by: Falak <[email protected]> * cleaned up pseudo code from firebase.ts and implemented overdue logic for buying status Co-authored-by: Falak <[email protected]> * update the shopping list api readme to include the new comparePurchaseUrgency info * update readme to be more descriptive of the objects used as parameters * removed buy from status for consistency, moved some grouping of logic * cleaning up with guard clauses for the status checks added an inactive check for item2 * Small spellckeck correction in readme * updated api readme to reflect the two daysSinceLastActivity --------- Co-authored-by: Falak <[email protected]> Co-authored-by: Falak Zahra <[email protected]>
1 parent a2bcb7f commit f5cde9a

File tree

5 files changed

+140
-15
lines changed

5 files changed

+140
-15
lines changed

src/api/README.md

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ The `firebase.js` file contains all of the functions that communicate with your
1010

1111
This function adds a new user to the database on their initial sign-in.
1212

13-
| Parameter | Type | Description |
14-
| --------- | -------- | ---------------------------------------------------- |
15-
| `user` | `object` | The user object returned by Firebase Authentication. |
13+
| Parameter | Type | Description |
14+
| ------------ | -------- | ----------------------------------------------------------------------- |
15+
| `user.name` | `string` | The name of the user returned in an object by Firebase Authentication. |
16+
| `user.email` | `string` | The email of the user returned in an object by Firebase Authentication. |
17+
| `user.uid` | `string` | The uid of the user returned in an object by Firebase Authentication. |
1618

1719
### `createList`
1820

@@ -46,24 +48,50 @@ This function takes user-provided data and uses it to create a new item in the F
4648

4749
#### Note
4850

49-
**`daysUntilNextPurchase` is not added to the item directly**. It is used alomngside the `getFutureDate` utility function to create a new _JavaScript Date_ that represents when we think the user will buy the item again.
51+
**`daysUntilNextPurchase` is not added to the item directly**. It is used alongside the `getFutureDate` utility function to create a new _JavaScript Date_ that represents when we think the user will buy the item again.
5052

5153
### `updateItem`
5254

5355
This function takes user-provided data and uses it to update an exiting item in the Firestore database.
5456

55-
| Parameter | Type | Description |
56-
| ---------- | -------- | --------------------------------------------------------------- |
57-
| `listPath` | `string` | The Firestore path of the list to which the item will be added. |
58-
| `item` | `string` | The item object. |
57+
| Parameter | Type | Description |
58+
| ------------------------ | ----------------------------- | --------------------------------------------------------------------------------------- |
59+
| `listPath` | `string` | The Firestore path of the list to which the item will be added. |
60+
| `item.id` | `string` | A unique identifier of the item generated by firebase when it is added to the database. |
61+
| `item.name` | `string` | The name of the item. |
62+
| `item.dateLastPurchased` | `FirebaseTimestamp` or `null` | The date the item was last purchased, `null` when item first added. |
63+
| `item.dateNextPurchased` | `FirebaseTimestamp` | The date the item is predicted to be purchased by next. |
64+
| `item.totalPurchases` | `number` | The total number of times the item has been purchased. |
65+
| `item.dateCreated` | `FirebaseTimestamp` | The date the item was added to the user's list. |
5966

6067
### `deleteItem`
6168

6269
🚧 To be completed! 🚧
6370

71+
### `comparePurchaseUrgency`
72+
73+
This function compares two item objects to determine their priority order for sorting. It evaluates `item1DaysSinceLastActivity` and `item2DaysSinceLastActivity` and uses both with `item1.dateNextPurchased` and `item2.dateNextPurchased` to establish the order urgency.
74+
75+
| Parameter | Type | Description |
76+
| ------------------------- | ----------------------------- | --------------------------------------------------------------------------------------- |
77+
| `item1` | `object` | The first item being compared to the second item. |
78+
| `item2` | `object` | The second item being compared to the first item. |
79+
| `item1.id` | `string` | A unique identifier of the item generated by firebase when it is added to the database. |
80+
| `item1.name` | `string` | The name of the item. |
81+
| `item1.dateLastPurchased` | `FirebaseTimestamp` or `null` | The date the item was last purchased, `null` when item first added. |
82+
| `item1.dateNextPurchased` | `FirebaseTimestamp` | The date the item is predicted to be purchased by next. |
83+
| `item1.totalPurchases` | `number` | The total number of times the item has been purchased. |
84+
| `item1.dateCreated` | `FirebaseTimestamp` | The date the item was added to the user's list. |
85+
| `item2.id` | `string` | A unique identifier of the item generated by firebase when it is added to the database. |
86+
| `item2.name` | `string` | The name of the item. |
87+
| `item2.dateLastPurchased` | `FirebaseTimestamp` or `null` | The date the item was last purchased, `null` when item first added. |
88+
| `item2.dateNextPurchased` | `FirebaseTimestamp` | The date the item is predicted to be purchased by next. |
89+
| `item2.totalPurchases` | `number` | The total number of times the item has been purchased. |
90+
| `item2.dateCreated` | `FirebaseTimestamp` | The date the item was added to the user's list. |
91+
6492
## The shape of the the data
6593

66-
When we request a shopping list, it is sent to us as an array of objects with a specific shape – a specific arrangeement of properties and the kinds of data those properties can hold. This table describes the shape of the items that go into the shopping list.
94+
When we request a shopping list, it is sent to us as an array of objects with a specific shape – a specific arrangement of properties and the kinds of data those properties can hold. This table describes the shape of the items that go into the shopping list.
6795

6896
| Property | Type | Description |
6997
| ------------------- | -------- | ----------------------------------------------------------------- |

src/api/firebase.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,61 @@ export async function deleteItem(listPath: string, item: ListItem) {
316316
}
317317
}
318318
}
319+
320+
export function comparePurchaseUrgency(
321+
item1: ListItem,
322+
item2: ListItem,
323+
): -1 | 0 | 1 {
324+
const currentDate = new Date();
325+
326+
// Check for last time item1 had any activity
327+
const daysSinceItem1LastActivity = item1.dateLastPurchased
328+
? getDaysBetweenDates(currentDate, item1.dateLastPurchased.toDate())
329+
: getDaysBetweenDates(currentDate, item1.dateCreated.toDate());
330+
331+
// De-prioritize an item if its had no activity for more than 60 days
332+
if (daysSinceItem1LastActivity >= 60) {
333+
return 1;
334+
}
335+
336+
// Prioritize item1 if current date is past next purchase date and not inactive yet
337+
if (currentDate > item1.dateNextPurchased.toDate()) {
338+
return -1;
339+
}
340+
341+
// Check for last time item2 had any activity
342+
const daysSinceItem2LastActivity = item2.dateLastPurchased
343+
? getDaysBetweenDates(currentDate, item2.dateLastPurchased.toDate())
344+
: getDaysBetweenDates(currentDate, item2.dateCreated.toDate());
345+
346+
// Prioritize item2 if current date is past next purchase date and not inactive yet
347+
if (
348+
currentDate > item2.dateNextPurchased.toDate() &&
349+
daysSinceItem2LastActivity < 60
350+
) {
351+
return 1;
352+
}
353+
354+
const item1DaysUntilNextPurchased = getDaysBetweenDates(
355+
currentDate,
356+
item1.dateNextPurchased.toDate(),
357+
);
358+
const item2DaysUntilNextPurchased = getDaysBetweenDates(
359+
currentDate,
360+
item2.dateNextPurchased.toDate(),
361+
);
362+
363+
//Compare days until next purchase for item1 and item2
364+
//if item1 needs to be purchased sooner, prioritize item1 over item2
365+
if (item1DaysUntilNextPurchased < item2DaysUntilNextPurchased) {
366+
return -1;
367+
}
368+
369+
//if item2 needs to be purchased sooner, prioritize item2 over item1
370+
if (item1DaysUntilNextPurchased > item2DaysUntilNextPurchased) {
371+
return 1;
372+
}
373+
374+
//if both items have the same sort order, we sort alphabetically
375+
return item1.name.toLowerCase() < item2.name.toLowerCase() ? -1 : 1;
376+
}

src/components/ListItem.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
display: flex;
44
flex-direction: row;
55
font-size: 1.2em;
6+
justify-content: space-between;
67
}
78

89
.ListItem-checkbox {

src/components/ListItem.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import "./ListItem.css";
22
import { updateItem, deleteItem, ListItem } from "../api";
33
import { useState } from "react";
44
import toast from "react-hot-toast";
5-
import { moreThan24HoursPassed } from "../utils";
5+
import { moreThan24HoursPassed, getDaysBetweenDates } from "../utils";
66

77
interface Props {
88
item: ListItem;
@@ -29,6 +29,37 @@ export function ListItemCheckBox({ item, listPath }: Props) {
2929
? !moreThan24HoursPassed(item.dateLastPurchased.toDate())
3030
: false;
3131

32+
const getUrgencyStatus = (item: ListItem) => {
33+
const currentDate = new Date();
34+
35+
const daysUntilNextPurchase = getDaysBetweenDates(
36+
currentDate,
37+
item.dateNextPurchased.toDate(),
38+
);
39+
40+
const daysSinceItemLastActivity = item.dateLastPurchased
41+
? getDaysBetweenDates(currentDate, item.dateLastPurchased.toDate())
42+
: getDaysBetweenDates(currentDate, item.dateCreated.toDate());
43+
44+
if (daysSinceItemLastActivity >= 60) {
45+
return "inactive";
46+
}
47+
48+
if (currentDate > item.dateNextPurchased.toDate()) {
49+
return "overdue";
50+
}
51+
52+
if (daysUntilNextPurchase >= 30) {
53+
return "not soon";
54+
}
55+
56+
if (daysUntilNextPurchase <= 7) {
57+
return "soon";
58+
}
59+
60+
return "kind of soon";
61+
};
62+
3263
const handleCheckChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
3364
const newCheckedState = e.target.checked;
3465

@@ -84,7 +115,12 @@ export function ListItemCheckBox({ item, listPath }: Props) {
84115
/>
85116
{item.name}
86117
</label>
87-
<button onClick={() => deleteItemHandler()}>Delete Item</button>
118+
119+
<span>
120+
{getUrgencyStatus(item)}
121+
122+
<button onClick={() => deleteItemHandler()}>Delete Item</button>
123+
</span>
88124
</div>
89125
);
90126
}

src/views/authenticated/List.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useMemo } from "react";
22
import { ListItemCheckBox } from "../../components/ListItem";
33
import { FilterListInput } from "../../components/FilterListInput";
4-
import { ListItem } from "../../api";
4+
import { ListItem, comparePurchaseUrgency } from "../../api";
55
import { useNavigate } from "react-router-dom";
66

77
interface Props {
@@ -14,9 +14,11 @@ export function List({ data: unfilteredListItems, listPath }: Props) {
1414
const [searchTerm, setSearchTerm] = useState<string>("");
1515

1616
const filteredListItems = useMemo(() => {
17-
return unfilteredListItems.filter((item) =>
18-
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
19-
);
17+
return unfilteredListItems
18+
.filter((item) =>
19+
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
20+
)
21+
.sort(comparePurchaseUrgency);
2022
}, [searchTerm, unfilteredListItems]);
2123

2224
// Early return if the list is empty

0 commit comments

Comments
 (0)