Updated default image and added additional admin cards
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.7 MiB |
@@ -1914,9 +1914,11 @@ app.get('/api/admin/summary', requireAuth, requireSessionAuth, requireAdmin, asy
|
|||||||
res.json({
|
res.json({
|
||||||
summary: {
|
summary: {
|
||||||
totalBirds: Number(summary?.total_birds ?? 0),
|
totalBirds: Number(summary?.total_birds ?? 0),
|
||||||
|
memorializedBirds: Number(summary?.memorialized_birds ?? 0),
|
||||||
totalUsers: Number(summary?.total_users ?? 0),
|
totalUsers: Number(summary?.total_users ?? 0),
|
||||||
totalWorkspaces: Number(summary?.total_workspaces ?? 0),
|
totalWorkspaces: Number(summary?.total_workspaces ?? 0),
|
||||||
rescueWorkspaces: Number(summary?.rescue_workspaces ?? 0),
|
rescueWorkspaces: Number(summary?.rescue_workspaces ?? 0),
|
||||||
|
rescueBirds: Number(summary?.rescue_birds ?? 0),
|
||||||
pendingRescues: Number(summary?.pending_rescues ?? 0),
|
pendingRescues: Number(summary?.pending_rescues ?? 0),
|
||||||
dailyUsers: Number(summary?.daily_users ?? 0),
|
dailyUsers: Number(summary?.daily_users ?? 0),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
deleteWorkspaceIfEmpty,
|
deleteWorkspaceIfEmpty,
|
||||||
ensurePersonalWorkspaceForUser,
|
ensurePersonalWorkspaceForUser,
|
||||||
findAlternateWorkspaceForUser,
|
findAlternateWorkspaceForUser,
|
||||||
|
getPlatformAdminSummary,
|
||||||
listOwnedWorkspacesByOwnerEmail,
|
listOwnedWorkspacesByOwnerEmail,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
} from './workspaceRepository.js';
|
} 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, /accepted_at IS NOT NULL/);
|
||||||
assert.match(calls[0].text, /workspaces\.id <> \$2/);
|
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'/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -549,17 +549,21 @@ export const setWorkspaceSubscriptionStatusByStripeSubscriptionId = async (
|
|||||||
export const getPlatformAdminSummary = async () => {
|
export const getPlatformAdminSummary = async () => {
|
||||||
const result = await db.query<{
|
const result = await db.query<{
|
||||||
total_birds: number;
|
total_birds: number;
|
||||||
|
memorialized_birds: number;
|
||||||
total_users: number;
|
total_users: number;
|
||||||
total_workspaces: number;
|
total_workspaces: number;
|
||||||
rescue_workspaces: number;
|
rescue_workspaces: number;
|
||||||
|
rescue_birds: number;
|
||||||
pending_rescues: number;
|
pending_rescues: number;
|
||||||
daily_users: number;
|
daily_users: number;
|
||||||
}>(
|
}>(
|
||||||
`SELECT
|
`SELECT
|
||||||
(SELECT COUNT(*)::int FROM birds) AS total_birds,
|
(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 users) AS total_users,
|
||||||
(SELECT COUNT(*)::int FROM workspaces) AS total_workspaces,
|
(SELECT COUNT(*)::int FROM workspaces) AS total_workspaces,
|
||||||
(SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue') AS rescue_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(*)::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`,
|
(SELECT COUNT(DISTINCT user_id)::int FROM auth_sessions WHERE created_at >= CURRENT_DATE) AS daily_users`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -140,9 +140,11 @@ type AuthSessionPayload = {
|
|||||||
|
|
||||||
type AdminSummary = {
|
type AdminSummary = {
|
||||||
totalBirds: number;
|
totalBirds: number;
|
||||||
|
memorializedBirds: number;
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
totalWorkspaces: number;
|
totalWorkspaces: number;
|
||||||
rescueWorkspaces: number;
|
rescueWorkspaces: number;
|
||||||
|
rescueBirds: number;
|
||||||
pendingRescues: number;
|
pendingRescues: number;
|
||||||
dailyUsers: number;
|
dailyUsers: number;
|
||||||
};
|
};
|
||||||
@@ -3945,6 +3947,10 @@ function App() {
|
|||||||
<span>Total birds</span>
|
<span>Total birds</span>
|
||||||
<strong>{adminSummary?.totalBirds ?? '-'}</strong>
|
<strong>{adminSummary?.totalBirds ?? '-'}</strong>
|
||||||
</article>
|
</article>
|
||||||
|
<article className="summary-card">
|
||||||
|
<span>Memorialized birds</span>
|
||||||
|
<strong>{adminSummary?.memorializedBirds ?? '-'}</strong>
|
||||||
|
</article>
|
||||||
<article className="summary-card">
|
<article className="summary-card">
|
||||||
<span>Daily users</span>
|
<span>Daily users</span>
|
||||||
<strong>{adminSummary?.dailyUsers ?? '-'}</strong>
|
<strong>{adminSummary?.dailyUsers ?? '-'}</strong>
|
||||||
@@ -3961,6 +3967,10 @@ function App() {
|
|||||||
<span>Rescue flocks</span>
|
<span>Rescue flocks</span>
|
||||||
<strong>{adminSummary?.rescueWorkspaces ?? '-'}</strong>
|
<strong>{adminSummary?.rescueWorkspaces ?? '-'}</strong>
|
||||||
</article>
|
</article>
|
||||||
|
<article className="summary-card">
|
||||||
|
<span>Total rescue birds</span>
|
||||||
|
<strong>{adminSummary?.rescueBirds ?? '-'}</strong>
|
||||||
|
</article>
|
||||||
<article className="summary-card">
|
<article className="summary-card">
|
||||||
<span>Pending rescues</span>
|
<span>Pending rescues</span>
|
||||||
<strong>{adminSummary?.pendingRescues ?? '-'}</strong>
|
<strong>{adminSummary?.pendingRescues ?? '-'}</strong>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.7 MiB |
Reference in New Issue
Block a user