diff --git a/src/app/components/FilterUI/FilterComponents/Evaluation.tsx b/src/app/components/FilterUI/FilterComponents/Evaluation.tsx new file mode 100644 index 0000000..f5aad4c --- /dev/null +++ b/src/app/components/FilterUI/FilterComponents/Evaluation.tsx @@ -0,0 +1,100 @@ +/* + * 評価方法フィルターのコンポーネント + */ + +"use client"; +import React, { ReactNode } from "react"; +import { Evaluation } from "@/app/type"; +import Checkbox from "../UI/Checkbox"; + +const evaluations: Evaluation[] = ["試験", "レポート", "出席", "平常"]; + +/** + * 評価方法フィルターのプロパティ + */ +interface EvaluationProp { + evaluation_included?: Evaluation[]; + evaluation_excluded?: Evaluation[]; + setEvaluation: ( + evaluation_included: Evaluation[], + evaluation_excluded: Evaluation[], + ) => void; +} + +/** + * 評価方法フィルターのコンポーネント + * @param prop 評価方法フィルターのプロパティ + * @returns 評価方法フィルターのコンポーネント + */ +export const EvaluationFilter: React.FC = ( + prop: EvaluationProp, +) => { + const slots: ReactNode[] = []; + + slots.push(
); + slots.push(
含む
); + slots.push(
除外
); + + evaluations.map((ev) => { + slots.push(
{ev.substring(0, 2)}
); + slots.push( + , + ); + slots.push( + , + ); + }); + + return
{slots}
; +}; + +/** + * グリッド上に並べるチェックボックスのコンポーネント + * @param param0 プロパティ + * @param param0.isInclude このチェックボックスが、含めたい評価方法を示しているか否か + * @param param0.ev 評価方法 + * @param param0.prop 評価方法のフィルターコンポーネントのプロパティ + * @returns チェックボックスコンポーネント + */ +const CheckboxInGrid: React.FC<{ + isInclude: boolean; + ev: Evaluation; + prop: EvaluationProp; +}> = ({ isInclude, ev, prop }) => { + let evaluation_included = prop.evaluation_included ?? []; + let evaluation_excluded = prop.evaluation_excluded ?? []; + let myEvaluation = isInclude ? evaluation_included : evaluation_excluded; + let otherEvaluation = isInclude ? evaluation_excluded : evaluation_included; + + // クリックされたときの挙動 + const onClick = (ev: Evaluation) => { + if (myEvaluation.includes(ev)) { + // もともとチェックされていたボックスをクリックしたら、外す + myEvaluation.splice(myEvaluation.indexOf(ev), 1); + } else { + // もともとチェックされていなかったボックスをクリックしたら + const index = otherEvaluation.indexOf(ev); // 相方のチェックがされているかを確認 + if (index >= 0) otherEvaluation.splice(index, 1); // 相方のチェックを外す + myEvaluation.push(ev); // 自分のボックスにチェックを入れる + } + + prop.setEvaluation(evaluation_included, evaluation_excluded); + }; + + return ( + onClick(ev)} + /> + ); +}; diff --git a/src/app/components/FilterUI/FilterComponents/Semester.tsx b/src/app/components/FilterUI/FilterComponents/Semester.tsx new file mode 100644 index 0000000..ac39596 --- /dev/null +++ b/src/app/components/FilterUI/FilterComponents/Semester.tsx @@ -0,0 +1,79 @@ +/* + * セメスターフィルターのコンポーネント + */ + +"use client"; +import React from "react"; +import { Semester } from "@/app/type"; +import { FlagButton } from "../UI/FlagButton"; + +/** + * セメスターフィルターのプロパティ + */ +interface SemesterProp { + selectedSemesters?: Semester[]; // 選択されているセメスター + setSelectedSemesters: (semesters: Semester[]) => void; +} + +/** + * セメスターフィルターのコンポーネント + * @param prop セメスターフィルターのプロパティ + * @returns コンポーネント + */ +export const SemestersCheckbox: React.FC = ( + prop: SemesterProp, +) => { + const selectedSemesters = prop.selectedSemesters ?? []; + + // ボタンがクリックされたときの関数 + const onClick = (semester: Semester) => { + if (selectedSemesters.includes(semester)) { + prop.setSelectedSemesters( + selectedSemesters.filter((s) => s !== semester), + ); + } else { + prop.setSelectedSemesters([...selectedSemesters, semester]); + } + }; + + return ( +
+ onClick("S1")} + className="aspect-square" + /> + onClick("S2")} + className="aspect-square" + /> + onClick("A1")} + className="aspect-square" + /> + onClick("A2")} + className="aspect-square" + /> + onClick("S")} + className="col-span-2" + /> + onClick("A")} + className="col-span-2" + /> +
+ ); +}; diff --git a/src/app/components/FilterUI/FilterUI.tsx b/src/app/components/FilterUI/FilterUI.tsx new file mode 100644 index 0000000..b5c04b9 --- /dev/null +++ b/src/app/components/FilterUI/FilterUI.tsx @@ -0,0 +1,58 @@ +/* + * 全てのフィルターを表示するコンポーネント + */ + +"use client"; +import { useState } from "react"; +import { ClassType, Evaluation, Semester } from "@/app/type"; +import { SemestersCheckbox } from "./FilterComponents/Semester"; +import { FilterCard } from "./UI/FilterCard"; +import { EvaluationFilter } from "./FilterComponents/Evaluation"; + +/** + * フィルタの型定義 + */ +type Filter = { + isFreewordForSyllabusDetail?: boolean; // フリーワード検索 + semesters?: Semester[]; // セメスター + evaluation_included?: Evaluation[]; // 含めたい評価方法 + evaluation_excluded?: Evaluation[]; // 除外したい評価方法 + classTypes?: ClassType[]; // 種別 + showRegistered?: boolean; // 履修登録済みの授業を表示する + showNotRegistered?: boolean; // 未履修の授業を表示する +}; + +/** + * フィルタUI + * @returns フィルタUI + */ +export const FilterUI: React.FC = () => { + // 現在のフィルター + const [filter, setFilter] = useState({}); + + return ( +
+ + + setFilter({ ...filter, semesters }) + } + /> + + + + + setFilter({ ...filter, evaluation_included, evaluation_excluded }) + } + /> + +
+ ); +}; diff --git a/src/app/components/FilterUI/Sample/page.tsx b/src/app/components/FilterUI/Sample/page.tsx new file mode 100644 index 0000000..05e6069 --- /dev/null +++ b/src/app/components/FilterUI/Sample/page.tsx @@ -0,0 +1,11 @@ +/* + * FilterUIのサンプルページ + */ + +import { FilterUI } from "../FilterUI"; + +const FilterUISample: React.FC = () => { + return ; +}; + +export default FilterUISample; diff --git a/src/app/components/FilterUI/UI/Checkbox.tsx b/src/app/components/FilterUI/UI/Checkbox.tsx new file mode 100644 index 0000000..d87e915 --- /dev/null +++ b/src/app/components/FilterUI/UI/Checkbox.tsx @@ -0,0 +1,33 @@ +"use client"; +import React from "react"; + +type CheckboxProps = { + checked: boolean; // チェック状態 + onChange: (checked: boolean) => void; // チェック状態が変化したときのコールバック + className?: string; +}; + +/** + * チェックボックスコンポーネント + * @param param0 チェックボックスのプロパティ + * @param param0.checked チェック状態 + * @param param0.onChange チェック状態が変化したときのコールバック + * @param param0.className tailwindcss + * @returns チェックボックスコンポーネント + */ +const Checkbox: React.FC = ({ + checked, + onChange, + className, +}) => { + return ( + onChange(!checked)} + className={"accent-primary w-6 h-6 " + className} + /> + ); +}; + +export default Checkbox; diff --git a/src/app/components/FilterUI/UI/FilterCard.tsx b/src/app/components/FilterUI/UI/FilterCard.tsx new file mode 100644 index 0000000..8de4a7e --- /dev/null +++ b/src/app/components/FilterUI/UI/FilterCard.tsx @@ -0,0 +1,32 @@ +/* + * フィルターの内容とタイトルをセットにするコンポーネント + */ + +import React, { ReactNode } from "react"; + +/** + * フィルターカードのプロパティ + */ +export interface FilterCardProps { + /** タイトル */ + title: string; + + /** フィルター */ + children: ReactNode; +} + +/** + * フィルターの内容とタイトルをセットにするコンポーネント + * @param param0 プロパティ + * @param param0.title フィルターのタイトル + * @param param0.children フィルターコンポーネント + * @returns コンポーネント + */ +export const FilterCard: React.FC = ({ title, children }) => { + return ( +
+
{title}
+ {children} +
+ ); +}; diff --git a/src/app/components/FilterUI/UI/FlagButton.tsx b/src/app/components/FilterUI/UI/FlagButton.tsx new file mode 100644 index 0000000..615f9bd --- /dev/null +++ b/src/app/components/FilterUI/UI/FlagButton.tsx @@ -0,0 +1,53 @@ +/* + * 選択状態を示すボタン。 + * (ボタンの背景色を変更することで選択状態を示す。) + * セメスター選択ボタンや、種別選択ボタンで使用。 + */ + +import React from "react"; + +/** + * 選択状態を示すボタンのプロパティ + */ +interface FlagButtonProp { + /** + * ボタンのラベル + */ + label: string; + /** + * 選択状態かどうか + */ + isSelected: boolean; + /** + * ボタンがクリックされたときの処理 + */ + onClick: () => void; + + /** + * ボタンのスタイル + */ + className?: string; +} + +/** + * 選択状態を示すボタンのコンポーネント + * @param prop 選択状態を示すボタンのプロパティ + * @returns 選択状態を示すボタンのコンポーネント + */ +export const FlagButton: React.FC = (prop: FlagButtonProp) => { + const className = prop.className ?? ""; + return ( + + ); +}; diff --git a/src/app/type.ts b/src/app/type.ts index 9345bcc..f1276dd 100644 --- a/src/app/type.ts +++ b/src/app/type.ts @@ -59,3 +59,29 @@ export const dayMapping: { [key in Day]: string } = { fri: "金", sat: "土", }; + +/** + * セメスターを表現する型 + */ +export type Semester = "S" | "S1" | "S2" | "A" | "A1" | "A2"; + +/** + * 評価方法を表現する型 + */ +export type Evaluation = "試験" | "レポート" | "出席" | "平常"; + +/** + * セメスターを表現する型 + */ +export type ClassType = + | "基礎" + | "要求" + | "主題" + | "展開" + | "L" + | "A" + | "B" + | "C" + | "D" + | "E" + | "F";