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/components/Layout.tsx

179 lines
5.5 KiB

/**
* アプリケーションの共通レイアウトを提供するコンポーネント
* ヘッダー(AppBar)とメインコンテンツ領域を含む基本的なページ構造を定義
*/
import React, { useEffect, useState } from 'react';
import {
AppBar,
Toolbar,
Typography,
Container,
Box,
Button,
Drawer,
List,
ListItemText,
ListItemIcon,
ListItemButton,
Divider,
IconButton,
AlertColor,
BottomNavigation,
BottomNavigationAction,
Paper
} from '@mui/material';
import {
Menu as MenuIcon,
ListAlt as ListAltIcon,
Inventory as InventoryIcon, // テストページ用のアイコン
Science as ScienceIcon, // 鈴木
SoupKitchen as SoupKitchenIcon,
ShoppingCart as ShoppingCartIcon
} 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();
const location = useLocation();
const [drawerOpen, setDrawerOpen] = useState(false);
const getTabIndex = (pathname: string) => {
pathname = pathname.split("/")[1];
console.log(pathname);
switch (pathname) {
case 'stock':
return 0;
case 'tasks':
return 1;
case 'recipeList':
return 2;
case 'addRecipe':
return 2;
default:
return -1;
}
};
const [bottomNavi, setBottomNavi] = useState(getTabIndex(location.pathname));
useEffect(() => {
setBottomNavi(getTabIndex(location.pathname));
}, [location.pathname]);
/**
* ログアウト処理を行うハンドラー関数
* ローカルストレージからトークンを削除し、ログインページにリダイレクト
*/
const handleLogout = () => {
localStorage.removeItem('token');
navigate('/login');
};
// メニューを開閉するハンドラー
const toggleDrawer = () => {
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<AlertColor>('info');
const showMessage = (msg: string, sev: AlertColor) => {
console.log('ShowMessage:', msg, sev);
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');
};
const onBottomNaviChange = (event: React.SyntheticEvent, value: any) => {
setBottomNavi(value);
switch (value) {
case 0:
navigate('stock');
break;
case 1:
navigate('tasks');
break;
case 2:
navigate('recipeList');
break;
}
// ここでルーティング処理などを行う
}
return (
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
{/* ヘッダー部分 - アプリ名とログアウトボタンを表示 */}
<AppBar position="static" elevation={0}>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
shopchop
</Typography>
<Button color="inherit" onClick={handleLogout}>
</Button>
</Toolbar>
</AppBar>
<Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: (theme) => theme.zIndex.drawer + 1 }} elevation={3}>
<BottomNavigation
showLabels
value={bottomNavi}
onChange={onBottomNaviChange}
>
<BottomNavigationAction label="在庫" icon={<InventoryIcon />} />
<BottomNavigationAction label="買うもの" icon={<ShoppingCartIcon />} />
<BottomNavigationAction label="レシピ" icon={<SoupKitchenIcon />} />
</BottomNavigation>
</Paper>
{/* メインコンテンツ領域 - 子ルートのコンポーネントがここに表示される */}
<Box component="main" sx={{ flexGrow: 1, bgcolor: 'background.default', py: 3 }}>
<Container sx={{ padding: 0, mt: 0, mb: 0, mr: 'auto', ml: 'auto' }}>
<MessageContext.Provider value={{ showErrorMessage, showWarningMessage, showSuccessMessage, showInfoMessage }}>
<MessageAlert
open={msgOpen}
message={msgText}
severity={msgType}
onClose={handleMsgClose}
/>
<Outlet /> {/* React Router の Outlet - 子ルートのコンポーネントがここにレンダリングされる */}
</MessageContext.Provider>
</Container>
</Box>
</Box>
);
};
export default Layout;