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 ? (
+
+ ) : (
+
+ {items.map((item) => {
+ const { year, month, day } = formatDate(item.date);
+
+ return (
+ -
+
+ {/* Date block */}
+
+
+ {year}
+
+
+ {month}
+ /
+ {day}
+
+
+
+ {/* Divider */}
+
+
+ {/* Content */}
+
+
+
+ {typeLabel(item.type)}
+
+
+ {periodLabel(item.period)}
+
+
+
+ {item.subjectName}
+
+
+ {item.detail}
+
+
+
+
+ );
+ })}
+
+ )}
+
+ );
+}
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 ? (
) : (
-
- {items.map((item) => {
- const { year, month, day } = formatDate(item.date);
-
- return (
- -
-
- {/* Date block */}
-
-
- {year}
+
+
- {/* Divider */}
-
-
- {/* Content */}
-
-
-
- {typeLabel(item.type)}
-
-
- {periodLabel(item.period)}
-
-
-
- {item.subjectName}
-
-
- {item.detail}
-
-
+ {sections.map((section) => (
+
+
+
+ {sectionLabel(section.type)}
+
+
+ {section.items.length}件
+
+
+
+ {section.items.length === 0 ? (
+
-
- );
- })}
-
+ ) : (
+
+ {section.items.map((item) => {
+ const { year, month, day } = formatDate(item.date);
+
+ return (
+ -
+
+
+
+ {year}
+
+
+ {month}
+ /
+ {day}
+
+
+
+
+
+
+
+
+ {typeLabel(item.type)}
+
+
+ {periodLabel(item.period)}
+
+
+
+ {item.subjectName}
+
+
+ {item.detail}
+
+
+
+
+ );
+ })}
+
+ )}
+
+ ))}
+
)}
);