diff --git a/convex/constants.ts b/convex/constants.ts index c8cd4ae..d725e8e 100644 --- a/convex/constants.ts +++ b/convex/constants.ts @@ -3,13 +3,16 @@ import { RiAccountCircleLine, RiAtLine, RiBankLine, + RiCalendar2Line, RiCalendarLine, + RiCalendarScheduleLine, RiChat3Line, RiCheckboxBlankCircleLine, RiCheckboxCircleFill, RiCheckboxLine, RiComputerLine, RiDropdownList, + RiFlashlightLine, RiFolderCheckLine, RiFolderLine, RiGamepadLine, @@ -159,6 +162,7 @@ export const GROUP_QUESTS_BY = { dateAdded: "Date added", category: "Category", status: "Status", + timeRequired: "Time required", } as const; export type GroupQuestsBy = keyof typeof GROUP_QUESTS_BY; @@ -166,10 +170,10 @@ export type GroupQuestsBy = keyof typeof GROUP_QUESTS_BY; * Generic group details. * Used for UI display of filter groups. */ -interface GroupDetails { +export type GroupDetails = { label: string; icon: RemixiconComponentType; -} +}; /** * Categories. @@ -336,23 +340,42 @@ export type Cost = { }; /** - * Estimated time units. - * Used to display estimated time in quest details. + * Time units. + * Used to display time required in quest details. */ -export const TIME_UNITS = { - minutes: "Minutes", - hours: "Hours", - days: "Days", - weeks: "Weeks", - months: "Months", -} as const; +export type TimeUnit = "minutes" | "hours" | "days" | "weeks" | "months"; + +export const TIME_UNITS: Record = { + minutes: { + label: "Minutes", + icon: RiFlashlightLine, + }, + hours: { + label: "Hours", + icon: RiTimeLine, + }, + days: { + label: "Days", + icon: RiCalendarLine, + }, + weeks: { + label: "Weeks", + icon: RiCalendar2Line, + }, + months: { + label: "Months", + icon: RiCalendarScheduleLine, + }, +}; -export type TimeRequiredUnit = keyof typeof TIME_UNITS; +export const TIME_UNITS_ORDER: TimeUnit[] = Object.keys( + TIME_UNITS, +) as TimeUnit[]; export type TimeRequired = { min: number; max: number; - unit: TimeRequiredUnit; + unit: TimeUnit; }; export const DEFAULT_TIME_REQUIRED: TimeRequired = { diff --git a/convex/userQuests.ts b/convex/userQuests.ts index 1f6025d..f2f9505 100644 --- a/convex/userQuests.ts +++ b/convex/userQuests.ts @@ -1,6 +1,6 @@ import { v } from "convex/values"; import { query } from "./_generated/server"; -import { type Category, STATUS, type Status } from "./constants"; +import { type Category, STATUS, type Status, type TimeUnit } from "./constants"; import { userMutation, userQuery } from "./helpers"; import { status } from "./validators"; @@ -319,7 +319,7 @@ export const getUserQuestsByStatus = userQuery({ ); // Initialize an object with all possible status keys - const initial: Record = { + const initial: Record = { notStarted: [], inProgress: [], readyToFile: [], @@ -329,8 +329,49 @@ export const getUserQuestsByStatus = userQuery({ // Group quests by their status return validQuests.reduce((acc, quest) => { - acc[quest.status].push(quest); + acc[quest.status as Status].push(quest); return acc; }, initial); }, }); + +export const getUserQuestsByTimeRequired = userQuery({ + args: {}, + handler: async (ctx) => { + const userQuests = await ctx.db + .query("userQuests") + .withIndex("userId", (q) => q.eq("userId", ctx.userId)) + .collect(); + + const questsWithDetails = await Promise.all( + userQuests.map(async (userQuest) => { + const quest = await ctx.db.get(userQuest.questId); + return quest && quest.deletionTime === undefined + ? { ...quest, ...userQuest } + : null; + }), + ); + + const validQuests = questsWithDetails.filter( + (q): q is NonNullable => q !== null, + ); + + // Initialize an object with all possible time required keys + const initial: Record = { + minutes: [], + hours: [], + days: [], + weeks: [], + months: [], + }; + + // Group quests by their time required unit + const group = validQuests.reduce((acc, quest) => { + acc[quest.timeRequired.unit as TimeUnit].push(quest); + return acc; + }, initial); + + console.log("group", group); + return group; + }, +}); diff --git a/src/routes/_authenticated/_home.tsx b/src/routes/_authenticated/_home.tsx index 9de1e9c..7c6e185 100644 --- a/src/routes/_authenticated/_home.tsx +++ b/src/routes/_authenticated/_home.tsx @@ -24,10 +24,14 @@ import { DATE_ADDED, DATE_ADDED_ORDER, type DateAdded, + type GroupDetails, type GroupQuestsBy, STATUS, STATUS_ORDER, type Status, + TIME_UNITS, + TIME_UNITS_ORDER, + type TimeUnit, } from "@convex/constants"; import { RiAddLine, RiListCheck2, RiSignpostLine } from "@remixicon/react"; import { Outlet, createFileRoute } from "@tanstack/react-router"; @@ -61,6 +65,11 @@ function sortGroupedQuests( const indexB = DATE_ADDED_ORDER.indexOf(groupB as DateAdded); return indexA - indexB; } + case "timeRequired": { + const indexA = TIME_UNITS_ORDER.indexOf(groupA as TimeUnit); + const indexB = TIME_UNITS_ORDER.indexOf(groupB as TimeUnit); + return indexA - indexB; + } } } @@ -85,11 +94,15 @@ function IndexRoute() { const questsByCategory = useQuery(api.userQuests.getUserQuestsByCategory); const questsByDate = useQuery(api.userQuests.getUserQuestsByDate); const questsByStatus = useQuery(api.userQuests.getUserQuestsByStatus); + const questsByTimeRequired = useQuery( + api.userQuests.getUserQuestsByTimeRequired, + ); const groupedQuests = { category: questsByCategory, dateAdded: questsByDate, status: questsByStatus, + timeRequired: questsByTimeRequired, }[groupByValue]; if (groupedQuests === undefined) return; @@ -111,8 +124,9 @@ function IndexRoute() { const allCategoryKeys = [ ...Object.values(CATEGORIES).map((category) => category.label), - ...Object.values(DATE_ADDED).map((date) => date.label), ...Object.values(STATUS).map((status) => status.label), + ...Object.values(DATE_ADDED).map((date) => date.label), + ...Object.values(TIME_UNITS).map((timeUnit) => timeUnit.label), ]; return ( @@ -136,8 +150,9 @@ function IndexRoute() { > Category - Date added Status + Date added + Time required @@ -165,12 +180,22 @@ function IndexRoute() { ) .map(([group, quests]) => { if (quests.length === 0) return null; - const { label, icon: Icon } = - groupByValue === "category" - ? CATEGORIES[group as keyof typeof CATEGORIES] - : groupByValue === "status" - ? STATUS[group as keyof typeof STATUS] - : DATE_ADDED[group as keyof typeof DATE_ADDED]; + let groupDetails: GroupDetails; + switch (groupByValue) { + case "category": + groupDetails = CATEGORIES[group as keyof typeof CATEGORIES]; + break; + case "status": + groupDetails = STATUS[group as keyof typeof STATUS]; + break; + case "timeRequired": + groupDetails = TIME_UNITS[group as keyof typeof TIME_UNITS]; + break; + case "dateAdded": + groupDetails = DATE_ADDED[group as keyof typeof DATE_ADDED]; + break; + } + const { label, icon: Icon } = groupDetails; return ( onChange({ ...timeRequired, - unit: key as TimeRequiredUnit, + unit: key as TimeUnit, }) } > - {Object.entries(TIME_UNITS).map(([key, label]) => ( + {Object.entries(TIME_UNITS).map(([key, unit]) => ( - {label} + {unit.label} ))}