You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
joint_exc/frontend/src/pages/AddRecipe.tsx

313 lines
11 KiB

/**
* テストページコンポーネント
* 白紙の状態で表示されるテスト用のページ
*/
import React, { useState, useEffect } from 'react';
import {
Container,
Typography,
Tooltip,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
IconButton,
Checkbox,
Fab,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
FormGroup,
MenuItem,
Select,
FormControl,
InputLabel,
ListItemIcon
} 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 } from '../services/api';
import { useNavigate, useParams } from 'react-router-dom';
const AddRecipe: React.FC = () => {
const { recipeId: recipeIdStr } = useParams();
const recipeId = recipeIdStr ? parseInt(recipeIdStr) : null
const navigate = useNavigate();
// 何人分かを格納する
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 loadRecipe = async () => {
if (recipeId && !recipeName) {
const recipe = await recipeApi.getById(recipeId);
console.log('loaded recipe=', recipe)
setRecipeName(recipe.recipeName)
setRecipeSummary(recipe.summary)
setItems(recipe.stuffAndAmountArray)
}
}
const handleSaveRecipe = async () => {
// if (!recipeName) {
// alert('レシピ名が入力されていません!')
// console.log("yes1");
// return false;
// }
// if (!items.length) {
// alert('材料が追加されていません!')
// console.log("yes2");
// return false;
// }
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,
})
console.log("yes4");
return recipeId;
}
const checkRecipeAndItems = async () => {
if (!recipeName) {
alert('レシピ名が入力されていません!')
console.log("yes1");
return false;
}
if (!items.length) {
alert('材料が追加されていません!')
console.log("yes2");
return false;
}
return true;
}
const handleSubmit = async () => {
const recipeId = await handleSaveRecipe();
// alert('レシピが保存されました!');
if (!recipeId) return;
navigate('/recipeList');
}
const handleSubmitAndAddToBuy = async () => {
console.log("too");
console.log(recipeName);
const recipeId = await handleSaveRecipe();
console.log("before");
if (!recipeId) return false;
console.log("ds");
await toBuyApi.addByRecipe(recipeId);
// alert('レシピが保存されて買うものリストに追加されました!');
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 (
(recipeId && !recipeName)
? <p>...</p>
:
<Box>
<div>
<h1>
<SoupKitchenIcon sx={{ marginRight: "0.5em" }} />
{!recipeId ? '料理の追加' : '料理の編集'}
</h1>
<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={() => setItems([...items.slice(0, index), ...items.slice(index + 1)])}>
<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: "2%", 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);
}} />
{/* 数量変更ダイアログ */}
<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>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenNumOfPeapleDialog(false)}></Button>
<Button onClick={() => handleSubmitAndAddToBuy} variant="contained"
style={{ width: '40%', fontSize: '1vw' }}
>
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default AddRecipe;