diff --git a/server/prisma/migrations/20250401154435_add_course_cascade/migration.sql b/server/prisma/migrations/20250401154435_add_course_cascade/migration.sql new file mode 100644 index 00000000..0de34369 --- /dev/null +++ b/server/prisma/migrations/20250401154435_add_course_cascade/migration.sql @@ -0,0 +1,14 @@ +-- DropForeignKey +ALTER TABLE "Enrollment" DROP CONSTRAINT "Enrollment_courseId_fkey"; + +-- DropForeignKey +ALTER TABLE "Slot" DROP CONSTRAINT "Slot_courseId_fkey"; + +-- AlterTable +ALTER TABLE "Message" ALTER COLUMN "isPicture" DROP DEFAULT; + +-- AddForeignKey +ALTER TABLE "Slot" ADD CONSTRAINT "Slot_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 8f6bb750..4ebc225a 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -92,7 +92,7 @@ model Course { // コマ。1つの講義に対して複数存在しうる。 model Slot { id Int @id @default(autoincrement()) - course Course @relation(fields: [courseId], references: [id]) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) courseId String period Int // 1-6. 0 の場合はなし (集中など) day Day // 曜日。other の場合は集中など @@ -104,7 +104,7 @@ model Enrollment { id Int @id @default(autoincrement()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int - course Course @relation(fields: [courseId], references: [id]) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) courseId String @@unique([userId, courseId]) diff --git a/server/src/.gitignore b/server/src/.gitignore index 3bfac405..f839c428 100644 --- a/server/src/.gitignore +++ b/server/src/.gitignore @@ -1,2 +1,3 @@ /common /seeds/data.json +/seeds/json diff --git a/server/src/seeds/insertCoursesFromJSONByCreateMany.ts b/server/src/seeds/insertCoursesFromJSONByCreateMany.ts new file mode 100644 index 00000000..98cf3451 --- /dev/null +++ b/server/src/seeds/insertCoursesFromJSONByCreateMany.ts @@ -0,0 +1,83 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; + +import { prisma } from "../database/client"; + +// シ楽バス形式のデータを読み込む。 +const FILE_PATH = path.join(__dirname, "data.json"); + +async function main() { + const jsonData = JSON.parse(fs.readFileSync(FILE_PATH, "utf-8")); + + const coursesData = jsonData.map( + (course: { + code: string; + titleJp: string; + lecturerJp: string; + }) => { + const { code, titleJp, lecturerJp } = course; + return { + id: code, + name: titleJp, + teacher: lecturerJp, + }; + }, + ); + + await prisma.course.createMany({ + data: coursesData, + }); + + const slotsData: { + day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun" | "other"; + period: number; + courseId: string; + }[] = []; + + for (const courseData of jsonData) { + const { code, periods } = courseData; + + for (const period of periods) { + const [dayJp, periodStr] = period.split(""); + const day = + dayJp === "月" + ? "mon" + : dayJp === "火" + ? "tue" + : dayJp === "水" + ? "wed" + : dayJp === "木" + ? "thu" + : dayJp === "金" + ? "fri" + : dayJp === "土" + ? "sat" + : dayJp === "日" + ? "sun" + : "other"; + + slotsData.push({ + day, + period: Number.parseInt(periodStr) || 0, + courseId: code, + }); + } + } + + await prisma.slot.createMany({ + data: slotsData, + skipDuplicates: true, + }); + + console.log("Data inserted successfully!"); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/web/components/course/components/SelectCourseDialog.tsx b/web/components/course/components/SelectCourseDialog.tsx index eafd7fbc..52f5dafa 100644 --- a/web/components/course/components/SelectCourseDialog.tsx +++ b/web/components/course/components/SelectCourseDialog.tsx @@ -20,6 +20,7 @@ export default function SelectCourseDialog({ handleCoursesUpdate: (courses: Course[]) => void; }) { const [availableCourses, setAvailableCourses] = useState([]); + const [searchText, setSearchText] = useState(""); const [filteredAvailableCourses, setFilteredAvailableCourses] = useState< Course[] >([]); @@ -48,7 +49,14 @@ export default function SelectCourseDialog({ onClick={(e) => e.stopPropagation()} >
-
@@ -64,7 +72,11 @@ export default function SelectCourseDialog({ @@ -102,9 +114,12 @@ export default function SelectCourseDialog({ type="text" placeholder="授業名で検索" className="input input-bordered mt-4 w-full" + value={searchText} onChange={(e) => { + const text = e.target.value.trim(); + setSearchText(text); const newFilteredCourses = availableCourses.filter((course) => - course.name.includes(e.target.value.trim()), + course.name.includes(text), ); setFilteredAvailableCourses(newFilteredCourses); }} @@ -140,6 +155,8 @@ export default function SelectCourseDialog({ onClose={() => { setConfirmDialogStatus("closed"); setNewCourse(null); + setSearchText(""); + setFilteredAvailableCourses(availableCourses); onClose(); }} onCancel={() => {