-
Notifications
You must be signed in to change notification settings - Fork 2
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
Sort items by urgency #57
Conversation
…te created; propose a function that will calculate days form passed in date to today; pass the latter into firebase
…m/the-collab-lab/tcl-75-smart-shopping-list into wc-db-view-shopping-list-in-order
@@ -15,7 +15,7 @@ | |||
"settings": { "react": { "version": "detect" } }, | |||
"rules": { | |||
"no-duplicate-imports": "warn", | |||
"no-unused-vars": "warn", | |||
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], | |||
"react/jsx-filename-extension": [1, { "allow": "as-needed" }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was my solution as I had to loop over sets and didn't know how to avoid this here:
const getClassName = (name) => {
const statusArray = Object.entries(urgency).find(([_, items]) => {
return Array.from(items).some((item) => item.name === name);
});
if (!statusArray) {
throw new Error(`Failed to get class name of ${name}`);
}
return statusArray[0];
};
Visit the preview URL for this PR (updated for commit c92e940): https://tcl-75-smart-shopping-list--pr57-sort-by-urgency-jwz2o8i8.web.app (expires Sat, 05 Oct 2024 14:08:38 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 1f1fd53c369e1fa31e15c310aa075b4e8f4f8dde |
@@ -221,3 +225,74 @@ export async function deleteItem(listPath, itemId) { | |||
throw new Error(`Failed updating item: ${error.message}`); | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
global urgency object will be populated with details and later set urgency state within List component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like leveraging context would be a great approach if the urgencyObject
needs to be accessible across different components. Could we create a context for this and remove it from this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, it was in firebase.js as per acceptance criteria, but the logic for useUrgency is inside hooks.js!
- Updated `getItemUrgency` to calculate item urgency based on the time until the next purchase date in addition to the last purchase or creation date. - If more than 60 days have passed since the last purchase or creation, the item is considered inactive and assigned an urgency of 100. - If fewer than 60 days have passed, urgency is determined by the number of days until the next estimated purchase date.
const sortByUrgency = (item, daysUntilNextPurchase) => { | ||
if (item.name.includes('sun')) { | ||
console.log(`${item.name} urgencyStatus ${daysUntilNextPurchase}`); | ||
} | ||
if (daysUntilNextPurchase < 0) { | ||
urgencyObject.overdue.add(item); | ||
return; | ||
} else if (daysUntilNextPurchase === 1000) { | ||
urgencyObject.inactive.add(item); | ||
return; | ||
} else if (daysUntilNextPurchase < 7) { | ||
urgencyObject.soon.add(item); | ||
return; | ||
} else if (daysUntilNextPurchase >= 7 && daysUntilNextPurchase < 30) { | ||
urgencyObject.kindOfSoon.add(item); | ||
return; | ||
} else if (daysUntilNextPurchase >= 30) { | ||
urgencyObject.notSoon.add(item); | ||
return; | ||
} else { | ||
throw new Error(`Failed to place [${item.name}]`); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function appears to be purely client-side and doesn't interact with Firebase. Could we move it to a utility file and add a test to ensure its logic is covered?
const getItemUrgency = (item) => { | ||
// get date the item was purchased or created | ||
const itemDate = getDateLastPurchasedOrDateCreated( | ||
item.dateLastPurchased, | ||
item.dateCreated, | ||
); | ||
// check how many days have passed since that date | ||
const daysToToday = getDaysFromDate(itemDate); | ||
|
||
// if more than 60 days have passed | ||
if (daysToToday >= 60) { | ||
// sort as inactive | ||
sortByUrgency(item, 1000); | ||
return 1000; | ||
} else { | ||
// sort by the amount of days until next purchase date | ||
const daysUntilNextPurchase = getDaysBetweenDates( | ||
new Date(), | ||
item.dateNextPurchased.toDate(), | ||
); | ||
if (item.name.includes('sun')) { | ||
console.log( | ||
`${item.name} days until next purchase is ${daysUntilNextPurchase}`, | ||
); | ||
} | ||
sortByUrgency(item, daysUntilNextPurchase); | ||
|
||
return daysUntilNextPurchase; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function appears to be purely client-side and doesn't interact with Firebase. Could we move it to a utility file and add a test to ensure its logic is covered?
export function comparePurchaseUrgency(item1, item2) { | ||
const item1UrgencyStatus = getItemUrgency(item1); | ||
const item2UrgencyStatus = getItemUrgency(item2); | ||
|
||
if (item1UrgencyStatus === item2UrgencyStatus) { | ||
console.log(`Sorting alphabetically: ${item1.name} vs ${item2.name}`); | ||
return item1.name.localeCompare(item2.name); | ||
} | ||
// otherwise sort in descending order | ||
return item1UrgencyStatus - item2UrgencyStatus; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function appears to be purely client-side and doesn't interact with Firebase. Could we move it to a utility file and add a test to ensure its logic is covered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add tests!
@@ -17,7 +17,7 @@ const calculateIsPurchased = (dateLastPurchased) => { | |||
return currentDate < oneDayLater; | |||
}; | |||
|
|||
export function ListItem({ item, listPath }) { | |||
export function ListItem({ item, listPath, className }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you're trying to do here! Could I suggest a cleaner approach? Instead of passing a className
, we should pass an urgencyStatus
prop. This way, the className
can be determined internally based on the urgencyStatus
, keeping the styling logic encapsulated within the ListItem
component. This approach is more semantic, makes the component more reusable, and keeps the parent component simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I am adding this as a change to useUrgency and use-urgency branch
<li className={`ListItem`}> | ||
<div className={`urgency-status ${className}`} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once you've passed the urgencyStatus
prop, you can use a switch statement like the one below to determine the appropriate class for rendering:
const getUrgencyClass = (urgencyStatus) => {
switch (urgencyStatus) {
case 'overdue':
return 'urgency-overdue';
case 'soon':
return 'urgency-soon';
case 'kindOfSoon':
return 'urgency-kind-of-soon';
case 'notSoon':
return 'urgency-not-soon';
case 'inactive':
return 'urgency-inactive';
default:
return '';
}
};
Then, in the JSX:
<li className="ListItem">
<div className={`urgency-status ${getUrgencyClass(urgencyStatus)}`} />
</li>
As a bonus, you could even make getUrgencyClass
into its own util to keep the code in this file cleaner!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I understand, do we just add 'urgency-' to the class with this function? Is is about camel case? Or would it return a ui piece in the future?
export const getDaysFromDate = (pastDate) => { | ||
const today = new Date(); | ||
const daysToToday = getDaysBetweenDates(pastDate, today); | ||
return daysToToday; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can further optimize this by simplifying the function to:
export const getDaysFromDate = (pastDate) => getDaysBetweenDates(pastDate, new Date());
export const getDateLastPurchasedOrDateCreated = ( | ||
dateLastPurchased, | ||
dateCreated, | ||
) => { | ||
const lastPurchaseDate = dateLastPurchased?.toDate(); | ||
return lastPurchaseDate ?? dateCreated.toDate(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can further optimize this by simplifying the function to:
export const getDateLastPurchasedOrDateCreated = (dateLastPurchased, dateCreated) => dateLastPurchased?.toDate() ?? dateCreated.toDate();
const [urgency, setUrgency] = useState(urgencyObject); | ||
const listName = listPath.slice(listPath.indexOf('/') + 1); | ||
|
||
useEffect(() => { | ||
setUrgency(urgencyObject); | ||
}, [urgencyObject]); | ||
|
||
const getClassName = (name) => { | ||
const statusArray = Object.entries(urgency).find(([_, items]) => { | ||
return Array.from(items).some((item) => item.name === name); | ||
}); | ||
if (!statusArray) { | ||
throw new Error(`Failed to get class name of ${name}`); | ||
} | ||
return statusArray[0]; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic will likely be replaced when we start passing the urgencyStatus
prop directly to the component, removing the need for managing urgency locally.
console.log('----'); | ||
console.log(`before filter: ${data.map((d) => d.name)}`); | ||
const filteredItems = data | ||
.filter((item) => | ||
item.name.toLowerCase().includes(searchItem.toLowerCase()), | ||
) | ||
.sort(comparePurchaseUrgency); | ||
|
||
const filteredItems = data.filter((item) => | ||
item.name.toLowerCase().includes(searchItem.toLowerCase()), | ||
); | ||
|
||
const listName = listPath.slice(listPath.indexOf('/') + 1); | ||
console.log(`filter: ${filteredItems.map((d) => d.name)}`); | ||
console.log('----'); | ||
console.log(`Urgency state object:`); | ||
console.log(urgency); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need to keep these console logs in production, can we put them behind a feature flag controlled by a query parameter in the URL? Perhaps something like ?debug=on
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The console logs are just to make testing more visual, I will remove before merging!
key={item.id} | ||
item={item} | ||
listPath={listPath} | ||
className={itemClassName} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be replaced by passing the urgencyStatus
prop!
@@ -1,15 +1,29 @@ | |||
import { render, screen } from '@testing-library/react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating the test!
describe('Compare Purchase Urgency', () => { | ||
test('it works', () => { | ||
expect(true).toBe(true); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a placeholder. Could we remove it for now and add a meaningful test for the comparePurchaseUrgency
function once it's ready?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, it was very much a prototype, I will work on creating proper tests
@@ -15,7 +15,7 @@ | |||
"settings": { "react": { "version": "detect" } }, | |||
"rules": { | |||
"no-duplicate-imports": "warn", | |||
"no-unused-vars": "warn", | |||
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious about the reasoning behind this change! Are there specific cases where we're intentionally using unused variables or function parameters prefixed with an underscore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove this for the urgency hook! but this was my original getClassName function where I didnt know how to avoid an unused variable when looping over items within a set. In the urgency hook I managed though!
original reason:
const getClassName = (name) => {
const statusArray = Object.entries(urgency).find(([_, items]) => {
return Array.from(items).some((item) => item.name === name);
});
if (!statusArray) {
throw new Error(`Failed to get class name of ${name}`);
}
return statusArray[0];
};
Description
This code proposes a way to sort items using sortByUrgency function and urgency state. The urgency status is passed to each item as a className.
I am using colors and borders to visualize the urgency, but it is a temporary solution:
overdue - dotted white border,
soon - green solid;
kindOfSoon - yellow thick border;
notSoon - solid blue thin border;
inactive - dashed red border;
The code does not pass tests, I do not know why. I faked return mock values, as it seems that I was not able to read dates.js module.
What isn't good:
Type of Changes
enhancement
Updates
After
Testing Steps / QA Criteria
Copious console logs show that items do get sorted