Skip to content

Commit 9f8defb

Browse files
authored
授業を学部ごとフィルタ (#708)
1 parent 2bcb544 commit 9f8defb

File tree

4 files changed

+168
-58
lines changed

4 files changed

+168
-58
lines changed

web/app/globals.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
max-height: 450px;
99
}
1010

11+
.btn {
12+
@apply font-normal;
13+
}
14+
1115
.cm-li-btn {
1216
@apply no-animation h-auto w-full justify-start rounded-none border-none bg-white px-6 py-4 text-left font-normal text-base shadow-none hover:bg-zinc-100 focus:bg-zinc-300;
1317
}
@@ -16,3 +20,11 @@
1620
.cm-pb-footer {
1721
padding-bottom: calc(3rem + env(safe-area-inset-bottom));
1822
}
23+
24+
.scrollbar-hide::-webkit-scrollbar {
25+
display: none;
26+
}
27+
.scrollbar-hide {
28+
-ms-overflow-style: none;
29+
scrollbar-width: none;
30+
}

web/components/course/components/CourseRegisterConfirmDialog.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ export default function CourseRegisterConfirmDialog({
4646
return (
4747
<div className={`modal ${open ? "modal-open" : ""}`}>
4848
<div className="modal-box">
49-
<h3 className="font-bold text-lg">
50-
{mode === "add" ? "変更" : "削除"}の確認
51-
</h3>
49+
<h3 className="text-xl">{mode === "add" ? "変更" : "削除"}の確認</h3>
5250
<p className="py-4">
5351
{mode === "add" ? "次のように変更" : "次の授業を削除"}
5452
します。よろしいですか?

web/components/course/components/SelectCourseDialog.tsx

Lines changed: 121 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,57 @@
11
import { DAY_TO_JAPANESE_MAP } from "common/consts";
22
import type { Course, Day } from "common/types";
33
import { useEffect, useState } from "react";
4+
import { MdClose, MdSearch } from "react-icons/md";
45
import courseApi from "~/api/course";
56
import CourseRegisterConfirmDialog from "./CourseRegisterConfirmDialog";
7+
import TagFilter from "./TagFilter";
68

9+
const faculties = [
10+
"all",
11+
"zenki",
12+
"law",
13+
"medicine",
14+
"engineering",
15+
"arts",
16+
"science",
17+
"agriculture",
18+
"economics",
19+
"liberal-arts",
20+
"education",
21+
"pharmacy",
22+
] as const;
23+
export type FacultyKey = (typeof faculties)[number];
24+
const facultyRegExMap = new Map<FacultyKey, RegExp>([
25+
["all", /.*/],
26+
["zenki", /^[34].*/],
27+
["law", /^01.*/],
28+
["medicine", /^02.*/],
29+
["engineering", /^FEN.*/],
30+
["arts", /^04.*/],
31+
["science", /^05.*/],
32+
["agriculture", /^06.*/],
33+
["economics", /^07.*/],
34+
["liberal-arts", /^08.*/],
35+
["education", /^09.*/],
36+
["pharmacy", /^10.*/],
37+
]);
38+
39+
const facultyNameMap = new Map<FacultyKey, string>([
40+
["all", "全て"],
41+
["zenki", "前期教養"],
42+
["law", "法"],
43+
["medicine", "医"],
44+
["engineering", "工"],
45+
["arts", "文"],
46+
["science", "理"],
47+
["agriculture", "農"],
48+
["economics", "経済"],
49+
["liberal-arts", "後期教養"],
50+
["education", "教育"],
51+
["pharmacy", "薬"],
52+
]);
53+
54+
// TODO: フィルタのロジックが異様にばらけているのでリファクタしよう・・
755
export default function SelectCourseDialog({
856
open,
957
onClose,
@@ -21,6 +69,7 @@ export default function SelectCourseDialog({
2169
}) {
2270
const [availableCourses, setAvailableCourses] = useState<Course[]>([]);
2371
const [searchText, setSearchText] = useState("");
72+
const [selectedFaculty, setSelectedFaculty] = useState<FacultyKey>("all");
2473
const [filteredAvailableCourses, setFilteredAvailableCourses] = useState<
2574
Course[]
2675
>([]);
@@ -45,7 +94,7 @@ export default function SelectCourseDialog({
4594
return (
4695
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
4796
<div
48-
className={`modal ${open ? "modal-open" : ""}`}
97+
className={`modal text-start ${open ? "modal-open" : ""}`}
4998
onClick={(e) => e.stopPropagation()}
5099
>
51100
<form className="modal-backdrop">
@@ -62,40 +111,42 @@ export default function SelectCourseDialog({
62111
</form>
63112

64113
<div className="modal-box">
65-
<h2 className="font-bold text-lg">
66-
{currentEdit
67-
? `${DAY_TO_JAPANESE_MAP.get(currentEdit.columnName)}${
68-
currentEdit.rowIndex + 1
69-
}限の授業を選択`
70-
: "授業を選択"}
71-
</h2>
72-
<button
73-
type="button"
74-
className="btn btn-ghost btn-sm absolute top-3 right-3"
75-
onClick={() => {
76-
setSearchText("");
77-
setFilteredAvailableCourses(availableCourses);
78-
onClose();
79-
}}
80-
>
81-
閉じる
82-
</button>
114+
<div className="flex items-center justify-between">
115+
<h2 className="text-lg">
116+
{currentEdit
117+
? `${DAY_TO_JAPANESE_MAP.get(currentEdit.columnName)}${
118+
currentEdit.rowIndex + 1
119+
}限の授業を編集中`
120+
: "編集"}
121+
</h2>
122+
<button
123+
type="button"
124+
className="btn btn-circle btn-sm"
125+
onClick={() => {
126+
setSearchText("");
127+
setFilteredAvailableCourses(availableCourses);
128+
onClose();
129+
}}
130+
>
131+
<MdClose className="text-2xl" />
132+
</button>
133+
</div>
83134
<div className="my-4">
84135
<div>
85-
<h3 className="font-semibold text-sm">現在の授業</h3>
136+
<h3 className="text-gray-600 text-sm">現在の授業</h3>
86137
{currentEdit?.course ? (
87-
<div className="flex items-center justify-between rounded-lg border p-2">
138+
<div className="my-2 flex items-center justify-between rounded-lg">
88139
<div>
89140
<p className="text-base">
90141
{currentEdit?.course?.name ?? "-"}
91142
</p>
92-
<p className="text-gray-500 text-sm">{`${
93-
currentEdit?.course?.teacher ?? "-"
94-
} / ${currentEdit?.course?.id ?? "-"}`}</p>
143+
<p className="text-gray-500 text-sm">{`${currentEdit?.course?.teacher ?? "-"} / ${
144+
currentEdit?.course?.id ?? "-"
145+
}`}</p>
95146
</div>
96147
<button
97148
type="button"
98-
className="btn btn-sm"
149+
className="btn btn-sm font-normal"
99150
onClick={async () => {
100151
if (!currentEdit?.course?.id) return;
101152
setNewCourse(currentEdit.course);
@@ -109,42 +160,57 @@ export default function SelectCourseDialog({
109160
<p className="text-gray-500">未登録</p>
110161
)}
111162
</div>
112-
113-
<input
114-
type="text"
115-
placeholder="授業名で検索"
116-
className="input input-bordered mt-4 w-full"
117-
value={searchText}
118-
onChange={(e) => {
119-
const text = e.target.value.trim();
120-
setSearchText(text);
121-
const newFilteredCourses = availableCourses.filter((course) =>
122-
course.name.includes(text),
123-
);
124-
setFilteredAvailableCourses(newFilteredCourses);
125-
}}
126-
/>
163+
<label className="input input-bordered mt-4 flex w-full items-center gap-2">
164+
<MdSearch className="text-gray-500 text-xl" />
165+
<input
166+
type="text"
167+
className="grow"
168+
placeholder="授業名で検索"
169+
value={searchText}
170+
onChange={(e) => {
171+
const text = e.target.value.trim();
172+
setSearchText(text);
173+
const newFilteredCourses = availableCourses.filter((course) =>
174+
course.name.includes(text),
175+
);
176+
setFilteredAvailableCourses(newFilteredCourses);
177+
}}
178+
/>
179+
</label>
180+
<div className="my-4 flex flex-row">
181+
<TagFilter
182+
keyNameMap={facultyNameMap}
183+
selectedTag={selectedFaculty ?? "all"}
184+
onTagChange={(tag) => {
185+
setSelectedFaculty((prev) => (prev === tag ? "all" : tag));
186+
}}
187+
/>
188+
</div>
127189
{filteredAvailableCourses.length === 0 ? (
128190
<p className="mt-2 text-gray-500">
129191
条件に当てはまる授業はありません。
130192
</p>
131193
) : (
132194
<ul className="mt-4 max-h-[300px] overflow-auto">
133-
{filteredAvailableCourses.map((course) => (
134-
<li key={course.id}>
135-
<button
136-
type="button"
137-
className="w-full cursor-pointer rounded-lg border p-2 hover:bg-gray-100"
138-
onClick={() => {
139-
setNewCourse(course);
140-
setConfirmDialogStatus("add");
141-
}}
142-
>
143-
<p>{course.name}</p>
144-
<p className="text-gray-500 text-sm">{`${course.teacher} / ${course.id}`}</p>
145-
</button>
146-
</li>
147-
))}
195+
{filteredAvailableCourses
196+
.filter((course) =>
197+
facultyRegExMap.get(selectedFaculty)?.test(course.id),
198+
)
199+
.map((course) => (
200+
<li key={course.id}>
201+
<button
202+
type="button"
203+
className="w-full cursor-pointer border-b p-2 text-start hover:bg-gray-100"
204+
onClick={() => {
205+
setNewCourse(course);
206+
setConfirmDialogStatus("add");
207+
}}
208+
>
209+
<p>{course.name}</p>
210+
<p className="text-gray-500 text-sm">{`${course.teacher} / ${course.id}`}</p>
211+
</button>
212+
</li>
213+
))}
148214
</ul>
149215
)}
150216
</div>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
type Props<T> = {
2+
keyNameMap: Map<T, string>;
3+
selectedTag: T;
4+
onTagChange: (tag: T) => void;
5+
};
6+
7+
export default function TagFilter<T extends string>({
8+
keyNameMap,
9+
selectedTag,
10+
onTagChange,
11+
}: Props<T>) {
12+
const tags = Array.from(keyNameMap.keys());
13+
return (
14+
<div className="scrollbar-hide flex justify-start gap-1 overflow-x-auto whitespace-nowrap">
15+
{tags.map((tag) => (
16+
<div key={tag}>
17+
<input
18+
type="checkbox"
19+
id={tag}
20+
className="peer hidden"
21+
checked={selectedTag === tag}
22+
onChange={() => onTagChange(tag)}
23+
/>
24+
<label
25+
htmlFor={tag}
26+
className="badge badge-lg cursor-pointer bg-gray-200 text-gray-800 transition-colors duration-200 peer-checked:bg-primary peer-checked:text-white"
27+
>
28+
{keyNameMap.get(tag)}
29+
</label>
30+
</div>
31+
))}
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)