Merge branch 'develop' into develop-backend

feature-backend-tobuy-history
Masaharu.Kato 4 months ago
commit c6cb147b89
  1. 7
      backend/src/main/java/com/example/todoapp/controller/ToBuysController.java
  2. 3
      backend/src/main/java/com/example/todoapp/service/StocksService.java
  3. 28
      backend/src/main/java/com/example/todoapp/service/ToBuysService.java
  4. 20
      backend/src/main/resources/application-localfwd.yml
  5. 15
      frontend/src/components/BuyDialog.tsx
  6. 165
      frontend/src/components/Layout.tsx
  7. 29
      frontend/src/components/MessageAlert.tsx
  8. 17
      frontend/src/components/MessageContext.tsx
  9. 88
      frontend/src/pages/AddRecipe.tsx
  10. 7
      frontend/src/pages/DishList.tsx
  11. 9
      frontend/src/pages/RecipeList.tsx
  12. 250
      frontend/src/pages/StockPage.tsx
  13. 20
      frontend/src/pages/TaskListPage.tsx

@ -92,7 +92,6 @@ public class ToBuysController {
} }
/** /**
* 指定されたユーザーIDに基づいてすべての買うものリストを取得する * 指定されたユーザーIDに基づいてすべての買うものリストを取得する
* *
@ -125,9 +124,6 @@ public class ToBuysController {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// Map<String, Object> responseBody = new HashMap<>();
// responseBody.put("tobuy_array", responseList);
return ResponseEntity.ok(responseList); return ResponseEntity.ok(responseList);
} }
@ -189,7 +185,8 @@ public class ToBuysController {
Authentication authentication) { Authentication authentication) {
Long recipeId = payload.get("recipeId"); Long recipeId = payload.get("recipeId");
List<ToBuyResponseDTO> responseList = toBuysService.addByRecipeId(recipeId, authentication); Long servings = payload.get("servings");
List<ToBuyResponseDTO> responseList = toBuysService.addByRecipeId(recipeId, servings,authentication);
//shopのフィールドを削除 //shopのフィールドを削除
List<Map<String, Object>> filteredList = responseList.stream() List<Map<String, Object>> filteredList = responseList.stream()

@ -56,9 +56,6 @@ public class StocksService {
} else { } else {
// 材料情報を取得 // 材料情報を取得
Optional<Stuffs> existstuffs = stuffsRepository.findById(stock.getStuffId()); Optional<Stuffs> existstuffs = stuffsRepository.findById(stock.getStuffId());
if (existstuffs == null) {
throw new RuntimeException("材料がありません");
}
stuffs = existstuffs.get(); stuffs = existstuffs.get();
} }

@ -91,14 +91,22 @@ public class ToBuysService {
stuff = optStuff.get(); stuff = optStuff.get();
} }
ToBuys toBuys = new ToBuys(); Optional<ToBuys> existingToBuy = toBuysRepository.findByUserAndStuff(user, stuff);
toBuys.setUser(user);
toBuys.setStuff(stuff);
toBuys.setAmount(toBuyDTO.getAmount());
toBuys.setStore(toBuyDTO.getShop());
// データベースに保存 if (existingToBuy.isPresent()) {
return toBuysRepository.save(toBuys); // 存在する場合は数量を更新
ToBuys existing = existingToBuy.get();
existing.setAmount(existing.getAmount() + toBuyDTO.getAmount());
return toBuysRepository.save(existing);
} else {
// 新しい材料を作成
ToBuys toBuys = new ToBuys();
toBuys.setUser(user);
toBuys.setStuff(stuff);
toBuys.setAmount(toBuyDTO.getAmount());
toBuys.setStore(toBuyDTO.getShop());
return toBuysRepository.save(toBuys);
}
} }
@ -212,7 +220,7 @@ public class ToBuysService {
* @param authentication 認証情報 * @param authentication 認証情報
* @return 追加された買うもののリスト * @return 追加された買うもののリスト
*/ */
public List<ToBuyResponseDTO> addByRecipeId(Long recipeId, Authentication authentication) { public List<ToBuyResponseDTO> addByRecipeId(Long recipeId, Long servings,Authentication authentication) {
// ユーザー情報を取得 // ユーザー情報を取得
String username = authentication.getName(); String username = authentication.getName();
User user = userRepository.findByUsername(username) User user = userRepository.findByUsername(username)
@ -225,7 +233,9 @@ public class ToBuysService {
for (RecipeStuffs rs : recipeStuffsList) { for (RecipeStuffs rs : recipeStuffsList) {
Stuffs stuff = rs.getStuff(); Stuffs stuff = rs.getStuff();
int requiredAmount = rs.getAmount();
// 材料の数量をサービング数に基づいて計算
int requiredAmount = rs.getAmount() * (servings != null ? servings.intValue() : 1);
Optional<ToBuys> existingToBuyOpt = toBuysRepository.findByUserAndStuff(user, stuff); Optional<ToBuys> existingToBuyOpt = toBuysRepository.findByUserAndStuff(user, stuff);

@ -0,0 +1,20 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/${LOCAL_DB_NAME}
driver-class-name: org.postgresql.Driver
username: ${LOCAL_DB_USER}
password: ${LOCAL_DB_PASSWORD}
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
server:
address: 0.0.0.0
port: 8080
cors:
allowed-origins: http://${WIN_IP}:3000

@ -26,12 +26,14 @@ const formatDateLocal = (date: Date) => {
const BuyDialog = ({ const BuyDialog = ({
openDialog, openDialog,
setOpenDialog, setOpenDialog,
stuffName,
newStock, newStock,
setNewStock, setNewStock,
onSubmit, onSubmit,
}: { }: {
openDialog: boolean, openDialog: boolean,
setOpenDialog: (open: boolean) => void, setOpenDialog: (open: boolean) => void,
stuffName: string,
newStock: NewStock, newStock: NewStock,
setNewStock: (tobuy: NewStock) => void, setNewStock: (tobuy: NewStock) => void,
onSubmit: () => void, onSubmit: () => void,
@ -40,11 +42,21 @@ const BuyDialog = ({
return ( return (
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true} PaperProps={{ sx: { minHeight: '500px', maxHeight: '80vh' } }} <Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true} PaperProps={{ sx: { minHeight: '600px', maxHeight: '80vh' } }}
> >
<DialogTitle></DialogTitle> <DialogTitle></DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ pt: 1 }}> <Box sx={{ pt: 1 }}>
{/* 材料名表示 */}
<TextField
margin="dense"
label="材料名"
fullWidth
value={stuffName}
disabled
sx={{ marginBottom: 2 , marginTop: 2}}
/>
{/* 価格入力フィールド */} {/* 価格入力フィールド */}
<TextField <TextField
autoFocus autoFocus
@ -58,6 +70,7 @@ const BuyDialog = ({
setNewStock({ ...newStock, price: value }) setNewStock({ ...newStock, price: value })
}; };
}} }}
sx={{ marginBottom: 2 }}
/> />
{/* 購入日・消費期限を横並びに */} {/* 購入日・消費期限を横並びに */}
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}> <Box sx={{ display: 'flex', gap: 2, mb: 2 }}>

@ -2,7 +2,7 @@
* *
* AppBar * AppBar
*/ */
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
AppBar, AppBar,
Toolbar, Toolbar,
@ -16,7 +16,11 @@ import {
ListItemIcon, ListItemIcon,
ListItemButton, ListItemButton,
Divider, Divider,
IconButton IconButton,
AlertColor,
BottomNavigation,
BottomNavigationAction,
Paper
} from '@mui/material'; } from '@mui/material';
import { import {
Menu as MenuIcon, Menu as MenuIcon,
@ -24,13 +28,17 @@ import {
Inventory as InventoryIcon, // テストページ用のアイコン Inventory as InventoryIcon, // テストページ用のアイコン
Science as ScienceIcon, // 鈴木 Science as ScienceIcon, // 鈴木
SoupKitchen as SoupKitchenIcon, SoupKitchen as SoupKitchenIcon,
ShoppingCart as ShoppingCartIcon
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useNavigate, Outlet, useLocation } from 'react-router-dom'; import { useNavigate, Outlet, useLocation } from 'react-router-dom';
import { MessageContext } from './MessageContext';
import MessageAlert from './MessageAlert';
const Layout: React.FC = () => { const Layout: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const [drawerOpen, setDrawerOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false);
const [bottomNavi, setBottomNavi] = useState(1);
/** /**
* *
@ -41,39 +49,69 @@ const Layout: React.FC = () => {
navigate('/login'); navigate('/login');
}; };
/**
*
*
*/
const handleNavigate = (path: string) => {
navigate(path);
setDrawerOpen(false);
};
// 現在のパスに基づいてメニュー項目が選択状態かどうかを判定
const isSelected = (path: string): boolean => {
return location.pathname === path;
};
// メニューを開閉するハンドラー // メニューを開閉するハンドラー
const toggleDrawer = () => { const toggleDrawer = () => {
setDrawerOpen(!drawerOpen); setDrawerOpen(!drawerOpen);
}; };
// メッセージ表示
// ページ遷移後もメッセージを維持
useEffect(() => {
const saved = sessionStorage.getItem('globalMessage');
if (saved) {
const { message, severity } = JSON.parse(saved);
showMessage(message, severity);
}
}, []);
const [msgOpen, setMsgOpen] = useState(false);
const [msgText, setMsgText] = useState('');
const [msgType, setMsgType] = useState<AlertColor>('info');
const showMessage = (msg: string, sev: AlertColor) => {
setMsgText(msg);
setMsgType(sev);
setMsgOpen(true);
sessionStorage.setItem('globalMessage', JSON.stringify({ message: msg, severity: sev }));
};
const showErrorMessage = (message: string) => showMessage(message, 'error');
const showWarningMessage = (message: string) => showMessage(message, 'warning');
const showInfoMessage = (message: string) => showMessage(message, 'info');
const showSuccessMessage = (message: string) => showMessage(message, 'success');
const handleMsgClose = () => {
setMsgOpen(false);
// setMsgText(''); // ここで空にすると,メッセージが消えるアニメーションが始まる時点で文字が消えてしまう
sessionStorage.removeItem('globalMessage');
};
const handleBottomNavigation = (event: React.SyntheticEvent, newValue: any) => {
setBottomNavi(newValue);
switch(newValue) {
case 0:
navigate('stock');
break;
case 1:
navigate('tasks');
break;
case 2:
navigate('recipeList');
break;
}
// ここでルーティング処理などを行う
}
return ( return (
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}> <Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
{/* ヘッダー部分 - アプリ名とログアウトボタンを表示 */} {/* ヘッダー部分 - アプリ名とログアウトボタンを表示 */}
<AppBar position="static" elevation={0}> <AppBar position="static" elevation={0}>
<Toolbar> <Toolbar>
<IconButton
edge="start"
color="inherit"
aria-label="menu"
onClick={toggleDrawer}
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
shopchop shopchop
</Typography> </Typography>
@ -83,57 +121,46 @@ const Layout: React.FC = () => {
</Toolbar> </Toolbar>
</AppBar> </AppBar>
{/* サイドメニュー */} <Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={3}>
<Drawer <BottomNavigation
anchor="left" showLabels
open={drawerOpen} value={bottomNavi}
onClose={() => setDrawerOpen(false)} onChange={(event, newValue) => {
> setBottomNavi(newValue);
<Box switch(newValue) {
sx={{ width: 250 }} case 0:
role="presentation" navigate('stock');
break;
case 1:
navigate('tasks');
break;
case 2:
navigate('recipeList');
break;
}
// ここでルーティング処理などを行う
}}
> >
<List> <BottomNavigationAction label="在庫" icon={<InventoryIcon />} />
<ListItemButton <BottomNavigationAction label="買うもの" icon={<ShoppingCartIcon />} />
onClick={() => handleNavigate('/tasks')} <BottomNavigationAction label="レシピ" icon={<SoupKitchenIcon />} />
selected={isSelected('/tasks')} </BottomNavigation>
> </Paper>
<ListItemIcon><ListAltIcon /></ListItemIcon>
<ListItemText primary="買うものリスト" />
</ListItemButton>
{/* テストページへのリンクを追加 */}
{/* 在庫リストへのリンクを追加 */}
<ListItemButton
onClick={() => handleNavigate('/addRecipe')}
selected={isSelected('/addRecipe')}
>
<ListItemIcon><SoupKitchenIcon /></ListItemIcon>
<ListItemText primary="料理の追加" />
</ListItemButton>
<ListItemButton
onClick={() => handleNavigate('/recipeList')}
selected={isSelected('/recipeList')}
>
<ListItemIcon><ScienceIcon /></ListItemIcon>
<ListItemText primary="料理リスト" />
</ListItemButton>
<ListItemButton
onClick={() => handleNavigate('/stock')}
selected={isSelected('/stock')}
>
<ListItemIcon><InventoryIcon /></ListItemIcon>
<ListItemText primary="在庫管理" />
</ListItemButton>
<Divider />
</List>
</Box>
</Drawer>
{/* メインコンテンツ領域 - 子ルートのコンポーネントがここに表示される */} {/* メインコンテンツ領域 - 子ルートのコンポーネントがここに表示される */}
<Box component="main" sx={{ flexGrow: 1, bgcolor: 'background.default', py: 3 }}> <Box component="main" sx={{ flexGrow: 1, bgcolor: 'background.default', py: 3 }}>
<Container> <Container>
<Outlet /> {/* React Router の Outlet - 子ルートのコンポーネントがここにレンダリングされる */}
<MessageContext.Provider value={{ showErrorMessage, showWarningMessage, showSuccessMessage, showInfoMessage }}>
<MessageAlert
open={msgOpen}
message={msgText}
severity={msgType}
onClose={handleMsgClose}
/>
<Outlet /> {/* React Router の Outlet - 子ルートのコンポーネントがここにレンダリングされる */}
</MessageContext.Provider>
</Container> </Container>
</Box> </Box>
</Box> </Box>

@ -0,0 +1,29 @@
import React from 'react';
import { Snackbar, Alert, AlertColor } from '@mui/material';
interface MessageAlertProps {
open: boolean;
message: string;
severity: AlertColor; // 'error' | 'warning' | 'info' | 'success'
onClose: (event?: React.SyntheticEvent | Event, reason?: string) => void;
duration?: number;
}
const MessageAlert: React.FC<MessageAlertProps> = ({
open,
message,
severity,
onClose,
duration = 6000,
}) => {
return (
<Snackbar open={open} autoHideDuration={duration} onClose={onClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} sx={{bottom: '120px'}}>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
);
};
export default MessageAlert;

@ -0,0 +1,17 @@
import React, { createContext, useContext } from 'react';
export interface MessageContextType {
showErrorMessage: (message: string) => void;
showWarningMessage: (message: string) => void;
showSuccessMessage: (message: string) => void;
showInfoMessage: (message: string) => void;
}
export const MessageContext = createContext<MessageContextType | undefined>(undefined);
export const useMessage = () => {
const context = useContext(MessageContext);
if (!context) throw new Error('useMessage must be used within MessageContext.Provider');
return context;
};

@ -25,7 +25,8 @@ import {
Select, Select,
FormControl, FormControl,
InputLabel, InputLabel,
ListItemIcon ListItemIcon,
AlertColor
} from '@mui/material'; } from '@mui/material';
import { import {
Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon, Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon,
@ -36,6 +37,8 @@ import { StuffAndCategoryAndAmount } from '../types/types';
import EditAmountDialog from '../components/EditAmountDialog'; import EditAmountDialog from '../components/EditAmountDialog';
import { recipeApi, toBuyApi } from '../services/api'; import { recipeApi, toBuyApi } from '../services/api';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import MessageAlert from '../components/MessageAlert';
import { useMessage } from '../components/MessageContext';
const AddRecipe: React.FC = () => { const AddRecipe: React.FC = () => {
const { recipeId: recipeIdStr } = useParams(); const { recipeId: recipeIdStr } = useParams();
@ -43,6 +46,8 @@ const AddRecipe: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
// 編集時,既存情報を読み込んだかどうか
const [recipeLoaded, setRecipeLoaded] = useState(false);
// 料理名,説明 // 料理名,説明
const [recipeName, setRecipeName] = useState<string>(''); const [recipeName, setRecipeName] = useState<string>('');
const [recipeSummary, setRecipeSummary] = useState<string>(''); const [recipeSummary, setRecipeSummary] = useState<string>('');
@ -58,53 +63,64 @@ const AddRecipe: React.FC = () => {
// 編集しているアイテム // 編集しているアイテム
const [editingItem, setEditingItem] = useState<StuffAndCategoryAndAmount>(emptyItem); const [editingItem, setEditingItem] = useState<StuffAndCategoryAndAmount>(emptyItem);
const [editingItemIdx, setEditingItemIdx] = useState(0); const [editingItemIdx, setEditingItemIdx] = useState(0);
//削除確認ダイアログの表示状態
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
// エラーメッセージ表示
const { showErrorMessage, showSuccessMessage } = useMessage();
const loadRecipe = async () => { const loadRecipe = async () => {
if (recipeId && !recipeName) { if (recipeId && !recipeLoaded) {
const recipe = await recipeApi.getById(recipeId); const recipe = await recipeApi.getById(recipeId);
console.log('loaded recipe=', recipe) console.log('loaded recipe=', recipe)
setRecipeName(recipe.recipeName) setRecipeName(recipe.recipeName)
setRecipeSummary(recipe.summary) setRecipeSummary(recipe.summary)
setItems(recipe.stuffAndAmountArray) setItems(recipe.stuffAndAmountArray)
setRecipeLoaded(true)
} }
} }
const handleSaveRecipe = async () => { const handleSaveRecipe = async () => {
if (!recipeName) { if (!recipeName) {
alert('レシピ名が入力されていません!') showErrorMessage('レシピ名が入力されていません!')
return false; return false;
} }
if (!items.length) { if (!items.length) {
alert('材料が追加されていません!') showErrorMessage('材料が追加されていません!')
return false; return false;
} }
if (!recipeId) { try {
// 新規追加 if (!recipeId) {
const response = await recipeApi.addRecipe({ // 新規追加
const response = await recipeApi.addRecipe({
recipeName,
summary: recipeSummary,
stuffAndAmountArray: items,
})
return response.recipeId;
}
const response = await recipeApi.updateRecipe({
recipeId,
recipeName, recipeName,
summary: recipeSummary, summary: recipeSummary,
stuffAndAmountArray: items, stuffAndAmountArray: items,
}) })
return response.recipeId; } catch {
showErrorMessage('レシピの送信に失敗しました。同じ料理名が存在する可能性があります。');
return false;
} }
const response = await recipeApi.updateRecipe({
recipeId,
recipeName,
summary: recipeSummary,
stuffAndAmountArray: items,
})
return recipeId; return recipeId;
} }
const handleSubmit = async () => { const handleSubmit = async () => {
const recipeId = await handleSaveRecipe(); const recipeId = await handleSaveRecipe();
// alert('レシピが保存されました!');
if (!recipeId) return; if (!recipeId) return;
showSuccessMessage('レシピが保存されました!');
navigate('/recipeList'); navigate('/recipeList');
} }
@ -112,7 +128,7 @@ const AddRecipe: React.FC = () => {
const recipeId = await handleSaveRecipe(); const recipeId = await handleSaveRecipe();
if (!recipeId) return false; if (!recipeId) return false;
await toBuyApi.addByRecipe(recipeId); await toBuyApi.addByRecipe(recipeId);
// alert('レシピが保存されて買うものリストに追加されました!'); showSuccessMessage('レシピが保存されて買うものリストに追加されました!');
navigate('/tasks'); navigate('/tasks');
} }
@ -122,7 +138,8 @@ const AddRecipe: React.FC = () => {
}, []); }, []);
return ( return (
(recipeId && !recipeName) <>
{(recipeId && !recipeLoaded)
? <p>...</p> ? <p>...</p>
: :
<Box> <Box>
@ -138,7 +155,7 @@ const AddRecipe: React.FC = () => {
value={recipeSummary} onChange={(e) => setRecipeSummary(e.target.value)} value={recipeSummary} onChange={(e) => setRecipeSummary(e.target.value)}
/> />
</div> </div>
<h2 style={{marginTop: "0.5em" }}> <h2 style={{ marginTop: "0.5em" }}>
</h2> </h2>
{/* すべての材料情報を表示 */} {/* すべての材料情報を表示 */}
@ -179,7 +196,11 @@ const AddRecipe: React.FC = () => {
tooltip: { sx: { backgroundColor: "white", color: "red", fontSize: "0.8rem", padding: "6px", borderRadius: "6px" } }, tooltip: { sx: { backgroundColor: "white", color: "red", fontSize: "0.8rem", padding: "6px", borderRadius: "6px" } },
}}> }}>
<IconButton edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete" <IconButton edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete"
onClick={() => setItems([...items.slice(0, index), ...items.slice(index + 1)])}> onClick={() => {
setOpenDeleteDialog(true)
setEditingItem(item)
setEditingItemIdx(index)
}}>
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -200,7 +221,7 @@ const AddRecipe: React.FC = () => {
</Fab> </Fab>
</div> </div>
<div style={{ position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "2%", whiteSpace: 'nowrap' }}> <div style={{ position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "64px", whiteSpace: 'nowrap' }}>
<Button variant='contained' color="primary" onClick={handleSubmit} sx={{ marginRight: "1rem" }}> <Button variant='contained' color="primary" onClick={handleSubmit} sx={{ marginRight: "1rem" }}>
<SaveIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} /> <SaveIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} />
@ -219,6 +240,29 @@ const AddRecipe: React.FC = () => {
setOpenAddDialog(false); setOpenAddDialog(false);
}} /> }} />
{/* 削除ダイアログ */}
<Dialog open={openDeleteDialog} onClose={() => setOpenDeleteDialog(false)} disableScrollLock={true}
fullWidth
maxWidth="sm"
sx={{ overflow: "hidden" }}
>
<DialogTitle></DialogTitle>
<DialogContent>
{editingItem && (
<>
<Typography variant="h4">{editingItem.stuffName}</Typography>
<Typography variant="body1" color="error"> 注意: 削除すると復元できません</Typography>
<Button onClick={() => setOpenDeleteDialog(false)} sx={{ mt: 3, mb: 2, left: '70%' }}></Button>
<Button variant="contained" color="error" onClick={() => {
setItems([...items.slice(0, editingItemIdx), ...items.slice(editingItemIdx + 1)])
setOpenDeleteDialog(false); // 削除処理後にダイアログを閉じる
}}
style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '72%' }}></Button>
</>
)}
</DialogContent>
</Dialog>
{/* 数量変更ダイアログ */} {/* 数量変更ダイアログ */}
<EditAmountDialog openDialog={openAmountDialog} setOpenDialog={setOpenAmountDialog} <EditAmountDialog openDialog={openAmountDialog} setOpenDialog={setOpenAmountDialog}
editingItem={editingItem} editingItem={editingItem}
@ -229,6 +273,8 @@ const AddRecipe: React.FC = () => {
}} /> }} />
</Box> </Box>
}
</>
); );
}; };

@ -159,9 +159,10 @@ const DishList: React.FC = () => {
))} ))}
{/* </List> */} {/* </List> */}
</div> </div>
<div style={{width: "100%", position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "20px"}}> <div style={{width: "100%", position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "64px"}}>
<Button variant='contained' sx={{width: "60%", height: "60px", <Button variant='contained' sx={{
fontSize: "40px", left: "50%", transform: 'translateX(-50%)' }} width: "60%",
fontSize: "24px", left: "50%", transform: 'translateX(-50%)' }}
color="primary" color="primary"
onClick={() => navigate('/add1')} onClick={() => navigate('/add1')}
> >

@ -31,11 +31,14 @@ import {
SoupKitchen as SoupKitchenIcon SoupKitchen as SoupKitchenIcon
} from '@mui/icons-material'; } from '@mui/icons-material';
import { ToBuy, Stuff, RecipeWithId, RecipeDetail } from '../types/types'; import { ToBuy, Stuff, RecipeWithId, RecipeDetail } from '../types/types';
import { useMessage } from '../components/MessageContext';
const RecipeList: React.FC = () => { const RecipeList: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
// 料理リストの料理名を格納する配列 // 料理リストの料理名を格納する配列
const { showErrorMessage } = useMessage();
// すべての料理リスト // すべての料理リスト
const [allRecipes, setAllRecipes] = useState<RecipeWithId[]>(); const [allRecipes, setAllRecipes] = useState<RecipeWithId[]>();
@ -48,7 +51,7 @@ const RecipeList: React.FC = () => {
const recipes = await recipeApi.getAllRecipes(); const recipes = await recipeApi.getAllRecipes();
setAllRecipes(recipes); setAllRecipes(recipes);
} catch (error) { } catch (error) {
alert("レシピの取得に失敗しました."); showErrorMessage("レシピの取得に失敗しました。");
// console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error); // console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error);
} }
}; };
@ -93,10 +96,10 @@ const RecipeList: React.FC = () => {
))} ))}
{/* </List> */} {/* </List> */}
</div> </div>
<div style={{ width: "100%", position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "20px" }}> <div style={{ width: "100%", position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "64px" }}>
<Button variant='contained' sx={{ <Button variant='contained' sx={{
width: "60%", height: "60px", width: "60%", height: "60px",
fontSize: "40px", left: "50%", transform: 'translateX(-50%)' fontSize: "32px", left: "50%", transform: 'translateX(-50%)'
}} }}
color="primary" color="primary"
onClick={() => navigate('/AddRecipe')} onClick={() => navigate('/AddRecipe')}

@ -29,6 +29,7 @@ import {
import { STOCK_ERRORS } from '../constants/errorMessages'; import { STOCK_ERRORS } from '../constants/errorMessages';
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';
// 日付をyyyy-MM-dd形式で返す関数 // 日付をyyyy-MM-dd形式で返す関数
const formatDateLocal = (date: Date) => { const formatDateLocal = (date: Date) => {
@ -72,6 +73,8 @@ const StockPage: React.FC = () => {
// 在庫の編集状態 // 在庫の編集状態
const [editStock, setEditStock] = useState<Stock | null>(null); const [editStock, setEditStock] = useState<Stock | null>(null);
const { showWarningMessage } = useMessage();
// コンポーネントマウント時にタスク一覧を取得 // コンポーネントマウント時にタスク一覧を取得
useEffect(() => { useEffect(() => {
fetchStocks(); fetchStocks();
@ -146,7 +149,7 @@ const StockPage: React.FC = () => {
}; };
/** /**
* *
*/ */
const onChangeCategory = async (category: string) => { const onChangeCategory = async (category: string) => {
setNewStock({ ...newStock, category }) setNewStock({ ...newStock, category })
@ -177,18 +180,23 @@ const StockPage: React.FC = () => {
const handleOpenAdd = () => { const handleOpenAdd = () => {
setIsAddOpen(true); setIsAddOpen(true);
}; };
/** 削除ダイアログを閉じる */ /** 追加ダイアログを閉じる */
const handleCloseAdd = () => { const handleCloseAdd = () => {
setIsAddOpen(false); setIsAddOpen(false);
}; };
/** /**
* . * .
*/ */
const handleRowClick = (stock: Stock) => { // const handleRowClick = (stock: Stock) => {
// setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock));
// };
// チェックボックス切り替え
const handleCheckboxChange = (stock: Stock) => {
setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock)); setSelectedRow(prev => (prev?.stockId === stock.stockId ? null : stock));
}; };
/** 編集ボタンを押したときにダイアログを開く */ /** 編集ボタンを押したときにダイアログを開く */
// ダイアログを開く際に `selectedRow` の値を `editStock` にセット // ダイアログを開く際に `selectedRow` の値を `editStock` にセット
const handleOpenEdit = () => { const handleOpenEdit = () => {
@ -196,29 +204,38 @@ const StockPage: React.FC = () => {
setEditStock({ ...selectedRow }); setEditStock({ ...selectedRow });
setIsEditOpen(true); setIsEditOpen(true);
} else { } else {
alert("編集する食材を選択してください。"); showWarningMessage("編集する食材を選択してください。");
} }
}; };
// 変更を適用 // 変更を適用. 数量に0を入力したとき、削除ダイアログに飛ぶ機能を追加
const handleApplyChanges = async () => { const handleApplyChanges = async () => {
if (editStock) { if (!editStock) return;
try {
await handleUpdateStock( try {
editStock.stockId, if (Number(editStock.amount) === 0) {
Number(editStock.amount), // 数量が 0 の場合は削除処理へ誘導
Number(editStock.price), setIsEditOpen(false); // 編集ダイアログを閉じる
editStock.buyDate, setSelectedRow(editStock); // 削除対象をセット
editStock.expDate setIsDeleteOpen(true); // 削除ダイアログを開く
); return;
setSelectedRow(editStock); // `selectedRow` を更新して変更を即時反映
fetchStocks(); // 最新データを取得してテーブルに反映
setSelectedRow(null); // セルの選択を解除
} catch (error) {
console.error(`${STOCK_ERRORS.UPDATE_FAILED}:`, error);
} }
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); // ダイアログを閉じる
setIsEditOpen(false); // 編集ダイアログを閉じる
}; };
// ダイアログを開く際に `selectedRow` の値を `editStock` にコピー // ダイアログを開く際に `selectedRow` の値を `editStock` にコピー
@ -229,15 +246,25 @@ const StockPage: React.FC = () => {
}, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映 }, [selectedRow]); // `selectedRow` が変更されたら `editStock` に反映
// テキストフィールドの変更を検知 // テキストフィールドの変更を検知
// 負の値を入力できないように書き換え
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { 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) { if (editStock) {
setEditStock({ setEditStock({
...editStock, ...editStock,
[event.target.name]: event.target.value, [name]: value,
}); });
} }
}; };
/** 編集ダイアログを閉じる */ /** 編集ダイアログを閉じる */
const handleCloseEdit = () => { const handleCloseEdit = () => {
setIsEditOpen(false); setIsEditOpen(false);
@ -248,7 +275,7 @@ const StockPage: React.FC = () => {
if (selectedRow) { if (selectedRow) {
setIsDeleteOpen(true); setIsDeleteOpen(true);
} else { } else {
alert("削除する食材を選択してください。"); showWarningMessage("削除する食材を選択してください。");
} }
}; };
/** 削除ダイアログを閉じる */ /** 削除ダイアログを閉じる */
@ -268,15 +295,15 @@ const StockPage: React.FC = () => {
<Table> <Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}> <TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow> <TableRow>
<TableCell padding="checkbox" />
<TableCell></TableCell> <TableCell></TableCell>
<TableCell></TableCell> <TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></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 timeDiff = expDate.getTime() - today.getTime();
@ -285,13 +312,19 @@ const StockPage: React.FC = () => {
return ( return (
<TableRow <TableRow
key={stock.stockId} key={stock.stockId}
onClick={() => handleRowClick(stock)} sx={{
style={{ backgroundColor: selectedRow?.stockId === stock.stockId ? "yellow" : "white", cursor: "pointer" }} backgroundColor: isSelected ? 'yellow' : 'white',
}}
> >
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={() => handleCheckboxChange(stock)}
/>
</TableCell>
<TableCell>{stock.stuffName}</TableCell> <TableCell>{stock.stuffName}</TableCell>
<TableCell>{stock.amount}</TableCell> <TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.buyDate)}</TableCell>
<TableCell <TableCell
style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}} style={daysLeft <= 3 ? { color: "red", fontWeight: "bold" } : {}}
> >
@ -320,6 +353,13 @@ const StockPage: React.FC = () => {
type="number" type="number"
value={editStock.amount} value={editStock.amount}
onChange={handleChange} onChange={handleChange}
inputProps={{ min: 0 }}
onKeyDown={(e) => {
if (e.key === '-' || e.key === 'e' || e.key === 'E') {
e.preventDefault();
}
}}
/> />
<TextField <TextField
label="購入価格" label="購入価格"
@ -329,34 +369,69 @@ const StockPage: React.FC = () => {
type="number" type="number"
value={editStock.price} value={editStock.price}
onChange={handleChange} onChange={handleChange}
/> inputProps={{ min: 0 }}
<TextField onKeyDown={(e) => {
label="購入日 (yyyy-MM-dd)" if (e.key === '-' || e.key === 'e' || e.key === 'E') {
fullWidth e.preventDefault();
margin="normal" }
name="buyDate" }}
value={editStock.buyDate}
onChange={handleChange}
/>
<TextField
label="消費・賞味期限 (yyyy-MM-dd)"
fullWidth
margin="normal"
name="expDate"
value={editStock.expDate}
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>
/>
{/* 購入日・消費期限を横並びに */}
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
{/* 購入日 */}
<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
/>
{/* 消費・賞味期限 */}
<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> </DialogContent>
@ -375,13 +450,26 @@ const StockPage: React.FC = () => {
<> <>
<Typography variant="h4">{selectedRow.stuffName}</Typography> <Typography variant="h4">{selectedRow.stuffName}</Typography>
<Typography variant="body1" color="error"> 注意: 削除すると復元できません</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={() => { <Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2, mt: 3, mb: 2 }}>
handleDeleteStock(selectedRow.stockId); <Button onClick={() => {
setIsDeleteOpen(false); // 削除処理後にダイアログを閉じる setIsDeleteOpen(false);
setSelectedRow(null); // セルの選択を解除 setSelectedRow(null);
}} }}>
style={{ marginTop: "10px" }} sx={{ mt: 3, mb: 2, left: '72%' }}></Button>
</Button>
<Button
variant="contained"
color="error"
onClick={() => {
handleDeleteStock(selectedRow.stockId);
setIsDeleteOpen(false);
setSelectedRow(null);
}}
>
</Button>
</Box>
</> </>
)} )}
</DialogContent> </DialogContent>
@ -397,14 +485,29 @@ const StockPage: React.FC = () => {
</Typography> </Typography>
<Box sx={{ textAlign: 'right' }}> {/* <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 variant="contained" color="primary" onClick={handleOpenAdd} sx={{ mt: 3, mb: 2, mr: 1 }}>
</Button> </Button>
{/* 新規タスク作成ダイアログ */} {/* 新規タスク作成ダイアログ */}
<Dialog open={isAddOpen} onClose={() => setIsAddOpen(false)} 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 }}></DialogTitle>
<FormGroup row> <FormGroup row>
@ -515,15 +618,6 @@ const StockPage: React.FC = () => {
isClearable isClearable
//withPortal // ← 他の文字との重なり対策 //withPortal // ← 他の文字との重なり対策
/> />
{/*
<TextField
margin="dense"
label="購入日(yyyy/MM/dd)"
fullWidth
value={newStock.buyDate}
onChange={(e) => setNewStock({ ...newStock, buyDate: e.target.value })}
/>
*/}
{/* 消費・賞味期限入力フィールド */} {/* 消費・賞味期限入力フィールド */}
<DatePicker <DatePicker
popperClassName="custom-datepicker-popper" popperClassName="custom-datepicker-popper"
@ -554,8 +648,10 @@ const StockPage: React.FC = () => {
</Dialog> </Dialog>
{/* 在庫の食材編集ボタン(全テーブル共通) */} {/* 在庫の食材編集ボタン(全テーブル共通) */}
<Button variant="contained" color="success" onClick={handleOpenEdit} sx={{ mt: 3, mb: 2, mr: 1 }}> <Button variant="contained" color="success" onClick={handleOpenEdit} sx={{
mt: 3, mb: 2, mr: 1
}}>
</Button> </Button>
{/* 在庫の食材削除ボタン (全テーブル共通) */} {/* 在庫の食材削除ボタン (全テーブル共通) */}

@ -31,6 +31,7 @@ import AddStuffAmountDialog from '../components/AddStuffAmountDialog';
import BuyDialog from '../components/BuyDialog'; import BuyDialog from '../components/BuyDialog';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import DatePicker from 'react-datepicker'; import DatePicker from 'react-datepicker';
import { useMessage } from '../components/MessageContext';
//import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留 //import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留
@ -91,7 +92,7 @@ const TaskListPage: React.FC = () => {
shop: undefined, shop: undefined,
}); });
const { showErrorMessage } = useMessage();
// コンポーネントマウント時にタスク一覧を取得 // コンポーネントマウント時にタスク一覧を取得
useEffect(() => { useEffect(() => {
@ -149,7 +150,7 @@ const TaskListPage: React.FC = () => {
const handleAddNewToBuy = async () => { const handleAddNewToBuy = async () => {
try { try {
if (isNaN(newToBuy.amount)) { if (isNaN(newToBuy.amount)) {
console.log('数量が正しくありません.'); showErrorMessage('数量が正しくありません.');
return; return;
} }
@ -171,7 +172,7 @@ const TaskListPage: React.FC = () => {
const handleUpdateNewToBuy = async () => { const handleUpdateNewToBuy = async () => {
try { try {
if (isNaN(editingItem.amount)) { if (isNaN(editingItem.amount)) {
console.log('数量が正しくありません.'); showErrorMessage('数量が正しくありません.');
return; return;
} }
@ -195,7 +196,7 @@ const TaskListPage: React.FC = () => {
console.log("newPrice:", newStock.price) console.log("newPrice:", newStock.price)
console.log("parsedPrice: ", parsedPrice) console.log("parsedPrice: ", parsedPrice)
if (isNaN(parsedPrice)) { if (isNaN(parsedPrice)) {
alert('入力が無効です') showErrorMessage('価格が正しく入力されていません。')
return return
//setNewStock({ ...newStock, price: parsedPrice }); //setNewStock({ ...newStock, price: parsedPrice });
} }
@ -251,6 +252,7 @@ const TaskListPage: React.FC = () => {
<IconButton color="primary" sx={{ marginRight: 0, marginLeft: 0 }} edge="end" aria-label="購入情報を記入" <IconButton color="primary" sx={{ marginRight: 0, marginLeft: 0 }} edge="end" aria-label="購入情報を記入"
onClick={() => { onClick={() => {
setOpenInfoDialog(true) setOpenInfoDialog(true)
setEditingItem(tobuy)
setSelectedToBuyId(tobuy.tobuyId) setSelectedToBuyId(tobuy.tobuyId)
// handleDeleteTask(tobuy.tobuyId) // handleDeleteTask(tobuy.tobuyId)
}}> }}>
@ -294,28 +296,28 @@ const TaskListPage: React.FC = () => {
</List> </List>
</div> </div>
{/* 新規材料作成ボタン - 画面下部に固定表示 */} {/* 新規材料作成ボタン - 画面下部に固定表示 */}
<Box sx={{ textAlign: 'center', position: 'fixed', bottom: 76, left: '40%', transform: 'translateX(-50%)' }}> <Box sx={{ textAlign: 'center', position: 'fixed', bottom: 66, left: '20%', transform: 'translateX(-50%)' }}>
<Typography variant="caption" color="textSecondary"> <Typography variant="caption" color="textSecondary">
</Typography> </Typography>
</Box> </Box>
<Fab <Fab
color="primary" color="primary"
sx={{ position: 'fixed', bottom: 16, left: '40%', transform: 'translateX(-50%)' }} sx={{ position: 'fixed', bottom: 90, left: '20%', transform: 'translateX(-50%)' }}
onClick={() => setOpenAddToBuyDialog(true)} onClick={() => setOpenAddToBuyDialog(true)}
> >
<AddIcon /> <AddIcon />
</Fab> </Fab>
{/*新規料理追加ボタン - 画面下部に固定表示 */} {/*新規料理追加ボタン - 画面下部に固定表示 */}
<Box sx={{ textAlign: 'center', position: 'fixed', bottom: 76, left: '60%', transform: 'translateX(-50%)' }}> <Box sx={{ textAlign: 'center', position: 'fixed', bottom: 66, left: '80%', transform: 'translateX(-50%)' }}>
<Typography variant="caption" color="textSecondary"> <Typography variant="caption" color="textSecondary">
</Typography> </Typography>
</Box> </Box>
<Fab <Fab
color="primary" color="primary"
sx={{ position: 'fixed', bottom: 16, left: '60%', transform: 'translateX(-50%)' }} sx={{ position: 'fixed', bottom: 90, left: '80%', transform: 'translateX(-50%)' }}
onClick={() => { onClick={() => {
// setOpenAddToBuyDialog(true); // setOpenAddToBuyDialog(true);
navigate('/RecipeList'); navigate('/RecipeList');
@ -330,7 +332,7 @@ const TaskListPage: React.FC = () => {
<AddStuffAmountDialog openDialog={openAddToBuyDialog} setOpenDialog={setOpenAddToBuyDialog} newItem={newToBuy} setNewItem={setNewToBuy} onSubmit={handleAddNewToBuy} /> <AddStuffAmountDialog openDialog={openAddToBuyDialog} setOpenDialog={setOpenAddToBuyDialog} newItem={newToBuy} setNewItem={setNewToBuy} onSubmit={handleAddNewToBuy} />
{/* 購入処理(在庫登録)のための数値入力ダイアログ */} {/* 購入処理(在庫登録)のための数値入力ダイアログ */}
<BuyDialog openDialog={openInfoDialog} setOpenDialog={setOpenInfoDialog} newStock={newStock} setNewStock={setNewStock} onSubmit={handleBuy} /> <BuyDialog openDialog={openInfoDialog} setOpenDialog={setOpenInfoDialog} stuffName={editingItem.stuffName} newStock={newStock} setNewStock={setNewStock} onSubmit={handleBuy} />
{/* 数量変更ダイアログ */} {/* 数量変更ダイアログ */}
<EditAmountDialog openDialog={openAmountDialog} setOpenDialog={setOpenAmountDialog} <EditAmountDialog openDialog={openAmountDialog} setOpenDialog={setOpenAmountDialog}

Loading…
Cancel
Save