Merge remote-tracking branch 'origin/feature-frontend-stock-suzuki' into develop-frontend

feature-frontend-top
Masaharu.Kato 4 months ago
commit 6fe89f8023
  1. 188
      frontend/src/pages/StockPage.tsx

@ -27,6 +27,7 @@ import {
MenuItem, MenuItem,
} from '@mui/material'; } from '@mui/material';
import { GENERAL_ERRORS, STOCK_ERRORS } from '../constants/errorMessages'; import { GENERAL_ERRORS, STOCK_ERRORS } from '../constants/errorMessages';
import { Add as AddIcon } from '@mui/icons-material';
import DatePicker, { registerLocale } from 'react-datepicker'; import DatePicker, { registerLocale } from 'react-datepicker';
import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート import { ja } from 'date-fns/locale/ja'; // date-fnsの日本語ロケールをインポート
import { useMessage } from '../components/MessageContext'; import { useMessage } from '../components/MessageContext';
@ -115,6 +116,27 @@ const StockPage: React.FC = () => {
newStock.shop = newStock.shop || null; newStock.shop = newStock.shop || null;
console.log(newStock) console.log(newStock)
// 購入日と消費・賞味期限の整合性チェック
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 today = new Date().toISOString().substring(0, 10);
const updatedStock = { ...newStock, lastUpdate: today }; // lastUpdate に today を設定 const updatedStock = { ...newStock, lastUpdate: today }; // lastUpdate に today を設定
@ -197,37 +219,42 @@ const StockPage: React.FC = () => {
}; };
/** /**
* . *
*/ */
// const handleRowClick = (stock: Stock) => { const handleRowClick = (stock: Stock) => {
// setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock)); setSelectedRow(stock); // 行選択
// }; setEditStock({ ...stock }); // 編集対象にセット
// チェックボックス切り替え setIsEditOpen(true); // 編集ダイアログを開く
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を入力したとき、削除ダイアログに飛ぶ機能を追加 // 変更を適用. 数量に0を入力したとき、削除ダイアログに飛ぶ機能を追加
const handleApplyChanges = async () => { const handleApplyChanges = async () => {
if (!editStock) return; if (!editStock) return;
const {stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate} = editStock; const {stockId, amount, buyAmount, price, shop, buyDate, expDate, lastUpdate} = editStock;
// 購入日が消費・賞味期限より未来の場合はエラー表示
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 { try {
if (amount === 0) { if (amount === 0) {
// 数量が 0 の場合は削除処理へ誘導 // 数量が 0 の場合は削除処理へ誘導
setIsEditOpen(false); // 編集ダイアログを閉じる // setIsEditOpen(false); // 編集ダイアログを閉じる
setSelectedRow(editStock); // 削除対象をセット setSelectedRow(editStock); // 削除対象をセット
setIsDeleteOpen(true); // 削除ダイアログを開く setIsDeleteOpen(true); // 削除ダイアログを開く
return; return;
@ -297,7 +324,6 @@ const StockPage: React.FC = () => {
} }
}; };
/** 編集ダイアログを閉じる */ /** 編集ダイアログを閉じる */
const handleCloseEdit = () => { const handleCloseEdit = () => {
setIsEditOpen(false); setIsEditOpen(false);
@ -308,7 +334,8 @@ const StockPage: React.FC = () => {
if (selectedRow) { if (selectedRow) {
setIsDeleteOpen(true); setIsDeleteOpen(true);
} else { } else {
showWarningMessage("削除する食材を選択してください。"); // showWarningMessage("削除する食材を選択してください。");
showErrorMessage('削除する食材を選択してください.');
} }
}; };
/** 削除ダイアログを閉じる */ /** 削除ダイアログを閉じる */
@ -328,37 +355,38 @@ const StockPage: React.FC = () => {
<Table> <Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}> <TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow> <TableRow>
<TableCell padding="checkbox" /> <TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}></TableCell>
<TableCell></TableCell> <TableCell align="center" sx={{ width: '20%', fontSize: '16px' }}></TableCell>
<TableCell></TableCell> <TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}></TableCell>
<TableCell></TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{filteredStocks.map(stock => { {filteredStocks.map(stock => {
const isSelected = selectedRow?.stockId === stock.stockId;
const today = new Date(); const today = new Date();
const expDate = new Date(stock.expDate); const expDate = new Date(stock.expDate);
const timeDiff = expDate.getTime() - today.getTime(); // 時間をリセットして純粋な“日付のみ”のタイムスタンプを取得
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();
const daysLeft = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); const 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 ( return (
<TableRow <TableRow
key={stock.stockId} key={stock.stockId}
sx={{ onClick={() => handleRowClick(stock)}
backgroundColor: isSelected ? 'yellow' : 'white', style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }}
}}
> >
<TableCell padding="checkbox"> <TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}>{stock.stuffName}</TableCell>
<Checkbox <TableCell align="center" sx={{ width: '20%', fontSize: '16px' }}>{stock.amount}</TableCell>
checked={isSelected} <TableCell align="center" sx={{ width: '40%', fontSize: '16px' }}
onChange={() => handleCheckboxChange(stock)}
/>
</TableCell>
<TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell
style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}} style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}}
> >
{formatDate(stock.expDate)} {formatDate(stock.expDate)}
@ -370,17 +398,20 @@ const StockPage: React.FC = () => {
</Table> </Table>
</TableContainer> </TableContainer>
{/* 編集ダイアログ */} {/* 編集ダイアログ */}
<Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm"> <Dialog open={isEditOpen} onClose={handleCloseEdit} fullWidth maxWidth="sm">
<DialogTitle></DialogTitle> <DialogTitle>
<Typography variant="h5" >
</Typography>
</DialogTitle>
<DialogContent> <DialogContent>
{editStock && ( {editStock && (
<> <>
{/* 材料名 */} {/* 材料名 */}
<Typography variant="h4">{editStock.stuffName}</Typography> <Typography variant="h4">{editStock.stuffName}</Typography>
<TextField <TextField
label="現在の数量" label="現在の数量"
fullWidth fullWidth
@ -490,9 +521,10 @@ const StockPage: React.FC = () => {
<Button onClick={() => { setIsEditOpen(false); setSelectedRow(null); }}> <Button onClick={() => { setIsEditOpen(false); setSelectedRow(null); }}>
</Button> </Button>
<Button variant="contained" color="success" onClick={handleApplyChanges}> <Button variant="contained" color="success" onClick={handleApplyChanges} sx={{ mt: 3, mb: 2 }}>
</Button> </Button>
<Button variant="contained" color="error" onClick={handleOpenDelete} sx={{ mt: 3, mb: 2 }}></Button>
</Box> </Box>
</> </>
)} )}
@ -506,7 +538,11 @@ const StockPage: React.FC = () => {
maxWidth="sm" maxWidth="sm"
sx={{ overflow: "hidden" }} sx={{ overflow: "hidden" }}
> >
<DialogTitle></DialogTitle> <DialogTitle>
<Typography variant="h5" >
</Typography>
</DialogTitle>
<DialogContent> <DialogContent>
{selectedRow && ( {selectedRow && (
<> <>
@ -516,7 +552,7 @@ const StockPage: React.FC = () => {
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 3, mb: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 3, mb: 2 }}>
<Button onClick={() => { <Button onClick={() => {
setIsDeleteOpen(false); setIsDeleteOpen(false);
setSelectedRow(null); // setSelectedRow(null);
}}> }}>
</Button> </Button>
@ -527,6 +563,7 @@ const StockPage: React.FC = () => {
handleDeleteStock(selectedRow.stockId); handleDeleteStock(selectedRow.stockId);
setIsDeleteOpen(false); setIsDeleteOpen(false);
setSelectedRow(null); setSelectedRow(null);
setIsEditOpen(false);
}} }}
> >
@ -540,38 +577,51 @@ const StockPage: React.FC = () => {
); );
}; };
return ( return (
<Container> <Container>
<Typography variant="h4" component="h1" gutterBottom> <Typography variant="h3" component="h1" sx={{ mb: 4 }} >
</Typography> </Typography>
{/* <Box sx={{ textAlign: 'right' }}> */}
<Box <Box
sx={{ sx={{
position: 'sticky', position: 'fixed', // ← sticky から fixed に変更
top: 0, bottom: 55, // ← 下に固定
zIndex: 1000, left: 0,
right: 0,
zIndex: 1300, // ダイアログよりは低く
backgroundColor: '#f5f5f5', backgroundColor: '#f5f5f5',
padding: 2, // backgroundColor: 'white',
// padding: 2,
px: 2,
py: 1,
display: 'flex', display: 'flex',
gap: 0.5, justifyContent: 'flex-end', // ← 左寄せ
justifyContent: 'flex-end', // ← 右寄せ boxShadow: 'none', // 軽めの上向きシャドウ
borderBottom: 'none', // ← これで線を消す
boxShadow: 'none', // ← 影も消す
}} }}
> >
{/* 在庫の食材追加ボタン */} {/* 在庫の食材追加ボタン */}
<Button variant="contained" color="primary" onClick={handleOpenAdd} sx={{ mt: 3, mb: 2, mr: 1 }}> <Box sx={{
display: 'flex', flexDirection: 'column', alignItems: 'flex-end',
</Button> mr: 2,
}}>
<Typography variant="caption" color="textSecondary">
</Typography>
<Fab color="primary" onClick={handleOpenAdd} >
<AddIcon />
</Fab>
</Box>
{/* 新規タスク作成ダイアログ */} {/* 新規タスク作成ダイアログ */}
<Dialog open={isAddOpen} onClose={handleCloseAdd} disableScrollLock={true}> <Dialog open={isAddOpen} onClose={handleCloseAdd} disableScrollLock={true}>
<Box display="flex" alignItems="center" > <Box display="flex" alignItems="center" >
<DialogTitle sx={{ flexGrow: 1 }}></DialogTitle> <DialogTitle sx={{ flexGrow: 1 }}>
<Typography variant="h5" >
</Typography>
</DialogTitle>
<FormGroup row> <FormGroup row>
<FormControlLabel <FormControlLabel
control={<Checkbox />} control={<Checkbox />}
@ -708,17 +758,6 @@ const StockPage: React.FC = () => {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </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> </Box>
{/* 在庫一覧リスト */} {/* 在庫一覧リスト */}
@ -751,6 +790,7 @@ const StockPage: React.FC = () => {
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}> <div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{StockTable(stocks, ["その他"])} {StockTable(stocks, ["その他"])}
</div> </div>
<Box sx={{ height: '80px' }} /> {/* フッターの高さと同じくらいに調整 */}
</Container> </Container>
); );
}; };

Loading…
Cancel
Save