feat: add Categories page with refined layout and translation support

This commit is contained in:
sebseb7
2025-12-06 14:29:33 +01:00
parent 5d3e0832fe
commit e88370ff3e
6 changed files with 698 additions and 323 deletions

231
src/pages/CategoriesPage.js Normal file
View File

@@ -0,0 +1,231 @@
import React, { Component } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import Paper from '@mui/material/Paper';
import LegalPage from './LegalPage.js';
import CategoryBox from '../components/CategoryBox.js';
import { withI18n } from '../i18n/withTranslation.js';
// Helper function to recursively collect all categories from the tree
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
if (!categoryNode) return categories;
// Add current category (skip root category 209)
if (categoryNode.id !== 209 && categoryNode.seoName) {
categories.push({
id: categoryNode.id,
name: categoryNode.name,
seoName: categoryNode.seoName,
level: level
});
}
// Recursively add children
if (categoryNode.children) {
for (const child of categoryNode.children) {
collectAllCategories(child, categories, level + 1);
}
}
return categories;
};
// Check for cached data - handle both browser and prerender environments
const getProductCache = () => {
if (typeof window !== "undefined" && window.productCache) {
return window.productCache;
}
if (
typeof global !== "undefined" &&
global.window &&
global.window.productCache
) {
return global.window.productCache;
}
return null;
};
// Initialize categories from cache if available (for prerendering)
const initializeCategoryTree = (language = 'de') => {
// Try synchronous get from service first if available
if (typeof window !== "undefined" && window.categoryService) {
const syncData = window.categoryService.getSync(209, language);
if (syncData) return syncData;
}
const productCache = getProductCache();
// Fallback to productCache checks (mostly for prerender context if service isn't init)
const cacheKey = `categoryTree_209_${language}`; // Note: Service uses simpler keys, might mismatch if strictly relying on this
// Check old style cache just in case
if (productCache && productCache[cacheKey]) {
const cached = productCache[cacheKey];
if (cached.categoryTree) return cached.categoryTree;
}
return null;
};
class CategoriesPage extends Component {
constructor(props) {
super(props);
// Use languageContext if available, otherwise fallback to i18n or 'de'
const currentLanguage = props.languageContext?.currentLanguage || props.i18n?.language || 'de';
const initialTree = initializeCategoryTree(currentLanguage);
console.log("CategoriesPage constructor: currentLanguage =", currentLanguage);
this.state = {
categoryTree: initialTree,
loading: !initialTree
};
}
componentDidMount() {
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
// If we don't have data yet, or if we want to ensure freshness/socket connection
if (!this.state.categoryTree) {
this.fetchCategories(currentLanguage);
}
}
componentDidUpdate(prevProps) {
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const prevLanguage = prevProps.languageContext?.currentLanguage || prevProps.i18n?.language || 'de';
if (currentLanguage !== prevLanguage) {
console.log(`CategoriesPage: Language changed from ${prevLanguage} to ${currentLanguage}. Refetching.`);
this.setState({ loading: true, categoryTree: [] }); // Clear tree to force re-render/loading state
this.fetchCategories(currentLanguage);
}
}
fetchCategories = (language) => {
// Use categoryService which handles caching and translated vs untranslated responses correctly
console.log(`CategoriesPage: Fetching categories for ${language} using categoryService`);
window.categoryService.get(209, language).then((tree) => {
if (tree) {
this.setState({
categoryTree: tree,
loading: false
});
} else {
console.error('Failed to fetch categories via service');
this.setState({ loading: false });
}
}).catch(err => {
console.error("Error in categoryService:", err);
this.setState({ loading: false });
});
};
renderLevel1Section = (l1Node) => {
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
const descendants = collectAllCategories(l1Node).slice(1);
return (
<Paper
key={l1Node.id}
elevation={1}
sx={{
p: 2,
mb: 3,
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'flex-start', md: 'flex-start' },
gap: 3
}}
>
{/* Level 1 Header/Box */}
<Box sx={{
minWidth: '150px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1
}}>
<CategoryBox
id={l1Node.id}
name={l1Node.name}
seoName={l1Node.seoName}
sx={{
boxShadow: 4,
width: '150px',
height: '150px'
}}
/>
<Typography
variant="h6"
sx={{
textAlign: 'center',
fontWeight: 'bold',
display: { xs: 'block', md: 'none' } // Only show text below box on mobile if needed, or rely on box text
}}
>
{/* Box already has text, so maybe no extra text needed here */}
</Typography>
</Box>
{/* Descendants area */}
<Box sx={{ flex: 1 }}>
<Box sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2
}}>
{descendants.map((cat) => (
<CategoryBox
key={cat.id}
id={cat.id}
name={cat.name}
seoName={cat.seoName}
sx={{
width: '100px',
height: '100px',
minWidth: '100px',
minHeight: '100px',
boxShadow: 1,
transition: 'transform 0.2s',
'&:hover': { transform: 'scale(1.05)', boxShadow: 3 },
fontSize: '0.9rem' // Smaller text for smaller boxes
}}
/>
))}
</Box>
</Box>
</Paper>
);
};
render() {
const { t } = this.props;
const { categoryTree, loading } = this.state;
const content = (
<Box>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
) : (
<Box>
{categoryTree && categoryTree.children && categoryTree.children.map((child) => (
this.renderLevel1Section(child)
))}
{(!categoryTree || !categoryTree.children || categoryTree.children.length === 0) && (
<Typography>Keine Kategorien gefunden.</Typography>
)}
</Box>
)}
</Box>
);
return <LegalPage title={t ? t('navigation.categories') : 'Kategorien'} content={content} />;
}
}
export default withI18n()(CategoriesPage);