Compare commits

..

No commits in common. "cef242e173d3ba3c70c0ab18d2fd8874f02c49ac" and "e42eec5dc1f81d166fc50a911b6cbd6d629a45e3" have entirely different histories.

2 changed files with 51 additions and 105 deletions

View file

@ -44,7 +44,17 @@ async function loadItemsFromDb(url: string): Promise<ItemsResponse> {
const selectedProperties = await propertiesResponse.json(); const selectedProperties = await propertiesResponse.json();
console.log('[DEBUG] Successfully received selected properties'); console.log('[DEBUG] Successfully received selected properties');
return { items, selectedProperties }; // Filter items to only include selected properties
const filteredItems = items.map((item: Item) => ({
...item,
customProperties: Object.fromEntries(
Object.entries(item.customProperties).filter(([key]) =>
selectedProperties.includes(key)
)
)
}));
return { items: filteredItems, selectedProperties };
} }
async function saveItemToDb(url: string, item: Item): Promise<void> { async function saveItemToDb(url: string, item: Item): Promise<void> {
@ -434,13 +444,13 @@ export function ItemsList({ url }: ItemsListProps) {
console.log('Property already visible in UI:', normalizedProperty); console.log('Property already visible in UI:', normalizedProperty);
return; return;
} }
console.log('Proceeding to add property:', normalizedProperty); console.log('Proceeding to add property:', normalizedProperty);
// FIRST: Add to selected properties and custom properties (optimistic update) // FIRST: Add to selected properties and custom properties (optimistic update)
setSelectedProperties(prev => ({ ...prev, [normalizedProperty]: true })); setSelectedProperties(prev => ({ ...prev, [normalizedProperty]: true }));
setCustomProperties(prev => [...prev, normalizedProperty]); setCustomProperties(prev => [...prev, normalizedProperty]);
// SECOND: Fetch label if not exists // SECOND: Fetch label if not exists
if (!propertyLabels[normalizedProperty]) { if (!propertyLabels[normalizedProperty]) {
try { try {
@ -450,70 +460,37 @@ export function ItemsList({ url }: ItemsListProps) {
console.error('Error fetching property labels:', error); console.error('Error fetching property labels:', error);
} }
} }
// THIRD: Add to all items with auto-population from propertyCache AND save immediately // THIRD: Add to all items with auto-population from propertyCache
const itemsToSave: Item[] = []; setItems(prev => prev.map(item => {
const updatedItems = items.map(item => {
const existingValue = item.customProperties[normalizedProperty] || ''; const existingValue = item.customProperties[normalizedProperty] || '';
let autoPopulatedValue = existingValue; let autoPopulatedValue = existingValue;
// If item has Wikidata ID and we have cached properties, use the cached value // If item has Wikidata ID and we have cached properties, use the cached value
if (item.wikidataId && propertyCache[item.wikidataId] && propertyCache[item.wikidataId][normalizedProperty]) { if (item.wikidataId && propertyCache[item.wikidataId] && propertyCache[item.wikidataId][normalizedProperty]) {
autoPopulatedValue = propertyCache[item.wikidataId][normalizedProperty]; autoPopulatedValue = propertyCache[item.wikidataId][normalizedProperty];
console.log(`Auto-populating ${normalizedProperty} for ${item.wikidataId} with value:`, autoPopulatedValue); console.log(`Auto-populating ${normalizedProperty} for ${item.wikidataId} with value:`, autoPopulatedValue);
} }
const updatedItem = { return {
...item, ...item,
customProperties: { customProperties: {
...item.customProperties, ...item.customProperties,
[normalizedProperty]: autoPopulatedValue [normalizedProperty]: autoPopulatedValue
} }
}; };
}));
// If we auto-populated a value, save it immediately // FOURTH: Save to database and refresh data
if (autoPopulatedValue && autoPopulatedValue !== existingValue) {
console.log(`Adding item to save queue: ${item.id} with ${normalizedProperty}=${autoPopulatedValue}`);
itemsToSave.push(updatedItem);
}
return updatedItem;
});
// Update state with all items at once
setItems(updatedItems);
// Save all auto-populated items immediately
console.log('Items to save from cache population:', itemsToSave.length);
if (itemsToSave.length > 0) {
console.log('Saving auto-populated items:', itemsToSave.map(item => ({ id: item.id, name: item.name })));
}
const cachePopulationPromises = itemsToSave.map(item => {
console.log(`Saving item ${item.id} with auto-populated value`);
return saveItemToDb(url, item);
});
console.log('Items to save from cache population:', itemsToSave.length);
if (itemsToSave.length > 0) {
console.log('Items being saved:', itemsToSave.map(item => ({ id: item.id, name: item.name, properties: item.customProperties })));
}
// FOURTH: Save property to database
try { try {
if (cachePopulationPromises.length > 0) {
console.log(`Waiting for ${cachePopulationPromises.length} auto-populated items to save...`);
await Promise.all(cachePopulationPromises);
console.log('All cache-populated items saved successfully');
} else {
console.log('No auto-populated items to save');
}
await addPropertyToDb(url, normalizedProperty); await addPropertyToDb(url, normalizedProperty);
console.log('Property successfully saved to database:', normalizedProperty); console.log('Property successfully saved to database:', normalizedProperty);
// Invalidate queries to refresh data from database
queryClient.invalidateQueries({ queryKey: ['items', url] });
} catch (error) { } catch (error) {
console.error('Error in property addition process:', error); console.error('Error saving property to database:', error);
// Revert optimistic updates on error // Revert optimistic updates on error
setSelectedProperties(prev => { setSelectedProperties(prev => {
const newSelected = { ...prev }; const newSelected = { ...prev };
@ -529,13 +506,10 @@ export function ItemsList({ url }: ItemsListProps) {
}))); })));
return; return;
} }
// FIFTH: Fetch Wikidata properties for items with wikidata_id that don't have cached data // FIFTH: Fetch Wikidata properties for items with wikidata_id that don't have cached data
const itemsWithWikidataId = items.filter(item => item.wikidataId && !propertyCache[item.wikidataId]); const itemsWithWikidataId = items.filter(item => item.wikidataId && !propertyCache[item.wikidataId]);
// Create array to store all save promises
const savePromises: Promise<void>[] = [];
for (const item of itemsWithWikidataId) { for (const item of itemsWithWikidataId) {
if (item.wikidataId) { if (item.wikidataId) {
try { try {
@ -560,22 +534,20 @@ export function ItemsList({ url }: ItemsListProps) {
: prevItem : prevItem
)); ));
// Get current items state and save the updated item to database // Save the updated item to database to persist the auto-populated value
setItems(currentItems => { const updatedItem = items.find(i => i.wikidataId === item.wikidataId);
const updatedItem = currentItems.find(i => i.wikidataId === item.wikidataId); if (updatedItem) {
if (updatedItem) { const itemToSave = {
const itemToSave = { ...updatedItem,
...updatedItem, customProperties: {
customProperties: { ...updatedItem.customProperties,
...updatedItem.customProperties, [normalizedProperty]: properties[normalizedProperty]
[normalizedProperty]: properties[normalizedProperty] }
} };
}; saveItemToDb(url, itemToSave).catch(error => {
// Add save promise to array instead of awaiting immediately console.error('Error saving auto-populated value:', error);
savePromises.push(saveItemToDb(url, itemToSave)); });
} }
return currentItems; // Return unchanged state since we already updated it above
});
} }
} catch (error) { } catch (error) {
console.error(`Error fetching Wikidata properties for ${item.wikidataId}:`, error); console.error(`Error fetching Wikidata properties for ${item.wikidataId}:`, error);
@ -583,21 +555,8 @@ export function ItemsList({ url }: ItemsListProps) {
} }
} }
// WAIT for all saves to complete before invalidating queries console.log('Property addition completed:', normalizedProperty);
if (savePromises.length > 0) { }, [customProperties, propertyLabels, items, propertyCache, url]);
try {
await Promise.all(savePromises);
console.log('All auto-populated items saved successfully');
} catch (error) {
console.error('Error saving some auto-populated items:', error);
}
}
// SIXTH: Only invalidate queries after ALL operations are complete
console.log('Property addition completed, refreshing data:', normalizedProperty);
queryClient.invalidateQueries({ queryKey: ['items', url] });
}, [customProperties, propertyLabels, items, propertyCache, url, queryClient, saveItemToDb]);
const handleWikidataSelect = useCallback(async (suggestion: WikidataSuggestion, itemId: string) => { const handleWikidataSelect = useCallback(async (suggestion: WikidataSuggestion, itemId: string) => {
console.log('Wikidata selection for item:', itemId, suggestion); console.log('Wikidata selection for item:', itemId, suggestion);

View file

@ -165,31 +165,18 @@ export function PropertyInput({
// Handle blur // Handle blur
const handleBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => { const handleBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
// Delay and more robust checking // Delay hiding suggestions to allow for clicks on suggestions
setTimeout(() => { setTimeout(() => {
// Check if the active element is within our suggestions dropdown const relatedTarget = e.relatedTarget as HTMLElement;
const activeElement = document.activeElement; if (!suggestionsRef.current?.contains(relatedTarget)) {
const isClickingOnSuggestion = suggestionsRef.current?.contains(activeElement) ||
suggestionsRef.current?.contains(e.relatedTarget as HTMLElement);
if (!isClickingOnSuggestion) {
setShowSuggestions(false); setShowSuggestions(false);
setSelectedIndex(-1); setSelectedIndex(-1);
} }
}, 200); }, 150);
}, []); }, []);
// Handle suggestion click // Handle suggestion click
const handleSuggestionClick = useCallback((property: PropertySuggestion, event: React.MouseEvent) => { const handleSuggestionClick = useCallback((property: PropertySuggestion) => {
// Prevent the blur event from interfering
event.preventDefault();
event.stopPropagation();
// Immediately hide suggestions and process selection
setShowSuggestions(false);
setSelectedIndex(-1);
// Process the selection
handlePropertySelect(property); handlePropertySelect(property);
}, [handlePropertySelect]); }, [handlePropertySelect]);
@ -259,7 +246,7 @@ export function PropertyInput({
: 'hover:bg-gray-50 border-l-4 border-transparent' : 'hover:bg-gray-50 border-l-4 border-transparent'
} }
`} `}
onClick={(event) => handleSuggestionClick(suggestion, event)} onClick={() => handleSuggestionClick(suggestion)}
onMouseEnter={() => setSelectedIndex(index)} onMouseEnter={() => setSelectedIndex(index)}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">