|
|
/**
|
|
|
* テストページコンポーネント
|
|
|
* 白紙の状態で表示されるテスト用のページ
|
|
|
*/
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
import {
|
|
|
Typography,
|
|
|
Tooltip,
|
|
|
List,
|
|
|
ListItem,
|
|
|
ListItemText,
|
|
|
ListItemSecondaryAction,
|
|
|
IconButton,
|
|
|
Fab,
|
|
|
Dialog,
|
|
|
DialogTitle,
|
|
|
DialogContent,
|
|
|
DialogActions,
|
|
|
TextField,
|
|
|
Button,
|
|
|
Checkbox,
|
|
|
FormControlLabel,
|
|
|
Box,
|
|
|
Container,
|
|
|
} from '@mui/material';
|
|
|
import {
|
|
|
Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon,
|
|
|
SoupKitchen as SoupKitchenIcon, Edit as EditIcon, Save as SaveIcon, ListAlt as ListAltIcon
|
|
|
} from '@mui/icons-material';
|
|
|
import AddStuffAmountDialog from '../components/AddStuffAmountDialog';
|
|
|
import { StuffAndCategoryAndAmount } from '../types/types';
|
|
|
import EditAmountDialog from '../components/EditAmountDialog';
|
|
|
import { recipeApi, toBuyApi, stockApi } from '../services/api';
|
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
|
import MessageAlert from '../components/MessageAlert';
|
|
|
import { useMessage } from '../components/MessageContext';
|
|
|
import { RECIPE_MESSAGES } from '../constants/normalMessages';
|
|
|
import { RECIPE_ERRORS } from '../constants/errorMessages';
|
|
|
|
|
|
const AddRecipe: React.FC = () => {
|
|
|
const { recipeId: recipeIdStr } = useParams();
|
|
|
const recipeId = recipeIdStr ? parseInt(recipeIdStr) : null
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
// 編集時,既存情報を読み込んだかどうか
|
|
|
const [recipeLoaded, setRecipeLoaded] = useState(false);
|
|
|
// 何人分かを格納する
|
|
|
const [numOfPeaple, setNumOfPeaple] = useState<number>(1);
|
|
|
const [openNumOfPeapleDialog, setOpenNumOfPeapleDialog] = useState(false);
|
|
|
// 料理名,説明
|
|
|
const [recipeName, setRecipeName] = useState<string>('');
|
|
|
const [recipeSummary, setRecipeSummary] = useState<string>('');
|
|
|
// 材料リスト
|
|
|
const [items, setItems] = useState<StuffAndCategoryAndAmount[]>([]);
|
|
|
// 材料追加作成ダイアログの表示状態
|
|
|
const [openAddDialog, setOpenAddDialog] = useState(false);
|
|
|
// 材料追加作成ダイアログの表示状態
|
|
|
const [openAmountDialog, setOpenAmountDialog] = useState(false);
|
|
|
// 新規アイテムの入力内容
|
|
|
const emptyItem: StuffAndCategoryAndAmount = { stuffId: null, stuffName: '', category: '', amount: 1 }
|
|
|
const [newItem, setNewItem] = useState<StuffAndCategoryAndAmount>(emptyItem);
|
|
|
// 編集しているアイテム
|
|
|
const [editingItem, setEditingItem] = useState<StuffAndCategoryAndAmount>(emptyItem);
|
|
|
const [editingItemIdx, setEditingItemIdx] = useState(0);
|
|
|
//削除確認ダイアログの表示状態
|
|
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
|
|
// チェックボックスが押されたかどうか
|
|
|
const [checked, setChecked] = useState(false);
|
|
|
|
|
|
// エラーメッセージ表示
|
|
|
const { showErrorMessage, showInfoMessage, showSuccessMessage } = useMessage();
|
|
|
|
|
|
const loadRecipe = async () => {
|
|
|
if (recipeId && !recipeLoaded) {
|
|
|
const recipe = await recipeApi.getById(recipeId);
|
|
|
console.log('loaded recipe=', recipe)
|
|
|
setRecipeName(recipe.recipeName)
|
|
|
setRecipeSummary(recipe.summary)
|
|
|
setItems(recipe.stuffAndAmountArray)
|
|
|
setRecipeLoaded(true)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const handleSaveRecipe = async () => {
|
|
|
|
|
|
if (!recipeName) {
|
|
|
showErrorMessage('レシピ名が入力されていません!')
|
|
|
// console.log("yes1");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (!items.length) {
|
|
|
showErrorMessage('材料が追加されていません!')
|
|
|
// console.log("yes2");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
if (!recipeId) {
|
|
|
// console.log("yes3");
|
|
|
// 新規追加
|
|
|
const response = await recipeApi.addRecipe({
|
|
|
recipeName,
|
|
|
summary: recipeSummary,
|
|
|
stuffAndAmountArray: items,
|
|
|
})
|
|
|
return response.recipeId;
|
|
|
}
|
|
|
|
|
|
const response = await recipeApi.updateRecipe({
|
|
|
recipeId,
|
|
|
recipeName,
|
|
|
summary: recipeSummary,
|
|
|
stuffAndAmountArray: items,
|
|
|
})
|
|
|
} catch {
|
|
|
showErrorMessage('レシピの送信に失敗しました。同じ料理名が存在する可能性があります。');
|
|
|
return false;
|
|
|
}
|
|
|
// console.log("yes4");
|
|
|
return recipeId;
|
|
|
}
|
|
|
|
|
|
const checkRecipeAndItems = async () => {
|
|
|
|
|
|
if (!recipeName) {
|
|
|
showErrorMessage(RECIPE_ERRORS.INVALID_RECIPE_NAME);
|
|
|
// console.log("yes1");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (!items.length) {
|
|
|
showErrorMessage(RECIPE_ERRORS.NO_STUFFS);
|
|
|
// console.log("yes2");
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
const recipeId = await handleSaveRecipe();
|
|
|
if (!recipeId) return;
|
|
|
showSuccessMessage(RECIPE_MESSAGES.SAVED);
|
|
|
navigate('/recipeList');
|
|
|
}
|
|
|
|
|
|
const handleSubmitAndAddToBuy = async () => {
|
|
|
// console.log("too");
|
|
|
console.log('recipeName:', recipeName);
|
|
|
const recipeId = await handleSaveRecipe();
|
|
|
// console.log("before");
|
|
|
if (!recipeId) return false;
|
|
|
// console.log("ds");
|
|
|
if (!numOfPeaple) {
|
|
|
showErrorMessage(RECIPE_ERRORS.INVALID_SERVINGS);
|
|
|
// console.log("yes2");
|
|
|
return false;
|
|
|
}
|
|
|
const finalAddResult = await toBuyApi.addByRecipe(recipeId, numOfPeaple, checked);
|
|
|
console.log(finalAddResult)
|
|
|
// const recipeStuffInfo = (await recipeApi.getById(recipeId)).stuffAndAmountArray;
|
|
|
// const recipeStuffId = recipeStuffInfo.map(item => item.stuffId);
|
|
|
// console.log(recipeStuffId);
|
|
|
// const stockStuff = await stockApi.getStocks();
|
|
|
// const stockedStuffId = stockStuff.filter(stock => recipeStuff.stuffAndAmountArray).map(item => item.id);
|
|
|
// const stockedStuffAmountRecipe = recipeStuff.filter(recipe => (stockStuff.some(stuff => recipe.stuffId === stuff.stuffId)));
|
|
|
// console.log(stockedStuffAmountRecipe)
|
|
|
// const stockedStuffAmountStock = stockStuff.filter(stock => (stockedStuffAmountRecipe.some(stocked => stock.stuffId === stocked.stuffId)));
|
|
|
|
|
|
// recipeStuff.map(item => {
|
|
|
// await Promise.all(stockStuff.map(async stock => {
|
|
|
// if (item.stuffId == stock.stuffId)
|
|
|
// await toBuyApi.updateToBuy({stuffName: item.stuffName,
|
|
|
// amount: item.amount - stock.amount,
|
|
|
// tobuyId: item.tobuyId,
|
|
|
// stuffId: item.stuffId});
|
|
|
// }))
|
|
|
// });
|
|
|
if (finalAddResult.data.length === 0) {
|
|
|
showInfoMessage(RECIPE_MESSAGES.NO_ADDITIONALS);
|
|
|
}
|
|
|
else {
|
|
|
showSuccessMessage(RECIPE_MESSAGES.SAVED_AND_ADDED);
|
|
|
}
|
|
|
navigate('/tasks');
|
|
|
}
|
|
|
|
|
|
const openNumOfPeopleDialog = async () => {
|
|
|
const check = await checkRecipeAndItems();
|
|
|
if (!check) return false;
|
|
|
setOpenNumOfPeapleDialog(true);
|
|
|
}
|
|
|
|
|
|
const cancelNumOfPeopleDialog = async () => {
|
|
|
const recipeId = await handleSaveRecipe();
|
|
|
if (!recipeId) return false;
|
|
|
setOpenNumOfPeapleDialog(false);
|
|
|
}
|
|
|
|
|
|
// コンポーネントマウント時にタスク一覧を取得
|
|
|
useEffect(() => {
|
|
|
loadRecipe();
|
|
|
}, []);
|
|
|
|
|
|
return (
|
|
|
<div className="mainContainer">
|
|
|
{(recipeId && !recipeLoaded)
|
|
|
? <p>読み込み中...</p>
|
|
|
:
|
|
|
<>
|
|
|
<div className="mainTitle">
|
|
|
<SoupKitchenIcon sx={{ marginRight: "0.5em" }} />
|
|
|
{!recipeId ? '料理の追加' : '料理の編集'}
|
|
|
</div>
|
|
|
<div style={{padding: '1rem'}}>
|
|
|
<div>
|
|
|
<TextField autoFocus margin="dense" label="料理名" fullWidth
|
|
|
value={recipeName} onChange={(e) => setRecipeName(e.target.value)}
|
|
|
/>
|
|
|
<TextField margin="dense" label="説明" fullWidth
|
|
|
value={recipeSummary} onChange={(e) => setRecipeSummary(e.target.value)}
|
|
|
/>
|
|
|
</div>
|
|
|
<h2 style={{ marginTop: "0.5em" }}>
|
|
|
材料リスト
|
|
|
</h2>
|
|
|
{/* すべての材料情報を表示 */}
|
|
|
{(!items || !items.length)
|
|
|
? (<p>+ボタンで材料を追加してください</p>)
|
|
|
: (<List>{items.map((item, index) => (
|
|
|
|
|
|
<ListItem
|
|
|
key={index}
|
|
|
sx={{
|
|
|
bgcolor: 'background.paper',
|
|
|
mb: 1,
|
|
|
borderRadius: 1,
|
|
|
boxShadow: 1,
|
|
|
}}
|
|
|
>
|
|
|
<ListItemText primary={item.stuffName} />
|
|
|
{/* 買い物リスト:食材情報記入ボタン */}
|
|
|
<ListItemSecondaryAction>
|
|
|
<Typography variant="body1" component="span" sx={{ marginRight: '1em' }}>
|
|
|
{`× ${item.amount}`}
|
|
|
</Typography>
|
|
|
{/* 買い物リスト:数量変更ボタン */}
|
|
|
<Tooltip title="数量変更">
|
|
|
<IconButton sx={{ marginRight: 0, marginLeft: 0 }} edge="end" aria-label="数量変更"
|
|
|
onClick={() => {
|
|
|
setOpenAmountDialog(true)
|
|
|
setEditingItemIdx(index)
|
|
|
setEditingItem(item)
|
|
|
}}
|
|
|
>
|
|
|
<EditIcon />
|
|
|
</IconButton>
|
|
|
</Tooltip>
|
|
|
{/* 買い物リスト:食材削除ボタン */}
|
|
|
<Tooltip title="項目を削除"
|
|
|
componentsProps={{
|
|
|
tooltip: { sx: { backgroundColor: "white", color: "red", fontSize: "0.8rem", padding: "6px", borderRadius: "6px" } },
|
|
|
}}>
|
|
|
<IconButton edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete"
|
|
|
onClick={() => {
|
|
|
setOpenDeleteDialog(true)
|
|
|
setEditingItem(item)
|
|
|
setEditingItemIdx(index)
|
|
|
}}>
|
|
|
<DeleteIcon />
|
|
|
</IconButton>
|
|
|
</Tooltip>
|
|
|
|
|
|
</ListItemSecondaryAction>
|
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
))}</List>)}
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ position: "fixed", left: "80%", transform: 'translateX(-50%)', bottom: "10%" }}>
|
|
|
<Box sx={{ textAlign: 'center' }}>
|
|
|
<Typography variant="caption">材料を追加</Typography>
|
|
|
</Box>
|
|
|
<Fab color="primary" onClick={() => setOpenAddDialog(true)}>
|
|
|
<AddIcon sx={{ fontSize: "1.5rem" }} />
|
|
|
</Fab>
|
|
|
</div>
|
|
|
|
|
|
<div style={{ position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "64px", whiteSpace: 'nowrap' }}>
|
|
|
<Button variant='contained' color="primary" onClick={handleSubmit} sx={{ marginRight: "1rem" }}>
|
|
|
<SaveIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} />
|
|
|
レシピを保存
|
|
|
</Button>
|
|
|
<Button variant='contained' color="primary" onClick={openNumOfPeopleDialog}>
|
|
|
<ListAltIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} />
|
|
|
保存して買うものリストへ追加
|
|
|
</Button>
|
|
|
</div>
|
|
|
|
|
|
{/* 新規材料追加ダイアログ */}
|
|
|
<AddStuffAmountDialog openDialog={openAddDialog} setOpenDialog={setOpenAddDialog} newItem={newItem} setNewItem={setNewItem}
|
|
|
onSubmit={() => {
|
|
|
console.log('newItem:', newItem);
|
|
|
setItems([...items, newItem]);
|
|
|
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}
|
|
|
editingItem={editingItem}
|
|
|
setEditingItem={(v) => setEditingItem({ ...editingItem, ...v })}
|
|
|
onSubmit={() => {
|
|
|
setItems([...items.slice(0, editingItemIdx), editingItem, ...items.slice(editingItemIdx + 1)])
|
|
|
setOpenAmountDialog(false);
|
|
|
}} />
|
|
|
|
|
|
{/* 人数入力ダイアログ */}
|
|
|
<Dialog open={openNumOfPeapleDialog} onClose={() => setOpenNumOfPeapleDialog(false)} disableScrollLock={true}
|
|
|
style={{ width : '100%', position : 'fixed', left: '50%', transform: 'translateX(-50%)' }}
|
|
|
>
|
|
|
<Box display="flex" alignItems="center"
|
|
|
>
|
|
|
<DialogTitle sx={{ flexGrow: 1 }}>買うものリストへ追加</DialogTitle>
|
|
|
</Box>
|
|
|
<DialogContent>
|
|
|
<div>
|
|
|
{/* 人数入力フィールド */}
|
|
|
<TextField
|
|
|
margin="dense"
|
|
|
label="何人前"
|
|
|
fullWidth
|
|
|
value={numOfPeaple}
|
|
|
onChange={(e) => {
|
|
|
const value = e.target.value;
|
|
|
const parsedValue = parseInt(value, 10); // 数値に変換
|
|
|
if (/*!isNaN(parsedValue) && */ isNaN(parsedValue) || parsedValue >= 1) { //負数除外
|
|
|
setNumOfPeaple(parsedValue); // number型で保存
|
|
|
}
|
|
|
}}
|
|
|
sx={{ minWidth: "8px", width: "100%" }}
|
|
|
type="number"
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
|
|
|
|
|
|
/>
|
|
|
</div>
|
|
|
<FormControlLabel
|
|
|
control={<Checkbox checked={checked} onChange={(e) => setChecked(e.target.checked)} />}
|
|
|
label={<Typography sx={{ fontSize: '85%' }}>足りない食材のみ登録</Typography>}
|
|
|
/>
|
|
|
</DialogContent>
|
|
|
<DialogActions>
|
|
|
<Button onClick={() => setOpenNumOfPeapleDialog(false)}>前の画面に戻る</Button>
|
|
|
<Button onClick={() => handleSubmitAndAddToBuy()} variant="contained"
|
|
|
style={{ width: '40%' }}
|
|
|
>
|
|
|
追加
|
|
|
</Button>
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
</div>
|
|
|
</>
|
|
|
}
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default AddRecipe; |