diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d4d6caf..82bce23 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -93,6 +93,7 @@ const App: React.FC = () => { } /> + }> {/* ルートパスへのアクセスはタスク一覧にリダイレクト */} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index eb3612f..4ac785b 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -22,6 +22,7 @@ import { Menu as MenuIcon, ListAlt as ListAltIcon, Inventory as InventoryIcon, // テストページ用のアイコン + Science as ScienceIcon, // 鈴木 } from '@mui/icons-material'; import { useNavigate, Outlet, useLocation } from 'react-router-dom'; @@ -100,6 +101,8 @@ const Layout: React.FC = () => { {/* テストページへのリンクを追加 */} + + {/* 在庫リストへのリンクを追加 */} handleNavigate('/stock')} selected={isSelected('/stock')} diff --git a/frontend/src/pages/StockPage.tsx b/frontend/src/pages/StockPage.tsx index 8de8bd9..e305d3b 100644 --- a/frontend/src/pages/StockPage.tsx +++ b/frontend/src/pages/StockPage.tsx @@ -2,15 +2,273 @@ * テストページコンポーネント * 白紙の状態で表示されるテスト用のページ */ -import React from 'react'; -import { Box } from '@mui/material'; +import React, { useState, useEffect } from 'react'; +import { stockApi } from '../services/api'; +import { Stock } from '../types/types'; +import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material"; +import { + Container, + Typography, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, + +} from '@mui/material'; +import { TASK_ERRORS } from '../constants/errorMessages'; +import CategoryDropDown from "../components/CategoryDropDown"; const StockPage: React.FC = () => { - return ( - - {/* 白紙のページ - 何も表示しない */} - - ); + + const [stocks, setStocks] = useState([]); + // 在庫編集ダイアログの表示状態 + const [openDialog, setOpenDialog] = useState(false); + + // コンポーネントマウント時にタスク一覧を取得 + useEffect(() => { + fetchStocks(); + }, []); + + /** + * APIからタスク一覧を取得する関数 + * 取得したタスクをstate(tasks)に設定 + */ + const fetchStocks = async () => { + try { + const stocks = await stockApi.getStocks(); + setStocks(stocks.stock_array); + } catch (error) { + console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error); + } + }; + + /** + * 文字列(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}`; + }; + */ + + return ( + + + 在庫一覧 + + + {/* タスク編集ボタン */} + + + {/* タスク削除ボタン */} + + + + 乳製品 + + {/* 乳製品一覧表示エリア - 青い背景のコンテナ */} +
+ + {/* 要素が無かったら表示しない */} + {stocks.filter(stock => stock.category === "乳製品").length === 0 ? null : ( + + + + + 食材名 + 数量 + 購入価格 + 賞味・消費期限 + 購入日 + + + + {stocks + .filter(stock => stock.category == "乳製品") // 乳製品だけ抽出 + .map(stock => ( + + {stock.stuff_name} + {stock.amount} + {stock.price} + {formatDate(stock.exp_date)} + {formatDate(stock.buy_date)} + + ))} + +
+
+ )} +
+ + + 肉・魚 + + {/* 肉・魚一覧表示エリア - 青い背景のコンテナ */} +
+ + {stocks.filter(stock => stock.category == "肉" || stock.category == "魚").length === 0 ? null : ( + + + + + 食材名 + 数量 + 購入価格 + 賞味・消費期限 + 購入日 + + + + {stocks + .filter(stock => stock.category == "肉" || stock.category == "魚") // 肉と魚だけ抽出 + .map(stock => ( + + {stock.stuff_name} + {stock.amount} + {stock.price} + {formatDate(stock.exp_date)} + {formatDate(stock.buy_date)} + + ))} + +
+
+ )} + +
+ + + 野菜 + + {/* 野菜一覧表示エリア - 青い背景のコンテナ */} +
+ {stocks.filter(stock => stock.category === "野菜").length === 0 ? null : ( + + + + + 食材名 + 数量 + 購入価格 + 賞味・消費期限 + 購入日 + + + + {stocks + .filter(stock => stock.category == "野菜") // 野菜だけ抽出 + .map(stock => ( + + {stock.stuff_name} + {stock.amount} + {stock.price} + {formatDate(stock.exp_date)} + {formatDate(stock.buy_date)} + + ))} + +
+
+ )} +
+ + + 調味料 + + {/* 調味料一覧表示エリア - 青い背景のコンテナ */} +
+ {stocks.filter(stock => stock.category === "調味料").length === 0 ? null : ( + + + + + 食材名 + 数量 + 購入価格 + 賞味・消費期限 + 購入日 + + + + {stocks + .filter(stock => stock.category == "調味料") // 調味料だけ抽出 + .map(stock => ( + + {stock.stuff_name} + {stock.amount} + {stock.price} + {formatDate(stock.exp_date)} + {formatDate(stock.buy_date)} + + ))} + +
+
+ )} +
+ + + その他 + + {/* その他一覧表示エリア - 青い背景のコンテナ */} +
+ {stocks.filter(stock => stock.category === "その他").length === 0 ? null : ( + + + + + 食材名 + 数量 + 購入価格 + 賞味・消費期限 + 購入日 + + + + {stocks + .filter(stock => stock.category == "その他") // その他だけ抽出 + .map(stock => ( + + {stock.stuff_name} + {stock.amount} + {stock.price} + {formatDate(stock.exp_date)} + {formatDate(stock.buy_date)} + + ))} + +
+
+ )} +
+
+ ); }; export default StockPage; \ No newline at end of file diff --git a/frontend/src/pages/TaskListPage.tsx b/frontend/src/pages/TaskListPage.tsx index 430d4bc..411b115 100644 --- a/frontend/src/pages/TaskListPage.tsx +++ b/frontend/src/pages/TaskListPage.tsx @@ -3,7 +3,7 @@ * タスクの表示、作成、完了状態の切り替え、削除などの機能を提供 */ import React, { useState, useEffect } from 'react'; -import { toBuyApi } from '../services/api'; +import { toBuyApi, stuffApi } from '../services/api'; import { Container, Typography, @@ -23,13 +23,17 @@ import { Button, Box, FormControlLabel, - FormGroup + FormGroup, + FormControl, + InputLabel, + Select, + MenuItem } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon, SoupKitchen as SoupKitchenIcon } from '@mui/icons-material'; -import { Task, ToBuy } from '../types/types'; +import { Task, ToBuy, Stuff } from '../types/types'; import { TASK_ERRORS } from '../constants/errorMessages'; //import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留 import CategoryDropDown from "../components/CategoryDropDown"; @@ -38,13 +42,13 @@ import CategoryDropDown from "../components/CategoryDropDown"; // 新規タスクの初期状態 -const EMPTY_TASK: Omit & {category: string} & {newAddition: boolean} = { - stuff_id: 0, +const EMPTY_TASK: Omit & { stuff_id: number | null, category: string } & { newAddition: boolean } = { + stuff_id: null, stuff_name: '', amount: 0, shop: '', category: '', - newAddition: false + newAddition: false, } const TaskListPage: React.FC = () => { @@ -52,8 +56,7 @@ const TaskListPage: React.FC = () => { const [tobuys, setToBuys] = useState([]); // 新規タスク作成ダイアログの表示状態 const [openDialog, setOpenDialog] = useState(false); - // 新規タスクの入力内容 - const [newTask, setNewTask] = useState(EMPTY_TASK); + //在庫登録ダイアログの表示状態 const [openInfoDialog, setOpenInfoDialog] = useState(false); @@ -61,6 +64,9 @@ const TaskListPage: React.FC = () => { const [newToBuy, setNewToBuy] = useState(EMPTY_TASK); + const [stuffs, setStuffs] = useState([]); + + // コンポーネントマウント時にタスク一覧を取得 useEffect(() => { fetchTasks(); @@ -79,6 +85,12 @@ const TaskListPage: React.FC = () => { } }; + const onChangeCategory = async (category: string) => { + setNewToBuy({ ...newToBuy, category }) + const result = await stuffApi.getStuffs(category) + setStuffs(result.stuff_array) + } + // /** // * タスクの完了状態を切り替えるハンドラー // * 対象タスクの完了状態を反転させてAPIに更新を要求 @@ -225,22 +237,53 @@ const TaskListPage: React.FC = () => { {/* 新規タスク作成ダイアログ */} setOpenDialog(false)} disableScrollLock={true}> - - 材料の追加 - - } - label="食材を新規追加" - checked={newTask.newAddition} - /> - + + 材料の追加 + + } + label="食材を新規追加" + checked={newToBuy.newAddition} + onChange={(e) => setNewToBuy({ ...newToBuy, newAddition: (e.target as HTMLInputElement).checked })} + /> + {/*材料カテゴリ選択 */} - + + + カテゴリ + + + + {!newToBuy.newAddition && + 材料名(選択) + + } + {/* タスクタイトル入力フィールド */} - { value={newToBuy.stuff_name} onChange={(e) => setNewToBuy({ ...newToBuy, stuff_name: e.target.value })} sx={{ marginBottom: 2 }} - /> + />} {/* 数量入力フィールド */} { setOpenInfoDialog(false) setNewToBuy(EMPTY_TASK); // 入力内容をリセット } - }} + }} variant="contained"> 登録 - + ); }; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 854e67f..5853f7c 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -3,7 +3,7 @@ * バックエンドAPIとの通信を担当するモジュール * 認証、タスク管理などの機能を提供 */ -import { LoginCredentials, RegisterCredentials, AuthResponse, Task, ToBuy } from '../types/types'; +import { LoginCredentials, RegisterCredentials, AuthResponse, Task, ToBuy, Stuff, Stock } from '../types/types'; import { AUTH_ERRORS, TASK_ERRORS } from '../constants/errorMessages'; // APIのベースURL - 環境変数から取得するか、デフォルト値を使用 @@ -89,8 +89,8 @@ export const authApi = { */ export const toBuyApi = { /** - * 全タスクを取得 - * @returns タスク一覧 + * 全買うものリストを取得 + * @returns 買うものリスト一覧 */ getToBuys: async (): Promise<{ "tobuy_array": ToBuy[] }> => { // const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, { @@ -128,7 +128,7 @@ export const toBuyApi = { * @param tobuy 作成する材料情報 * @returns 作成された材料情報 */ - addToBuy: async (tobuy: Omit & { category: string }): Promise => { + addToBuy: async (tobuy: Omit & { stuff_id: number | null, category: string }): Promise => { // const response = await fetch(`${API_BASE_URL}/api/tasks`, { // method: 'POST', // headers: getHeaders(), @@ -171,6 +171,96 @@ export const toBuyApi = { }, } +export const stuffApi = { + getStuffs: async (category: string): Promise<{ stuff_array: Stuff[] }> => { + const data = [ + { "stuff_id": 1, "stuff_name": "牛乳", "category": "乳製品" }, + { "stuff_id": 2, "stuff_name": "ヨーグルト", "category": "乳製品" }, + { "stuff_id": 3, "stuff_name": "チーズ", "category": "乳製品" }, + { "stuff_id": 4, "stuff_name": "バター", "category": "乳製品" }, + { "stuff_id": 5, "stuff_name": "生クリーム", "category": "乳製品" }, + + { "stuff_id": 6, "stuff_name": "鮭", "category": "魚・肉" }, + { "stuff_id": 7, "stuff_name": "鶏むね肉", "category": "魚・肉" }, + { "stuff_id": 8, "stuff_name": "豚バラ肉", "category": "魚・肉" }, + { "stuff_id": 9, "stuff_name": "牛ひき肉", "category": "魚・肉" }, + { "stuff_id": 10, "stuff_name": "まぐろ", "category": "魚・肉" }, + + { "stuff_id": 11, "stuff_name": "にんじん", "category": "野菜" }, + { "stuff_id": 12, "stuff_name": "キャベツ", "category": "野菜" }, + { "stuff_id": 13, "stuff_name": "ほうれん草", "category": "野菜" }, + { "stuff_id": 14, "stuff_name": "玉ねぎ", "category": "野菜" }, + { "stuff_id": 15, "stuff_name": "ピーマン", "category": "野菜" }, + + { "stuff_id": 16, "stuff_name": "醤油", "category": "調味料" }, + { "stuff_id": 17, "stuff_name": "味噌", "category": "調味料" }, + { "stuff_id": 18, "stuff_name": "塩", "category": "調味料" }, + { "stuff_id": 19, "stuff_name": "砂糖", "category": "調味料" }, + { "stuff_id": 20, "stuff_name": "酢", "category": "調味料" }, + + { "stuff_id": 21, "stuff_name": "米", "category": "その他" }, + { "stuff_id": 22, "stuff_name": "パスタ", "category": "その他" }, + { "stuff_id": 23, "stuff_name": "小麦粉", "category": "その他" }, + { "stuff_id": 24, "stuff_name": "卵", "category": "その他" }, + { "stuff_id": 25, "stuff_name": "豆腐", "category": "その他" } + ] + + const filtered = data.filter(stuff => stuff.category == category) + + return { + "stuff_array": filtered + } + } +} + + +export const stockApi = { + /** + * 全在庫リストを取得 + * @returns 買在庫リスト一覧 + */ + getStocks: async (): Promise<{ "stock_array": Stock[] }> => { + // 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(); + + return { + "stock_array": [ + { + "stock_id": 1, + "stuff_id": 10, + "stuff_name": "豚肉", + "amount": 100, + "price": 200, + "buy_date": "2025-05-18T09:00:00.000Z", + "last_update": "2025-05-18T09:00:00.000Z", + "exp_date": "2025-05-19T10:15:00.000Z", + "category": "肉" + }, + { + "stock_id": 2, + "stuff_id": 1, + "stuff_name": "トマト", + "amount": 10, + "price": 200, + "buy_date": "2025-05-18T09:00:00.000Z", + "last_update": "2025-05-18T09:00:00.000Z", + "exp_date": "2025-05-19T10:15:00.000Z", + "category": "野菜" + } + ] + } + + + }, +} + /** * (サンプル,実際には不要) @@ -216,7 +306,7 @@ export const taskApi = { * @param task 作成するタスク情報(価格,作成日時、更新日時は除外) * @returns 作成されたタスク情報 */ - addStuff: async (task: Omit): Promise => { + addStuff: async (task: Omit): Promise => { const response = await fetch(`${API_BASE_URL}/api/tubuy/add`, { method: 'POST', headers: getHeaders(), diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 42b1cfe..76c645d 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -29,6 +29,28 @@ export interface ToBuy { shop?: string, } +export interface Stuff { + stuff_id: number, + stuff_name: string, + category: string, +} + +/** + * タスク情報を表す型定義 + * タスクの基本情報と状態を管理 + */ +export interface Stock { + stock_id: number, + stuff_id: number, + stuff_name: string, + amount: number, + price: number, + buy_date: string, + last_update: string, + exp_date: string, + category: string, +} + /** * ユーザー情報を表す型定義 * ユーザーの基本情報を管理