From d866df7c0669866670e9976f403a3e78d84e01d8 Mon Sep 17 00:00:00 2001 From: Huda Mabkhoot Date: Sat, 12 Oct 2024 15:10:48 +0300 Subject: [PATCH 1/5] Created item edit feature --- src/api/firebase.js | 19 ++ src/components/ListItem.jsx | 103 ++++++---- .../ManageListForms/EditItemForm.jsx | 178 ++++++++++++++++++ 3 files changed, 268 insertions(+), 32 deletions(-) create mode 100644 src/components/ManageListForms/EditItemForm.jsx diff --git a/src/api/firebase.js b/src/api/firebase.js index 4581091..81162de 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -230,6 +230,25 @@ export async function updateItem(listPath, checked, itemData) { } } +export async function editItem( + listPath, + id, + { itemName, itemQuantity, dateNextPurchased }, +) { + const listCollectionRef = collection(db, listPath, 'items'); + const itemRef = doc(listCollectionRef, id); + + try { + await updateDoc(itemRef, { + name: itemName, + quantity: itemQuantity, + dateNextPurchased: dateNextPurchased, + }); + } catch (error) { + console.error('There was an error editing the item state: ', error); + } +} + export async function deleteItem(listPath, id) { const listCollectionRef = collection(db, listPath, 'items'); const itemRef = doc(listCollectionRef, id); diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index f252310..2b0d946 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import { ONE_DAY_IN_MILLISECONDS } from '../utils/dates'; import toast from 'react-hot-toast'; import { Button } from './ui/button'; -import { Trash2 } from 'lucide-react'; +import { Trash2, Pencil } from 'lucide-react'; import { getIndicatorColor } from '../utils/helpers'; import { AlertDialog, @@ -16,6 +16,16 @@ import { AlertDialogTitle, AlertDialogTrigger, } from './ui/alert-dialog'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import EditItemForm from './ManageListForms/EditItemForm'; export function ListItem({ listPath, @@ -28,6 +38,9 @@ export function ListItem({ dayInterval, dateCreated, indicator, + isOpen, + handleOpenModal, + dateNextPurchased, }) { const [isAlertOpen, setIsAlertOpen] = useState(false); @@ -94,38 +107,64 @@ export function ListItem({

{indicator}

- - - - - - - Are you absolutely sure? - - This action cannot be undone. Do you really want to delete{' '} - {name}? - - - - setIsAlertOpen(false)}> - Cancel - - + + + + + + + Are you absolutely sure? + + This action cannot be undone. Do you really want to delete{' '} + {name}? + + + + setIsAlertOpen(false)}> + Cancel + + + Continue + + + + + handleOpenModal(id)}> + + + + + + Edit {name} + + Modify the details of the item you'd like to edit. + + + + + + + ); diff --git a/src/components/ManageListForms/EditItemForm.jsx b/src/components/ManageListForms/EditItemForm.jsx new file mode 100644 index 0000000..462c2d9 --- /dev/null +++ b/src/components/ManageListForms/EditItemForm.jsx @@ -0,0 +1,178 @@ +import { useEffect, useState } from 'react'; +import { editItem } from '../../api/firebase'; +import toast from 'react-hot-toast'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; +import { getFutureDate } from '../../utils/dates'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; + +export default function EditItemForm({ + listPath, + id, + name, + quantity, + dateNextPurchased, + handleOpenModal, +}) { + const [formData, setFormData] = useState({ + itemName: name || '', + itemQuantity: quantity || 1, + daysUntilNextPurchase: dateNextPurchased || '', + }); + + useEffect(() => { + setFormData({ + itemName: name, + itemQuantity: quantity, + daysUntilNextPurchase: dateNextPurchased, + }); + }, [name]); + + const handleSubmit = async (event) => { + event.preventDefault(); + + const formattedItemName = formData.itemName + .toLowerCase() + .replace(/^\s\s*/, '') + .replace(/\s\s*$/, '') + .replace(/[^a-zA-Z ]/g, ''); + + const formQuantity = parseInt(formData.itemQuantity, 10); + const newDateNextPurchased = formData.daysUntilNextPurchase; + + if (formattedItemName.length === 0) { + toast.error(`No numbers or special characters.`); + resetItemName(); + return; + } + + const hasChanged = + formattedItemName !== name || + formQuantity !== quantity || + newDateNextPurchased !== dateNextPurchased; + if (!hasChanged) { + toast.error('No changes were made.'); + handleOpenModal(); + return; + } + + try { + let updatedData = { + itemName: formattedItemName, + itemQuantity: formQuantity, + }; + + if (newDateNextPurchased !== dateNextPurchased) { + const futureDate = parseInt(newDateNextPurchased); + const formattedFutureDate = getFutureDate(futureDate); + updatedData.dateNextPurchased = formattedFutureDate; + } else { + updatedData.dateNextPurchased = dateNextPurchased; + } + console.log(); + handleOpenModal(); + await editItem(listPath, id, updatedData); + toast.success(`${formattedItemName} has been updated!`); + } catch (error) { + toast.error(`Failed to edit ${formattedItemName}`); + } + }; + + const handleChange = (event) => { + let { name, value } = event.target; + setFormData((prevFormData) => ({ ...prevFormData, [name]: value })); + }; + + const handleRadioChange = (value) => { + setFormData((prevFormData) => ({ + ...prevFormData, + daysUntilNextPurchase: value, + })); + }; + + return ( +
handleSubmit(event)} + > +
+ + +
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+
+ ); +} From 293f9bf932d568146feccbdaf535f9f7bbb878b7 Mon Sep 17 00:00:00 2001 From: Huda Mabkhoot Date: Sat, 12 Oct 2024 18:04:36 +0300 Subject: [PATCH 2/5] fin al chnages and fixing the issue of listName being null --- src/App.jsx | 2 +- src/components/ListItem.jsx | 68 +++++++++---------- .../ManageListForms/EditItemForm.jsx | 41 +++++------ src/components/ui/dialog.jsx | 2 +- 4 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 3853b0c..982d3d7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,7 +21,7 @@ export function App() { null, ); - const listName = listPath.split('/').pop(); + const listName = listPath ? listPath.split('/').pop() : null; /** * This custom hook holds info about the current signed in user. diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index daf3ec1..cddd20e 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -94,7 +94,7 @@ export function ListItem({ > @@ -105,7 +105,7 @@ export function ListItem({ )} -
+
@@ -113,68 +113,68 @@ export function ListItem({ {indicator}

-
+
+ handleOpenModal(id)}> + + + + + + Edit {name} + + Modify the details of the item you'd like to edit. + + + + + + - + Are you absolutely sure? - + This action cannot be undone. Do you really want to delete{' '} {name}? setIsAlertOpen(false)} > Cancel - + Delete - handleOpenModal(id)}> - - - - - - Edit {name} - - Modify the details of the item you'd like to edit. - - - - - -
diff --git a/src/components/ManageListForms/EditItemForm.jsx b/src/components/ManageListForms/EditItemForm.jsx index 462c2d9..c51fe3e 100644 --- a/src/components/ManageListForms/EditItemForm.jsx +++ b/src/components/ManageListForms/EditItemForm.jsx @@ -26,7 +26,7 @@ export default function EditItemForm({ itemQuantity: quantity, daysUntilNextPurchase: dateNextPurchased, }); - }, [name]); + }, []); const handleSubmit = async (event) => { event.preventDefault(); @@ -45,31 +45,32 @@ export default function EditItemForm({ resetItemName(); return; } - + // check if any of the values have been edited const hasChanged = formattedItemName !== name || formQuantity !== quantity || newDateNextPurchased !== dateNextPurchased; + + // if no changes were made exit early if (!hasChanged) { toast.error('No changes were made.'); handleOpenModal(); return; } - try { - let updatedData = { - itemName: formattedItemName, - itemQuantity: formQuantity, - }; + let updatedData = { + itemName: formattedItemName, + itemQuantity: formQuantity, + }; + // check if dateNextPurchased have been edited to use getFutureDate if not use the same date + if (newDateNextPurchased !== dateNextPurchased) { + const futureDate = parseInt(newDateNextPurchased); + updatedData.dateNextPurchased = getFutureDate(futureDate); + } else { + updatedData.dateNextPurchased = dateNextPurchased; + } - if (newDateNextPurchased !== dateNextPurchased) { - const futureDate = parseInt(newDateNextPurchased); - const formattedFutureDate = getFutureDate(futureDate); - updatedData.dateNextPurchased = formattedFutureDate; - } else { - updatedData.dateNextPurchased = dateNextPurchased; - } - console.log(); + try { handleOpenModal(); await editItem(listPath, id, updatedData); toast.success(`${formattedItemName} has been updated!`); @@ -118,18 +119,18 @@ export default function EditItemForm({ className="flex my-2 items-center justify-center gap-4" id="daysUntilNextPurchase" > -
+
-
+
-
+
diff --git a/src/components/ui/dialog.jsx b/src/components/ui/dialog.jsx index 8154999..386a3a0 100644 --- a/src/components/ui/dialog.jsx +++ b/src/components/ui/dialog.jsx @@ -16,7 +16,7 @@ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( Date: Sat, 12 Oct 2024 20:13:35 +0300 Subject: [PATCH 3/5] Applied changes to the edit and delete icons colors and to the dialog buttons opacity --- src/components/ListItem.jsx | 6 +++--- .../ManageListForms/AddItemForm.jsx | 19 ++++++++++++++----- .../ManageListForms/EditItemForm.jsx | 19 ++++++++++++++----- src/views/List.jsx | 2 +- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index cddd20e..51868ff 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -117,7 +117,7 @@ export function ListItem({ handleOpenModal(id)}> @@ -146,7 +146,7 @@ export function ListItem({ id={id} onClick={() => setIsAlertOpen(true)} > - + @@ -167,7 +167,7 @@ export function ListItem({ Cancel Delete diff --git a/src/components/ManageListForms/AddItemForm.jsx b/src/components/ManageListForms/AddItemForm.jsx index 939358a..3cfaa5c 100644 --- a/src/components/ManageListForms/AddItemForm.jsx +++ b/src/components/ManageListForms/AddItemForm.jsx @@ -98,7 +98,10 @@ export default function AddItemForm({ listPath, data, handleOpenModal }) { name="timeFrame" className="border border-soon text-soon" /> -
@@ -109,13 +112,19 @@ export default function AddItemForm({ listPath, data, handleOpenModal }) { name="timeFrame" className="border border-kind-of-soon text-kind-of-soon" /> -
- -
@@ -141,7 +150,7 @@ export default function AddItemForm({ listPath, data, handleOpenModal }) {
diff --git a/src/components/ManageListForms/EditItemForm.jsx b/src/components/ManageListForms/EditItemForm.jsx index c51fe3e..0655d27 100644 --- a/src/components/ManageListForms/EditItemForm.jsx +++ b/src/components/ManageListForms/EditItemForm.jsx @@ -126,7 +126,10 @@ export default function EditItemForm({ name="timeFrame" className="border border-soon text-soon" /> -
@@ -137,13 +140,19 @@ export default function EditItemForm({ name="timeFrame" className="border border-kind-of-soon text-kind-of-soon" /> -
- -
@@ -169,7 +178,7 @@ export default function EditItemForm({
diff --git a/src/views/List.jsx b/src/views/List.jsx index efa6b61..d1726c6 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -79,7 +79,7 @@ export function List({ data, listPath, listName }) { From ab32d3713780e27a69a558416bdf94d7f91f1e4e Mon Sep 17 00:00:00 2001 From: Huda Mabkhoot Date: Sun, 13 Oct 2024 12:36:59 +0300 Subject: [PATCH 4/5] Matched the buttons in list page to home and added a loading state for list items --- src/components/ListItem.jsx | 4 ++-- src/components/SingleList.jsx | 4 ++-- src/views/List.jsx | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index 51868ff..0c7c145 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -117,7 +117,7 @@ export function ListItem({ handleOpenModal(id)}> @@ -146,7 +146,7 @@ export function ListItem({ id={id} onClick={() => setIsAlertOpen(true)} > - + diff --git a/src/components/SingleList.jsx b/src/components/SingleList.jsx index 135eb3b..687a82d 100644 --- a/src/components/SingleList.jsx +++ b/src/components/SingleList.jsx @@ -34,13 +34,13 @@ export function SingleList({ aria-label="Share list" className="text-green-500 hover:text-green-500 dark:text-ruby-pink dark:hover:text-primary-pink hover:text-opacity-80 dark:hover:text-opacity-80 transform hover:scale-110 transition-transform duration-150 sm:hover:scale-125" > - +
diff --git a/src/views/List.jsx b/src/views/List.jsx index d1726c6..1499634 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -21,6 +21,7 @@ export function List({ data, listPath, listName }) { const [displayData, setDisplayData] = useState([]); const [isOpen, setIsOpen] = useState(false); const [openItemId, setOpenItemId] = useState(null); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { const arrayWithIndicator = data.map((item) => ({ @@ -32,6 +33,12 @@ export function List({ data, listPath, listName }) { setDisplayData(urgencyData); }, [data]); + useEffect(() => { + if (displayData.length > 1) { + setIsLoading(false); + } + }, [displayData]); + const handleAddModal = () => { if (search.length > 0) { setDisplayData(allData); @@ -119,12 +126,33 @@ export function List({ data, listPath, listName }) { /> ))} - {displayData.length === 0 && search.length > 0 && ( + {data.length === 0 && isLoading && ( +
+ + Loading... +
+ )} + {displayData.length === 0 && search.length > 0 && !isLoading && (

No items found. Try searching for a different item!

)} - {data.length === 0 && ( + {data.length === 0 && !isLoading && (

Your list is empty. Start adding some items now! From b8d5d5c1eb9db86ef719ad725a43b3226dc8926f Mon Sep 17 00:00:00 2001 From: Huda Mabkhoot Date: Sun, 13 Oct 2024 17:57:54 +0300 Subject: [PATCH 5/5] Fixed the loading state and some minor design adjustments --- src/App.jsx | 11 ++++- src/components/SingleList.jsx | 2 + src/views/Home.jsx | 2 + src/views/List.jsx | 82 ++++++++++++++++++++--------------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 982d3d7..d59876f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,8 @@ import Login from './views/Login'; export function App() { const [isModalOpen, setIsModalOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + /** * This custom hook takes the path of a shopping list * in our database and syncs it with localStorage for later use. @@ -63,13 +65,20 @@ export function App() { setListPath={setListPath} isModalOpen={isModalOpen} handleShareModalClick={handleShareModalClick} + setIsLoading={setIsLoading} /> } /> + } /> diff --git a/src/components/SingleList.jsx b/src/components/SingleList.jsx index bd4107b..7871c53 100644 --- a/src/components/SingleList.jsx +++ b/src/components/SingleList.jsx @@ -31,6 +31,7 @@ export function SingleList({ setListPath, handleShareModalClick, setSelectedItem, + setIsLoading, }) { const [isAlertOpen, setIsAlertOpen] = useState(false); const [collectionId, setCollectionId] = useState(''); @@ -44,6 +45,7 @@ export function SingleList({ const navigate = useNavigate(); function handleClick() { + setIsLoading(true); setListPath(path); navigate(`/list`); } diff --git a/src/views/Home.jsx b/src/views/Home.jsx index c10ad70..8e63ab6 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -9,6 +9,7 @@ export function Home({ setListPath, isModalOpen, handleShareModalClick, + setIsLoading, }) { const [selectedItem, setSelectedItem] = useState(''); @@ -54,6 +55,7 @@ export function Home({ setListPath={setListPath} handleShareModalClick={handleShareModalClick} setSelectedItem={setSelectedItem} + setIsLoading={setIsLoading} /> ))} diff --git a/src/views/List.jsx b/src/views/List.jsx index 0cc4bf4..9237c3e 100644 --- a/src/views/List.jsx +++ b/src/views/List.jsx @@ -15,15 +15,16 @@ import { import { Button } from '@/components/ui/button'; import { SquarePlus } from 'lucide-react'; -export function List({ data, listPath, listName }) { +export function List({ data, listPath, listName, isLoading, setIsLoading }) { const [search, setSearch] = useState(''); const [allData, setAllData] = useState([]); const [displayData, setDisplayData] = useState([]); const [isOpen, setIsOpen] = useState(false); const [openItemId, setOpenItemId] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [noData, setNoData] = useState(false); useEffect(() => { + setIsLoading(true); const arrayWithIndicator = data.map((item) => ({ ...item, indicator: getIndicator(item), @@ -39,6 +40,13 @@ export function List({ data, listPath, listName }) { } }, [displayData]); + setTimeout(() => { + if (data.length === 0) { + setIsLoading(false); + setNoData(true); + } + }, 2000); + const handleAddModal = () => { if (search.length > 0) { setDisplayData(allData); @@ -75,9 +83,14 @@ export function List({ data, listPath, listName }) { {listName} Description + Description

@@ -89,9 +102,9 @@ export function List({ data, listPath, listName }) { search={search} /> - + @@ -110,27 +123,7 @@ export function List({ data, listPath, listName }) {
-
    - {displayData.map((item) => ( - - ))} -
- {data.length === 0 && isLoading && ( + {isLoading ? (
Loading...
+ ) : ( +
    + {displayData.map((item) => ( + + ))} +
)} - {displayData.length === 0 && search.length > 0 && ( -
-

No items found. Try searching for a different item!

-
- )} - {data.length === 0 && !isLoading && ( + {!isLoading && noData && displayData.length === 0 && (

Your list is empty. Start adding some items now!

)} + {displayData.length === 0 && search.length > 0 && ( +
+

No items found. Try searching for a different item!

+
+ )} )}