From 51886f37303c70808eb7e878c5c263d9d1338e4c Mon Sep 17 00:00:00 2001 From: "Masaharu.Kato" Date: Wed, 18 Jun 2025 17:36:23 +0900 Subject: [PATCH] =?UTF-8?q?=E5=85=A8=E4=BD=93=E7=9A=84=E3=81=AA=E3=83=AC?= =?UTF-8?q?=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88=E3=82=92=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.css | 40 +++ frontend/src/App.tsx | 2 +- frontend/src/components/Layout.tsx | 2 +- frontend/src/pages/AddRecipe.tsx | 19 +- frontend/src/pages/RecipeList.tsx | 62 ++-- frontend/src/pages/StockPage.tsx | 455 +++++++++++++--------------- frontend/src/pages/TaskListPage.tsx | 41 ++- 7 files changed, 312 insertions(+), 309 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index e8ad58a..868ed28 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -31,3 +31,43 @@ body { .MuiPaper-root { background-color: white; /* 白背景を確保 */ } + +.mainContainer { + padding-top: 0; + padding-left: 0; + padding-right: 0; + padding-bottom: 80px; /* ボトムナビゲーションバーで隠れる分 */ +} + +.mainTitle { + padding-left: 1rem; + font-size: 2rem; + margin-bottom: 0.25rem; +} + +.listWrapper { + background: #ebcba2; + margin: 0; + padding: 1rem; +} + +.plusButtonWrapper { + position: fixed !important; + text-align: center; + bottom: 80px; + left: 80%; + transform: translateX(-50%); +} + +.plusButtonLabel { + font-size: 0.9rem; + color: #000; + background: rgba(255, 255, 255, 0.8); + padding: 0.5rem; + border-radius: 4px; + white-space: nowrap; +} + +.plusButton { + /* box-shadow: 0px 4px 15px 5px #ebcba2 !important; */ +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4d12e10..4b2ec32 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,7 +29,7 @@ const theme = createTheme({ palette: { mode: 'light', primary: { - main: '#1976d2', + main: '#d27619', }, secondary: { main: '#dc004e', diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7e1c73f..74872cb 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -167,7 +167,7 @@ const Layout: React.FC = () => { {/* メインコンテンツ領域 - 子ルートのコンポーネントがここに表示される */} - + { }, []); return ( - <> +
{(recipeId && !recipeLoaded) ?

読み込み中...

: - + <> +
+ + {!recipeId ? '料理の追加' : '料理の編集'} +
+
-

- - {!recipeId ? '料理の追加' : '料理の編集'} -

setRecipeName(e.target.value)} /> @@ -381,9 +383,10 @@ const AddRecipe: React.FC = () => { - +
+ } - +
); }; diff --git a/frontend/src/pages/RecipeList.tsx b/frontend/src/pages/RecipeList.tsx index 66d91e8..e2efffb 100644 --- a/frontend/src/pages/RecipeList.tsx +++ b/frontend/src/pages/RecipeList.tsx @@ -26,6 +26,7 @@ import { FormControl, InputLabel } from '@mui/material'; +import '../App.css'; import { Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon, SoupKitchen as SoupKitchenIcon @@ -46,7 +47,7 @@ const RecipeList: React.FC = () => { const openRecipeById = (recipeId: number) => { navigate('/addRecipe/' + recipeId); } - + const fetchAllRecipes = async () => { try { const recipes = await recipeApi.getAllRecipes(); @@ -63,54 +64,45 @@ const RecipeList: React.FC = () => { }, []); return ( - - -
-

料理リスト

-
-
- {/* */} +
+
+ + レシピリスト +
+
+ {/* 料理一覧をマップして各タスクをリストアイテムとして表示 */} {!allRecipes - ?

読み込み中...

- : allRecipes.map((recipe, index) => ( - - - - // - ))} - {/*
*/} -
-
- -
+ + ))} + +
+ + + navigate('/addRecipe')} className="plusButton"> + + + {/* + 新しいレシピの追加 + */} - +
); }; diff --git a/frontend/src/pages/StockPage.tsx b/frontend/src/pages/StockPage.tsx index 013cef6..a5c37de 100644 --- a/frontend/src/pages/StockPage.tsx +++ b/frontend/src/pages/StockPage.tsx @@ -26,8 +26,9 @@ import { Select, MenuItem, } from '@mui/material'; +import '../App.css'; import { GENERAL_ERRORS, STOCK_ERRORS } from '../constants/errorMessages'; -import { Add as AddIcon } from '@mui/icons-material'; +import { Add as AddIcon, KeyboardArrowDown as ArrowDownIcon, KeyboardArrowUp as ArrowUpIcon, Inventory as InventoryIcon } from '@mui/icons-material'; import DatePicker, { registerLocale } from 'react-datepicker'; import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート import { useMessage } from '../components/MessageContext'; @@ -79,6 +80,19 @@ const StockPage: React.FC = () => { const { showErrorMessage, showWarningMessage } = useMessage(); + // カテゴリ名一覧 + const CATEGORY_NAMES = [ + "乳製品", + "魚・肉", + "野菜", + "調味料", + "その他", + ]; + + const [openCategory, setOpenCategory] = useState(Object.fromEntries( + CATEGORY_NAMES.map(category => [category, true]) + )); + // コンポーネントマウント時にタスク一覧を取得 useEffect(() => { fetchStocks(); @@ -209,15 +223,6 @@ const StockPage: React.FC = () => { }; */ - /** 追加ボタンを押したときにダイアログを開く */ - const handleOpenAdd = () => { - setIsAddOpen(true); - }; - /** 追加ダイアログを閉じる */ - const handleCloseAdd = () => { - setIsAddOpen(false); - }; - /** * セルを選択して編集画面 */ @@ -358,8 +363,8 @@ const StockPage: React.FC = () => { return ( <> - - +
+ 食材名 数量 @@ -588,244 +593,208 @@ const StockPage: React.FC = () => { }; return ( - - +
+
+ 在庫リスト - - - - - {/* 在庫の食材追加ボタン */} - - - 食材の追加 - - - - +
+ + {/* 新規タスク作成ダイアログ */} + setIsAddOpen(false)} disableScrollLock={true}> + + + + 在庫に食材を追加 + + + + } + label="食材を新規追加" + checked={newStock.newAddition} + onChange={(e) => setNewStock({ ...newStock, newAddition: (e.target as HTMLInputElement).checked })} + /> + - {/* 新規タスク作成ダイアログ */} - - - - - 在庫に食材を追加 - - - - } - 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) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK - 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) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK - setNewStock({ ...newStock, buyAmount: 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) || parsedValue >= 0) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は0以上のみOK - setNewStock({ ...newStock, price: parsedValue }); // number型で保存 - } - }} - // sx={{ width: "50%" }} - type="number" - inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + + + {/*材料カテゴリ選択 */} + + + カテゴリ + + + + {!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) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK + 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) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK + setNewStock({ ...newStock, buyAmount: 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) || parsedValue >= 0) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は0以上のみOK + setNewStock({ ...newStock, price: parsedValue }); // number型で保存 + } + }} + // sx={{ width: "50%" }} + type="number" + inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可 + /> + {/* 購入店舗入力フィールド */} + setNewStock({...newStock, shop: e.target.value})} + /> + {/* 購入日・消費期限を横並びに */} + + {/* 購入日入力フィールド */} + + setNewStock({ ...newStock, buyDate: date ? formatDateLocal(date) : '' }) + } + dateFormat="yyyy/MM/dd" + customInput={ + + } + isClearable + //withPortal // ← 他の文字との重なり対策 /> - {/* 購入店舗入力フィールド */} - setNewStock({...newStock, shop: e.target.value})} + {/* 消費・賞味期限入力フィールド */} + + setNewStock({ ...newStock, expDate: date ? formatDateLocal(date) : '' }) + } + dateFormat="yyyy/MM/dd" + customInput={ + + } + isClearable + //withPortal /> - {/* 購入日・消費期限を横並びに */} - - {/* 購入日入力フィールド */} - - setNewStock({ ...newStock, buyDate: date ? formatDateLocal(date) : '' }) - } - dateFormat="yyyy/MM/dd" - customInput={ - - } - isClearable - //withPortal // ← 他の文字との重なり対策 - /> - {/* 消費・賞味期限入力フィールド */} - - setNewStock({ ...newStock, expDate: date ? formatDateLocal(date) : '' }) - } - dateFormat="yyyy/MM/dd" - customInput={ - - } - isClearable - //withPortal - /> - - - - - - - + + + + + + + + + {/* 各カテゴリを表示 */} + {CATEGORY_NAMES.map(category => { + return ( + + setOpenCategory({...openCategory, [category]: !openCategory[category]})} + > + {!openCategory[category] ? : } + {category} + + {openCategory[category] && StockTable(stocks, [category])} + + ) + })} + + {/* 材料ボタン - 画面下部に固定表示 */} + + setIsAddOpen(true)} className="plusButton"> + + + {/* + 材料の追加 + */} - - {/* 在庫一覧リスト */} - {/* 乳製品 */} - 乳製品 -
- {StockTable(stocks, ["乳製品"])} -
- - {/* 肉・魚 */} - 魚・肉 -
- {StockTable(stocks, ["魚・肉"])} -
- - {/* 野菜 */} - 野菜 -
- {StockTable(stocks, ["野菜"])} -
- - {/* 調味料 */} - 調味料 -
- {StockTable(stocks, ["調味料"])} -
- - {/* その他 */} - その他 -
- {StockTable(stocks, ["その他"])} -
- {/* フッターの高さと同じくらいに調整 */} - + +
); }; diff --git a/frontend/src/pages/TaskListPage.tsx b/frontend/src/pages/TaskListPage.tsx index 5d77b5a..02a5eec 100644 --- a/frontend/src/pages/TaskListPage.tsx +++ b/frontend/src/pages/TaskListPage.tsx @@ -22,8 +22,9 @@ import { } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon, - SoupKitchen as SoupKitchenIcon, Edit as EditIcon + SoupKitchen as SoupKitchenIcon, Edit as EditIcon, ShoppingCart as ShoppingCartIcon } from '@mui/icons-material'; +import '../App.css'; import { ToBuy, Stuff, NewToBuy, NewStock, StuffAndCategoryAndAmount, StuffNameAndAmount, StockHistory, /*Stock*/ } from '../types/types'; import { GENERAL_ERRORS, TOBUY_ERRORS, STUFF_HISTORY_ERRORS } from '../constants/errorMessages'; import { TOBUY_MESSAGES } from '../constants/normalMessages'; @@ -160,7 +161,7 @@ const TaskListPage: React.FC = () => { } newToBuy.stuffName = newToBuy.stuffName.trim(); // 材料名の前後の空白を削除 - + if (!newToBuy.stuffName) { showErrorMessage(GENERAL_ERRORS.INVALID_STUFF_NAME); return; @@ -233,7 +234,7 @@ const TaskListPage: React.FC = () => { } }; - + //履歴表示ダイアログ const [openHistoryDialog, setOpenHistoryDialog] = useState(false); const [historyTobuy, setHistoryTobuy] = useState(null); @@ -251,19 +252,20 @@ const TaskListPage: React.FC = () => { } return ( - - +
+
+ 買うものリスト - - {/* タスク一覧表示エリア - 青い背景のコンテナ */} -
+
+ {/* 買うものリスト表示エリア - 背景色のコンテナ */} +
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */} {tobuys && tobuys.map((tobuy) => ( { ))}
- {/* 新規材料作成ボタン - 画面下部に固定表示 */} - - + + {/* 材料追加ボタン - 画面下部に固定表示 */} + + setOpenAddToBuyDialog(true)} className="plusButton"> + + + {/* 材料の追加 - + */} - setOpenAddToBuyDialog(true)} - > - - {/*新規料理追加ボタン - 画面下部に固定表示 */} {/* @@ -413,7 +412,7 @@ const TaskListPage: React.FC = () => { } - +
); };