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
100 changes: 100 additions & 0 deletions src/app/components/FilterUI/FilterComponents/Evaluation.tsx
Original file line number Diff line number Diff line change
@@ -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<EvaluationProp> = (
prop: EvaluationProp,
) => {
const slots: ReactNode[] = [];

slots.push(<div key={"void"} />);
slots.push(<div key={"label_in"}>含む</div>);
slots.push(<div key={"label_ex"}>除外</div>);

evaluations.map((ev) => {
slots.push(<div key={ev + "header"}>{ev.substring(0, 2)}</div>);
slots.push(
<CheckboxInGrid
isInclude={true}
ev={ev}
key={ev + "included"}
prop={prop}
/>,
);
slots.push(
<CheckboxInGrid
isInclude={false}
ev={ev}
key={ev + "excluded"}
prop={prop}
/>,
);
});

return <div className="grid grid-rows-3 grid-flow-col gap-2">{slots}</div>;
};

/**
* グリッド上に並べるチェックボックスのコンポーネント
* @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 (
<Checkbox
checked={myEvaluation.includes(ev)}
onChange={(_) => onClick(ev)}
/>
);
};
79 changes: 79 additions & 0 deletions src/app/components/FilterUI/FilterComponents/Semester.tsx
Original file line number Diff line number Diff line change
@@ -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<SemesterProp> = (
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 (
<div className="grid grid-cols-4 gap-2">
<FlagButton
label={"S1"}
isSelected={selectedSemesters.includes("S1")}
onClick={() => onClick("S1")}
className="aspect-square"
/>
<FlagButton
label={"S2"}
isSelected={selectedSemesters.includes("S2")}
onClick={() => onClick("S2")}
className="aspect-square"
/>
<FlagButton
label={"A1"}
isSelected={selectedSemesters.includes("A1")}
onClick={() => onClick("A1")}
className="aspect-square"
/>
<FlagButton
label={"A2"}
isSelected={selectedSemesters.includes("A2")}
onClick={() => onClick("A2")}
className="aspect-square"
/>
<FlagButton
label={"S"}
isSelected={selectedSemesters.includes("S")}
onClick={() => onClick("S")}
className="col-span-2"
/>
<FlagButton
label={"A"}
isSelected={selectedSemesters.includes("A")}
onClick={() => onClick("A")}
className="col-span-2"
/>
</div>
);
};
58 changes: 58 additions & 0 deletions src/app/components/FilterUI/FilterUI.tsx
Original file line number Diff line number Diff line change
@@ -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<Filter>({});

return (
<div className="flex gap-8 flex-wrap">
<FilterCard title={"セメスター"}>
<SemestersCheckbox
selectedSemesters={filter.semesters}
setSelectedSemesters={(semesters: Semester[]) =>
setFilter({ ...filter, semesters })
}
/>
</FilterCard>

<FilterCard title={"評価方法"}>
<EvaluationFilter
evaluation_included={filter.evaluation_included}
evaluation_excluded={filter.evaluation_excluded}
setEvaluation={(
evaluation_included: Evaluation[],
evaluation_excluded,
) =>
setFilter({ ...filter, evaluation_included, evaluation_excluded })
}
/>
</FilterCard>
</div>
);
};
11 changes: 11 additions & 0 deletions src/app/components/FilterUI/Sample/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* FilterUIのサンプルページ
*/

import { FilterUI } from "../FilterUI";

const FilterUISample: React.FC = () => {
return <FilterUI />;
};

export default FilterUISample;
33 changes: 33 additions & 0 deletions src/app/components/FilterUI/UI/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -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<CheckboxProps> = ({
checked,
onChange,
className,
}) => {
return (
<input
type="checkbox"
checked={checked}
onChange={() => onChange(!checked)}
className={"accent-primary w-6 h-6 " + className}
/>
);
};

export default Checkbox;
32 changes: 32 additions & 0 deletions src/app/components/FilterUI/UI/FilterCard.tsx
Original file line number Diff line number Diff line change
@@ -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<FilterCardProps> = ({ title, children }) => {
return (
<div className="gap-8 flex-wrap">
<div className="text-2xl m-4">{title}</div>
{children}
</div>
);
};
53 changes: 53 additions & 0 deletions src/app/components/FilterUI/UI/FlagButton.tsx
Original file line number Diff line number Diff line change
@@ -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<FlagButtonProp> = (prop: FlagButtonProp) => {
const className = prop.className ?? "";
return (
<button
className={
`${
prop.isSelected ? "bg-primary/30" : "bg-surface"
} text-text-default px-4 py-2 rounded-full outline-2 outline-primary/30 outline` +
" " +
className
}
onClick={prop.onClick}
>
{prop.label}
</button>
);
};
Loading
Loading