mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 15:48:32 -06:00
hot reloading is better
This commit is contained in:
18
nextjs/src/app/CourseDetailsWrapper.tsx
Normal file
18
nextjs/src/app/CourseDetailsWrapper.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
|
||||||
|
import { CourseContext } from "./course/[courseName]/courseContext";
|
||||||
|
import CourseDetails from "./course/[courseName]/CourseDetails";
|
||||||
|
|
||||||
|
export default function CourseDetailsWrapper({
|
||||||
|
courseName,
|
||||||
|
}: {
|
||||||
|
courseName: string;
|
||||||
|
}) {
|
||||||
|
const { data: course } = useLocalCourseDetailsQuery(courseName);
|
||||||
|
console.log("courseName", courseName);
|
||||||
|
return (
|
||||||
|
<CourseContext.Provider value={{ localCourse: course }}>
|
||||||
|
<CourseDetails />
|
||||||
|
</CourseContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
nextjs/src/app/CourseList.tsx
Normal file
16
nextjs/src/app/CourseList.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"use client"
|
||||||
|
import { useLocalCoursesQuery } from "@/hooks/localCoursesHooks";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function CourseList() {
|
||||||
|
const { data: courses } = useLocalCoursesQuery();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{courses.map((c) => (
|
||||||
|
<Link href={`/course/${c.settings.name}`} key={c.settings.name}>
|
||||||
|
{c.settings.name}{" "}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
"use client"
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CalendarMonthModel } from "./calendarMonthUtils";
|
import { CalendarMonthModel } from "./calendarMonthUtils";
|
||||||
import { DayOfWeek, LocalCourse } from "@/models/local/localCourse";
|
import { DayOfWeek, LocalCourse } from "@/models/local/localCourse";
|
||||||
import Day from "./day";
|
import Day from "./Day";
|
||||||
|
|
||||||
export default function CalendarMonth({
|
export default function CalendarMonth({
|
||||||
month,
|
month,
|
||||||
@@ -55,7 +56,7 @@ export default function CalendarMonth({
|
|||||||
{month.daysByWeek.map((week, weekIndex) => (
|
{month.daysByWeek.map((week, weekIndex) => (
|
||||||
<div className="grid grid-cols-7 m-3" key={weekIndex}>
|
<div className="grid grid-cols-7 m-3" key={weekIndex}>
|
||||||
{week.map((day, dayIndex) => (
|
{week.map((day, dayIndex) => (
|
||||||
<Day key={dayIndex} day={day} />
|
<Day key={dayIndex} day={day} month={month.month} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
19
nextjs/src/app/course/[courseName]/CourseContextProvider.tsx
Normal file
19
nextjs/src/app/course/[courseName]/CourseContextProvider.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client"
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { CourseContext } from "./courseContext";
|
||||||
|
import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
|
||||||
|
|
||||||
|
export default function CourseContextProvider({
|
||||||
|
localCourseName,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
localCourseName: string;
|
||||||
|
}) {
|
||||||
|
const { data: course } = useLocalCourseDetailsQuery(localCourseName);
|
||||||
|
return (
|
||||||
|
<CourseContext.Provider value={{ localCourse: course }}>
|
||||||
|
{children}
|
||||||
|
</CourseContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
nextjs/src/app/course/[courseName]/CourseDetails.tsx
Normal file
42
nextjs/src/app/course/[courseName]/CourseDetails.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
|
||||||
|
import { useCourseContext } from "./courseContext";
|
||||||
|
import { getMonthsBetweenDates } from "./calendarMonthUtils";
|
||||||
|
import CalendarMonth from "./CalendarMonth";
|
||||||
|
|
||||||
|
export default function CourseDetails() {
|
||||||
|
const context = useCourseContext();
|
||||||
|
|
||||||
|
const startDate = getDateFromStringOrThrow(
|
||||||
|
context.localCourse.settings.startDate,
|
||||||
|
"course start date"
|
||||||
|
);
|
||||||
|
const endDate = getDateFromStringOrThrow(
|
||||||
|
context.localCourse.settings.endDate,
|
||||||
|
"course end date"
|
||||||
|
);
|
||||||
|
|
||||||
|
const months = getMonthsBetweenDates(startDate, endDate);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{context.localCourse.settings.name}
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="grow">
|
||||||
|
{months.map((month) => (
|
||||||
|
<CalendarMonth
|
||||||
|
key={month.month + "" + month.year}
|
||||||
|
month={month}
|
||||||
|
localCourse={context.localCourse}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="w-96">
|
||||||
|
<details>
|
||||||
|
<summary role="button">first module</summary>
|
||||||
|
<div>here are the module items</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
nextjs/src/app/course/[courseName]/Day.tsx
Normal file
13
nextjs/src/app/course/[courseName]/Day.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"use client"
|
||||||
|
export default function Day({ day, month }: { day: Date; month: number }) {
|
||||||
|
const classes = "border rounded rounded-3 p-2 pb-4 m-1 ";
|
||||||
|
|
||||||
|
const backgroundClass = day.getMonth() + 1 != month ? "" : "bg-slate-900";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes + " " + backgroundClass}>
|
||||||
|
{day.getDate()}
|
||||||
|
{/* <div>{day.getMonth()}</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
nextjs/src/app/course/[courseName]/ModuleList.tsx
Normal file
4
nextjs/src/app/course/[courseName]/ModuleList.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"use client"
|
||||||
|
export default function ModuleList() {
|
||||||
|
return <div></div>
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
"use client"
|
||||||
export interface CalendarMonthModel {
|
export interface CalendarMonthModel {
|
||||||
year: number;
|
year: number;
|
||||||
month: number;
|
month: number;
|
||||||
weeks: (number | undefined)[][];
|
weeks: number[][];
|
||||||
daysByWeek: (Date | undefined)[][];
|
daysByWeek: (Date)[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
function weeksInMonth(year: number, month: number): number {
|
function weeksInMonth(year: number, month: number): number {
|
||||||
@@ -17,42 +18,27 @@ function weeksInMonth(year: number, month: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createCalendarMonth(year: number, month: number): CalendarMonthModel {
|
function createCalendarMonth(year: number, month: number): CalendarMonthModel {
|
||||||
const daysByWeek: (Date | undefined)[][] = [];
|
|
||||||
const weeksNumber = weeksInMonth(year, month);
|
const weeksNumber = weeksInMonth(year, month);
|
||||||
const daysInMonth = new Date(year, month, 0).getDate();
|
const daysInMonth = new Date(year, month, 0).getDate();
|
||||||
|
|
||||||
let currentDay = 1;
|
let currentDay = 1;
|
||||||
const firstDayOfMonth = new Date(year, month - 1, 1).getDay();
|
const firstDayOfMonth = new Date(year, month - 1, 1).getDay();
|
||||||
|
|
||||||
for (let i = 0; i < weeksNumber; i++) {
|
const daysByWeek = Array.from({ length: weeksNumber }).map((_, weekIndex) =>
|
||||||
const thisWeek: (Date | undefined)[] = [];
|
Array.from({ length: 7 }).map((_, dayIndex) => {
|
||||||
if (i === 0 && firstDayOfMonth !== 0) {
|
if (weekIndex === 0 && dayIndex < firstDayOfMonth) {
|
||||||
// 0 is Sunday in JavaScript
|
return new Date(year, month - 1, dayIndex - firstDayOfMonth + 1);
|
||||||
for (let j = 0; j < 7; j++) {
|
} else if (currentDay <= daysInMonth) {
|
||||||
if (j < firstDayOfMonth) {
|
return new Date(year, month - 1, currentDay++);
|
||||||
thisWeek.push(undefined);
|
} else {
|
||||||
} else {
|
currentDay++;
|
||||||
thisWeek.push(new Date(year, month - 1, currentDay));
|
return new Date(year, month, currentDay - daysInMonth);
|
||||||
currentDay++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
for (let j = 0; j < 7; j++) {
|
|
||||||
if (currentDay <= daysInMonth) {
|
|
||||||
thisWeek.push(new Date(year, month - 1, currentDay));
|
|
||||||
currentDay++;
|
|
||||||
} else {
|
|
||||||
thisWeek.push(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
daysByWeek.push(thisWeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
const weeks = daysByWeek.map((week) =>
|
|
||||||
week.map((day) => (day ? day.getDate() : undefined))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const weeks = daysByWeek.map((week) => week.map((day) => day.getDate()));
|
||||||
|
|
||||||
return { year, month, weeks, daysByWeek };
|
return { year, month, weeks, daysByWeek };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
nextjs/src/app/course/[courseName]/courseContext.ts
Normal file
31
nextjs/src/app/course/[courseName]/courseContext.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
import { LocalCourse } from "@/models/local/localCourse";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
export interface CourseContextInterface {
|
||||||
|
localCourse: LocalCourse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValue: CourseContextInterface = {
|
||||||
|
localCourse: {
|
||||||
|
modules: [],
|
||||||
|
settings: {
|
||||||
|
name: "",
|
||||||
|
assignmentGroups: [],
|
||||||
|
daysOfWeek: [],
|
||||||
|
startDate: "",
|
||||||
|
endDate: "",
|
||||||
|
defaultDueTime: {
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CourseContext =
|
||||||
|
createContext<CourseContextInterface>(defaultValue);
|
||||||
|
|
||||||
|
export function useCourseContext() {
|
||||||
|
return useContext(CourseContext);
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default function Day({ day }: { day?: Date }) {
|
|
||||||
const classes = "border rounded rounded-3 p-2 pb-4 m-1 ";
|
|
||||||
if (!day) return <div className={classes + ""}></div>;
|
|
||||||
|
|
||||||
return <div className={classes + " bg-slate-900"}>{day.getDate()}</div>;
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,16 @@
|
|||||||
"use client";
|
import CourseDetailsWrapper from "@/app/CourseDetailsWrapper";
|
||||||
import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
|
import { getDehydratedClient } from "@/app/layout";
|
||||||
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
|
import { HydrationBoundary } from "@tanstack/react-query";
|
||||||
import { getMonthsBetweenDates } from "./calendarMonthUtils";
|
|
||||||
import CalendarMonth from "./calendarMonth";
|
|
||||||
|
|
||||||
|
export default async function CoursePage({
|
||||||
export default function Page({
|
|
||||||
params: { courseName },
|
params: { courseName },
|
||||||
}: {
|
}: {
|
||||||
params: { courseName: string };
|
params: { courseName: string };
|
||||||
}) {
|
}) {
|
||||||
const { data: course } = useLocalCourseDetailsQuery(courseName);
|
const dehydratedState = await getDehydratedClient();
|
||||||
|
|
||||||
const startDate = getDateFromStringOrThrow(course.settings.startDate);
|
|
||||||
const endDate = getDateFromStringOrThrow(course.settings.endDate);
|
|
||||||
|
|
||||||
const months = getMonthsBetweenDates(startDate, endDate);
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<HydrationBoundary state={dehydratedState}>
|
||||||
{course.settings.name}
|
<CourseDetailsWrapper courseName={courseName} />
|
||||||
<div>
|
</HydrationBoundary>
|
||||||
{months.map((month) => (
|
|
||||||
<CalendarMonth key={month.month + "" + month.year} month={month} localCourse={course} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
11
nextjs/src/app/getDehydratedState.ts
Normal file
11
nextjs/src/app/getDehydratedState.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { hydrateCourses } from "@/hooks/hookHydration";
|
||||||
|
import { createQueryClientForServer } from "@/services/utils/queryClientServer";
|
||||||
|
import { dehydrate } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export async function getDehydratedState() {
|
||||||
|
const queryClient = createQueryClientForServer();
|
||||||
|
|
||||||
|
await hydrateCourses(queryClient);
|
||||||
|
const dehydratedState = dehydrate(queryClient);
|
||||||
|
return dehydratedState;
|
||||||
|
}
|
||||||
@@ -18,12 +18,7 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
color: rgb(var(--foreground-rgb));
|
color: rgb(var(--foreground-rgb));
|
||||||
background: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
transparent,
|
|
||||||
rgb(var(--background-end-rgb))
|
|
||||||
)
|
|
||||||
rgb(var(--background-start-rgb));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import "./globals.css";
|
|||||||
import { dehydrate } from "@tanstack/react-query";
|
import { dehydrate } from "@tanstack/react-query";
|
||||||
import { hydrateCourses } from "@/hooks/hookHydration";
|
import { hydrateCourses } from "@/hooks/hookHydration";
|
||||||
import { createQueryClientForServer } from "@/services/utils/queryClientServer";
|
import { createQueryClientForServer } from "@/services/utils/queryClientServer";
|
||||||
import MyQueryClientProvider from "@/services/utils/MyQueryClientProvider";
|
import Providers from "./providers";
|
||||||
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Canvas Manager 2.0",
|
title: "Canvas Manager 2.0",
|
||||||
@@ -18,18 +17,16 @@ export async function getDehydratedClient() {
|
|||||||
return dehydratedState;
|
return dehydratedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const dehydratedState = await getDehydratedClient();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<MyQueryClientProvider dehydratedState={dehydratedState}>
|
<Providers>
|
||||||
<body className="bg-slate-950">{children}</body>
|
<body className="bg-slate-950">{children}</body>
|
||||||
</MyQueryClientProvider>
|
</Providers>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client"
|
import { HydrationBoundary } from "@tanstack/react-query";
|
||||||
import { useLocalCoursesQuery } from "@/hooks/localCoursesHooks";
|
import CourseList from "./CourseList";
|
||||||
import Link from "next/link";
|
import { getDehydratedClient } from "./layout";
|
||||||
|
|
||||||
export default function Home() {
|
export default async function Home() {
|
||||||
const { data: courses } = useLocalCoursesQuery();
|
const dehydratedState = await getDehydratedClient();
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen">
|
<main className="min-h-screen">
|
||||||
{courses.map((c) => (
|
<HydrationBoundary state={dehydratedState}>
|
||||||
<Link href={`/course/${c.settings.name}`} key={c.settings.name}>{c.settings.name} </Link>
|
<CourseList />
|
||||||
))}
|
</HydrationBoundary>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
47
nextjs/src/app/providers.tsx
Normal file
47
nextjs/src/app/providers.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
isServer,
|
||||||
|
QueryClient,
|
||||||
|
QueryClientProvider,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
function makeQueryClient() {
|
||||||
|
return new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
// With SSR, we usually want to set some default staleTime
|
||||||
|
// above 0 to avoid refetching immediately on the client
|
||||||
|
staleTime: 60 * 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let browserQueryClient: QueryClient | undefined = undefined;
|
||||||
|
|
||||||
|
function getQueryClient() {
|
||||||
|
if (isServer) {
|
||||||
|
// Server: always make a new query client
|
||||||
|
return makeQueryClient();
|
||||||
|
} else {
|
||||||
|
// Browser: make a new query client if we don't already have one
|
||||||
|
// This is very important, so we don't re-make a new client if React
|
||||||
|
// suspends during the initial render. This may not be needed if we
|
||||||
|
// have a suspense boundary BELOW the creation of the query client
|
||||||
|
if (!browserQueryClient) browserQueryClient = makeQueryClient();
|
||||||
|
return browserQueryClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Providers({ children }: { children: ReactNode }) {
|
||||||
|
// NOTE: Avoid useState when initializing the query client if you don't
|
||||||
|
// have a suspense boundary between this and the code that may
|
||||||
|
// suspend because React will throw away the client on the initial
|
||||||
|
// render if it suspends and there is no boundary
|
||||||
|
const queryClient = getQueryClient();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -56,9 +56,9 @@ export const getDateFromString = (value: string): Date | undefined => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDateFromStringOrThrow = (value: string): Date => {
|
export const getDateFromStringOrThrow = (value: string, labelForError: string): Date => {
|
||||||
const d = getDateFromString(value);
|
const d = getDateFromString(value);
|
||||||
if (!d) throw Error(`Invalid date format, ${value}`);
|
if (!d) throw Error(`Invalid date format for ${labelForError}, ${value}`);
|
||||||
return d;
|
return d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ export const canvasServiceUtils = {
|
|||||||
const url = new URL(request.url!, BASE_URL);
|
const url = new URL(request.url!, BASE_URL);
|
||||||
url.searchParams.set("per_page", "100");
|
url.searchParams.set("per_page", "100");
|
||||||
|
|
||||||
const [firstData, firstResponse] = await webRequestor.get<T>(
|
const { data: firstData, response: firstResponse } =
|
||||||
url.toString()
|
await webRequestor.get<T>(url.toString());
|
||||||
);
|
|
||||||
|
|
||||||
if (!firstResponse.ok) {
|
if (!firstResponse.ok) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -41,7 +40,7 @@ export const canvasServiceUtils = {
|
|||||||
|
|
||||||
while (nextUrl) {
|
while (nextUrl) {
|
||||||
requestCount += 1;
|
requestCount += 1;
|
||||||
const [data, response] = await webRequestor.get<T>(nextUrl);
|
const {data, response} = await webRequestor.get<T>(nextUrl);
|
||||||
if (data) {
|
if (data) {
|
||||||
returnData = [...returnData, data];
|
returnData = [...returnData, data];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
"use client";
|
"use client"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DehydratedState,
|
DehydratedState,
|
||||||
hydrate,
|
hydrate,
|
||||||
QueryClientProvider,
|
QueryClientProvider,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FC, ReactNode, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
import { createQueryClient } from "./queryClient";
|
import { createQueryClient } from "./queryClient";
|
||||||
|
|
||||||
export default function MyQueryClientProvider({
|
export default function MyQueryClientProvider({
|
||||||
|
|||||||
Reference in New Issue
Block a user