mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
sort module button
This commit is contained in:
@@ -15,6 +15,7 @@ services:
|
|||||||
- NEXT_PUBLIC_ENABLE_FILE_SYNC=true
|
- NEXT_PUBLIC_ENABLE_FILE_SYNC=true
|
||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
volumes:
|
volumes:
|
||||||
|
# - ./globalSettings.dev.yml:/app/globalSettings.yml
|
||||||
- ./globalSettings.yml:/app/globalSettings.yml
|
- ./globalSettings.yml:/app/globalSettings.yml
|
||||||
- .:/app
|
- .:/app
|
||||||
- ~/projects/faculty:/app/storage
|
- ~/projects/faculty:/app/storage
|
||||||
|
|||||||
@@ -14,30 +14,8 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
# - FILE_POLLING=true
|
# - FILE_POLLING=true
|
||||||
volumes:
|
volumes:
|
||||||
# - ~/projects/faculty/3840_Telemetry/2024Spring_alex/modules:/app/storage/spring_2024_telemetry
|
- ./globalSettings.yml:/app/globalSettings.yml
|
||||||
|
- ~/projects/faculty:/app/storage
|
||||||
# - ~/projects/faculty/1400/2025_spring_alex/modules:/app/storage/1400
|
|
||||||
# - ~/projects/faculty/1405/2025_spring_alex:/app/storage/1405
|
|
||||||
# - ~/projects/faculty/3840_Telemetry/2025_spring_alex/modules:/app/storage/telemetry
|
|
||||||
# - ~/projects/faculty/4620_Distributed/2025Spring/modules:/app/storage/distributed
|
|
||||||
# - ~/projects/faculty/4620_Distributed/2024Spring/modules:/app/storage/distributed_old
|
|
||||||
|
|
||||||
|
|
||||||
- ~/projects/faculty/1810/2025-spring-alex/in-person:/app/storage/intro_to_web_old
|
|
||||||
- ~/projects/faculty/1810/2025-fall-alex/modules:/app/storage/intro_to_web
|
|
||||||
|
|
||||||
- ~/projects/faculty/4850_AdvancedFE/2025-fall-alex/modules:/app/storage/advanced_frontend
|
|
||||||
- ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend_old
|
|
||||||
|
|
||||||
- ~/projects/faculty/1430/2024-fall-alex/modules:/app/storage/ux_old
|
|
||||||
- ~/projects/faculty/1430/2025-fall-alex/modules:/app/storage/ux
|
|
||||||
|
|
||||||
- ~/projects/faculty/1420/2024-fall/Modules:/app/storage/1420_old
|
|
||||||
- ~/projects/faculty/1420/2025-fall-alex/modules:/app/storage/1420
|
|
||||||
|
|
||||||
- ~/projects/faculty/1425/2024-fall/Modules:/app/storage/1425_old
|
|
||||||
- ~/projects/faculty/1425/2025-fall-alex/modules:/app/storage/1425
|
|
||||||
|
|
||||||
- ~/projects/facultyFiles:/app/public/images/facultyFiles
|
- ~/projects/facultyFiles:/app/public/images/facultyFiles
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
globalSettings.dev.yml
Normal file
5
globalSettings.dev.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
courses:
|
||||||
|
- path: ./3820_BackEnd/2025-fall/Modules/
|
||||||
|
name: Back-End
|
||||||
|
- path: ./3820_BackEnd/2024-fall/Modules/
|
||||||
|
name: Back-End
|
||||||
12
requests/module.http
Normal file
12
requests/module.http
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
### doesn't work, there is a form request that does thi son the site, but this isn't it
|
||||||
|
POST https://snow.instructure.com/api/v1/courses/1154759/modules/3965250/reorder
|
||||||
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
29273428, 29274455, 29272498, 29274867, 29272743, 29273425
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -15,6 +15,8 @@ export default function CourseList() {
|
|||||||
|
|
||||||
const sortedDates = Object.keys(coursesByStartDate).sort();
|
const sortedDates = Object.keys(coursesByStartDate).sort();
|
||||||
|
|
||||||
|
console.log(allSettings, coursesByStartDate);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row ">
|
<div className="flex flex-row ">
|
||||||
{sortedDates.map((startDate) => (
|
{sortedDates.map((startDate) => (
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import { useQuizzesQueries } from "@/features/local/quizzes/quizHooks";
|
|||||||
import { useTRPC } from "@/services/serverFunctions/trpcClient";
|
import { useTRPC } from "@/services/serverFunctions/trpcClient";
|
||||||
import { useSuspenseQueries } from "@tanstack/react-query";
|
import { useSuspenseQueries } from "@tanstack/react-query";
|
||||||
import { useAssignmentNamesQuery } from "@/features/local/assignments/assignmentHooks";
|
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({
|
export default function ExpandableModule({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -50,6 +53,8 @@ export default function ExpandableModule({
|
|||||||
const { data: quizzes } = useQuizzesQueries(moduleName);
|
const { data: quizzes } = useQuizzesQueries(moduleName);
|
||||||
const { data: pages } = usePagesQueries(moduleName);
|
const { data: pages } = usePagesQueries(moduleName);
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
|
const reorderMutation = useReorderCanvasModuleItemsMutation();
|
||||||
|
const { data: canvasModules } = useCanvasModulesQuery();
|
||||||
|
|
||||||
const moduleItems: {
|
const moduleItems: {
|
||||||
type: "assignment" | "quiz" | "page";
|
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
|
<Modal
|
||||||
modalControl={modal}
|
modalControl={modal}
|
||||||
buttonText="New Item"
|
buttonText="New Item"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { CanvasModuleItem } from "@/features/canvas/models/modules/canvasModuleItems";
|
||||||
import { useLocalCourseSettingsQuery } from "@/features/local/course/localCoursesHooks";
|
import { useLocalCourseSettingsQuery } from "@/features/local/course/localCoursesHooks";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { canvasModuleService } from "../services/canvasModuleService";
|
import { canvasModuleService } from "../services/canvasModuleService";
|
||||||
|
import { IModuleItem } from "@/features/local/modules/IModuleItem";
|
||||||
|
|
||||||
export const canvasCourseModuleKeys = {
|
export const canvasCourseModuleKeys = {
|
||||||
modules: (canvasId: number) => ["canvas", canvasId, "module list"] as const,
|
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),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ export const canvasModuleService = {
|
|||||||
if (!data) throw new Error("Something went wrong updating module item");
|
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(
|
async createModuleItem(
|
||||||
canvasCourseId: number,
|
canvasCourseId: number,
|
||||||
canvasModuleId: number,
|
canvasModuleId: number,
|
||||||
@@ -63,4 +70,21 @@ export const canvasModuleService = {
|
|||||||
const response = await axiosClient.post<CanvasModule>(url, body);
|
const response = await axiosClient.post<CanvasModule>(url, body);
|
||||||
return response.data.id;
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { GlobalSettingsCourse } from "../globalSettings/globalSettingsModels";
|
|||||||
const getCourseSettings = async (
|
const getCourseSettings = async (
|
||||||
course: GlobalSettingsCourse
|
course: GlobalSettingsCourse
|
||||||
): Promise<LocalCourseSettings> => {
|
): Promise<LocalCourseSettings> => {
|
||||||
const courseDirectory = await getCoursePathByName(course.name);
|
const courseDirectory = path.join(basePath, course.path);
|
||||||
const settingsPath = path.join(courseDirectory, "settings.yml");
|
const settingsPath = path.join(courseDirectory, "settings.yml");
|
||||||
if (!(await directoryOrFileExists(settingsPath))) {
|
if (!(await directoryOrFileExists(settingsPath))) {
|
||||||
const errorMessage = `could not find settings for ${course.name}, settings file ${settingsPath}`;
|
const errorMessage = `could not find settings for ${course.name}, settings file ${settingsPath}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user