Compare commits

..

3 commits

Author SHA1 Message Date
cef242e173 fix(property addition): resolve property addition and persistence issues in ItemsList
- Implement immediate saving of auto-populated values from propertyCache when adding properties
- Fix timing issues by ensuring all database operations complete before query invalidation
- Add comprehensive error handling with optimistic update rollback on failures
- Improve property addition sequence: optimistic updates → cache auto-population → database saves → Wikidata fetching → query refresh
- Add extensive debugging logs for property addition workflow
- Eliminate race conditions between state updates and data refresh

Fixes issues where:
- Properties would disappear after selection due to premature query invalidation
- Auto-populated values were lost during data refresh cycles
- Multiple clicks were required for property selection to persist
- UI state became inconsistent with database state during property operations

The property addition process now follows a proper async sequence ensuring data persistence and UI consistency.
2025-07-16 17:25:02 +03:00
f380397f71 fix(add property): edit ItemsList component to save auto-populated items immediately and invalidate queries after all operations are complete 2025-07-16 17:15:04 +03:00
90abad12b9 fix(PropertyInput): improve blur handling and suggestion click event handling 2025-07-16 15:27:57 +03:00
2 changed files with 105 additions and 51 deletions

View file

@ -44,17 +44,7 @@ async function loadItemsFromDb(url: string): Promise<ItemsResponse> {
const selectedProperties = await propertiesResponse.json();
console.log('[DEBUG] Successfully received selected properties');
// 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 };
return { items, selectedProperties };
}
async function saveItemToDb(url: string, item: Item): Promise<void> {
@ -461,8 +451,10 @@ export function ItemsList({ url }: ItemsListProps) {
}
}
// THIRD: Add to all items with auto-population from propertyCache
setItems(prev => prev.map(item => {
// THIRD: Add to all items with auto-population from propertyCache AND save immediately
const itemsToSave: Item[] = [];
const updatedItems = items.map(item => {
const existingValue = item.customProperties[normalizedProperty] || '';
let autoPopulatedValue = existingValue;
@ -472,25 +464,56 @@ export function ItemsList({ url }: ItemsListProps) {
console.log(`Auto-populating ${normalizedProperty} for ${item.wikidataId} with value:`, autoPopulatedValue);
}
return {
const updatedItem = {
...item,
customProperties: {
...item.customProperties,
[normalizedProperty]: autoPopulatedValue
}
};
}));
// FOURTH: Save to database and refresh data
// If we auto-populated a value, save it immediately
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 {
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);
console.log('Property successfully saved to database:', normalizedProperty);
// Invalidate queries to refresh data from database
queryClient.invalidateQueries({ queryKey: ['items', url] });
} catch (error) {
console.error('Error saving property to database:', error);
console.error('Error in property addition process:', error);
// Revert optimistic updates on error
setSelectedProperties(prev => {
const newSelected = { ...prev };
@ -510,6 +533,9 @@ export function ItemsList({ url }: ItemsListProps) {
// 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]);
// Create array to store all save promises
const savePromises: Promise<void>[] = [];
for (const item of itemsWithWikidataId) {
if (item.wikidataId) {
try {
@ -534,20 +560,22 @@ export function ItemsList({ url }: ItemsListProps) {
: prevItem
));
// Save the updated item to database to persist the auto-populated value
const updatedItem = items.find(i => i.wikidataId === item.wikidataId);
if (updatedItem) {
const itemToSave = {
...updatedItem,
customProperties: {
...updatedItem.customProperties,
[normalizedProperty]: properties[normalizedProperty]
}
};
saveItemToDb(url, itemToSave).catch(error => {
console.error('Error saving auto-populated value:', error);
});
}
// Get current items state and save the updated item to database
setItems(currentItems => {
const updatedItem = currentItems.find(i => i.wikidataId === item.wikidataId);
if (updatedItem) {
const itemToSave = {
...updatedItem,
customProperties: {
...updatedItem.customProperties,
[normalizedProperty]: properties[normalizedProperty]
}
};
// Add save promise to array instead of awaiting immediately
savePromises.push(saveItemToDb(url, itemToSave));
}
return currentItems; // Return unchanged state since we already updated it above
});
}
} catch (error) {
console.error(`Error fetching Wikidata properties for ${item.wikidataId}:`, error);
@ -555,8 +583,21 @@ export function ItemsList({ url }: ItemsListProps) {
}
}
console.log('Property addition completed:', normalizedProperty);
}, [customProperties, propertyLabels, items, propertyCache, url]);
// WAIT for all saves to complete before invalidating queries
if (savePromises.length > 0) {
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) => {
console.log('Wikidata selection for item:', itemId, suggestion);

View file

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