From 5ef68d3ae97c6f80d5fbaa92ec3fa95e74092210 Mon Sep 17 00:00:00 2001 From: Nasaki Mikami Date: Tue, 21 Apr 2026 17:21:59 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BC=91=E8=AC=9B=E3=83=BB=E8=A3=9C?= =?UTF-8?q?=E8=AC=9B=E3=83=BB=E6=95=99=E5=AE=A4=E5=A4=89=E6=9B=B4=E3=83=9A?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=81=A8=E3=83=90=E3=83=BC=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/lectureInformation/page.tsx | 180 ++++++++++++++++++++++++++ src/components/layout/app-sidebar.tsx | 7 +- 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/app/lectureInformation/page.tsx diff --git a/src/app/lectureInformation/page.tsx b/src/app/lectureInformation/page.tsx new file mode 100644 index 0000000..ff9113b --- /dev/null +++ b/src/app/lectureInformation/page.tsx @@ -0,0 +1,180 @@ +import type { Metadata } from "next"; +import { api } from "@/lib/api"; + +export const metadata: Metadata = { + title: "休講・補講・教室変更", + description: "休講・補講・教室変更情報の一覧", +}; + +type LectureInfoType = "Cancelled" | "Makeup" | "RoomChanged"; + +type LectureInfoItem = { + id: string; + type: LectureInfoType; + date: string; + period: string; + subjectName: string; + detail: string; +}; + +function formatDate(iso: string): { year: string; month: string; day: string } { + const d = new Date(`${iso}T00:00:00`); + return { + year: String(d.getFullYear()), + month: String(d.getMonth() + 1).padStart(2, "0"), + day: String(d.getDate()).padStart(2, "0"), + }; +} + +function periodLabel(period: string): string { + return period.replace("Period", "") + "限"; +} + +function typeLabel(type: LectureInfoType): string { + if (type === "Cancelled") return "休講"; + if (type === "Makeup") return "補講"; + return "教室変更"; +} + +function typeBadgeClass(type: LectureInfoType): string { + if (type === "Cancelled") { + return "border-accent-error/30 text-accent-error bg-accent-error/5"; + } + if (type === "Makeup") { + return "border-accent-info/30 text-accent-info bg-accent-info/5"; + } + return "border-border-primary text-label-secondary bg-background-secondary"; +} + +export default async function Page() { + const [cancelledRes, makeupRes, roomChangeRes] = await Promise.all([ + api.GET("/v1/cancelledClasses"), + api.GET("/v1/makeupClasses"), + api.GET("/v1/roomChanges"), + ]); + + const hasError = !!( + cancelledRes.error || + makeupRes.error || + roomChangeRes.error || + !cancelledRes.data || + !makeupRes.data || + !roomChangeRes.data + ); + + const items: LectureInfoItem[] = hasError + ? [] + : [ + ...cancelledRes.data.cancelledClasses.map((item) => ({ + id: item.id, + type: "Cancelled" as const, + date: item.date, + period: item.period, + subjectName: item.subject.name, + detail: item.comment, + })), + ...makeupRes.data.makeupClasses.map((item) => ({ + id: item.id, + type: "Makeup" as const, + date: item.date, + period: item.period, + subjectName: item.subject.name, + detail: item.comment, + })), + ...roomChangeRes.data.roomChanges.map((item) => ({ + id: item.id, + type: "RoomChanged" as const, + date: item.date, + period: item.period, + subjectName: item.subject.name, + detail: `${item.originalRoom.name} → ${item.newRoom.name}`, + })), + ].sort((a, b) => { + const aTime = new Date(`${a.date}T00:00:00`).getTime(); + const bTime = new Date(`${b.date}T00:00:00`).getTime(); + return bTime - aTime; + }); + + return ( +
+ {/* Hero Header */} +
+

+ Lecture / Information +

+
+

+ 休講・補講・教室変更 +

+ {!hasError && ( +

+ {items.length} 件 +

+ )} +
+
+ + {hasError ? ( +
+

+ 情報の取得に失敗しました。 +

+
+ ) : items.length === 0 ? ( +
+

現在、情報はありません

+
+ ) : ( + + )} +
+ ); +} diff --git a/src/components/layout/app-sidebar.tsx b/src/components/layout/app-sidebar.tsx index a7566a1..c593fc2 100644 --- a/src/components/layout/app-sidebar.tsx +++ b/src/components/layout/app-sidebar.tsx @@ -13,7 +13,7 @@ import { SidebarMenuButton, SidebarMenuItem, } from "@/components/ui/sidebar"; -import { BellIcon, HomeIcon, MonitorIcon } from "lucide-react"; +import { BellIcon, BookOpenIcon, HomeIcon, MonitorIcon } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; @@ -22,6 +22,11 @@ import DottoIcon from "@/app/icon1024.png"; const navItems = [ { title: "ホーム", href: "/", icon: HomeIcon }, { title: "お知らせ", href: "/announcements", icon: BellIcon }, + { + title: "休講・補講・教室変更", + href: "/lectureInformation", + icon: BookOpenIcon, + }, { title: "Mac サポート", href: "/mac", icon: MonitorIcon }, ]; From 938bb784db56361d2bab1d0e2f997917102e5dee Mon Sep 17 00:00:00 2001 From: Nasaki Mikami Date: Mon, 11 May 2026 17:24:01 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BC=91=E8=AC=9B=E8=A3=9C=E8=AC=9B?= =?UTF-8?q?=E6=95=99=E5=AE=A4=E5=A4=89=E6=9B=B4=E3=81=AE=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=82=92=E8=A6=8B=E3=82=84=E3=81=99=E3=81=8F=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/lectureInformation/page.tsx | 194 +++++++++++++++++++--------- 1 file changed, 135 insertions(+), 59 deletions(-) diff --git a/src/app/lectureInformation/page.tsx b/src/app/lectureInformation/page.tsx index ff9113b..bd6ac37 100644 --- a/src/app/lectureInformation/page.tsx +++ b/src/app/lectureInformation/page.tsx @@ -46,6 +46,22 @@ function typeBadgeClass(type: LectureInfoType): string { return "border-border-primary text-label-secondary bg-background-secondary"; } +function sortByDateDesc(a: LectureInfoItem, b: LectureInfoItem): number { + const aTime = new Date(`${a.date}T00:00:00`).getTime(); + const bTime = new Date(`${b.date}T00:00:00`).getTime(); + return bTime - aTime; +} + +function sectionLabel(type: LectureInfoType): string { + return typeLabel(type); +} + +function sectionId(type: LectureInfoType): string { + if (type === "Cancelled") return "cancelled"; + if (type === "Makeup") return "makeup"; + return "room-changed"; +} + export default async function Page() { const [cancelledRes, makeupRes, roomChangeRes] = await Promise.all([ api.GET("/v1/cancelledClasses"), @@ -62,38 +78,53 @@ export default async function Page() { !roomChangeRes.data ); - const items: LectureInfoItem[] = hasError + const cancelledItems: LectureInfoItem[] = hasError ? [] - : [ - ...cancelledRes.data.cancelledClasses.map((item) => ({ + : cancelledRes.data.cancelledClasses + .map((item) => ({ id: item.id, type: "Cancelled" as const, date: item.date, period: item.period, subjectName: item.subject.name, detail: item.comment, - })), - ...makeupRes.data.makeupClasses.map((item) => ({ + })) + .sort(sortByDateDesc); + + const makeupItems: LectureInfoItem[] = hasError + ? [] + : makeupRes.data.makeupClasses + .map((item) => ({ id: item.id, type: "Makeup" as const, date: item.date, period: item.period, subjectName: item.subject.name, detail: item.comment, - })), - ...roomChangeRes.data.roomChanges.map((item) => ({ + })) + .sort(sortByDateDesc); + + const roomChangedItems: LectureInfoItem[] = hasError + ? [] + : roomChangeRes.data.roomChanges + .map((item) => ({ id: item.id, type: "RoomChanged" as const, date: item.date, period: item.period, subjectName: item.subject.name, detail: `${item.originalRoom.name} → ${item.newRoom.name}`, - })), - ].sort((a, b) => { - const aTime = new Date(`${a.date}T00:00:00`).getTime(); - const bTime = new Date(`${b.date}T00:00:00`).getTime(); - return bTime - aTime; - }); + })) + .sort(sortByDateDesc); + + const sections: { type: LectureInfoType; items: LectureInfoItem[] }[] = [ + { type: "Cancelled", items: cancelledItems }, + { type: "Makeup", items: makeupItems }, + { type: "RoomChanged", items: roomChangedItems }, + ]; + + const totalCount = + cancelledItems.length + makeupItems.length + roomChangedItems.length; return (
@@ -108,7 +139,7 @@ export default async function Page() { {!hasError && (

- {items.length} 件 + {totalCount} 件

)}
@@ -120,60 +151,105 @@ export default async function Page() { 情報の取得に失敗しました。

- ) : items.length === 0 ? ( + ) : totalCount === 0 ? (

現在、情報はありません

) : ( - + ) : ( + + )} + + ))} + )} );