From 4f7fa90d51d562edd9abfab4fe51aae7c16fc477 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 16:26:10 +0000 Subject: [PATCH 01/57] refactor: generic types in getFromDb DatabaseManager.ts method --- src/lib/db/DatabaseManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/db/DatabaseManager.ts b/src/lib/db/DatabaseManager.ts index a6425856..447319f8 100644 --- a/src/lib/db/DatabaseManager.ts +++ b/src/lib/db/DatabaseManager.ts @@ -1,4 +1,4 @@ -import { addRxPlugin } from "rxdb"; +import { addRxPlugin, RxDocumentData } from "rxdb"; import { RxDBDevModePlugin } from "rxdb/plugins/dev-mode"; import { getRxStorageDexie } from "rxdb/plugins/storage-dexie"; import { createRxDatabase, RxDatabase } from "rxdb"; @@ -117,14 +117,14 @@ class DatabaseManager { } } - async getFromDb(collection: string) { + async getFromDb(collection: string): Promise[]> { const db = await this.accessDatabase(); const collectionExists = db.collections[collection]; if (!collectionExists) throw new Error(`Collection '${collection}' not found`); const data = await collectionExists.find().exec(); console.log(`Getting data from ${collection}:`, data); - return data; + return data.map((doc) => doc.toJSON() as RxDocumentData); } async addToDb(collectionName: string, document: object) { From c0af107c4af6bb02b97b6a8e3f2aca32fe93bd71 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 16:30:17 +0000 Subject: [PATCH 02/57] refactor: make generic Section.tsx component from NeedsSection.tsx --- src/app/needs/components/NeedsSection.tsx | 21 ---------------- src/app/needs/components/Section.tsx | 30 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 src/app/needs/components/NeedsSection.tsx create mode 100644 src/app/needs/components/Section.tsx diff --git a/src/app/needs/components/NeedsSection.tsx b/src/app/needs/components/NeedsSection.tsx deleted file mode 100644 index 635057e8..00000000 --- a/src/app/needs/components/NeedsSection.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Button from "@/ui/shared/Button"; - -export default function NeedsSection({ categoryData, handleOpen }) { - return ( -
-

{categoryData.category}

-
- {categoryData.needs.map((need, needIndex) => { - return ( -
-
- ); -} diff --git a/src/app/needs/components/Section.tsx b/src/app/needs/components/Section.tsx new file mode 100644 index 00000000..3c7b69a1 --- /dev/null +++ b/src/app/needs/components/Section.tsx @@ -0,0 +1,30 @@ +import Button from "@/ui/shared/Button"; + +interface SectionProps { + categoryData: { + key: string; + items: (U & { label: string })[]; + }; + handleOpen: () => void; +} + +export default function Section({ + categoryData, + handleOpen, +}: SectionProps) { + return ( +
+

{categoryData.key}

+
+ {categoryData.items.map((item, index) => ( +
+
+ ); +} From 631be4103f8607ac08acce2d3993a2daad96d022 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 16:30:36 +0000 Subject: [PATCH 03/57] refactor: make generic Display.tsx component from NeedsDisplay.tsx --- src/app/needs/components/Display.tsx | 88 +++++++++++++++++++++++ src/app/needs/components/NeedsDisplay.tsx | 78 -------------------- 2 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/app/needs/components/Display.tsx delete mode 100644 src/app/needs/components/NeedsDisplay.tsx diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx new file mode 100644 index 00000000..9f7fc371 --- /dev/null +++ b/src/app/needs/components/Display.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useDatabase } from "@/context/DatabaseContext"; +import Modal from "@/ui/shared/Modal"; +import Section from "./Section"; +import { RxDocumentData } from "rxdb"; + +export interface Base { + id: string; + name: string; +} + +interface FilteredData { + key: string; + items: U[]; +} + +interface DisplayProps< + T extends RxDocumentData, + U extends RxDocumentData> +> { + mainKey: keyof T; + relatedKey: keyof U; + mainTable: string; + relatedTable: string; +} + +export default function Display< + T extends RxDocumentData, + U extends RxDocumentData +>({ mainKey, relatedKey, mainTable, relatedTable }: DisplayProps) { + const database = useDatabase(); + const [mainData, setMainData] = useState[]>([]); + const [relatedData, setRelatedData] = useState[]>([]); + + const fetchMainData = async () => { + const response = await database.getFromDb(mainTable); + setMainData(response); + }; + + const fetchRelatedData = async () => { + const response = await database.getFromDb(relatedTable); + setRelatedData(response); + }; + + useEffect(() => { + fetchMainData(); + fetchRelatedData(); + }, []); + + const filteredData: FilteredData[] = mainData.map( + (mainItem) => { + const filteredItems = relatedData + .filter( + (relatedItem) => + (relatedItem[relatedKey] as unknown as string) === + (mainItem[mainKey] as unknown as string) + ) + .map((item) => ({ + ...item, + label: item.name, // Add label here + })) as (U & { label: string })[]; // Explicit type assertion + return { key: mainItem.name, items: filteredItems }; + } + ); + + const [modalOpen, setModalOpen] = useState(false); + const handleOpen = () => setModalOpen(true); + + return ( + <> +
+ {filteredData.map((data, index) => ( +
+ ))} +
+ + + ); +} diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx deleted file mode 100644 index 1b10d698..00000000 --- a/src/app/needs/components/NeedsDisplay.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client"; - -import Button from "@/ui/shared/Button"; -import { useState, useEffect } from "react"; -import { useDatabase } from "@/context/DatabaseContext"; -import retrieveDataObject from "@/lib/utils/retrieveDataObject"; -import { RxDocumentData } from "rxdb"; -import NeedsSection from "./NeedsSection"; - -import Modal from "@/ui/shared/Modal"; - -// Define the types for the category and need data -interface Category { - id: string; - name: string; -} - -interface Need { - name: string; - category: string; // Assuming category is a reference to the category ID -} - -interface CategorizedNeed { - category: string; - needs: Need[]; -} - -export default function NeedsDisplay() { - const database = useDatabase(); - const [categories, setCategories] = useState[]>([]); - const [needs, setNeeds] = useState[]>([]); - - const fetchCategories = async () => { - const response = await database.getFromDb("needs_categories"); - const categories = retrieveDataObject(response); - console.log(categories); - setCategories(categories); - }; - - const fetchNeeds = async () => { - const response = await database.getFromDb("needs"); - const needs = retrieveDataObject(response); - setNeeds(needs); - }; - - useEffect(() => { - fetchCategories(); - fetchNeeds(); - }, []); - - const categorisedNeeds: CategorizedNeed[] = categories.map((category) => { - const categoryNeeds = needs.filter((need) => need.category === category.id); - return { category: category.name, needs: categoryNeeds }; - }); - - const [modalOpen, setModalOpen] = useState(false); - - const handleOpen = () => { - setModalOpen(true); - }; - - return ( - <> -
- {categorisedNeeds.map((categoryData, index) => { - return ( - - ); - })} -
- - - ); -} From ad4300b5f5234c0cefe7204b64614e843917fb36 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 16:36:06 +0000 Subject: [PATCH 04/57] refactor: use generic diplay component in needs page.tsx --- src/app/needs/page.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/needs/page.tsx b/src/app/needs/page.tsx index 16da3f52..e7d40845 100644 --- a/src/app/needs/page.tsx +++ b/src/app/needs/page.tsx @@ -1,5 +1,6 @@ import { Header } from "@/ui/shared/Header"; -import NeedsDisplay from "./components/NeedsDisplay"; +import Display, { Base } from "./components/Display"; +import { RxDocumentData } from "rxdb"; export default function NeedsPage() { return ( @@ -15,7 +16,12 @@ export default function NeedsPage() {

Select what you need from the list below

- + } + mainTable="needs_categories" + relatedTable="needs" + /> ); } From 171f1705cb2191480fc16a7c9df456381147ff3d Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 16:36:34 +0000 Subject: [PATCH 05/57] feat: next actions page.tsx using generic display component to categorise next actions by need --- src/app/needs/next-actions/page.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/app/needs/next-actions/page.tsx diff --git a/src/app/needs/next-actions/page.tsx b/src/app/needs/next-actions/page.tsx new file mode 100644 index 00000000..db80224b --- /dev/null +++ b/src/app/needs/next-actions/page.tsx @@ -0,0 +1,20 @@ +import { Header } from "@/ui/shared/Header"; +import Display, { Base } from "../components/Display"; +import { RxDocumentData } from "rxdb"; + +export default function NextActionsPage() { + return ( + <> +
+

+ Add your own next actions to meet the needs that you selected: +

+ } + mainTable="needs" + relatedTable="next_actions" + /> + + ); +} From ef48b447816c414d8d6d3e8b10f950994afdfaf2 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 17:06:51 +0000 Subject: [PATCH 06/57] feat: add highlighting/filtering for needs and next actions based on expiry date --- src/app/needs/components/Display.tsx | 78 ++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index 9f7fc371..00f4357d 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -13,35 +13,67 @@ export interface Base { interface FilteredData { key: string; - items: U[]; + items: (U & { label: string; highlighted?: boolean })[]; } interface DisplayProps< T extends RxDocumentData, - U extends RxDocumentData> + U extends RxDocumentData > { mainKey: keyof T; relatedKey: keyof U; mainTable: string; relatedTable: string; + filterKey?: keyof T | keyof U; + highlight?: boolean; } +type ExtendedRelatedData = U & { highlighted?: boolean }; + export default function Display< T extends RxDocumentData, U extends RxDocumentData ->({ mainKey, relatedKey, mainTable, relatedTable }: DisplayProps) { +>({ + mainKey, + relatedKey, + mainTable, + relatedTable, + filterKey, + highlight = false, +}: DisplayProps) { const database = useDatabase(); const [mainData, setMainData] = useState[]>([]); - const [relatedData, setRelatedData] = useState[]>([]); + const [relatedData, setRelatedData] = useState[]>([]); const fetchMainData = async () => { const response = await database.getFromDb(mainTable); - setMainData(response); + let data = response; + + if (filterKey && !highlight) { + const now = new Date(); + data = data.filter( + (item) => + new Date(item[filterKey as keyof T] as unknown as string) > now + ); + } + + setMainData(data); }; const fetchRelatedData = async () => { const response = await database.getFromDb(relatedTable); - setRelatedData(response); + let data = response; + + if (filterKey && highlight) { + const now = new Date(); + data = data.map((item) => ({ + ...item, + highlighted: + new Date(item[filterKey as keyof U] as unknown as string) > now, + })) as ExtendedRelatedData[]; + } + + setRelatedData(data); }; useEffect(() => { @@ -49,21 +81,21 @@ export default function Display< fetchRelatedData(); }, []); - const filteredData: FilteredData[] = mainData.map( - (mainItem) => { - const filteredItems = relatedData - .filter( - (relatedItem) => - (relatedItem[relatedKey] as unknown as string) === - (mainItem[mainKey] as unknown as string) - ) - .map((item) => ({ - ...item, - label: item.name, // Add label here - })) as (U & { label: string })[]; // Explicit type assertion - return { key: mainItem.name, items: filteredItems }; - } - ); + const filteredData: FilteredData< + ExtendedRelatedData & { label: string } + >[] = mainData.map((mainItem) => { + const filteredItems = relatedData + .filter( + (relatedItem) => + (relatedItem[relatedKey] as unknown as string) === + (mainItem[mainKey] as unknown as string) + ) + .map((item) => ({ + ...item, + label: item.name, + })) as (ExtendedRelatedData & { label: string })[]; + return { key: mainItem.name, items: filteredItems }; + }); const [modalOpen, setModalOpen] = useState(false); const handleOpen = () => setModalOpen(true); @@ -75,8 +107,8 @@ export default function Display<
From 93db53cfa84a4d1d85d7cb73bfa5b52f39fa2c5d Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 17:07:13 +0000 Subject: [PATCH 07/57] refactor: adapt needs page.tsx with new display component --- src/app/needs/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/needs/page.tsx b/src/app/needs/page.tsx index e7d40845..733bf2d3 100644 --- a/src/app/needs/page.tsx +++ b/src/app/needs/page.tsx @@ -21,6 +21,8 @@ export default function NeedsPage() { relatedKey={"category" as keyof RxDocumentData} mainTable="needs_categories" relatedTable="needs" + filterKey={"selectedExpiry" as keyof RxDocumentData} + highlight={true} /> ); From ee1988d66eb6c8e4f8704f014e38482dff0ef425 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 17:07:25 +0000 Subject: [PATCH 08/57] refactor: adapt next actions page.tsx with new display component --- src/app/needs/next-actions/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/needs/next-actions/page.tsx b/src/app/needs/next-actions/page.tsx index db80224b..d1aeea1f 100644 --- a/src/app/needs/next-actions/page.tsx +++ b/src/app/needs/next-actions/page.tsx @@ -14,6 +14,8 @@ export default function NextActionsPage() { relatedKey={"need" as keyof RxDocumentData} mainTable="needs" relatedTable="next_actions" + filterKey={"selectedExpiry" as keyof RxDocumentData} + highlight={false} /> ); From 83ec5b24a7798987cf89f4ef43a6812cdcc1f466 Mon Sep 17 00:00:00 2001 From: maxitect Date: Wed, 11 Dec 2024 17:46:05 +0000 Subject: [PATCH 09/57] style: use highlighted key to conditionally style needs items --- src/app/needs/components/Section.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/needs/components/Section.tsx b/src/app/needs/components/Section.tsx index 3c7b69a1..b55f8638 100644 --- a/src/app/needs/components/Section.tsx +++ b/src/app/needs/components/Section.tsx @@ -1,9 +1,10 @@ import Button from "@/ui/shared/Button"; +import clsx from "clsx"; interface SectionProps { categoryData: { key: string; - items: (U & { label: string })[]; + items: (U & { label: string; highlighted?: boolean })[]; }; handleOpen: () => void; } @@ -19,8 +20,11 @@ export default function Section({ {categoryData.items.map((item, index) => ( - )} - +
+

Step {needsStep} of 3

+ {needsStep > 1 && ( + + )} + -
-

{title}

-

- Select the button that best describes meeting this need right now. -

+
+

{title}

+

+ Select the button that best describes meeting this need right now. +

-
- {backButton && ( -
-
+
+
- )} - +
+
); } From 1537ade592d69acd4d0827fae6e877110a8897ed Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 08:40:46 +0000 Subject: [PATCH 13/57] refactor: update needs display to extend generic needs component with modal --- src/app/needs/components/NeedsDisplay.tsx | 309 ++++++++-------------- 1 file changed, 103 insertions(+), 206 deletions(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index 4566fe11..77e558e2 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -2,276 +2,173 @@ import { useState, useEffect } from "react"; import { useDatabase } from "@/context/DatabaseContext"; -import retrieveDataObject from "@/lib/utils/retrieveDataObject"; -import { RxDocumentData } from "rxdb"; -import NeedsSection from "./NeedsSection"; +import Display, { Base } from "./Display"; import NeedsModal from "./NeedsModal"; +import { RxDocumentData } from "rxdb"; -export interface Category { - id: string; - name: string; -} - -export interface Need { - id: string; - name: string; - category: string; -} - -interface CategorizedNeed { +interface Category extends RxDocumentData {} +interface Need extends RxDocumentData { category: string; - needs: Need[]; } export default function NeedsDisplay() { const database = useDatabase(); - const [categories, setCategories] = useState[]>([]); - const [needs, setNeeds] = useState[]>([]); - const [urgent, setUrgent] = useState(0); - const [effortful, setEffortful] = useState(0); - const [worthDoing, setWorthDoing] = useState(0); - const [positiveLabel, setPositiveLabel] = useState("urgent"); - const [negativeLabel, setNegativeLabel] = useState("not urgent"); + const [modalOpen, setModalOpen] = useState(false); + const [selectedNeed, setSelectedNeed] = useState(null); + const [needsStep, setNeedsStep] = useState(1); + const [urgent, setUrgent] = useState(0); + const [effortful, setEffortful] = useState(0); + const [worthDoing, setWorthDoing] = useState(0); - const fetchCategories = async () => { - const response = await database.getFromDb("needs_categories"); - const categories = retrieveDataObject(response); + const [positiveLabel, setPositiveLabel] = useState("urgent"); + const [negativeLabel, setNegativeLabel] = useState("not urgent"); - setCategories(categories); + const handleItemClick = (item: Need) => { + setSelectedNeed(item); + setModalOpen(true); }; - const fetchNeeds = async () => { - const response = await database.getFromDb("needs"); - const needs = retrieveDataObject(response); - setNeeds(needs); + const handleCloseModal = () => { + setModalOpen(false); + setSelectedNeed(null); + setNeedsStep(1); + resetNeuros(); }; - useEffect(() => { - fetchCategories(); - fetchNeeds(); - }, []); - - const categorisedNeeds: CategorizedNeed[] = categories.map((category) => { - const categoryNeeds = needs.filter((need) => need.category === category.id); - return { category: category.name, needs: categoryNeeds }; - }); - - const [modalOpen, setModalOpen] = useState(false); - const [selectedNeed, setSelectedNeed] = useState(""); - const [needsStep, setNeedsStep] = useState(1); + const handleStepIncrease = () => { + setNeedsStep((prev) => prev + 1); + }; - const handleOpen = (need: string) => { - setModalOpen(true); - setSelectedNeed(need); + const handleBackClick = () => { + setNeedsStep((prev) => Math.max(1, prev - 1)); + if (needsStep === 2) adjustUrgency(); + if (needsStep === 3) adjustEffort(); }; - const handleStepIncrease = () => { - setNeedsStep((prevStep) => prevStep + 1); + const adjustUrgency = () => { + urgent > 0 ? handleDecrease(setUrgent) : handleIncrease(setUrgent); }; - const handleBackClick = () => { - setNeedsStep((prevStep) => prevStep - 1); - { - if (needsStep === 2 && urgent > 0) { - handleDecrease(setUrgent); - } else if (needsStep === 2 && urgent < 0) { - handleIncrease(setUrgent); - } else if (needsStep === 3 && effortful > 0) { - handleDecrease(setEffortful); - } else if (needsStep === 3 && effortful < 0) { - handleIncrease(setEffortful); - } - } + + const adjustEffort = () => { + effortful > 0 ? handleDecrease(setEffortful) : handleIncrease(setEffortful); }; const handleIncrease = ( setter: React.Dispatch> - ) => { - setter((prev) => prev + 1); - }; + ) => setter((prev) => prev + 1); + const handleDecrease = ( setter: React.Dispatch> - ) => { - setter((prev) => prev - 1); - }; - - const updateNeedWithAction = async (needName: string, action: string) => { - try { - const selectedNeed = needs.find((need) => need.name === needName); - - if (selectedNeed) { - const docId = selectedNeed.id; - - await database.updateDocument("needs", docId, "mood", action); - await database.updateDocument( - "needs", - docId, - "selectedExpiry", - new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString() - ); - console.log(`Updated ${needName} with action: ${action}`); - } else { - console.log(`Need ${needName} not found`); - } - } catch (error) { - console.error(`Failed to update need: ${needName}`, error); - } - }; + ) => setter((prev) => prev - 1); const handlePositiveClick = () => { - if (needsStep === 1) { - handleIncrease(setUrgent); - } else if (needsStep === 2) { - handleIncrease(setEffortful); - } else if (needsStep === 3) { + if (needsStep === 1) handleIncrease(setUrgent); + else if (needsStep === 2) handleIncrease(setEffortful); + else if (needsStep === 3) { handleIncrease(setWorthDoing); - - setModalOpen(false); - setNeedsStep(1); + const action = determineAction(); + updateNeedWithAction(action); + handleCloseModal(); } + handleStepIncrease(); }; const handleNegativeClick = () => { - if (needsStep === 1) { - handleDecrease(setUrgent); - } else if (needsStep === 2) { - handleDecrease(setEffortful); - } else if (needsStep === 3) { + if (needsStep === 1) handleDecrease(setUrgent); + else if (needsStep === 2) handleDecrease(setEffortful); + else if (needsStep === 3) { handleDecrease(setWorthDoing); - - setModalOpen(false); - setNeedsStep(1); + const action = determineAction(); + updateNeedWithAction(action); + handleCloseModal(); } - }; - - const resetNeuros = () => { - setUrgent(0); - setEffortful(0); - setWorthDoing(0); - }; - - const handleLabelChange = () => { - switch (needsStep) { - case 1: - setPositiveLabel("urgent"); - setNegativeLabel("not urgent"); - break; - case 2: - setPositiveLabel("A lot of effort"); - setNegativeLabel("A little effort"); - break; - case 3: - setPositiveLabel("worth doing"); - setNegativeLabel("not worth doing"); - break; - } - }; - - const handleStepAction = () => { handleStepIncrease(); }; - useEffect(() => { - handleLabelChange(); - console.log(`selected need: ${selectedNeed}`); - console.log(`urgency: ${urgent}`); - console.log(`effort: ${effortful}`); - console.log(`worthDoing: ${worthDoing}`); - }, [needsStep]); - - useEffect(() => { - if (modalOpen) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = "auto"; - } - - return () => { - document.body.style.overflow = "auto"; - }; - }, [modalOpen]); - - useEffect(() => { - if (urgent !== 0 && effortful !== 0 && worthDoing !== 0) { - const action = determineAction(urgent, effortful, worthDoing); - updateNeedWithAction(selectedNeed, action); - } - resetNeuros(); - }, [worthDoing]); - - function determineAction( - urgent: number, - effortful: number, - worthDoing: number - ): string { + const determineAction = (): string => { switch (true) { case urgent === 1 && effortful === 1 && worthDoing === 1: - console.log("interest"); return "interest"; case urgent === -1 && effortful === -1 && worthDoing === -1: - console.log("guilt"); return "guilt"; case urgent === 1 && effortful === -1 && worthDoing === -1: - console.log("freeze"); return "freeze"; case urgent === 1 && effortful === 1 && worthDoing === -1: - console.log("fight/flight"); return "fight/flight"; case urgent === 1 && effortful === -1 && worthDoing === 1: - console.log("joy"); return "joy"; case urgent === -1 && effortful === -1 && worthDoing === 1: - console.log("content"); return "content"; case urgent === -1 && effortful === 1 && worthDoing === 1: - console.log("relief"); return "relief"; case urgent === -1 && effortful === 1 && worthDoing === -1: - console.log("distress"); return "distress"; default: return "Invalid input"; } - } + }; - const handleCloseClick = () => { - resetNeuros(); - setModalOpen(false); - setNeedsStep(1); + const updateNeedWithAction = async (action: string) => { + if (!selectedNeed) return; + try { + await database.updateDocument("needs", selectedNeed.id, "mood", action); + await database.updateDocument( + "needs", + selectedNeed.id, + "selectedExpiry", + new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString() + ); + console.log(`Updated ${selectedNeed.name} with action: ${action}`); + } catch (error) { + console.error(`Failed to update need: ${selectedNeed.name}`, error); + } }; + const resetNeuros = () => { + setUrgent(0); + setEffortful(0); + setWorthDoing(0); + }; + + useEffect(() => { + switch (needsStep) { + case 1: + setPositiveLabel("urgent"); + setNegativeLabel("not urgent"); + break; + case 2: + setPositiveLabel("A lot of effort"); + setNegativeLabel("A little effort"); + break; + case 3: + setPositiveLabel("worth doing"); + setNegativeLabel("not worth doing"); + break; + } + }, [needsStep]); + return ( <> -
- {categorisedNeeds.map((categoryData, index) => { - return ( - - ); - })} -
+ + mainKey="id" + relatedKey="category" + mainTable="needs_categories" + relatedTable="needs" + onItemClick={handleItemClick} + /> { - handleStepAction(); - handlePositiveClick(); - }, - }} - backButton={{ - label: negativeLabel, - action: () => { - handleStepAction(); - handleNegativeClick(); - }, - }} - title={`You have selected ~${selectedNeed}~`} + title={`You have selected ~${selectedNeed?.name}~`} needsStep={needsStep} + positiveLabel={positiveLabel} + negativeLabel={negativeLabel} + urgent={urgent} + effortful={effortful} + worthDoing={worthDoing} + handlePositiveClick={handlePositiveClick} + handleNegativeClick={handleNegativeClick} handleBackClick={handleBackClick} - handleCloseClick={handleCloseClick} + handleCloseClick={handleCloseModal} /> ); From 4c4eebe999781dcbf885b827057f3a29b5c27fb7 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 08:41:23 +0000 Subject: [PATCH 14/57] refactor: update section component to take multiple callback function props --- src/app/needs/components/Section.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/needs/components/Section.tsx b/src/app/needs/components/Section.tsx index b55f8638..3e276cea 100644 --- a/src/app/needs/components/Section.tsx +++ b/src/app/needs/components/Section.tsx @@ -6,7 +6,7 @@ interface SectionProps { key: string; items: (U & { label: string; highlighted?: boolean })[]; }; - handleOpen: () => void; + handleOpen: (item: U & { label: string; highlighted?: boolean }) => void; } export default function Section({ @@ -25,7 +25,7 @@ export default function Section({ "font-normal text-nowrap", item.highlighted ? "bg-twd-primary-purple" : "bg-gray-600" )} - onClick={handleOpen} + onClick={() => handleOpen(item)} /> ))} From a408b6dbff16d094cf0f6fe095131e668e2c7c95 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 08:41:54 +0000 Subject: [PATCH 15/57] refactor: update needs page.tsx to use needs display extended component --- src/app/needs/page.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/app/needs/page.tsx b/src/app/needs/page.tsx index 733bf2d3..16da3f52 100644 --- a/src/app/needs/page.tsx +++ b/src/app/needs/page.tsx @@ -1,6 +1,5 @@ import { Header } from "@/ui/shared/Header"; -import Display, { Base } from "./components/Display"; -import { RxDocumentData } from "rxdb"; +import NeedsDisplay from "./components/NeedsDisplay"; export default function NeedsPage() { return ( @@ -16,14 +15,7 @@ export default function NeedsPage() {

Select what you need from the list below

- } - mainTable="needs_categories" - relatedTable="needs" - filterKey={"selectedExpiry" as keyof RxDocumentData} - highlight={true} - /> + ); } From f610b0fe4cfed18827150045922b0f6dd66596e3 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 11:05:46 +0000 Subject: [PATCH 16/57] feat: combined needs display component updating database with mood and expiry and highlight --- src/app/needs/components/NeedsDisplay.tsx | 49 ++++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index 77e558e2..bb5531f7 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -66,9 +66,10 @@ export default function NeedsDisplay() { else if (needsStep === 2) handleIncrease(setEffortful); else if (needsStep === 3) { handleIncrease(setWorthDoing); - const action = determineAction(); - updateNeedWithAction(action); - handleCloseModal(); + //const action = determineAction(); + setModalOpen(false); + //updateNeedWithAction(action); + //handleCloseModal(); } handleStepIncrease(); }; @@ -78,9 +79,10 @@ export default function NeedsDisplay() { else if (needsStep === 2) handleDecrease(setEffortful); else if (needsStep === 3) { handleDecrease(setWorthDoing); - const action = determineAction(); - updateNeedWithAction(action); - handleCloseModal(); + //const action = determineAction(); + setModalOpen(false); + //updateNeedWithAction(action); + //handleCloseModal(); } handleStepIncrease(); }; @@ -124,6 +126,23 @@ export default function NeedsDisplay() { } }; + const handleLabelChange = () => { + switch (needsStep) { + case 1: + setPositiveLabel("urgent"); + setNegativeLabel("not urgent"); + break; + case 2: + setPositiveLabel("A lot of effort"); + setNegativeLabel("A little effort"); + break; + case 3: + setPositiveLabel("worth doing"); + setNegativeLabel("not worth doing"); + break; + } + }; + const resetNeuros = () => { setUrgent(0); setEffortful(0); @@ -147,6 +166,22 @@ export default function NeedsDisplay() { } }, [needsStep]); + useEffect(() => { + if (urgent !== 0 && effortful !== 0 && worthDoing !== 0) { + const action = determineAction(); + updateNeedWithAction(action); + } + resetNeuros(); + }, [worthDoing]); + + useEffect(() => { + handleLabelChange(); + console.log(`selected need: ${selectedNeed}`); + console.log(`urgency: ${urgent}`); + console.log(`effort: ${effortful}`); + console.log(`worthDoing: ${worthDoing}`); + }, [needsStep]); + return ( <> @@ -154,6 +189,8 @@ export default function NeedsDisplay() { relatedKey="category" mainTable="needs_categories" relatedTable="needs" + filterKey={"selectedExpiry" as keyof RxDocumentData} + highlight={true} onItemClick={handleItemClick} /> Date: Thu, 12 Dec 2024 11:11:40 +0000 Subject: [PATCH 17/57] refactor: remove comments in NeedsDisplay.tsx --- src/app/needs/components/NeedsDisplay.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index bb5531f7..a51944fa 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -66,10 +66,7 @@ export default function NeedsDisplay() { else if (needsStep === 2) handleIncrease(setEffortful); else if (needsStep === 3) { handleIncrease(setWorthDoing); - //const action = determineAction(); setModalOpen(false); - //updateNeedWithAction(action); - //handleCloseModal(); } handleStepIncrease(); }; @@ -79,10 +76,7 @@ export default function NeedsDisplay() { else if (needsStep === 2) handleDecrease(setEffortful); else if (needsStep === 3) { handleDecrease(setWorthDoing); - //const action = determineAction(); setModalOpen(false); - //updateNeedWithAction(action); - //handleCloseModal(); } handleStepIncrease(); }; From 9ecd2c113ae567f8616273dc50a1df4473de5b62 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 11:36:31 +0000 Subject: [PATCH 18/57] refactor: remove any types from Display.tsx --- src/app/needs/components/Display.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index 52b7aec6..db79e511 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -10,6 +10,12 @@ export interface Base { name: string; } +interface ModalProps { + modalOpen: boolean; + onClose: () => void; + selectedItem: U | null; +} + interface FilteredData { key: string; items: (U & { label: string; highlighted?: boolean })[]; @@ -26,8 +32,8 @@ interface DisplayProps< filterKey?: keyof T | keyof U; highlight?: boolean; onItemClick?: (item: U & { label: string }) => void; - modalComponent?: React.ElementType; // Accepts a custom modal component - modalProps?: Record; // Additional props for the modal + modalComponent?: React.ComponentType>; + modalProps?: Partial>; } type ExtendedRelatedData = U & { highlighted?: boolean }; From a0b3a4f30ecd66aceee438eb64ad7576e1f423e2 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 11:43:01 +0000 Subject: [PATCH 19/57] refactor: delete unused NeedsSection.tsx --- src/app/needs/components/NeedsSection.tsx | 33 ----------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/app/needs/components/NeedsSection.tsx diff --git a/src/app/needs/components/NeedsSection.tsx b/src/app/needs/components/NeedsSection.tsx deleted file mode 100644 index 2e1ddff1..00000000 --- a/src/app/needs/components/NeedsSection.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Button from "@/ui/shared/Button"; -import { Need } from "./NeedsDisplay"; - -interface NeedsSectionProps { - categoryData: { - category: string; - needs: Need[]; - }; - handleOpen: (needName: string) => void; -} - -export default function NeedsSection({ - categoryData, - handleOpen, -}: NeedsSectionProps) { - return ( -
-

{categoryData.category}

-
- {categoryData.needs.map((need: Need, needIndex: number) => { - return ( -
-
- ); -} From a9c7ba9b148a2aa6993bc5b2d12474c9ef394220 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 12:15:59 +0000 Subject: [PATCH 20/57] build: eslint plugin for react hooks --- package-lock.json | 20 +++++++++++++++++--- package.json | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index aff8590b..114e0924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@types/react-plotly.js": "^2.6.3", "eslint": "^8", "eslint-config-next": "15.0.3", + "eslint-plugin-react-hooks": "^5.1.0-rc-79ddf5b5-20241210", "husky": "^9.1.7", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -7073,6 +7074,19 @@ } } }, + "node_modules/eslint-config-next/node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -7320,9 +7334,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", - "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", + "version": "5.1.0-rc-79ddf5b5-20241210", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-79ddf5b5-20241210.tgz", + "integrity": "sha512-OwBcJH9GxpZzkgLl/MKCgHuQLThwAFSa2ue5g0Z6fAriEsvc/ly7j0bbFsXDGEDwCtsvOulOk2d10AqBPBBShQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 966d3c8a..43d15b0f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@types/react-plotly.js": "^2.6.3", "eslint": "^8", "eslint-config-next": "15.0.3", + "eslint-plugin-react-hooks": "^5.1.0-rc-79ddf5b5-20241210", "husky": "^9.1.7", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", From 8ebd2ac9c3ce5406c656ced1f770da96b308cca9 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 12:16:18 +0000 Subject: [PATCH 21/57] refactor: fix eslint errors in Display.tsx --- src/app/needs/components/Display.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index db79e511..9c7dde18 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useDatabase } from "@/context/DatabaseContext"; import Section from "./Section"; import { RxDocumentData } from "rxdb"; @@ -58,7 +58,7 @@ export default function Display< const [modalOpen, setModalOpen] = useState(false); const [selectedItem, setSelectedItem] = useState(null); - const fetchMainData = async () => { + const fetchMainData = useCallback(async () => { const response = await database.getFromDb(mainTable); let data = response; @@ -71,9 +71,9 @@ export default function Display< } setMainData(data); - }; + }, [database, mainTable, filterKey, highlight]); - const fetchRelatedData = async () => { + const fetchRelatedData = useCallback(async () => { const response = await database.getFromDb(relatedTable); let data = response; @@ -87,12 +87,12 @@ export default function Display< } setRelatedData(data); - }; + }, [database, relatedTable, filterKey, highlight]); useEffect(() => { fetchMainData(); fetchRelatedData(); - }, []); + }, [fetchMainData, fetchRelatedData]); const filteredData: FilteredData< ExtendedRelatedData & { label: string } From 1a75fba6c5aafc1b02afab86e41837eeed0d17fd Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 12:16:38 +0000 Subject: [PATCH 22/57] refactor: fix eslint errors in NeedsDisplay.tsx --- src/app/needs/components/NeedsDisplay.tsx | 100 +++++++++++++--------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index a51944fa..17088182 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -1,12 +1,12 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useDatabase } from "@/context/DatabaseContext"; import Display, { Base } from "./Display"; import NeedsModal from "./NeedsModal"; import { RxDocumentData } from "rxdb"; -interface Category extends RxDocumentData {} +type Category = RxDocumentData; interface Need extends RxDocumentData { category: string; } @@ -46,11 +46,19 @@ export default function NeedsDisplay() { }; const adjustUrgency = () => { - urgent > 0 ? handleDecrease(setUrgent) : handleIncrease(setUrgent); + if (urgent > 0) { + handleDecrease(setUrgent); + } else { + handleIncrease(setUrgent); + } }; const adjustEffort = () => { - effortful > 0 ? handleDecrease(setEffortful) : handleIncrease(setEffortful); + if (effortful > 0) { + handleDecrease(setEffortful); + } else { + handleIncrease(setEffortful); + } }; const handleIncrease = ( @@ -64,24 +72,22 @@ export default function NeedsDisplay() { const handlePositiveClick = () => { if (needsStep === 1) handleIncrease(setUrgent); else if (needsStep === 2) handleIncrease(setEffortful); - else if (needsStep === 3) { - handleIncrease(setWorthDoing); - setModalOpen(false); - } - handleStepIncrease(); + else if (needsStep === 3) handleIncrease(setWorthDoing); + + if (needsStep === 3) setModalOpen(false); + else handleStepIncrease(); }; const handleNegativeClick = () => { if (needsStep === 1) handleDecrease(setUrgent); else if (needsStep === 2) handleDecrease(setEffortful); - else if (needsStep === 3) { - handleDecrease(setWorthDoing); - setModalOpen(false); - } - handleStepIncrease(); + else if (needsStep === 3) handleDecrease(setWorthDoing); + + if (needsStep === 3) setModalOpen(false); + else handleStepIncrease(); }; - const determineAction = (): string => { + const determineAction = useCallback((): string => { switch (true) { case urgent === 1 && effortful === 1 && worthDoing === 1: return "interest"; @@ -102,25 +108,28 @@ export default function NeedsDisplay() { default: return "Invalid input"; } - }; - - const updateNeedWithAction = async (action: string) => { - if (!selectedNeed) return; - try { - await database.updateDocument("needs", selectedNeed.id, "mood", action); - await database.updateDocument( - "needs", - selectedNeed.id, - "selectedExpiry", - new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString() - ); - console.log(`Updated ${selectedNeed.name} with action: ${action}`); - } catch (error) { - console.error(`Failed to update need: ${selectedNeed.name}`, error); - } - }; + }, [urgent, effortful, worthDoing]); + + const updateNeedWithAction = useCallback( + async (action: string) => { + if (!selectedNeed) return; + try { + await database.updateDocument("needs", selectedNeed.id, "mood", action); + await database.updateDocument( + "needs", + selectedNeed.id, + "selectedExpiry", + new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString() + ); + console.log(`Updated ${selectedNeed.name} with action: ${action}`); + } catch (error) { + console.error(`Failed to update need: ${selectedNeed.name}`, error); + } + }, + [selectedNeed, database] + ); - const handleLabelChange = () => { + const handleLabelChange = useCallback(() => { switch (needsStep) { case 1: setPositiveLabel("urgent"); @@ -135,7 +144,7 @@ export default function NeedsDisplay() { setNegativeLabel("not worth doing"); break; } - }; + }, [needsStep]); const resetNeuros = () => { setUrgent(0); @@ -161,12 +170,18 @@ export default function NeedsDisplay() { }, [needsStep]); useEffect(() => { - if (urgent !== 0 && effortful !== 0 && worthDoing !== 0) { + if (urgent !== 0 && effortful !== 0 && worthDoing !== 0 && selectedNeed) { const action = determineAction(); - updateNeedWithAction(action); + updateNeedWithAction(action).then(() => resetNeuros()); } - resetNeuros(); - }, [worthDoing]); + }, [ + determineAction, + updateNeedWithAction, + urgent, + effortful, + worthDoing, + selectedNeed, + ]); useEffect(() => { handleLabelChange(); @@ -174,7 +189,14 @@ export default function NeedsDisplay() { console.log(`urgency: ${urgent}`); console.log(`effort: ${effortful}`); console.log(`worthDoing: ${worthDoing}`); - }, [needsStep]); + }, [ + effortful, + handleLabelChange, + needsStep, + selectedNeed, + urgent, + worthDoing, + ]); return ( <> From 28dbd2fadb6f81a8c541ed7ed85c076e8ddbe332 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 13:26:57 +0000 Subject: [PATCH 23/57] refactor: remove toJSON method from db get method in DatabaseManager.ts --- src/lib/db/DatabaseManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/db/DatabaseManager.ts b/src/lib/db/DatabaseManager.ts index 50aae1ff..4b470dc3 100644 --- a/src/lib/db/DatabaseManager.ts +++ b/src/lib/db/DatabaseManager.ts @@ -127,7 +127,7 @@ class DatabaseManager { throw new Error(`Collection '${collection}' not found`); const data = await collectionExists.find().exec(); console.log(`Getting data from ${collection}:`, data); - return data.map((doc) => doc.toJSON() as RxDocumentData); + return data; } async addToDb(collectionName: string, document: object) { From 1eadf78a8ed6cd0fdd4a7b94aefda62fe9b0dd9f Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 14:24:24 +0000 Subject: [PATCH 24/57] refactor: access data using toJSON in Display.tsx --- src/app/needs/components/Display.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index 9c7dde18..a8125871 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from "react"; import { useDatabase } from "@/context/DatabaseContext"; import Section from "./Section"; -import { RxDocumentData } from "rxdb"; +import { RxDocument, RxDocumentData } from "rxdb"; export interface Base { id: string; @@ -59,8 +59,8 @@ export default function Display< const [selectedItem, setSelectedItem] = useState(null); const fetchMainData = useCallback(async () => { - const response = await database.getFromDb(mainTable); - let data = response; + const response = await database.getFromDb>(mainTable); + let data = response.map((doc) => doc.toJSON() as RxDocumentData); if (filterKey && !highlight) { const now = new Date(); @@ -74,8 +74,8 @@ export default function Display< }, [database, mainTable, filterKey, highlight]); const fetchRelatedData = useCallback(async () => { - const response = await database.getFromDb(relatedTable); - let data = response; + const response = await database.getFromDb>(relatedTable); + let data = response.map((doc) => doc.toJSON() as RxDocumentData); if (filterKey && highlight) { const now = new Date(); From 251d1688a260afe5cb6f101dabaa153a0b3c1f87 Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 16:35:50 +0000 Subject: [PATCH 25/57] chore: add chainEnd prop to generic Display.tsx component --- src/app/needs/components/Display.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index a8125871..15af98a9 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -34,6 +34,7 @@ interface DisplayProps< onItemClick?: (item: U & { label: string }) => void; modalComponent?: React.ComponentType>; modalProps?: Partial>; + chainEnd: number; } type ExtendedRelatedData = U & { highlighted?: boolean }; @@ -51,6 +52,7 @@ export default function Display< onItemClick, modalComponent: CustomModal, modalProps = {}, + chainEnd, }: DisplayProps) { const database = useDatabase(); const [mainData, setMainData] = useState[]>([]); @@ -92,7 +94,7 @@ export default function Display< useEffect(() => { fetchMainData(); fetchRelatedData(); - }, [fetchMainData, fetchRelatedData]); + }, [fetchMainData, fetchRelatedData, chainEnd]); const filteredData: FilteredData< ExtendedRelatedData & { label: string } From 36330cf2177f7438d6064efcf9be495cc32c365c Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 16:36:22 +0000 Subject: [PATCH 26/57] chore: ad chainEnd state and pass to display component in NeedsDisplay.tsx --- src/app/needs/components/NeedsDisplay.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index 17088182..d6587b2b 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -19,6 +19,7 @@ export default function NeedsDisplay() { const [urgent, setUrgent] = useState(0); const [effortful, setEffortful] = useState(0); const [worthDoing, setWorthDoing] = useState(0); + const [chainEnd, setChainEnd] = useState(0); const [positiveLabel, setPositiveLabel] = useState("urgent"); const [negativeLabel, setNegativeLabel] = useState("not urgent"); @@ -172,7 +173,11 @@ export default function NeedsDisplay() { useEffect(() => { if (urgent !== 0 && effortful !== 0 && worthDoing !== 0 && selectedNeed) { const action = determineAction(); - updateNeedWithAction(action).then(() => resetNeuros()); + updateNeedWithAction(action).then(() => { + resetNeuros(); + setChainEnd((prevChainEnd) => prevChainEnd + 1); + setNeedsStep(1); + }); } }, [ determineAction, @@ -208,6 +213,7 @@ export default function NeedsDisplay() { filterKey={"selectedExpiry" as keyof RxDocumentData} highlight={true} onItemClick={handleItemClick} + chainEnd={chainEnd} /> Date: Thu, 12 Dec 2024 17:20:17 +0000 Subject: [PATCH 27/57] chore: add button to next actions in NeedsDisplay.tsx --- src/app/needs/components/NeedsDisplay.tsx | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index d6587b2b..cd4be637 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -5,6 +5,9 @@ import { useDatabase } from "@/context/DatabaseContext"; import Display, { Base } from "./Display"; import NeedsModal from "./NeedsModal"; import { RxDocumentData } from "rxdb"; +import Button from "@/ui/shared/Button"; +import { useRouter } from "next/navigation"; +import { Header } from "@/ui/shared/Header"; type Category = RxDocumentData; interface Need extends RxDocumentData { @@ -12,6 +15,7 @@ interface Need extends RxDocumentData { } export default function NeedsDisplay() { + const router = useRouter(); const database = useDatabase(); const [modalOpen, setModalOpen] = useState(false); const [selectedNeed, setSelectedNeed] = useState(null); @@ -153,6 +157,13 @@ export default function NeedsDisplay() { setWorthDoing(0); }; + const openInfo = () => { + const basePath = window.location.pathname.endsWith("/") + ? window.location.pathname.slice(0, -1) + : window.location.pathname; + router.push(`${basePath}/next-actions`); + }; + useEffect(() => { switch (needsStep) { case 1: @@ -205,6 +216,24 @@ export default function NeedsDisplay() { return ( <> +
+
+
+

+ What do you need right now? +

+

+ Select what you need from the list below +

mainKey="id" relatedKey="category" From 56f07a01eedadd97954fd462420582b2d5239eed Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 17:20:41 +0000 Subject: [PATCH 28/57] refactor: move all page elements into NeedsDisplay.tsx --- src/app/needs/page.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/app/needs/page.tsx b/src/app/needs/page.tsx index 16da3f52..f97eeea8 100644 --- a/src/app/needs/page.tsx +++ b/src/app/needs/page.tsx @@ -1,20 +1,8 @@ -import { Header } from "@/ui/shared/Header"; import NeedsDisplay from "./components/NeedsDisplay"; export default function NeedsPage() { return ( <> -
-

- What do you need right now? -

-

- Select what you need from the list below -

); From 2845a66bda6d47c9d8480feea5f96e7738361eba Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 17:21:09 +0000 Subject: [PATCH 29/57] chore: add missing chainEnd attribute in display component on next actions page.tsx --- src/app/needs/next-actions/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/needs/next-actions/page.tsx b/src/app/needs/next-actions/page.tsx index d1aeea1f..2ea75c6d 100644 --- a/src/app/needs/next-actions/page.tsx +++ b/src/app/needs/next-actions/page.tsx @@ -16,6 +16,7 @@ export default function NextActionsPage() { relatedTable="next_actions" filterKey={"selectedExpiry" as keyof RxDocumentData} highlight={false} + chainEnd={0} /> ); From 3c2eb7f7a54559485b6714aa5728043d15c9af9a Mon Sep 17 00:00:00 2001 From: maxitect Date: Thu, 12 Dec 2024 17:32:26 +0000 Subject: [PATCH 30/57] refactor: move header component out of NeedsDisplay.tsx --- src/app/needs/components/NeedsDisplay.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/needs/components/NeedsDisplay.tsx b/src/app/needs/components/NeedsDisplay.tsx index cd4be637..1e117271 100644 --- a/src/app/needs/components/NeedsDisplay.tsx +++ b/src/app/needs/components/NeedsDisplay.tsx @@ -7,7 +7,6 @@ import NeedsModal from "./NeedsModal"; import { RxDocumentData } from "rxdb"; import Button from "@/ui/shared/Button"; import { useRouter } from "next/navigation"; -import { Header } from "@/ui/shared/Header"; type Category = RxDocumentData; interface Need extends RxDocumentData { @@ -216,11 +215,6 @@ export default function NeedsDisplay() { return ( <> -

What do you need right now?

From 48071f1dddf3987340d47c5369c3d698ae714b47 Mon Sep 17 00:00:00 2001 From: maxitect Date: Fri, 13 Dec 2024 09:21:47 +0000 Subject: [PATCH 37/57] chore: move button to next actions into Display.tsx so that it can access needs states --- src/app/needs/components/Display.tsx | 29 +++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/app/needs/components/Display.tsx b/src/app/needs/components/Display.tsx index 15af98a9..97c80855 100644 --- a/src/app/needs/components/Display.tsx +++ b/src/app/needs/components/Display.tsx @@ -4,6 +4,9 @@ import { useState, useEffect, useCallback } from "react"; import { useDatabase } from "@/context/DatabaseContext"; import Section from "./Section"; import { RxDocument, RxDocumentData } from "rxdb"; +import Button from "@/ui/shared/Button"; +import { useRouter } from "next/navigation"; +import clsx from "clsx"; export interface Base { id: string; @@ -54,18 +57,20 @@ export default function Display< modalProps = {}, chainEnd, }: DisplayProps) { + const router = useRouter(); const database = useDatabase(); const [mainData, setMainData] = useState[]>([]); const [relatedData, setRelatedData] = useState[]>([]); const [modalOpen, setModalOpen] = useState(false); + const [isUnmet, setIsUnmet] = useState(true); const [selectedItem, setSelectedItem] = useState(null); const fetchMainData = useCallback(async () => { const response = await database.getFromDb>(mainTable); let data = response.map((doc) => doc.toJSON() as RxDocumentData); + const now = new Date(); if (filterKey && !highlight) { - const now = new Date(); data = data.filter( (item) => new Date(item[filterKey as keyof T] as unknown as string) > now @@ -87,9 +92,11 @@ export default function Display< new Date(item[filterKey as keyof U] as unknown as string) > now, })) as ExtendedRelatedData[]; } - setRelatedData(data); - }, [database, relatedTable, filterKey, highlight]); + if (highlight) { + setIsUnmet(relatedData.some((datum) => datum.highlighted)); + } + }, [database, relatedTable, filterKey, highlight, relatedData]); useEffect(() => { fetchMainData(); @@ -123,8 +130,24 @@ export default function Display< setSelectedItem(null); }; + const openInfo = () => { + const basePath = window.location.pathname.endsWith("/") + ? window.location.pathname.slice(0, -1) + : window.location.pathname; + router.push(`${basePath}/next-actions`); + }; + return ( <> + )} - @@ -64,11 +72,13 @@ export default function NeedsModal({ onClick={handleNegativeClick} label={negativeLabel} className="text-xl font-normal w-36 h-48 bg-twd-secondary-purple text-balance rounded-none" + aria-label="Negative" />