Merge remote-tracking branch 'origin/feature-frontend-stock' into feature-backend-stock-fix

feature-backend-add-springdocs
Masaharu.Kato 4 months ago
commit 26ad09da97
  1. 2
      frontend/public/index.html
  2. 2
      frontend/src/components/Layout.tsx
  3. 13
      frontend/src/pages/AddDishies1.tsx
  4. 368
      frontend/src/pages/StockPage.tsx
  5. 8
      frontend/src/pages/TaskListPage.tsx
  6. 91
      frontend/src/services/api.ts

@ -23,7 +23,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>shopchop</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

@ -74,7 +74,7 @@ const Layout: React.FC = () => {
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
ToDoアプリ
shopchop
</Typography>
<Button color="inherit" onClick={handleLogout}>

@ -0,0 +1,13 @@
import React from 'react';
import { Box } from '@mui/material';
const AddDishies1: React.FC = () => {
return (
<Box>
{/* 白紙のページ - 何も表示しない */}
</Box>
);
};
export default AddDishies1;

@ -25,15 +25,12 @@ import { STOCK_ERRORS } from '../constants/errorMessages';
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);
const [selectedRow, setSelectedRow] = useState<Stock | null>(null);
// 編集ダイアロボックスの表示状態
const [isEditOpen, setIsEditOpen] = useState(false);
// 削除メッセージダイアログの表示状態
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
// コンポーネントマウント時にタスク一覧を取得
useEffect(() => {
@ -41,7 +38,7 @@ const StockPage: React.FC = () => {
}, []);
/**
* APIからタスク一覧を取得する関数
* APIから在庫一覧を取得する関数
* state(tasks)
*/
const fetchStocks = async () => {
@ -54,6 +51,19 @@ const StockPage: React.FC = () => {
}
};
/**
*
* IDのタスクをAPIを通じて削除
*/
const handleDeleteStock = async (stockId: number) => {
try {
await stockApi.deleteStock(stockId);
fetchStocks(); // 削除後の買うもの一覧を再取得
} catch (error) {
console.error(`${STOCK_ERRORS.DELETE_FAILED}:`, error);
}
};
/**
* (ISO 8601)yyyy/MM/ddに変換する関数
*/
@ -74,10 +84,123 @@ const StockPage: React.FC = () => {
*/
/**
*
* .
*/
const handleRowClick = (id: number) => {
setSelectedRow(prevSelected => (prevSelected === id ? null : id)); // クリックで選択・解除を切り替え
const handleRowClick = (stock: Stock) => {
setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock));
};
/** 編集ボタンを押したときにダイアログを開く */
const handleOpenEdit = () => {
if (selectedRow) {
setIsEditOpen(true);
} else {
alert("編集する食材を選択してください。");
}
};
/** 編集ダイアログを閉じる */
const handleCloseEdit = () => {
setIsEditOpen(false);
};
/** 削除ボタンを押したときにダイアログを開く */
const handleOpenDelete = () => {
if (selectedRow) {
setIsDeleteOpen(true);
} else {
alert("削除する食材を選択してください。");
}
};
/** 削除ダイアログを閉じる */
const handleCloseDelete = () => {
setIsDeleteOpen(false);
};
/** テーブルを表示する関数 */
const StockTable = (stocks: Stock[], categories: string[]) => {
const filteredStocks = stocks.filter(stock => categories.includes(stock.category));
if (filteredStocks.length === 0) return null;
return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredStocks.map(stock => (
<TableRow
key={stock.stockId}
onClick={() => handleRowClick(stock)}
style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }}
>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* 編集ダイアログ */}
<Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm">
<DialogTitle></DialogTitle>
<DialogContent>
{selectedRow && (
<>
<Typography variant="body1"></Typography>
<TextField label="食材名" fullWidth margin="normal" defaultValue={selectedRow.stuffName} />
<TextField label="数量" fullWidth margin="normal" defaultValue={selectedRow.amount} />
<TextField label="購入価格" fullWidth margin="normal" defaultValue={selectedRow.price} />
<TextField label="賞味・消費期限" fullWidth margin="normal" defaultValue={selectedRow.expDate} />
<TextField label="購入日" fullWidth margin="normal" defaultValue={selectedRow.buyDate} />
<Button onClick={() => setIsEditOpen(false)} sx={{ mt: 3, mb: 2, left: '68%' }}></Button>
<Button variant="contained" color="success" onClick={handleCloseEdit} sx={{ mt: 3, mb: 2, left: "68%" }}>
</Button>
</>
)}
</DialogContent>
</Dialog>
{/* タスク削除ダイアログ */}
<Dialog open={isDeleteOpen}
onClose={handleCloseDelete}
fullWidth
maxWidth="sm"
sx={{ overflow: "hidden" }}
>
<DialogTitle></DialogTitle>
<DialogContent>
{selectedRow && (
<>
<Typography variant="body1"></Typography>
<Typography variant="body2" color="error"> 注意: 削除すると復元できません</Typography>
<Button onClick={() => setIsDeleteOpen(false)} sx={{ mt: 3, mb: 2, left: '70%' }}></Button>
{/* <Button variant="contained" color="error" onClick={handleCloseDelete} style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '72%' }}>削除</Button> */}
<Button variant="contained" color="error" onClick={() => {
handleDeleteStock(selectedRow.stockId);
}}
style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '72%' }}></Button>
</>
)}
</DialogContent>
</Dialog>
</>
);
};
@ -87,227 +210,44 @@ const StockPage: React.FC = () => {
</Typography>
{/* タスク編集ボタン */}
<Button color="success"
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2, left: '80%' }}
>
{/* タスク編集ボタン(全テーブル共通) */}
<Button variant="contained" color="success" onClick={handleOpenEdit} 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={handleOpenDelete} sx={{ mt: 3, mb: 2, left: '82%' }}></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>
{/* 乳製品一覧表示エリア - 青い背景のコンテナ */}
{/* 在庫一覧リスト */}
{/* 乳製品 */}
<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.stockId}
onClick={() => handleRowClick(stock.stockId)}
style={{ backgroundColor: selectedRow === stock.stockId ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{StockTable(stocks, ["乳製品"])}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 魚・肉一覧表示エリア - 青い背景のコンテナ */}
{/* 肉・魚 */}
<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.stockId}
onClick={() => handleRowClick(stock.stockId)}
style={{ backgroundColor: selectedRow === stock.stockId ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{StockTable(stocks, ["魚・肉"])}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 野菜一覧表示エリア - 青い背景のコンテナ */}
{/* 野菜 */}
<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.stockId}
onClick={() => handleRowClick(stock.stockId)}
style={{ backgroundColor: selectedRow === stock.stockId ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{StockTable(stocks, ["野菜"])}
</div>
<Typography variant="h4" component="h1" gutterBottom>
調
</Typography>
{/* 調味料一覧表示エリア - 青い背景のコンテナ */}
{/* 調味料 */}
<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.stockId}
onClick={() => handleRowClick(stock.stockId)}
style={{ backgroundColor: selectedRow === stock.stockId ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{StockTable(stocks, ["調味料"])}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* その他一覧表示エリア - 青い背景のコンテナ */}
{/* その他 */}
<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.stockId}
onClick={() => handleRowClick(stock.stockId)}
style={{ backgroundColor: selectedRow === stock.stockId ? "yellow" : "white", cursor: "pointer" }}>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.expDate)}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{StockTable(stocks, ["その他"])}
</div>
</Container>
);

@ -4,6 +4,7 @@
*/
import React, { useState, useEffect } from 'react';
import { toBuyApi, stuffApi } from '../services/api';
import { useNavigate, Outlet, useLocation } from 'react-router-dom';
import {
Container,
Typography,
@ -252,7 +253,12 @@ const TaskListPage: React.FC = () => {
<Fab
color="primary"
sx={{ position: 'fixed', bottom: 16, left: '60%', transform: 'translateX(-50%)' }}
onClick={() => setOpenDialog(true)}
onClick={() => {setOpenDialog(true);
//handleNavigate('/AddDishies1');
}}
//selected={isSelected('/test')}
>
<SoupKitchenIcon />
</Fab>

@ -140,7 +140,7 @@ export const toBuyApi = {
const response = await fetch(`${API_BASE_URL}/api/tobuy/delete`, {
method: 'DELETE',
headers: getHeaders(),
body: JSON.stringify({tobuyId}),
body: JSON.stringify({ tobuyId }),
});
if (!response.ok) {
@ -157,13 +157,13 @@ export const toBuyApi = {
/**
*
*/
buy: async (req: {tobuyId: number, price: number, expDate: string, buyDate: string, lastUpdate: string}): Promise<{ result: boolean }> => {
buy: async (req: { tobuyId: number, price: number, expDate: string, buyDate: string, lastUpdate: string }): Promise<{ result: boolean }> => {
console.log('req: ', req)
req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || ''
req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || ''
const response = await fetch(`${API_BASE_URL}/api/tobuy/buy`, {
method: 'POST',
headers: getHeaders(),
@ -245,16 +245,87 @@ export const stockApi = {
return response.json();
},
/**
*
*/
updateStock: async (req: {stockId: number, amount: number, price: number, lastUpdate: string, buyDate: string, expDate: string}): Promise<{ result: boolean; message: string }> => {
const response = await fetch(`${API_BASE_URL}/api/stocks/update`, {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify(req),
});
if (!response.ok) {
throw new Error(STOCK_ERRORS.UPDATE_FAILED);
}
return response.json()
// return {
// "result": true
// }
},
/**
*
* @param id ID
*/
deleteStock: async (stockId: number): Promise<{ result: boolean; message: string }> => {
const response = await fetch(`${API_BASE_URL}/api/stocks/delete`, {
method: 'DELETE',
headers: getHeaders(),
body: JSON.stringify({ stockId }),
});
console.log("API レスポンスステータス:", response.status);
console.log("API レスポンス本文:", await response.text());
if (!response.ok) {
throw new Error(STOCK_ERRORS.DELETE_FAILED);
}
return response.json()
// return {
// "result": true
// }
},
}
const deleteStock = async (stockId: number): Promise<{ result: boolean; message: string }> => {
console.log("API の deleteStock メソッドが呼ばれました。stockId:", stockId);
const response = await fetch(`${API_BASE_URL}/api/stocks/delete`, {
method: "DELETE",
headers: getHeaders(),
body: JSON.stringify({ stockId }),
});
console.log("API レスポンスステータス:", response.status);
console.log("API レスポンス本文:", await response.text());
if (!response.ok) {
return { result: false, message: "削除に失敗しました。" };
}
// API のレスポンスから result と message を取得
const data = await response.json();
return { result: data.result, message: data.message };
};
function makeDateObject(dateStr: String) {
// 例: '2025/06/15' または '2025-06-15' を '2025-06-15' に変換
const parts = dateStr.split(/[-\/]/); // ハイフンかスラッシュで分割
if (parts.length === 3) {
return new Date(parts[0] + '-' + parts[1] + '-' + parts[2]);
}
return null; // 無効な日付の場合
// 例: '2025/06/15' または '2025-06-15' を '2025-06-15' に変換
const parts = dateStr.split(/[-\/]/); // ハイフンかスラッシュで分割
if (parts.length === 3) {
return new Date(parts[0] + '-' + parts[1] + '-' + parts[2]);
}
return null; // 無効な日付の場合
}

Loading…
Cancel
Save