View on GitHub

マイリストからの評価値の削除

Home

マイリストからの評価値の削除

バックエンド

ビュー

backend/api/online/views.py

......
class RatingsView(APIView):
    """評価値ビュークラス
    """

    def get(self, request, format=None):
......
    def post(self, request, format=None):
......
    def delete(self, request, format=None):
        """評価値を削除する。

        Requests
        --------
        id : str
            評価値ID

        Response
        --------
        rating : json
            評価値
        """
        # リクエストパラメタの取得
        id = request.data['id']

        # オブジェクトの削除
        rating_model = Rating.objects.get(pk=id)
        rating_model.delete()

        # レスポンス
        rating_dict = RatingMapper(rating_model).as_dict()
        data = {
            'rating': rating_dict,
        }
        return Response(data, status.HTTP_200_OK)

フロントエンド

API

frontend/src/services/ratings/deleteRating.ts

import { ApiContext, Movie, Rating, User } from '@/types/data';
import { fetcher } from '@/utils';

const context: ApiContext = {
  apiRootUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
};

/**
 * 評価値削除API
 * @param user - ユーザ
 * @param movie - 映画
 * @returns 評価値
 */
const deleteRating = async (user: User, movie: Movie): Promise<{ rating: Rating }> => {
  const id = user.id + '_' + String(movie.id).padStart(6, '0');
  const body = {
    id: id,
  };
  return await fetcher(`${context.apiRootUrl?.replace(/\/$/g, '')}/ratings/`, {
    method: 'DELETE',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
};

export default deleteRating;

コンポーネント

frontend/src/app/components/movie/MovieCard.tsx

import { Movie, User } from '@/types/data';
import Image from 'next/image';
import Link from 'next/link';
import React from 'react';
import StarRating from '../rating/StarRating';
import postRating from '@/services/ratings/postRating';
import getRating from '@/services/ratings/getRating';
import { IconButton } from '@mui/material'; // <- 追加
import HighlightOffIcon from '@mui/icons-material/HighlightOff'; // <- 追加

const STAR_WIDTH = 24;

type Props = {
  movie: Movie;
  user: User;
  isMyList?: boolean; // <- 追加
  handleRatingClick: Function;
  handleDelete?: Function; // <- 追加
};

const MovieCard = (props: Props) => {
......
  return (
    <>
      <article className="h-72 w-32 shadow" key={props.movie.id}>
...(略)...
        <div className="mx-1">
          <div className="line-clamp-2 font-semibold text-gray-800">{props.movie.title}</div>
          <div className="flex">
            <div className="my-1 rounded bg-gray-200 px-1 py-0.5 text-sm text-gray-800">
              {props.movie.year}
            </div>
            {/* ↓追加 */}
            {props.isMyList == true ? (
              <IconButton onClick={() => props.handleDelete!(props.movie)}>
                <HighlightOffIcon className="text-sm" />
              </IconButton>
            ) : (
              <></>
            )}
            {/* ↑追加 */}
          </div>
        </div>
      </article>
    </>
  );
};

export default MovieCard;

frontend/src/app/components/movie/MovieList.tsx

'use client';
import { Movie, User } from '@/types/data';
import React, { useEffect, useState } from 'react';
import MovieCard from './MovieCard';
import deleteRating from '@/services/ratings/deleteRating'; // <- 追加

import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';

type Props = {
  phrase: string;
  movies: Movie[];
  perPage: number;
  user: User;
  isMyList?: boolean; // <- 追加
};

const MovieList = (props: Props) => {
......
  useEffect(() => {
    // ↓追加
    if (movies.length <= 0) {
      setCurrentMovies(movies);
    }
    // ↑追加

    const start = props.perPage * currentPage;
    const end = start + props.perPage;
    const currentMovies_ = movies.slice(start, end);

    // ↓修正
    if (currentMovies_.length <= 0) {
      let currentPage_ = currentPage <= 0 ? 0 : currentPage - 1;
      setCurrentPage(currentPage_);
    } else {
      setCurrentMovies(currentMovies_);
    }
    // ↑修正
  }, [movies, currentPage]);
......
  // ↓追加
  const handleDelete = async (movie: Movie) => {
    await deleteRating(props.user, movie);
    const movies_ = movies.filter((movie_) => movie_.id != movie.id);
    setMovies(movies_);
  };
  // ↑追加

  return (
    <>
      <div className="text-xl font-bold text-gray-800">{props.phrase}</div>
      <div className="mx-4 my-4 flex justify-between">
        <button
...(略)...
        </button>
        {currentMovies?.map((movie) => (
          <MovieCard
            movie={movie}
            user={props.user}
            isMyList={props.isMyList} // <- 追加
            handleRatingClick={handleRatingClick}
            handleDelete={handleDelete} // <- 追加
            key={movie.id}
          />
        ))}
        <button
...(略)...
        </button>
      </div>
    </>
  );
};

export default MovieList;

ページ

frontend/src/app/mypage/page.tsx

......
const MyPage = async () => {
......
  return (
    <>
      {session?.user ? (
        <>
          <MovieList
            phrase={phrase}
            movies={movies}
            perPage={PER_PAGE}
            user={user!}
            isMyList={true} // <- 追加
          />
          <MovieListBPR user={user!} perPage={PER_PAGE} />
        </>
      ) : (
        <></>
      )}
    </>
  );
};

export default MyPage;

ブラウザで下記URLにアクセスしてください。

マイリストから削除アイコンをクリックすると、評価値が削除されます。