Skip to content

Commit

Permalink
Issue #13: Sort item list by when to buy urgency (#35)
Browse files Browse the repository at this point in the history
* created comparePurchaseUrgency function

Co-authored-by: Falak  <zahrafalak@users.noreply.github.com>

* added urgency status to list item check box

Co-authored-by: Falak  <zahrafalak@users.noreply.github.com>

* cleaned up pseudo code from firebase.ts and implemented overdue logic for buying status

Co-authored-by: Falak  <zahrafalak@users.noreply.github.com>

* 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 <zahrafalak@users.noreply.github.com>
Co-authored-by: Falak Zahra <falak.zahra16@gmail.com>
  • Loading branch information
3 people authored Sep 22, 2024
1 parent a2bcb7f commit f5cde9a
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 15 deletions.
46 changes: 37 additions & 9 deletions src/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ The `firebase.js` file contains all of the functions that communicate with your

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

| Parameter | Type | Description |
| --------- | -------- | ---------------------------------------------------- |
| `user` | `object` | The user object returned by Firebase Authentication. |
| Parameter | Type | Description |
| ------------ | -------- | ----------------------------------------------------------------------- |
| `user.name` | `string` | The name of the user returned in an object by Firebase Authentication. |
| `user.email` | `string` | The email of the user returned in an object by Firebase Authentication. |
| `user.uid` | `string` | The uid of the user returned in an object by Firebase Authentication. |

### `createList`

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

#### Note

**`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.
**`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.

### `updateItem`

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

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

### `deleteItem`

🚧 To be completed! 🚧

### `comparePurchaseUrgency`

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.

| Parameter | Type | Description |
| ------------------------- | ----------------------------- | --------------------------------------------------------------------------------------- |
| `item1` | `object` | The first item being compared to the second item. |
| `item2` | `object` | The second item being compared to the first item. |
| `item1.id` | `string` | A unique identifier of the item generated by firebase when it is added to the database. |
| `item1.name` | `string` | The name of the item. |
| `item1.dateLastPurchased` | `FirebaseTimestamp` or `null` | The date the item was last purchased, `null` when item first added. |
| `item1.dateNextPurchased` | `FirebaseTimestamp` | The date the item is predicted to be purchased by next. |
| `item1.totalPurchases` | `number` | The total number of times the item has been purchased. |
| `item1.dateCreated` | `FirebaseTimestamp` | The date the item was added to the user's list. |
| `item2.id` | `string` | A unique identifier of the item generated by firebase when it is added to the database. |
| `item2.name` | `string` | The name of the item. |
| `item2.dateLastPurchased` | `FirebaseTimestamp` or `null` | The date the item was last purchased, `null` when item first added. |
| `item2.dateNextPurchased` | `FirebaseTimestamp` | The date the item is predicted to be purchased by next. |
| `item2.totalPurchases` | `number` | The total number of times the item has been purchased. |
| `item2.dateCreated` | `FirebaseTimestamp` | The date the item was added to the user's list. |

## The shape of the the data

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.
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.

| Property | Type | Description |
| ------------------- | -------- | ----------------------------------------------------------------- |
Expand Down
58 changes: 58 additions & 0 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,61 @@ export async function deleteItem(listPath: string, item: ListItem) {
}
}
}

export function comparePurchaseUrgency(
item1: ListItem,
item2: ListItem,
): -1 | 0 | 1 {
const currentDate = new Date();

// Check for last time item1 had any activity
const daysSinceItem1LastActivity = item1.dateLastPurchased
? getDaysBetweenDates(currentDate, item1.dateLastPurchased.toDate())
: getDaysBetweenDates(currentDate, item1.dateCreated.toDate());

// De-prioritize an item if its had no activity for more than 60 days
if (daysSinceItem1LastActivity >= 60) {
return 1;
}

// Prioritize item1 if current date is past next purchase date and not inactive yet
if (currentDate > item1.dateNextPurchased.toDate()) {
return -1;
}

// Check for last time item2 had any activity
const daysSinceItem2LastActivity = item2.dateLastPurchased
? getDaysBetweenDates(currentDate, item2.dateLastPurchased.toDate())
: getDaysBetweenDates(currentDate, item2.dateCreated.toDate());

// Prioritize item2 if current date is past next purchase date and not inactive yet
if (
currentDate > item2.dateNextPurchased.toDate() &&
daysSinceItem2LastActivity < 60
) {
return 1;
}

const item1DaysUntilNextPurchased = getDaysBetweenDates(
currentDate,
item1.dateNextPurchased.toDate(),
);
const item2DaysUntilNextPurchased = getDaysBetweenDates(
currentDate,
item2.dateNextPurchased.toDate(),
);

//Compare days until next purchase for item1 and item2
//if item1 needs to be purchased sooner, prioritize item1 over item2
if (item1DaysUntilNextPurchased < item2DaysUntilNextPurchased) {
return -1;
}

//if item2 needs to be purchased sooner, prioritize item2 over item1
if (item1DaysUntilNextPurchased > item2DaysUntilNextPurchased) {
return 1;
}

//if both items have the same sort order, we sort alphabetically
return item1.name.toLowerCase() < item2.name.toLowerCase() ? -1 : 1;
}
1 change: 1 addition & 0 deletions src/components/ListItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
display: flex;
flex-direction: row;
font-size: 1.2em;
justify-content: space-between;
}

.ListItem-checkbox {
Expand Down
40 changes: 38 additions & 2 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "./ListItem.css";
import { updateItem, deleteItem, ListItem } from "../api";
import { useState } from "react";
import toast from "react-hot-toast";
import { moreThan24HoursPassed } from "../utils";
import { moreThan24HoursPassed, getDaysBetweenDates } from "../utils";

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

const getUrgencyStatus = (item: ListItem) => {
const currentDate = new Date();

const daysUntilNextPurchase = getDaysBetweenDates(
currentDate,
item.dateNextPurchased.toDate(),
);

const daysSinceItemLastActivity = item.dateLastPurchased
? getDaysBetweenDates(currentDate, item.dateLastPurchased.toDate())
: getDaysBetweenDates(currentDate, item.dateCreated.toDate());

if (daysSinceItemLastActivity >= 60) {
return "inactive";
}

if (currentDate > item.dateNextPurchased.toDate()) {
return "overdue";
}

if (daysUntilNextPurchase >= 30) {
return "not soon";
}

if (daysUntilNextPurchase <= 7) {
return "soon";
}

return "kind of soon";
};

const handleCheckChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const newCheckedState = e.target.checked;

Expand Down Expand Up @@ -84,7 +115,12 @@ export function ListItemCheckBox({ item, listPath }: Props) {
/>
{item.name}
</label>
<button onClick={() => deleteItemHandler()}>Delete Item</button>

<span>
{getUrgencyStatus(item)}

<button onClick={() => deleteItemHandler()}>Delete Item</button>
</span>
</div>
);
}
10 changes: 6 additions & 4 deletions src/views/authenticated/List.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useMemo } from "react";
import { ListItemCheckBox } from "../../components/ListItem";
import { FilterListInput } from "../../components/FilterListInput";
import { ListItem } from "../../api";
import { ListItem, comparePurchaseUrgency } from "../../api";
import { useNavigate } from "react-router-dom";

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

const filteredListItems = useMemo(() => {
return unfilteredListItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
return unfilteredListItems
.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
)
.sort(comparePurchaseUrgency);
}, [searchTerm, unfilteredListItems]);

// Early return if the list is empty
Expand Down

0 comments on commit f5cde9a

Please sign in to comment.