|
|
/**
|
|
|
* テストページコンポーネント
|
|
|
* 白紙の状態で表示されるテスト用のページ
|
|
|
*/
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
import { stockApi, stuffApi } from '../services/api';
|
|
|
import { Stock, StockUpdateRequest, Stuff } 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,
|
|
|
Checkbox,
|
|
|
FormControlLabel,
|
|
|
FormGroup,
|
|
|
FormControl,
|
|
|
InputLabel,
|
|
|
Select,
|
|
|
MenuItem,
|
|
|
} from '@mui/material';
|
|
|
import { GENERAL_ERRORS, STOCK_ERRORS } from '../constants/errorMessages';
|
|
|
import { Add as AddIcon } from '@mui/icons-material';
|
|
|
/*import DatePicker, { registerLocale } from 'react-datepicker';
|
|
|
import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート*/
|
|
|
import { useMessage } from '../components/MessageContext';
|
|
|
import BuyExpDateSelect from '../components/BuyExpDateSelect';
|
|
|
|
|
|
/*// 日付をyyyy-MM-dd形式で返す関数
|
|
|
const formatDateLocal = (date: Date) => {
|
|
|
const year = date.getFullYear();
|
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
|
return `${year}-${month}-${day}`;
|
|
|
};*/
|
|
|
|
|
|
// 新規在庫の初期状態
|
|
|
const EMPTY_STOCK: Omit<Stock, 'stockId' | 'stuffId'> & { stuffId: number | null } & { newAddition: boolean } = {
|
|
|
stuffId: null,
|
|
|
stuffName: '',
|
|
|
amount: 1,
|
|
|
buyAmount: 1,
|
|
|
price: 0,
|
|
|
shop: '',
|
|
|
lastUpdate: '',
|
|
|
buyDate: new Date().toISOString(),
|
|
|
expDate: '',
|
|
|
category: '',
|
|
|
newAddition: false, // 材料を新規作成するか否か
|
|
|
// shop '',
|
|
|
}
|
|
|
|
|
|
// 日本語ロケールを登録
|
|
|
//registerLocale('ja', ja);
|
|
|
|
|
|
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);
|
|
|
// 削除メッセージダイアログの表示状態
|
|
|
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
|
// 在庫の編集状態
|
|
|
const [editStock, setEditStock] = useState<Stock | null>(null);
|
|
|
|
|
|
const { showErrorMessage, showWarningMessage } = useMessage();
|
|
|
|
|
|
// コンポーネントマウント時にタスク一覧を取得
|
|
|
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);
|
|
|
showErrorMessage(STOCK_ERRORS.FETCH_FAILED);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 在庫リストに新規食材を作成するハンドラー
|
|
|
*/
|
|
|
const handleCreateStock = async () => {
|
|
|
try {
|
|
|
if (newStock.newAddition) {
|
|
|
newStock.stuffId = null;
|
|
|
}
|
|
|
if (isNaN(newStock.amount)) return;
|
|
|
if (newStock.price === null || isNaN(newStock.price)) return;
|
|
|
|
|
|
if (newStock.buyAmount !== null && isNaN(newStock.buyAmount)) {
|
|
|
newStock.buyAmount = null;
|
|
|
}
|
|
|
newStock.shop = newStock.shop || null;
|
|
|
|
|
|
console.log(newStock)
|
|
|
|
|
|
// 購入日と消費・賞味期限の整合性チェック
|
|
|
if (newStock.expDate !== null) {
|
|
|
const buy = new Date(newStock.buyDate);
|
|
|
const exp = new Date(newStock.expDate);
|
|
|
// 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得
|
|
|
const buyDateOnly = new Date(buy);
|
|
|
buyDateOnly.setHours(0, 0, 0, 0);
|
|
|
const expDateOnly = new Date(exp);
|
|
|
expDateOnly.setHours(0, 0, 0, 0);
|
|
|
|
|
|
// console.log("新規作成buy:", buy);
|
|
|
// console.log("新規作成exp:", exp);
|
|
|
// console.log("新規作成buyDateOnly:", buyDateOnly);
|
|
|
// console.log("新規作成expDateOnly:", expDateOnly);
|
|
|
|
|
|
if (buyDateOnly.getTime() > expDateOnly.getTime()) {
|
|
|
// alert("購入日は消費・賞味期限より前の日付を設定してください。");
|
|
|
showErrorMessage('購入日は消費・賞味期限より前の日付を設定してください.');
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
showErrorMessage(STOCK_ERRORS.CREATE_FAILED);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 在庫リストを編集するハンドラー
|
|
|
*/
|
|
|
const handleUpdateStock = async (request: StockUpdateRequest) => {
|
|
|
try {
|
|
|
await stockApi.updateStock(request);
|
|
|
fetchStocks(); // 削除後の買うもの一覧を再取得
|
|
|
} catch (error) {
|
|
|
console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error);
|
|
|
showErrorMessage(STOCK_ERRORS.UPDATE_FAILED);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 在庫を削除するハンドラー
|
|
|
* 指定されたIDのタスクをAPIを通じて削除
|
|
|
*/
|
|
|
const handleDeleteStock = async (stockId: number) => {
|
|
|
try {
|
|
|
await stockApi.deleteStock(stockId);
|
|
|
fetchStocks(); // 削除後の買うもの一覧を再取得
|
|
|
} catch (error) {
|
|
|
console.error(`${STOCK_ERRORS.DELETE_FAILED}:`, error);
|
|
|
showErrorMessage(STOCK_ERRORS.DELETE_FAILED);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* カテゴリー選択
|
|
|
*/
|
|
|
const onChangeCategory = async (category: string) => {
|
|
|
setNewStock({ ...newStock, category })
|
|
|
const result = await stuffApi.getStuffs(category)
|
|
|
setStuffs(result)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 文字列(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 handleOpenAdd = () => {
|
|
|
setIsAddOpen(true);
|
|
|
};
|
|
|
/** 追加ダイアログを閉じる */
|
|
|
const handleCloseAdd = () => {
|
|
|
setIsAddOpen(false);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* セルを選択して編集画面
|
|
|
*/
|
|
|
const handleRowClick = (stock: Stock) => {
|
|
|
setSelectedRow(stock); // 行選択
|
|
|
setEditStock({ ...stock }); // 編集対象にセット
|
|
|
setIsEditOpen(true); // 編集ダイアログを開く
|
|
|
};
|
|
|
|
|
|
// 変更を適用. 数量に0を入力したとき、削除ダイアログに飛ぶ機能を追加
|
|
|
const handleApplyChanges = async () => {
|
|
|
if (!editStock) return;
|
|
|
|
|
|
const { stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate } = editStock;
|
|
|
if (expDate !== null) {
|
|
|
// 購入日が消費・賞味期限より未来の場合はエラー表示
|
|
|
const buy = new Date(buyDate);
|
|
|
const exp = new Date(expDate);
|
|
|
// 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得
|
|
|
const buyDateOnly = new Date(buy);
|
|
|
buyDateOnly.setHours(0, 0, 0, 0);
|
|
|
const expDateOnly = new Date(exp);
|
|
|
expDateOnly.setHours(0, 0, 0, 0);
|
|
|
// console.log("編集buy:", buy);
|
|
|
// console.log("編集exp:", exp);
|
|
|
// console.log("編集buyDateOnly:", buyDateOnly);
|
|
|
// console.log("編集expDateOnly:", expDateOnly);
|
|
|
|
|
|
if (buy > exp) {
|
|
|
// alert("購入日は消費・賞味期限より前の日付を設定してください。");
|
|
|
showErrorMessage('購入日は消費・賞味期限より前の日付を設定してください.');
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// Number型に変換した変数を用意
|
|
|
const numericAmount = Number(amount);
|
|
|
const numericBuyAmount = Number(buyAmount);
|
|
|
const numericPrice = Number(price);
|
|
|
|
|
|
if (numericAmount === 0) {
|
|
|
// 数量が 0 の場合は削除処理へ誘導
|
|
|
// setIsEditOpen(false); // 編集ダイアログを閉じる
|
|
|
setSelectedRow(editStock); // 削除対象をセット
|
|
|
setIsDeleteOpen(true); // 削除ダイアログを開く
|
|
|
return;
|
|
|
}
|
|
|
if (!numericAmount || !numericBuyAmount) {
|
|
|
showErrorMessage(GENERAL_ERRORS.INVALID_AMOUNT);
|
|
|
return;
|
|
|
}
|
|
|
if (!numericPrice) {
|
|
|
showErrorMessage(GENERAL_ERRORS.INVALID_PRICE);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const lastUpdate = new Date().toISOString().substring(0, 10);
|
|
|
|
|
|
const updateRequest = {
|
|
|
stockId,
|
|
|
amount,
|
|
|
buyAmount,
|
|
|
price,
|
|
|
shop,
|
|
|
buyDate,
|
|
|
expDate,
|
|
|
lastUpdate,
|
|
|
}
|
|
|
|
|
|
console.log('updateRequest:', updateRequest);
|
|
|
|
|
|
await handleUpdateStock(updateRequest);
|
|
|
|
|
|
setSelectedRow(editStock); // 更新後に選択行を反映
|
|
|
fetchStocks(); // 最新データを取得
|
|
|
setSelectedRow(null); // 選択解除
|
|
|
} catch (error) {
|
|
|
console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error);
|
|
|
showErrorMessage(STOCK_ERRORS.UPDATE_FAILED);
|
|
|
}
|
|
|
|
|
|
setIsEditOpen(false); // 編集ダイアログを閉じる
|
|
|
};
|
|
|
|
|
|
// ダイアログを開く際に `selectedRow` の値を `editStock` にコピー
|
|
|
useEffect(() => {
|
|
|
if (selectedRow) {
|
|
|
setEditStock({ ...selectedRow });
|
|
|
}
|
|
|
}, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映
|
|
|
|
|
|
// テキストフィールドの変更を検知
|
|
|
// 負の値を入力できないように書き換え
|
|
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
const { name, value } = event.target;
|
|
|
|
|
|
// 数値項目に対して負の値をブロック
|
|
|
const numericFields = ['amount', 'buyAmount', 'price'];
|
|
|
const numericValue = Number(value);
|
|
|
const isNumericField = numericFields.includes(name);
|
|
|
if (isNumericField && numericValue < 0) {
|
|
|
return; // 無視して更新しない
|
|
|
}
|
|
|
|
|
|
if (editStock) {
|
|
|
setEditStock({
|
|
|
...editStock,
|
|
|
[name]: value,
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/** 編集ダイアログを閉じる */
|
|
|
const handleCloseEdit = () => {
|
|
|
setIsEditOpen(false);
|
|
|
setSelectedRow(null);
|
|
|
};
|
|
|
|
|
|
/** 削除ボタンを押したときにダイアログを開く */
|
|
|
const handleOpenDelete = () => {
|
|
|
if (selectedRow) {
|
|
|
setIsDeleteOpen(true);
|
|
|
} else {
|
|
|
// showWarningMessage("削除する食材を選択してください。");
|
|
|
showErrorMessage('削除する食材を選択してください.');
|
|
|
}
|
|
|
};
|
|
|
/** 削除ダイアログを閉じる */
|
|
|
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 align="center" sx={{ width: '40%', fontSize: '16px' }}>食材名</TableCell>
|
|
|
<TableCell align="center" sx={{ width: '20%', fontSize: '16px' }}>数量</TableCell>
|
|
|
<TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}>消費・賞味期限</TableCell>
|
|
|
</TableRow>
|
|
|
</TableHead>
|
|
|
<TableBody>
|
|
|
{filteredStocks.map(stock => {
|
|
|
let daysLeft = null;
|
|
|
|
|
|
if (stock.expDate !== null) {
|
|
|
const today = new Date();
|
|
|
const expDate = new Date(stock.expDate);
|
|
|
// 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得
|
|
|
const todayDateOnly = new Date(today);
|
|
|
todayDateOnly.setHours(0, 0, 0, 0);
|
|
|
const expDateOnly = new Date(expDate);
|
|
|
expDateOnly.setHours(0, 0, 0, 0);
|
|
|
const timeDiff = expDateOnly.getTime() - todayDateOnly.getTime();
|
|
|
daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
// console.log("テーブルtoday:", today);
|
|
|
// console.log("テーブルexp:", expDate);
|
|
|
// console.log("テーブルtodayDateOnly:", todayDateOnly);
|
|
|
// console.log("テーブルexpDateOnly:", expDateOnly);
|
|
|
// console.log("日数差:", daysLeft);
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
<TableRow
|
|
|
key={stock.stockId}
|
|
|
onClick={() => handleRowClick(stock)}
|
|
|
style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }}
|
|
|
>
|
|
|
<TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}>{stock.stuffName}</TableCell>
|
|
|
<TableCell align="center" sx={{ width: '20%', fontSize: '16px' }}>{stock.amount}</TableCell>
|
|
|
<TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}
|
|
|
style={daysLeft !== null && daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}}
|
|
|
>
|
|
|
{stock.expDate && formatDate(stock.expDate)}
|
|
|
</TableCell>
|
|
|
</TableRow>
|
|
|
);
|
|
|
})}
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
</TableContainer>
|
|
|
|
|
|
{/* 編集ダイアログ */}
|
|
|
<Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm">
|
|
|
<DialogTitle>
|
|
|
<Typography variant="h5" >
|
|
|
在庫の編集
|
|
|
</Typography>
|
|
|
</DialogTitle>
|
|
|
<DialogContent>
|
|
|
{editStock && (
|
|
|
<>
|
|
|
|
|
|
{/* 材料の詳細 */}
|
|
|
<Typography variant="h4">【{editStock.stuffName}】</Typography>
|
|
|
|
|
|
{/* 現在の数量フィールド */}
|
|
|
<TextField
|
|
|
label="現在の数量"
|
|
|
fullWidth
|
|
|
margin="normal"
|
|
|
name="amount"
|
|
|
type="number"
|
|
|
value={editStock.amount}
|
|
|
onChange={handleChange}
|
|
|
inputProps={{ min: 0 }}
|
|
|
onKeyDown={(e) => {
|
|
|
if (e.key === '-' || e.key === 'e' || e.key === 'E') {
|
|
|
e.preventDefault();
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
|
|
|
{/* 購入時数量フィールド */}
|
|
|
<TextField
|
|
|
label="購入時の数量"
|
|
|
fullWidth
|
|
|
margin="normal"
|
|
|
name="buyAmount"
|
|
|
type="number"
|
|
|
value={editStock.buyAmount}
|
|
|
onChange={handleChange}
|
|
|
inputProps={{ min: 0 }}
|
|
|
onKeyDown={(e) => {
|
|
|
if (e.key === '-' || e.key === 'e' || e.key === 'E') {
|
|
|
e.preventDefault();
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
|
|
|
{/* 購入価格フィールド */}
|
|
|
<TextField
|
|
|
label="購入価格"
|
|
|
fullWidth
|
|
|
margin="normal"
|
|
|
name="price"
|
|
|
type="number"
|
|
|
value={editStock.price}
|
|
|
onChange={handleChange}
|
|
|
inputProps={{ min: 0 }}
|
|
|
onKeyDown={(e) => {
|
|
|
if (e.key === '-' || e.key === 'e' || e.key === 'E') {
|
|
|
e.preventDefault();
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
|
|
|
{/* 購入店舗フィールド */}
|
|
|
<TextField
|
|
|
label="購入店舗"
|
|
|
fullWidth
|
|
|
margin="normal"
|
|
|
name="shop"
|
|
|
type="text"
|
|
|
value={editStock.shop}
|
|
|
onChange={handleChange}
|
|
|
/>
|
|
|
|
|
|
{/* 購入日・賞味期限入力 */}
|
|
|
<BuyExpDateSelect newStock={editStock} setNewStock={({ buyDate, expDate }) => setEditStock({ ...editStock, buyDate, expDate })} />
|
|
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 3, mb: 2 }}>
|
|
|
<Button onClick={() => { setIsEditOpen(false); setSelectedRow(null); }}>
|
|
|
キャンセル
|
|
|
</Button>
|
|
|
<Button variant="contained" color="success" onClick={handleApplyChanges} sx={{ mt: 3, mb: 2 }}>
|
|
|
変更を適用
|
|
|
</Button>
|
|
|
<Button variant="contained" color="error" onClick={handleOpenDelete} sx={{ mt: 3, mb: 2 }}>削除</Button>
|
|
|
</Box>
|
|
|
</>
|
|
|
)}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
|
|
|
{/* 削除ダイアログ */}
|
|
|
<Dialog open={isDeleteOpen}
|
|
|
onClose={handleCloseDelete}
|
|
|
fullWidth
|
|
|
maxWidth="sm"
|
|
|
sx={{ overflow: "hidden" }}
|
|
|
>
|
|
|
<DialogTitle>
|
|
|
<Typography variant="h5" >
|
|
|
食材の削除
|
|
|
</Typography>
|
|
|
</DialogTitle>
|
|
|
<DialogContent>
|
|
|
{selectedRow && (
|
|
|
<>
|
|
|
<Typography variant="h4">【{selectedRow.stuffName}】を削除します。</Typography>
|
|
|
<Typography variant="body1" color="error">⚠️ 注意: 削除すると復元できません。</Typography>
|
|
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 3, mb: 2 }}>
|
|
|
<Button onClick={() => {
|
|
|
setIsDeleteOpen(false);
|
|
|
// setSelectedRow(null);
|
|
|
}}>
|
|
|
キャンセル
|
|
|
</Button>
|
|
|
<Button
|
|
|
variant="contained"
|
|
|
color="error"
|
|
|
onClick={() => {
|
|
|
handleDeleteStock(selectedRow.stockId);
|
|
|
setIsDeleteOpen(false);
|
|
|
setSelectedRow(null);
|
|
|
setIsEditOpen(false);
|
|
|
}}
|
|
|
>
|
|
|
削除
|
|
|
</Button>
|
|
|
</Box>
|
|
|
</>
|
|
|
)}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<Container>
|
|
|
<Typography variant="h3" component="h1" sx={{ mb: 4 }} >
|
|
|
在庫リスト
|
|
|
</Typography>
|
|
|
|
|
|
<Box
|
|
|
sx={{
|
|
|
position: 'fixed', // ← sticky から fixed に変更
|
|
|
bottom: 55, // ← 下に固定
|
|
|
left: 0,
|
|
|
right: 0,
|
|
|
zIndex: 1300, // ダイアログよりは低く
|
|
|
backgroundColor: '#f5f5f5',
|
|
|
// backgroundColor: 'white',
|
|
|
// padding: 2,
|
|
|
px: 2,
|
|
|
py: 1,
|
|
|
display: 'flex',
|
|
|
justifyContent: 'flex-end', // ← 左寄せ
|
|
|
boxShadow: 'none', // 軽めの上向きシャドウ
|
|
|
}}
|
|
|
>
|
|
|
|
|
|
{/* 在庫の食材追加ボタン */}
|
|
|
<Box sx={{
|
|
|
display: 'flex', flexDirection: 'column', alignItems: 'flex-end',
|
|
|
mr: 2
|
|
|
}}>
|
|
|
<Typography variant="caption" color="textSecondary">
|
|
|
食材の追加
|
|
|
</Typography>
|
|
|
<Fab color="primary" onClick={handleOpenAdd} >
|
|
|
<AddIcon />
|
|
|
</Fab>
|
|
|
</Box>
|
|
|
|
|
|
{/* 新規タスク作成ダイアログ */}
|
|
|
<Dialog open={isAddOpen} onClose={handleCloseAdd} disableScrollLock={true}>
|
|
|
<Box display="flex" alignItems="center" >
|
|
|
<DialogTitle sx={{ flexGrow: 1 }}>
|
|
|
<Typography variant="h5" >
|
|
|
在庫に食材を追加
|
|
|
</Typography>
|
|
|
</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) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK
|
|
|
setNewStock({ ...newStock, amount: parsedValue }); // number型で保存
|
|
|
}
|
|
|
}}
|
|
|
// sx={{ width: "50%" }}
|
|
|
type="number"
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
|
|
|
/>
|
|
|
{/* 購入時数量入力フィールド */}
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="購入時の数量"
|
|
|
fullWidth
|
|
|
value={newStock.buyAmount}
|
|
|
onChange={(e) => {
|
|
|
const value = e.target.value;
|
|
|
const parsedValue = parseInt(value, 10); // 数値に変換
|
|
|
if (isNaN(parsedValue) || parsedValue >= 1) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は1以上のみOK
|
|
|
setNewStock({ ...newStock, buyAmount: 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) || parsedValue >= 0) { // 入力欄をいったん空欄にできるようにする,ただし空欄でない場合は0以上のみOK
|
|
|
setNewStock({ ...newStock, price: parsedValue }); // number型で保存
|
|
|
}
|
|
|
}}
|
|
|
// sx={{ width: "50%" }}
|
|
|
type="number"
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
|
|
|
/>
|
|
|
{/* 購入店舗入力フィールド */}
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="購入店舗"
|
|
|
fullWidth
|
|
|
value={newStock.shop}
|
|
|
onChange={(e) => setNewStock({ ...newStock, shop: e.target.value })}
|
|
|
/>
|
|
|
|
|
|
{/* 購入日・賞味期限入力 */}
|
|
|
<BuyExpDateSelect newStock={newStock} setNewStock={({ buyDate, expDate }) => setNewStock({ ...newStock, buyDate, expDate })} />
|
|
|
|
|
|
</Box>
|
|
|
</DialogContent>
|
|
|
<DialogActions>
|
|
|
<Button onClick={() => setIsAddOpen(false)}>キャンセル</Button>
|
|
|
<Button onClick={handleCreateStock} variant="contained">
|
|
|
追加
|
|
|
</Button>
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
</Box>
|
|
|
|
|
|
{/* 在庫一覧リスト */}
|
|
|
{/* 乳製品 */}
|
|
|
<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>
|
|
|
<Box sx={{ height: '80px' }} /> {/* フッターの高さと同じくらいに調整 */}
|
|
|
</Container>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default StockPage; |