Skip to content

Commit e11eb90

Browse files
committed
feat: enhance InstanceProductsTable and SerializedPartsTable with improved API calls and UI feedback
1 parent c10cc4d commit e11eb90

File tree

4 files changed

+470
-90
lines changed

4 files changed

+470
-90
lines changed

ichub-frontend/src/features/catalog-management/components/product-detail/InstanceProductsTable.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* SPDX-License-Identifier: Apache-2.0
2121
********************************************************************************/
2222

23-
import { useEffect, useState } from 'react';
23+
import { useEffect, useState, useRef } from 'react';
2424
import Box from '@mui/material/Box';
2525
import Typography from '@mui/material/Typography';
2626
import Paper from '@mui/material/Paper';
@@ -46,7 +46,7 @@ import { SerializedPart } from '../../../serialized-parts/types';
4646
import { SerializedPartTwinRead } from '../../../serialized-parts/types/twin-types';
4747
import { fetchSerializedParts } from '../../../serialized-parts/api';
4848
import { createSerializedPartTwin, shareSerializedPartTwin, unshareSerializedPartTwin, deleteSerializedPart } from '../../../serialized-parts/api';
49-
import { fetchAllSerializedPartTwins } from '../../../serialized-parts/api';
49+
import { fetchSerializedPartTwinsForCatalogPart } from '../../../serialized-parts/api';
5050
import { PartType, StatusVariants } from '../../types/types';
5151
import { SerializedPartStatusChip } from './SerializedPartStatusChip';
5252

@@ -62,6 +62,9 @@ interface InstanceProductsTableProps {
6262
}
6363

6464
export default function InstanceProductsTable({ part, onAddClick }: Readonly<InstanceProductsTableProps>) {
65+
// Ref to prevent duplicate API calls in React StrictMode
66+
const dataLoadedRef = useRef(false);
67+
6568
const [rows, setRows] = useState<SerializedPartWithStatus[]>([]);
6669
const [twinCreatingId, setTwinCreatingId] = useState<number | null>(null);
6770
const [twinSharingId, setTwinSharingId] = useState<number | null>(null);
@@ -110,14 +113,22 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
110113
useEffect(() => {
111114
if (!part) {
112115
setRows([]);
116+
dataLoadedRef.current = false; // Reset ref when part changes
113117
return;
114118
}
119+
115120
const loadData = async () => {
121+
// Prevent duplicate calls in React StrictMode
122+
if (dataLoadedRef.current) {
123+
return;
124+
}
125+
dataLoadedRef.current = true;
126+
116127
try {
117128
// Fetch both serialized parts and twins
118129
const [serializedParts, twins] = await Promise.all([
119130
fetchSerializedParts(part.manufacturerId, part.manufacturerPartId),
120-
fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId)
131+
fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId)
121132
]);
122133

123134
// Merge serialized parts with twin status
@@ -135,6 +146,8 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
135146
console.log('Loaded instance products:', rowsWithStatus);
136147
} catch (error) {
137148
console.error("Error fetching instance products:", error);
149+
// Reset ref on error so it can be retried
150+
dataLoadedRef.current = false;
138151
}
139152
};
140153

@@ -163,7 +176,7 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
163176
// Refresh data after successful creation
164177
const [serializedParts, twins] = await Promise.all([
165178
fetchSerializedParts(part.manufacturerId, part.manufacturerPartId),
166-
fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId)
179+
fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId)
167180
]);
168181

169182
const rowsWithStatus = serializedParts.map((serializedPart, index) => {
@@ -214,7 +227,7 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
214227
// Refresh data after successful share
215228
const [serializedParts, twins] = await Promise.all([
216229
fetchSerializedParts(part.manufacturerId, part.manufacturerPartId),
217-
fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId)
230+
fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId)
218231
]);
219232

220233
const rowsWithStatus = serializedParts.map((serializedPart, index) => {
@@ -257,7 +270,7 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
257270
setTwinUnsharingId(row.id);
258271
try {
259272
// Find the twin to get the AAS ID
260-
const twins = await fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId);
273+
const twins = await fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId);
261274
const twin = twins.find(
262275
(t) => t.manufacturerId === row.manufacturerId &&
263276
t.manufacturerPartId === row.manufacturerPartId &&
@@ -281,7 +294,7 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
281294
// Refresh data after successful unshare
282295
const [serializedParts, updatedTwins] = await Promise.all([
283296
fetchSerializedParts(part.manufacturerId, part.manufacturerPartId),
284-
fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId)
297+
fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId)
285298
]);
286299

287300
const rowsWithStatus = serializedParts.map((serializedPart, index) => {
@@ -337,7 +350,7 @@ export default function InstanceProductsTable({ part, onAddClick }: Readonly<Ins
337350
// Refresh data after successful deletion
338351
const [serializedParts, twins] = await Promise.all([
339352
fetchSerializedParts(part.manufacturerId, part.manufacturerPartId),
340-
fetchAllSerializedPartTwins(part.manufacturerId, part.manufacturerPartId)
353+
fetchSerializedPartTwinsForCatalogPart(part.manufacturerId, part.manufacturerPartId)
341354
]);
342355

343356
const rowsWithStatus = serializedParts.map((serializedPart, index) => {

ichub-frontend/src/features/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import { FeatureConfig, NavigationItem } from '../types/routing';
3030
// Import all feature configurations
3131
export const allFeatures: FeatureConfig[] = [
3232
catalogManagementFeature,
33-
partDiscoveryFeature,
3433
serializedPartsFeature,
34+
partDiscoveryFeature,
3535
partnerManagementFeature,
3636
// Add placeholder for status feature (disabled)
3737
{

ichub-frontend/src/features/serialized-parts/api.ts

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ const SERIALIZED_PART_READ_BASE_PATH = '/part-management/serialized-part';
2929
const SERIALIZED_PART_TWIN_BASE_PATH = '/twin-management/serialized-part-twin';
3030
const backendUrl = getIchubBackendUrl();
3131

32-
// Simple cache for twins to avoid redundant API calls
33-
const twinsCache = new Map<string, { data: SerializedPartTwinRead[]; timestamp: number }>();
34-
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
35-
36-
const getCacheKey = (manufacturerId?: string, manufacturerPartId?: string) => {
37-
return `${manufacturerId || 'all'}-${manufacturerPartId || 'all'}`;
38-
};
32+
// Create axios instance with caching configuration for GET requests
33+
const cacheableAxios = axios.create({
34+
headers: {
35+
'Cache-Control': 'max-age=300', // Cache for 5 minutes
36+
},
37+
});
3938

4039
export const fetchAllSerializedParts = async (): Promise<SerializedPart[]> => {
4140
const response = await axios.get<SerializedPart[]>(`${backendUrl}${SERIALIZED_PART_READ_BASE_PATH}`);
@@ -81,42 +80,34 @@ export const shareSerializedPartTwin = async (
8180
);
8281
};
8382

84-
export const fetchAllSerializedPartTwins = async (
85-
manufacturerId?: string,
86-
manufacturerPartId?: string
87-
): Promise<SerializedPartTwinRead[]> => {
88-
// Check cache first
89-
const cacheKey = getCacheKey(manufacturerId, manufacturerPartId);
90-
const cached = twinsCache.get(cacheKey);
91-
const now = Date.now();
83+
export const fetchAllSerializedPartTwins = async (): Promise<SerializedPartTwinRead[]> => {
84+
// Fetch all twins without any filters using browser caching
85+
const params = new URLSearchParams();
86+
params.append('include_data_exchange_agreements', 'true');
9287

93-
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
94-
console.log('Returning cached twins data for:', cacheKey);
95-
return cached.data;
96-
}
88+
console.log('Fetching all twins from API (browser-cached)');
89+
const response = await cacheableAxios.get<SerializedPartTwinRead[]>(
90+
`${backendUrl}${SERIALIZED_PART_TWIN_BASE_PATH}?${params.toString()}`
91+
);
9792

98-
// Build query parameters to filter on the server side
93+
return response.data;
94+
};
95+
96+
export const fetchSerializedPartTwinsForCatalogPart = async (
97+
manufacturerId: string,
98+
manufacturerPartId: string
99+
): Promise<SerializedPartTwinRead[]> => {
100+
// Build query parameters to filter by catalog part using browser caching
99101
const params = new URLSearchParams();
100102
params.append('include_data_exchange_agreements', 'true');
103+
params.append('manufacturerId', manufacturerId);
104+
params.append('manufacturerPartId', manufacturerPartId);
101105

102-
if (manufacturerId) {
103-
params.append('manufacturerId', manufacturerId);
104-
}
105-
if (manufacturerPartId) {
106-
params.append('manufacturerPartId', manufacturerPartId);
107-
}
108-
109-
console.log('Fetching twins from API for:', cacheKey);
110-
const response = await axios.get<SerializedPartTwinRead[]>(
106+
console.log('Fetching filtered twins from API for catalog part (browser-cached):', manufacturerId, manufacturerPartId);
107+
const response = await cacheableAxios.get<SerializedPartTwinRead[]>(
111108
`${backendUrl}${SERIALIZED_PART_TWIN_BASE_PATH}?${params.toString()}`
112109
);
113110

114-
// Cache the result
115-
twinsCache.set(cacheKey, {
116-
data: response.data,
117-
timestamp: now
118-
});
119-
120111
return response.data;
121112
};
122113

0 commit comments

Comments
 (0)