diff --git a/YodaDefault.png b/YodaDefault.png
new file mode 100644
index 0000000..4298f9f
Binary files /dev/null and b/YodaDefault.png differ
diff --git a/backend/assets/yoda.png b/backend/assets/yoda.png
index 00bc6c5..4298f9f 100644
Binary files a/backend/assets/yoda.png and b/backend/assets/yoda.png differ
diff --git a/backend/src/app.ts b/backend/src/app.ts
index 2516bcc..c3a948f 100644
--- a/backend/src/app.ts
+++ b/backend/src/app.ts
@@ -1914,9 +1914,11 @@ app.get('/api/admin/summary', requireAuth, requireSessionAuth, requireAdmin, asy
res.json({
summary: {
totalBirds: Number(summary?.total_birds ?? 0),
+ memorializedBirds: Number(summary?.memorialized_birds ?? 0),
totalUsers: Number(summary?.total_users ?? 0),
totalWorkspaces: Number(summary?.total_workspaces ?? 0),
rescueWorkspaces: Number(summary?.rescue_workspaces ?? 0),
+ rescueBirds: Number(summary?.rescue_birds ?? 0),
pendingRescues: Number(summary?.pending_rescues ?? 0),
dailyUsers: Number(summary?.daily_users ?? 0),
},
diff --git a/backend/src/repositories/workspaceRepository.test.ts b/backend/src/repositories/workspaceRepository.test.ts
index e9ae239..e2e0ce4 100644
--- a/backend/src/repositories/workspaceRepository.test.ts
+++ b/backend/src/repositories/workspaceRepository.test.ts
@@ -6,6 +6,7 @@ import {
deleteWorkspaceIfEmpty,
ensurePersonalWorkspaceForUser,
findAlternateWorkspaceForUser,
+ getPlatformAdminSummary,
listOwnedWorkspacesByOwnerEmail,
updateWorkspace,
} from './workspaceRepository.js';
@@ -179,3 +180,30 @@ test('listOwnedWorkspacesByOwnerEmail resolves accepted owner flocks by email',
assert.match(calls[0].text, /accepted_at IS NOT NULL/);
assert.match(calls[0].text, /workspaces\.id <> \$2/);
});
+
+test('getPlatformAdminSummary counts memorialized birds separately', async () => {
+ const { calls } = mockDb({
+ rowCount: 1,
+ rows: [
+ {
+ total_birds: 8,
+ memorialized_birds: 3,
+ total_users: 4,
+ total_workspaces: 2,
+ rescue_workspaces: 1,
+ rescue_birds: 5,
+ pending_rescues: 1,
+ daily_users: 2,
+ },
+ ],
+ });
+
+ const summary = await getPlatformAdminSummary();
+
+ assert.equal(summary?.memorialized_birds, 3);
+ assert.equal(calls.length, 1);
+ assert.match(calls[0].text, /memorialized_birds/);
+ assert.match(calls[0].text, /memorialized_at IS NOT NULL/);
+ assert.match(calls[0].text, /rescue_birds/);
+ assert.match(calls[0].text, /workspaces\.workspace_type = 'rescue'/);
+});
diff --git a/backend/src/repositories/workspaceRepository.ts b/backend/src/repositories/workspaceRepository.ts
index cd472b5..c06183a 100644
--- a/backend/src/repositories/workspaceRepository.ts
+++ b/backend/src/repositories/workspaceRepository.ts
@@ -549,17 +549,21 @@ export const setWorkspaceSubscriptionStatusByStripeSubscriptionId = async (
export const getPlatformAdminSummary = async () => {
const result = await db.query<{
total_birds: number;
+ memorialized_birds: number;
total_users: number;
total_workspaces: number;
rescue_workspaces: number;
+ rescue_birds: number;
pending_rescues: number;
daily_users: number;
}>(
`SELECT
(SELECT COUNT(*)::int FROM birds) AS total_birds,
+ (SELECT COUNT(*)::int FROM birds WHERE memorialized_at IS NOT NULL) AS memorialized_birds,
(SELECT COUNT(*)::int FROM users) AS total_users,
(SELECT COUNT(*)::int FROM workspaces) AS total_workspaces,
(SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue') AS rescue_workspaces,
+ (SELECT COUNT(*)::int FROM birds INNER JOIN workspaces ON workspaces.id = birds.workspace_id WHERE workspaces.workspace_type = 'rescue') AS rescue_birds,
(SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue' AND rescue_verification_status = 'pending') AS pending_rescues,
(SELECT COUNT(DISTINCT user_id)::int FROM auth_sessions WHERE created_at >= CURRENT_DATE) AS daily_users`,
);
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b9289c7..7a2184f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -140,9 +140,11 @@ type AuthSessionPayload = {
type AdminSummary = {
totalBirds: number;
+ memorializedBirds: number;
totalUsers: number;
totalWorkspaces: number;
rescueWorkspaces: number;
+ rescueBirds: number;
pendingRescues: number;
dailyUsers: number;
};
@@ -3945,6 +3947,10 @@ function App() {
Total birds
{adminSummary?.totalBirds ?? '-'}
+
+ Memorialized birds
+ {adminSummary?.memorializedBirds ?? '-'}
+
Daily users
{adminSummary?.dailyUsers ?? '-'}
@@ -3961,6 +3967,10 @@ function App() {
Rescue flocks
{adminSummary?.rescueWorkspaces ?? '-'}
+
+ Total rescue birds
+ {adminSummary?.rescueBirds ?? '-'}
+
Pending rescues
{adminSummary?.pendingRescues ?? '-'}
diff --git a/frontend/src/assets/yoda.png b/frontend/src/assets/yoda.png
index 00bc6c5..4298f9f 100644
Binary files a/frontend/src/assets/yoda.png and b/frontend/src/assets/yoda.png differ