diff --git a/__tests__/AreaChart.test.tsx b/__tests__/AreaChart.test.tsx new file mode 100644 index 00000000..26308e94 --- /dev/null +++ b/__tests__/AreaChart.test.tsx @@ -0,0 +1,97 @@ +import { render, screen } from "@testing-library/react"; +import MoodAreaChart from "@/app/insights/components/AreaChart"; + +jest.mock("@/ui/shared/PlotlyChart", () => ({ + __esModule: true, + default: ({ data }: { data: any }) => ( +
+ ), +})); + +describe("AreaChart Component", () => { + const mockData = [ + { + neurotransmitters: { + dopamine: 5, + serotonin: 3, + adrenaline: 7, + }, + moodName: "Happy", + timestamp: "2023-01-01T12:00:00Z", + id: "1", + createdAt: "2023-01-01T12:00:00Z", + }, + { + neurotransmitters: { + dopamine: 6, + serotonin: 4, + adrenaline: 8, + }, + moodName: "Sad", + timestamp: "2023-01-02T12:00:00Z", + id: "2", + createdAt: "2023-01-02T12:00:00Z", + } + ]; + + const mockStartOfRange = new Date("2023-01-01T00:00:00Z"); + const mockEndOfRange = new Date("2023-12-31T23:59:59Z"); + + it("renders without crashing", () => { + render( + + ); + + const plotlyChart = screen.getByTestId("plotly-chart"); + expect(plotlyChart).toBeInTheDocument(); + }); + + it("displays a message when no data is available", () => { + render( + + ); + + expect( + screen.getByText("No data available for the graph.") + ).toBeInTheDocument(); + }); + + it("correctly calculates percentage distribution", () => { + render( + + ); + + const plotlyChart = screen.getByTestId("plotly-chart"); + const plotlyData = JSON.parse( + plotlyChart.getAttribute("data-prop") || "[]" + ); + + // Check that we have two traces (one for each mood) + expect(plotlyData).toHaveLength(2); + + // First data point should be 100% for "Happy" + const happyTrace = plotlyData.find((d: any) => d.name === "Happy"); + expect(happyTrace.y[0]).toBe(100); // First point should be 100% + expect(happyTrace.y[1]).toBe(50); // Second point should be 50% + + // Second mood should start at 0% and go to 50% + const sadTrace = plotlyData.find((d: any) => d.name === "Sad"); + expect(sadTrace.y[0]).toBe(0); // First point should be 0% + expect(sadTrace.y[1]).toBe(50); // Second point should be 50% + }); +}); \ No newline at end of file diff --git a/__tests__/Moods.test.tsx b/__tests__/Moods.test.tsx index 4d958ce8..5fc43b2b 100644 --- a/__tests__/Moods.test.tsx +++ b/__tests__/Moods.test.tsx @@ -1,13 +1,14 @@ import { render, screen } from "@testing-library/react"; import MoodsPage from "@/app/moods/page"; -// Mock the child components jest.mock("@/app/moods/components/Cube", () => ({ - Cube: () =>
Cube Component
, + __esModule: true, + default: () =>
Cube Component
, })); jest.mock("@/app/moods/components/SliderBox", () => ({ - SliderBox: () =>
SliderBox Component
, + __esModule: true, + default: () =>
SliderBox Component
, })); jest.mock("next/navigation", () => ({ @@ -32,7 +33,7 @@ describe("MoodsPage", () => { it("has correct layout structure", () => { const { container } = render(); - + const mainDiv = container.firstChild; expect(mainDiv).toHaveClass("flex", "flex-col", "gap-4"); }); diff --git a/__tests__/MoodsCube.test.tsx b/__tests__/MoodsCube.test.tsx index 61855d99..6e5487e3 100644 --- a/__tests__/MoodsCube.test.tsx +++ b/__tests__/MoodsCube.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from "@testing-library/react"; -import { Cube } from "@/app/moods/components/Cube"; -import { SliderBox } from "@/app/moods/components/SliderBox"; +import Cube from "@/app/moods/components/Cube"; +import SliderBox from "@/app/moods/components/SliderBox"; import { useState } from "react"; import { Datum } from "plotly.js"; diff --git a/__tests__/MoodsMatrixToggle.test.tsx b/__tests__/MoodsMatrixToggle.test.tsx index c9cd79cd..7c1219c6 100644 --- a/__tests__/MoodsMatrixToggle.test.tsx +++ b/__tests__/MoodsMatrixToggle.test.tsx @@ -1,12 +1,11 @@ import { render, screen, fireEvent, act } from "@testing-library/react"; -import { Cube } from "@/app/moods/components/Cube"; +import Cube from "@/app/moods/components/Cube"; import { useState } from "react"; import { Datum } from "plotly.js"; jest.mock("@/ui/shared/PlotlyChart", () => ({ __esModule: true, default: ({ data, onLoaded }: { data: any; onLoaded: () => void }) => { - // Simulate the Plotly chart triggering the onLoaded callback setTimeout(onLoaded, 0); return
; }, diff --git a/__tests__/StreamGraph.test.tsx b/__tests__/StreamGraph.test.tsx new file mode 100644 index 00000000..4df95abe --- /dev/null +++ b/__tests__/StreamGraph.test.tsx @@ -0,0 +1,116 @@ +import { render, screen } from "@testing-library/react"; +import MoodStreamGraph from "@/app/insights/components/StreamGraph"; + +jest.mock("@/ui/shared/PlotlyChart", () => ({ + __esModule: true, + default: ({ data }: { data: any }) => ( +
+ ), +})); + +describe("StreamGraph Component", () => { + const mockData = [ + { + neurotransmitters: { + dopamine: 5, + serotonin: 3, + adrenaline: 7, + }, + moodName: "Happy", + timestamp: "2023-01-01T12:00:00Z", + id: "1", + createdAt: "2023-01-01T12:00:00Z", + }, + { + neurotransmitters: { + dopamine: 6, + serotonin: 4, + adrenaline: 8, + }, + moodName: "Sad", + timestamp: "2023-01-02T12:00:00Z", + id: "2", + createdAt: "2023-01-02T12:00:00Z", + } + ]; + + const mockStartOfRange = new Date("2023-01-01T00:00:00Z"); + const mockEndOfRange = new Date("2023-12-31T23:59:59Z"); + + it("renders without crashing", () => { + render( + + ); + + const plotlyChart = screen.getByTestId("plotly-chart"); + expect(plotlyChart).toBeInTheDocument(); + }); + + it("displays a message when no data is available", () => { + render( + + ); + + expect( + screen.getByText("No data available for the graph.") + ).toBeInTheDocument(); + }); + + it("displays a message when no data is in selected range", () => { + const outOfRangeStart = new Date("2024-01-01T00:00:00Z"); + const outOfRangeEnd = new Date("2024-12-31T23:59:59Z"); + + render( + + ); + + expect( + screen.getByText("No data available in the selected range.") + ).toBeInTheDocument(); + }); + + it("correctly sets up stream graph data", () => { + render( + + ); + + const plotlyChart = screen.getByTestId("plotly-chart"); + const plotlyData = JSON.parse( + plotlyChart.getAttribute("data-prop") || "[]" + ); + + // Check that we have two traces (one for each mood) + expect(plotlyData).toHaveLength(2); + + // First mood should start with height 0.5 + const firstMoodTrace = plotlyData[0]; + expect(firstMoodTrace.y[0]).toBe(0.5); + + // Second mood should start with height -0.5 to balance the graph + const secondMoodTrace = plotlyData[1]; + expect(secondMoodTrace.y[0]).toBe(-0.5); + + // Values should sum to 0 at any point + expect(firstMoodTrace.y[0] + secondMoodTrace.y[0]).toBe(0); + }); +}); \ No newline at end of file diff --git a/src/app/insights/components/AreaChart.tsx b/src/app/insights/components/AreaChart.tsx new file mode 100644 index 00000000..0178a7c8 --- /dev/null +++ b/src/app/insights/components/AreaChart.tsx @@ -0,0 +1,149 @@ +import PlotlyChart from "@/ui/shared/PlotlyChart"; +import { Insight } from "./InsightsDisplay"; + +interface MoodAreaChartProps { + dataArray: Insight[]; + startOfRange: Date; + endOfRange: Date; + selectedButton: string; +} + +export default function MoodAreaChart({ + dataArray, + startOfRange, + endOfRange, + selectedButton, +}: MoodAreaChartProps) { + if (!dataArray || dataArray.length === 0) { + return ( +
No data available for the graph.
+ ); + } + + // Sort all the mood records by their timestamp + const sortedData = [...dataArray].sort( + (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + ); + + // Create an array of the mood names that exist in sortedData + const uniqueMoods = Array.from( + new Set(sortedData.map((entry) => entry.moodName)) + ); + + const traces = uniqueMoods.map((mood) => { + const dataPoints = sortedData.reduce((acc, entry) => { + const timestamp = new Date(entry.timestamp).toISOString(); + + // Get all entries up to this timestamp + const entriesUpToNow = sortedData.filter(e => + new Date(e.timestamp) <= new Date(entry.timestamp) + ); + + // Count total entries and entries for this mood + const totalCount = entriesUpToNow.length; + const moodCount = entriesUpToNow.filter(e => e.moodName === mood).length; + + // Calculate percentage + const percentage = (moodCount / totalCount) * 100; + + acc.push({ + x: timestamp, + y: percentage + }); + + return acc; + }, [] as Array<{x: string, y: number}>); + + return { + x: dataPoints.map(point => point.x), + y: dataPoints.map(point => point.y), + type: "scatter", + mode: "none", + fill: "tonexty", + name: mood, + stackgroup: "one", + line: { shape: "spline" } + }; + }); + + const tickFormat = (() => { + switch (selectedButton) { + case "day": + return "%H:00"; + case "week": + return "%a"; + case "month": + return "%d"; + case "year": + return "%b"; + default: + return ""; + } + })(); + + return ( + <> +
+
+

Mood Accumulation

+

See how prevalent each mood has been at different times.

+
+ +
+ +
+
+ + ); +} \ No newline at end of file diff --git a/src/app/insights/components/InsightsDisplay.tsx b/src/app/insights/components/InsightsDisplay.tsx index 9fc98349..8ff7087e 100644 --- a/src/app/insights/components/InsightsDisplay.tsx +++ b/src/app/insights/components/InsightsDisplay.tsx @@ -1,13 +1,15 @@ +/* eslint-disable react-hooks/exhaustive-deps */ "use client"; import { useDatabase } from "@/context/DatabaseContext"; - import LineGraph from "./LineGraph"; +import MoodAreaChart from "./AreaChart"; import retrieveDataObject from "@/lib/utils/retrieveDataObject"; -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect } from "react"; import Button from "@/ui/shared/Button"; import clsx from "clsx"; +import MoodStreamGraph from "./StreamGraph"; export interface Insight { neurotransmitters: { @@ -27,76 +29,46 @@ export default function InsightsDisplay() { const dateOptions = ["day", "week", "month", "year"]; - const handleDateChange = (dateChoice: keyof typeof dateParams) => { + const handleDateChange = (dateChoice: keyof typeof dateOffsets) => { setSelectedButton(dateChoice); }; - const [selectedButton, setSelectedButton] = - useState("day"); - const [useNow, setUseNow] = useState(true); + const [selectedButton, setSelectedButton] = useState("day"); + + /* Handler & State for Currently Unused "To Now" Button + const [useNow, setUseNow] = useState(true); - const handleUseNowClick = () => { - setUseNow((prevUseNow) => !prevUseNow); + const handleUseNowClick = () => { + setUseNow((prevUseNow) => !prevUseNow); + }; */ + + const [now, setNow] = useState(null); + + const getDateRange = (selected: keyof typeof dateOffsets) => { + if (!now) return { start: new Date(), end: new Date() }; + const offset = dateOffsets[selected]; + const start = new Date(now.getTime() - offset); + const end = now; + return { start, end }; }; - const now = useMemo(() => new Date(), []); - - const dateParams = useMemo( - () => ({ - day: { - start: new Date( - now.getFullYear(), - now.getMonth(), - now.getDate(), - 6, - 0, - 0, - 0 - ), - end: new Date( - now.getFullYear(), - now.getMonth(), - now.getDate(), - 24, - 0, - 0, - 0 - ), - }, - - // This is from Monday to Sunday - week: { - start: new Date( - now.getFullYear(), - now.getMonth(), - now.getDate() - (now.getDay() === 0 ? 6 : now.getDay() - 1) // Adjust for Sunday - ), - end: new Date( - now.getFullYear(), - now.getMonth(), - now.getDate() + (now.getDay() === 0 ? 0 : 7 - now.getDay()) // Adjust for Sunday - ), - }, - month: { - start: new Date(now.getFullYear(), now.getMonth(), 1), - end: new Date(now.getFullYear(), now.getMonth() + 1, 0), - }, - year: { - start: new Date(now.getFullYear(), 0, 1), - end: new Date(now.getFullYear(), 11, 31), - }, - }), - [now] - ); + + const dateOffsets = { + day: 24 * 60 * 60 * 1000, + week: 7 * 24 * 60 * 60 * 1000, + month: 30 * 24 * 60 * 60 * 1000, + year: 365 * 24 * 60 * 60 * 1000 + } as const; - const [startOfRange, setStartOfRange] = useState(dateParams.day.start); - const [endOfRange, setEndOfRange] = useState(dateParams.day.end); + const [startOfRange, setStartOfRange] = useState(new Date()); + const [endOfRange, setEndOfRange] = useState(new Date()); useEffect(() => { - const { start, end } = dateParams[selectedButton]; - console.log(start, end); + if (!now) return; + const { start, end } = getDateRange(selectedButton); setStartOfRange(start); - setEndOfRange(useNow ? now : end); - }, [useNow, selectedButton, dateParams, now]); + setEndOfRange(end); + }, [/* useNow, */ selectedButton, now]); + const getInsights = async () => { const myInsights = await database.getFromDb("mood_records"); @@ -109,48 +81,79 @@ export default function InsightsDisplay() { const goodInsights = retrieveDataObject(myInsights); setInsights(goodInsights); + + if (goodInsights.length > 0) { + const latestInsight = goodInsights.reduce((acc, curr) => { + return new Date(curr.timestamp) > new Date(acc.timestamp) ? curr : acc; + }); + setNow(new Date(latestInsight.timestamp)); + } else { + setNow(new Date()); + } }; - useEffect(() => { - getInsights(); + useEffect(() => { + getInsights(); }, []); return ( <> -
+
{dateOptions.map((dateOption, index) => { const isActive = selectedButton === dateOption; return (
- {insights ? ( + {insights ? ( // Line Graph + dataArray={insights} + startOfRange={startOfRange} + endOfRange={endOfRange} + selectedButton={selectedButton} + /> + ) : ( +
Loading Line Graph...
+ )} + + {insights ? ( // Area Chart + <> + + ) : ( -
Loading insights...
+
Loading Area Chart...
+ )} + + {insights ? ( // Stream Graph + <> + + + ) : ( +
Loading Stream Graph...
)} ); -} +} \ No newline at end of file diff --git a/src/app/insights/components/LineGraph.tsx b/src/app/insights/components/LineGraph.tsx index 1578713f..ebcc9924 100644 --- a/src/app/insights/components/LineGraph.tsx +++ b/src/app/insights/components/LineGraph.tsx @@ -9,6 +9,11 @@ interface LineGraphProps { selectedButton: string; } +interface AggregatedData { // Averaged Data for Year View + timestamp: string; + value: number; +} + export default function LineGraph({ dataArray, startOfRange, @@ -19,23 +24,52 @@ export default function LineGraph({ return
No data available for the graph.
; } + const aggregateDataByMonth = (data: number[], timestamps: string[]): AggregatedData[] => { + const monthlyData: { [key: string]: number[] } = {}; + + timestamps.forEach((timestamp, index) => { + const date = new Date(timestamp); + const monthKey = `${date.getFullYear()}-${date.getMonth()}`; + + if (!monthlyData[monthKey]) { + monthlyData[monthKey] = []; + } + monthlyData[monthKey].push(data[index]); + }); + + return Object.entries(monthlyData).map(([monthKey, values]) => ({ + timestamp: new Date( + parseInt(monthKey.split('-')[0]), + parseInt(monthKey.split('-')[1]), + 1 + ).toISOString(), + value: values.reduce((sum, val) => sum + val, 0) / values.length + })); + }; + const sortedData = [...dataArray].sort( (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() ); - + const xAxis = sortedData.map((entry) => new Date(entry.timestamp).toISOString() ); - const dopamineValues = sortedData.map( - (entry) => entry.neurotransmitters.dopamine + + const processData = (values: number[]): [string[], number[]] => { + if (selectedButton === "year") { + const aggregated = aggregateDataByMonth(values, xAxis); + return [aggregated.map(d => d.timestamp), aggregated.map(d => d.value)]; + } + return [xAxis, values]; + }; + const [dopamineX, dopamineY] = processData( + sortedData.map((entry) => entry.neurotransmitters.dopamine) ); - - const serotoninValues = sortedData.map( - (entry) => entry.neurotransmitters.serotonin + const [serotoninX, serotoninY] = processData( + sortedData.map((entry) => entry.neurotransmitters.serotonin) ); - - const adrenalineValues = sortedData.map( - (entry) => entry.neurotransmitters.adrenaline + const [adrenalineX, adrenalineY] = processData( + sortedData.map((entry) => entry.neurotransmitters.adrenaline) ); const tickFormat = (() => { @@ -68,6 +102,17 @@ export default function LineGraph({ } })(); + const yMax = Math.max( + ...dopamineY, + ...serotoninY, + ...adrenalineY + ); + const yMin = Math.min( + ...dopamineY, + ...serotoninY, + ...adrenalineY + ); + return ( <>
@@ -75,32 +120,33 @@ export default function LineGraph({

Decision Maker

How did the things I wanted to do feel?

+
No data available for the graph.
; + } + + // Sort data by timestamp + const sortedData = [...dataArray].sort( + (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + ); + + // Filter to the current range + const filteredData = sortedData.filter(entry => { + const t = new Date(entry.timestamp).getTime(); + return t >= startOfRange.getTime() && t <= endOfRange.getTime(); + }); + + if (filteredData.length === 0) { + return
No data available in the selected range.
; + } + + const uniqueMoods = Array.from( + new Set(filteredData.map((entry) => entry.moodName)) + ); + + // After filtering data but before creating traces + const firstMood = filteredData[0].moodName; + + const traces = uniqueMoods.map((mood) => { + const dataPoints = filteredData.map((entry, index) => { + const timestamp = new Date(entry.timestamp).toISOString(); + const entriesUpToNow = filteredData.slice(0, index + 1); + const moodCount = entriesUpToNow.filter(e => e.moodName === mood).length; + + // For the first timestamp, set initial values + if (index === 0) { + // If this is the first mood, give it a height of 1 + // Other moods start at 0 + return { + x: timestamp, + y: mood === firstMood ? 1 : 0 + }; + } + + return { x: timestamp, y: moodCount }; + }); + + return { + x: dataPoints.map(point => point.x), + y: dataPoints.map(point => point.y), + type: "scatter", + mode: "none", + fill: "tonexty", + name: mood, + stackgroup: "one", + fillcolor: "auto", + orientation: "v", + stackgaps: "interpolate", + line: { shape: "spline" }, + hoveron: "points+fills", + hoverinfo: "name+y" + }; + }); + + // Apply silhouette offset at each timestamp to center the stack + if (traces.length > 0) { + const timeSteps = traces[0].x; + for (let i = 0; i < timeSteps.length; i++) { + let totalAtTime = 0; + for (let t = 0; t < traces.length; t++) { + totalAtTime += (traces[t].y as number[])[i]; + } + const midpoint = totalAtTime / 2; + + // For the first timestamp, use a smaller offset to create the 20% band + const offset = i === 0 ? 0.5 : midpoint; // This will create roughly a 20% band + + for (let t = 0; t < traces.length; t++) { + (traces[t].y as number[])[i] = (traces[t].y as number[])[i] - offset; + } + } + } + + const tickFormat = (() => { + switch (selectedButton) { + case "day": + return "%H:00"; + case "week": + return "%a"; + case "month": + return "%d"; + case "year": + return "%b"; + default: + return ""; + } + })(); + + return ( +
+
+

Mood Flow

+

Find out how which moods have been most prevalent in this time period

+
+ +
+ +
+
+ ); +} diff --git a/src/app/insights/info/page.tsx b/src/app/insights/info/page.tsx index 57aedcf3..91b89b31 100644 --- a/src/app/insights/info/page.tsx +++ b/src/app/insights/info/page.tsx @@ -1,3 +1,84 @@ +import { Header } from "@/ui/shared/Header"; + export default function Page() { - return

this is a placeholder

; -} + return ( + <> +
+
+ {/* Insights Tab Explanation */} +
+

+ What is Insights Tab About? +

+

+ The Insights tab is designed to help you make the most out of your + data by providing{" "} + + personalized insights, trends, and summaries + + . Whether you’re tracking your mood, monitoring patterns over time, + or discovering which tools work best for you, this feature ensures + you gain valuable insights without needing to engage with the app + every single day. +

+
+ + {/* How Can This Tab Help Section */} +
+

+ How Can This Tab Help? +

+
    +
  • + + Time-Based Filtering: + {" "} + This helps you visualize how urgency, effort, and mood vary over + specific periods—whether mornings are more stressful, weekends + feel more rewarding, or winters bring unique challenges. +
  • +
  • + + Summarized Insights: + {" "} + Overall Mood Trends, Frequently Unmet Needs, Most-Used Tools: + These summaries give you a clear picture of your behavior and what + supports you best. +
  • +
  • + + Insights Without Consistency: + {" "} + Even if you don’t use the app daily, you’ll still get meaningful + insights. This feature analyzes the data you’ve entered to create + trends and summaries, so there’s no pressure to engage frequently. +
  • +
+
+ + {/* Special Features Section */} +
+

+ What Makes This Feature Special? +

+

+ This feature isn’t just about collecting data—it’s about helping you + see the bigger picture. It empowers you to: +

+
    +
  • Understand patterns in your behavior and emotions.
  • +
  • Discover what tools and approaches work best for you.
  • +
  • + Take control of your well-being by gaining actionable insights. +
  • +
+

+ Whether you’re a casual user or someone who loves diving into + details, this feature adapts to your needs and makes your data + meaningful. +

+
+
+ + ); +} \ No newline at end of file diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index a71ccde2..7acea234 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -9,6 +9,7 @@ export default function InsightsPage() { description="analyse your moods and needs over time." hasInfoButton={true} /> + ); diff --git a/src/app/moods/components/Cube.tsx b/src/app/moods/components/Cube.tsx index 865a6da0..218a6801 100644 --- a/src/app/moods/components/Cube.tsx +++ b/src/app/moods/components/Cube.tsx @@ -47,7 +47,7 @@ interface CubeProps { neuroState: NeurochemState; } -export function Cube({ neuroState }: CubeProps) { +export default function Cube({ neuroState }: CubeProps) { const [isPriorityMatrix, setIsPriorityMatrix] = useState(false); const [hasRendered, setHasRendered] = useState(false); diff --git a/src/app/moods/components/MoodsDisplay.tsx b/src/app/moods/components/MoodsDisplay.tsx index e49e089f..0a85c37a 100644 --- a/src/app/moods/components/MoodsDisplay.tsx +++ b/src/app/moods/components/MoodsDisplay.tsx @@ -1,7 +1,7 @@ "use client"; -import { Cube } from "./Cube"; -import { SliderBox } from "./SliderBox"; +import Cube from "./Cube"; +import SliderBox from "./SliderBox"; import MoodButtons from "./MoodButtons"; import { useDatabase } from "@/context/DatabaseContext"; import { useState } from "react"; @@ -31,7 +31,42 @@ export default function MoodsDisplay() { }; const submitMood = () => { - const { dopamine, serotonin, adrenaline } = neuroState; + let moodName = ""; + const { dopamine, serotonin, adrenaline } = neuroState as { + dopamine: number; + serotonin: number; + adrenaline: number; + }; + + if (dopamine <= 5) { + if (adrenaline <= 5) { + if (serotonin <= 5) { + moodName = "guilt"; + } else if (serotonin >= 6) { + moodName = "content"; + } + } else if (adrenaline >= 6) { + if (serotonin <= 5) { + moodName = "distress"; + } else if (serotonin >= 6) { + moodName = "relief"; + } + } + } else if (dopamine >= 6) { + if (adrenaline <= 5) { + if (serotonin <= 5) { + moodName = "freeze"; + } else if (serotonin >= 6) { + moodName = "joy"; + } + } else if (adrenaline >= 6) { + if (serotonin <= 5) { + moodName = "fight/flight"; + } else if (serotonin >= 6) { + moodName = "interest"; + } + } + } const submitObj = { neurotransmitters: { @@ -39,7 +74,7 @@ export default function MoodsDisplay() { serotonin: serotonin, adrenaline: adrenaline, }, - moodName: "new-mood", + moodName: moodName, timestamp: new Date().toISOString(), }; diff --git a/src/app/moods/components/SliderBox.tsx b/src/app/moods/components/SliderBox.tsx index fac2dad3..218addd7 100644 --- a/src/app/moods/components/SliderBox.tsx +++ b/src/app/moods/components/SliderBox.tsx @@ -18,7 +18,7 @@ const sliders: { { chem: "adrenaline", label: "Step 3. Does it feel worth doing?" }, ]; -export function SliderBox({ handleChange, neuroState }: SliderBoxProps) { +export default function SliderBox({ handleChange, neuroState }: SliderBoxProps) { return (
diff --git a/src/lib/db/DatabaseManager.ts b/src/lib/db/DatabaseManager.ts index 4b470dc3..a23a684b 100644 --- a/src/lib/db/DatabaseManager.ts +++ b/src/lib/db/DatabaseManager.ts @@ -3,20 +3,17 @@ import { RxDBDevModePlugin } from "rxdb/plugins/dev-mode"; import { getRxStorageDexie } from "rxdb/plugins/storage-dexie"; import { createRxDatabase, RxDatabase } from "rxdb"; import { v4 as uuidv4 } from "uuid"; - import toolkitItemSchema from "./schemas/toolkitItemSchema.json"; import moodRecordSchema from "./schemas/moodRecordSchema.json"; import categoriesSchema from "./schemas/categoriesSchema.json"; import needsCategoriesSchema from "./schemas/categoriesSchema.json"; import needsSchema from "./schemas/categoriesSchema.json"; import nextActionsSchema from "./schemas/categoriesSchema.json"; - import { categories, toolkit } from "./seed/toolkit"; import { needsCategories, needs, nextActions } from "./seed/needs"; - +import generateMoodRecords from "./seed/moods"; import { RxDBUpdatePlugin } from "rxdb/plugins/update"; addRxPlugin(RxDBUpdatePlugin); - addRxPlugin(RxDBDevModePlugin); let dbInstance: RxDatabase | null = null; @@ -78,6 +75,7 @@ class DatabaseManager { private async seedDatabase() { try { + await this.seed("mood_records", generateMoodRecords("dev")); await this.seed("categories", categories); await this.seed("toolkit_items", toolkit); await this.seed("needs_categories", needsCategories); @@ -90,11 +88,13 @@ class DatabaseManager { private async seed(collectionName: string, data: T[]) { if (!dbInstance) throw new Error("Database not initialised."); + const collection = dbInstance.collections[collectionName]; - if (!collection) - throw new Error(`${collectionName} collection is missing.`); + + if (!collection) throw new Error(`${collectionName} collection is missing.`); const existingDocs = await collection.find().exec(); + if (existingDocs.length === 0) { console.log(`Seeding ${collectionName}...`); await collection.bulkInsert(data); @@ -208,4 +208,4 @@ class DatabaseManager { } } -export default DatabaseManager.getInstance(); +export default DatabaseManager.getInstance(); \ No newline at end of file diff --git a/src/lib/db/seed/moods.ts b/src/lib/db/seed/moods.ts new file mode 100644 index 00000000..066f4e12 --- /dev/null +++ b/src/lib/db/seed/moods.ts @@ -0,0 +1,36 @@ +import getMoodName from "@/lib/utils/getMoodName"; +import { v4 as uuidv4 } from "uuid"; + +export default function generateMoodRecords(mode: string) { + const records = []; + const now = new Date(); + let weeks: number = 0; + + if (mode == "dev") { + weeks = 78; + } else { + weeks = 1; + } + + for (let i = 0; i < weeks; i++) { + const dopamine = Math.floor(Math.random() * 10) + 1; + const serotonin = Math.floor(Math.random() * 10) + 1; + const adrenaline = Math.floor(Math.random() * 10) + 1; + + const timestamp = new Date(now); + timestamp.setDate(timestamp.getDate() - (i * 7)); + + records.push({ + id: uuidv4(), + neurotransmitters: { + serotonin, + dopamine, + adrenaline + }, + moodName: getMoodName(dopamine, adrenaline, serotonin), + timestamp: timestamp.toISOString() + }); + } + + return records; +}; \ No newline at end of file diff --git a/src/lib/utils/getMoodName.ts b/src/lib/utils/getMoodName.ts new file mode 100644 index 00000000..c4dd876d --- /dev/null +++ b/src/lib/utils/getMoodName.ts @@ -0,0 +1,35 @@ +export default function getMoodName(dopamine: number, adrenaline: number, serotonin: number): string { + let moodName = ""; + + if (dopamine <= 5) { + if (adrenaline <= 5) { + if (serotonin <= 5) { + moodName = "guilt"; + } else if (serotonin >= 6) { + moodName = "content"; + } + } else if (adrenaline >= 6) { + if (serotonin <= 5) { + moodName = "distress"; + } else if (serotonin >= 6) { + moodName = "relief"; + } + } + } else if (dopamine >= 6) { + if (adrenaline <= 5) { + if (serotonin <= 5) { + moodName = "freeze"; + } else if (serotonin >= 6) { + moodName = "joy"; + } + } else if (adrenaline >= 6) { + if (serotonin <= 5) { + moodName = "fight/flight"; + } else if (serotonin >= 6) { + moodName = "interest"; + } + } + } + + return moodName; +} \ No newline at end of file