|
|
/**
|
|
|
* テストページコンポーネント
|
|
|
* 白紙の状態で表示されるテスト用のページ
|
|
|
*/
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
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,
|
|
|
Typography,
|
|
|
Tooltip,
|
|
|
Fab,
|
|
|
Dialog,
|
|
|
DialogTitle,
|
|
|
DialogContent,
|
|
|
DialogActions,
|
|
|
TextField,
|
|
|
Button,
|
|
|
Box,
|
|
|
Checkbox,
|
|
|
FormControlLabel,
|
|
|
FormGroup,
|
|
|
FormControl,
|
|
|
InputLabel,
|
|
|
Select,
|
|
|
MenuItem,
|
|
|
} from '@mui/material';
|
|
|
import { STOCK_ERRORS } from '../constants/errorMessages';
|
|
|
/*import DatePicker, { registerLocale } from 'react-datepicker';
|
|
|
import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート*/
|
|
|
import { useMessage } from '../components/MessageContext';
|
|
|
|
|
|
/*// 日付を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,
|
|
|
price: 0,
|
|
|
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 { 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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 在庫リストに新規食材を作成するハンドラー
|
|
|
*/
|
|
|
const handleCreateStock = async () => {
|
|
|
try {
|
|
|
if (newStock.newAddition) {
|
|
|
newStock.stuffId = null;
|
|
|
}
|
|
|
if (isNaN(newStock.amount)) return;
|
|
|
if (isNaN(newStock.price)) return;
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 在庫リストを編集するハンドラー
|
|
|
*/
|
|
|
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);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* カテゴリー選択
|
|
|
*/
|
|
|
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(prev => (prev?.stockId === stock.stockId ? null : stock));
|
|
|
// };
|
|
|
// チェックボックス切り替え
|
|
|
const handleCheckboxChange = (stock: Stock) => {
|
|
|
setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock));
|
|
|
};
|
|
|
|
|
|
|
|
|
/** 編集ボタンを押したときにダイアログを開く */
|
|
|
// ダイアログを開く際に `selectedRow` の値を `editStock` にセット
|
|
|
const handleOpenEdit = () => {
|
|
|
if (selectedRow) {
|
|
|
setEditStock({ ...selectedRow });
|
|
|
setIsEditOpen(true);
|
|
|
} else {
|
|
|
showWarningMessage("編集する食材を選択してください。");
|
|
|
}
|
|
|
};
|
|
|
// 変更を適用. 数量に0を入力したとき、削除ダイアログに飛ぶ機能を追加
|
|
|
const handleApplyChanges = async () => {
|
|
|
if (!editStock) return;
|
|
|
|
|
|
try {
|
|
|
if (Number(editStock.amount) === 0) {
|
|
|
// 数量が 0 の場合は削除処理へ誘導
|
|
|
setIsEditOpen(false); // 編集ダイアログを閉じる
|
|
|
setSelectedRow(editStock); // 削除対象をセット
|
|
|
setIsDeleteOpen(true); // 削除ダイアログを開く
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
await handleUpdateStock(
|
|
|
editStock.stockId,
|
|
|
Number(editStock.amount),
|
|
|
Number(editStock.price),
|
|
|
editStock.buyDate,
|
|
|
editStock.expDate
|
|
|
);
|
|
|
|
|
|
setSelectedRow(editStock); // 更新後に選択行を反映
|
|
|
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>) => {
|
|
|
const { name, value } = event.target;
|
|
|
|
|
|
// 数値項目に対して負の値をブロック
|
|
|
const numericFields = ['amount', 'price'];
|
|
|
if (numericFields.includes(name) && Number(value) < 0) {
|
|
|
return; // 無視して更新しない
|
|
|
}
|
|
|
|
|
|
if (editStock) {
|
|
|
setEditStock({
|
|
|
...editStock,
|
|
|
[name]: value,
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
/** 編集ダイアログを閉じる */
|
|
|
const handleCloseEdit = () => {
|
|
|
setIsEditOpen(false);
|
|
|
};
|
|
|
|
|
|
/** 削除ボタンを押したときにダイアログを開く */
|
|
|
const handleOpenDelete = () => {
|
|
|
if (selectedRow) {
|
|
|
setIsDeleteOpen(true);
|
|
|
} else {
|
|
|
showWarningMessage("削除する食材を選択してください。");
|
|
|
}
|
|
|
};
|
|
|
/** 削除ダイアログを閉じる */
|
|
|
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 padding="checkbox" />
|
|
|
<TableCell>食材名</TableCell>
|
|
|
<TableCell>数量</TableCell>
|
|
|
<TableCell>消費・賞味期限</TableCell>
|
|
|
</TableRow>
|
|
|
</TableHead>
|
|
|
<TableBody>
|
|
|
{filteredStocks.map(stock => {
|
|
|
const isSelected = selectedRow?.stockId === stock.stockId;
|
|
|
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}
|
|
|
sx={{
|
|
|
backgroundColor: isSelected ? 'yellow' : 'white',
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
<TableCell padding="checkbox">
|
|
|
<Checkbox
|
|
|
checked={isSelected}
|
|
|
onChange={() => handleCheckboxChange(stock)}
|
|
|
/>
|
|
|
</TableCell>
|
|
|
<TableCell>{stock.stuffName}</TableCell>
|
|
|
<TableCell>{stock.amount}</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>
|
|
|
<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="price"
|
|
|
type="number"
|
|
|
value={editStock.price}
|
|
|
onChange={handleChange}
|
|
|
inputProps={{ min: 0 }}
|
|
|
onKeyDown={(e) => {
|
|
|
if (e.key === '-' || e.key === 'e' || e.key === 'E') {
|
|
|
e.preventDefault();
|
|
|
}
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
{/* 購入日・消費期限を横並びに */}
|
|
|
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
|
|
|
{/* 購入日 */}
|
|
|
<TextField
|
|
|
margin="normal"
|
|
|
label="購入日"
|
|
|
type="date"
|
|
|
fullWidth
|
|
|
name="buyDate"
|
|
|
value={editStock.buyDate ? editStock.buyDate.substring(0, 10) : ''}
|
|
|
onChange={(e) => setEditStock({ ...editStock, buyDate: e.target.value })}
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
/>
|
|
|
{/*<DatePicker
|
|
|
selected={editStock.buyDate ? new Date(editStock.buyDate) : null}
|
|
|
onChange={(date) => {
|
|
|
if (editStock) {
|
|
|
setEditStock({
|
|
|
...editStock,
|
|
|
buyDate: date ? formatDateLocal(date) : '',
|
|
|
});
|
|
|
}
|
|
|
}}
|
|
|
dateFormat="yyyy/MM/dd"
|
|
|
customInput={
|
|
|
<TextField
|
|
|
margin="normal"
|
|
|
label="購入日 (yyyy/MM/dd)"
|
|
|
fullWidth
|
|
|
name="buyDate"
|
|
|
/>
|
|
|
}
|
|
|
isClearable
|
|
|
/>*/}
|
|
|
{/* 消費・賞味期限 */}
|
|
|
<TextField
|
|
|
margin="normal"
|
|
|
label="消費・賞味期限"
|
|
|
type="date"
|
|
|
fullWidth
|
|
|
name="expDate"
|
|
|
value={editStock.expDate ? editStock.expDate.substring(0, 10) : ''}
|
|
|
onChange={(e) => setEditStock({ ...editStock, expDate: e.target.value })}
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
InputProps={{
|
|
|
inputProps: {
|
|
|
min: newStock.buyDate ? newStock.buyDate.substring(0, 10) : undefined,
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
|
|
|
{/*<DatePicker
|
|
|
selected={editStock.expDate ? new Date(editStock.expDate) : null}
|
|
|
onChange={(date) => {
|
|
|
if (editStock) {
|
|
|
setEditStock({
|
|
|
...editStock,
|
|
|
expDate: date ? formatDateLocal(date) : '',
|
|
|
});
|
|
|
}
|
|
|
}}
|
|
|
dateFormat="yyyy/MM/dd"
|
|
|
customInput={
|
|
|
<TextField
|
|
|
margin="normal"
|
|
|
label="消費・賞味期限 (yyyy/MM/dd)"
|
|
|
fullWidth
|
|
|
name="expDate"
|
|
|
/>
|
|
|
}
|
|
|
isClearable
|
|
|
/>*/}
|
|
|
</Box>
|
|
|
<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}>
|
|
|
変更を適用
|
|
|
</Button>
|
|
|
</Box>
|
|
|
</>
|
|
|
)}
|
|
|
</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>
|
|
|
|
|
|
<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);
|
|
|
}}
|
|
|
>
|
|
|
削除
|
|
|
</Button>
|
|
|
</Box>
|
|
|
</>
|
|
|
)}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
|
|
|
return (
|
|
|
<Container>
|
|
|
<Typography variant="h4" component="h1" gutterBottom>
|
|
|
在庫一覧
|
|
|
</Typography>
|
|
|
|
|
|
{/* <Box sx={{ textAlign: 'right' }}> */}
|
|
|
<Box
|
|
|
sx={{
|
|
|
position: 'sticky',
|
|
|
top: 0,
|
|
|
zIndex: 1000,
|
|
|
backgroundColor: '#f5f5f5',
|
|
|
padding: 2,
|
|
|
display: 'flex',
|
|
|
gap: 0.5,
|
|
|
justifyContent: 'flex-end', // ← 右寄せ
|
|
|
borderBottom: 'none', // ← これで線を消す
|
|
|
boxShadow: 'none', // ← 影も消す
|
|
|
}}
|
|
|
>
|
|
|
|
|
|
{/* 在庫の食材追加ボタン */}
|
|
|
<Button variant="contained" color="primary" onClick={handleOpenAdd} sx={{ mt: 3, mb: 2, mr: 1 }}>
|
|
|
追加
|
|
|
</Button>
|
|
|
|
|
|
{/* 新規タスク作成ダイアログ */}
|
|
|
<Dialog open={isAddOpen} onClose={handleCloseAdd} 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) || 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.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]*" }} // ここで整数のみ許可
|
|
|
/>
|
|
|
|
|
|
{/* 購入日・消費期限を横並びに */}
|
|
|
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
|
|
|
{/* 購入日入力フィールド */}
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="購入日"
|
|
|
type="date"
|
|
|
fullWidth
|
|
|
value={newStock.buyDate ? newStock.buyDate.substring(0, 10) : ''}
|
|
|
onChange={(e) =>
|
|
|
setNewStock({ ...newStock, buyDate: e.target.value })
|
|
|
}
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
/>
|
|
|
{/*<DatePicker
|
|
|
popperClassName="custom-datepicker-popper"
|
|
|
selected={newStock.buyDate ? new Date(newStock.buyDate) : null}
|
|
|
onChange={(date) =>
|
|
|
setNewStock({ ...newStock, buyDate: date ? formatDateLocal(date) : '' })
|
|
|
}
|
|
|
dateFormat="yyyy/MM/dd"
|
|
|
customInput={
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="購入日(yyyy/MM/dd)"
|
|
|
fullWidth
|
|
|
/>
|
|
|
}
|
|
|
isClearable
|
|
|
//withPortal // ← 他の文字との重なり対策
|
|
|
/>*/}
|
|
|
{/* 消費・賞味期限入力フィールド */}
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="消費・賞味期限"
|
|
|
type="date"
|
|
|
fullWidth
|
|
|
value={newStock.expDate ? newStock.expDate.substring(0, 10) : ''}
|
|
|
onChange={(e) =>
|
|
|
setNewStock({ ...newStock, expDate: e.target.value })
|
|
|
}
|
|
|
InputLabelProps={{ shrink: true }}
|
|
|
InputProps={{
|
|
|
inputProps: {
|
|
|
min: newStock.buyDate ? newStock.buyDate.substring(0, 10) : undefined,
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
{/*<DatePicker
|
|
|
popperClassName="custom-datepicker-popper"
|
|
|
selected={newStock.expDate ? new Date(newStock.expDate) : null}
|
|
|
onChange={(date) =>
|
|
|
setNewStock({ ...newStock, expDate: date ? formatDateLocal(date) : '' })
|
|
|
}
|
|
|
dateFormat="yyyy/MM/dd"
|
|
|
customInput={
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="消費・賞味期限(yyyy/MM/dd)"
|
|
|
fullWidth
|
|
|
/>
|
|
|
}
|
|
|
isClearable
|
|
|
//withPortal
|
|
|
/>*/}
|
|
|
</Box>
|
|
|
</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, mr: 1
|
|
|
}}>
|
|
|
詳細・編集
|
|
|
</Button>
|
|
|
|
|
|
{/* 在庫の食材削除ボタン (全テーブル共通) */}
|
|
|
<Button variant="contained" color="error" onClick={handleOpenDelete} sx={{ mt: 3, mb: 2 }}>削除</Button>
|
|
|
|
|
|
</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>
|
|
|
</Container>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default StockPage; |