Skip to content

Commit

Permalink
Merge pull request #34 from the-collab-lab/an-uncheck-feature
Browse files Browse the repository at this point in the history
Uncheck feature
  • Loading branch information
eonflower authored Mar 25, 2024
2 parents 0bf6786 + 7d7fae7 commit 64a774c
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 37 deletions.
53 changes: 48 additions & 5 deletions src/api/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,39 +170,83 @@ export async function shareList(listPath, currentUserId, recipientEmail) {
* @param {string} itemData.itemName The name of the item.
* @param {number} itemData.daysUntilNextPurchase The number of days until the user thinks they'll need to buy the item again.
*/

export async function addItem(listPath, { itemName, daysUntilNextPurchase }) {
const listCollectionRef = collection(db, listPath, 'items');

const newDocRef = doc(listCollectionRef);

return setDoc(newDocRef, {
dateCreated: new Date(),
// NOTE: This is null because the item has just been created.
// We'll use updateItem to put a Date here when the item is purchased!
dateLastPurchased: null,
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
name: itemName,
totalPurchases: 0,
});
}

/**
* Updates the specified item in the shopping list with the new purchase information.
* @param {string} listPath - Path of the shopping list in Firestore.
* @param {string} itemId - ID of the item to be updated.
* @param {Timestamp} dateLastPurchased - Timestamp of the last purchase date.
* @param {number} nextPurchaseEstimate - Estimated number of days until the next purchase.
* @returns {Promise} A promise that resolves when the item is successfully updated.
*/
export async function updateItem(
listPath,
itemId,
todaysDate,
dateLastPurchased,
dateNextPurchased,
nextPurchaseEstimate,
) {
const listCollectionRef = collection(db, listPath, 'items');

const itemDocRef = doc(listCollectionRef, itemId);

return updateDoc(itemDocRef, {
dateLastPurchased,
previousNextPurchased: dateNextPurchased,
previousLastPurchased: dateLastPurchased,
dateLastPurchased: todaysDate,
dateNextPurchased: getFutureDate(nextPurchaseEstimate),
totalPurchases: increment(1),
});
}

/**
* Removes the last purchase information from the specified item in the shopping list.
* If the item has no previous purchases, the function resolves without making any changes.
* @param {string} listPath - Path of the shopping list in Firestore.
* @param {string} itemId - ID of the item to be unchecked.
* @returns {Promise} A promise that resolves when the item is successfully unchecked or if there are no changes needed.
*/

export async function uncheckItem(
listPath,
itemId,
previousLastPurchased,
previousNextPurchased,
totalPurchases,
) {
// Create a reference to the item document in the specified shopping list
const listCollectionRef = collection(db, listPath, 'items');
const itemDocRef = doc(listCollectionRef, itemId);

// Retrieve the item document data
const itemDoc = await getDoc(itemDocRef);
const itemData = itemDoc.data();

// Check if the item has a previous purchase
if (itemData.dateLastPurchased) {
// Update the item document with the new information
updateDoc(itemDocRef, {
dateLastPurchased: previousLastPurchased,
dateNextPurchased: previousNextPurchased,
totalPurchases: totalPurchases > 0 ? increment(-1) : 0,
});
}
}

export async function deleteItem(listPath, itemId) {
const listCollectionRef = collection(db, listPath, 'items');
const itemDocRef = doc(listCollectionRef, itemId);
Expand Down Expand Up @@ -258,7 +302,6 @@ export function comparePurchaseUrgency(array) {
if (dateA !== dateB) {
return dateA - dateB;
}

// if dates are equal sort by character value
return itemA.localeCompare(itemB);
});
Expand Down
18 changes: 11 additions & 7 deletions src/components/AddItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { addItem } from '../api/firebase';
export default function AddItem({ listPath, data }) {
const initialState = {
itemName: '',
daysUntilNextPurchase: 0,
daysUntilNextPurchase: '',
};

const [itemValue, setItemValue] = useState(initialState);
Expand All @@ -25,21 +25,25 @@ export default function AddItem({ listPath, data }) {

const newItemName = itemValue.itemName.trim();

if (itemValue.daysUntilNextPurchase === '') {
alert('Please select a timeframe to add item to list!');
return;
}
// Check if the itemName is empty
if (newItemName === '') {
alert(`Error: please name your item. Empty spaces don't count!`);
alert(`Please name your item. Empty spaces don't count!`);
return;
}

// Check if the itemName already exists in the list
if (existingItem(newItemName)) {
alert('Error: This item is already in your list!');
alert('Uh oh, this item is already in your list!');
return;
}

try {
await addItem(listPath, itemValue);
alert('Item added!');
alert(`${newItemName} added to list!`);
setItemValue(initialState);
} catch (err) {
alert('Error adding item to database');
Expand Down Expand Up @@ -70,10 +74,10 @@ export default function AddItem({ listPath, data }) {
<select
name="daysUntilNextPurchase"
onChange={handleInputChange}
value={itemValue.daysUntilNextPurchase}
defaultValue={''}
>
<option value="" default>
--Select an option--
<option value="" disabled>
Select an option
</option>
<option value="7">Soon</option>
<option value="14">Kind of Soon</option>
Expand Down
4 changes: 4 additions & 0 deletions src/components/ListItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
.ListItem-label {
margin-left: 0.2em;
}

.strikethrough {
text-decoration: line-through;
}
64 changes: 42 additions & 22 deletions src/components/ListItem.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getDifferenceBetweenDates, todaysDate, subtractDates } from '../utils';
import { updateItem, uncheckItem, deleteItem } from '../api';
import {
getDifferenceBetweenDates,
subtractDatesForAutoUncheck,
todaysDate,
} from '../utils';
import { colorPicker, calculateUrgency } from '../utils/helpers';
import { updateItem, deleteItem } from '../api';
import { Timestamp } from 'firebase/firestore';
import { calculateEstimate } from '@the-collab-lab/shopping-list-utils';
import './ListItem.css';
Expand All @@ -11,29 +15,36 @@ export function ListItem({
id,
dateLastPurchased,
dateNextPurchased,
previousLastPurchased,
previousNextPurchased,
totalPurchases,
dateCreated,
}) {
// if dateLastPurchased is true subtract it from dateNextPurchased, else subtract dateCreated from dateNextPurchased to get the estimated number of days till next purchase
const todaysDateTimestamp = Timestamp.now();
const isChecked = subtractDatesForAutoUncheck(
todaysDateTimestamp,
dateLastPurchased,
);

// Calculate the previous estimate based on the last purchase date or creation date
const previousEstimate = Math.ceil(
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
// Calculate the number of days since the last transaction
const daysSinceLastPurchase = Math.floor(
getDifferenceBetweenDates(
todaysDate,
dateLastPurchased ? dateLastPurchased.toDate() : dateCreated.toDate(),
),
) /
(24 * 60 * 60 * 1000),
);

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

const nextPurchaseEstimate = calculateEstimate(
Expand All @@ -43,24 +54,32 @@ export function ListItem({
);

const handleChecked = async () => {
const todaysDateTimestamp = Timestamp.now();
try {
await updateItem(listPath, id, todaysDateTimestamp, nextPurchaseEstimate);
if (isChecked) {
// Uncheck item
await uncheckItem(
listPath,
id,
previousLastPurchased,
previousNextPurchased,
totalPurchases,
);
} else {
// Check item
await updateItem(
listPath,
id,
todaysDateTimestamp,
dateLastPurchased,
dateNextPurchased,
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);

Expand All @@ -79,13 +98,14 @@ export function ListItem({
return (
<li className="ListItem">
<label>
{name} <span style={{ color: textColor }}>{urgency}</span>
<span className={isChecked ? 'strikethrough' : ''}>{name}</span>
<span style={{ color: textColor }}>{urgency}</span>
<input
type="checkbox"
id={`checkbox-${id}`} // Unique identifier
name={name}
onChange={handleChecked}
checked={isChecked()}
checked={isChecked}
></input>
</label>
<button onClick={handleDelete} className="delete-button">
Expand Down
18 changes: 16 additions & 2 deletions src/utils/dates.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ONE_DAY_IN_MILLISECONDS = 86400000;
export const ONE_DAY_IN_MILLISECONDS = 86400000;

/**
* Get a new JavaScript Date that is `offset` days in the future.
Expand All @@ -12,7 +12,21 @@ export function getFutureDate(offset) {
}

export function getDifferenceBetweenDates(date1, date2) {
return (date1 - date2) / (24 * 60 * 60 * 1000);
const millisecondsDifference = date1 - date2;
const hoursDifference = millisecondsDifference / (1000 * 60 * 60); // Convert milliseconds to hours

if (date1.getDate() === date2.getDate() && Math.abs(hoursDifference) < 24) {
return 0; // If dates are within 24 hours on the same day, return 0
}
return millisecondsDifference / ONE_DAY_IN_MILLISECONDS;
}

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

return false;
}

export const todaysDate = new Date();
7 changes: 6 additions & 1 deletion src/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export function colorPicker(text) {
case 'overdue':
color = 'red';
break;
case 'today':
color = 'pink';
break;
case 'soon':
color = 'orange';
break;
Expand All @@ -25,7 +28,9 @@ export function colorPicker(text) {
export function calculateUrgency(daysTillNextPurchase, daysSinceLastPurchase) {
let urgency;

if (
if (daysTillNextPurchase === 0) {
urgency = 'today';
} else if (
daysTillNextPurchase < 0 &&
daysSinceLastPurchase > daysTillNextPurchase
) {
Expand Down
2 changes: 2 additions & 0 deletions src/views/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export function List({ data, listPath, loading }) {
key={item.id}
name={item.name}
id={item.id}
previousNextPurchased={item.previousNextPurchased}
previousLastPurchased={item.previousLastPurchased}
dateLastPurchased={item.dateLastPurchased}
dateNextPurchased={item.dateNextPurchased}
totalPurchases={item.totalPurchases}
Expand Down

0 comments on commit 64a774c

Please sign in to comment.