|
|
|
|
/**
|
|
|
|
|
* テストページコンポーネント
|
|
|
|
|
* 白紙の状態で表示されるテスト用のページ
|
|
|
|
|
*/
|
|
|
|
|
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 { STOCK_ERRORS } from '../constants/errorMessages';
|
|
|
|
|
|
|
|
|
|
const StockPage: React.FC = () => {
|
|
|
|
|
|
|
|
|
|
const [stocks, setStocks] = useState<Stock[]>([]);
|
|
|
|
|
// セル選択の表示状態
|
|
|
|
|
const [selectedRow, setSelectedRow] = useState<Stock | null>(null);
|
|
|
|
|
// 編集ダイアロボックスの表示状態
|
|
|
|
|
const [isEditOpen, setIsEditOpen] = useState(false);
|
|
|
|
|
// 削除メッセージダイアログの表示状態
|
|
|
|
|
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
|
|
|
// 在庫の編集状態
|
|
|
|
|
const [editStock, setEditStock] = useState<Stock | null>(null);
|
|
|
|
|
|
|
|
|
|
// コンポーネントマウント時にタスク一覧を取得
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchStocks();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* APIから在庫一覧を取得する関数
|
|
|
|
|
* 取得したタスクをstate(tasks)に設定
|
|
|
|
|
*/
|
|
|
|
|
const fetchStocks = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const stocks = await stockApi.getStocks();
|
|
|
|
|
console.log('Stocks=', stocks)
|
|
|
|
|
setStocks(stocks);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`${STOCK_ERRORS.FETCH_FAILED}:`, error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在庫リストを編集するハンドラー
|
|
|
|
|
*/
|
|
|
|
|
const handleUpdateStock = async (stockId: number, amount: number, price: number, buyDate: string, expDate: string) => {
|
|
|
|
|
try {
|
|
|
|
|
const today = new Date().toISOString().substring(0, 10);
|
|
|
|
|
await stockApi.updateStock({ stockId, amount, price, lastUpdate: today, buyDate, expDate });
|
|
|
|
|
fetchStocks(); // 削除後の買うもの一覧を再取得
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在庫を削除するハンドラー
|
|
|
|
|
* 指定された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に変換する関数
|
|
|
|
|
*/
|
|
|
|
|
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}`;
|
|
|
|
|
};
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* セルを選択する関数. 再度クリックで選択解除
|
|
|
|
|
*/
|
|
|
|
|
const handleRowClick = (stock: Stock) => {
|
|
|
|
|
setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 編集ボタンを押したときにダイアログを開く */
|
|
|
|
|
// ダイアログを開く際に `selectedRow` の値を `editStock` にセット
|
|
|
|
|
const handleOpenEdit = () => {
|
|
|
|
|
if (selectedRow) {
|
|
|
|
|
setEditStock({ ...selectedRow });
|
|
|
|
|
setIsEditOpen(true);
|
|
|
|
|
} else {
|
|
|
|
|
alert("編集する食材を選択してください。");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// 変更を適用
|
|
|
|
|
const handleApplyChanges = async () => {
|
|
|
|
|
if (editStock) {
|
|
|
|
|
try {
|
|
|
|
|
await handleUpdateStock(
|
|
|
|
|
editStock.stockId,
|
|
|
|
|
Number(editStock.amount),
|
|
|
|
|
Number(editStock.price),
|
|
|
|
|
editStock.buyDate,
|
|
|
|
|
editStock.expDate
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setSelectedRow(editStock); // `selectedRow` を更新して変更を即時反映
|
|
|
|
|
fetchStocks(); // 最新データを取得してテーブルに反映
|
|
|
|
|
setSelectedRow(null); // セルの選択を解除
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setIsEditOpen(false); // ダイアログを閉じる
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ダイアログを開く際に `selectedRow` の値を `editStock` にコピー
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedRow) {
|
|
|
|
|
setEditStock({ ...selectedRow });
|
|
|
|
|
}
|
|
|
|
|
}, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映
|
|
|
|
|
|
|
|
|
|
// テキストフィールドの変更を検知
|
|
|
|
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
if (editStock) {
|
|
|
|
|
setEditStock({
|
|
|
|
|
...editStock,
|
|
|
|
|
[event.target.name]: event.target.value,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 編集ダイアログを閉じる */
|
|
|
|
|
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 => {
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const expDate = new Date(stock.expDate);
|
|
|
|
|
const timeDiff = expDate.getTime() - today.getTime();
|
|
|
|
|
const daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<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
|
|
|
|
|
style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}}
|
|
|
|
|
>
|
|
|
|
|
{formatDate(stock.expDate)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>{formatDate(stock.buyDate)}</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</TableContainer>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 編集ダイアログ */}
|
|
|
|
|
<Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm">
|
|
|
|
|
<DialogTitle>在庫の編集</DialogTitle>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
{editStock && (
|
|
|
|
|
<>
|
|
|
|
|
<Typography variant="h4">{editStock.stuffName}</Typography>
|
|
|
|
|
<TextField
|
|
|
|
|
label="数量"
|
|
|
|
|
fullWidth
|
|
|
|
|
margin="normal"
|
|
|
|
|
name="amount"
|
|
|
|
|
type="number"
|
|
|
|
|
value={editStock.amount}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
label="購入価格"
|
|
|
|
|
fullWidth
|
|
|
|
|
margin="normal"
|
|
|
|
|
name="price"
|
|
|
|
|
type="number"
|
|
|
|
|
value={editStock.price}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
label="賞味・消費期限 (yyyy-MM-dd)"
|
|
|
|
|
fullWidth
|
|
|
|
|
margin="normal"
|
|
|
|
|
name="expDate"
|
|
|
|
|
value={editStock.expDate}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
label="購入日 (yyyy-MM-dd)"
|
|
|
|
|
fullWidth
|
|
|
|
|
margin="normal"
|
|
|
|
|
name="buyDate"
|
|
|
|
|
value={editStock.buyDate}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Button onClick={() => { setIsEditOpen(false); setSelectedRow(null); }} sx={{ mt: 3, mb: 2, left: '68%' }}>キャンセル</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
color="success"
|
|
|
|
|
onClick={handleApplyChanges}
|
|
|
|
|
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="h4">【{selectedRow.stuffName}】を削除します。</Typography>
|
|
|
|
|
<Typography variant="body1" color="error">⚠️ 注意: 削除すると復元できません。</Typography>
|
|
|
|
|
<Button onClick={() => { setIsDeleteOpen(false); setSelectedRow(null); }} sx={{ mt: 3, mb: 2, left: '70%' }}>キャンセル</Button>
|
|
|
|
|
<Button variant="contained" color="error" onClick={() => {
|
|
|
|
|
handleDeleteStock(selectedRow.stockId);
|
|
|
|
|
setIsDeleteOpen(false); // 削除処理後にダイアログを閉じる
|
|
|
|
|
setSelectedRow(null); // セルの選択を解除
|
|
|
|
|
}}
|
|
|
|
|
style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '72%' }}>削除</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Container>
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>
|
|
|
|
|
在庫一覧
|
|
|
|
|
</Typography>
|
|
|
|
|
|
|
|
|
|
{/* タスク編集ボタン(全テーブル共通) */}
|
|
|
|
|
<Button variant="contained" color="success" onClick={handleOpenEdit} sx={{ mt: 3, mb: 2, left: '80%' }}>
|
|
|
|
|
編集
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{/* タスク削除ボタン */}
|
|
|
|
|
<Button variant="contained" color="error" onClick={handleOpenDelete} sx={{ mt: 3, mb: 2, left: '82%' }}>削除</Button>
|
|
|
|
|
|
|
|
|
|
{/* 在庫一覧リスト */}
|
|
|
|
|
{/* 乳製品 */}
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>乳製品</Typography>
|
|
|
|
|
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
|
|
|
|
|
{StockTable(stocks, ["乳製品"])}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 肉・魚 */}
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>魚・肉</Typography>
|
|
|
|
|
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
|
|
|
|
|
{StockTable(stocks, ["魚・肉"])}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 野菜 */}
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>野菜</Typography>
|
|
|
|
|
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
|
|
|
|
|
{StockTable(stocks, ["野菜"])}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 調味料 */}
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>調味料</Typography>
|
|
|
|
|
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
|
|
|
|
|
{StockTable(stocks, ["調味料"])}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* その他 */}
|
|
|
|
|
<Typography variant="h4" component="h1" gutterBottom>その他</Typography>
|
|
|
|
|
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
|
|
|
|
|
{StockTable(stocks, ["その他"])}
|
|
|
|
|
</div>
|
|
|
|
|
</Container>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default StockPage;
|