Skip to content

Commit 889b720

Browse files
committed
Fix Use button not updating SparseTree row display
- Client: onFieldChanged now refetches person data after applying override - Server: getPerson() now applies local overrides to returned person data - Added applyLocalOverridesToPerson() to database.service.ts
1 parent 02a18a7 commit 889b720

3 files changed

Lines changed: 113 additions & 1 deletion

File tree

‎.changelog/v0.6.x.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ This release normalizes FamilySearch as a downstream provider (equal to Ancestry
6767
- Added detailed debug logging for photo extraction troubleshooting
6868
- Improved UI feedback: loading toast during scrape, success toast shows extracted fields
6969

70+
### "Use" Button Not Updating SparseTree Row (v0.6.x)
71+
- Fixed clicking "Use" on provider field values not updating the SparseTree row display
72+
- Root causes fixed:
73+
- Client: `onFieldChanged` callback now refetches person data after applying override
74+
- Server: `getPerson()` now applies local overrides to returned person data
75+
- Added `applyLocalOverridesToPerson()` to database service for consistent override application
76+
- Birth/death date/place values from providers now properly display after clicking "Use"
77+
7078
### "Set as Primary" Photo Not Updating (v0.6.x)
7179
- Fixed photo not updating after clicking "Set as Primary" on provider photos
7280
- Root causes fixed:

‎client/src/components/person/PersonDetail.tsx‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export function PersonDetail() {
190190
const [showUploadDialog, setShowUploadDialog] = useState(false);
191191
const [showAncestryUploadDialog, setShowAncestryUploadDialog] = useState(false);
192192
const [showRelationshipModal, setShowRelationshipModal] = useState(false);
193+
const [hintsProcessing, setHintsProcessing] = useState(false);
193194

194195
// Local overrides state
195196
const [overrides, setOverrides] = useState<PersonOverrides | null>(null);
@@ -495,6 +496,36 @@ export function PersonDetail() {
495496
setSyncLoading(false);
496497
};
497498

499+
const handleProcessAncestryHints = async () => {
500+
if (!dbId || !personId) return;
501+
502+
setHintsProcessing(true);
503+
504+
const result = await api.processAncestryHints(dbId, personId).catch(err => {
505+
toast.error(`Failed to process hints: ${err.message}`);
506+
return null;
507+
});
508+
509+
if (result) {
510+
if (result.hintsProcessed > 0) {
511+
toast.success(`Processed ${result.hintsProcessed} free hints on Ancestry`);
512+
// Refresh data from Ancestry after processing hints
513+
await handleRefreshProvider('ancestry');
514+
} else if (result.hintsFound === 0) {
515+
toast('No free hints available', { icon: 'ℹ️' });
516+
} else if (result.errors.length > 0) {
517+
toast.error(`Hints processing failed: ${result.errors[0]}`);
518+
}
519+
}
520+
521+
setHintsProcessing(false);
522+
};
523+
524+
const handleRefreshProvider = async (provider: 'ancestry' | 'wikitree' | 'familysearch') => {
525+
if (!dbId || !personId) return;
526+
await api.refreshFromProvider(dbId, personId, provider).catch(() => null);
527+
};
528+
498529
// =============================================================================
499530
// LOCAL OVERRIDE HANDLERS
500531
// =============================================================================
@@ -596,6 +627,15 @@ export function PersonDetail() {
596627
toast.success('Deleted');
597628
}, [dbId, personId, refreshOverrides]);
598629

630+
// Called when a field value is applied from provider data (e.g., "Use" button)
631+
const handleFieldApplied = useCallback(async () => {
632+
await refreshOverrides();
633+
// Refresh person data to show the new value in the SparseTree row
634+
if (!dbId || !personId) return;
635+
const newPerson = await api.getPerson(dbId, personId);
636+
if (newPerson) setPerson(newPerson);
637+
}, [dbId, personId, refreshOverrides]);
638+
599639
// =============================================================================
600640
// HELPER FUNCTIONS FOR OVERRIDES
601641
// =============================================================================
@@ -1159,10 +1199,12 @@ export function PersonDetail() {
11591199
onShowAncestryUploadDialog={() => setShowAncestryUploadDialog(true)}
11601200
onShowLinkInput={(platform) => setLinkingPlatform(platform)}
11611201
onPhotoChanged={refreshPhotoState}
1162-
onFieldChanged={refreshOverrides}
1202+
onFieldChanged={handleFieldApplied}
1203+
onProcessAncestryHints={handleProcessAncestryHints}
11631204
syncLoading={syncLoading}
11641205
scrapeLoading={scrapeLoading}
11651206
fetchingPhotoFrom={fetchingPhotoFrom}
1207+
hintsProcessing={hintsProcessing}
11661208
/>
11671209

11681210
{/* Lineage path - compact */}
@@ -1198,6 +1240,11 @@ export function PersonDetail() {
11981240
dbId={dbId}
11991241
personId={personId}
12001242
onClose={() => setShowUploadDialog(false)}
1243+
onPhotoSynced={() => {
1244+
// Photo was uploaded and synced to local cache - update UI without re-downloading
1245+
setHasFsPhoto(true);
1246+
setPhotoVersion(Date.now()); // Bust browser cache to show the photo
1247+
}}
12011248
/>
12021249
)}
12031250

‎server/src/services/database.service.ts‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Database, DatabaseInfo, Person, PersonWithId } from '@fsf/shared';
44
import { sqliteService } from '../db/sqlite.service.js';
55
import { idMappingService } from './id-mapping.service.js';
66
import { scraperService } from './scraper.service.js';
7+
import { localOverrideService } from './local-override.service.js';
78

89
// Data directory is at root of project, not in server/
910
const DATA_DIR = path.resolve(import.meta.dirname, '../../../data');
@@ -147,6 +148,59 @@ function findDatabasePath(id: string): string | null {
147148
return null;
148149
}
149150

151+
/**
152+
* Apply local overrides to a Person object
153+
* Modifies the person in place to reflect user overrides
154+
*/
155+
function applyLocalOverridesToPerson(person: Person, personId: string): void {
156+
// Get person-level overrides (name, gender)
157+
const personOverrides = localOverrideService.getOverridesForEntity('person', personId);
158+
for (const override of personOverrides) {
159+
if (override.fieldName === 'name' && override.overrideValue) {
160+
person.name = override.overrideValue;
161+
} else if (override.fieldName === 'gender' && override.overrideValue) {
162+
person.gender = override.overrideValue as 'male' | 'female' | 'unknown';
163+
}
164+
}
165+
166+
// Get vital event IDs for this person and check for overrides
167+
const vitalEventIds = sqliteService.queryAll<{ id: number; event_type: string }>(
168+
`SELECT id, event_type FROM vital_event WHERE person_id = @personId`,
169+
{ personId }
170+
);
171+
172+
for (const event of vitalEventIds) {
173+
const eventOverrides = localOverrideService.getOverridesForEntity('vital_event', String(event.id));
174+
for (const override of eventOverrides) {
175+
if (event.event_type === 'birth') {
176+
if (!person.birth) person.birth = {};
177+
if (override.fieldName === 'date' && override.overrideValue) {
178+
person.birth.date = override.overrideValue;
179+
} else if (override.fieldName === 'place' && override.overrideValue) {
180+
person.birth.place = override.overrideValue;
181+
}
182+
} else if (event.event_type === 'death') {
183+
if (!person.death) person.death = {};
184+
if (override.fieldName === 'date' && override.overrideValue) {
185+
person.death.date = override.overrideValue;
186+
} else if (override.fieldName === 'place' && override.overrideValue) {
187+
person.death.place = override.overrideValue;
188+
}
189+
}
190+
}
191+
}
192+
193+
// Also update the computed fields
194+
if (person.birth?.date || person.death?.date) {
195+
const birthYear = person.birth?.date?.match(/\d{4}/)?.at(0) ?? '';
196+
const deathYear = person.death?.date?.match(/\d{4}/)?.at(0) ?? '';
197+
person.lifespan = birthYear || deathYear ? `${birthYear}-${deathYear}` : '';
198+
}
199+
if (person.birth?.place || person.death?.place) {
200+
person.location = person.birth?.place ?? person.death?.place ?? undefined;
201+
}
202+
}
203+
150204
// Build a Person object from SQLite data
151205
function buildPersonFromSqlite(
152206
personId: string,
@@ -278,6 +332,9 @@ function buildPersonFromSqlite(
278332
person.canonicalId = row.person_id;
279333
}
280334

335+
// Apply local overrides (user-set values take precedence)
336+
applyLocalOverridesToPerson(person, personId);
337+
281338
return person;
282339
}
283340

0 commit comments

Comments
 (0)