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

Feat/add item quantity #46

Merged
merged 42 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c27cfb9
feat: Add item quantity parameter to addItem function in firebase.ts.…
RossaMania Sep 25, 2024
e6a00cf
feat: Add logging of name and itemQuantity for successful item additi…
RossaMania Sep 25, 2024
f2789d6
feat: Add item quantity parameter to addItem function in AddItemForm.…
RossaMania Sep 25, 2024
f175b7b
feat: Add quantity input to AddItemForm.tsx with type "number". Wire …
RossaMania Sep 25, 2024
170d3e3
feat: Add item quantity property to ListItemModel in firebase.ts. Thi…
RossaMania Sep 25, 2024
912f056
feat: Add item quantity input to AddItemForm.tsx and create ItemQuant…
RossaMania Sep 25, 2024
2097649
feat: Add ItemQuantityForm component to ListItem.tsx. Import ItemQuan…
RossaMania Sep 26, 2024
7c848c3
feat: Add updateItemQuantity function to firebase.ts. The function re…
RossaMania Sep 26, 2024
153c3f0
feat: Update the updateItemQuantity function in firebase.ts with upda…
RossaMania Sep 26, 2024
266c2cc
feat: Update updateItemQuantity function in firebase.ts to include lo…
RossaMania Sep 26, 2024
ed049b5
fix: updated console logs for better readability.
RossaMania Sep 27, 2024
c41b19a
feat: Update item quantity functionality.
RossaMania Sep 27, 2024
dd7d557
feat: Update ItemQuantityForm to initialize item quantity from ListItem.
RossaMania Sep 27, 2024
f4eb386
feat: Update AddItemForm to pass item prop to ItemQuantityForm.
RossaMania Sep 27, 2024
ccde792
fix: Update the ItemQuantityForm component to handle cases where the …
RossaMania Sep 27, 2024
cb1804c
feat: Handle case where item quantity is 0 in ListItemCheckBox compon…
RossaMania Sep 27, 2024
49c2029
refactor: Remove unnecessary div element in ItemQuantityForm component
RossaMania Sep 28, 2024
9d5342b
Merge latest changes from main into feat/add-item-quantity. Render It…
RossaMania Sep 28, 2024
ff80a47
Refactor: Remove unnecessary check for listPath in editItemQuantity f…
RossaMania Sep 28, 2024
0dd580d
Refactor: Remove unnecessary section element in ItemQuantityForm comp…
RossaMania Sep 28, 2024
1755648
Refactor: Update label htmlFor attribute in ItemQuantityForm componen…
RossaMania Sep 28, 2024
94bd6df
Refactor: Change ItemQuantityForm from a default export to a named ex…
RossaMania Sep 28, 2024
0781346
Refactor: Update label in ItemQuantityForm component to "How many?"
RossaMania Sep 28, 2024
9a8cd06
Refactor: Update import statement for ItemQuantityForm in ListItem co…
RossaMania Sep 28, 2024
1c48aaa
Refactor: Update default item quantity in ItemQuantityForm component …
RossaMania Sep 28, 2024
006fc39
fix: Show error toast then set default item quantity to 1 if it is le…
RossaMania Sep 28, 2024
a20b47d
Refactor: Update error message in AddItemForm component when addItem …
RossaMania Sep 28, 2024
a46682a
Fix: Set default item quantity to 1 if it is less than 1 in AddItemFo…
RossaMania Sep 28, 2024
d8ca614
Refactor: Remove unnecessary deleteItemHandler call in ListItemCheckB…
RossaMania Sep 28, 2024
6d98f71
Refactor: Update ItemQuantityForm component to show error toast and s…
RossaMania Sep 28, 2024
f5f9ee6
Refactor: Update error message in AddItemForm component when item qua…
RossaMania Sep 28, 2024
eb19b32
Refactor: Add validation for item quantity in the editItemQuantity fu…
RossaMania Sep 28, 2024
9e7d038
Refactor: Update ItemQuantityForm component to use fragment instead o…
RossaMania Sep 29, 2024
d42c05a
Change the name of firebase function from updateItemQuantity to store…
RossaMania Sep 29, 2024
5beeed5
Refactor: Update console log message in AddItemForm component to be m…
RossaMania Sep 29, 2024
1883d22
Refactor: Have AddItemForm just use an in-line label and input for ad…
RossaMania Sep 29, 2024
b3ac8ef
Refactor: Remove unused 'item' prop from AddItemForm component
RossaMania Sep 29, 2024
1ee02d3
Refactor: Remove unnecessary checks for item quantity in AddItemForm …
RossaMania Sep 29, 2024
0778dd5
Refactor: Remove label for item quantity in ItemQuantityForm component.
RossaMania Sep 29, 2024
525ac74
Refactor: Update ItemQuantityForm component to include cancel button …
RossaMania Sep 29, 2024
b582e82
Refactor: Merge main branch changes into feat/add-item-quantity branc…
RossaMania Oct 2, 2024
5427158
Refactor ListItem component to display item quantity and name togethe…
RossaMania Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function useShoppingLists(user: User | null) {
const ListItemModel = t.type({
id: t.string,
name: t.string,
itemQuantity: t.number,
RossaMania marked this conversation as resolved.
Show resolved Hide resolved
dateLastPurchased: t.union([FirebaseTimestamp, t.null]),
dateNextPurchased: FirebaseTimestamp,
totalPurchases: t.number,
Expand Down Expand Up @@ -233,6 +234,7 @@ export async function addItem(
listPath: string,
name: string,
daysUntilNextPurchase: number,
itemQuantity: number,
) {
const listCollectionRef = collection(db, listPath, "items");

Expand All @@ -244,8 +246,10 @@ export async function addItem(
dateLastPurchased: null,
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
name,
itemQuantity,
totalPurchases: 0,
});
console.log("Item added successfully!", name, itemQuantity);
} catch (error) {
console.error("Error adding an item", error);
throw error;
Expand Down Expand Up @@ -295,6 +299,28 @@ export async function updateItem(listPath: string, item: ListItem) {
}
}

export async function storeItemQuantity(
listPath: string,
item: ListItem,
newQuantity: number,
) {
const itemDocRef = doc(db, listPath, "items", item.id);
const oldItemQuantity = item.itemQuantity;
console.log("Old item quantity from Firebase:", oldItemQuantity);

const updates = {
itemQuantity: newQuantity,
};

try {
await updateDoc(itemDocRef, updates);
console.log("Item quantity updated in Firebase:", newQuantity);
} catch (error) {
console.error("Error updating quantity", error);
throw error;
}
}

//delete an item from the list
export async function deleteItem(listPath: string, item: ListItem) {
//reference the item document
Expand Down
28 changes: 24 additions & 4 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import "./ListItem.scss";
import { updateItem, deleteItem, ListItem } from "../api";
import { updateItem, deleteItem, ListItem, storeItemQuantity } from "../api";
import { useState } from "react";
import toast from "react-hot-toast";
import { moreThan24HoursPassed, getDaysBetweenDates } from "../utils";
import { ItemQuantityForm } from "./forms/ItemQuantityForm";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";

Expand Down Expand Up @@ -80,6 +81,26 @@ export function ListItemCheckBox({ item, listPath }: Props) {
}
};

Copy link
Collaborator

@bbland1 bbland1 Sep 28, 2024

Choose a reason for hiding this comment

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

suggestion: i think we could move this logic for updating the item quantity into the ItemQuantityForm. reading through the code right now it feels like there is some duplication of logic, kind of adding logic to components to make another component work.

moving this to be handled by the ItemQuantityForm it could make it a bit more reusable because we could just place it into the location it is needed and not write logic within that component to get it to work. it would be like this check box and other forms the logic to do the updating and steps the form is doing is housed within the logic.

To get the item prop needed with these changes on the list view we could try moving the <ItemQuantityForm saveItemQuantity={editItemQuantity} item={item} /> x{" "} call into the List component with this logic

{filteredListItems.map((item) => (<ListItemCheckBox key={item.id} item={item} listPath={listPath} />))}

and I believe it would get the same result.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Great idea, that way the logic outside of firebase for quantity is all referable in one place.

const editItemQuantity = async (quantity: number) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

More a comment for later if we ever do a refactor. It may still be beneficial to move the logic for updating the item quantity to be contained in the ItemQuanitiyForm

Nonblocking!

Awesome functionality to add to the app!

console.log("Quantity edited in list:", quantity);

if (quantity < 1) {
toast.error("Oops! Quantity must be at least 1!");
return;
}

try {
await toast.promise(storeItemQuantity(listPath, item, quantity), {
loading: `Updating ${item.name} quantity!`,
success: `${item.name} quantity updated!`,
error: `Failed to update ${item.name} quantity. Please try again!`,
});
} catch (error) {
console.error(`Error updating ${item.name} quantity`, error);
alert("Error updating item quantity!");
}
};

const deleteItemHandler = () => {
const isConfirmed = window.confirm("Do you want to delete this item?");

Expand Down Expand Up @@ -108,12 +129,11 @@ export function ListItemCheckBox({ item, listPath }: Props) {
onChange={handleCheckChange}
aria-checked={isChecked}
disabled={isChecked}
label={item.name}
/>

<ItemQuantityForm saveItemQuantity={editItemQuantity} item={item} /> x{" "}
{item.name}{" "}
<span>
{getUrgencyStatus(item)}

<Button onClick={() => deleteItemHandler()}>Delete Item</Button>
</span>
</div>
Expand Down
167 changes: 91 additions & 76 deletions src/components/forms/AddItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
PurchaseTime.soon,
);

const [addedQuantity, setAddedQuantity] = useState(1);

const handleItemNameTextChange = (e: ChangeEvent<HTMLInputElement>) => {
setItemName(e.target.value);
};
Expand All @@ -40,6 +42,11 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
setItemNextPurchaseTimeline(changed);
};

const handleItemQuantityChange = (e: ChangeEvent<HTMLInputElement>) => {
setAddedQuantity(Number(e.target.value));
console.log("Quantity captured in Add Item input:", addedQuantity);
};

const handleSubmit = async (
e: FormEvent<HTMLFormElement>,
listPath: string,
Expand Down Expand Up @@ -69,103 +76,111 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {

try {
await toast.promise(
addItem(listPath, itemName, daysUntilNextPurchase), // saving original input
addItem(listPath, itemName, daysUntilNextPurchase, addedQuantity), // saving original input
{
loading: "Adding item to list.",
success: () => {
setItemName("");
setItemNextPurchaseTimeline(PurchaseTime.soon);
return `${itemName} successfully added to your list!`; // showing original input
setAddedQuantity(1);
return `${addedQuantity} ${itemName} successfully added to your list!`; // showing original input
},
error: () => {
return `${itemName} failed to add to your list. Please try again!`;
return `Failed to add ${addedQuantity} ${itemName} to your list. Please try again!`;
},
},
);
console.log("Quantity added from Add Item form:", addedQuantity);
} catch (error) {
console.error("Failed to add item:", error);
}
};

const navigateToListPage = () => {
navigate("/list");
};

return (
<section>
{listPath && (
<>
<Form onSubmit={(e) => handleSubmit(e, listPath)}>
<h3>First, add your item!</h3>
<Form.Label htmlFor="item-name">Item:</Form.Label>
<Form.Control
id="item-name"
type="text"
name="item"
value={itemName}
onChange={handleItemNameTextChange}
aria-label="Enter the item name"
aria-required
<Form onSubmit={(e) => handleSubmit(e, listPath)}>
<h3>First, add your item!</h3>
<Form.Label htmlFor="item-name">
Item:
<Form.Control
id="item-name"
type="text"
name="item"
value={itemName}
onChange={handleItemNameTextChange}
aria-label="Enter the item name"
aria-required
/>
</Form.Label>
<label htmlFor="item-quantity">
Quantity:
<Form.Control
id="item-quantity"
type="number"
name="quantity"
value={addedQuantity}
onChange={handleItemQuantityChange}
aria-label="Enter the item quantity"
aria-required
/>
</label>
<br />
<h3>Next, pick when you plan on buying this item again!</h3>
<fieldset>
<legend>When to buy:</legend>
<Form.Label htmlFor={PurchaseTime.soon}>
<Form.Check
type="radio"
id={PurchaseTime.soon}
name="when-to-buy"
value={PurchaseTime.soon}
required
onChange={() => handleNextPurchaseChange(PurchaseTime.soon)}
checked={itemNextPurchaseTimeline === PurchaseTime.soon}
aria-label={`Set buy to soon, within ${purchaseTimelines[PurchaseTime.soon]} days`}
/>
Soon -- Within {purchaseTimelines[PurchaseTime.soon]} days!
</Form.Label>
<br />
<Form.Label htmlFor={PurchaseTime.kindOfSoon}>
<Form.Check
type="radio"
id={PurchaseTime.kindOfSoon}
name="when-to-buy"
value={PurchaseTime.kindOfSoon}
required
onChange={() => handleNextPurchaseChange(PurchaseTime.kindOfSoon)}
checked={itemNextPurchaseTimeline === PurchaseTime.kindOfSoon}
aria-label={`Set buy to kind of soon, within ${purchaseTimelines[PurchaseTime.kindOfSoon]} days`}
/>
Kind of soon -- Within {purchaseTimelines[PurchaseTime.kindOfSoon]}{" "}
days!
</Form.Label>
<br />
<label htmlFor={PurchaseTime.notSoon}>
<Form.Check
type="radio"
id={PurchaseTime.notSoon}
name="when-to-buy"
value={PurchaseTime.notSoon}
required
onChange={() => handleNextPurchaseChange(PurchaseTime.notSoon)}
checked={itemNextPurchaseTimeline === PurchaseTime.notSoon}
aria-label={`Set buy to not soon, within ${purchaseTimelines[PurchaseTime.notSoon]} days`}
/>
<br />
<h3>Next, pick when you plan on buying this item again!</h3>
<fieldset className="border border-2 rounded-1 p-2 mb-4">
<legend className="fs-2 float-none w-auto p-2">
When to buy:
</legend>
<Form.Check
type="radio"
className="mb-3 ms-3"
id={PurchaseTime.soon}
name="when-to-buy"
value={PurchaseTime.soon}
required
onChange={() => handleNextPurchaseChange(PurchaseTime.soon)}
checked={itemNextPurchaseTimeline === PurchaseTime.soon}
aria-label={`Set buy to soon, within ${purchaseTimelines[PurchaseTime.soon]} days`}
label={`Soon -- Within ${purchaseTimelines[PurchaseTime.soon]} days!`}
/>
<Form.Check
type="radio"
className="mb-3 ms-3"
id={PurchaseTime.kindOfSoon}
name="when-to-buy"
value={PurchaseTime.kindOfSoon}
required
onChange={() =>
handleNextPurchaseChange(PurchaseTime.kindOfSoon)
}
checked={itemNextPurchaseTimeline === PurchaseTime.kindOfSoon}
aria-label={`Set buy to kind of soon, within ${purchaseTimelines[PurchaseTime.kindOfSoon]} days`}
label={`Kind of soon -- Within
${purchaseTimelines[PurchaseTime.kindOfSoon]} days!`}
/>
<Form.Check
type="radio"
className="mb-3 ms-3"
id={PurchaseTime.notSoon}
name="when-to-buy"
value={PurchaseTime.notSoon}
required
onChange={() => handleNextPurchaseChange(PurchaseTime.notSoon)}
checked={itemNextPurchaseTimeline === PurchaseTime.notSoon}
aria-label={`Set buy to not soon, within ${purchaseTimelines[PurchaseTime.notSoon]} days`}
label={`Not soon -- Within ${purchaseTimelines[PurchaseTime.notSoon]} days!`}
/>
</fieldset>
<Button
type="submit"
aria-label="Add item to shopping list"
className="mb-3"
>
Submit Item
</Button>
</Form>
<h4>Let&apos;s go look at your list!</h4>
<Button onClick={navigateToListPage} className="my-3">
{"View List"}
</Button>
</>
)}
Not soon -- Within {purchaseTimelines[PurchaseTime.notSoon]} days!
</label>
</fieldset>
<Button type="submit" aria-label="Add item to shopping list">
Submit Item
</Button>
</Form>
<h4>Let&apos;s go look at your list!</h4>
<Button onClick={navigateToListPage}>{"View List"}</Button>
</section>
);
}
68 changes: 68 additions & 0 deletions src/components/forms/ItemQuantityForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { FormEvent, useState } from "react";
import { ListItem } from "../../api";
import { toast } from "react-hot-toast";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";

interface ItemQuantityFormProps {
saveItemQuantity: (quantity: number) => void;
item: ListItem;
}

export function ItemQuantityForm({
saveItemQuantity,
item,
}: ItemQuantityFormProps) {
// A state variable to store the item quantity.
const [itemQuantity, setItemQuantity] = useState<number>(
item ? item.itemQuantity : 1,
);

// A state variable to store the edit mode.
const [edit, setEdit] = useState<boolean>(false);

// A function that will toggle the edit mode.
const toggleEdit = (e: FormEvent<HTMLButtonElement>): void => {
e.preventDefault();
setEdit(!edit);
console.log("Toggle edit mode:", edit);
};

// A function that will save the item quantity.
const updateItemQuantity = (e: FormEvent<HTMLButtonElement>): void => {
e.preventDefault();

if (itemQuantity < 1) {
toast.error("Oops! Quantity must be at least 1!");
setEdit(edit);
return;
}
setEdit(!edit);
saveItemQuantity(itemQuantity);
console.log("Item quantity updated in Item Quantity Form:", itemQuantity);
};

return (
<>
<Form.Control
id="item-quantity"
aria-label="Item quantity"
type="number"
name="item-quantity"
min="1"
max="100"
value={itemQuantity}
onChange={(e) => setItemQuantity(Number(e.target.value))}
disabled={!edit}
/>
{edit ? (
<span>
<Button onClick={updateItemQuantity}>Save!</Button>{" "}
<Button onClick={toggleEdit}>Cancel!</Button>
</span>
) : (
<Button onClick={toggleEdit}>Edit!</Button>
)}
</>
);
}