|
|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
import { useState, useCallback, useEffect } from 'react'
|
|
|
|
|
import { useState, useCallback, useEffect, useRef } from 'react'
|
|
|
|
|
import axios from 'axios'
|
|
|
|
|
import { toast } from 'sonner'
|
|
|
|
|
|
|
|
|
|
@@ -18,24 +18,53 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
const [companyLinesCache, setCompanyLinesCache] = 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
|
|
|
|
|
const fetchProductLines = useCallback(async (rowIndex: string | number, companyId: string) => {
|
|
|
|
|
const fetchProductLines = useCallback(async (rowIndex: string | number | undefined | null, companyId: string) => {
|
|
|
|
|
try {
|
|
|
|
|
// Only fetch if we have a valid company ID
|
|
|
|
|
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
|
|
|
|
|
if (companyLinesCache[companyId]) {
|
|
|
|
|
console.log(`Using cached product lines for company ${companyId}`);
|
|
|
|
|
// Use cached data
|
|
|
|
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: companyLinesCache[companyId] }));
|
|
|
|
|
return companyLinesCache[companyId];
|
|
|
|
|
const cached = 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
|
|
|
|
|
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: true }));
|
|
|
|
|
if (rowIndex !== undefined && rowIndex !== null) {
|
|
|
|
|
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: true }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch product lines from API
|
|
|
|
|
const productLinesUrl = `/api/import/product-lines/${companyId}`;
|
|
|
|
|
@@ -53,8 +82,22 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
// Store in company cache
|
|
|
|
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
|
|
|
|
|
|
|
|
|
|
// Store for this specific row
|
|
|
|
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: formattedLines }));
|
|
|
|
|
// Update specific row if provided
|
|
|
|
|
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;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@@ -65,33 +108,63 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
|
|
|
|
|
|
|
|
|
|
// Store empty array for this specific row
|
|
|
|
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: [] }));
|
|
|
|
|
if (rowIndex !== undefined && rowIndex !== null) {
|
|
|
|
|
setRowProductLines(prev => ({ ...prev, [rowIndex]: [] }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
} finally {
|
|
|
|
|
// Clear pending flag
|
|
|
|
|
pendingCompanyRequests.current.delete(companyId);
|
|
|
|
|
// 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
|
|
|
|
|
const fetchSublines = useCallback(async (rowIndex: string | number, lineId: string) => {
|
|
|
|
|
const fetchSublines = useCallback(async (rowIndex: string | number | undefined | null, lineId: string) => {
|
|
|
|
|
try {
|
|
|
|
|
// Only fetch if we have a valid line ID
|
|
|
|
|
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
|
|
|
|
|
if (lineSublineCache[lineId]) {
|
|
|
|
|
console.log(`Using cached sublines for line ${lineId}`);
|
|
|
|
|
// Use cached data
|
|
|
|
|
setRowSublines(prev => ({ ...prev, [rowIndex]: lineSublineCache[lineId] }));
|
|
|
|
|
return lineSublineCache[lineId];
|
|
|
|
|
const cached = 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
|
|
|
|
|
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: true }));
|
|
|
|
|
if (rowIndex !== undefined && rowIndex !== null) {
|
|
|
|
|
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: true }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch sublines from API
|
|
|
|
|
const sublinesUrl = `/api/import/sublines/${lineId}`;
|
|
|
|
|
@@ -109,8 +182,22 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
// Store in line cache
|
|
|
|
|
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
|
|
|
|
|
|
|
|
|
|
// Store for this specific row
|
|
|
|
|
setRowSublines(prev => ({ ...prev, [rowIndex]: formattedSublines }));
|
|
|
|
|
// Update specific row if provided
|
|
|
|
|
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;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@@ -120,14 +207,20 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
|
|
|
|
|
|
|
|
|
|
// Store empty array for this specific row
|
|
|
|
|
setRowSublines(prev => ({ ...prev, [rowIndex]: [] }));
|
|
|
|
|
if (rowIndex !== undefined && rowIndex !== null) {
|
|
|
|
|
setRowSublines(prev => ({ ...prev, [rowIndex]: [] }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
} finally {
|
|
|
|
|
// Clear pending flag
|
|
|
|
|
pendingLineRequests.current.delete(lineId);
|
|
|
|
|
// 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
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
@@ -198,6 +291,11 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
|
|
|
|
|
// Process companies that need product lines
|
|
|
|
|
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
|
|
|
|
|
if (companyLinesCache[companyId]) {
|
|
|
|
|
console.log(`Using cached product lines for company ${companyId}`);
|
|
|
|
|
@@ -218,6 +316,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
|
|
|
|
|
// Create fetch promise
|
|
|
|
|
const fetchPromise = (async () => {
|
|
|
|
|
// Mark this company as pending
|
|
|
|
|
pendingCompanyRequests.current.add(companyId);
|
|
|
|
|
// Safety timeout to ensure loading state is cleared after 10 seconds
|
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
|
|
|
console.log(`Safety timeout triggered for company ${companyId}`);
|
|
|
|
|
@@ -253,13 +353,19 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
const productLines = response.data;
|
|
|
|
|
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
|
|
|
|
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: productLines }));
|
|
|
|
|
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
|
|
|
|
|
|
|
|
|
|
// Update all rows with this company
|
|
|
|
|
const updates: Record<string, any[]> = {};
|
|
|
|
|
rowIds.forEach(rowId => {
|
|
|
|
|
updates[rowId] = productLines;
|
|
|
|
|
updates[rowId] = formattedLines;
|
|
|
|
|
});
|
|
|
|
|
setRowProductLines(prev => ({ ...prev, ...updates }));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@@ -278,6 +384,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
// Show error toast
|
|
|
|
|
toast.error(`Failed to load product lines for company ${companyId}`);
|
|
|
|
|
} finally {
|
|
|
|
|
// Clear pending flag for company
|
|
|
|
|
pendingCompanyRequests.current.delete(companyId);
|
|
|
|
|
// Clear the safety timeout
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
|
|
|
|
|
@@ -295,6 +403,11 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
|
|
|
|
|
// Process lines that need sublines
|
|
|
|
|
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
|
|
|
|
|
if (lineSublineCache[lineId]) {
|
|
|
|
|
console.log(`Using cached sublines for line ${lineId}`);
|
|
|
|
|
@@ -315,6 +428,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
|
|
|
|
|
// Create fetch promise
|
|
|
|
|
const fetchPromise = (async () => {
|
|
|
|
|
// Mark this line as pending
|
|
|
|
|
pendingLineRequests.current.add(lineId);
|
|
|
|
|
// Safety timeout to ensure loading state is cleared after 10 seconds
|
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
|
|
|
console.log(`Safety timeout triggered for line ${lineId}`);
|
|
|
|
|
@@ -350,13 +465,19 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
const sublines = response.data;
|
|
|
|
|
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
|
|
|
|
|
setLineSublineCache(prev => ({ ...prev, [lineId]: sublines }));
|
|
|
|
|
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
|
|
|
|
|
|
|
|
|
|
// Update all rows with this line
|
|
|
|
|
const updates: Record<string, any[]> = {};
|
|
|
|
|
rowIds.forEach(rowId => {
|
|
|
|
|
updates[rowId] = sublines;
|
|
|
|
|
updates[rowId] = formattedSublines;
|
|
|
|
|
});
|
|
|
|
|
setRowSublines(prev => ({ ...prev, ...updates }));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@@ -375,6 +496,8 @@ export const useProductLinesFetching = (data: Record<string, any>[]) => {
|
|
|
|
|
// Show error toast
|
|
|
|
|
toast.error(`Failed to load sublines for line ${lineId}`);
|
|
|
|
|
} finally {
|
|
|
|
|
// Clear pending flag for line
|
|
|
|
|
pendingLineRequests.current.delete(lineId);
|
|
|
|
|
// Clear the safety timeout
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
|
|
|
|
|
|