sort module button

This commit is contained in:
2025-08-11 14:06:59 -06:00
parent 5715b081a9
commit 762a51d6da
9 changed files with 133 additions and 29 deletions

View File

@@ -15,6 +15,8 @@ export default function CourseList() {
const sortedDates = Object.keys(coursesByStartDate).sort();
console.log(allSettings, coursesByStartDate);
return (
<div className="flex flex-row ">
{sortedDates.map((startDate) => (
@@ -29,10 +31,10 @@ export default function CourseList() {
href={getCourseUrl(settings.name)}
shallow={true}
className="
font-bold text-xl block
transition-all hover:scale-105 hover:underline hover:text-slate-200
mb-3
"
font-bold text-xl block
transition-all hover:scale-105 hover:underline hover:text-slate-200
mb-3
"
>
{settings.name}
</Link>

View File

@@ -25,6 +25,9 @@ import { useQuizzesQueries } from "@/features/local/quizzes/quizHooks";
import { useTRPC } from "@/services/serverFunctions/trpcClient";
import { useSuspenseQueries } from "@tanstack/react-query";
import { useAssignmentNamesQuery } from "@/features/local/assignments/assignmentHooks";
import { useReorderCanvasModuleItemsMutation } from "@/features/canvas/hooks/canvasModuleHooks";
import { useCanvasModulesQuery } from "@/features/canvas/hooks/canvasModuleHooks";
import { Spinner } from "@/components/Spinner";
export default function ExpandableModule({
moduleName,
@@ -50,6 +53,8 @@ export default function ExpandableModule({
const { data: quizzes } = useQuizzesQueries(moduleName);
const { data: pages } = usePagesQueries(moduleName);
const modal = useModal();
const reorderMutation = useReorderCanvasModuleItemsMutation();
const { data: canvasModules } = useCanvasModulesQuery();
const moduleItems: {
type: "assignment" | "quiz" | "page";
@@ -110,6 +115,30 @@ export default function ExpandableModule({
)}
>
<>
{!reorderMutation.isPending && (
<button
className=" me-3"
onClick={() => {
const canvasModuleId = canvasModules?.find(
(m) => m.name === moduleName
)?.id;
if (!canvasModuleId) {
console.error(
"Canvas module ID not found for",
moduleName
);
return;
}
reorderMutation.mutate({
moduleId: canvasModuleId,
items: moduleItems.map((item) => item.item),
});
}}
>
Sort by Due Date
</button>
)}
{reorderMutation.isPending && <Spinner />}
<Modal
modalControl={modal}
buttonText="New Item"

View File

@@ -1,6 +1,8 @@
import { CanvasModuleItem } from "@/features/canvas/models/modules/canvasModuleItems";
import { useLocalCourseSettingsQuery } from "@/features/local/course/localCoursesHooks";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { canvasModuleService } from "../services/canvasModuleService";
import { IModuleItem } from "@/features/local/modules/IModuleItem";
export const canvasCourseModuleKeys = {
modules: (canvasId: number) => ["canvas", canvasId, "module list"] as const,
@@ -28,3 +30,54 @@ export const useAddCanvasModuleMutation = () => {
},
});
};
export const useReorderCanvasModuleItemsMutation = () => {
const { data: settings } = useLocalCourseSettingsQuery();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
moduleId,
items,
}: {
moduleId: number;
items: IModuleItem[];
}) => {
if (!settings?.canvasId) throw new Error("No canvasId in settings");
const canvasModule = await canvasModuleService.getModuleWithItems(
settings.canvasId,
moduleId
);
if (!canvasModule.items) {
throw new Error(
"cannot sort canvas module items, no items found in module"
);
}
const canvasItems = canvasModule.items;
// Sort IModuleItems by dueAt
const sorted = [...items].sort((a, b) => {
const aDate = a.dueAt ? new Date(a.dueAt).getTime() : 0;
const bDate = b.dueAt ? new Date(b.dueAt).getTime() : 0;
return aDate - bDate;
});
// Map sorted IModuleItems to CanvasModuleItem ids by matching name/title
const orderedIds = sorted
.map((localItem) => canvasItems.find((canvasItem) => canvasItem.title === localItem.name)?.id)
.filter((id): id is number => typeof id === "number");
return await canvasModuleService.reorderModuleItems(
settings.canvasId,
moduleId,
orderedIds
);
},
onSuccess: (_data) => {
if (!settings?.canvasId) return;
queryClient.invalidateQueries({
queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
});
},
});
};

View File

@@ -20,6 +20,13 @@ export const canvasModuleService = {
if (!data) throw new Error("Something went wrong updating module item");
},
async getModuleWithItems(canvasCourseId: number, moduleId: number) {
const url = `${canvasApi}/courses/${canvasCourseId}/modules/${moduleId}`;
const params = { include: ["items"] };
const response = await axiosClient.get<CanvasModule>(url, { params });
return response.data;
},
async createModuleItem(
canvasCourseId: number,
canvasModuleId: number,
@@ -63,4 +70,21 @@ export const canvasModuleService = {
const response = await axiosClient.post<CanvasModule>(url, body);
return response.data.id;
},
async reorderModuleItems(
canvasCourseId: number,
canvasModuleId: number,
itemIds: number[]
) {
for (let i = 0; i < itemIds.length; i++) {
const itemId = itemIds[i];
const url = `${canvasApi}/courses/${canvasCourseId}/modules/${canvasModuleId}/items/${itemId}`;
const body = {
module_item: {
position: i + 1,
},
};
await axiosClient.put(url, body);
}
},
};

View File

@@ -15,7 +15,7 @@ import { GlobalSettingsCourse } from "../globalSettings/globalSettingsModels";
const getCourseSettings = async (
course: GlobalSettingsCourse
): Promise<LocalCourseSettings> => {
const courseDirectory = await getCoursePathByName(course.name);
const courseDirectory = path.join(basePath, course.path);
const settingsPath = path.join(courseDirectory, "settings.yml");
if (!(await directoryOrFileExists(settingsPath))) {
const errorMessage = `could not find settings for ${course.name}, settings file ${settingsPath}`;