Skip to content

Commit 2922861

Browse files
committed
feat: upgrade education resource browser
1 parent ffd9d8e commit 2922861

8 files changed

Lines changed: 512 additions & 197 deletions

File tree

docs/E2E_TESTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ For protected Vercel previews, configure the automation environment with `VERCEL
2929
## Current Coverage
3030

3131
- Public education list and education detail page.
32+
- Education browser rendering after the automatic search/filter upgrade.
3233
- Admin credential login.
3334
- `/api/health`.
3435
- Dashboard patient and appointment creation entry points.
@@ -56,3 +57,4 @@ These match the seeded staff accounts. Override them for shared environments.
5657
4. Upload a small test attachment and verify preview/download links.
5758
5. Submit one clinical form per workflow with generated data.
5859
6. Add protected-preview GitHub Actions smoke checks once deployment URL discovery is wired in.
60+
7. Add automatic education search, category filter, sort, and featured-resource checks.

docs/EDUCATION_SYSTEM.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Education System
2+
3+
Last updated: 2026-05-07
4+
5+
The education area is the public learning library for patients, families, and care teams.
6+
7+
## Public Experience
8+
9+
Public route:
10+
11+
```text
12+
/education
13+
```
14+
15+
Resource detail route:
16+
17+
```text
18+
/education/[id-or-slug]
19+
```
20+
21+
The public education browser includes:
22+
23+
- Automatic search as the user types.
24+
- URL-backed filters for category, difficulty, content type, sort order, and page.
25+
- Featured resource recommendations.
26+
- Published resource, category, and current result counts.
27+
- Category buttons with live published-resource counts.
28+
- Mobile-friendly filter and result layout.
29+
- Error and empty states that keep the page usable when the content API is unavailable.
30+
31+
## Automatic Behavior
32+
33+
Search is debounced, then written to the URL automatically. That means users can share filtered education links and use browser back/forward naturally.
34+
35+
The content API supports these sorting modes:
36+
37+
- `newest`
38+
- `popular`
39+
- `title-asc`
40+
- `title-desc`
41+
42+
Featured resources are loaded automatically from published content marked `isFeatured`.
43+
44+
## Content Management
45+
46+
Dashboard route:
47+
48+
```text
49+
/dashboard/education
50+
```
51+
52+
Admins and healthcare providers can:
53+
54+
- Add a new education resource.
55+
- Review draft and published resources.
56+
- Publish or unpublish resources.
57+
- Open resources from the dashboard list.
58+
59+
Content creation route:
60+
61+
```text
62+
/education/new
63+
```
64+
65+
## API
66+
67+
List published content:
68+
69+
```text
70+
GET /api/content
71+
```
72+
73+
Common query params:
74+
75+
- `page`
76+
- `limit`
77+
- `search`
78+
- `category`
79+
- `difficulty`
80+
- `type`
81+
- `featured=true`
82+
- `sort`
83+
84+
List categories:
85+
86+
```text
87+
GET /api/content/categories
88+
```
89+
90+
## Verification
91+
92+
The Playwright suite verifies:
93+
94+
- Public education resources load.
95+
- A resource detail page can be opened.
96+
- Dashboard education management controls render for admins.
97+
98+
Next test improvements:
99+
100+
- Exercise automatic search and filter URLs.
101+
- Verify featured resources render when seeded content has `isFeatured=true`.
102+
- Add a publish/unpublish dashboard flow in an isolated test database.

docs/PRODUCTION_READINESS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ Avoid the session pooler on port `5432` for app runtimes. It can produce `MaxCli
5858
The `/api/export` endpoint is intentionally protected behind verified admin or healthcare-provider accounts. Exports are sent with `Cache-Control: no-store` and each successful export writes an audit event with the export type, file format, filters, and row count.
5959

6060
Admins can review recent export activity from the reports dashboard Export History panel or the full audit log at `/dashboard/audit`. See `docs/CLINICAL_EXPORTS.md` for the current export workflow and verification coverage.
61+
62+
## Education Readiness
63+
64+
The public `/education` page depends on the content and category APIs. Before production promotion, confirm seeded or managed content exists, `/api/content` returns published resources, and `/api/content/categories` returns category counts. See `docs/EDUCATION_SYSTEM.md` for the current public browser, automatic search, filters, and content-management workflow.

docs/PROJECT_STATUS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ Latest local checks:
4141
- Patient timeline with role-aware clinical edit links.
4242
- Upload preview/download management for attachments.
4343
- Education resource publishing controls.
44+
- Public education browser with automatic search, URL-backed filters, sorting, featured resources, and live result counts.
4445
- Demo seed data for patients, appointments, records, pregnancies, ANC visits, newborns, immunizations, reports, notifications, audit logs, and long-form education content.
4546
- Secured clinical export API with verified admin/provider access, no-store download responses, and audit logging.
4647
- Reports dashboard Clinical Exports panel for patient, appointment, and medical-record downloads.
4748
- Admin Export History on the reports dashboard for recent audited export activity.
49+
- Education system documentation covering public browsing, automatic behavior, content management, and API parameters.
4850

4951
## Best Next Upgrades
5052

@@ -61,7 +63,7 @@ Latest local checks:
6163
Back up Supabase, restore into a rehearsal DB, verify drift, then replace legacy SQLite-era migrations with the generated PostgreSQL baseline.
6264

6365
5. Deepen the E2E tests from smoke coverage into create/update workflows.
64-
Current tests verify entry points, rendering, education reading, health, export security, and dashboard export controls. Next add real patient creation, appointment creation, attachment upload, and clinical record submission in an isolated test database.
66+
Current tests verify entry points, rendering, education reading, health, export security, dashboard export controls, and dashboard education management presence. Next add automatic education filter checks, real patient creation, appointment creation, attachment upload, and clinical record submission in an isolated test database.
6567

6668
6. Add production observability review.
6769
Confirm Web Analytics, Speed Insights, logs, health dashboard, upload storage, Redis rate limiting, and email delivery all behave in a preview/prod environment.

docs/UPGRADE_PLAN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Last updated: 2026-05-07
2727
- Secured `/api/export` behind verified admin/provider access and audit logging.
2828
- Added Clinical Exports controls to the reports dashboard.
2929
- Added admin Export History to the reports dashboard.
30+
- Rebuilt the public education page with automatic search, working category/type/difficulty filters, sorting, featured resources, and stronger empty/error states.
31+
- Added `docs/EDUCATION_SYSTEM.md`.
3032
- Confirmed local verification with lint, typecheck, Jest, Playwright, production build, and production audit.
3133

3234
## Prisma 7 Status
@@ -82,6 +84,7 @@ Current decision:
8284
- Add automated protected-preview smoke tests using `VERCEL_PROTECTION_BYPASS`.
8385
- Clean the ESLint warning backlog reported by `npm run lint:all`.
8486
- Add deeper end-to-end tests for patient creation, appointment creation, uploads, and submitted clinical forms.
87+
- Add focused education E2E coverage for automatic search, category filters, sorting, featured resources, and publish/unpublish flows.
8588
- Plan Better Auth migration as a dedicated project.
8689
- Activate the PostgreSQL baseline only after Supabase backup and restore rehearsal.
8790

e2e/public-and-auth.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ test.describe('public and auth flows', () => {
55
test('education resources load and a resource can be opened', async ({ page }) => {
66
await page.goto('/education');
77
await expectPageReady(page, /educational resources/i);
8+
await expect(page.getByText(/auto-search/i)).toBeVisible();
9+
10+
await page.getByLabel(/search/i).fill('pregnancy');
11+
await expect(page).toHaveURL(/search=pregnancy/);
12+
await expect(page.getByText(/filtered by "pregnancy"/i)).toBeVisible();
813

914
const response = await page.request.get('/api/content?page=1&limit=1');
1015
expect(response.ok()).toBeTruthy();

src/app/api/content/route.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { auth } from '@/auth';
22
import { prisma } from '@/lib/prisma';
3-
import { AuditAction, ContentType, DifficultyLevel } from '@prisma/client';
3+
import { AuditAction, ContentType, DifficultyLevel, type Prisma } from '@prisma/client';
44
import { writeAuditLog } from '@/lib/audit';
55
import { NextResponse } from 'next/server';
66

@@ -64,8 +64,21 @@ export async function GET(req: Request) {
6464
const difficulty = searchParams.get('difficulty') as DifficultyLevel | null;
6565
const type = searchParams.get('type') as ContentType | null;
6666
const isFeatured = searchParams.get('featured') === 'true';
67+
const sort = searchParams.get('sort') || 'newest';
6768

6869
const skip = (page - 1) * limit;
70+
const orderBy: Prisma.ContentOrderByWithRelationInput[] = (() => {
71+
switch (sort) {
72+
case 'popular':
73+
return [{ viewCount: 'desc' }, { createdAt: 'desc' }];
74+
case 'title-asc':
75+
return [{ title: 'asc' }];
76+
case 'title-desc':
77+
return [{ title: 'desc' }];
78+
default:
79+
return [{ createdAt: 'desc' }];
80+
}
81+
})();
6982

7083
const where = {
7184
isPublished: true,
@@ -77,7 +90,7 @@ export async function GET(req: Request) {
7790
OR: [
7891
{ title: { contains: search } },
7992
{ description: { contains: search } },
80-
{ tags: { hasSome: [search] } },
93+
{ tags: { hasSome: search.split(/\s+/).filter(Boolean) } },
8194
],
8295
}),
8396
};
@@ -93,7 +106,7 @@ export async function GET(req: Request) {
93106
select: { name: true, slug: true },
94107
},
95108
},
96-
orderBy: { createdAt: 'desc' },
109+
orderBy,
97110
skip,
98111
take: limit,
99112
}),

0 commit comments

Comments
 (0)