Skip to content

Commit 4bb08cc

Browse files
committed
Merge remote-tracking branch 'origin/main' into ma/view-list-name
2 parents 0d436c6 + 429554f commit 4bb08cc

File tree

4 files changed

+208
-80
lines changed

4 files changed

+208
-80
lines changed

src/api/firebase.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export function useShoppingLists(user: User | null) {
9595
const ListItemModel = t.type({
9696
id: t.string,
9797
name: t.string,
98+
itemQuantity: t.number,
9899
dateLastPurchased: t.union([FirebaseTimestamp, t.null]),
99100
dateNextPurchased: FirebaseTimestamp,
100101
totalPurchases: t.number,
@@ -233,6 +234,7 @@ export async function addItem(
233234
listPath: string,
234235
name: string,
235236
daysUntilNextPurchase: number,
237+
itemQuantity: number,
236238
) {
237239
const listCollectionRef = collection(db, listPath, "items");
238240

@@ -244,8 +246,10 @@ export async function addItem(
244246
dateLastPurchased: null,
245247
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
246248
name,
249+
itemQuantity,
247250
totalPurchases: 0,
248251
});
252+
console.log("Item added successfully!", name, itemQuantity);
249253
} catch (error) {
250254
console.error("Error adding an item", error);
251255
throw error;
@@ -295,6 +299,28 @@ export async function updateItem(listPath: string, item: ListItem) {
295299
}
296300
}
297301

302+
export async function storeItemQuantity(
303+
listPath: string,
304+
item: ListItem,
305+
newQuantity: number,
306+
) {
307+
const itemDocRef = doc(db, listPath, "items", item.id);
308+
const oldItemQuantity = item.itemQuantity;
309+
console.log("Old item quantity from Firebase:", oldItemQuantity);
310+
311+
const updates = {
312+
itemQuantity: newQuantity,
313+
};
314+
315+
try {
316+
await updateDoc(itemDocRef, updates);
317+
console.log("Item quantity updated in Firebase:", newQuantity);
318+
} catch (error) {
319+
console.error("Error updating quantity", error);
320+
throw error;
321+
}
322+
}
323+
298324
//delete an item from the list
299325
export async function deleteItem(listPath: string, item: ListItem) {
300326
//reference the item document

src/components/ListItem.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import "./ListItem.scss";
2-
import { updateItem, deleteItem, ListItem } from "../api";
2+
import { updateItem, deleteItem, ListItem, storeItemQuantity } from "../api";
33
import { useState } from "react";
44
import toast from "react-hot-toast";
55
import { moreThan24HoursPassed, getDaysBetweenDates } from "../utils";
6+
import { ItemQuantityForm } from "./forms/ItemQuantityForm";
67
import Button from "react-bootstrap/Button";
78
import Form from "react-bootstrap/Form";
89

@@ -80,6 +81,26 @@ export function ListItemCheckBox({ item, listPath }: Props) {
8081
}
8182
};
8283

84+
const editItemQuantity = async (quantity: number) => {
85+
console.log("Quantity edited in list:", quantity);
86+
87+
if (quantity < 1) {
88+
toast.error("Oops! Quantity must be at least 1!");
89+
return;
90+
}
91+
92+
try {
93+
await toast.promise(storeItemQuantity(listPath, item, quantity), {
94+
loading: `Updating ${item.name} quantity!`,
95+
success: `${item.name} quantity updated!`,
96+
error: `Failed to update ${item.name} quantity. Please try again!`,
97+
});
98+
} catch (error) {
99+
console.error(`Error updating ${item.name} quantity`, error);
100+
alert("Error updating item quantity!");
101+
}
102+
};
103+
83104
const deleteItemHandler = () => {
84105
const isConfirmed = window.confirm("Do you want to delete this item?");
85106

@@ -108,12 +129,11 @@ export function ListItemCheckBox({ item, listPath }: Props) {
108129
onChange={handleCheckChange}
109130
aria-checked={isChecked}
110131
disabled={isChecked}
111-
label={item.name}
112132
/>
113-
133+
<ItemQuantityForm saveItemQuantity={editItemQuantity} item={item} /> x{" "}
134+
{item.name}{" "}
114135
<span>
115136
{getUrgencyStatus(item)}
116-
117137
<Button onClick={() => deleteItemHandler()}>Delete Item</Button>
118138
</span>
119139
</div>

src/components/forms/AddItemForm.tsx

Lines changed: 90 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
3232
PurchaseTime.soon,
3333
);
3434

35+
const [addedQuantity, setAddedQuantity] = useState(1);
36+
3537
const handleItemNameTextChange = (e: ChangeEvent<HTMLInputElement>) => {
3638
setItemName(e.target.value);
3739
};
@@ -40,6 +42,11 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
4042
setItemNextPurchaseTimeline(changed);
4143
};
4244

45+
const handleItemQuantityChange = (e: ChangeEvent<HTMLInputElement>) => {
46+
setAddedQuantity(Number(e.target.value));
47+
console.log("Quantity captured in Add Item input:", addedQuantity);
48+
};
49+
4350
const handleSubmit = async (
4451
e: FormEvent<HTMLFormElement>,
4552
listPath: string,
@@ -69,19 +76,21 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
6976

7077
try {
7178
await toast.promise(
72-
addItem(listPath, itemName, daysUntilNextPurchase), // saving original input
79+
addItem(listPath, itemName, daysUntilNextPurchase, addedQuantity), // saving original input
7380
{
7481
loading: "Adding item to list.",
7582
success: () => {
7683
setItemName("");
7784
setItemNextPurchaseTimeline(PurchaseTime.soon);
78-
return `${itemName} successfully added to your list!`; // showing original input
85+
setAddedQuantity(1);
86+
return `${addedQuantity} ${itemName} successfully added to your list!`; // showing original input
7987
},
8088
error: () => {
81-
return `${itemName} failed to add to your list. Please try again!`;
89+
return `Failed to add ${addedQuantity} ${itemName} to your list. Please try again!`;
8290
},
8391
},
8492
);
93+
console.log("Quantity added from Add Item form:", addedQuantity);
8594
} catch (error) {
8695
console.error("Failed to add item:", error);
8796
}
@@ -96,80 +105,85 @@ export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
96105

97106
return (
98107
<section>
99-
{listPath && (
100-
<>
101-
<Form onSubmit={(e) => handleSubmit(e, listPath)}>
102-
<h3>First, add your item!</h3>
103-
<Form.Label htmlFor="item-name">Item:</Form.Label>
104-
<Form.Control
105-
id="item-name"
106-
type="text"
107-
name="item"
108-
value={itemName}
109-
onChange={handleItemNameTextChange}
110-
aria-label="Enter the item name"
111-
aria-required
108+
<Form onSubmit={(e) => handleSubmit(e, listPath)}>
109+
<h3>First, add your item!</h3>
110+
<Form.Label htmlFor="item-name">
111+
Item:
112+
<Form.Control
113+
id="item-name"
114+
type="text"
115+
name="item"
116+
value={itemName}
117+
onChange={handleItemNameTextChange}
118+
aria-label="Enter the item name"
119+
aria-required
120+
/>
121+
</Form.Label>
122+
<label htmlFor="item-quantity">
123+
Quantity:
124+
<Form.Control
125+
id="item-quantity"
126+
type="number"
127+
name="quantity"
128+
value={addedQuantity}
129+
onChange={handleItemQuantityChange}
130+
aria-label="Enter the item quantity"
131+
aria-required
132+
/>
133+
</label>
134+
<br />
135+
<h3>Next, pick when you plan on buying this item again!</h3>
136+
<fieldset>
137+
<legend>When to buy:</legend>
138+
<Form.Label htmlFor={PurchaseTime.soon}>
139+
<Form.Check
140+
type="radio"
141+
id={PurchaseTime.soon}
142+
name="when-to-buy"
143+
value={PurchaseTime.soon}
144+
required
145+
onChange={() => handleNextPurchaseChange(PurchaseTime.soon)}
146+
checked={itemNextPurchaseTimeline === PurchaseTime.soon}
147+
aria-label={`Set buy to soon, within ${purchaseTimelines[PurchaseTime.soon]} days`}
148+
/>
149+
Soon -- Within {purchaseTimelines[PurchaseTime.soon]} days!
150+
</Form.Label>
151+
<br />
152+
<Form.Label htmlFor={PurchaseTime.kindOfSoon}>
153+
<Form.Check
154+
type="radio"
155+
id={PurchaseTime.kindOfSoon}
156+
name="when-to-buy"
157+
value={PurchaseTime.kindOfSoon}
158+
required
159+
onChange={() => handleNextPurchaseChange(PurchaseTime.kindOfSoon)}
160+
checked={itemNextPurchaseTimeline === PurchaseTime.kindOfSoon}
161+
aria-label={`Set buy to kind of soon, within ${purchaseTimelines[PurchaseTime.kindOfSoon]} days`}
162+
/>
163+
Kind of soon -- Within {purchaseTimelines[PurchaseTime.kindOfSoon]}{" "}
164+
days!
165+
</Form.Label>
166+
<br />
167+
<label htmlFor={PurchaseTime.notSoon}>
168+
<Form.Check
169+
type="radio"
170+
id={PurchaseTime.notSoon}
171+
name="when-to-buy"
172+
value={PurchaseTime.notSoon}
173+
required
174+
onChange={() => handleNextPurchaseChange(PurchaseTime.notSoon)}
175+
checked={itemNextPurchaseTimeline === PurchaseTime.notSoon}
176+
aria-label={`Set buy to not soon, within ${purchaseTimelines[PurchaseTime.notSoon]} days`}
112177
/>
113-
<br />
114-
<h3>Next, pick when you plan on buying this item again!</h3>
115-
<fieldset className="border border-2 rounded-1 p-2 mb-4">
116-
<legend className="fs-2 float-none w-auto p-2">
117-
When to buy:
118-
</legend>
119-
<Form.Check
120-
type="radio"
121-
className="mb-3 ms-3"
122-
id={PurchaseTime.soon}
123-
name="when-to-buy"
124-
value={PurchaseTime.soon}
125-
required
126-
onChange={() => handleNextPurchaseChange(PurchaseTime.soon)}
127-
checked={itemNextPurchaseTimeline === PurchaseTime.soon}
128-
aria-label={`Set buy to soon, within ${purchaseTimelines[PurchaseTime.soon]} days`}
129-
label={`Soon -- Within ${purchaseTimelines[PurchaseTime.soon]} days!`}
130-
/>
131-
<Form.Check
132-
type="radio"
133-
className="mb-3 ms-3"
134-
id={PurchaseTime.kindOfSoon}
135-
name="when-to-buy"
136-
value={PurchaseTime.kindOfSoon}
137-
required
138-
onChange={() =>
139-
handleNextPurchaseChange(PurchaseTime.kindOfSoon)
140-
}
141-
checked={itemNextPurchaseTimeline === PurchaseTime.kindOfSoon}
142-
aria-label={`Set buy to kind of soon, within ${purchaseTimelines[PurchaseTime.kindOfSoon]} days`}
143-
label={`Kind of soon -- Within
144-
${purchaseTimelines[PurchaseTime.kindOfSoon]} days!`}
145-
/>
146-
<Form.Check
147-
type="radio"
148-
className="mb-3 ms-3"
149-
id={PurchaseTime.notSoon}
150-
name="when-to-buy"
151-
value={PurchaseTime.notSoon}
152-
required
153-
onChange={() => handleNextPurchaseChange(PurchaseTime.notSoon)}
154-
checked={itemNextPurchaseTimeline === PurchaseTime.notSoon}
155-
aria-label={`Set buy to not soon, within ${purchaseTimelines[PurchaseTime.notSoon]} days`}
156-
label={`Not soon -- Within ${purchaseTimelines[PurchaseTime.notSoon]} days!`}
157-
/>
158-
</fieldset>
159-
<Button
160-
type="submit"
161-
aria-label="Add item to shopping list"
162-
className="mb-3"
163-
>
164-
Submit Item
165-
</Button>
166-
</Form>
167-
<h4>Let&apos;s go look at your list!</h4>
168-
<Button onClick={navigateToListPage} className="my-3">
169-
{"View List"}
170-
</Button>
171-
</>
172-
)}
178+
Not soon -- Within {purchaseTimelines[PurchaseTime.notSoon]} days!
179+
</label>
180+
</fieldset>
181+
<Button type="submit" aria-label="Add item to shopping list">
182+
Submit Item
183+
</Button>
184+
</Form>
185+
<h4>Let&apos;s go look at your list!</h4>
186+
<Button onClick={navigateToListPage}>{"View List"}</Button>
173187
</section>
174188
);
175189
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { FormEvent, useState } from "react";
2+
import { ListItem } from "../../api";
3+
import { toast } from "react-hot-toast";
4+
import Button from "react-bootstrap/Button";
5+
import Form from "react-bootstrap/Form";
6+
7+
interface ItemQuantityFormProps {
8+
saveItemQuantity: (quantity: number) => void;
9+
item: ListItem;
10+
}
11+
12+
export function ItemQuantityForm({
13+
saveItemQuantity,
14+
item,
15+
}: ItemQuantityFormProps) {
16+
// A state variable to store the item quantity.
17+
const [itemQuantity, setItemQuantity] = useState<number>(
18+
item ? item.itemQuantity : 1,
19+
);
20+
21+
// A state variable to store the edit mode.
22+
const [edit, setEdit] = useState<boolean>(false);
23+
24+
// A function that will toggle the edit mode.
25+
const toggleEdit = (e: FormEvent<HTMLButtonElement>): void => {
26+
e.preventDefault();
27+
setEdit(!edit);
28+
console.log("Toggle edit mode:", edit);
29+
};
30+
31+
// A function that will save the item quantity.
32+
const updateItemQuantity = (e: FormEvent<HTMLButtonElement>): void => {
33+
e.preventDefault();
34+
35+
if (itemQuantity < 1) {
36+
toast.error("Oops! Quantity must be at least 1!");
37+
setEdit(edit);
38+
return;
39+
}
40+
setEdit(!edit);
41+
saveItemQuantity(itemQuantity);
42+
console.log("Item quantity updated in Item Quantity Form:", itemQuantity);
43+
};
44+
45+
return (
46+
<>
47+
<Form.Control
48+
id="item-quantity"
49+
aria-label="Item quantity"
50+
type="number"
51+
name="item-quantity"
52+
min="1"
53+
max="100"
54+
value={itemQuantity}
55+
onChange={(e) => setItemQuantity(Number(e.target.value))}
56+
disabled={!edit}
57+
/>
58+
{edit ? (
59+
<span>
60+
<Button onClick={updateItemQuantity}>Save!</Button>{" "}
61+
<Button onClick={toggleEdit}>Cancel!</Button>
62+
</span>
63+
) : (
64+
<Button onClick={toggleEdit}>Edit!</Button>
65+
)}
66+
</>
67+
);
68+
}

0 commit comments

Comments
 (0)