diff --git a/frontend/src/components/AddStuffAmountDialog.tsx b/frontend/src/components/AddStuffAmountDialog.tsx new file mode 100644 index 0000000..2f2b304 --- /dev/null +++ b/frontend/src/components/AddStuffAmountDialog.tsx @@ -0,0 +1,134 @@ +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, + InputLabel, + FormGroup, + FormControl, + FormControlLabel, + Checkbox, + Select, + MenuItem, +} from '@mui/material'; +import { NewToBuy, Stuff } from '../types/types'; +import { stuffApi } from '../services/api'; + + + +const AddStuffAmountDialog = ({ + openDialog, + setOpenDialog, + newToBuy, + setNewToBuy, + onSubmit, +}: { + openDialog: boolean, + setOpenDialog: (open: boolean) => void, + newToBuy: NewToBuy, + setNewToBuy: (tobuy: NewToBuy) => void, + onSubmit: () => void, +}) => { + + const onChangeCategory = async (category: string) => { + setNewToBuy({ ...newToBuy, category }) + const result = await stuffApi.getStuffs(category) + setStuffs(result) + } + + const [stuffs, setStuffs] = useState([]); + + return ( + + setOpenDialog(false)} disableScrollLock={true}> + + 材料の追加 + + } + label="食材を新規追加" + checked={newToBuy.newAddition} + onChange={(e) => setNewToBuy({ ...newToBuy, newAddition: (e.target as HTMLInputElement).checked })} + /> + + + + + {/*材料カテゴリ選択 */} + + + カテゴリ + + + + {!newToBuy.newAddition && + 材料名(選択) + + } + + {/* タスクタイトル入力フィールド */} + {newToBuy.newAddition && setNewToBuy({ ...newToBuy, stuffName: e.target.value })} + sx={{ marginBottom: 2 }} + />} + {/* 数量入力フィールド */} + { + const value = e.target.value; + const parsedValue = parseInt(value, 10); // 数値に変換 + if (/*!isNaN(parsedValue) && */ isNaN(parsedValue) || parsedValue >= 1) { //負数除外 + setNewToBuy({ ...newToBuy, amount: parsedValue }); // number型で保存 + } + }} + sx={{ width: "20%" }} + type="number" + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + + /> + + + + + + + + + ) +} + +export default AddStuffAmountDialog; \ No newline at end of file diff --git a/frontend/src/components/BuyDialog.tsx b/frontend/src/components/BuyDialog.tsx new file mode 100644 index 0000000..f76b0dd --- /dev/null +++ b/frontend/src/components/BuyDialog.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, +} from '@mui/material'; +import { NewStock } from '../types/types'; + + +const BuyDialog = ({ + openDialog, + setOpenDialog, + newStock, + setNewStock, + onSubmit, +}: { + openDialog: boolean, + setOpenDialog: (open: boolean) => void, + newStock: NewStock, + setNewStock: (tobuy: NewStock) => void, + onSubmit: () => void, +}) => { + + return ( + + + setOpenDialog(false)} disableScrollLock={true}> + 在庫登録 + + + {/* 価格入力フィールド */} + { + const value = e.target.value; + if (/^\d*$/.test(value)) { + setNewStock({ ...newStock, price: value }) + }; + }} + /> + {/* 購入日入力フィールド */} + setNewStock({ ...newStock, buyDate: e.target.value })} + /> + {/* 消費・賞味期限入力フィールド */} + setNewStock({ ...newStock, expDate: e.target.value })} + /> + {/* 購入店舗入力フィールド */} + {/* TODO: 実装 */} + + + + + + + + + + ) +} + +export default BuyDialog; \ No newline at end of file diff --git a/frontend/src/components/EditAmountDialog.tsx b/frontend/src/components/EditAmountDialog.tsx new file mode 100644 index 0000000..dbbd823 --- /dev/null +++ b/frontend/src/components/EditAmountDialog.tsx @@ -0,0 +1,76 @@ +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, +} from '@mui/material'; +import { ToBuy } from '../types/types'; + +const EditAmountDialog = ({ + openDialog, + setOpenDialog, + editingTobuy, + setEditingTobuy, + onSubmit +}: { + openDialog: boolean, + setOpenDialog: (open: boolean) => void, + editingTobuy: ToBuy, + setEditingTobuy: (tobuy: ToBuy) => void, + onSubmit: () => void, +}) => { + + return ( + + setOpenDialog(false)} disableScrollLock={true}> + + 数量の変更 + + + + + {/* 材料名表示 */} + + {/* 数量入力フィールド */} + { + const value = e.target.value; + const parsedValue = parseInt(value, 10); // 数値に変換 + if (/*!isNaN(parsedValue) && */ isNaN(parsedValue) || parsedValue >= 1) { //負数除外 + setEditingTobuy({ ...editingTobuy, amount: parsedValue }); // number型で保存 + } + }} + sx={{ width: "20%" }} + type="number" + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + + /> + + + + + + + + + ) +} + +export default EditAmountDialog; diff --git a/frontend/src/pages/TaskListPage.tsx b/frontend/src/pages/TaskListPage.tsx index d8ac976..0f2fbee 100644 --- a/frontend/src/pages/TaskListPage.tsx +++ b/frontend/src/pages/TaskListPage.tsx @@ -3,8 +3,7 @@ * タスクの表示、作成、完了状態の切り替え、削除などの機能を提供 */ import React, { useState, useEffect } from 'react'; -import { toBuyApi, stuffApi } from '../services/api'; -import { useNavigate, Outlet, useLocation } from 'react-router-dom'; +import { toBuyApi } from '../services/api'; import { Container, Typography, @@ -14,34 +13,24 @@ import { ListItemText, ListItemSecondaryAction, IconButton, - Checkbox, Fab, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - TextField, - Button, Box, - FormControlLabel, - FormGroup, - FormControl, - InputLabel, - Select, - MenuItem } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon, SoupKitchen as SoupKitchenIcon, Edit as EditIcon } from '@mui/icons-material'; -import { ToBuy, Stuff, /*Stock*/ } from '../types/types'; +import { ToBuy, Stuff, NewToBuy, NewStock, /*Stock*/ } from '../types/types'; import { TOBUY_ERRORS } from '../constants/errorMessages'; +import EditAmountDialog from '../components/EditAmountDialog'; +import AddStuffAmountDialog from '../components/AddStuffAmountDialog'; +import BuyDialog from '../components/BuyDialog'; //import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留 // 新規タスクの初期状態 -const EMPTY_TOBUY: Omit & { stuffId: number | null, category: string } & { newAddition: boolean } = { +const EMPTY_TOBUY: NewToBuy = { stuffId: null, stuffName: '', amount: 1, @@ -50,41 +39,37 @@ const EMPTY_TOBUY: Omit & { stuffId: number | null newAddition: false, } -const EMPTY_STOCK = { - price: 0, - buyDate: '', - expDate: '', -} - const TaskListPage: React.FC = () => { // タスク一覧の状態管理 const [tobuys, setToBuys] = useState([]); - // 新規タスク作成ダイアログの表示状態 - const [openDialog, setOpenDialog] = useState(false); + + // 買うものリストへの食材追加ダイアログの表示状態 + const [openAddToBuyDialog, setOpenAddToBuyDialog] = useState(false); //在庫登録ダイアログの表示状態 const [openInfoDialog, setOpenInfoDialog] = useState(false); + //数量変更ダイアログの表示状態 const [openAmountDialog, setOpenAmountDialog] = useState(false); - const [selectedTask, setSelectedTask] = useState(0); - - const [selectedEditingTask, setSelectedEditingTask] = useState({ - tobuyId: 0, - stuffId: 0, - stuffName: "", - amount: 0, - shop: undefined, -}); - + const [selectedToBuyId, setSelectedToBuyId] = useState(0); + + const [newStock, setNewStock] = useState({ + price: '', // ここではstring + buyDate: '', + expDate: '', + }); + + const [editingTask, setEditingTask] = useState({ + tobuyId: 0, + stuffId: 0, + stuffName: "", + amount: 0, + shop: undefined, + }); const [newToBuy, setNewToBuy] = useState(EMPTY_TOBUY); - const [stuffs, setStuffs] = useState([]); - - const [newStock, setNewStock] = useState(EMPTY_STOCK); - - const [newPrice, setNewPrice] = useState("");//入力用の価格(文字列) // コンポーネントマウント時にタスク一覧を取得 @@ -106,12 +91,6 @@ const TaskListPage: React.FC = () => { } }; - const onChangeCategory = async (category: string) => { - setNewToBuy({ ...newToBuy, category }) - const result = await stuffApi.getStuffs(category) - setStuffs(result) - } - // /** // * タスクの完了状態を切り替えるハンドラー // * 対象タスクの完了状態を反転させてAPIに更新を要求 @@ -129,10 +108,10 @@ const TaskListPage: React.FC = () => { // }; /** - * タスクを削除するハンドラー + * 買うものを削除するハンドラー * 指定されたIDのタスクをAPIを通じて削除 */ - const handleDeleteTask = async (toBuyId: number) => { + const handleDeleteToBuy = async (toBuyId: number) => { try { await toBuyApi.deleteToBuy(toBuyId); fetchTasks(); // 削除後の買うもの一覧を再取得 @@ -144,20 +123,18 @@ const TaskListPage: React.FC = () => { /** * 買うものリストの在庫登録(購入処理)を行うハンドラー */ - const handleBuy = async (tobuyId: number) => { + const handleBuy = async () => { try { const today = new Date().toISOString().substring(0, 10); - const parsedPrice = parseInt(newPrice, 10); - console.log("newPrice") - console.log(newPrice) - console.log("parsedPrice") - console.log(parsedPrice) + const parsedPrice = parseInt(newStock.price, 10); + console.log("newPrice:", newStock.price) + console.log("parsedPrice: ", parsedPrice) if (isNaN(parsedPrice)) { alert('入力が無効です') return //setNewStock({ ...newStock, price: parsedPrice }); } - await toBuyApi.buy({ tobuyId, ...newStock, price: parsedPrice, lastUpdate: today }); //データベースに送信 + await toBuyApi.buy({ tobuyId: selectedToBuyId, ...newStock, price: parsedPrice, lastUpdate: today }); //データベースに送信 fetchTasks(); // 削除後の買うもの一覧を再取得 } catch (error) { console.error(`${TOBUY_ERRORS.BUY_FAILED}:`, error); @@ -165,11 +142,11 @@ const TaskListPage: React.FC = () => { }; /** - * 新規タスクを作成するハンドラー - * 入力されたタスク情報をAPIに送信して新規作成 + * 買うものリストへの材料追加を行う + * 入力された買うもの情報をAPIに送信して新規作成 * 作成後はダイアログを閉じ、入力内容をリセット */ - const handleCreateTask = async () => { + const handleAddNewToBuy = async () => { try { if (newToBuy.newAddition) { newToBuy.stuffId = null; @@ -181,7 +158,7 @@ const TaskListPage: React.FC = () => { console.log(newToBuy) await toBuyApi.addToBuy(newToBuy); - setOpenDialog(false); // ダイアログを閉じる + setOpenAddToBuyDialog(false); // ダイアログを閉じる setNewToBuy(EMPTY_TOBUY); // 入力内容をリセット fetchTasks(); // 作成後のタスク一覧を再取得 } catch (error) { @@ -194,15 +171,15 @@ const TaskListPage: React.FC = () => { * 入力されたタスク情報をAPIに送信して変更 * 作成後はダイアログを閉じ、入力内容をリセット */ - const handleUpdateTask = async () => { + const handleUpdateNewToBuy = async () => { try { - if (isNaN(selectedEditingTask.amount)) { + if (isNaN(editingTask.amount)) { console.log('数量が正しくありません.'); return; } - console.log(selectedEditingTask) - await toBuyApi.updateToBuy(selectedEditingTask); + console.log(editingTask) + await toBuyApi.updateToBuy(editingTask); setOpenAmountDialog(false); // ダイアログを閉じる //setNewToBuy(EMPTY_TOBUY); // 入力内容をリセット fetchTasks(); // 作成後のタスク一覧を再取得 @@ -269,7 +246,7 @@ const TaskListPage: React.FC = () => { aria-label="数量変更" onClick={() => { setOpenAmountDialog(true) - setSelectedEditingTask(tobuy) + setEditingTask(tobuy) }} > @@ -284,7 +261,7 @@ const TaskListPage: React.FC = () => { aria-label="購入情報を記入" onClick={() => { setOpenInfoDialog(true) - setSelectedTask(tobuy.tobuyId) + setSelectedToBuyId(tobuy.tobuyId) // handleDeleteTask(tobuy.tobuyId) }} > @@ -311,7 +288,7 @@ const TaskListPage: React.FC = () => { edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete" - onClick={() => handleDeleteTask(tobuy.tobuyId)} + onClick={() => handleDeleteToBuy(tobuy.tobuyId)} > @@ -331,7 +308,7 @@ const TaskListPage: React.FC = () => { setOpenDialog(true)} + onClick={() => setOpenAddToBuyDialog(true)} > @@ -346,7 +323,7 @@ const TaskListPage: React.FC = () => { color="primary" sx={{ position: 'fixed', bottom: 16, left: '60%', transform: 'translateX(-50%)' }} onClick={() => { - setOpenDialog(true); + setOpenAddToBuyDialog(true); //handleNavigate('/AddDishies1'); }} //selected={isSelected('/test')} @@ -356,195 +333,14 @@ const TaskListPage: React.FC = () => { {/* 新規タスク作成ダイアログ */} - setOpenDialog(false)} disableScrollLock={true}> - - 材料の追加 - - } - label="食材を新規追加" - checked={newToBuy.newAddition} - onChange={(e) => setNewToBuy({ ...newToBuy, newAddition: (e.target as HTMLInputElement).checked })} - /> - - - - - {/*材料カテゴリ選択 */} - - - カテゴリ - - - - {!newToBuy.newAddition && - 材料名(選択) - - } - - {/* タスクタイトル入力フィールド */} - {newToBuy.newAddition && setNewToBuy({ ...newToBuy, stuffName: e.target.value })} - sx={{ marginBottom: 2 }} - />} - {/* 数量入力フィールド */} - { - const value = e.target.value; - const parsedValue = parseInt(value, 10); // 数値に変換 - if (/*!isNaN(parsedValue) && */ isNaN(parsedValue) || parsedValue >= 1) { //負数除外 - setNewToBuy({ ...newToBuy, amount: parsedValue }); // number型で保存 - } - }} - sx={{ width: "20%" }} - type="number" - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 - - /> - - - - - - - + {/*在庫登録のための数値入力ダイアログ */} - setOpenInfoDialog(false)} disableScrollLock={true}> - 在庫登録 - - - {/* 価格入力フィールド */} - { - const value = e.target.value; - if (/^\d*$/.test(value)) { - setNewPrice(value) - }; - }} - /> - {/* 購入日入力フィールド */} - setNewStock({ ...newStock, buyDate: e.target.value })} - /> - {/* 消費・賞味期限入力フィールド */} - setNewStock({ ...newStock, expDate: e.target.value })} - /> - {/* 購入店舗入力フィールド */} - setNewToBuy({ ...newToBuy, shop: e.target.value })} - /> - - - - - - - + {/* 数量変更ダイアログ */} - setOpenAmountDialog(false)} disableScrollLock={true}> - - 数量の変更 - - - - - {/* 材料名表示 */} - - {/* 数量入力フィールド */} - { - const value = e.target.value; - const parsedValue = parseInt(value, 10); // 数値に変換 - if (/*!isNaN(parsedValue) && */ isNaN(parsedValue) || parsedValue >= 1) { //負数除外 - setSelectedEditingTask({ ...selectedEditingTask, amount: parsedValue }); // number型で保存 - } - }} - sx={{ width: "20%" }} - type="number" - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 - - /> - - - - - - - + + ); diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 177c719..04998bb 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -18,8 +18,8 @@ // } /** - * タスク情報を表す型定義 - * タスクの基本情報と状態を管理 + * 買うもの情報を表す型定義 + * 買うものの基本情報と状態を管理 */ export interface ToBuy { tobuyId: number, @@ -29,6 +29,18 @@ export interface ToBuy { shop?: string, } +/** + * 新規追加時用の買うもの情報 + */ +export interface NewToBuy { + stuffId: null | number, + stuffName: string, + amount: number, + shop: string, + category: string, + newAddition: boolean, +} + export interface Stuff { stuffId: number, stuffName: string, @@ -51,6 +63,15 @@ export interface Stock { category: string, } +/** + * 購入処理用の在庫情報 + */ +export interface NewStock { + price: string, + buyDate: string, + expDate: string, +} + /** * ユーザー情報を表す型定義 * ユーザーの基本情報を管理 @@ -94,9 +115,9 @@ export interface RegisterCredentials { export interface Recipes { recipeName: string;// レシピ名 summary: string;// レシピ概要 - + // 材料リスト(直接配列として内包) - stuffAndAmountArray : Array<{ + stuffAndAmountArray: Array<{ // 既存材料IDまたは新規作成情報のどちらか一方のみ必要 stuffId?: number; // 既存材料ID(オプション) stuffName?: string;// 新規材料名(オプション)