184 lines
6.4 KiB
JavaScript
184 lines
6.4 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import SidebarMenu from './SidebarMenu';
|
|
import { Box, CircularProgress, Typography } from '@mui/material';
|
|
import axios from 'axios';
|
|
|
|
const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
|
|
const [menuData, setMenuData] = useState(null);
|
|
const [lastModified, setLastModified] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [editingItem, setEditingItem] = useState(null);
|
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
|
const [backgroundLoading, setBackgroundLoading] = useState(false);
|
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
|
|
|
|
const forceRefreshMenu = () => {
|
|
setRefreshTrigger(prev => prev + 1);
|
|
localStorage.removeItem('menuCache'); // Очищаем кэш
|
|
};
|
|
|
|
// Загружаем меню из localStorage при инициализации
|
|
useEffect(() => {
|
|
const loadCachedMenu = () => {
|
|
try {
|
|
const cached = localStorage.getItem('menuCache');
|
|
if (cached) {
|
|
const { data, timestamp } = JSON.parse(cached);
|
|
setMenuData(data);
|
|
setLastModified(timestamp);
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to load menu from cache', e);
|
|
}
|
|
};
|
|
|
|
loadCachedMenu();
|
|
}, []);
|
|
|
|
// Основная загрузка меню
|
|
useEffect(() => {
|
|
const fetchMenuData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const headers = lastModified ? { 'If-Modified-Since': lastModified } : {};
|
|
|
|
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/full`, {
|
|
headers,
|
|
validateStatus: status => status === 200 || status === 304
|
|
});
|
|
|
|
if (response.status === 200) {
|
|
const newLastModified = response.headers['last-modified'];
|
|
setMenuData(response.data);
|
|
setLastModified(newLastModified);
|
|
|
|
// Сохраняем в кэш
|
|
localStorage.setItem('menuCache', JSON.stringify({
|
|
data: response.data,
|
|
timestamp: newLastModified
|
|
}));
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching menu data:', err);
|
|
setError(err.message || 'Failed to fetch menu data');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchMenuData();
|
|
}, [refreshTrigger]);
|
|
|
|
// Фоновая проверка обновлений
|
|
useEffect(() => {
|
|
if (!lastModified) return;
|
|
|
|
const checkForUpdates = async () => {
|
|
try {
|
|
setBackgroundLoading(true);
|
|
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/check-updates`, {
|
|
headers: { 'If-Modified-Since': lastModified }
|
|
});
|
|
|
|
if (response.data.hasUpdates) {
|
|
// Если есть обновления, загружаем их в фоне
|
|
const updateResponse = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/full`);
|
|
setMenuData(updateResponse.data);
|
|
setLastModified(updateResponse.headers['last-modified']);
|
|
|
|
localStorage.setItem('menuCache', JSON.stringify({
|
|
data: updateResponse.data,
|
|
timestamp: updateResponse.headers['last-modified']
|
|
}));
|
|
}
|
|
} catch (err) {
|
|
console.warn('Background update check failed', err);
|
|
} finally {
|
|
setBackgroundLoading(false);
|
|
}
|
|
};
|
|
|
|
// Проверяем обновления каждые 5 минут
|
|
const interval = setInterval(checkForUpdates, 5 * 60 * 1000);
|
|
return () => clearInterval(interval);
|
|
}, [lastModified]);
|
|
|
|
const handleSaveChanges = async (updatedItem) => {
|
|
try {
|
|
const response = await axios.put(
|
|
`${import.meta.env.VITE_BACK_URL}/api/menu/${updatedItem.id}`,
|
|
updatedItem,
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
}
|
|
);
|
|
|
|
// Обновляем локальное состояние
|
|
const updateItemInTree = (items) => {
|
|
return items.map(item => {
|
|
if (item.id === updatedItem.id) {
|
|
return { ...item, ...updatedItem };
|
|
}
|
|
if (item.items) {
|
|
return { ...item, items: updateItemInTree(item.items) };
|
|
}
|
|
return item;
|
|
});
|
|
};
|
|
|
|
setMenuData(prev => ({
|
|
...prev,
|
|
items: updateItemInTree(prev.items),
|
|
}));
|
|
|
|
setEditModalOpen(false);
|
|
} catch (err) {
|
|
console.error('Error updating menu item:', err);
|
|
setError(err.response?.data?.message || err.message || 'Failed to update menu item');
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Box p={2}>
|
|
<Typography color="error">Error loading menu: {error}</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!menuData) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<SidebarMenu
|
|
data={menuData}
|
|
isDarkMode={isDarkMode}
|
|
setIsDarkMode={setIsDarkMode}
|
|
onEditItem={(item) => {
|
|
setEditingItem(item);
|
|
setEditModalOpen(true);
|
|
}}
|
|
onSelectItem={onMenuSelect}
|
|
editModalOpen={editModalOpen}
|
|
editingItem={editingItem}
|
|
onCloseEditModal={() => setEditModalOpen(false)}
|
|
onSaveChanges={handleSaveChanges}
|
|
forceRefreshMenu={forceRefreshMenu}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default SidebarMenuWrapper; |