Merge remote-tracking branch 'origin/develop-frontend' into feature-frontend-stock

feature-backend-add-springdocs
Yuna.Suzuki 9 months ago
commit a4db8a5017
  1. 7
      backend/src/main/java/com/example/todoapp/config/SecurityConfig.java
  2. 140
      backend/src/main/java/com/example/todoapp/controller/ToBuysController.java
  3. 9
      backend/src/main/java/com/example/todoapp/dto/DeleteToBuyRequest.java
  4. 12
      backend/src/main/java/com/example/todoapp/dto/ToBuyResponse.java
  5. 21
      backend/src/main/java/com/example/todoapp/dto/ToBuysDTO.java
  6. 74
      backend/src/main/java/com/example/todoapp/model/RecipeStuffs.java
  7. 53
      backend/src/main/java/com/example/todoapp/model/Recipes.java
  8. 100
      backend/src/main/java/com/example/todoapp/model/Stocks.java
  9. 59
      backend/src/main/java/com/example/todoapp/model/Stuffs.java
  10. 79
      backend/src/main/java/com/example/todoapp/model/ToBuys.java
  11. 25
      backend/src/main/java/com/example/todoapp/repository/StuffsRepository.java
  12. 49
      backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java
  13. 154
      backend/src/main/java/com/example/todoapp/service/ToBuysService.java
  14. 20
      frontend/src/App.tsx
  15. 2
      frontend/src/components/CategoryDropDown.tsx
  16. 7
      frontend/src/components/Layout.tsx
  17. 8
      frontend/src/constants/errorMessages.ts
  18. 82
      frontend/src/pages/AddDishes1.tsx
  19. 279
      frontend/src/pages/AddDishes2.tsx
  20. 1
      frontend/src/pages/StockPage.tsx
  21. 14
      frontend/src/pages/TaskListPage.tsx
  22. 12
      frontend/src/pages/TestPage.tsx
  23. 106
      frontend/src/services/api.ts

@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -49,8 +50,10 @@ public class SecurityConfig {
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/auth/**").permitAll() // 認証エンドポイントは認証不要
.anyRequest().authenticated(); // その他のエンドポイントは認証必要
// .requestMatchers("/auth/**").permitAll() // 認証エンドポイントは認証不要
// .anyRequest().authenticated()
.anyRequest().permitAll();
; // その他のエンドポイントは認証必要
// JWTフィルターを認証フィルターの前に追加
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

@ -0,0 +1,140 @@
//--------------------------------
// ToBuysController.java
//
// 更新履歴:2025/06/05 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.controller;
import com.example.todoapp.dto.DeleteToBuyRequest;
import com.example.todoapp.dto.ToBuyResponse;
import com.example.todoapp.dto.ToBuysDTO;
import com.example.todoapp.model.ToBuys;
import com.example.todoapp.model.User;
import com.example.todoapp.repository.UserRepository;
import com.example.todoapp.service.ToBuysService;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
/**
* 購入リストに関するRESTコントローラー
* <p>
* このコントローラーは購入リスト (to_buys) へのアイテム追加機能を提供します
* リクエストボディには ToBuyDTO 形式のデータが期待されます
* </p>
*/
@RestController
@RequestMapping("/tobuy")
public class ToBuysController {
@Autowired
private ToBuysService toBuysService;
@Autowired
private UserRepository userRepository;
/**
* 新しい購入アイテムを追加する
*
* @param dto 追加する購入アイテムのデータDTO
* @return 成功時のレスポンスメッセージ
*/
@PostMapping("/add")
public ResponseEntity<String> addToBuys(
@Valid @RequestBody ToBuysDTO dto,
Authentication authentication) {
toBuysService.addToBuys(dto, authentication);
return ResponseEntity.ok("Item added to 'To Buys' successfully");
}
@PutMapping("/update")
public ResponseEntity<String> updateToBuys(
@Valid @RequestBody ToBuysDTO dto,
Authentication authentication) {
toBuysService.updateToBuys(dto, authentication);
return ResponseEntity.ok("Item updated to 'To Buys' successfully");
}
/**
* 指定されたユーザーIDに基づいてすべての買うものリストを取得する
*
* @param userId ユーザーID
* @return ユーザーに紐づく買うものリスト
*/
@GetMapping("/get")
public ResponseEntity<?> getAllToBuysByUserId(Authentication authentication) {
// 認証されたユーザー名を取得
String username = authentication.getName();
// ユーザー情報を取得
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
List<ToBuys> toBuysList = toBuysService.getToBuysByUserId(user.getId());
// DTO形式に変換して返す
List<ToBuyResponse> responseList = toBuysList.stream()
.map(toBuy -> {
ToBuyResponse resp = new ToBuyResponse();
resp.setTobuy_id(toBuy.getTobuy_id());
resp.setStuff_id(toBuy.getStuffs().getStuff_id());
resp.setStuff_name(toBuy.getStuffs().getStuff_name());
resp.setAmount(toBuy.getAmount());
resp.setShop(toBuy.getStore());
return resp;
})
.collect(Collectors.toList());
Map<String, Object> responseBody = new HashMap<>();
responseBody.put("tobuy_array", responseList);
return ResponseEntity.ok(responseList);
}
/**
* ユーザーが指定したIDの買うものを削除する
*
* @param request 削除する買うものの情報を含むリクエストボディ
* @return 削除が成功した場合にtrueを含むレスポンス
*/
@DeleteMapping("/delete")
public ResponseEntity<Map<String, Boolean>> deleteToBuy(
@RequestBody DeleteToBuyRequest request,
Authentication authentication) {
// 認証されたユーザー名を取得
String username = authentication.getName();
// ユーザー情報を取得
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
int deletedCount = toBuysService.deleteToBuyByIds(user.getId(), request.getTobuy_id());
Map<String, Boolean> response = new HashMap<>();
if (deletedCount > 0) {
response.put("result", true);
} else {
response.put("result", false);
}
return ResponseEntity.ok(response);
}
}

@ -0,0 +1,9 @@
package com.example.todoapp.dto;
import lombok.Data;
@Data
public class DeleteToBuyRequest {
private Long user_id;
private int tobuy_id;
}

@ -0,0 +1,12 @@
package com.example.todoapp.dto;
import lombok.Data;
@Data
public class ToBuyResponse {
private int tobuy_id;
private Long stuff_id;
private String stuff_name;
private int amount;
private String shop;
}

@ -0,0 +1,21 @@
package com.example.todoapp.dto;
import lombok.Data;
/**
* カテゴリDTOクラス
* このクラスはタスクのカテゴリ情報を表します
* カテゴリは名前所有ユーザーなどの情報を持ちます
*
*/
@Data
public class ToBuysDTO {
private Long stuff_id;
private Long user_id;
private int amount;
private String shop;
private String stuff_name;
private String category;
}

@ -0,0 +1,74 @@
//--------------------------------
// RecipiesStuffs.java
//
// 分類:社員管理システムV2・ビジネスロジック層
//
// 更新履歴:2025/06/02 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 料理に必要な材料
*/
@Data
@NoArgsConstructor
@Entity
@Table(name = "recipe_stuffs")
public class RecipeStuffs {
/**
* 料理に必要リストの一意識別子
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int recipe_stuffs_id ;
/**
* 料理の一意識別子 FK
*/
@NotBlank
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "recipe_id",
referencedColumnName = "recipe_id",
nullable = false
)
private Recipes recipes;
/**
* 材料の一意識別子 FK
*/
@NotBlank
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "stuff_id",
referencedColumnName = "stuff_id",
nullable = false
)
private Stuffs stuffs;
/**
* 材料の数量
*/
@NotBlank
@Column(nullable = false)
private int amount;
}

@ -0,0 +1,53 @@
//--------------------------------
// Recipes.java
//
// 分類:社員管理システムV2・ビジネスロジック層
//
// 更新履歴:2025/06/02 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* カテゴリエンティティクラス
* このクラスはタスクのカテゴリ情報を表します
* カテゴリは名前所有ユーザーなどの情報を持ちます
*/
@Data
@NoArgsConstructor
@Entity
@Table(name = "recipes")
public class Recipes {
/**
* カテゴリID主キー
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int recipe_id ;
/**
* カテゴリ名
*/
@NotNull
@Column(unique = true, length = 255, nullable = false)
private String recipie_name;
/**
* カテゴリ
*/
@Column (columnDefinition = "TEXT")
private String summary;
}

@ -0,0 +1,100 @@
//--------------------------------
// Stocks.java
//
// 分類:社員管理システムV2・ビジネスロジック層
//
// 更新履歴:2025/06/03 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.model;
import java.time.LocalDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 在庫にある食材
*/
@Data
@NoArgsConstructor
@Entity
@Table(name = "stocks")
public class Stocks {
/**
* 在庫リストの一意識別子
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int stock_id ;
/**
* 商品テーブル参照用の外部キー
*/
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "stuff_id",
referencedColumnName = "stuff_id",
nullable = false
)
private Stuffs stuffs;
/**
* ユーザーテーブル参照用の外部キー
*/
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "user_id",
referencedColumnName = "id",
nullable = false
)
private User user_id;
/**
* 在庫数量デフォルト値: 1
*/
@Column(nullable = false)
private int amount = 1;
/**
* シングルの値段デフォルト値:
*/
@Column(nullable = false)
private int price = 0;
/**
* 購入日
*/
@Column(nullable = false)
private LocalDate buy_date;
/**
* 最後の操作時間
*/
@Column(nullable = false)
private LocalDate last_update;
/**
* 賞味期限
*/
@Column(nullable = false)
private LocalDate exp_date;
}

@ -0,0 +1,59 @@
//--------------------------------
// Stuffs.java
//
// 分類:社員管理システムV2・ビジネスロジック層
//
// 更新履歴:2025/06/02 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* カテゴリエンティティクラス
* このクラスはタスクのカテゴリ情報を表します
* カテゴリは名前所有ユーザーなどの情報を持ちます
*/
@Data
@NoArgsConstructor
@Entity
@Table(name = "stuffs")
public class Stuffs {
/**
* カテゴリID主キー
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long stuff_id ;
/**
* カテゴリ名
*/
@NotNull
@Column(unique = true, length = 255, nullable = false)
private String stuff_name;
/**
* カテゴリ
*/
@Column (columnDefinition = "TEXT")
private String summary;
/**
* カテゴリ
*/
@NotNull
@Column(nullable = false, length = 225)
private String category = "その他" ;
}

@ -0,0 +1,79 @@
//--------------------------------
// ToBuys.java
//
//
// 更新履歴:2025/06/03 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ユーザーの買うものリスト
*/
@Data
@NoArgsConstructor
@Entity
@Table(name = "to_buys")
public class ToBuys {
/**
* 購入項目の一意識別子
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int tobuy_id ;
/**
* 材料の一意識別子 FK
*/
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "stuff_id",
referencedColumnName = "stuff_id",
nullable = false
)
private Stuffs stuffs;
/**
* ユーザーテーブル参照用の外部キー
*/
// @NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "user_id",
referencedColumnName = "id"
)
private User user_id;
/**
* 購入する数量
*/
@NotNull
@Column(nullable = false)
private int amount = 1;
/**
* 購入するお店
*/
@NotNull
@Column(nullable = false)
private String store;
}

@ -0,0 +1,25 @@
//--------------------------------
// StuffsRepository.java
//
//
// 更新履歴:2025/06/05 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.repository;
import com.example.todoapp.model.Stuffs;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* 材料 (stuffs) テーブルへのアクセスを行うリポジトリ.
* <p>
* このクラスは材料テーブル (stuffs) に対する基本的なCRUD操作を提供します
* Spring Data JPAによって自動的に実装されます
* </p>
*/
@Repository
public interface StuffsRepository extends JpaRepository<Stuffs, Long> {
// 材料情報を主キーで取得するメソッド(必要に応じて追加)
}

@ -0,0 +1,49 @@
//--------------------------------
// ToBuysRepository.java
//
//
// 更新履歴:2025/06/05 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.repository;
import com.example.todoapp.model.ToBuys;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* to_buys テーブルへのアクセスを行うリポジトリ
* <p>
* このクラスは to_buys テーブルに対する基本的なCRUD操作を提供します
* Spring Data JPAによって自動的に実装されます
* </p>
*/
@Repository
public interface ToBuysRepository extends JpaRepository<ToBuys, Integer> {
/**
* 指定されたユーザーIDに基づいて買うものリストを取得する
*
* @param userId ユーザーID
* @return 買うものリスト
*/
@Query("SELECT t FROM ToBuys t WHERE t.user_id.id = ?1")
List<ToBuys> findByUserId(Long user_id);
/**
* 指定されたユーザーIDに基づいて買うものリストを取得する
*
* @param userId ユーザーID
* @param tobuyId 買うものID
* @return
*/
@Modifying
@Query("DELETE FROM ToBuys t WHERE t.user_id.id = :userId AND t.tobuy_id = :tobuyId")
int deleteByUserIdAndTobuyId(@Param("userId") Long userId, @Param("tobuyId") int tobuyId);
}

@ -0,0 +1,154 @@
//--------------------------------
// ToBuysService.java
//
//
// 更新履歴:2025/06/05 新規作成
// Copyright(c) 2025 IVIS All rights reserved.
//--------------------------------------------
package com.example.todoapp.service;
import com.example.todoapp.dto.ToBuysDTO;
import com.example.todoapp.model.Stuffs;
import com.example.todoapp.model.ToBuys;
import com.example.todoapp.model.User;
import com.example.todoapp.repository.StuffsRepository;
import com.example.todoapp.repository.ToBuysRepository;
import com.example.todoapp.repository.UserRepository;
import jakarta.transaction.Transactional;
import org.springframework.security.core.Authentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* 購入リストのサービスクラス
* <p>
* このクラスは購入リスト (to_buys) の登録処理を提供します
* 材料 (stuffs) の存在確認と新規作成ユーザー情報の取得などを行います
* </p>
*/
@Service
public class ToBuysService {
@Autowired
private ToBuysRepository toBuysRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private StuffsRepository stuffsRepository;
/**
* 購入リストに新しいアイテムを追加する
*
* @param toBuyDTO 追加する購入アイテムのデータDTO
*/
public void addToBuys(ToBuysDTO toBuyDTO, Authentication authentication) {
// ユーザー情報を取得
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("ユーザーが見つかりません: " + username));
Stuffs stuffs;
if (toBuyDTO.getStuff_id() == null) {
// 新しい材料を作成
stuffs = new Stuffs();
stuffs.setStuff_name(toBuyDTO.getStuff_name());
stuffs.setCategory(toBuyDTO.getCategory());
stuffs = stuffsRepository.save(stuffs);
} else {
// 材料情報を取得
Optional<Stuffs> optionalStuffs = stuffsRepository.findById(toBuyDTO.getStuff_id());
if (!optionalStuffs.isPresent()) {
throw new RuntimeException("材料がありません");
}
stuffs = optionalStuffs.get();
}
ToBuys toBuys = new ToBuys();
toBuys.setUser_id(user);
toBuys.setStuffs(stuffs);
toBuys.setAmount(toBuyDTO.getAmount());
toBuys.setStore(toBuyDTO.getShop());
// データベースに保存
toBuysRepository.save(toBuys);
}
/**
* 購入リストに新しいアイテムを変更する
*
* @param toBuyDTO 変更する購入アイテムのデータDTO
*/
public void updateToBuys(ToBuysDTO toBuyDTO, Authentication authentication) {
// ユーザー情報を取得
String username = authentication.getName();
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("ユーザーが見つかりません: " + username));
Stuffs stuffs;
if (toBuyDTO.getStuff_id() == null) {
// 新しい材料を作成
stuffs = new Stuffs();
stuffs.setStuff_name(toBuyDTO.getStuff_name());
stuffs.setCategory(toBuyDTO.getCategory());
stuffs = stuffsRepository.save(stuffs);
} else {
// 材料情報を取得
Optional<Stuffs> optionalStuffs = stuffsRepository.findById(toBuyDTO.getStuff_id());
if (!optionalStuffs.isPresent()) {
throw new RuntimeException("材料がありません");
}
stuffs = optionalStuffs.get();
//update
// stuffs.setStuff_name(toBuyDTO.getStuff_name());
// stuffs.setCategory(toBuyDTO.getCategory());
stuffs = stuffsRepository.save(stuffs);
}
ToBuys toBuys = new ToBuys();
toBuys.setUser_id(user);
toBuys.setStuffs(stuffs);
toBuys.setAmount(toBuyDTO.getAmount());
toBuys.setStore(toBuyDTO.getShop());
// データベースに保存
toBuysRepository.save(toBuys);
}
/**
* 指定されたユーザーIDに基づいてすべての買うものリストを取得する
*
* @param userId ユーザーID
* @return ユーザーに紐づく買うものリスト
*/
public List<ToBuys> getToBuysByUserId(Long userId) {
return toBuysRepository.findByUserId(userId);
}
/**
* 指定されたユーザーIDと購入リストIDに基づいて買うものを削除する
*
* @param userId ユーザーID
* @param tobuyId 購入リストID
*/
@Transactional
public int deleteToBuyByIds(Long userId, int tobuyId) {
return toBuysRepository.deleteByUserIdAndTobuyId(userId, tobuyId);
}
}

@ -11,6 +11,9 @@ import RegisterPage from './pages/RegisterPage';
import TaskListPage from './pages/TaskListPage';
import StockPage from './pages/StockPage';
import './App.css';
// 必要なインポートを追加
import AddDishes1 from './pages/AddDishes1';
import AddDishes2 from './pages/AddDishes2';
/**
* Material UIテーマを定義
@ -94,6 +97,23 @@ const App: React.FC = () => {
}
/>
{/* テストページへのルートを追加 */}
<Route
path="add1"
element={
<PrivateRoute>
<AddDishes1 />
</PrivateRoute>
}
/>
<Route
path="add2"
element={
<PrivateRoute>
<AddDishes2 />
</PrivateRoute>
}
/>
</Route>
<Route path="/" element={<Layout />}>
{/* ルートパスへのアクセスはタスク一覧にリダイレクト */}

@ -16,7 +16,7 @@ const CategoryDropDown = () => {
onChange={(event) => setSelectedValue(event.target.value)}
>
<MenuItem value="乳製品"></MenuItem>
<MenuItem value="肉・魚"></MenuItem>
<MenuItem value="魚・肉"></MenuItem>
<MenuItem value="野菜"></MenuItem>
<MenuItem value="調味料">調</MenuItem>
<MenuItem value="その他"></MenuItem>

@ -103,6 +103,13 @@ const Layout: React.FC = () => {
{/* テストページへのリンクを追加 */}
{/* 在庫リストへのリンクを追加 */}
<ListItemButton
onClick={() => handleNavigate('/add1')}
selected={isSelected('/add1')}
>
<ListItemIcon><ScienceIcon /></ListItemIcon>
<ListItemText primary="料理の追加" />
</ListItemButton>
<ListItemButton
onClick={() => handleNavigate('/stock')}
selected={isSelected('/stock')}

@ -21,3 +21,11 @@ export const TASK_ERRORS = {
UPDATE_FAILED: 'タスクの更新に失敗しました',
DELETE_FAILED: 'タスクの削除に失敗しました',
};
// 買うものリスト関連のエラーメッセージ
export const TOBUY_ERRORS = {
FETCH_FAILED: '買うものリストの取得に失敗しました',
CREATE_FAILED: '買うものリストの作成に失敗しました',
UPDATE_FAILED: '買うものリストの更新に失敗しました',
DELETE_FAILED: '買うものリストの削除に失敗しました',
};

@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
AppBar,
Toolbar,
Typography,
Container,
Box,
Button,
Drawer,
List,
ListItemText,
ListItemIcon,
ListItemButton,
Divider,
IconButton,
TextField,
Paper,
Alert,
Link,
Grid,
} from '@mui/material';
import { LoginCredentials } from '../types/types';
import { authApi } from '../services/api';
import { GENERAL_ERRORS } from '../constants/errorMessages';
const AddDishes1: React.FC = () => {
const navigate = useNavigate();
const [dish, setDish] = useState("");
// エラーメッセージの状態管理
const [error, setError] = useState(false);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setDish(event.target.value);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // フォームのデフォルト送信動作を防止
if (!dish.trim()) {
alert("エラー");
setError(true);
} else {
alert("送信成功!");
localStorage.setItem("dishName", dish); // ローカルストレージにフォームに入力された料理名を保存
navigate('/add2', { state: dish }); // 料理名から材料名追加ページにリダイレクト
}
};
return (
<div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
<Box component="form" onSubmit={handleSubmit}>
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '10px', textAlign: 'center'}}>
<TextField
required
// id="username"
label="追加・編集したい料理名を入力"
variant="outlined"
InputLabelProps={{ style: { fontSize: "40px" }}}
style={{width: "80%" }}
InputProps={{ style: { fontSize: "40px"} }}
name="dish_name"
// autoComplete="username"
autoFocus
value={dish}
onChange={handleChange}
error={error}
helperText={error ? "入力が必要です" : ""}
/>
</div>
<div style={{position: "fixed", left: "75%", transform: 'translateX(-50%)', bottom: "10px"}}>
<Button type="submit" variant='contained' sx={{ width: "250px", height: "60px", fontSize: "40px" }} color="primary" >
</Button>
</div>
</Box>
</div>
);
};
export default AddDishes1;

@ -0,0 +1,279 @@
/**
*
*
*/
import React, { useState, useEffect } from 'react';
import { taskApi, toBuyApi } from '../services/api';
import {
Container,
Typography,
Tooltip,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
IconButton,
Checkbox,
Fab,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
MenuItem,
Select,
FormControl,
InputLabel
} from '@mui/material';
import {
Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon,
SoupKitchen as SoupKitchenIcon
} from '@mui/icons-material';
import { Task } from '../types/types';
import { TASK_ERRORS } from '../constants/errorMessages';
import { GENERAL_ERRORS } from '../constants/errorMessages';
import CategoryDropDown from "../components/CategoryDropDown";
// 新規タスクの初期状態(画面表示用)
const EMPTY_TASK = { id: 0, title: '', amount: 0, completed: false };
// 新規タスクの初期状態(データベース登録用)
const EMPTY_TASK_DATA_BASE = { id: 0, title: '', amount: 0, completed: false };
interface Empty_Task {
title: string; // 食材名
amount: number; // 食材の個数
completed: boolean; //
}
const AddDishes2: React.FC = () => {
const receivedData = localStorage.getItem("dishName");
// タスク一覧の状態管理
const [tasks, setTasks] = useState<Task[]>([]);
const [addtasks, setAddTasks] = useState<Empty_Task[]>([]);
// エラーメッセージの状態管理
const [error, setError] = useState(false);
// 新規タスク作成ダイアログの表示状態
const [openDialog, setOpenDialog] = useState(false);
// 新規タスクの入力内容
const [newTask, setNewTask] = useState(EMPTY_TASK);
// コンポーネントマウント時にタスク一覧を取得
useEffect(() => {
fetchTasks();
}, []);
/**
* APIからタスク一覧を取得する関数
* state(tasks)
*/
const fetchTasks = async () => {
try {
const tasks = await taskApi.getTasks();
setTasks(tasks);
} catch (error) {
console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error);
}
};
/**
*
* IDのタスクをAPIを通じて削除
*/
const handleDeleteTask = async (index: number) => {
try {
let newAddTasks = [...addtasks]; // 配列をコピー
newAddTasks.splice(index, 1);
setAddTasks(newAddTasks);
// fetchTasks(); // 削除後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.DELETE_FAILED}:`, error);
}
};
/**
*
* APIに送信して新規作成
*
*/
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // フォームのデフォルト送信動作を防止
if (addtasks[0] == null) {
setError(true);
alert("食材を追加してください");
} else {
alert("送信成功!");
handleCreateTask_DataBase();
// localStorage.setItem("dishName", dish); // ローカルストレージにフォームに入力された料理名を保存
// navigate('/add2', { state: dish }); // 料理名から材料名追加ページにリダイレクト
}
};
const handleCreateTask_Temp = async () => {
try {
// await taskApi.createTask(newTask);
let newAddTasks = [...addtasks]; // 配列をコピー
newAddTasks.push(newTask);
setAddTasks(newAddTasks);
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
// fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
}
};
const handleCreateTask_DataBase = async () => {
try {
// for (let i = 0; i < addtasks.length; i++) {
// // await taskApi.createTask(addtasks[i]);
// await toBuyApi.addToBuy(addtasks[i]);
// }
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
// fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
}
};
return (
<Box>
<div>
<h1></h1>
<p style={{fontSize: "40px"}}>{receivedData}</p>
</div>
<List>
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */}
{addtasks.map((task, index) => (
<ListItem
key={index}
sx={{
bgcolor: 'background.paper',
mb: 1,
borderRadius: 1,
boxShadow: 1,
}}
>
{/*
<Checkbox
checked={task.completed}
onChange={() => handleToggleComplete(task.id)}
/> */}
{/* タスクのタイトルと説明 - 完了状態に応じて取り消し線を表示 */}
<ListItemText
primary={task.title}
// secondary={task.description}
sx={{
textDecoration: task.completed ? 'line-through' : 'none',
}}
/>
{/* 食材の個数を表示 */}
<ListItemText
primary={<Typography style={{textAlign:"center"}}><br />
{task.amount}
</Typography>
}
// secondary={task.description}
primaryTypographyProps={{ align: "right", marginRight: "20%", fontSize: "20px" }}
/>
{/* 買い物リスト:食材情報記入ボタン */}
<ListItemSecondaryAction>
<Tooltip title="食材情報追加">
<IconButton
edge="end"
aria-label="食材情報追加"
//onClick={() => handleDeleteTask(task.id)}
>
<ShoppingBasketIcon />
</IconButton>
</Tooltip>
{/* 買い物リスト:食材削除ボタン */}
<Tooltip title="項目を削除"
componentsProps={{
tooltip: {
sx: {
backgroundColor: "white",
color: "red",
fontSize: "0.8rem",
padding: "6px",
borderRadius: "6px",
},
},
}}
>
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleDeleteTask(index)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
<div style={{position: "fixed", left: "75%", transform: 'translateX(-50%)', bottom: "20%"}}>
<Button variant='contained' sx={{ width: "250px", height: "60px", fontSize: "40px" }}
color="primary" onClick={() => setOpenDialog(true)}>
</Button>
</div>
<div style={{position: "fixed", width: "60%", left: "50%", transform: 'translateX(-50%)', bottom: "2%"}}>
<Button variant='contained' sx={{ width: "100%", height: "100px", fontSize: "40px" }}
color="primary" onClick={handleSubmit}>
</Button>
</div>
{/* 新規タスク作成ダイアログ */}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true}>
<DialogTitle></DialogTitle>
<DialogContent>
<Box sx={{ pt: 1 }}>
{/*材料カテゴリ選択 */}
<CategoryDropDown></CategoryDropDown>
{/* タスクタイトル入力フィールド */}
<TextField
autoFocus
margin="dense"
label="材料名"
fullWidth
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
sx={{ marginBottom: 2 }}
/>
{/* 数量入力フィールド */}
<TextField
margin="dense"
label="数量"
fullWidth
value={newTask.amount}
onChange={(e) => {
const value = e.target.value;
const parsedValue = parseInt(value, 10); // 数値に変換
if (!isNaN(parsedValue)) {
setNewTask({ ...newTask, amount: parsedValue }); // number型で保存
}
}}
sx={{ width: "20%" }}
type="number"
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}></Button>
<Button onClick={handleCreateTask_Temp} variant="contained">
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default AddDishes2;

@ -21,7 +21,6 @@ import {
} from '@mui/material';
import { TASK_ERRORS } from '../constants/errorMessages';
import CategoryDropDown from "../components/CategoryDropDown";
const StockPage: React.FC = () => {

@ -36,8 +36,6 @@ import {
import { Task, ToBuy, Stuff } from '../types/types';
import { TASK_ERRORS } from '../constants/errorMessages';
//import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留
import CategoryDropDown from "../components/CategoryDropDown";
@ -127,6 +125,10 @@ const TaskListPage: React.FC = () => {
*/
const handleCreateTask = async () => {
try {
if (newToBuy.newAddition) {
newToBuy.stuff_id = null;
}
console.log(newToBuy)
await toBuyApi.addToBuy(newToBuy);
setOpenDialog(false); // ダイアログを閉じる
setNewToBuy(EMPTY_TASK); // 入力内容をリセット
@ -139,13 +141,13 @@ const TaskListPage: React.FC = () => {
return (
<Container>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* タスク一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px' }}>
<List>
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */}
{tobuys.map((tobuy) => (
{tobuys && tobuys.map((tobuy) => (
<ListItem
key={tobuy.tobuy_id}
sx={{
@ -179,7 +181,7 @@ const TaskListPage: React.FC = () => {
onClick={() => {
setOpenInfoDialog(true)
setSelectedTask(tobuy.tobuy_id)
handleDeleteTask(tobuy.tobuy_id)
// handleDeleteTask(tobuy.tobuy_id)
}}
>
<ShoppingBasketIcon />
@ -260,7 +262,7 @@ const TaskListPage: React.FC = () => {
onChange={(e) => onChangeCategory(e.target.value) }
>
<MenuItem value="乳製品"></MenuItem>
<MenuItem value="肉・魚"></MenuItem>
<MenuItem value="魚・肉"></MenuItem>
<MenuItem value="野菜"></MenuItem>
<MenuItem value="調味料">調</MenuItem>
<MenuItem value="その他"></MenuItem>

@ -0,0 +1,12 @@
import React from 'react';
import { Box } from '@mui/material';
const TestPage: React.FC = () => {
return (
<Box>
{/* 白紙のページ - 何も表示しない */}
</Box>
);
};
export default TestPage;

@ -4,7 +4,7 @@
*
*/
import { LoginCredentials, RegisterCredentials, AuthResponse, Task, ToBuy, Stuff, Stock } from '../types/types';
import { AUTH_ERRORS, TASK_ERRORS } from '../constants/errorMessages';
import { AUTH_ERRORS, TASK_ERRORS, TOBUY_ERRORS } from '../constants/errorMessages';
// APIのベースURL - 環境変数から取得するか、デフォルト値を使用
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080';
@ -93,34 +93,35 @@ export const toBuyApi = {
* @returns
*/
getToBuys: async (): Promise<{ "tobuy_array": ToBuy[] }> => {
// const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, {
// headers: getHeaders(), // 認証トークンを含むヘッダー
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.FETCH_FAILED);
// }
// return response.json();
const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, {
headers: getHeaders(), // 認証トークンを含むヘッダー
});
return {
"tobuy_array": [
{
"tobuy_id": 1,
"stuff_id": 2,
"stuff_name": "じゃがいも",
"amount": 3,
"shop": "shopXXX"
},
{
"tobuy_id": 2,
"stuff_id": 5,
"stuff_name": "にんじん",
"amount": 1
}
]
if (!response.ok) {
throw new Error(TOBUY_ERRORS.FETCH_FAILED);
}
const tobuy_array = await response.json();
return {tobuy_array};
// return {
// "tobuy_array": [
// {
// "tobuy_id": 1,
// "stuff_id": 2,
// "stuff_name": "じゃがいも",
// "amount": 3,
// "shop": "shopXXX"
// },
// {
// "tobuy_id": 2,
// "stuff_id": 5,
// "stuff_name": "にんじん",
// "amount": 1
// }
// ]
// }
},
/**
@ -129,24 +130,25 @@ export const toBuyApi = {
* @returns
*/
addToBuy: async (tobuy: Omit<ToBuy, 'stuff_id' | 'tobuy_id'> & { stuff_id: number | null, category: string }): Promise<any> => {
// const response = await fetch(`${API_BASE_URL}/api/tasks`, {
// method: 'POST',
// headers: getHeaders(),
// body: JSON.stringify(tobuy),
// });
const response = await fetch(`${API_BASE_URL}/api/tobuy/add`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(tobuy),
});
// if (!response.ok) {
// throw new Error(TASK_ERRORS.CREATE_FAILED);
// }
if (!response.ok) {
throw new Error(TOBUY_ERRORS.CREATE_FAILED);
}
// return response.json();
return {result: true}
return {
"result": true,
"tobuy_id": 1,
"stuff_id": 6,
"message": "追加に成功しました",
}
// return {
// "result": true,
// "tobuy_id": 1,
// "stuff_id": 6,
// "message": "追加に成功しました",
// }
},
@ -155,19 +157,21 @@ export const toBuyApi = {
* @param id ID
*/
deleteToBuy: async (tobuy_id: number): Promise<{ result: boolean }> => {
// const response = await fetch(`${API_BASE_URL}/api/tobuy/delete`, {
// method: 'DELETE',
// headers: getHeaders(),
// body: JSON.stringify({tobuy_id}),
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.DELETE_FAILED);
// }
const response = await fetch(`${API_BASE_URL}/api/tobuy/delete`, {
method: 'DELETE',
headers: getHeaders(),
body: JSON.stringify({tobuy_id}),
});
return {
"result": true
if (!response.ok) {
throw new Error(TOBUY_ERRORS.DELETE_FAILED);
}
return response.json()
// return {
// "result": true
// }
},
}

Loading…
Cancel
Save