feat(GrowTentKonfigurator): add periodic cache validity checking

Implement interval-based cache monitoring every 60 seconds to detect
misses or expirations across categories (Zelte, Lampen, Abluft-sets,
Set-zubehoer). Update per-category load status tracking to conditionally
render sections independently, improving UX by avoiding global loading
delays and ensuring timely refetches. Clear interval on unmount to prevent
memory leaks.
This commit is contained in:
sebseb7
2025-09-09 18:10:08 +02:00
parent 77ffe864b1
commit 0dd1e01018

View File

@@ -92,8 +92,7 @@ class GrowTentKonfigurator extends Component {
this.handleExtraToggle = this.handleExtraToggle.bind(this); this.handleExtraToggle = this.handleExtraToggle.bind(this);
this.calculateTotalPrice = this.calculateTotalPrice.bind(this); this.calculateTotalPrice = this.calculateTotalPrice.bind(this);
this.saveStateToWindow = this.saveStateToWindow.bind(this); this.saveStateToWindow = this.saveStateToWindow.bind(this);
this.checkCacheValidity = this.checkCacheValidity.bind(this);
} }
saveStateToWindow() { saveStateToWindow() {
@@ -116,6 +115,7 @@ class GrowTentKonfigurator extends Component {
this.fetchCategoryData("Lampen"); this.fetchCategoryData("Lampen");
this.fetchCategoryData("Abluft-sets"); this.fetchCategoryData("Abluft-sets");
this.fetchCategoryData("Set-zubehoer"); this.fetchCategoryData("Set-zubehoer");
this.cacheCheckInterval = setInterval(this.checkCacheValidity, 60000);
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@@ -156,12 +156,25 @@ class GrowTentKonfigurator extends Component {
} }
} }
componentWillUnmount() {
if (this.cacheCheckInterval) {
clearInterval(this.cacheCheckInterval);
}
}
fetchCategoryData(categoryId) { fetchCategoryData(categoryId) {
const cachedData = getCachedCategoryData(categoryId); const cachedData = getCachedCategoryData(categoryId);
if (cachedData) { if (cachedData) {
this.setState(prevState => ({
categoryLoadStatus: {
...prevState.categoryLoadStatus,
[categoryId]: true
}
}));
return; return;
} }
console.log(`Cache miss for category ${categoryId}, fetching...`);
window.socketManager.off(`productList:${categoryId}`); window.socketManager.off(`productList:${categoryId}`);
window.socketManager.on(`productList:${categoryId}`,(response) => { window.socketManager.on(`productList:${categoryId}`,(response) => {
@@ -182,6 +195,23 @@ class GrowTentKonfigurator extends Component {
); );
} }
checkCacheValidity() {
const categories = ["Zelte", "Lampen", "Abluft-sets", "Set-zubehoer"];
let needsUpdate = false;
const newStatus = { ...this.state.categoryLoadStatus };
for (const categoryId of categories) {
if (newStatus[categoryId] && !getCachedCategoryData(categoryId)) {
console.log(`Refetching ${categoryId} due to cache miss/expiry`);
newStatus[categoryId] = false;
needsUpdate = true;
this.fetchCategoryData(categoryId);
}
}
if (needsUpdate) {
this.setState({ categoryLoadStatus: newStatus });
}
}
handleTentShapeSelect(shapeId) { handleTentShapeSelect(shapeId) {
this.setState({ selectedTentShape: shapeId }); this.setState({ selectedTentShape: shapeId });
} }
@@ -600,22 +630,20 @@ class GrowTentKonfigurator extends Component {
return null; // Don't show tent sizes until shape is selected return null; // Don't show tent sizes until shape is selected
} }
if (!this.state.categoryLoadStatus["Zelte"]) {
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary">
Lade Growbox-Produkte...
</Typography>
</Box>
);
}
// Get real filtered tent products for the selected shape // Get real filtered tent products for the selected shape
const filteredTents = this.getTentsForShape(selectedTentShape); const filteredTents = this.getTentsForShape(selectedTentShape);
// Show loading state if data is not yet available
if (filteredTents.length === 0) { if (filteredTents.length === 0) {
const cachedData = getCachedCategoryData('Zelte');
if (!cachedData) {
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary">
Lade Growbox-Produkte...
</Typography>
</Box>
);
}
// If we have cached data but no filtered tents, show empty state
return ( return (
<Box sx={{ textAlign: 'center', py: 4 }}> <Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary"> <Typography variant="h6" color="text.secondary">
@@ -624,18 +652,6 @@ class GrowTentKonfigurator extends Component {
</Box> </Box>
); );
} }
if (filteredTents.length === 0) {
return (
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h6" color="text.secondary">
Keine Produkte verfügbar
</Typography>
</Box>
);
}
return ( return (
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}> <Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
@@ -743,11 +759,7 @@ class GrowTentKonfigurator extends Component {
); );
} }
// Get real lamps for selected tent shape if (!this.state.categoryLoadStatus["Lampen"]) {
const availableLamps = this.getLampsForTentShape(selectedTentShape);
const cachedData = getCachedCategoryData('Lampen');
if (!cachedData) {
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}> <Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
@@ -760,6 +772,8 @@ class GrowTentKonfigurator extends Component {
); );
} }
const availableLamps = this.getLampsForTentShape(selectedTentShape);
if (availableLamps.length === 0) { if (availableLamps.length === 0) {
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
@@ -772,7 +786,7 @@ class GrowTentKonfigurator extends Component {
</Box> </Box>
); );
} }
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}> <Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
@@ -872,11 +886,7 @@ class GrowTentKonfigurator extends Component {
); );
} }
// Get real ventilation products for selected tent shape if (!this.state.categoryLoadStatus["Abluft-sets"]) {
const availableVentilation = this.getVentilationForTentShape(selectedTentShape);
const cachedData = getCachedCategoryData('Abluft-sets');
if (!cachedData) {
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}> <Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
@@ -889,6 +899,8 @@ class GrowTentKonfigurator extends Component {
); );
} }
const availableVentilation = this.getVentilationForTentShape(selectedTentShape);
if (availableVentilation.length === 0) { if (availableVentilation.length === 0) {
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
@@ -901,7 +913,7 @@ class GrowTentKonfigurator extends Component {
</Box> </Box>
); );
} }
return ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}> <Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
@@ -1025,6 +1037,19 @@ class GrowTentKonfigurator extends Component {
renderExtrasSection() { renderExtrasSection() {
const { selectedExtras } = this.state; const { selectedExtras } = this.state;
if (!this.state.categoryLoadStatus["Set-zubehoer"]) {
return (
<Box sx={{ mb: 4 }}>
<Typography variant="h2" component="h2" gutterBottom sx={{ color: '#2e7d32', fontWeight: 'bold' }}>
5. Extras hinzufügen (optional)
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Lade Extras...
</Typography>
</Box>
);
}
const extrasData = getCachedCategoryData('Set-zubehoer'); const extrasData = getCachedCategoryData('Set-zubehoer');
if (!extrasData) { if (!extrasData) {
@@ -1034,7 +1059,7 @@ class GrowTentKonfigurator extends Component {
5. Extras hinzufügen (optional) 5. Extras hinzufügen (optional)
</Typography> </Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}> <Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Lade Extras... Keine Extras verfügbar
</Typography> </Typography>
</Box> </Box>
); );
@@ -1225,14 +1250,6 @@ class GrowTentKonfigurator extends Component {
render() { render() {
// Check if all 4 category data sections have loaded
const tentsData = getCachedCategoryData('Zelte');
const lampsData = getCachedCategoryData('Lampen');
const ventilationData = getCachedCategoryData('Abluft-sets');
const extrasData = getCachedCategoryData('Set-zubehoer');
const allDataLoaded = tentsData && lampsData && ventilationData && extrasData;
return ( return (
<Container maxWidth="lg" sx={{ py: 4 }}> <Container maxWidth="lg" sx={{ py: 4 }}>
<Paper elevation={2} sx={{ p: 4, borderRadius: 2 }}> <Paper elevation={2} sx={{ p: 4, borderRadius: 2 }}>
@@ -1297,27 +1314,19 @@ class GrowTentKonfigurator extends Component {
{this.renderTentShapeSection()} {this.renderTentShapeSection()}
<Divider sx={{ my: 4 }} /> <Divider sx={{ my: 4 }} />
{allDataLoaded && (
<>
{this.renderTentSizeSection()} {this.renderTentSizeSection()}
{this.state.selectedTentShape && <Divider sx={{ my: 4 }} />} {this.state.selectedTentShape && <Divider sx={{ my: 4 }} />}
{this.renderLightSection()} {this.renderLightSection()}
<Divider sx={{ my: 4 }} /> <Divider sx={{ my: 4 }} />
{this.renderVentilationSection()} {this.renderVentilationSection()}
<Divider sx={{ my: 4 }} /> <Divider sx={{ my: 4 }} />
{this.renderExtrasSection()} {this.renderExtrasSection()}
{/* Inline summary section - expands when scrolling to bottom */} {/* Inline summary section - expands when scrolling to bottom */}
{this.renderInlineSummary()} {this.renderInlineSummary()}
</>
)}
</Paper> </Paper>
</Container> </Container>
); );