diff --git a/.claude/settings.json b/.claude/settings.json index b03c1b51..47ecdbeb 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -86,4 +86,4 @@ } ] } -} +} \ No newline at end of file diff --git a/apps/web/src/app.test.ts b/apps/web/src/app.test.ts index 933d9267..fde3824f 100644 --- a/apps/web/src/app.test.ts +++ b/apps/web/src/app.test.ts @@ -180,8 +180,8 @@ describe("Web Application", () => { it("should register public pages routes", async () => { const { createSimpleRouter } = await import("@hmcts/simple-router"); - // Should be called 8 times: location API routes, civil-family-cause-list pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes - expect(createSimpleRouter).toHaveBeenCalledTimes(8); + // Should be called 9 times: location API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes + expect(createSimpleRouter).toHaveBeenCalledTimes(9); }); it("should register system-admin page routes", async () => { diff --git a/apps/web/src/app.ts b/apps/web/src/app.ts index d831cafb..4d1f3a37 100644 --- a/apps/web/src/app.ts +++ b/apps/web/src/app.ts @@ -4,6 +4,10 @@ import "@hmcts/web-core"; // Import for Express type augmentation import { fileUploadRoutes as adminFileUploadRoutes, moduleRoot as adminModuleRoot, pageRoutes as adminRoutes } from "@hmcts/admin-pages/config"; import { authNavigationMiddleware, cftCallbackHandler, configurePassport, ssoCallbackHandler } from "@hmcts/auth"; import { moduleRoot as authModuleRoot, pageRoutes as authRoutes } from "@hmcts/auth/config"; +import { + moduleRoot as careStandardsTribunalModuleRoot, + pageRoutes as careStandardsTribunalRoutes +} from "@hmcts/care-standards-tribunal-weekly-hearing-list/config"; import { moduleRoot as civilFamilyCauseListModuleRoot, pageRoutes as civilFamilyCauseListRoutes } from "@hmcts/civil-and-family-daily-cause-list/config"; import { configurePropertiesVolume, healthcheck, monitoringMiddleware } from "@hmcts/cloud-native-platform"; import { moduleRoot as listTypesCommonModuleRoot } from "@hmcts/list-types-common/config"; @@ -69,6 +73,7 @@ export async function createApp(): Promise { adminModuleRoot, authModuleRoot, listTypesCommonModuleRoot, + careStandardsTribunalModuleRoot, civilFamilyCauseListModuleRoot, systemAdminModuleRoot, publicPagesModuleRoot, @@ -106,8 +111,9 @@ export async function createApp(): Promise { // Register API routes for location autocomplete app.use(await createSimpleRouter(locationApiRoutes)); - // Register civil-and-family-daily-cause-list routes first to ensure proper route matching + // Register list type routes first to ensure proper route matching app.use(await createSimpleRouter(civilFamilyCauseListRoutes)); + app.use(await createSimpleRouter(careStandardsTribunalRoutes)); app.use(await createSimpleRouter({ path: `${__dirname}/pages` }, pageRoutes)); app.use(await createSimpleRouter(authRoutes, pageRoutes)); diff --git a/apps/web/src/assets/js/index.test.ts b/apps/web/src/assets/js/index.test.ts index 5c3397c5..aaaa9f8c 100644 --- a/apps/web/src/assets/js/index.test.ts +++ b/apps/web/src/assets/js/index.test.ts @@ -23,6 +23,10 @@ vi.mock("@hmcts/web-core/src/assets/js/search-autocomplete.js", () => ({ initSearchAutocomplete: vi.fn(() => Promise.resolve()) })); +vi.mock("@hmcts/web-core/src/assets/js/search-highlight.js", () => ({ + initSearchHighlight: vi.fn() +})); + vi.mock("govuk-frontend", () => ({ initAll: vi.fn() })); diff --git a/apps/web/src/assets/js/index.ts b/apps/web/src/assets/js/index.ts index 81b94e49..b12d5312 100644 --- a/apps/web/src/assets/js/index.ts +++ b/apps/web/src/assets/js/index.ts @@ -2,6 +2,7 @@ import cookieManager from "@hmcts/cookie-manager"; import { initBackToTop } from "@hmcts/web-core/src/assets/js/back-to-top.js"; import { initFilterPanel } from "@hmcts/web-core/src/assets/js/filter-panel.js"; import { initSearchAutocomplete } from "@hmcts/web-core/src/assets/js/search-autocomplete.js"; +import { initSearchHighlight } from "@hmcts/web-core/src/assets/js/search-highlight.js"; import { initAll } from "govuk-frontend"; initAll(); @@ -14,6 +15,7 @@ if (document.readyState === "loading") { }); initFilterPanel(); initBackToTop(); + initSearchHighlight(); }); } else { void initSearchAutocomplete().catch((error) => { @@ -21,6 +23,7 @@ if (document.readyState === "loading") { }); initFilterPanel(); initBackToTop(); + initSearchHighlight(); } const config = { diff --git a/docs/tickets/VIBE-166/plan.md b/docs/tickets/VIBE-166/plan.md new file mode 100644 index 00000000..7233ae10 --- /dev/null +++ b/docs/tickets/VIBE-166/plan.md @@ -0,0 +1,447 @@ +# VIBE-166: Technical Implementation Plan + +**Schema Reference:** `https://github.com/hmcts/pip-data-management/blob/master/src/main/resources/schemas/non-strategic/cst_weekly_hearing_list.json` + +This plan has been updated to match the official Care Standards Tribunal schema from pip-data-management. + +--- + +## 1. Technical Approach + +### High-Level Strategy + +The upload flow (form → summary → success) is **already implemented** in `libs/admin-pages`. This ticket focuses on: + +1. Adding **Care Standards Tribunal Weekly Hearing List** as a new list type +2. Implementing **Excel-to-JSON conversion** for flat file uploads +3. Creating the **front-end display page** for Care Standards Tribunal lists +4. Validating JSON against a **custom schema** specific to Care Standards Tribunal + +### Architecture Decisions + +**Modular List Type Architecture** +- Follow the existing pattern in `libs/list-types/civil-and-family-daily-cause-list` +- Create a new module: `libs/list-types/care-standards-tribunal-weekly-hearing-list` +- Each list type is self-contained with its own schema, validation, rendering, and display page + +**Excel Processing Strategy** +- Excel-to-JSON conversion should happen **after** the user confirms on the summary page +- Conversion logic will be part of the new list type module +- Use `xlsx` library (already available in dependencies) to parse Excel files +- Store the converted JSON file alongside the original Excel file +- Validation runs on the JSON output to ensure schema compliance + +**File Storage Pattern** +- Excel files stored in `storage/temp/uploads/{artefactId}.xlsx` +- JSON files stored in `storage/temp/uploads/{artefactId}.json` +- Both files associated with the same `artefactId` in the database +- The `isFlatFile` flag distinguishes Excel uploads (true) from JSON uploads (false) + +--- + +## 2. Implementation Details + +### File Structure + +``` +libs/list-types/care-standards-tribunal-weekly-hearing-list/ +├── package.json +├── tsconfig.json +└── src/ + ├── config.ts # Module configuration exports + ├── index.ts # Business logic exports + ├── models/ + │ └── types.ts # TypeScript types for CST data + ├── pages/ + │ ├── index.ts # GET handler for list display + │ ├── care-standards-tribunal-weekly-hearing-list.njk # Template + │ ├── en.ts # English content + │ └── cy.ts # Welsh content + ├── schemas/ + │ └── care-standards-tribunal-weekly-hearing-list.json # JSON schema + ├── validation/ + │ └── json-validator.ts # Schema validation + ├── rendering/ + │ └── renderer.ts # Transform JSON → view data + └── conversion/ + ├── excel-to-json.ts # Excel → JSON conversion + └── excel-to-json.test.ts # Unit tests +``` + +### Component Details + +#### 1. List Type Registration + +Add to `libs/list-types/common/src/mock-list-types.ts`: + +```typescript +{ + id: 9, + name: "CARE_STANDARDS_TRIBUNAL_WEEKLY_HEARING_LIST", + englishFriendlyName: "Care Standards Tribunal Weekly Hearing List", + welshFriendlyName: "Welsh placeholder", + provenance: "MANUAL_UPLOAD", + urlPath: "care-standards-tribunal-weekly-hearing-list" +} +``` + +#### 2. JSON Schema + +**Schema Location:** `https://github.com/hmcts/pip-data-management/blob/master/src/main/resources/schemas/non-strategic/cst_weekly_hearing_list.json` + +**Schema Structure (from pip-data-management):** + +Each hearing record contains: + +```json +{ + "date": { + "title": "Date", + "type": "string", + "pattern": "^\\d{2}/\\d{2}/\\d{4}$", + "examples": ["02/01/2025"] + }, + "caseName": { + "title": "Case name", + "type": "string", + "pattern": "^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$", + "examples": ["A Vs B"] + }, + "hearingLength": { + "title": "Length of hearing", + "type": "string", + "pattern": "^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$", + "examples": ["1 hour"] + }, + "hearingType": { + "title": "Type of hearing being presented", + "type": "string", + "pattern": "^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$", + "examples": ["mda"] + }, + "venue": { + "title": "Venue name of the hearing", + "type": "string", + "pattern": "^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$", + "examples": ["This is the venue of the hearing"] + }, + "additionalInformation": { + "title": "Additional information", + "type": "string", + "pattern": "^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$", + "examples": ["This is additional information"] + } +} +``` + +**Key Validation Rules:** +- `date`: Must match `dd/MM/yyyy` format (e.g., "02/01/2025") +- All text fields: Must not contain HTML tags (XSS protection via pattern `^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$`) +- All fields are required (as per ticket specification) + +#### 3. Excel-to-JSON Conversion + +**Function Signature:** +```typescript +export async function convertExcelToJson( + excelBuffer: Buffer +): Promise + +interface CareStandardsTribunalHearing { + date: string; // dd/MM/yyyy format + caseName: string; + hearingLength: string; + hearingType: string; + venue: string; + additionalInformation: string; +} +``` + +**Process:** +1. Parse Excel buffer using `xlsx` library +2. Read first worksheet +3. Validate headers match expected columns (case-insensitive): + - "Date", "Case name", "Hearing length", "Hearing type", "Venue", "Additional information" +4. Map each row to a hearing object +5. Validate each field: + - `date`: Must match `dd/MM/yyyy` pattern (e.g., "02/01/2025") + - All text fields: Must not contain HTML tags (XSS protection) +6. Return array of hearing objects matching schema +7. **Note:** Date transformation from `dd/MM/yyyy` to `d MMMM yyyy` happens during rendering, not conversion + +#### 4. Integration with Upload Summary + +Modify `libs/admin-pages/src/pages/non-strategic-upload-summary/index.ts`: + +**POST handler enhancement:** +```typescript +// After creating artefact, before saving file +if (isFlatFile && listTypeId === 9) { + // Import conversion and validation functions + const { convertExcelToJson, validateCareStandardsTribunalList } = await import( + "@hmcts/care-standards-tribunal-weekly-hearing-list" + ); + + // Convert Excel to JSON (array of hearing objects) + const hearingsData = await convertExcelToJson(uploadData.file); + + // Validate against pip-data-management schema + const validation = validateCareStandardsTribunalList(hearingsData); + if (!validation.isValid) { + throw new Error(`Invalid Excel format: ${validation.errors.join(", ")}`); + } + + // Save both Excel and JSON + await saveUploadedFile(artefactId, uploadData.fileName, uploadData.file); + await saveUploadedFile( + artefactId, + `${artefactId}.json`, + Buffer.from(JSON.stringify(hearingsData)) + ); +} +``` + +**Note:** The conversion returns an array of hearing objects directly. Metadata like court name, list title, and duration will be stored separately in the artefact record and combined during rendering. + +#### 5. Front-End Display Page + +**Route:** `/care-standards-tribunal-weekly-hearing-list?artefactId={id}` + +**Template Structure:** +```nunjucks +{# Header #} +

Care Standards Tribunal Weekly Hearing List

+

List for week commencing {{ header.duration }}

+

Last updated {{ header.lastUpdated }}

+ +{# Important Information Accordion #} +
+ Important information +

Please contact the Care Standards Office at cst@justice.gov.uk...

+ Observe a court or tribunal hearing... +
+ +{# Search Cases #} +

Search Cases

+ + +{# Cases Table #} + + + + + + + + + + + + + {% for hearing in hearings %} + + + + + + + + + {% endfor %} + +
DateCase nameHearing lengthHearing typeVenueAdditional information
{{ hearing.date }}{{ hearing.caseName }}{{ hearing.hearingLength }}{{ hearing.hearingType }}{{ hearing.venue }}{{ hearing.additionalInformation }}
+ +{# Footer #} +

Data source: Care Standards Tribunal

+Back to top +``` + +**Search Functionality:** +- Client-side JavaScript search (same pattern as civil-and-family list) +- Highlights matching text with yellow background +- Searches across all table columns + +#### 6. Application Registration + +**Register page routes** in `apps/web/src/app.ts`: +```typescript +import { pageRoutes as cstPages } from "@hmcts/care-standards-tribunal-weekly-hearing-list/config"; + +app.use(await createSimpleRouter(cstPages)); +``` + +**Register in tsconfig.json:** +```json +{ + "compilerOptions": { + "paths": { + "@hmcts/care-standards-tribunal-weekly-hearing-list": [ + "libs/list-types/care-standards-tribunal-weekly-hearing-list/src" + ] + } + } +} +``` + +--- + +## 3. Error Handling & Edge Cases + +### Excel Validation Errors + +**Scenario:** Excel file has wrong headers or missing columns + +**Handling:** +- Validate headers during conversion (case-insensitive match) +- Expected headers: "Date", "Case name", "Hearing length", "Hearing type", "Venue", "Additional information" +- Throw descriptive error: "Excel file must contain columns: Date, Case name, Hearing length, Hearing type, Venue, Additional information" +- Catch error in POST handler and render error on summary page +- Error message: "The uploaded file format is invalid. Please check your file and try again." + +### Date Format Errors + +**Scenario:** Date column contains invalid dates (e.g., "32/01/2025", "2025-01-01", "1/1/2025") + +**Handling:** +- Validate each date against pattern `^\\d{2}/\\d{2}/\\d{4}$` during conversion +- Must be exactly `dd/MM/yyyy` format (e.g., "02/01/2025" not "2/1/2025") +- Validate date is actually valid (e.g., not "32/01/2025") +- Throw error: "Invalid date format in row X: '{value}'. Expected format: dd/MM/yyyy (e.g., 02/01/2025)" +- Display error on summary page with row number +- User must fix Excel and re-upload + +### HTML/XSS Validation Errors + +**Scenario:** Excel contains HTML tags in any field (e.g., case name contains "") + +**Handling:** +- Validate all text fields against pattern `^(?!(.|\\r|\\n)*<[^>]+>)(.|\\r|\\n)*$` +- Pattern rejects any string containing HTML tags +- Throw error: "Invalid content in row X, column {fieldName}: HTML tags are not allowed" +- Display error on summary page +- User must remove HTML tags from Excel and re-upload + +### Empty Excel File + +**Scenario:** Excel file has no data rows (only headers) + +**Handling:** +- Check row count after parsing +- Throw error: "Excel file must contain at least one hearing" +- Display error on summary page + +### Missing Required Fields + +**Scenario:** Excel row has empty cells for required fields + +**Handling:** +- All 6 fields are required per the schema +- Validate each field is non-empty during conversion +- Throw error: "Missing required field '{fieldName}' in row X" +- Display error on summary page with row and field details + +### JSON Schema Validation Failure + +**Scenario:** Converted JSON doesn't match schema (edge case if conversion logic has bugs) + +**Handling:** +- Log validation errors to console +- Display generic error: "We could not process your upload. Please try again." +- Admin can check logs for details + +### File Not Found on Display + +**Scenario:** User navigates to list display but JSON file is missing + +**Handling:** +- Check file existence in GET handler +- Return 404 with error template +- Error message: "The requested list could not be found" + +### Session Expiry + +**Scenario:** User takes too long on summary page (Redis TTL expires) + +**Handling:** +- Already handled by existing code +- uploadData will be null +- Returns 404: "Upload not found" +- User must start upload again + +--- + +## 4. Acceptance Criteria Mapping + +| Criterion | Implementation | Verification | +|-----------|---------------|--------------| +| Access verified account | Already implemented in auth middleware | E2E test: Sign in as Local Admin | +| Summary page displays details | Already implemented | E2E test: Upload file, verify summary table | +| Change links work | Already implemented | E2E test: Click Change, verify navigation | +| Continue completes upload | Already implemented | E2E test: Submit summary, verify database record | +| Success banner displayed | Already implemented using govukPanel | E2E test: Verify green panel with "Success" | +| Success links present | Already implemented | E2E test: Verify Upload another, Remove, Home links | +| Excel to JSON conversion | New: Excel-to-JSON converter | Unit test: Parse sample Excel, verify JSON structure | +| JSON validation | New: JSON schema validator | Unit test: Valid/invalid JSON, verify results | +| CST list type created | New: List type registration | Verify list type appears in dropdown | +| Front-end list display | New: Display page with specific format | E2E test: View published list, verify headers/table | +| List title format | Renderer formats title with date/language | E2E test: Verify "Care Standards Tribunal Weekly Hearing List for week commencing..." | +| Duration display | Renderer formats duration | Verify "List for week commencing 24 November 2025" | +| Last updated timestamp | Uses artefact.lastReceivedDate | Verify timestamp format "24 November 2025 at 9:55am" | +| Important Information accordion | Nunjucks template with GOV.UK details | E2E test: Open accordion, verify content and link | +| Search functionality | Client-side JS (same as civil-and-family) | E2E test: Enter search term, verify highlighting | +| Table with 6 columns | Nunjucks template renders all columns | E2E test: Verify all column headers present | +| Data source footer | Template renders provenance | Verify "Data source: Care Standards Tribunal" | +| Back to top link | Template includes anchor link | E2E test: Click link, verify scroll | +| WCAG 2.2 AA compliance | GOV.UK components + semantic HTML | Accessibility audit with axe-core | + +--- + +## 5. Open Questions & Clarifications + +### CLARIFICATIONS NEEDED + +1. **Welsh translations** + - The ticket specifies "Welsh placeholder" for Welsh content + - Should we implement placeholder text (e.g., "Welsh placeholder") or leave Welsh implementation for a future ticket? + - Recommendation: Use placeholder strings matching ticket specification + +2. **Data source label** + - Ticket shows: "Data source: Care Standards Tribunal" + - Should this always say "Care Standards Tribunal" or should it dynamically pull from provenance? + - Current assumption: Hard-code "Care Standards Tribunal" in template since this list type is CST-specific + +3. **Excel file validation timing** + - Should we validate Excel format on the **upload form** (before summary) or on the **summary page** (before submission)? + - Trade-off: Early validation = better UX, Late validation = simpler implementation + - Current approach: Validate on summary page POST (during conversion) + +4. **Invalid Excel error handling** + - Should invalid Excel files show errors on the summary page or redirect back to upload form? + - Current approach: Show error on summary page (keep user context) + +5. **Search functionality scope** + - Should search be client-side only (current implementation) or support server-side filtering for large lists? + - Current approach: Client-side only (consistent with civil-and-family list) + +6. **Excel field validation rules** ✅ ANSWERED + - **Answer from schema:** All 6 fields are mandatory + - Date must match `dd/MM/yyyy` pattern exactly + - All text fields validated against HTML tag pattern for XSS protection + - No additional format validation for hearing length or other text fields (free text) + +7. **Date range for duration** + - Ticket shows "List for week commencing 24 November 2025" + - Should this always be a week? Should we calculate end date or just show start date? + - Current approach: Use displayFrom and displayTo from upload form metadata + +8. **Multiple sheets in Excel** + - If Excel file has multiple worksheets, which sheet should we read? + - Current assumption: Always read first sheet + +9. **Excel to JSON caching** + - Should we cache the converted JSON or regenerate it on each page load? + - Current approach: Save JSON file once during upload, read from file on display + +10. **Provenance value** + - Should Care Standards Tribunal uploads use `Provenance.MANUAL_UPLOAD` or a new `Provenance.CARE_STANDARDS_TRIBUNAL`? + - Current approach: Use existing `MANUAL_UPLOAD` diff --git a/docs/tickets/VIBE-166/tasks.md b/docs/tickets/VIBE-166/tasks.md new file mode 100644 index 00000000..f59739de --- /dev/null +++ b/docs/tickets/VIBE-166/tasks.md @@ -0,0 +1,153 @@ +# VIBE-166: Implementation Tasks + +**Schema Reference:** `https://github.com/hmcts/pip-data-management/blob/master/src/main/resources/schemas/non-strategic/cst_weekly_hearing_list.json` + +## Implementation Tasks + +### 1. Module Setup +- [x] Create new module directory: `libs/list-types/care-standards-tribunal-weekly-hearing-list/` +- [x] Create `package.json` with build scripts and dependencies +- [x] Create `tsconfig.json` with proper configuration +- [x] Register module in root `tsconfig.json` paths +- [x] Create `src/config.ts` for module configuration exports +- [x] Create `src/index.ts` for business logic exports + +### 2. List Type Registration +- [x] Add Care Standards Tribunal Weekly Hearing List to `libs/list-types/common/src/mock-list-types.ts` +- [x] Set ID to 9, name to `CARE_STANDARDS_TRIBUNAL_WEEKLY_HEARING_LIST` +- [x] Set urlPath to `care-standards-tribunal-weekly-hearing-list` +- [x] Set provenance to `MANUAL_UPLOAD` + +### 3. JSON Schema Creation +- [x] Copy schema from pip-data-management to `src/schemas/care-standards-tribunal-weekly-hearing-list.json` +- [x] Schema defines array of hearing objects, each with 6 required fields +- [x] Fields: date (dd/MM/yyyy pattern), caseName, hearingLength, hearingType, venue, additionalInformation +- [x] All text fields include HTML tag validation pattern for XSS protection +- [x] Date field includes regex pattern: `^\\d{2}/\\d{2}/\\d{4}$` + +### 4. TypeScript Types +- [x] Create `src/models/types.ts` +- [x] Define `CareStandardsTribunalHearing` interface with 6 string fields +- [x] date: string (dd/MM/yyyy format) +- [x] caseName, hearingLength, hearingType, venue, additionalInformation: string +- [x] Export type as array: `CareStandardsTribunalHearing[]` + +### 5. JSON Validation +- [x] Create `src/validation/json-validator.ts` +- [x] Implement `validateCareStandardsTribunalList()` function +- [x] Use Ajv library for JSON schema validation +- [x] Return validation result with isValid flag and errors array +- [x] Create `src/validation/json-validator.test.ts` with unit tests + +### 6. Excel-to-JSON Conversion +- [x] Create `src/conversion/excel-to-json.ts` +- [x] Implement `convertExcelToJson(buffer: Buffer): Promise` +- [x] Parse Excel buffer using xlsx library and read first worksheet +- [x] Validate Excel headers match expected columns (case-insensitive) +- [x] Expected: "Date", "Case name", "Hearing length", "Hearing type", "Venue", "Additional information" +- [x] Transform each row to hearing object with camelCase field names +- [x] Validate date field matches `dd/MM/yyyy` pattern (e.g., "02/01/2025") +- [x] Keep date in dd/MM/yyyy format (DO NOT transform to readable format yet) +- [x] Validate all text fields do not contain HTML tags (XSS protection) +- [x] Handle empty cells and throw error for missing required fields +- [x] Throw descriptive errors with row numbers for invalid data +- [x] Create `src/conversion/excel-to-json.test.ts` with unit tests + +### 7. Rendering Logic +- [x] Create `src/rendering/renderer.ts` +- [x] Implement `renderCareStandardsTribunalData()` function +- [x] Transform JSON data to view model +- [x] Format header with list title, duration, last updated (from artefact metadata) +- [x] Transform date format from dd/MM/yyyy to "d MMMM yyyy" for display (e.g., "02/01/2025" → "2 January 2025") +- [x] Format hearings array for table display with all 6 columns +- [x] Create `src/rendering/renderer.test.ts` with unit tests + +### 8. Content Files +- [x] Create `src/pages/en.ts` with English content +- [x] Add page title, headings, labels, button text +- [x] Add Important Information accordion content with email and GOV.UK link +- [x] Add table column headers +- [x] Create `src/pages/cy.ts` with Welsh placeholder content +- [x] Mirror English structure with "Welsh placeholder" strings + +### 9. Display Page Controller +- [x] Create `src/pages/index.ts` +- [x] Implement GET handler +- [x] Retrieve artefactId from query params +- [x] Fetch artefact from database +- [x] Read JSON file from storage +- [x] Validate JSON against schema +- [x] Call renderer to transform data +- [x] Render template with data +- [x] Handle errors (404 for missing artefact, 400 for validation errors) + +### 10. Display Page Template +- [x] Create `src/pages/care-standards-tribunal-weekly-hearing-list.njk` +- [x] Extend base template layout +- [x] Add page title and header section with duration and last updated +- [x] Add Important Information details component (accordion) +- [x] Include email (cst@justice.gov.uk) and GOV.UK link +- [x] Add Search Cases input field +- [x] Render govukTable with 6 columns matching Excel fields +- [x] Add data source footer text +- [x] Add Back to top link +- [x] Include client-side search JavaScript with highlighting + +### 11. Integration with Upload Summary +- [x] Modify `libs/admin-pages/src/pages/non-strategic-upload-summary/index.ts` +- [x] In POST handler, detect if listTypeId === 9 (Care Standards Tribunal) +- [x] Import and call `convertExcelToJson(buffer)` with Excel buffer only +- [x] Import and call `validateCareStandardsTribunalList()` on converted JSON array +- [x] Handle conversion/validation errors and display user-friendly messages on summary page +- [x] Save both original Excel file and converted JSON file to storage with same artefactId +- [x] Keep existing success redirect flow + +### 12. Application Registration +- [x] Register page routes in `apps/web/src/app.ts` +- [x] Import pageRoutes from CST module config +- [x] Add to createSimpleRouter call +- [x] Verify routing works for `/care-standards-tribunal-weekly-hearing-list?artefactId=...` + +### 13. Testing +- [x] Write unit tests for Excel-to-JSON conversion: + - [x] Valid Excel with correct headers and data + - [x] Invalid headers (missing or misnamed columns) + - [x] Invalid dates (wrong format, e.g., "2025-01-01", "1/1/2025", "32/01/2025") + - [x] HTML tags in fields (XSS protection test) + - [x] Empty file (no data rows) + - [x] Missing required fields (empty cells) +- [x] Write unit tests for JSON validation (valid JSON, missing fields, invalid patterns) +- [x] Write unit tests for renderer (data transformation, date formatting from dd/MM/yyyy to d MMMM yyyy) +- [ ] Write unit tests for page controller (success case, missing artefact, invalid JSON) +- [x] Create E2E test for full upload flow: upload Excel → verify summary → submit → verify success +- [x] Create E2E test for Excel validation on upload page (missing fields, invalid dates, HTML tags) +- [x] Create E2E test for display page: navigate to list → verify headers → verify table → test search +- [x] Create E2E test for important information accordion: open accordion → verify content +- [x] Create E2E test for search functionality with highlighting +- [x] Create E2E test for data source display (Manual upload) +- [x] Create E2E test for keyboard navigation throughout flow +- [x] Create E2E test for Welsh language support +- [x] Run accessibility audit with axe-core on upload form, error page, and display page + +### 14. Error Handling +- [x] Add error handling for invalid Excel headers (missing or misnamed columns) +- [x] Add error handling for invalid date formats in Excel (must be dd/MM/yyyy) +- [x] Add error handling for HTML tags in any field (XSS protection) +- [x] Add error handling for empty Excel files (no data rows) +- [x] Add error handling for missing required fields (empty cells) +- [x] Add error handling for JSON validation failures +- [x] Add error handling for missing JSON files on display +- [x] Add user-friendly error messages on summary page with row/column details + +### 15. Documentation +- [x] Update module README if needed +- [x] Add JSDoc comments to public functions +- [x] Document Excel file format requirements + +### 16. Build and Deploy +- [x] Run `yarn build` to verify module builds successfully +- [x] Run `yarn test` to verify all tests pass +- [x] Run `yarn lint:fix` to fix any linting issues +- [x] Verify Nunjucks templates are copied to dist/ folder +- [ ] Test in local development environment +- [ ] Verify file upload, summary, success, and display pages work end-to-end diff --git a/docs/tickets/VIBE-166/ticket.md b/docs/tickets/VIBE-166/ticket.md new file mode 100644 index 00000000..2f230de6 --- /dev/null +++ b/docs/tickets/VIBE-166/ticket.md @@ -0,0 +1,338 @@ +# VIBE-166: Excel Upload – Complete excel upload process / Care Standards Tribunal Weekly Hearing List + +## Summary +Excel Upload – Complete excel upload process / Care Standards Tribunal Weekly Hearing List + +## Status +In Progress + +## Assignee +Iqbal, Junaid (Junaid.Iqbal@justice.gov.uk) + +## Created +2025-10-07T10:28:22.239+0000 + +## Updated +2025-12-04T09:48:44.172+0000 + +## Description + +### PROBLEM STATEMENT + +The non-strategic publishing route requires the upload of an excel file in CaTH which is then transformed at the back end to a Json file before publishing. The upload process is completed over a number of steps. + +### User Story + +**AS A** Local Admin + +**I WANT** to complete the excel file upload process + +**SO THAT** I can publish a hearing list in CaTH + +### ACCEPTANCE CRITERIA + +* A local admin is able to access a verified account by signing in through the sign in page with their approved log in details +* When the local admin clicks the continue button on the excel file upload form, the user sees a summary page that displays the high level details of the uploaded file (Court name, File, List type, hearing start date, sensitivity, language and display file dates) in a table +* Each detail row shows the selected data and a link titled 'change' beside it that allows the user update the selected detail +* The local admin can click the continue button below to complete the excel file upload process +* When the local admin clicks the continue button to complete the excel file upload process, a confirmation of successful upload is displayed by the system +* The system displays 'success' boldly in a green banner +* In the same banner, a descriptive text is displayed and reads 'Your file has been uploaded' +* Beneath the green banner, the user can see several links that directs them to 'upload another file', 'remove file' or 'home'. +* when the list is uploaded, at the back end, the excel file is converted to a JSon file and validated using the approved validation schema displayed in the front end with the attached style guide +* The first list type to be created through the excel upload is for the Care Standards Tribunal +* at the front end, the list name will be displayed with the date of publication and language version. i.e. **Care Standards Tribunal Weekly Hearing List for week commencing 24 November 2025 - English (Saesneg)** +* The list will display the list title 'Care Standards Tribunal Weekly Hearing List' at the top of the list followed by the duration covered in the list written in the format 'List for week commencing 24 November 2025' and then the date the list was last updated which is displayed as 'Last updated 24 November 2025 at 9:55am' +* this is followed by a collapsible accordion beside the words 'Important Information' which when opened, displays the following sentence 'Please contact the Care Standards Office at cst@justice.gov.uk for details of how to access video hearings.' and then the link [Observe a court or tribunal hearing - GOV.UK](https://www.gov.uk/guidance/observe-a-court-or-tribunal-hearing) which is masked in the text 'Observe a court or tribunal hearing as a journalist, researcher or member of the public'. +* This will be followed by a search field with the title 'Search Cases' and then a table with the following data fields / columns; Date, Case name, Hearing length, Hearing type, Venue and Additional information +* At the bottom of the page, the 'Data Source: ' is listed followed by the 'Back to top' arrow +* All CaTH pages specifications are maintained + +### Excel Field Mapping + +| Excel field name | Schema field name | Mandatory | Comments | +|------------------|-------------------|-----------|----------| +| Date | date | Yes | Must be in format dd/MM/yyyy. E.g. 01/01/2025. This will be transformed into 1 January 2025 when displaying on the style guide. | +| Case name | caseName | Yes | | +| Hearing length | hearingLength | Yes | | +| Hearing type | hearingType | Yes | | +| Venue | venue | Yes | | +| Additional information | additionalInformation | Yes | | + +## Detailed Specification + +### 1. User Story + +**As a** Local Admin +**I want to** complete the Excel file upload process +**So that** I can publish a hearing list in CaTH + +### 2. Form Fields + +(Summary page uses read-only values; upload form fields are out of scope for this story. Only summary fields are listed here.) + +| Field name | Input type | Required | Validation / Notes | +|------------|------------|----------|-------------------| +| Court name | Display | Yes | Must match existing CaTH venue reference data | +| File name | Display | Yes | Must be a valid Excel file previously uploaded | +| List type | Display | Yes | First list type implemented: **Care Standards Tribunal Weekly Hearing List** | +| Hearing start date | Display | Yes | Must be a valid date | +| Sensitivity | Display | Yes | Public or Restricted | +| Language | Display | Yes | EN / CY | +| Display file dates | Display | Yes | Date range (e.g., "List for week commencing…") | + +Each display row includes a **Change** link pointing back to the upload form to modify the selected attribute. + +### 3. Content Requirements + +#### 3.1 Summary Page (Step Before Submission) + +**EN Content** +* Title/H1: **"Check your upload details"** +* Table row labels: + * Court name + * File + * List type + * Hearing start date + * Sensitivity + * Language + * Display file dates +* Link: **"Change"** (one per row) +* Button: **"Continue"** + +**CY Content (placeholders)** +* Title/H1: "Welsh placeholder" +* Labels: "Welsh placeholder" +* Button: "Welsh placeholder" +* Link: "Welsh placeholder" + +#### 3.2 Success Page After Submission + +**EN** +* Success banner header: **"Success"** +* Success banner text: **"Your file has been uploaded"** +* Links below banner: + * **Upload another file** + * **Remove file** + * **Home** + +**CY** +* "Welsh placeholder" for banner and links. + +#### 3.3 Front-End Display — Care Standards Tribunal Weekly Hearing List + +The first list type to be supported is the **Care Standards Tribunal Weekly Hearing List**. +After Excel → JSON conversion and validation, the published list appears on the CaTH front end as follows. + +##### Header Format +* **List title:** "Care Standards Tribunal Weekly Hearing List" +* **Duration:** "List for week commencing 24 November 2025" +* **Last updated:** "Last updated 24 November 2025 at 9:55am" + +##### Important Information Accordion +* Label: **"Important information"** (accordion closed by default) +* When opened, show: + * Sentence: **"Please contact the Care Standards Office at cst@justice.gov.uk for details of how to access video hearings."** + * Link masked in text: **"Observe a court or tribunal hearing as a journalist, researcher or member of the public"** (links to Observe a court or tribunal hearing – GOV.UK) + +##### Search Section +* Title/H2: **"Search Cases"** +* Search input: Free text (case name, date, venue, etc.) + +##### Cases Table + +Column headers: +1. **Date** +2. **Case name** +3. **Hearing length** +4. **Hearing type** +5. **Venue** +6. **Additional information** + +Rows are populated from the validated JSON derived from the Excel upload. + +##### Footer Content +* "**Data source:** Care Standards Tribunal" +* "**Back to top**" arrow/text + +### 4. Errors + +#### Summary Page +This page never triggers field-level validation (all values originate from the upload form). +Errors are only triggered if: +* Mandatory data is missing (redirect back to upload form) +* Session expired (redirect to sign-in) + +#### Upload Success +No error messages appear on the success page. + +### 5. Back Navigation +* **Summary page Back link:** Returns to Excel upload form with pre-populated values. +* **Success page Back link:** Returns to summary page (non-editable). +* **Front-end list Back to Top:** Scrolls user back to H1. + +### 6. Accessibility + +All pages must comply with **WCAG 2.2 AA**, **GOV.UK Design System**, and **CaTH page specifications**, including: +* Success banner must use `role="status"` +* Summary table must use: + * `` for row labels + * `` for header cells +* Accordion must implement: + * `aria-expanded` + * `aria-controls` + * Standard GOV.UK accordion pattern +* Links must have visible focus states +* Search input must have: + * Associated `