料理追加・編集画面を実装

feature-frontend-dishedit-kato
Masaharu.Kato 4 months ago
parent 8fd8771ac5
commit 75b055574f
  1. 4
      backend/src/main/java/com/example/todoapp/dto/RecipeDetailDTO.java
  2. 7
      backend/src/main/java/com/example/todoapp/dto/StuffDetailDTO.java
  3. 40
      backend/src/main/java/com/example/todoapp/service/RecipeService.java
  4. 17
      frontend/src/App.tsx
  5. 10
      frontend/src/constants/errorMessages.ts
  6. 261
      frontend/src/pages/AddRecipe.tsx
  7. 76
      frontend/src/services/api.ts
  8. 10
      frontend/src/types/types.ts

@ -12,7 +12,7 @@ import lombok.Data;
* </p> * </p>
*/ */
@Data @Data
public class RecipeDetailDTO { public class RecipeDetailDTO {
/** /**
* レシピID * レシピID
* ユニークなレシピを識別するためのID * ユニークなレシピを識別するためのID
@ -35,5 +35,5 @@ public class RecipeDetailDTO {
* 食材リスト * 食材リスト
* このレシピに必要な食材とその数量のリスト * このレシピに必要な食材とその数量のリスト
*/ */
private List<StuffDetailDTO> stuffs; private List<StuffDetailDTO> stuffAndAmountArray;
} }

@ -1,6 +1,7 @@
package com.example.todoapp.dto; package com.example.todoapp.dto;
import lombok.Data; import lombok.Data;
/** /**
* 食材詳細情報のデータ転送オブジェクトDTO * 食材詳細情報のデータ転送オブジェクトDTO
* <p> * <p>
@ -22,6 +23,12 @@ public class StuffDetailDTO {
*/ */
private String stuffName; private String stuffName;
/**
* カテゴリ名
* 食材のカテゴリ
*/
private String category;
/** /**
* 数量 * 数量
* レシピに必要な食材の量 * レシピに必要な食材の量

@ -79,13 +79,13 @@ public class RecipeService {
RecipeStuffs recipeStuffs = new RecipeStuffs(); RecipeStuffs recipeStuffs = new RecipeStuffs();
recipeStuffs.setRecipes(recipe); // 関連レシピ設定 recipeStuffs.setRecipes(recipe); // 関連レシピ設定
recipeStuffs.setStuff(stuff); // 関連材料設定 recipeStuffs.setStuff(stuff); // 関連材料設定
// 数量設定、defaultは1 // 数量設定、defaultは1
if (stuffDTO.getAmount() == null || stuffDTO.getAmount().isEmpty()) { if (stuffDTO.getAmount() == null || stuffDTO.getAmount().isEmpty()) {
stuffDTO.setAmount("1"); stuffDTO.setAmount("1");
} }
recipeStuffs.setAmount(Integer.parseInt(stuffDTO.getAmount())); recipeStuffs.setAmount(Integer.parseInt(stuffDTO.getAmount()));
recipeStuffsList.add(recipeStuffs); recipeStuffsList.add(recipeStuffs);
} }
@ -111,26 +111,28 @@ public class RecipeService {
*/ */
public RecipeDetailDTO getRecipeDetailsById(Long recipeId) { public RecipeDetailDTO getRecipeDetailsById(Long recipeId) {
Recipes recipe = recipesRepository.findById(recipeId) Recipes recipe = recipesRepository.findById(recipeId)
.orElseThrow(() -> new RuntimeException("レシピが見つかりません")); .orElseThrow(() -> new RuntimeException("レシピが見つかりません"));
List<RecipeStuffs> recipeStuffsList = recipeStuffsRepository.findByRecipesRecipeId(recipeId); List<RecipeStuffs> recipeStuffsList = recipeStuffsRepository.findByRecipesRecipeId(recipeId);
List<StuffDetailDTO> stuffList = recipeStuffsList.stream() List<StuffDetailDTO> stuffList = recipeStuffsList.stream()
.map(rs -> { .map(rs -> {
StuffDetailDTO stuffDTO = new StuffDetailDTO(); StuffDetailDTO stuffDTO = new StuffDetailDTO();
stuffDTO.setStuffId(rs.getStuff().getStuffId()); Stuffs stuff = rs.getStuff();
stuffDTO.setStuffName(rs.getStuff().getStuffName()); stuffDTO.setStuffId(stuff.getStuffId());
stuffDTO.setAmount(rs.getAmount()); stuffDTO.setStuffName(stuff.getStuffName());
return stuffDTO; stuffDTO.setCategory(stuff.getCategory());
}) stuffDTO.setAmount(rs.getAmount());
.collect(Collectors.toList()); return stuffDTO;
})
.collect(Collectors.toList());
RecipeDetailDTO dto = new RecipeDetailDTO(); RecipeDetailDTO dto = new RecipeDetailDTO();
dto.setRecipeId(recipe.getRecipeId()); dto.setRecipeId(recipe.getRecipeId());
dto.setRecipeName(recipe.getRecipeName()); dto.setRecipeName(recipe.getRecipeName());
dto.setSummary(recipe.getSummary()); dto.setSummary(recipe.getSummary());
dto.setStuffs(stuffList); dto.setStuffAndAmountArray(stuffList);
return dto; return dto;
} }
@ -145,7 +147,7 @@ public class RecipeService {
public Recipes updateRecipe(RecipeDetailDTO dto) { public Recipes updateRecipe(RecipeDetailDTO dto) {
// IDでレシピを検索し、見つからない場合は例外をスロー // IDでレシピを検索し、見つからない場合は例外をスロー
Recipes recipe = recipesRepository.findById(dto.getRecipeId()) Recipes recipe = recipesRepository.findById(dto.getRecipeId())
.orElseThrow(() -> new RuntimeException("レシピが見つかりません")); .orElseThrow(() -> new RuntimeException("レシピが見つかりません"));
// レシピ名と概要を更新 // レシピ名と概要を更新
recipe.setRecipeName(dto.getRecipeName()); recipe.setRecipeName(dto.getRecipeName());
@ -155,12 +157,12 @@ public class RecipeService {
Set<Long> incomingStuffIds = new HashSet<>(); Set<Long> incomingStuffIds = new HashSet<>();
// 提供された材料の詳細を繰り返し処理 // 提供された材料の詳細を繰り返し処理
for (StuffDetailDTO stuffDTO : dto.getStuffs()) { for (StuffDetailDTO stuffDTO : dto.getStuffAndAmountArray()) {
if (stuffDTO.getStuffId() == null) { if (stuffDTO.getStuffId() == null) {
// 材料IDがnullの場合、新しい材料を作成 // 材料IDがnullの場合、新しい材料を作成
Stuffs newStuff = new Stuffs(); Stuffs newStuff = new Stuffs();
newStuff.setStuffName(stuffDTO.getStuffName()); newStuff.setStuffName(stuffDTO.getStuffName());
newStuff.setCategory("その他"); newStuff.setCategory("その他");
newStuff = stuffsRepository.save(newStuff); newStuff = stuffsRepository.save(newStuff);
// 新しいRecipeStuffsエントリを作成 // 新しいRecipeStuffsエントリを作成
@ -174,7 +176,7 @@ public class RecipeService {
} else { } else {
// 材料IDが提供されている場合、既存のRecipeStuffsエントリを検索 // 材料IDが提供されている場合、既存のRecipeStuffsエントリを検索
Optional<RecipeStuffs> optionalRs = recipeStuffsRepository Optional<RecipeStuffs> optionalRs = recipeStuffsRepository
.findByRecipesRecipeIdAndStuffStuffId(dto.getRecipeId(), stuffDTO.getStuffId()); .findByRecipesRecipeIdAndStuffStuffId(dto.getRecipeId(), stuffDTO.getStuffId());
if (optionalRs.isPresent()) { if (optionalRs.isPresent()) {
// RecipeStuffsエントリが存在する場合、数量を更新 // RecipeStuffsエントリが存在する場合、数量を更新
@ -185,7 +187,7 @@ public class RecipeService {
} else { } else {
// オプション:見つからない場合、新しいRecipeStuffsエントリを作成 // オプション:見つからない場合、新しいRecipeStuffsエントリを作成
Stuffs existingStuff = stuffsRepository.findById(stuffDTO.getStuffId()) Stuffs existingStuff = stuffsRepository.findById(stuffDTO.getStuffId())
.orElseThrow(() -> new RuntimeException("材料が見つかりません")); .orElseThrow(() -> new RuntimeException("材料が見つかりません"));
RecipeStuffs rs = new RecipeStuffs(); RecipeStuffs rs = new RecipeStuffs();
rs.setRecipes(recipe); rs.setRecipes(recipe);
@ -207,4 +209,4 @@ public class RecipeService {
return recipe; return recipe;
} }
} }

@ -108,16 +108,16 @@ const App: React.FC = () => {
</PrivateRoute> </PrivateRoute>
} }
/> />
{/* テストページへのルートを追加 */} {/* 料理リストへのルートを追加 */}
<Route <Route
path="dishList" path="dishList"
element={ element={
<PrivateRoute> <PrivateRoute>
<DishList/> <DishList />
</PrivateRoute> </PrivateRoute>
} }
/> />
{/* テストページへのルートを追加 */} {/* 料理追加ページ(新規追加)へのルートを追加 */}
<Route <Route
path="addRecipe" path="addRecipe"
element={ element={
@ -126,6 +126,15 @@ const App: React.FC = () => {
</PrivateRoute> </PrivateRoute>
} }
/> />
{/* 料理追加ページ(編集)へのルートを追加 */}
<Route
path="addRecipe/:recipeId"
element={
<PrivateRoute>
<AddRecipe />
</PrivateRoute>
}
/>
</Route> </Route>
<Route path="/" element={<Layout />}> <Route path="/" element={<Layout />}>
{/* ルートパスへのアクセスはタスク一覧にリダイレクト */} {/* ルートパスへのアクセスはタスク一覧にリダイレクト */}
@ -140,7 +149,7 @@ const App: React.FC = () => {
</PrivateRoute> </PrivateRoute>
} }
/> />
</Route> </Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>

@ -41,6 +41,16 @@ export const STOCK_ERRORS = {
BUY_FAILED: '在庫リストの購入処理に失敗しました', BUY_FAILED: '在庫リストの購入処理に失敗しました',
}; };
// 料理リスト関連のエラーメッセージ
export const RECIPE_ERRORS = {
FETCH_FAILED: '料理リストの取得に失敗しました',
GET_FAILED: '料理情報の取得に失敗しました',
CREATE_FAILED: '料理リストの作成に失敗しました',
UPDATE_FAILED: '料理リストの更新に失敗しました',
DELETE_FAILED: '料理リストの削除に失敗しました',
};
// // タスク関連のエラーメッセージ // // タスク関連のエラーメッセージ
// export const TASK_ERRORS = { // export const TASK_ERRORS = {
// FETCH_FAILED: 'タスクの取得に失敗しました', // FETCH_FAILED: 'タスクの取得に失敗しました',

@ -29,15 +29,23 @@ import {
} 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,
SoupKitchen as SoupKitchenIcon, Edit as EditIcon, ListAlt as ListAltIcon SoupKitchen as SoupKitchenIcon, Edit as EditIcon, Save as SaveIcon, ListAlt as ListAltIcon
} from '@mui/icons-material'; } from '@mui/icons-material';
import AddStuffAmountDialog from '../components/AddStuffAmountDialog'; import AddStuffAmountDialog from '../components/AddStuffAmountDialog';
import { StuffAndCategoryAndAmount, StuffNameAndAmount } from '../types/types'; import { StuffAndCategoryAndAmount } from '../types/types';
import EditAmountDialog from '../components/EditAmountDialog'; import EditAmountDialog from '../components/EditAmountDialog';
import { recipeApi, toBuyApi } from '../services/api';
import { useNavigate, useParams } from 'react-router-dom';
const AddDishes2: React.FC = () => { const AddRecipe: React.FC = () => {
// 料理名 const { recipeId: recipeIdStr } = useParams();
const recipeId = recipeIdStr ? parseInt(recipeIdStr) : null
const navigate = useNavigate();
// 料理名,説明
const [recipeName, setRecipeName] = useState<string>(''); const [recipeName, setRecipeName] = useState<string>('');
const [recipeSummary, setRecipeSummary] = useState<string>('');
// 材料リスト // 材料リスト
const [items, setItems] = useState<StuffAndCategoryAndAmount[]>([]); const [items, setItems] = useState<StuffAndCategoryAndAmount[]>([]);
// 材料追加作成ダイアログの表示状態 // 材料追加作成ダイアログの表示状態
@ -51,105 +59,158 @@ const AddDishes2: 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 handleAddRecipeToBuy = () => { const loadRecipe = async () => {
console.log('追加された材料:', items); if (recipeId && !recipeName) {
const recipe = await recipeApi.getById(recipeId);
console.log('loaded recipe=', recipe)
setRecipeName(recipe.recipeName)
setRecipeSummary(recipe.summary)
setItems(recipe.stuffAndAmountArray)
}
}
loadRecipe();
const handleSaveRecipe = async () => {
if (!recipeId) {
// 新規追加
const response = await recipeApi.addRecipe({
recipeName,
summary: recipeSummary,
stuffAndAmountArray: items,
})
return response.recipeId;
}
const response = await recipeApi.updateRecipe({
recipeId,
recipeName,
summary: recipeSummary,
stuffAndAmountArray: items,
})
return recipeId;
}
const handleSubmit = async () => {
const recipeId = await handleSaveRecipe()
alert('レシピが保存されました!') // 仮メッセージ
// navigate('/tasks');
}
const handleSubmitAndAddToBuy = async () => {
const recipeId = await handleSaveRecipe();
await toBuyApi.addByRecipe(recipeId)
navigate('/tasks')
} }
return ( return (
<Box> (recipeId && !recipeName)
<div> ? <p>...</p>
<h1></h1> :
<TextField autoFocus margin="dense" label="料理名" fullWidth <Box>
value={recipeName} onChange={(e) => setRecipeName(e.target.value)} <div>
/> <h1>
</div> <SoupKitchenIcon sx={{ marginRight: "0.5em" }} />
<h2></h2> {!recipeId ? '料理の追加' : '料理の編集'}
{/* すべての材料情報を表示 */} </h1>
{!items.length <TextField autoFocus margin="dense" label="料理名" fullWidth
? (<p>+</p>) value={recipeName} onChange={(e) => setRecipeName(e.target.value)}
: (<List>{items.map((item, index) => ( />
<TextField autoFocus margin="dense" label="説明" fullWidth
<ListItem value={recipeSummary} onChange={(e) => setRecipeSummary(e.target.value)}
key={index} />
sx={{ </div>
bgcolor: 'background.paper', <h2></h2>
mb: 1, {/* すべての材料情報を表示 */}
borderRadius: 1, {(!items || !items.length)
boxShadow: 1, ? (<p>+</p>)
}} : (<List>{items.map((item, index) => (
>
<ListItemText primary={item.stuffName} /> <ListItem
{/* 買い物リスト:食材情報記入ボタン */} key={index}
<ListItemSecondaryAction> sx={{
<Typography variant="body1" component="span" sx={{ marginRight: '1em' }}> bgcolor: 'background.paper',
{`× ${item.amount}`} mb: 1,
</Typography> borderRadius: 1,
{/* 買い物リスト:数量変更ボタン */} boxShadow: 1,
<Tooltip title="数量変更"> }}
<IconButton sx={{ marginRight: 0, marginLeft: 0 }} edge="end" aria-label="数量変更" >
onClick={() => { <ListItemText primary={item.stuffName} />
setOpenAmountDialog(true) {/* 買い物リスト:食材情報記入ボタン */}
setEditingItemIdx(index) <ListItemSecondaryAction>
setEditingItem(item) <Typography variant="body1" component="span" sx={{ marginRight: '1em' }}>
}} {`× ${item.amount}`}
> </Typography>
<EditIcon /> {/* 買い物リスト:数量変更ボタン */}
</IconButton> <Tooltip title="数量変更">
</Tooltip> <IconButton sx={{ marginRight: 0, marginLeft: 0 }} edge="end" aria-label="数量変更"
{/* 買い物リスト:食材削除ボタン */} onClick={() => {
<Tooltip title="項目を削除" setOpenAmountDialog(true)
componentsProps={{ setEditingItemIdx(index)
tooltip: { sx: { backgroundColor: "white", color: "red", fontSize: "0.8rem", padding: "6px", borderRadius: "6px" } }, setEditingItem(item)
}}> }}
<IconButton edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete" >
onClick={() => setItems([...items.slice(0, index), ...items.slice(index + 1)])}> <EditIcon />
<DeleteIcon /> </IconButton>
</IconButton> </Tooltip>
</Tooltip> {/* 買い物リスト:食材削除ボタン */}
<Tooltip title="項目を削除"
</ListItemSecondaryAction> componentsProps={{
tooltip: { sx: { backgroundColor: "white", color: "red", fontSize: "0.8rem", padding: "6px", borderRadius: "6px" } },
</ListItem> }}>
<IconButton edge="end" sx={{ marginRight: 0, marginLeft: 0 }} aria-label="delete"
))}</List>)} onClick={() => setItems([...items.slice(0, index), ...items.slice(index + 1)])}>
<DeleteIcon />
</IconButton>
<div style={{ position: "fixed", left: "80%", transform: 'translateX(-50%)', bottom: "10%" }}> </Tooltip>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="caption"></Typography> </ListItemSecondaryAction>
</Box>
<Fab color="primary" onClick={() => setOpenAddDialog(true)}> </ListItem>
<AddIcon sx={{ fontSize: "1.5rem" }} />
</Fab> ))}</List>)}
</div>
<div style={{ position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "2%" }}> <div style={{ position: "fixed", left: "80%", transform: 'translateX(-50%)', bottom: "10%" }}>
<Button variant='contained' sx={{ fontSize: "1.0rem" }} <Box sx={{ textAlign: 'center' }}>
color="primary" onClick={handleAddRecipeToBuy}> <Typography variant="caption"></Typography>
<ListAltIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} /> </Box>
<Fab color="primary" onClick={() => setOpenAddDialog(true)}>
</Button> <AddIcon sx={{ fontSize: "1.5rem" }} />
</div> </Fab>
</div>
{/* 新規材料追加ダイアログ */}
<AddStuffAmountDialog openDialog={openAddDialog} setOpenDialog={setOpenAddDialog} newItem={newItem} setNewItem={setNewItem} <div style={{ position: "fixed", left: "50%", transform: 'translateX(-50%)', bottom: "2%" }}>
onSubmit={() => { <Button variant='contained' color="primary" onClick={handleSubmit} sx={{ marginRight: "1rem" }}>
console.log('newItem:', newItem); <SaveIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} />
setItems([...items, newItem]);
setOpenAddDialog(false); </Button>
}} /> <Button variant='contained' color="primary" onClick={handleSubmitAndAddToBuy}>
<ListAltIcon sx={{ fontSize: "1.5rem", marginRight: "0.5rem" }} />
{/* 数量変更ダイアログ */}
<EditAmountDialog openDialog={openAmountDialog} setOpenDialog={setOpenAmountDialog} </Button>
editingItem={editingItem} </div>
setEditingItem={(v) => setEditingItem({ ...editingItem, ...v })}
onSubmit={() => { {/* 新規材料追加ダイアログ */}
setItems([...items.slice(0, editingItemIdx), editingItem, ...items.slice(editingItemIdx + 1)]) <AddStuffAmountDialog openDialog={openAddDialog} setOpenDialog={setOpenAddDialog} newItem={newItem} setNewItem={setNewItem}
setOpenAmountDialog(false); onSubmit={() => {
}} /> console.log('newItem:', newItem);
setItems([...items, newItem]);
</Box> 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);
}} />
</Box>
); );
}; };
export default AddDishes2; export default AddRecipe;

@ -3,8 +3,8 @@
* APIとの通信を担当するモジュール * APIとの通信を担当するモジュール
* *
*/ */
import { LoginCredentials, RegisterCredentials, AuthResponse, /* Task, */ ToBuy, Stuff, Stock, Recipes } from '../types/types'; import { LoginCredentials, RegisterCredentials, AuthResponse, /* Task, */ ToBuy, Stuff, Stock, RecipeData, StuffAndCategoryAndAmount } from '../types/types';
import { AUTH_ERRORS, TOBUY_ERRORS, STOCK_ERRORS } from '../constants/errorMessages'; import { AUTH_ERRORS, TOBUY_ERRORS, STOCK_ERRORS, RECIPE_ERRORS } from '../constants/errorMessages';
// APIのベースURL - 環境変数から取得するか、デフォルト値を使用 // APIのベースURL - 環境変数から取得するか、デフォルト値を使用
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080'; const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080';
@ -109,7 +109,7 @@ export const toBuyApi = {
* @param tobuy * @param tobuy
* @returns * @returns
*/ */
addToBuy:async (tobuy: Omit<ToBuy, 'stuffId' | 'tobuyId'> & { stuffId: number | null, category: string }): Promise<any> => { addToBuy: async (tobuy: Omit<ToBuy, 'stuffId' | 'tobuyId'> & { stuffId: number | null, category: string }): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/api/tobuy/add`, { const response = await fetch(`${API_BASE_URL}/api/tobuy/add`, {
method: 'POST', method: 'POST',
headers: getHeaders(), headers: getHeaders(),
@ -132,6 +132,18 @@ export const toBuyApi = {
}, },
addByRecipe: async (recipeId: number): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/api/tobuy/addByRecipe`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ recipeId }),
})
if (!response.ok) {
throw new Error(TOBUY_ERRORS.CREATE_FAILED);
}
return response.json();
},
/** /**
* *
* @param id ID * @param id ID
@ -159,7 +171,7 @@ export const toBuyApi = {
* @param tobuy * @param tobuy
* @returns * @returns
*/ */
updateToBuy: async (tobuy:ToBuy): Promise<any> => { updateToBuy: async (tobuy: ToBuy): Promise<any> => {
const response = await fetch(`${API_BASE_URL}/api/tobuy/update`, { const response = await fetch(`${API_BASE_URL}/api/tobuy/update`, {
method: 'PUT', method: 'PUT',
headers: getHeaders(), headers: getHeaders(),
@ -274,7 +286,7 @@ export const stockApi = {
req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || '' req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || ''
req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || '' req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || ''
console.log('req: ', req) console.log('req: ', req)
const response = await fetch(`${API_BASE_URL}/api/stocks/update`, { const response = await fetch(`${API_BASE_URL}/api/stocks/update`, {
method: 'PUT', method: 'PUT',
@ -331,11 +343,13 @@ export const recipeApi = {
* @param recipeData * @param recipeData
* @returns * @returns
*/ */
addRecipe: async (recipeData: Recipes): Promise<{ addRecipe: async (recipeData: RecipeData): Promise<{
result: string; result: string;
recipe_id: number; recipeId: number;
message: string; message: string;
}> => { }> => {
console.log('recipeData:', recipeData)
const response = await fetch(`${API_BASE_URL}/api/recipes/add`, { const response = await fetch(`${API_BASE_URL}/api/recipes/add`, {
method: 'POST', method: 'POST',
headers: getHeaders(), // 認証トークンを含むヘッダー headers: getHeaders(), // 認証トークンを含むヘッダー
@ -344,7 +358,31 @@ export const recipeApi = {
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => null); const errorData = await response.json().catch(() => null);
throw new Error(errorData?.message); console.error('/api/recipes/add failed on backend:', errorData?.message)
throw new Error(RECIPE_ERRORS.CREATE_FAILED);
}
return response.json();
},
/**
*
* @param recipeData
* @returns /
*/
updateRecipe: async (recipeData: { recipeId: number } & RecipeData): Promise<{ result: boolean; message: string }> => {
// console.log('recipeData:', recipeData)
const response = await fetch(`${API_BASE_URL}/api/recipes/update`, {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify(recipeData),
});
if (!response.ok) {
const errorData = await response.json().catch(() => null);
console.error('/api/recipes/update failed on backend:', errorData?.message)
throw new Error(RECIPE_ERRORS.UPDATE_FAILED);
} }
return response.json(); return response.json();
@ -382,7 +420,7 @@ export const recipeApi = {
recipeId: number; recipeId: number;
recipeName: string; recipeName: string;
summary: string; summary: string;
stuffs: Array<{ stuffId: number; stuffName: string; amount: number }>; stuffAndAmountArray: StuffAndCategoryAndAmount[];
}> => { }> => {
const response = await fetch(`${API_BASE_URL}/api/recipes/getById?recipeId=${recipeId}`, { const response = await fetch(`${API_BASE_URL}/api/recipes/getById?recipeId=${recipeId}`, {
method: 'GET', method: 'GET',
@ -396,26 +434,6 @@ export const recipeApi = {
return response.json(); return response.json();
}, },
/**
*
* @param recipeData
* @returns /
*/
update: async (recipeData: { recipeId: number } & Recipes): Promise<{ result: boolean; message: string }> => {
const response = await fetch(`${API_BASE_URL}/api/recipes/update`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(recipeData),
});
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(errorData?.message);
}
return response.json();
},
}; };

@ -123,16 +123,16 @@ export interface RegisterCredentials {
* *
* *
*/ */
export interface Recipes { export interface RecipeData {
recipeName: string;// レシピ名 recipeName: string;// レシピ名
summary: string;// レシピ概要 summary: string;// レシピ概要
// 材料リスト(直接配列として内包) // 材料リスト(直接配列として内包)
stuffAndAmountArray: Array<{ stuffAndAmountArray: {
// 既存材料IDまたは新規作成情報のどちらか一方のみ必要 // 既存材料IDまたは新規作成情報のどちらか一方のみ必要
stuffId?: number; // 既存材料ID(オプション) stuffId: number | null; // 既存材料ID(オプション)
stuffName?: string;// 新規材料名(オプション) stuffName?: string;// 新規材料名(オプション)
category?: string; // 新規材料カテゴリ(オプション) category?: string; // 新規材料カテゴリ(オプション)
amount?: number; // 使用量(必須) amount: number; // 使用量(必須)
}>; }[];
} }
Loading…
Cancel
Save