Skip to content

Commit

Permalink
Add sorting by time required
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker committed Nov 17, 2024
1 parent 5d9e8a0 commit b6939fc
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 28 deletions.
49 changes: 36 additions & 13 deletions convex/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import {
RiAccountCircleLine,
RiAtLine,
RiBankLine,
RiCalendar2Line,
RiCalendarLine,
RiCalendarScheduleLine,
RiChat3Line,
RiCheckboxBlankCircleLine,
RiCheckboxCircleFill,
RiCheckboxLine,
RiComputerLine,
RiDropdownList,
RiFlashlightLine,
RiFolderCheckLine,
RiFolderLine,
RiGamepadLine,
Expand Down Expand Up @@ -159,17 +162,18 @@ 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;

/**
* Generic group details.
* Used for UI display of filter groups.
*/
interface GroupDetails {
export type GroupDetails = {
label: string;
icon: RemixiconComponentType;
}
};

/**
* Categories.
Expand Down Expand Up @@ -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<TimeUnit, GroupDetails> = {
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 = {
Expand Down
47 changes: 44 additions & 3 deletions convex/userQuests.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -319,7 +319,7 @@ export const getUserQuestsByStatus = userQuery({
);

// Initialize an object with all possible status keys
const initial: Record<string, typeof validQuests> = {
const initial: Record<Status, typeof validQuests> = {
notStarted: [],
inProgress: [],
readyToFile: [],
Expand All @@ -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<typeof q> => q !== null,
);

// Initialize an object with all possible time required keys
const initial: Record<TimeUnit, typeof validQuests> = {
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;
},
});
41 changes: 33 additions & 8 deletions src/routes/_authenticated/_home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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;
Expand All @@ -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 (
Expand All @@ -136,8 +150,9 @@ function IndexRoute() {
>
<MenuSection title="Group by">
<MenuItem id="category">Category</MenuItem>
<MenuItem id="dateAdded">Date added</MenuItem>
<MenuItem id="status">Status</MenuItem>
<MenuItem id="dateAdded">Date added</MenuItem>
<MenuItem id="timeRequired">Time required</MenuItem>
</MenuSection>
</Menu>
</MenuTrigger>
Expand Down Expand Up @@ -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 (
<Disclosure
Expand Down
8 changes: 4 additions & 4 deletions src/routes/_authenticated/admin/quests/$questId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
type Jurisdiction,
TIME_UNITS,
type TimeRequired,
type TimeRequiredUnit,
type TimeUnit,
} from "@convex/constants";
import { RiQuestionLine } from "@remixicon/react";
import { createFileRoute } from "@tanstack/react-router";
Expand Down Expand Up @@ -187,13 +187,13 @@ const TimeRequiredInput = memo(function TimeRequiredInput({
onSelectionChange={(key) =>
onChange({
...timeRequired,
unit: key as TimeRequiredUnit,
unit: key as TimeUnit,
})
}
>
{Object.entries(TIME_UNITS).map(([key, label]) => (
{Object.entries(TIME_UNITS).map(([key, unit]) => (
<SelectItem key={key} id={key}>
{label}
{unit.label}
</SelectItem>
))}
</Select>
Expand Down

0 comments on commit b6939fc

Please sign in to comment.