Skip to content

Commit 5440520

Browse files
committed
feat(stats): add top occupations distribution to tree stats page
1 parent ae88be3 commit 5440520

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

client/src/components/stats/TreeStatsPage.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Star,
1414
Type,
1515
Clock,
16+
Briefcase,
1617
} from 'lucide-react';
1718
import type { TreeStats, DatabaseInfo } from '@fsf/shared';
1819
import { api } from '../../services/api';
@@ -345,6 +346,35 @@ export function TreeStatsPage() {
345346
);
346347
})()}
347348

349+
{/* Top Occupations */}
350+
{stats.occupations?.length > 0 && (() => {
351+
const maxOccCount = Math.max(...stats.occupations.map(o => o.count), 1);
352+
return (
353+
<div className="bg-app-card border border-app-border rounded-lg p-4 mb-4">
354+
<h2 className="text-sm font-semibold text-app-text mb-3 flex items-center gap-2">
355+
<Briefcase size={14} />
356+
Top Occupations
357+
</h2>
358+
<div className="space-y-1.5 max-h-64 overflow-y-auto">
359+
{stats.occupations.map(({ occupation, count }) => (
360+
<div key={occupation} className="flex items-center gap-2">
361+
<span className="text-xs text-app-text truncate w-32 text-right shrink-0" title={occupation}>
362+
{occupation}
363+
</span>
364+
<div className="flex-1 h-4 bg-app-border rounded overflow-hidden">
365+
<div
366+
className="h-full bg-amber-500/50 rounded transition-all"
367+
style={{ width: `${Math.max((count / maxOccCount) * 100, 1)}%` }}
368+
/>
369+
</div>
370+
<span className="text-xs text-app-text-muted w-10 text-right shrink-0">{count}</span>
371+
</div>
372+
))}
373+
</div>
374+
</div>
375+
);
376+
})()}
377+
348378
{/* Top Birth Countries */}
349379
{stats.birthCountries?.length > 0 && (() => {
350380
const maxCountryCount = Math.max(...stats.birthCountries.map(c => c.count), 1);

server/src/services/database.service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,7 @@ export const databaseService = {
10961096
};
10971097
birthPlaces: { place: string; count: number }[];
10981098
birthCountries: { country: string; count: number }[];
1099+
occupations: { occupation: string; count: number }[];
10991100
}> {
11001101
if (!useSqlite) {
11011102
throw new Error('SQLite is required for tree stats');
@@ -1302,6 +1303,18 @@ export const databaseService = {
13021303
.sort((a, b) => b.count - a.count)
13031304
.slice(0, 20);
13041305

1306+
// Top occupations from claims table
1307+
const occupationRows = sqliteService.queryAll<{ occupation: string; count: number }>(
1308+
`SELECT c.value_text as occupation, COUNT(DISTINCT c.person_id) as count
1309+
FROM claim c
1310+
JOIN database_membership dm ON c.person_id = dm.person_id AND dm.db_id = @dbId
1311+
WHERE c.predicate = 'occupation' AND c.value_text IS NOT NULL AND c.value_text != ''
1312+
GROUP BY c.value_text
1313+
ORDER BY count DESC
1314+
LIMIT 30`,
1315+
{ dbId }
1316+
);
1317+
13051318
const lifespanByCentury = sqliteService.queryAll<{ century: number; avgAge: number; count: number }>(
13061319
`SELECT CAST((b.date_year / 100) AS INTEGER) as century,
13071320
ROUND(AVG(d.date_year - b.date_year), 1) as avgAge,
@@ -1339,6 +1352,7 @@ export const databaseService = {
13391352
},
13401353
birthPlaces: birthPlaceRows.map(r => ({ place: r.place, count: r.count })),
13411354
birthCountries: birthCountryRows.map(r => ({ country: r.country, count: r.count })),
1355+
occupations: occupationRows.map(r => ({ occupation: r.occupation, count: r.count })),
13421356
};
13431357
},
13441358
};

shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ export interface TreeStats {
520520
};
521521
birthPlaces: { place: string; count: number }[];
522522
birthCountries: { country: string; count: number }[];
523+
occupations: { occupation: string; count: number }[];
523524
}
524525

525526
// Person with ID included

0 commit comments

Comments
 (0)