diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index fa90b27..fc5a140 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -2,7 +2,7 @@ * アプリケーションの共通レイアウトを提供するコンポーネント * ヘッダー(AppBar)とメインコンテンツ領域を含む基本的なページ構造を定義 */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { AppBar, Toolbar, @@ -16,7 +16,8 @@ import { ListItemIcon, ListItemButton, Divider, - IconButton + IconButton, + AlertColor } from '@mui/material'; import { Menu as MenuIcon, @@ -26,6 +27,8 @@ import { SoupKitchen as SoupKitchenIcon, } from '@mui/icons-material'; import { useNavigate, Outlet, useLocation } from 'react-router-dom'; +import { MessageContext } from './MessageContext'; +import MessageAlert from './MessageAlert'; const Layout: React.FC = () => { const navigate = useNavigate(); @@ -60,6 +63,40 @@ const Layout: React.FC = () => { 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('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'); + }; + + return ( {/* ヘッダー部分 - アプリ名とログアウトボタンを表示 */} @@ -103,16 +140,16 @@ const Layout: React.FC = () => { {/* テストページへのリンクを追加 */} - {/* 在庫リストへのリンクを追加 */} - handleNavigate('/addRecipe')} + {/* 在庫リストへのリンクを追加 */} + handleNavigate('/addRecipe')} selected={isSelected('/addRecipe')} > - handleNavigate('/recipeList')} + handleNavigate('/recipeList')} selected={isSelected('/recipeList')} > @@ -133,7 +170,17 @@ const Layout: React.FC = () => { {/* メインコンテンツ領域 - 子ルートのコンポーネントがここに表示される */} - {/* React Router の Outlet - 子ルートのコンポーネントがここにレンダリングされる */} + + + + {/* React Router の Outlet - 子ルートのコンポーネントがここにレンダリングされる */} + + diff --git a/frontend/src/components/MessageContext.tsx b/frontend/src/components/MessageContext.tsx new file mode 100644 index 0000000..599f2a5 --- /dev/null +++ b/frontend/src/components/MessageContext.tsx @@ -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(undefined); + +export const useMessage = () => { + const context = useContext(MessageContext); + if (!context) throw new Error('useMessage must be used within MessageContext.Provider'); + return context; +}; diff --git a/frontend/src/pages/AddRecipe.tsx b/frontend/src/pages/AddRecipe.tsx index 2486d70..9a397dd 100644 --- a/frontend/src/pages/AddRecipe.tsx +++ b/frontend/src/pages/AddRecipe.tsx @@ -38,6 +38,7 @@ import EditAmountDialog from '../components/EditAmountDialog'; import { recipeApi, toBuyApi } from '../services/api'; import { useNavigate, useParams } from 'react-router-dom'; import MessageAlert from '../components/MessageAlert'; +import { useMessage } from '../components/MessageContext'; const AddRecipe: React.FC = () => { const { recipeId: recipeIdStr } = useParams(); @@ -45,20 +46,6 @@ const AddRecipe: React.FC = () => { const navigate = useNavigate(); - // メッセージ - const [_openMessage, _setOpenMessage] = useState(false); - const [_messageText, _setMessageText] = useState(''); - const [_messageType, _setMessageType] = useState('info'); - const _showMessage = (message: string, severity: AlertColor) => { - _setOpenMessage(true); - _setMessageText(message); - _setMessageType(severity); - } - 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 [recipeLoaded, setRecipeLoaded] = useState(false); // 料理名,説明 @@ -77,6 +64,9 @@ const AddRecipe: React.FC = () => { const [editingItem, setEditingItem] = useState(emptyItem); const [editingItemIdx, setEditingItemIdx] = useState(0); + // エラーメッセージ表示 + const { showErrorMessage } = useMessage(); + const loadRecipe = async () => { if (recipeId && !recipeLoaded) { const recipe = await recipeApi.getById(recipeId); @@ -147,8 +137,6 @@ const AddRecipe: React.FC = () => { return ( <> - _setOpenMessage(false)}> - {(recipeId && !recipeLoaded) ?

読み込み中...

: