Merge remote-tracking branch 'origin/release/sprint1' into feature-backend-tobuy

feature-backend-tobuy-stock-2
Amagasu 9 months ago
commit c993525931
  1. 1
      frontend/src/App.tsx
  2. 3
      frontend/src/components/Layout.tsx
  3. 310
      frontend/src/pages/StockPage.tsx
  4. 19
      frontend/src/pages/TaskListPage.tsx
  5. 54
      frontend/src/services/api.ts
  6. 16
      frontend/src/types/types.ts

@ -93,6 +93,7 @@ const App: React.FC = () => {
</PrivateRoute>
}
/>
</Route>
<Route path="/" element={<Layout />}>
{/* ルートパスへのアクセスはタスク一覧にリダイレクト */}

@ -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 = () => {
<ListItemText primary="タスク一覧" />
</ListItemButton>
{/* テストページへのリンクを追加 */}
{/* 在庫リストへのリンクを追加 */}
<ListItemButton
onClick={() => handleNavigate('/stock')}
selected={isSelected('/stock')}

@ -2,14 +2,314 @@
*
*
*/
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,
Tooltip,
Fab,
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 = () => {
const [stocks, setStocks] = useState<Stock[]>([]);
// 削除メッセージダイアログの表示状態
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
// セル選択の表示状態
const [selectedRow, setSelectedRow] = useState<number | null>(null);
// コンポーネントマウント時にタスク一覧を取得
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}`;
};
/* Dateyyyy/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 handleRowClick = (id: number) => {
setSelectedRow(prevSelected => (prevSelected === id ? null : id)); // クリックで選択・解除を切り替え
};
return (
<Box>
{/* 白紙のページ - 何も表示しない */}
</Box>
<Container>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* タスク編集ボタン */}
<Button color="success"
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2, left: '80%' }}
>
</Button>
{/* タスク削除ボタン */}
{/* <Button color="error"
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2, left: '82%' }}
onClick={() => setOpenDialog(true)}
>
</Button> */}
<Button variant="contained" color="error" onClick={handleOpen} sx={{ mt: 3, mb: 2, left: '82%' }}></Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle></DialogTitle>
<DialogContent>
<Typography variant="body1"></Typography>
<Typography variant="body2" color="error"> 注意: 削除すると復元できません</Typography>
<Button variant="contained" color="error" onClick={handleClose} style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '85%' }}></Button>
</DialogContent>
</Dialog>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 乳製品一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{/* 要素が無かったら表示しない */}
{stocks.filter(stock => stock.category === "乳製品").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "乳製品") // 乳製品だけ抽出
.map(stock => (
<TableRow
key={stock.stock_id}
onClick={() => handleRowClick(stock.stock_id)}
style={{ backgroundColor: selectedRow === stock.stock_id ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 肉・魚一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category == "肉" || stock.category == "魚").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "肉" || stock.category == "魚") // 肉と魚だけ抽出
.map(stock => (
<TableRow
key={stock.stock_id}
onClick={() => handleRowClick(stock.stock_id)}
style={{ backgroundColor: selectedRow === stock.stock_id ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 野菜一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "野菜").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "野菜") // 野菜だけ抽出
.map(stock => (
<TableRow
key={stock.stock_id}
onClick={() => handleRowClick(stock.stock_id)}
style={{ backgroundColor: selectedRow === stock.stock_id ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
調
</Typography>
{/* 調味料一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "調味料").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "調味料") // 調味料だけ抽出
.map(stock => (
<TableRow
key={stock.stock_id}
onClick={() => handleRowClick(stock.stock_id)}
style={{ backgroundColor: selectedRow === stock.stock_id ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* その他一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "その他").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "その他") // その他だけ抽出
.map(stock => (
<TableRow
key={stock.stock_id}
onClick={() => handleRowClick(stock.stock_id)}
style={{ backgroundColor: selectedRow === stock.stock_id ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
</Container>
);
};

@ -60,8 +60,7 @@ const TaskListPage: React.FC = () => {
//在庫登録ダイアログの表示状態
const [openInfoDialog, setOpenInfoDialog] = useState(false);
const [selectedTask, setSelectedTask] = useState<ToBuy>();
const [selectedTask, setSelectedTask] = useState<ToBuy["tobuy_id"]>(0);
const [newToBuy, setNewToBuy] = useState(EMPTY_TASK);
@ -165,8 +164,8 @@ const TaskListPage: React.FC = () => {
*/}
{/* タスクのタイトルと説明 - 完了状態に応じて取り消し線を表示 */}
<ListItemText
primary={tobuy.stuff_name}
secondary={tobuy.amount}
primary={`${tobuy.stuff_name} × ${tobuy.amount}`}
//secondary={tobuy.amount}
sx={{
textDecoration: false ? 'line-through' : 'none',
}}
@ -177,8 +176,11 @@ const TaskListPage: React.FC = () => {
<IconButton
edge="end"
aria-label="食材情報追加"
onClick={() => setOpenInfoDialog(true)}
//onClick={() => handleDeleteTask(task.id)}
onClick={() => {
setOpenInfoDialog(true)
setSelectedTask(tobuy.tobuy_id)
handleDeleteTask(tobuy.tobuy_id)
}}
>
<ShoppingBasketIcon />
</IconButton>
@ -265,7 +267,7 @@ const TaskListPage: React.FC = () => {
</Select>
</FormControl>
{!newToBuy.newAddition && <FormControl sx={{ width: "50%", marginBottom: 2 }}>
{!newToBuy.newAddition && <FormControl sx={{ width: "100%", marginBottom: 2 }}>
<InputLabel id="demo-simple-select-label"></InputLabel>
<Select
labelId="demo-simple-select-label"
@ -350,8 +352,9 @@ const TaskListPage: React.FC = () => {
<Button onClick={() => setOpenInfoDialog(false)}></Button>
<Button onClick={() => {
if (selectedTask) {
handleDeleteTask(selectedTask.tobuy_id)
handleDeleteTask(selectedTask)
setOpenInfoDialog(false)
setNewToBuy(EMPTY_TASK); // 入力内容をリセット
}
}}
variant="contained">

@ -3,7 +3,7 @@
* APIとの通信を担当するモジュール
*
*/
import { LoginCredentials, RegisterCredentials, AuthResponse, Task, ToBuy, Stuff } 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`, {
@ -214,6 +214,54 @@ export const stuffApi = {
}
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": "野菜"
}
]
}
},
}
/**
* ()
* API機能を提供するオブジェクト

@ -35,6 +35,22 @@ export interface Stuff {
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,
}
/**
*
*

Loading…
Cancel
Save