From 4ae594001d04e18faa5e52fea213fc12e72258fb Mon Sep 17 00:00:00 2001 From: Nastia Piven Date: Tue, 10 Sep 2024 14:55:50 +0200 Subject: [PATCH] feat: add stats to Month header --- client/src/api/getEntries.ts | 28 +++++++++++++++-- client/src/components/MonthCardHeader.tsx | 24 +++++++++++--- client/src/hooks/index.ts | 1 + client/src/hooks/useAllDailyEntries.ts | 38 +++++++++++++++++++++++ client/src/hooks/useEntries.ts | 9 ++++-- client/src/utils/handlers.ts | 19 ++++++++++-- server/src/api/entries.ts | 5 +-- server/src/controllers/getEntries.ts | 35 +++++++++++++++++++-- 8 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 client/src/hooks/useAllDailyEntries.ts diff --git a/client/src/api/getEntries.ts b/client/src/api/getEntries.ts index 159a1e3..a1b943c 100644 --- a/client/src/api/getEntries.ts +++ b/client/src/api/getEntries.ts @@ -4,7 +4,7 @@ import axios from "axios"; import { BASE_URL } from "~/utils"; import { IEntry } from "~/~/models/Entry"; -export async function getEntries( +export async function getMonthEntriesByTaskId( userId: string, taskId: mongoose.Types.ObjectId | undefined, year: number, @@ -12,7 +12,31 @@ export async function getEntries( ): Promise { try { const response = await axios.get( - `${BASE_URL}/entries/${userId}/${taskId}/${year}/${month}`, + `${BASE_URL}/entries/month/task/${userId}/${taskId}/${year}/${month}`, + ); + + if (response.status !== 200) { + throw new Error(`${response.status} ${response.statusText}`); + } + + const data = response.data; + + return data; + } catch (err) { + console.log(err); // Handle + } +} + +// All daily entries +export async function getAllDailyEntries( + userId: string, + year: number, + month: number, + day: number, +): Promise { + try { + const response = await axios.get( + `${BASE_URL}/entries/day/${userId}/${year}/${month}/${day}`, ); if (response.status !== 200) { diff --git a/client/src/components/MonthCardHeader.tsx b/client/src/components/MonthCardHeader.tsx index 5ae5c83..7e99af1 100644 --- a/client/src/components/MonthCardHeader.tsx +++ b/client/src/components/MonthCardHeader.tsx @@ -1,6 +1,5 @@ -import { useAppContext } from "~/hooks"; -import { useDayEntries } from "~/hooks/useDayEntries"; -import { cn, getDateDetails } from "~/utils"; +import { useAppContext, useAllDailyEntries } from "~/hooks"; +import { calculateStatusPercentage, cn, getDateDetails } from "~/utils"; interface MonthCardHeaderProps { title: string; @@ -13,11 +12,26 @@ export function MonthCardHeader({ title, classes }: MonthCardHeaderProps) { const today = new Date(); const { year, month, day } = getDateDetails(today); - // const { data, isLoading, error } = useDayEntries(userId, year, month, day); + const { data, isLoading, error } = useAllDailyEntries( + userId, + year, + month, + day, + ); + + const statuses = data?.map((entry) => entry.status); + + const percentage = calculateStatusPercentage(statuses); return ( -
+

{title}

+ +
+ {`${data?.length} tasks`} + + {`${percentage}% today 🔥`} +
); } diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index 2d3d396..20485df 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -2,3 +2,4 @@ export * from "./useTasks"; export * from "./useUser"; export * from "./useEntries"; export * from "./useContext"; +export * from "./useAllDailyEntries"; diff --git a/client/src/hooks/useAllDailyEntries.ts b/client/src/hooks/useAllDailyEntries.ts new file mode 100644 index 0000000..5e3100f --- /dev/null +++ b/client/src/hooks/useAllDailyEntries.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; + +import { getAllDailyEntries } from "~/api"; +import { IEntry } from "~/~/models/Entry"; + +export function useAllDailyEntries( + userId: string, + year: number, + month: number, + day: number, +) { + const [data, setData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + async function fetchAllDailyEntries() { + setIsLoading(true); + setError(false); + + try { + const entries = await getAllDailyEntries(userId, year, month, day); + + setData(entries); + setIsLoading(false); + } catch (err) { + setError(true); + console.log("Error", err); + } finally { + setIsLoading(false); + } + } + + fetchAllDailyEntries(); + }, [userId, year, month, day]); + + return { data, isLoading, error }; +} diff --git a/client/src/hooks/useEntries.ts b/client/src/hooks/useEntries.ts index 522f0cb..df21c29 100644 --- a/client/src/hooks/useEntries.ts +++ b/client/src/hooks/useEntries.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import mongoose from "mongoose"; -import { getEntries } from "~/api"; +import { getMonthEntriesByTaskId } from "~/api"; import { IEntry } from "~/~/models/Entry"; export function useEntries( @@ -20,7 +20,12 @@ export function useEntries( setError(false); try { - const entries = await getEntries(userId, taskId, year, month); + const entries = await getMonthEntriesByTaskId( + userId, + taskId, + year, + month, + ); setData(entries); setIsLoading(false); diff --git a/client/src/utils/handlers.ts b/client/src/utils/handlers.ts index d5327b4..cc9772a 100644 --- a/client/src/utils/handlers.ts +++ b/client/src/utils/handlers.ts @@ -1,13 +1,28 @@ import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -import { MonthType } from "~/~/utils/types"; +import { MonthType, StatusType } from "~/~/utils/types"; + +// Calculate % of accomplishment +export function calculateStatusPercentage(statuses: StatusType[] | undefined) { + if (!statuses || statuses.length === 0) { + return 0; + } + + const maxStatusValue: StatusType = 5; + const totalPossibleScore = statuses.length * maxStatusValue; + + const totalScore = statuses.reduce((sum, status) => sum + status, 0); + const percentage = (totalScore / totalPossibleScore) * 100; + + return percentage; +} // Get date details export function getDateDetails(date: Date) { return { year: date.getFullYear(), - month: date.getMonth(), + month: date.getMonth() + 1, day: date.getDate(), }; } diff --git a/server/src/api/entries.ts b/server/src/api/entries.ts index d63aa89..0209b0a 100644 --- a/server/src/api/entries.ts +++ b/server/src/api/entries.ts @@ -1,9 +1,10 @@ import express from "express"; -import { getTaskEntriesByMonth } from "../controllers/getEntries.js"; +import { getAllDailyEntries, getMonthEntriesByTaskId } from "../controllers/getEntries.js"; const router = express.Router(); -router.route("/:userId/:taskId/:year/:month").get(getTaskEntriesByMonth); +router.route("/month/task/:userId/:taskId/:year/:month").get(getMonthEntriesByTaskId); +router.route("/day/:userId/:year/:month/:day").get(getAllDailyEntries); export { router as entriesRouter }; diff --git a/server/src/controllers/getEntries.ts b/server/src/controllers/getEntries.ts index 226f228..2f194fd 100644 --- a/server/src/controllers/getEntries.ts +++ b/server/src/controllers/getEntries.ts @@ -2,8 +2,8 @@ import { Request, Response } from "express"; import { Entry } from "../models/Entry.js"; -// Get Entries by year, month, taskId -export async function getTaskEntriesByMonth(req: Request, res: Response) { +// Month entries by taskId +export async function getMonthEntriesByTaskId(req: Request, res: Response) { const { userId, taskId, year, month } = req.params; if (!taskId || !userId || !year || !month) { @@ -21,3 +21,34 @@ export async function getTaskEntriesByMonth(req: Request, res: Response) { return res.json(entries); } + +// Get all daily entries +export async function getAllDailyEntries(req: Request, res: Response) { + const { userId, year, month, day } = req.params; + + if (!userId || !year || !month || !day) { + throw new Error("Some parameters are missing: userId, year, month, day."); + } + + const parsedYear = Number(year); + const parsedMonth = Number(month) - 1; + const parsedDay = Number(day); + + const startOfDay = new Date(parsedYear, parsedMonth, parsedDay); + const endOfDay = new Date(parsedYear, parsedMonth, parsedDay + 1); + + try { + const entries = await Entry.find({ + userId, + entryDate: { $gte: startOfDay, $lt: endOfDay }, + }).exec(); + + return res.json(entries); + } catch (err) { + console.error("Error fetching daily entries:", err); + + return res.status(500).json({ + message: "Internal server error in getAllDailyEntries().", + }); + } +}