|
|
|
@ -3,8 +3,8 @@ |
|
|
|
|
* 白紙の状態で表示されるテスト用のページ |
|
|
|
|
*/ |
|
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
|
|
import { stockApi } from '../services/api'; |
|
|
|
|
import { Stock } from '../types/types'; |
|
|
|
|
import { stockApi, stuffApi } from '../services/api'; |
|
|
|
|
import { Stock, Stuff } from '../types/types'; |
|
|
|
|
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material"; |
|
|
|
|
import { |
|
|
|
|
Container, |
|
|
|
@ -18,15 +18,40 @@ import { |
|
|
|
|
TextField, |
|
|
|
|
Button, |
|
|
|
|
Box, |
|
|
|
|
|
|
|
|
|
Checkbox, |
|
|
|
|
FormControlLabel, |
|
|
|
|
FormGroup, |
|
|
|
|
FormControl, |
|
|
|
|
InputLabel, |
|
|
|
|
Select, |
|
|
|
|
MenuItem, |
|
|
|
|
} from '@mui/material'; |
|
|
|
|
import { STOCK_ERRORS } from '../constants/errorMessages'; |
|
|
|
|
|
|
|
|
|
// 新規在庫の初期状態
|
|
|
|
|
const EMPTY_STOCK: Omit<Stock, 'stockId' | 'stuffId'> & { stuffId: number | null } & { newAddition: boolean } = { |
|
|
|
|
stuffId: null, |
|
|
|
|
stuffName: '', |
|
|
|
|
amount: 0, |
|
|
|
|
price: 0, |
|
|
|
|
lastUpdate: '', |
|
|
|
|
buyDate: '', |
|
|
|
|
expDate: '', |
|
|
|
|
category: '', |
|
|
|
|
newAddition: false, // 材料を新規作成するか否か
|
|
|
|
|
// shop '',
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const StockPage: React.FC = () => { |
|
|
|
|
|
|
|
|
|
const [stocks, setStocks] = useState<Stock[]>([]); |
|
|
|
|
// セル選択の表示状態
|
|
|
|
|
const [selectedRow, setSelectedRow] = useState<Stock | null>(null); |
|
|
|
|
// 追加ダイアログボックスの表示状態
|
|
|
|
|
const [isAddOpen, setIsAddOpen] = useState(false); |
|
|
|
|
// 在庫追加に使う状態
|
|
|
|
|
const [newStock, setNewStock] = useState(EMPTY_STOCK); |
|
|
|
|
const [stuffs, setStuffs] = useState<Stuff[]>([]); |
|
|
|
|
// 編集ダイアロボックスの表示状態
|
|
|
|
|
const [isEditOpen, setIsEditOpen] = useState(false); |
|
|
|
|
// 削除メッセージダイアログの表示状態
|
|
|
|
@ -53,6 +78,31 @@ const StockPage: React.FC = () => { |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 在庫リストに新規食材を作成するハンドラー |
|
|
|
|
*/ |
|
|
|
|
const handleCreateStock = async () => { |
|
|
|
|
try { |
|
|
|
|
if (newStock.newAddition) { |
|
|
|
|
newStock.stuffId = null; |
|
|
|
|
} |
|
|
|
|
console.log(newStock) |
|
|
|
|
const today = new Date().toISOString().substring(0, 10); |
|
|
|
|
|
|
|
|
|
const updatedStock = { ...newStock, lastUpdate: today }; // lastUpdate に today を設定
|
|
|
|
|
console.log("送信するデータ:", updatedStock); // 送信前のデータを確認
|
|
|
|
|
await stockApi.addStock(updatedStock); // 修正したオブジェクトを API に送信
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// await stockApi.addStock(newStock);
|
|
|
|
|
setIsAddOpen(false); // ダイアログを閉じる
|
|
|
|
|
setNewStock(EMPTY_STOCK); // 入力内容をリセット
|
|
|
|
|
fetchStocks(); // 作成後のタスク一覧を再取得
|
|
|
|
|
} catch (error) { |
|
|
|
|
console.error(`${STOCK_ERRORS.CREATE_FAILED}:`, error); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 在庫リストを編集するハンドラー |
|
|
|
|
*/ |
|
|
|
@ -79,6 +129,15 @@ const StockPage: React.FC = () => { |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* カテゴリー選択?? |
|
|
|
|
*/ |
|
|
|
|
const onChangeCategory = async (category: string) => { |
|
|
|
|
setNewStock({ ...newStock, category }) |
|
|
|
|
const result = await stuffApi.getStuffs(category) |
|
|
|
|
setStuffs(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 文字列(ISO 8601形式)をyyyy/MM/ddに変換する関数 |
|
|
|
|
*/ |
|
|
|
@ -98,6 +157,15 @@ const StockPage: React.FC = () => { |
|
|
|
|
}; |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
/** 追加ボタンを押したときにダイアログを開く */ |
|
|
|
|
const handleOpenAdd = () => { |
|
|
|
|
setIsAddOpen(true); |
|
|
|
|
}; |
|
|
|
|
/** 削除ダイアログを閉じる */ |
|
|
|
|
const handleCloseAdd = () => { |
|
|
|
|
setIsAddOpen(false); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* セルを選択する関数. 再度クリックで選択解除 |
|
|
|
|
*/ |
|
|
|
@ -187,28 +255,40 @@ const StockPage: React.FC = () => { |
|
|
|
|
<TableCell>食材名</TableCell> |
|
|
|
|
<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> |
|
|
|
|
))} |
|
|
|
|
{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>{formatDate(stock.buyDate)}</TableCell> |
|
|
|
|
<TableCell |
|
|
|
|
style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}} |
|
|
|
|
> |
|
|
|
|
{formatDate(stock.expDate)} |
|
|
|
|
</TableCell> |
|
|
|
|
</TableRow> |
|
|
|
|
); |
|
|
|
|
})} |
|
|
|
|
</TableBody> |
|
|
|
|
</Table> |
|
|
|
|
</TableContainer> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 編集ダイアログ */} |
|
|
|
|
<Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm"> |
|
|
|
|
<DialogTitle>在庫の編集</DialogTitle> |
|
|
|
@ -235,23 +315,23 @@ const StockPage: React.FC = () => { |
|
|
|
|
onChange={handleChange} |
|
|
|
|
/> |
|
|
|
|
<TextField |
|
|
|
|
label="賞味・消費期限 (yyyy-MM-dd)" |
|
|
|
|
label="購入日 (yyyy-MM-dd)" |
|
|
|
|
fullWidth |
|
|
|
|
margin="normal" |
|
|
|
|
name="expDate" |
|
|
|
|
value={editStock.expDate} |
|
|
|
|
name="buyDate" |
|
|
|
|
value={editStock.buyDate} |
|
|
|
|
onChange={handleChange} |
|
|
|
|
/> |
|
|
|
|
<TextField |
|
|
|
|
label="購入日 (yyyy-MM-dd)" |
|
|
|
|
label="消費・賞味期限 (yyyy-MM-dd)" |
|
|
|
|
fullWidth |
|
|
|
|
margin="normal" |
|
|
|
|
name="buyDate" |
|
|
|
|
value={editStock.buyDate} |
|
|
|
|
name="expDate" |
|
|
|
|
value={editStock.expDate} |
|
|
|
|
onChange={handleChange} |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<Button onClick={() => {setIsEditOpen(false); setSelectedRow(null);}} sx={{ mt: 3, mb: 2, left: '68%' }}>キャンセル</Button> |
|
|
|
|
<Button onClick={() => { setIsEditOpen(false); setSelectedRow(null); }} sx={{ mt: 3, mb: 2, left: '68%' }}>キャンセル</Button> |
|
|
|
|
<Button |
|
|
|
|
variant="contained" |
|
|
|
|
color="success" |
|
|
|
@ -277,9 +357,9 @@ const StockPage: React.FC = () => { |
|
|
|
|
<DialogContent> |
|
|
|
|
{selectedRow && ( |
|
|
|
|
<> |
|
|
|
|
<Typography variant="h4">{selectedRow.stuffName}を削除します。</Typography> |
|
|
|
|
<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 onClick={() => { setIsDeleteOpen(false); setSelectedRow(null); }} sx={{ mt: 3, mb: 2, left: '70%' }}>キャンセル</Button> |
|
|
|
|
<Button variant="contained" color="error" onClick={() => { |
|
|
|
|
handleDeleteStock(selectedRow.stockId); |
|
|
|
|
setIsDeleteOpen(false); // 削除処理後にダイアログを閉じる
|
|
|
|
@ -290,8 +370,6 @@ const StockPage: React.FC = () => { |
|
|
|
|
)} |
|
|
|
|
</DialogContent> |
|
|
|
|
</Dialog> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
@ -303,15 +381,138 @@ const StockPage: React.FC = () => { |
|
|
|
|
在庫一覧 |
|
|
|
|
</Typography> |
|
|
|
|
|
|
|
|
|
{/* タスク編集ボタン(全テーブル共通) */} |
|
|
|
|
{/* 在庫の食材追加ボタン */} |
|
|
|
|
<Button variant="contained" color="primary" onClick={handleOpenAdd} sx={{ mt: 3, mb: 2, left: '78%' }}> |
|
|
|
|
追加 |
|
|
|
|
</Button> |
|
|
|
|
|
|
|
|
|
{/* 新規タスク作成ダイアログ */} |
|
|
|
|
<Dialog open={isAddOpen} onClose={() => setIsAddOpen(false)} disableScrollLock={true}> |
|
|
|
|
<Box display="flex" alignItems="center" > |
|
|
|
|
<DialogTitle sx={{ flexGrow: 1 }}>在庫に食材を追加</DialogTitle> |
|
|
|
|
<FormGroup row> |
|
|
|
|
<FormControlLabel |
|
|
|
|
control={<Checkbox />} |
|
|
|
|
label="食材を新規追加" |
|
|
|
|
checked={newStock.newAddition} |
|
|
|
|
onChange={(e) => setNewStock({ ...newStock, newAddition: (e.target as HTMLInputElement).checked })} |
|
|
|
|
/> |
|
|
|
|
</FormGroup> |
|
|
|
|
</Box> |
|
|
|
|
<DialogContent> |
|
|
|
|
<Box sx={{ pt: 1 }}> |
|
|
|
|
{/*材料カテゴリ選択 */} |
|
|
|
|
|
|
|
|
|
<FormControl sx={{ width: "50%", marginBottom: 2 }}> |
|
|
|
|
<InputLabel id="demo-simple-select-label">カテゴリ</InputLabel> |
|
|
|
|
<Select |
|
|
|
|
labelId="demo-simple-select-label" |
|
|
|
|
value={newStock.category} |
|
|
|
|
onChange={(e) => onChangeCategory(e.target.value)} |
|
|
|
|
> |
|
|
|
|
<MenuItem value="乳製品">乳製品</MenuItem> |
|
|
|
|
<MenuItem value="魚・肉">魚・肉</MenuItem> |
|
|
|
|
<MenuItem value="野菜">野菜</MenuItem> |
|
|
|
|
<MenuItem value="調味料">調味料</MenuItem> |
|
|
|
|
<MenuItem value="その他">その他</MenuItem> |
|
|
|
|
</Select> |
|
|
|
|
</FormControl> |
|
|
|
|
|
|
|
|
|
{!newStock.newAddition && <FormControl sx={{ width: "100%", marginBottom: 2 }}> |
|
|
|
|
<InputLabel id="demo-simple-select-label">材料名(選択)</InputLabel> |
|
|
|
|
<Select |
|
|
|
|
labelId="demo-simple-select-label" |
|
|
|
|
value={newStock.stuffId} |
|
|
|
|
onChange={(e) => setNewStock({ ...newStock, stuffId: Number(e.target.value) })} |
|
|
|
|
> |
|
|
|
|
{stuffs.map((stuff) => ( |
|
|
|
|
<MenuItem key={stuff.stuffId} value={stuff.stuffId}> |
|
|
|
|
{stuff.stuffName} |
|
|
|
|
</MenuItem> |
|
|
|
|
))} |
|
|
|
|
</Select> |
|
|
|
|
</FormControl>} |
|
|
|
|
|
|
|
|
|
{/* タスクタイトル入力フィールド */} |
|
|
|
|
{newStock.newAddition && <TextField |
|
|
|
|
autoFocus |
|
|
|
|
margin="dense" |
|
|
|
|
label="材料名" |
|
|
|
|
fullWidth |
|
|
|
|
value={newStock.stuffName} |
|
|
|
|
onChange={(e) => setNewStock({ ...newStock, stuffName: e.target.value })} |
|
|
|
|
sx={{ marginBottom: 2 }} |
|
|
|
|
/>} |
|
|
|
|
{/* 数量入力フィールド */} |
|
|
|
|
<TextField |
|
|
|
|
margin="dense" |
|
|
|
|
label="数量" |
|
|
|
|
fullWidth |
|
|
|
|
value={newStock.amount} |
|
|
|
|
onChange={(e) => { |
|
|
|
|
const value = e.target.value; |
|
|
|
|
const parsedValue = parseInt(value, 10); // 数値に変換
|
|
|
|
|
if (!isNaN(parsedValue)) { |
|
|
|
|
setNewStock({ ...newStock, amount: parsedValue }); // number型で保存
|
|
|
|
|
} |
|
|
|
|
}} |
|
|
|
|
// sx={{ width: "50%" }}
|
|
|
|
|
type="number" |
|
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
|
|
|
|
|
/> |
|
|
|
|
{/* 購入価格入力フィールド */} |
|
|
|
|
<TextField |
|
|
|
|
margin="dense" |
|
|
|
|
label="購入価格" |
|
|
|
|
fullWidth |
|
|
|
|
value={newStock.price} |
|
|
|
|
onChange={(e) => { |
|
|
|
|
const value = e.target.value; |
|
|
|
|
const parsedValue = parseInt(value, 10); // 数値に変換
|
|
|
|
|
if (!isNaN(parsedValue)) { |
|
|
|
|
setNewStock({ ...newStock, price: parsedValue }); // number型で保存
|
|
|
|
|
} |
|
|
|
|
}} |
|
|
|
|
// sx={{ width: "50%" }}
|
|
|
|
|
type="number" |
|
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
{/* 購入日入力フィールド */} |
|
|
|
|
<TextField |
|
|
|
|
margin="dense" |
|
|
|
|
label="購入日(yyyy/MM/dd)" |
|
|
|
|
fullWidth |
|
|
|
|
value={newStock.buyDate} |
|
|
|
|
onChange={(e) => setNewStock({ ...newStock, buyDate: e.target.value })} |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
{/* 賞味・消費期限入力フィールド */} |
|
|
|
|
<TextField |
|
|
|
|
margin="dense" |
|
|
|
|
label="消費・賞味期限(yyyy/MM/dd)" |
|
|
|
|
fullWidth |
|
|
|
|
value={newStock.expDate} |
|
|
|
|
onChange={(e) => setNewStock({ ...newStock, expDate: e.target.value })} |
|
|
|
|
/> |
|
|
|
|
</Box> |
|
|
|
|
</DialogContent> |
|
|
|
|
<DialogActions> |
|
|
|
|
<Button onClick={() => setIsAddOpen(false)}>キャンセル</Button> |
|
|
|
|
<Button onClick={handleCreateStock} variant="contained"> |
|
|
|
|
追加 |
|
|
|
|
</Button> |
|
|
|
|
</DialogActions> |
|
|
|
|
</Dialog> |
|
|
|
|
|
|
|
|
|
{/* 在庫の食材編集ボタン(全テーブル共通) */} |
|
|
|
|
<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> |
|
|
|
|