評価値コンポーネントの作成
画像ファイルの準備
- 下記ファイルをダウンロードする。
- recsyslab / recsys_full / src / frontend / public / img /
star_00.pngstar_01.pngstar_10.pngstar_11.png
- recsyslab / recsys_full / src / frontend / public / img /
frontend$ mv ~/Downloads/star*.png public/img/
frontend$ ls public/img/
dummy_poster.png star_00.png star_01.png star_10.png star_11.png
データ型
src/frontend/src/types/data.d.ts
...(略)...
/**
* 映画モデル
*/
export type Movie = {
movie_id: number;
title: string;
year: number;
genres: Genre[];
imdb_id: number;
tmdb_id: number;
rating: Rating | null; // <- 追加
};
// ↓追加
/**
* 評価モデル
*/
export type Rating = {
id: string;
user_id: string;
movie_id: number;
rating: number;
rated_at: string;
};
// ↑追加
定数の定義
src/frontend/src/constants/styles.ts
/**
* スタイル関連の定数
*/
export const STYLES = {
...(略)...
// 映画(詳細)カード関連
...(略)...
MOVIE_DETAIL_TAG_GENRE: `rounded bg-gray-100 px-3 py-1 text-sm`,
MOVIE_DETAIL_STAR_WIDTH: 48, // <- 追加
// ↓追加
// 評価値関連
STAR_BUTTON: `cursor-pointer rounded-lg bg-white hover:bg-indigo-100`,
STAR_IMAGE: `opacity-75 hover:opacity-100 active:scale-125 active:opacity-100`,
STAR_RATING: `flex`,
// ↑追加
} as const;
コンポーネント
スターコンポーネント
src/frontend/src/app/components/ui/rating/Star.tsx
"use client";
import { useCallback } from "react";
import Image from "next/image";
import { STYLES } from "@/constants";
interface Props {
index: number;
width: number;
rating: number;
setRating: Function;
}
const Star = (props: Props) => {
const handleRatingClick = useCallback(() => {
const rating = (props.index + 1) / 2;
props.setRating(rating);
}, [props.setRating]);
return (
<>
<button
className={`${STYLES.STAR_BUTTON}`}
onClick={() => handleRatingClick()}
>
<Image
className={`${STYLES.STAR_IMAGE}`}
src={`/img/star_${props.index % 2}${props.index < props.rating * 2 ? 1 : 0}.png`}
alt=""
width={props.width / 2}
height={props.width}
/>
</button>
</>
);
};
export default Star;
評価値コンポーネント
src/frontend/src/app/components/ui/rating/StarRating.tsx
"use client";
import { useState } from "react";
import { STYLES } from "@/constants";
import Star from "./Star";
interface Props {
starWidth: number;
rating: number;
}
const StarRating = (props: Props) => {
const [rating, setRating] = useState<number>(props.rating);
return (
<>
<div className={`${STYLES.STAR_RATING}`}>
{(function () {
const stars = [];
for (let i = 0; i < 10; i++) {
stars.push(
<Star
key={i}
index={i}
width={props.starWidth}
rating={rating}
setRating={setRating}
/>,
);
}
return <div>{stars}</div>;
})()}
</div>
</>
);
};
export default StarRating;
映画詳細カードコンポーネント
src/frontend/src/app/components/ui/card/CardMovieDetail.tsx
...(略)...
import { Movie, User } from "@/types/data"; // <- Userを追加
import { MESSAGES, STYLES, ERROR_MESSAGES } from "@/constants";
import getMovie from "@/api/movies/getMovie";
import getMyAccount from "@/api/auth/getMyAccount"; // <- 追加
import Loading from "../Loading";
import StarRating from "../rating/StarRating"; // <- 追加
...(略)...
/**
* 映画(詳細)カードコンポーネント
*/
const CardMovieDetail = (props: Props) => {
const [loading, setLoading] = useState(true);
const [loadingUser, setLoadingUser] = useState(true); // <- 追加
const [user, setUser] = useState<User | null>(null); // <- 追加
const [movie, setMovie] = useState<Movie | null>(null);
...(略)...
useEffect(() => {
const load = async () => {
// ↓追加
try {
const user_ = await getMyAccount();
setUser(user_);
} catch (e) {
setUser(null);
} finally {
setLoadingUser(false);
}
// ↑追加
try {
const { movie: movie_ } = await getMovie(props.movieId);
setMovie(movie_);
} catch (e) {
console.error(ERROR_MESSAGES.MOVIE_GET_FAILED, e);
setMovie(null);
} finally {
setLoading(false);
}
};
load();
}, []);
...(略)...
return (
<div className={`${STYLES.MOVIE_DETAIL_PAGE}`}>
<article className={`${STYLES.MOVIE_DETAIL_CARD}`} key={movie.movie_id}>
...(略)...
{/* ↓追加 */}
{!loadingUser && user && (
<StarRating starWidth={STYLES.MOVIE_DETAIL_STAR_WIDTH} rating={movie.rating?.rating!} />
)}
{/* ↑追加 */}
</article>
</div>
);
};
export default CardMovieDetail;
実行確認
ブラウザで下記 URL にアクセスしてください。
サインインすると、映画詳細カードに五つのスターで構成される評価値コンポーネントが表示されます。評価値コンポーネント上で任意の評価値をクリックすると、その評価値までのスターが黄色く表示されます。ただし、現時点では、評価値は記憶されないため、ブラウザを更新すると元に戻ります。また、現時点では、ユーザ情報は評価値コンポーネントの表示/非表示の判定のために参照しているだけですので、ユーザ依存の評価値は取得できません。