-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #73 from fac30/feature/needs-ui
Feature/needs UI
- Loading branch information
Showing
8 changed files
with
454 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
"use client"; | ||
|
||
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 NeedsModal from "./NeedsModal"; | ||
|
||
export interface Category { | ||
id: string; | ||
name: string; | ||
} | ||
|
||
export interface Need { | ||
id: string; | ||
name: string; | ||
category: string; | ||
} | ||
|
||
interface CategorizedNeed { | ||
category: string; | ||
needs: Need[]; | ||
} | ||
|
||
export default function NeedsDisplay() { | ||
const database = useDatabase(); | ||
const [categories, setCategories] = useState<RxDocumentData<Category>[]>([]); | ||
const [needs, setNeeds] = useState<RxDocumentData<Need>[]>([]); | ||
const [urgent, setUrgent] = useState<number>(0); | ||
const [effortful, setEffortful] = useState<number>(0); | ||
const [worthDoing, setWorthDoing] = useState<number>(0); | ||
const [positiveLabel, setPositiveLabel] = useState<string>("urgent"); | ||
const [negativeLabel, setNegativeLabel] = useState<string>("not urgent"); | ||
|
||
const fetchCategories = async () => { | ||
const response = await database.getFromDb("needs_categories"); | ||
const categories = retrieveDataObject(response); | ||
|
||
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 [selectedNeed, setSelectedNeed] = useState(""); | ||
const [needsStep, setNeedsStep] = useState(1); | ||
|
||
const handleOpen = (need: string) => { | ||
setModalOpen(true); | ||
setSelectedNeed(need); | ||
}; | ||
|
||
const handleStepIncrease = () => { | ||
setNeedsStep((prevStep) => prevStep + 1); | ||
}; | ||
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 handleIncrease = ( | ||
setter: React.Dispatch<React.SetStateAction<number>> | ||
) => { | ||
setter((prev) => prev + 1); | ||
}; | ||
const handleDecrease = ( | ||
setter: React.Dispatch<React.SetStateAction<number>> | ||
) => { | ||
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); | ||
} | ||
}; | ||
|
||
const handlePositiveClick = () => { | ||
if (needsStep === 1) { | ||
handleIncrease(setUrgent); | ||
} else if (needsStep === 2) { | ||
handleIncrease(setEffortful); | ||
} else if (needsStep === 3) { | ||
handleIncrease(setWorthDoing); | ||
|
||
setModalOpen(false); | ||
setNeedsStep(1); | ||
} | ||
}; | ||
|
||
const handleNegativeClick = () => { | ||
if (needsStep === 1) { | ||
handleDecrease(setUrgent); | ||
} else if (needsStep === 2) { | ||
handleDecrease(setEffortful); | ||
} else if (needsStep === 3) { | ||
handleDecrease(setWorthDoing); | ||
|
||
setModalOpen(false); | ||
setNeedsStep(1); | ||
} | ||
}; | ||
|
||
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 { | ||
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); | ||
}; | ||
|
||
return ( | ||
<> | ||
<div className="w-11/12 m-auto"> | ||
{categorisedNeeds.map((categoryData, index) => { | ||
return ( | ||
<NeedsSection | ||
key={index} | ||
categoryData={categoryData} | ||
handleOpen={handleOpen} | ||
/> | ||
); | ||
})} | ||
</div> | ||
<NeedsModal | ||
modalOpen={modalOpen} | ||
forwardButton={{ | ||
label: positiveLabel, | ||
action: () => { | ||
handleStepAction(); | ||
handlePositiveClick(); | ||
}, | ||
}} | ||
backButton={{ | ||
label: negativeLabel, | ||
action: () => { | ||
handleStepAction(); | ||
handleNegativeClick(); | ||
}, | ||
}} | ||
title={`You have selected ~${selectedNeed}~`} | ||
needsStep={needsStep} | ||
handleBackClick={handleBackClick} | ||
handleCloseClick={handleCloseClick} | ||
/> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import Button from "@/ui/shared/Button"; | ||
import { ChevronLeftIcon } from "@heroicons/react/24/outline"; | ||
import { XMarkIcon } from "@heroicons/react/24/outline"; | ||
|
||
interface ModalProps { | ||
inputModal?: boolean; | ||
placeholder?: string; | ||
modalOpen: boolean; | ||
title?: string; | ||
forwardButton?: { | ||
label: string; | ||
action: () => void; | ||
}; | ||
backButton?: { | ||
label: string; | ||
action: () => void; | ||
}; | ||
handleInputChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; | ||
needsStep: number; | ||
handleBackClick: () => void; | ||
handleCloseClick: () => void; | ||
} | ||
|
||
export default function NeedsModal({ | ||
modalOpen, | ||
title, | ||
forwardButton, | ||
backButton, | ||
needsStep, | ||
handleBackClick, | ||
handleCloseClick, | ||
}: ModalProps) { | ||
return ( | ||
<> | ||
{modalOpen && ( | ||
<div | ||
className="w-11/12 absolute top-1/2 left-1/2 bg-gray-800 border-[1.5px] rounded-lg -translate-x-1/2 -translate-y-1/2" | ||
style={{ | ||
position: "fixed", | ||
top: "50%", | ||
left: "50%", | ||
transform: "translate(-50%, -50%)", | ||
zIndex: 1000, | ||
overflow: "hidden", | ||
}} | ||
> | ||
<p className="text-center">step {needsStep} of 3</p> | ||
{needsStep > 1 && ( | ||
<button className="absolute left-2 top-3" onClick={handleBackClick}> | ||
<ChevronLeftIcon className="h-10 w-10" /> | ||
</button> | ||
)} | ||
<button className="absolute right-2 top-3" onClick={handleCloseClick}> | ||
<XMarkIcon className="h-10 w-10" /> | ||
</button> | ||
|
||
<div className="flex flex-col w-full items-center py-10 justify-between h-full "> | ||
<p className="text-xl w-10/12 text-center mb-5">{title}</p> | ||
<p className="text-md w-10/12 text-center mb-10"> | ||
Select the button that best describes meeting this need right now. | ||
</p> | ||
|
||
<div className="flex justify-center gap-10 w-2/3"> | ||
{backButton && ( | ||
<Button | ||
onClick={backButton.action} | ||
label={backButton.label} | ||
className="text-xl font-normal w-36 h-48 bg-twd-secondary-purple text-balance rounded-none" | ||
/> | ||
)} | ||
{forwardButton && ( | ||
<Button | ||
onClick={forwardButton.action} | ||
label={forwardButton.label} | ||
className="bg-twd-primary-purple text-xl font-normal w-36 text-balance rounded-none" | ||
/> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
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 ( | ||
<div> | ||
<h2 className="text-xl mb-5 font-semibold">{categoryData.category}</h2> | ||
<div className="flex gap-5 flex-wrap mb-10"> | ||
{categoryData.needs.map((need: Need, needIndex: number) => { | ||
return ( | ||
<Button | ||
key={needIndex} | ||
label={need.name} | ||
className="bg-gray-600 font-normal text-nowrap" | ||
onClick={() => handleOpen(need.name)} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.