Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
max-height: 450px;
}

.btn {
@apply font-normal;
}

.cm-li-btn {
@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;
}
Expand All @@ -16,3 +20,11 @@
.cm-pb-footer {
padding-bottom: calc(3rem + env(safe-area-inset-bottom));
}

.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ export default function CourseRegisterConfirmDialog({
return (
<div className={`modal ${open ? "modal-open" : ""}`}>
<div className="modal-box">
<h3 className="font-bold text-lg">
{mode === "add" ? "変更" : "削除"}の確認
</h3>
<h3 className="text-xl">{mode === "add" ? "変更" : "削除"}の確認</h3>
<p className="py-4">
{mode === "add" ? "次のように変更" : "次の授業を削除"}
します。よろしいですか?
Expand Down
176 changes: 121 additions & 55 deletions web/components/course/components/SelectCourseDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,57 @@
import { DAY_TO_JAPANESE_MAP } from "common/consts";
import type { Course, Day } from "common/types";
import { useEffect, useState } from "react";
import { MdClose, MdSearch } from "react-icons/md";
import courseApi from "~/api/course";
import CourseRegisterConfirmDialog from "./CourseRegisterConfirmDialog";
import TagFilter from "./TagFilter";

const faculties = [
"all",
"zenki",
"law",
"medicine",
"engineering",
"arts",
"science",
"agriculture",
"economics",
"liberal-arts",
"education",
"pharmacy",
] as const;
export type FacultyKey = (typeof faculties)[number];
const facultyRegExMap = new Map<FacultyKey, RegExp>([
["all", /.*/],
["zenki", /^[34].*/],
["law", /^01.*/],
["medicine", /^02.*/],
["engineering", /^FEN.*/],
["arts", /^04.*/],
["science", /^05.*/],
["agriculture", /^06.*/],
["economics", /^07.*/],
["liberal-arts", /^08.*/],
["education", /^09.*/],
["pharmacy", /^10.*/],
]);

const facultyNameMap = new Map<FacultyKey, string>([
["all", "全て"],
["zenki", "前期教養"],
["law", "法"],
["medicine", "医"],
["engineering", "工"],
["arts", "文"],
["science", "理"],
["agriculture", "農"],
["economics", "経済"],
["liberal-arts", "後期教養"],
["education", "教育"],
["pharmacy", "薬"],
]);

// TODO: フィルタのロジックが異様にばらけているのでリファクタしよう・・
export default function SelectCourseDialog({
open,
onClose,
Expand All @@ -21,6 +69,7 @@ export default function SelectCourseDialog({
}) {
const [availableCourses, setAvailableCourses] = useState<Course[]>([]);
const [searchText, setSearchText] = useState("");
const [selectedFaculty, setSelectedFaculty] = useState<FacultyKey>("all");
const [filteredAvailableCourses, setFilteredAvailableCourses] = useState<
Course[]
>([]);
Expand All @@ -45,7 +94,7 @@ export default function SelectCourseDialog({
return (
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
<div
className={`modal ${open ? "modal-open" : ""}`}
className={`modal text-start ${open ? "modal-open" : ""}`}
onClick={(e) => e.stopPropagation()}
>
<form className="modal-backdrop">
Expand All @@ -62,40 +111,42 @@ export default function SelectCourseDialog({
</form>

<div className="modal-box">
<h2 className="font-bold text-lg">
{currentEdit
? `${DAY_TO_JAPANESE_MAP.get(currentEdit.columnName)}曜${
currentEdit.rowIndex + 1
}限の授業を選択`
: "授業を選択"}
</h2>
<button
type="button"
className="btn btn-ghost btn-sm absolute top-3 right-3"
onClick={() => {
setSearchText("");
setFilteredAvailableCourses(availableCourses);
onClose();
}}
>
閉じる
</button>
<div className="flex items-center justify-between">
<h2 className="text-lg">
{currentEdit
? `${DAY_TO_JAPANESE_MAP.get(currentEdit.columnName)}曜${
currentEdit.rowIndex + 1
}限の授業を編集中`
: "編集"}
</h2>
<button
type="button"
className="btn btn-circle btn-sm"
onClick={() => {
setSearchText("");
setFilteredAvailableCourses(availableCourses);
onClose();
}}
>
<MdClose className="text-2xl" />
</button>
</div>
<div className="my-4">
<div>
<h3 className="font-semibold text-sm">現在の授業</h3>
<h3 className="text-gray-600 text-sm">現在の授業</h3>
{currentEdit?.course ? (
<div className="flex items-center justify-between rounded-lg border p-2">
<div className="my-2 flex items-center justify-between rounded-lg">
<div>
<p className="text-base">
{currentEdit?.course?.name ?? "-"}
</p>
<p className="text-gray-500 text-sm">{`${
currentEdit?.course?.teacher ?? "-"
} / ${currentEdit?.course?.id ?? "-"}`}</p>
<p className="text-gray-500 text-sm">{`${currentEdit?.course?.teacher ?? "-"} / ${
currentEdit?.course?.id ?? "-"
}`}</p>
</div>
<button
type="button"
className="btn btn-sm"
className="btn btn-sm font-normal"
onClick={async () => {
if (!currentEdit?.course?.id) return;
setNewCourse(currentEdit.course);
Expand All @@ -109,42 +160,57 @@ export default function SelectCourseDialog({
<p className="text-gray-500">未登録</p>
)}
</div>

<input
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(text),
);
setFilteredAvailableCourses(newFilteredCourses);
}}
/>
<label className="input input-bordered mt-4 flex w-full items-center gap-2">
<MdSearch className="text-gray-500 text-xl" />
<input
type="text"
className="grow"
placeholder="授業名で検索"
value={searchText}
onChange={(e) => {
const text = e.target.value.trim();
setSearchText(text);
const newFilteredCourses = availableCourses.filter((course) =>
course.name.includes(text),
);
setFilteredAvailableCourses(newFilteredCourses);
}}
/>
</label>
<div className="my-4 flex flex-row">
<TagFilter
keyNameMap={facultyNameMap}
selectedTag={selectedFaculty ?? "all"}
onTagChange={(tag) => {
setSelectedFaculty((prev) => (prev === tag ? "all" : tag));
}}
/>
</div>
{filteredAvailableCourses.length === 0 ? (
<p className="mt-2 text-gray-500">
条件に当てはまる授業はありません。
</p>
) : (
<ul className="mt-4 max-h-[300px] overflow-auto">
{filteredAvailableCourses.map((course) => (
<li key={course.id}>
<button
type="button"
className="w-full cursor-pointer rounded-lg border p-2 hover:bg-gray-100"
onClick={() => {
setNewCourse(course);
setConfirmDialogStatus("add");
}}
>
<p>{course.name}</p>
<p className="text-gray-500 text-sm">{`${course.teacher} / ${course.id}`}</p>
</button>
</li>
))}
{filteredAvailableCourses
.filter((course) =>
facultyRegExMap.get(selectedFaculty)?.test(course.id),
)
.map((course) => (
<li key={course.id}>
<button
type="button"
className="w-full cursor-pointer border-b p-2 text-start hover:bg-gray-100"
onClick={() => {
setNewCourse(course);
setConfirmDialogStatus("add");
}}
>
<p>{course.name}</p>
<p className="text-gray-500 text-sm">{`${course.teacher} / ${course.id}`}</p>
</button>
</li>
))}
</ul>
)}
</div>
Expand Down
34 changes: 34 additions & 0 deletions web/components/course/components/TagFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
type Props<T> = {
keyNameMap: Map<T, string>;
selectedTag: T;
onTagChange: (tag: T) => void;
};

export default function TagFilter<T extends string>({
keyNameMap,
selectedTag,
onTagChange,
}: Props<T>) {
const tags = Array.from(keyNameMap.keys());
return (
<div className="scrollbar-hide flex justify-start gap-1 overflow-x-auto whitespace-nowrap">
{tags.map((tag) => (
<div key={tag}>
<input
type="checkbox"
id={tag}
className="peer hidden"
checked={selectedTag === tag}
onChange={() => onTagChange(tag)}
/>
<label
htmlFor={tag}
className="badge badge-lg cursor-pointer bg-gray-200 text-gray-800 transition-colors duration-200 peer-checked:bg-primary peer-checked:text-white"
>
{keyNameMap.get(tag)}
</label>
</div>
))}
</div>
);
}