Fix lines/sublines getting stuck in loading state on change
This commit is contained in:
@@ -446,6 +446,13 @@ const ValidationContainer = <T extends string>({
|
|||||||
line: undefined,
|
line: undefined,
|
||||||
subline: undefined
|
subline: undefined
|
||||||
};
|
};
|
||||||
|
} else if (rowIndex >= 0 && rowIndex < newData.length) {
|
||||||
|
// Fallback if __index is not yet present on the row
|
||||||
|
newData[rowIndex] = {
|
||||||
|
...newData[rowIndex],
|
||||||
|
line: undefined,
|
||||||
|
subline: undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
@@ -454,7 +461,9 @@ const ValidationContainer = <T extends string>({
|
|||||||
setValidatingCells(prev => new Set(prev).add(`${rowIndex}-line`));
|
setValidatingCells(prev => new Set(prev).add(`${rowIndex}-line`));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchProductLines(rowId, companyId)
|
// Use __index when available, otherwise fall back to the UI row index
|
||||||
|
const rowKey = (rowId !== undefined && rowId !== null) ? rowId : rowIndex;
|
||||||
|
fetchProductLines(rowKey, companyId)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(`Error fetching product lines for company ${companyId}:`, err);
|
console.error(`Error fetching product lines for company ${companyId}:`, err);
|
||||||
toast.error("Failed to load product lines");
|
toast.error("Failed to load product lines");
|
||||||
@@ -480,6 +489,12 @@ const ValidationContainer = <T extends string>({
|
|||||||
...newData[idx],
|
...newData[idx],
|
||||||
subline: undefined
|
subline: undefined
|
||||||
};
|
};
|
||||||
|
} else if (rowIndex >= 0 && rowIndex < newData.length) {
|
||||||
|
// Fallback if __index is not yet present on the row
|
||||||
|
newData[rowIndex] = {
|
||||||
|
...newData[rowIndex],
|
||||||
|
subline: undefined
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Could not find row with ID ${rowId} to clear subline values`);
|
console.warn(`Could not find row with ID ${rowId} to clear subline values`);
|
||||||
}
|
}
|
||||||
@@ -489,7 +504,9 @@ const ValidationContainer = <T extends string>({
|
|||||||
// Fetch sublines
|
// Fetch sublines
|
||||||
setValidatingCells(prev => new Set(prev).add(`${rowIndex}-subline`));
|
setValidatingCells(prev => new Set(prev).add(`${rowIndex}-subline`));
|
||||||
|
|
||||||
fetchSublines(rowId, lineId)
|
// Use __index when available, otherwise fall back to the UI row index
|
||||||
|
const rowKey = (rowId !== undefined && rowId !== null) ? rowId : rowIndex;
|
||||||
|
fetchSublines(rowKey, lineId)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(`Error fetching sublines for line ${lineId}:`, err);
|
console.error(`Error fetching sublines for line ${lineId}:`, err);
|
||||||
toast.error("Failed to load sublines");
|
toast.error("Failed to load sublines");
|
||||||
|
|||||||
@@ -342,12 +342,13 @@ const ValidationTable = <T extends string>({
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
// Get row-specific options for line and subline fields
|
// Get row-specific options for line and subline fields
|
||||||
let options = fieldOptions;
|
let options = fieldOptions;
|
||||||
const rowId = row.original.__index;
|
const rowId = (row.original as any).__index;
|
||||||
|
const lookupKey = (rowId !== undefined && rowId !== null) ? rowId : row.index;
|
||||||
|
|
||||||
if (fieldKey === 'line' && rowId && rowProductLines[rowId]) {
|
if (fieldKey === 'line' && lookupKey !== undefined && rowProductLines[lookupKey]) {
|
||||||
options = rowProductLines[rowId];
|
options = rowProductLines[lookupKey];
|
||||||
} else if (fieldKey === 'subline' && rowId && rowSublines[rowId]) {
|
} else if (fieldKey === 'subline' && lookupKey !== undefined && rowSublines[lookupKey]) {
|
||||||
options = rowSublines[rowId];
|
options = rowSublines[lookupKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current cell value first
|
// Get the current cell value first
|
||||||
@@ -374,10 +375,10 @@ const ValidationTable = <T extends string>({
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
}
|
}
|
||||||
// Add loading state for line/subline fields
|
// Add loading state for line/subline fields
|
||||||
else if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) {
|
else if (fieldKey === 'line' && lookupKey !== undefined && isLoadingLines[lookupKey]) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
}
|
}
|
||||||
else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) {
|
else if (fieldKey === 'subline' && lookupKey !== undefined && isLoadingSublines[lookupKey]) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,15 +635,22 @@ const ValidationTable = <T extends string>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simplified memo - React 18+ handles most optimizations well
|
// Memo comparator: re-render when any prop affecting visible state changes.
|
||||||
// Only check critical props that frequently change
|
// Keep this conservative to avoid skipping updates for loading/options states.
|
||||||
const areEqual = (prev: ValidationTableProps<any>, next: ValidationTableProps<any>) => {
|
const areEqual = (prev: ValidationTableProps<any>, next: ValidationTableProps<any>) => {
|
||||||
// Only prevent re-render if absolutely nothing changed (rare case)
|
|
||||||
return (
|
return (
|
||||||
|
// Core props
|
||||||
prev.data === next.data &&
|
prev.data === next.data &&
|
||||||
prev.validationErrors === next.validationErrors &&
|
prev.validationErrors === next.validationErrors &&
|
||||||
prev.rowSelection === next.rowSelection
|
prev.rowSelection === next.rowSelection &&
|
||||||
|
// Loading + validation state that affects cell skeletons
|
||||||
|
prev.validatingCells === next.validatingCells &&
|
||||||
|
prev.isLoadingLines === next.isLoadingLines &&
|
||||||
|
prev.isLoadingSublines === next.isLoadingSublines &&
|
||||||
|
// Options sources used for line/subline selects
|
||||||
|
prev.rowProductLines === next.rowProductLines &&
|
||||||
|
prev.rowSublines === next.rowSublines
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(ValidationTable, areEqual);
|
export default React.memo(ValidationTable, areEqual);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react'
|
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
@@ -18,24 +18,53 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
const [companyLinesCache, setCompanyLinesCache] = useState<Record<string, any[]>>({});
|
const [companyLinesCache, setCompanyLinesCache] = useState<Record<string, any[]>>({});
|
||||||
const [lineSublineCache, setLineSublineCache] = useState<Record<string, any[]>>({});
|
const [lineSublineCache, setLineSublineCache] = useState<Record<string, any[]>>({});
|
||||||
|
|
||||||
|
// Track in-flight requests to prevent duplicate fetches (especially in StrictMode/dev)
|
||||||
|
const pendingCompanyRequests = useRef<Set<string>>(new Set());
|
||||||
|
const pendingLineRequests = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
// Function to fetch product lines for a specific company - memoized
|
// Function to fetch product lines for a specific company - memoized
|
||||||
const fetchProductLines = useCallback(async (rowIndex: string | number, companyId: string) => {
|
const fetchProductLines = useCallback(async (rowIndex: string | number | undefined | null, companyId: string) => {
|
||||||
try {
|
try {
|
||||||
// Only fetch if we have a valid company ID
|
// Only fetch if we have a valid company ID
|
||||||
if (!companyId) return;
|
if (!companyId) return;
|
||||||
|
|
||||||
console.log(`Fetching product lines for row ${rowIndex}, company ${companyId}`);
|
const logRowKey = (rowIndex !== undefined && rowIndex !== null) ? rowIndex : 'all-matching-rows';
|
||||||
|
console.log(`Fetching product lines for row ${logRowKey}, company ${companyId}`);
|
||||||
|
|
||||||
// Check if we already have this company's lines in the cache
|
// Check if we already have this company's lines in the cache
|
||||||
if (companyLinesCache[companyId]) {
|
if (companyLinesCache[companyId]) {
|
||||||
console.log(`Using cached product lines for company ${companyId}`);
|
console.log(`Using cached product lines for company ${companyId}`);
|
||||||
// Use cached data
|
// Use cached data
|
||||||
setRowProductLines(prev => ({ ...prev, [rowIndex]: companyLinesCache[companyId] }));
|
const cached = companyLinesCache[companyId];
|
||||||
return companyLinesCache[companyId];
|
// Update the specific row if provided
|
||||||
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: cached }));
|
||||||
|
}
|
||||||
|
// Also update all rows that currently have this company set
|
||||||
|
const updates: Record<string, any[]> = {};
|
||||||
|
data.forEach((row, idx) => {
|
||||||
|
if (row.company && String(row.company) === String(companyId)) {
|
||||||
|
const key = (row.__index !== undefined && row.__index !== null) ? row.__index : idx;
|
||||||
|
updates[key] = cached;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
setRowProductLines(prev => ({ ...prev, ...updates }));
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a request for this company is already in flight, skip duplicate fetch
|
||||||
|
if (pendingCompanyRequests.current.has(companyId)) {
|
||||||
|
console.log(`Skipping fetch for company ${companyId} - request already pending`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingCompanyRequests.current.add(companyId);
|
||||||
|
|
||||||
// Set loading state for this row
|
// Set loading state for this row
|
||||||
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: true }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: true }));
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch product lines from API
|
// Fetch product lines from API
|
||||||
const productLinesUrl = `/api/import/product-lines/${companyId}`;
|
const productLinesUrl = `/api/import/product-lines/${companyId}`;
|
||||||
@@ -53,8 +82,22 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
// Store in company cache
|
// Store in company cache
|
||||||
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
|
||||||
|
|
||||||
// Store for this specific row
|
// Update specific row if provided
|
||||||
setRowProductLines(prev => ({ ...prev, [rowIndex]: formattedLines }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: formattedLines }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update all rows that currently have this company set
|
||||||
|
const updates: Record<string, any[]> = {};
|
||||||
|
data.forEach((row, idx) => {
|
||||||
|
if (row.company && String(row.company) === String(companyId)) {
|
||||||
|
const key = (row.__index !== undefined && row.__index !== null) ? row.__index : idx;
|
||||||
|
updates[key] = formattedLines;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
setRowProductLines(prev => ({ ...prev, ...updates }));
|
||||||
|
}
|
||||||
|
|
||||||
return formattedLines;
|
return formattedLines;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -65,33 +108,63 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
|
||||||
|
|
||||||
// Store empty array for this specific row
|
// Store empty array for this specific row
|
||||||
setRowProductLines(prev => ({ ...prev, [rowIndex]: [] }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: [] }));
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
} finally {
|
} finally {
|
||||||
|
// Clear pending flag
|
||||||
|
pendingCompanyRequests.current.delete(companyId);
|
||||||
// Clear loading state
|
// Clear loading state
|
||||||
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: false }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: false }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [companyLinesCache]);
|
}, [companyLinesCache, data]);
|
||||||
|
|
||||||
// Function to fetch sublines for a specific line - memoized
|
// Function to fetch sublines for a specific line - memoized
|
||||||
const fetchSublines = useCallback(async (rowIndex: string | number, lineId: string) => {
|
const fetchSublines = useCallback(async (rowIndex: string | number | undefined | null, lineId: string) => {
|
||||||
try {
|
try {
|
||||||
// Only fetch if we have a valid line ID
|
// Only fetch if we have a valid line ID
|
||||||
if (!lineId) return;
|
if (!lineId) return;
|
||||||
|
|
||||||
console.log(`Fetching sublines for row ${rowIndex}, line ${lineId}`);
|
const logRowKey = (rowIndex !== undefined && rowIndex !== null) ? rowIndex : 'all-matching-rows';
|
||||||
|
console.log(`Fetching sublines for row ${logRowKey}, line ${lineId}`);
|
||||||
|
|
||||||
// Check if we already have this line's sublines in the cache
|
// Check if we already have this line's sublines in the cache
|
||||||
if (lineSublineCache[lineId]) {
|
if (lineSublineCache[lineId]) {
|
||||||
console.log(`Using cached sublines for line ${lineId}`);
|
console.log(`Using cached sublines for line ${lineId}`);
|
||||||
// Use cached data
|
// Use cached data
|
||||||
setRowSublines(prev => ({ ...prev, [rowIndex]: lineSublineCache[lineId] }));
|
const cached = lineSublineCache[lineId];
|
||||||
return lineSublineCache[lineId];
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowSublines(prev => ({ ...prev, [rowIndex]: cached }));
|
||||||
|
}
|
||||||
|
// Also update all rows with this line
|
||||||
|
const updates: Record<string, any[]> = {};
|
||||||
|
data.forEach((row, idx) => {
|
||||||
|
if (row.line && String(row.line) === String(lineId)) {
|
||||||
|
const key = (row.__index !== undefined && row.__index !== null) ? row.__index : idx;
|
||||||
|
updates[key] = cached;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
setRowSublines(prev => ({ ...prev, ...updates }));
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a request for this line is already in flight, skip duplicate fetch
|
||||||
|
if (pendingLineRequests.current.has(lineId)) {
|
||||||
|
console.log(`Skipping fetch for line ${lineId} - request already pending`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingLineRequests.current.add(lineId);
|
||||||
|
|
||||||
// Set loading state for this row
|
// Set loading state for this row
|
||||||
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: true }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: true }));
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch sublines from API
|
// Fetch sublines from API
|
||||||
const sublinesUrl = `/api/import/sublines/${lineId}`;
|
const sublinesUrl = `/api/import/sublines/${lineId}`;
|
||||||
@@ -109,8 +182,22 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
// Store in line cache
|
// Store in line cache
|
||||||
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
|
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
|
||||||
|
|
||||||
// Store for this specific row
|
// Update specific row if provided
|
||||||
setRowSublines(prev => ({ ...prev, [rowIndex]: formattedSublines }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowSublines(prev => ({ ...prev, [rowIndex]: formattedSublines }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update all rows with this line
|
||||||
|
const updates: Record<string, any[]> = {};
|
||||||
|
data.forEach((row, idx) => {
|
||||||
|
if (row.line && String(row.line) === String(lineId)) {
|
||||||
|
const key = (row.__index !== undefined && row.__index !== null) ? row.__index : idx;
|
||||||
|
updates[key] = formattedSublines;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
setRowSublines(prev => ({ ...prev, ...updates }));
|
||||||
|
}
|
||||||
|
|
||||||
return formattedSublines;
|
return formattedSublines;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -120,14 +207,20 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
|
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
|
||||||
|
|
||||||
// Store empty array for this specific row
|
// Store empty array for this specific row
|
||||||
setRowSublines(prev => ({ ...prev, [rowIndex]: [] }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setRowSublines(prev => ({ ...prev, [rowIndex]: [] }));
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
} finally {
|
} finally {
|
||||||
|
// Clear pending flag
|
||||||
|
pendingLineRequests.current.delete(lineId);
|
||||||
// Clear loading state
|
// Clear loading state
|
||||||
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: false }));
|
if (rowIndex !== undefined && rowIndex !== null) {
|
||||||
|
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: false }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [lineSublineCache]);
|
}, [lineSublineCache, data]);
|
||||||
|
|
||||||
// When data changes, fetch product lines and sublines for rows that have company/line values
|
// When data changes, fetch product lines and sublines for rows that have company/line values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -198,6 +291,11 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
|
|
||||||
// Process companies that need product lines
|
// Process companies that need product lines
|
||||||
companiesNeeded.forEach((rowIds, companyId) => {
|
companiesNeeded.forEach((rowIds, companyId) => {
|
||||||
|
// If this company is already being fetched, skip creating another request
|
||||||
|
if (pendingCompanyRequests.current.has(companyId)) {
|
||||||
|
console.log(`Skipping batch fetch for company ${companyId} - request already pending`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Skip if already in cache
|
// Skip if already in cache
|
||||||
if (companyLinesCache[companyId]) {
|
if (companyLinesCache[companyId]) {
|
||||||
console.log(`Using cached product lines for company ${companyId}`);
|
console.log(`Using cached product lines for company ${companyId}`);
|
||||||
@@ -218,6 +316,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
|
|
||||||
// Create fetch promise
|
// Create fetch promise
|
||||||
const fetchPromise = (async () => {
|
const fetchPromise = (async () => {
|
||||||
|
// Mark this company as pending
|
||||||
|
pendingCompanyRequests.current.add(companyId);
|
||||||
// Safety timeout to ensure loading state is cleared after 10 seconds
|
// Safety timeout to ensure loading state is cleared after 10 seconds
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
console.log(`Safety timeout triggered for company ${companyId}`);
|
console.log(`Safety timeout triggered for company ${companyId}`);
|
||||||
@@ -253,13 +353,19 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
const productLines = response.data;
|
const productLines = response.data;
|
||||||
console.log(`Received ${productLines.length} product lines for company ${companyId}`);
|
console.log(`Received ${productLines.length} product lines for company ${companyId}`);
|
||||||
|
|
||||||
|
// Format the data for dropdown display (consistent with single-row fetch)
|
||||||
|
const formattedLines = productLines.map((line: any) => ({
|
||||||
|
label: line.name || line.label || String(line.value || line.id),
|
||||||
|
value: String(line.value || line.id)
|
||||||
|
}));
|
||||||
|
|
||||||
// Store in company cache
|
// Store in company cache
|
||||||
setCompanyLinesCache(prev => ({ ...prev, [companyId]: productLines }));
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
|
||||||
|
|
||||||
// Update all rows with this company
|
// Update all rows with this company
|
||||||
const updates: Record<string, any[]> = {};
|
const updates: Record<string, any[]> = {};
|
||||||
rowIds.forEach(rowId => {
|
rowIds.forEach(rowId => {
|
||||||
updates[rowId] = productLines;
|
updates[rowId] = formattedLines;
|
||||||
});
|
});
|
||||||
setRowProductLines(prev => ({ ...prev, ...updates }));
|
setRowProductLines(prev => ({ ...prev, ...updates }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -278,6 +384,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
// Show error toast
|
// Show error toast
|
||||||
toast.error(`Failed to load product lines for company ${companyId}`);
|
toast.error(`Failed to load product lines for company ${companyId}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
// Clear pending flag for company
|
||||||
|
pendingCompanyRequests.current.delete(companyId);
|
||||||
// Clear the safety timeout
|
// Clear the safety timeout
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
@@ -295,6 +403,11 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
|
|
||||||
// Process lines that need sublines
|
// Process lines that need sublines
|
||||||
linesNeeded.forEach((rowIds, lineId) => {
|
linesNeeded.forEach((rowIds, lineId) => {
|
||||||
|
// If this line is already being fetched, skip creating another request
|
||||||
|
if (pendingLineRequests.current.has(lineId)) {
|
||||||
|
console.log(`Skipping batch fetch for line ${lineId} - request already pending`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Skip if already in cache
|
// Skip if already in cache
|
||||||
if (lineSublineCache[lineId]) {
|
if (lineSublineCache[lineId]) {
|
||||||
console.log(`Using cached sublines for line ${lineId}`);
|
console.log(`Using cached sublines for line ${lineId}`);
|
||||||
@@ -315,6 +428,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
|
|
||||||
// Create fetch promise
|
// Create fetch promise
|
||||||
const fetchPromise = (async () => {
|
const fetchPromise = (async () => {
|
||||||
|
// Mark this line as pending
|
||||||
|
pendingLineRequests.current.add(lineId);
|
||||||
// Safety timeout to ensure loading state is cleared after 10 seconds
|
// Safety timeout to ensure loading state is cleared after 10 seconds
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
console.log(`Safety timeout triggered for line ${lineId}`);
|
console.log(`Safety timeout triggered for line ${lineId}`);
|
||||||
@@ -350,13 +465,19 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
const sublines = response.data;
|
const sublines = response.data;
|
||||||
console.log(`Received ${sublines.length} sublines for line ${lineId}`);
|
console.log(`Received ${sublines.length} sublines for line ${lineId}`);
|
||||||
|
|
||||||
|
// Format the data for dropdown display (consistent with single-row fetch)
|
||||||
|
const formattedSublines = sublines.map((subline: any) => ({
|
||||||
|
label: subline.name || subline.label || String(subline.value || subline.id),
|
||||||
|
value: String(subline.value || subline.id)
|
||||||
|
}));
|
||||||
|
|
||||||
// Store in line cache
|
// Store in line cache
|
||||||
setLineSublineCache(prev => ({ ...prev, [lineId]: sublines }));
|
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
|
||||||
|
|
||||||
// Update all rows with this line
|
// Update all rows with this line
|
||||||
const updates: Record<string, any[]> = {};
|
const updates: Record<string, any[]> = {};
|
||||||
rowIds.forEach(rowId => {
|
rowIds.forEach(rowId => {
|
||||||
updates[rowId] = sublines;
|
updates[rowId] = formattedSublines;
|
||||||
});
|
});
|
||||||
setRowSublines(prev => ({ ...prev, ...updates }));
|
setRowSublines(prev => ({ ...prev, ...updates }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -375,6 +496,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
// Show error toast
|
// Show error toast
|
||||||
toast.error(`Failed to load sublines for line ${lineId}`);
|
toast.error(`Failed to load sublines for line ${lineId}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
// Clear pending flag for line
|
||||||
|
pendingLineRequests.current.delete(lineId);
|
||||||
// Clear the safety timeout
|
// Clear the safety timeout
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
@@ -417,4 +540,4 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|||||||
fetchProductLines,
|
fetchProductLines,
|
||||||
fetchSublines
|
fetchSublines
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ export const useValidationState = <T extends string>({
|
|||||||
// Core data state
|
// Core data state
|
||||||
const [data, setData] = useState<RowData<T>[]>(() => {
|
const [data, setData] = useState<RowData<T>[]>(() => {
|
||||||
// Clean price fields in initial data before setting state
|
// Clean price fields in initial data before setting state
|
||||||
return initialData.map((row) => {
|
return initialData.map((row, index) => {
|
||||||
const updatedRow = { ...row } as Record<string, any>;
|
const updatedRow = { ...row } as Record<string, any>;
|
||||||
|
|
||||||
|
// Ensure each row has a stable __index key for downstream lookups
|
||||||
|
if (updatedRow.__index === undefined || updatedRow.__index === null || updatedRow.__index === '') {
|
||||||
|
updatedRow.__index = String(index);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean price fields using utility
|
// Clean price fields using utility
|
||||||
if (updatedRow.msrp !== undefined) {
|
if (updatedRow.msrp !== undefined) {
|
||||||
updatedRow.msrp = cleanPriceField(updatedRow.msrp);
|
updatedRow.msrp = cleanPriceField(updatedRow.msrp);
|
||||||
|
|||||||
Reference in New Issue
Block a user