diff --git a/backend/src/main/java/com/example/todoapp/repository/RecipeStuffsRepository.java b/backend/src/main/java/com/example/todoapp/repository/RecipeStuffsRepository.java index 4c0609d..4ac6eaa 100644 --- a/backend/src/main/java/com/example/todoapp/repository/RecipeStuffsRepository.java +++ b/backend/src/main/java/com/example/todoapp/repository/RecipeStuffsRepository.java @@ -7,6 +7,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.example.todoapp.model.RecipeStuffs; +import jakarta.transaction.Transactional; + +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; + /** * レシピ食材関連データのリポジトリインターフェース *

@@ -31,4 +38,5 @@ public interface RecipeStuffsRepository extends JpaRepository findByRecipesRecipeIdAndStuffStuffId(Long recipeId, Long stuffId); + } diff --git a/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java b/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java index d9b6861..0778f5f 100644 --- a/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java +++ b/backend/src/main/java/com/example/todoapp/repository/ToBuysRepository.java @@ -40,13 +40,13 @@ public interface ToBuysRepository extends JpaRepository { ToBuys findByTobuyId(Long tobuyId); /** - * 指定されたユーザーIDに基づいて「買うもの」リストを取得する + * 指定されたユーザーIDに基づいて「買うもの」リストを取得する_(tobuyIdの順番) * * @param userId ユーザーID * @return 「買うもの」リスト */ - @Query("SELECT t FROM ToBuys t WHERE t.user.id = ?1") - List findByUserIdOrderByTobuyIdAsc(Long userId); + @Query("SELECT t FROM ToBuys t WHERE t.user.id = :userId ORDER BY t.tobuyId ASC") + List findByUserIdOrderByTobuyIdAsc(@Param("userId") Long userId); /** * 指定された「買うもの」IDに基づいて「買うもの」リストを削除 diff --git a/frontend/src/pages/AddRecipe.tsx b/frontend/src/pages/AddRecipe.tsx index fb2238e..2618fda 100644 --- a/frontend/src/pages/AddRecipe.tsx +++ b/frontend/src/pages/AddRecipe.tsx @@ -71,6 +71,16 @@ const AddRecipe: React.FC = () => { const handleSaveRecipe = async () => { + if (!recipeName) { + alert('レシピ名が入力されていません!') + return false; + } + + if (!items.length) { + alert('材料が追加されていません!') + return false; + } + if (!recipeId) { // 新規追加 const response = await recipeApi.addRecipe({ @@ -94,11 +104,13 @@ const AddRecipe: React.FC = () => { const handleSubmit = async () => { const recipeId = await handleSaveRecipe(); // alert('レシピが保存されました!'); + if (!recipeId) return; navigate('/recipeList'); } const handleSubmitAndAddToBuy = async () => { const recipeId = await handleSaveRecipe(); + if (!recipeId) return false; await toBuyApi.addByRecipe(recipeId); // alert('レシピが保存されて買うものリストに追加されました!'); navigate('/tasks'); @@ -188,7 +200,7 @@ const AddRecipe: React.FC = () => { -

+
- - - - )} - - - - {/* 削除ダイアログ */} - - 食材の削除 - - {selectedRow && ( - <> - 【{selectedRow.stuffName}】を削除します。 - ⚠️ 注意: 削除すると復元できません。 - - - - )} - - - + const handleDeleteStock = async (stockId: number) => { + try { + await stockApi.deleteStock(stockId); + fetchStocks(); // 削除後の買うもの一覧を再取得 + } catch (error) { + console.error(`${STOCK_ERRORS.DELETE_FAILED}:`, error); + } + }; + + /** + * カテゴリー選択?? + */ + const onChangeCategory = async (category: string) => { + setNewStock({ ...newStock, category }) + const result = await stuffApi.getStuffs(category) + setStuffs(result) + } + + /** + * 文字列(ISO 8601形式)をyyyy/MM/ddに変換する関数 + */ + const formatDate = (isoString: string): string => { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1 + const day = date.getDate().toString().padStart(2, "0"); + return `${year}/${month}/${day}`; + }; + /* Date型をyyyy/MM/ddに変換する関数 + const formatDate = (date: Date): string => { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1 + const day = date.getDate().toString().padStart(2, "0"); + return `${year}/${month}/${day}`; + }; + */ + + /** 追加ボタンを押したときにダイアログを開く */ + const handleOpenAdd = () => { + setIsAddOpen(true); + }; + /** 削除ダイアログを閉じる */ + const handleCloseAdd = () => { + setIsAddOpen(false); + }; + + /** + * セルを選択する関数. 再度クリックで選択解除 + */ + const handleRowClick = (stock: Stock) => { + setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock)); + }; + + /** 編集ボタンを押したときにダイアログを開く */ + // ダイアログを開く際に `selectedRow` の値を `editStock` にセット + const handleOpenEdit = () => { + if (selectedRow) { + setEditStock({ ...selectedRow }); + setIsEditOpen(true); + } else { + alert("編集する食材を選択してください。"); + } + }; + // 変更を適用 + const handleApplyChanges = async () => { + if (editStock) { + try { + await handleUpdateStock( + editStock.stockId, + Number(editStock.amount), + Number(editStock.price), + editStock.buyDate, + editStock.expDate ); - }; + setSelectedRow(editStock); // `selectedRow` を更新して変更を即時反映 + fetchStocks(); // 最新データを取得してテーブルに反映 + setSelectedRow(null); // セルの選択を解除 + } catch (error) { + console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error); + } + } + setIsEditOpen(false); // ダイアログを閉じる + }; + + // ダイアログを開く際に `selectedRow` の値を `editStock` にコピー + useEffect(() => { + if (selectedRow) { + setEditStock({ ...selectedRow }); + } + }, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映 + + // テキストフィールドの変更を検知 + const handleChange = (event: React.ChangeEvent) => { + if (editStock) { + setEditStock({ + ...editStock, + [event.target.name]: event.target.value, + }); + } + }; + + /** 編集ダイアログを閉じる */ + const handleCloseEdit = () => { + setIsEditOpen(false); + }; + + /** 削除ボタンを押したときにダイアログを開く */ + const handleOpenDelete = () => { + if (selectedRow) { + setIsDeleteOpen(true); + } else { + alert("削除する食材を選択してください。"); + } + }; + /** 削除ダイアログを閉じる */ + const handleCloseDelete = () => { + setIsDeleteOpen(false); + }; - return ( - - - 在庫一覧 - - - {/* 在庫の食材追加ボタン */} - + /** テーブルを表示する関数 */ + const StockTable = (stocks: Stock[], categories: string[]) => { + const filteredStocks = stocks.filter(stock => categories.includes(stock.category)); - {/* 新規タスク作成ダイアログ */} - setIsAddOpen(false)} disableScrollLock={true}> - - 在庫に食材を追加 - - } - label="食材を新規追加" - checked={newStock.newAddition} - onChange={(e) => setNewStock({ ...newStock, newAddition: (e.target as HTMLInputElement).checked })} - /> - - - - - {/*材料カテゴリ選択 */} - - - カテゴリ - - - - {!newStock.newAddition && - 材料名(選択) - - } - - {/* タスクタイトル入力フィールド */} - {newStock.newAddition && setNewStock({ ...newStock, stuffName: e.target.value })} - sx={{ marginBottom: 2 }} - />} - {/* 数量入力フィールド */} - { - const value = e.target.value; - const parsedValue = parseInt(value, 10); // 数値に変換 - if (!isNaN(parsedValue)) { - setNewStock({ ...newStock, amount: parsedValue }); // number型で保存 - } - }} - // sx={{ width: "50%" }} - type="number" - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 - /> - {/* 購入価格入力フィールド */} - { - const value = e.target.value; - const parsedValue = parseInt(value, 10); // 数値に変換 - if (!isNaN(parsedValue)) { - setNewStock({ ...newStock, price: parsedValue }); // number型で保存 - } - }} - // sx={{ width: "50%" }} - type="number" - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 - /> - - {/* 購入日入力フィールド */} - setNewStock({ ...newStock, buyDate: e.target.value })} - /> - - {/* 賞味・消費期限入力フィールド */} - setNewStock({ ...newStock, expDate: e.target.value })} - /> - - - - - - - - - {/* 在庫の食材編集ボタン(全テーブル共通) */} - + if (filteredStocks.length === 0) return null; - {/* 在庫の食材削除ボタン (全テーブル共通) */} - - - {/* 在庫一覧リスト */} - {/* 乳製品 */} - 乳製品 -
- {StockTable(stocks, ["乳製品"])} -
- - {/* 肉・魚 */} - 魚・肉 -
- {StockTable(stocks, ["魚・肉"])} -
- - {/* 野菜 */} - 野菜 -
- {StockTable(stocks, ["野菜"])} -
- - {/* 調味料 */} - 調味料 -
- {StockTable(stocks, ["調味料"])} -
- - {/* その他 */} - その他 -
- {StockTable(stocks, ["その他"])} -
-
+ return ( + <> + + + + + 食材名 + 数量 + 購入価格 + 購入日 + 消費・賞味期限 + + + + {filteredStocks.map(stock => { + const today = new Date(); + const expDate = new Date(stock.expDate); + const timeDiff = expDate.getTime() - today.getTime(); + const daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); + + return ( + handleRowClick(stock)} + style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }} + > + {stock.stuffName} + {stock.amount} + {stock.price} + {formatDate(stock.buyDate)} + + {formatDate(stock.expDate)} + + + ); + })} + +
+
+ + + {/* 編集ダイアログ */} + + 在庫の編集 + + {editStock && ( + <> + {editStock.stuffName} + + + + + + + + + + )} + + + + {/* 削除ダイアログ */} + + 食材の削除 + + {selectedRow && ( + <> + 【{selectedRow.stuffName}】を削除します。 + ⚠️ 注意: 削除すると復元できません。 + + + + )} + + + ); + }; + + + return ( + + + 在庫一覧 + + + + {/* 在庫の食材追加ボタン */} + + + {/* 新規タスク作成ダイアログ */} + setIsAddOpen(false)} disableScrollLock={true}> + + 在庫に食材を追加 + + } + label="食材を新規追加" + checked={newStock.newAddition} + onChange={(e) => setNewStock({ ...newStock, newAddition: (e.target as HTMLInputElement).checked })} + /> + + + + + {/*材料カテゴリ選択 */} + + + カテゴリ + + + + {!newStock.newAddition && + 材料名(選択) + + } + + {/* タスクタイトル入力フィールド */} + {newStock.newAddition && setNewStock({ ...newStock, stuffName: e.target.value })} + sx={{ marginBottom: 2 }} + />} + {/* 数量入力フィールド */} + { + const value = e.target.value; + const parsedValue = parseInt(value, 10); // 数値に変換 + if (!isNaN(parsedValue)) { + setNewStock({ ...newStock, amount: parsedValue }); // number型で保存 + } + }} + // sx={{ width: "50%" }} + type="number" + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + /> + {/* 購入価格入力フィールド */} + { + const value = e.target.value; + const parsedValue = parseInt(value, 10); // 数値に変換 + if (!isNaN(parsedValue)) { + setNewStock({ ...newStock, price: parsedValue }); // number型で保存 + } + }} + // sx={{ width: "50%" }} + type="number" + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + /> + + {/* 購入日入力フィールド */} + setNewStock({ ...newStock, buyDate: e.target.value })} + /> + + {/* 賞味・消費期限入力フィールド */} + setNewStock({ ...newStock, expDate: e.target.value })} + /> + + + + + + + + + {/* 在庫の食材編集ボタン(全テーブル共通) */} + + + {/* 在庫の食材削除ボタン (全テーブル共通) */} + + + + + {/* 在庫一覧リスト */} + {/* 乳製品 */} + 乳製品 +
+ {StockTable(stocks, ["乳製品"])} +
+ + {/* 肉・魚 */} + 魚・肉 +
+ {StockTable(stocks, ["魚・肉"])} +
+ + {/* 野菜 */} + 野菜 +
+ {StockTable(stocks, ["野菜"])} +
+ + {/* 調味料 */} + 調味料 +
+ {StockTable(stocks, ["調味料"])} +
+ + {/* その他 */} + その他 +
+ {StockTable(stocks, ["その他"])} +
+
+ ); }; export default StockPage; \ No newline at end of file