買うものリストへのレシピ追加画面の実装

feature-frontend-design-fix
Masaharu.Kato 4 months ago
parent 403d25a99c
commit 00f8401ed6
  1. 242
      frontend/src/components/AddByRecipeDialog.tsx
  2. 4
      frontend/src/pages/AddRecipe.tsx
  3. 10
      frontend/src/pages/RecipeList.tsx

@ -1,11 +1,19 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogContent, DialogContent,
DialogActions, DialogActions,
Button, Button,
Box, Box,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
Typography,
TextField,
FormControlLabel,
Checkbox,
} from '@mui/material'; } from '@mui/material';
import { toBuyApi } from '../services/api'; import { toBuyApi } from '../services/api';
@ -13,112 +21,138 @@ import { recipeApi } from '../services/api';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import { RecipeDetailWithId, StuffNameAndAmount } from '../types/types'; import { RecipeDetailWithId, StuffNameAndAmount } from '../types/types';
import { GENERAL_ERRORS } from '../constants/errorMessages';
import { useNavigate } from 'react-router-dom';
import { useMessage } from './MessageContext';
const AddByRecipeDialog = ({ const AddByRecipeDialog = ({
openDialog, openDialog,
setOpenDialog, setOpenDialog,
recipeId, recipeId,
numOfPeople, numOfPeople,
setNumOfPeaple, setNumOfPeaple,
checked, checked,
setChecked setChecked
}: { }: {
openDialog: boolean, openDialog: boolean,
setOpenDialog: (open: boolean) => void, setOpenDialog: (open: boolean) => void,
recipeId: number, recipeId: number,
numOfPeople: number, numOfPeople: number,
setNumOfPeaple: (num: number) => void, setNumOfPeaple: (num: number) => void,
checked: boolean, checked: boolean,
setChecked: (checked: boolean) => void setChecked: (checked: boolean) => void
}) => { }) => {
const [recipe, setRecipe] = useState<RecipeDetailWithId>(); const [recipe, setRecipe] = useState<RecipeDetailWithId>();
useEffect(() => {
console.log("called AddByRecipeDialog useEffect recipeId: ", recipeId);
if (recipeId) {
const fetchRecipe = async () => {
console.log("Fetching recipe with ID:", recipeId);
setRecipe(await recipeApi.getById(recipeId));
};
fetchRecipe();
}
}, [recipeId]);
return (
recipe ?
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true} PaperProps={{ sx: { minWidth: '300px', maxHeight: '80vh' } }}>
<DialogTitle sx={{ m: 0, p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
useEffect(() => {
<CloseIcon console.log("called AddByRecipeDialog useEffect recipeId: ", recipeId);
onClick={() => setOpenDialog(false)} if (recipeId) {
sx={{ const fetchRecipe = async () => {
cursor: 'pointer', console.log("Fetching recipe with ID:", recipeId);
color: (theme) => theme.palette.grey[500], setRecipe(await recipeApi.getById(recipeId));
}} };
/> fetchRecipe();
</DialogTitle> }
<DialogContent dividers sx={{ padding: 2 }}> }, [recipeId]);
<div>
<strong>:</strong> {recipe.recipeName}
</div>
<div>
({recipe.maxServings})
</div>
<div>
<strong>1:</strong>
<ul>
{recipe.stuffAndAmountArray.map((item, index) => (
<li key={index}>
{item.stuffName} - {item.amount}
</li>
))}
</ul>
</div>
{/* 在庫との差分を取るかのチェックボックス */}
<div>
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
</label>
</div>
{/* レシピを追加する人数分を入力 */} const navigate = useNavigate();
<div>
<strong>:</strong>
<input
type="number"
min="1"
defaultValue={1}
onChange={(e) => {
setNumOfPeaple(parseInt(e.target.value, 10));
}}
/>
</div>
{/* 買うものリストに追加するボタン */} // エラーメッセージ表示
<Box sx={{ mt: 2 }}> const { showErrorMessage, showSuccessMessage, showInfoMessage } = useMessage();
<Button
variant="contained"
color="primary"
onClick={() => {
toBuyApi.addByRecipe(recipe.recipeId, numOfPeople, checked);
setOpenDialog(false);
}}
>
</Button>
</Box>
</DialogContent>
</Dialog> return (
: <></> recipe ?
); <Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true} PaperProps={{ sx: { minWidth: '80vw', maxHeight: '80vh' } }}>
<DialogTitle sx={{ m: 0, p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CloseIcon
onClick={() => setOpenDialog(false)}
sx={{
cursor: 'pointer',
color: (theme) => theme.palette.grey[500],
}}
/>
</DialogTitle>
<DialogContent dividers sx={{ padding: 2 }}>
<Typography variant="h3">
{recipe.recipeName}
</Typography>
<Typography>
({recipe.maxServings})
</Typography>
<div>
<strong>1:</strong>
<List>
{recipe.stuffAndAmountArray.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>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
<div>
{/* 人数入力フィールド */}
<TextField
type="number"
margin="dense"
label="何人前"
fullWidth
// min={1}
defaultValue={1}
value={numOfPeople}
onChange={(e) => {
setNumOfPeaple(parseInt(e.target.value, 10));
}}
sx={{ minWidth: "8px", width: "100%" }}
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
/>
</div>
<FormControlLabel
control={<Checkbox checked={checked} onChange={(e) => setChecked(e.target.checked)} />}
label={<Typography></Typography>}
/>
{/* 買うものリストに追加するボタン */}
<Box sx={{ mt: 2, textAlign: 'right' }}>
<Button
variant="contained"
color="primary"
onClick={async () => {
if (!numOfPeople || isNaN(numOfPeople)) {
showErrorMessage('人数が正しく入力されていません。');
return;
}
const finalAddResult = await toBuyApi.addByRecipe(recipe.recipeId, numOfPeople, checked);
setOpenDialog(false);
if (finalAddResult.data.length === 0) {
showInfoMessage('必要な食材が在庫にあったのでリストには追加されませんでした!');
} else {
showSuccessMessage('レシピが保存されて買うものリストに追加されました!');
}
navigate('/tasks');
}}
>
</Button>
</Box>
</DialogContent>
</Dialog>
: <></>
);
} }
export default AddByRecipeDialog; export default AddByRecipeDialog;

@ -283,9 +283,9 @@ const AddRecipe: React.FC = () => {
))}</List>)} ))}</List>)}
<div style={{ position: "fixed", left: "80%", transform: 'translateX(-50%)', bottom: "10%" }}> <div style={{ position: "fixed", left: "80%", transform: 'translateX(-50%)', bottom: "10%" }}>
<Box sx={{ textAlign: 'center' }}> {/* <Box sx={{ textAlign: 'center' }}>
<Typography variant="caption"></Typography> <Typography variant="caption"></Typography>
</Box> </Box> */}
<Fab color="primary" onClick={() => setOpenAddDialog(true)}> <Fab color="primary" onClick={() => setOpenAddDialog(true)}>
<AddIcon sx={{ fontSize: "1.5rem" }} /> <AddIcon sx={{ fontSize: "1.5rem" }} />
</Fab> </Fab>

@ -155,6 +155,16 @@ const RecipeList: React.FC = () => {
checked = {checked} setChecked={setChecked} checked = {checked} setChecked={setChecked}
/> />
} }
{/* 追加ボタン - 画面下部に固定表示 */}
<Box className="plusButtonWrapper">
<Fab color="primary" onClick={() => navigate('/addRecipe/')} className="plusButton">
<AddIcon />
</Fab>
{/* <Typography className="plusButtonLabel">
</Typography> */}
</Box>
</> </>
); );
}; };

Loading…
Cancel
Save