clqms-be/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md
mahdahar d173098652 feat: implement audit logging and test management enhancements
Major Features:
- Add comprehensive audit logging system with AuditService
- Create AuditLogs database migration for tracking changes
- Implement TestValidationService for test data validation
- Add FRONTEND_TEST_MANAGEMENT_PROMPT.md documentation

Controllers:
- Update TestsController with improved test management

Models:
- Enhance PatientModel with additional functionality
- Update TestDefSiteModel for better site management

Database:
- Add CreateAuditLogs migration (2026-02-20-000011)
- Update TestSeeder with new test data

Services:
- Add AuditService for comprehensive audit trail logging

Documentation:
- Update AGENTS.md with improved guidelines
- Update audit-logging-plan.md with implementation details
- Add FRONTEND_TEST_MANAGEMENT_PROMPT.md for frontend guidance

API Documentation:
- Update api-docs.bundled.yaml
- Update tests.yaml schema definitions
- Update tests.yaml paths

Testing:
- Enhance TestsControllerTest with new test cases
- Update TestDefModelsTest for model coverage
2026-02-20 13:47:47 +07:00

57 KiB

CLQMS Master Data - Test Management Frontend Development Prompt

📋 Project Overview

Build a modern, responsive Svelte 5 frontend for CLQMS (Clinical Laboratory Quality Management System) Test Management module. The frontend will consume the existing REST API backend (/api/tests) and provide a comprehensive interface for managing laboratory test definitions across all test types.


🎯 Objective

Create a user-friendly, type-safe frontend application that enables laboratory administrators to:

  • Browse and search all test definitions with filtering
  • Create new tests of any type (TEST, PARAM, CALC, GROUP, TITLE)
  • Edit existing tests with type-specific configurations
  • Manage reference ranges (numeric and text-based)
  • Configure test mappings for external systems
  • Organize tests into groups and panels
  • Manage calculated test formulas

🛠️ Technology Stack (Svelte 5)

Core Requirements

  • Framework: Svelte 5 with runes ($state, $derived, $effect)
  • Meta-Framework: SvelteKit (for routing and server-side features)
  • Language: TypeScript (strict mode enabled)
  • Styling: Tailwind CSS
  • UI Components: Skeleton UI (or Melt UI)
  • Forms: Headless form components with validation
  • HTTP Client: Axios (with request/response interceptors)
  • State Management: Svelte 5 runes (no external store library required)
  • Build Tool: Vite (comes with SvelteKit)
{
  "dependencies": {
    "svelte": "^5.0.0",
    "@sveltejs/kit": "^2.0.0",
    "axios": "^1.6.0",
    "zod": "^3.22.0",
    "date-fns": "^3.0.0"
  },
  "devDependencies": {
    "skeleton": "^2.8.0",
    "@skeletonlabs/tw-plugin": "^0.3.0",
    "tailwindcss": "^3.4.0",
    "autoprefixer": "^10.4.0",
    "postcss": "^8.4.0",
    "typescript": "^5.3.0"
  }
}

📁 Project Structure

src/
├── lib/
│   ├── components/
│   │   ├── test/
│   │   │   ├── TestList.svelte                 # Main test listing page
│   │   │   ├── TestForm.svelte                 # Main form container with tabs
│   │   │   ├── TestCard.svelte                 # Single test card/row
│   │   │   ├── TestFilterPanel.svelte          # Search/filter panel
│   │   │   ├── SidebarTabs.svelte              # Left navigation tabs
│   │   │   ├── tabs/
│   │   │   │   ├── BasicInfoTab.svelte         # Basic test info
│   │   │   │   ├── TechDetailsTab.svelte      # Technical specifications
│   │   │   │   ├── CalcDetailsTab.svelte       # Calculated test formula
│   │   │   │   ├── GroupMembersTab.svelte      # Group member management
│   │   │   │   ├── MappingsTab.svelte          # System mappings
│   │   │   │   ├── RefNumTab.svelte           # Numeric reference ranges
│   │   │   │   └── RefTxtTab.svelte           # Text reference ranges
│   │   │   └── modals/
│   │   │       ├── RefNumModal.svelte          # Edit reference range modal
│   │   │       ├── RefTxtModal.svelte          # Edit text reference modal
│   │   │       ├── MappingModal.svelte         # Edit mapping modal
│   │   │       └── MemberModal.svelte          # Add group member modal
│   │   └── ui/                                 # Reusable UI components
│   │       ├── Button.svelte
│   │       ├── Input.svelte
│   │       ├── Select.svelte
│   │       ├── Checkbox.svelte
│   │       ├── Table.svelte
│   │       ├── Modal.svelte
│   │       ├── Badge.svelte
│   │       ├── Tabs.svelte
│   │       ├── Alert.svelte
│   │       └── Spinner.svelte
│   ├── stores/
│   │   ├── testStore.ts                        # Test form state with runes
│   │   ├── valueSetStore.ts                    # ValueSet/dropdown data
│   │   ├── authStore.ts                        # Authentication state
│   │   └── uiStore.ts                         # UI state (modals, tabs)
│   ├── services/
│   │   ├── api.ts                              # Axios instance with interceptors
│   │   ├── testService.ts                      # Test API operations
│   │   ├── valueSetService.ts                  # ValueSet API calls
│   │   └── validationService.ts                # Frontend validation logic
│   ├── types/
│   │   ├── test.types.ts                       # All test-related types
│   │   ├── api.types.ts                        # API response/request types
│   │   ├── valueset.types.ts                   # ValueSet types
│   │   └── index.ts                           # Type exports
│   └── utils/
│       ├── validation.ts                       # Validation helpers
│       ├── format.ts                           # Formatters (dates, numbers)
│       ├── constants.ts                        # App constants
│       └── helpers.ts                          # Utility functions
├── routes/
│   ├── +layout.svelte                          # Root layout with nav
│   ├── +page.svelte                            # Landing page
│   ├── tests/
│   │   ├── +page.svelte                        # Test list page
│   │   └── [id]/
│   │       └── +page.svelte                    # Test detail/edit page
│   └── login/
│       └── +page.svelte                        # Login page
├── app.html                                    # HTML template
└── app.css                                     # Global styles

🔌 API Integration

Base Configuration

API Base URL: http://localhost:8080/api (configurable via environment variable)

Authentication: JWT token via HTTP header

Authorization: Bearer {token}

Endpoints

1. List Tests

GET /api/tests
Query Parameters:
  - SiteID (optional): Filter by site
  - TestType (optional): Filter by test type (TEST, PARAM, CALC, GROUP, TITLE)
  - VisibleScr (optional): Filter by screen visibility (0/1)
  - VisibleRpt (optional): Filter by report visibility (0/1)
  - TestSiteName (optional): Search by test name (partial match)

Response:

{
  status: "success";
  message: string;
  data: TestSummary[];
}

interface TestSummary {
  TestSiteID: number;
  TestSiteCode: string;
  TestSiteName: string;
  TestType: string;
  TestTypeLabel: string;
  SeqScr: number;
  SeqRpt: number;
  VisibleScr: number;
  VisibleRpt: number;
  CountStat: number;
  StartDate: string;
  DisciplineID?: number;
  DepartmentID?: number;
  DisciplineName?: string;
  DepartmentName?: string;
}

2. Get Single Test

GET /api/tests/:id

Response:

{
  status: "success";
  message: string;
  data: TestDetail;
}

interface TestDetail {
  // Base fields
  TestSiteID: number;
  TestSiteCode: string;
  TestSiteName: string;
  TestType: string;
  TestTypeLabel: string;
  Description?: string;
  SiteID: number;
  SeqScr: number;
  SeqRpt: number;
  VisibleScr: number;
  VisibleRpt: number;
  CountStat: number;
  StartDate: string;
  EndDate?: string;

  // Technical details (TEST/PARAM/CALC)
  DisciplineID?: number;
  DepartmentID?: number;
  DisciplineName?: string;
  DepartmentName?: string;
  ResultType?: string;
  RefType?: string;
  VSet?: string;
  Unit1?: string;
  Factor?: number;
  Unit2?: string;
  Decimal?: number;
  ReqQty?: number;
  ReqQtyUnit?: string;
  CollReq?: string;
  Method?: string;
  ExpectedTAT?: number;

  // Nested data based on TestType
  testdefcal?: Calculation[];      // For CALC type
  testdefgrp?: GroupMember[];      // For GROUP type
  testmap?: TestMapping[];         // For all types
  testdeftech?: TechDetail[];      // For TEST/PARAM
  refnum?: RefNumRange[];          // For TEST/PARAM (numeric ref)
  reftxt?: RefTxtRange[];          // For TEST/PARAM (text ref)
}

interface RefNumRange {
  RefNumID: number;
  NumRefType: string;              // REF, CRTC, VAL, RERUN
  NumRefTypeLabel: string;
  RangeType: string;               // RANGE, THOLD
  RangeTypeLabel: string;
  Sex: string;                     // 0=All, 1=Female, 2=Male
  SexLabel: string;
  AgeStart: number;
  AgeEnd: number;
  LowSign?: string;                // =, <, <=
  LowSignLabel?: string;
  Low?: number;
  HighSign?: string;               // =, >, >=
  HighSignLabel?: string;
  High?: number;
  Flag?: string;                   // H, L, A, etc.
  Interpretation?: string;
}

interface RefTxtRange {
  RefTxtID: number;
  TxtRefType: string;              // Normal, Abnormal, Critical
  TxtRefTypeLabel: string;
  Sex: string;
  SexLabel: string;
  AgeStart: number;
  AgeEnd: number;
  RefTxt: string;
  Flag?: string;
}

3. Create Test

POST /api/tests
Content-Type: application/json
Body: CreateTestPayload

Request:

interface CreateTestPayload {
  SiteID: number;
  TestSiteCode: string;
  TestSiteName: string;
  TestType: 'TEST' | 'PARAM' | 'CALC' | 'GROUP' | 'TITLE';
  Description?: string;
  SeqScr?: number;
  SeqRpt?: number;
  VisibleScr?: number;
  VisibleRpt?: number;
  CountStat?: number;
  StartDate?: string;

  // Nested details (based on TestType)
  details?: {
    // Technical (TEST/PARAM/CALC)
    DisciplineID?: number;
    DepartmentID?: number;
    ResultType?: string;
    RefType?: string;
    VSet?: string;
    Unit1?: string;
    Factor?: number;
    Unit2?: string;
    Decimal?: number;
    ReqQty?: number;
    ReqQtyUnit?: string;
    CollReq?: string;
    Method?: string;
    ExpectedTAT?: number;

    // CALC only
    FormulaInput?: string;
    FormulaCode?: string;

    // GROUP only
    members?: number[];  // Array of TestSiteIDs
  };

  // Reference ranges (TEST/PARAM)
  refnum?: Omit<RefNumRange, 'RefNumID' | 'NumRefTypeLabel' | 'RangeTypeLabel' | 'SexLabel' | 'LowSignLabel' | 'HighSignLabel'>[];
  reftxt?: Omit<RefTxtRange, 'RefTxtID' | 'TxtRefTypeLabel' | 'SexLabel'>[];

  // Mappings (all types)
  testmap?: TestMapping[];
}

Response:

{
  status: "created";
  message: "Test created successfully";
  data: { TestSiteId: number };
}

4. Update Test

PATCH /api/tests
Content-Type: application/json
Body: CreateTestPayload & { TestSiteID: number }

Response:

{
  status: "success";
  message: "Test updated successfully";
  data: { TestSiteId: number };
}

5. Delete Test (Soft Delete)

DELETE /api/tests
Content-Type: application/json
Body: { TestSiteID: number }

Response:

{
  status: "success";
  message: "Test disabled successfully";
  data: { TestSiteId: number; EndDate: string };
}

🎨 UI/UX Design Specifications

Layout Architecture

Page Layout: Fixed sidebar + scrollable content area

┌─────────────────────────────────────────────────────────────┐
│  Header: CLQMS Test Management              [User] [Logout] │
├────────────┬────────────────────────────────────────────────┤
│            │                                                │
│  Sidebar  │              Main Content Area                 │
│  (Left)   │                                                │
│            │                                                │
│  Tab 1    │  ┌──────────────────────────────────────────┐  │
│  Tab 2    │  │                                          │  │
│  Tab 3    │  │        Dynamic Content                    │  │
│  ...      │  │                                          │  │
│            │  │                                          │  │
│            │  │                                          │  │
│            │  └──────────────────────────────────────────┘  │
│            │                                                │
│            │  [Save] [Cancel] [Delete]                      │
│            │                                                │
└────────────┴────────────────────────────────────────────────┘

Test List Page

Components:

  1. Filter Panel (top of page):

    • Site dropdown (multi-select)
    • Test Type dropdown (checkboxes)
    • Visibility toggles (Screen/Report)
    • Search input (debounced, 300ms)
    • Clear filters button
  2. Test Table/Card Grid:

    • Columns: Code, Name, Type, Discipline, Department, SeqScr, SeqRpt, Visibility, Actions
    • Sortable headers (Code, Name, SeqScr, SeqRpt)
    • Row actions: View, Edit, Delete (with confirmation)
    • Row hover effect
    • Test type badge with color coding
  3. Pagination:

    • Show 20 items per page
    • Page navigation buttons
    • Page size selector (10, 20, 50, 100)
    • Total count display

Table Design:

┌─────────┬────────────────────┬───────┬────────────┬────────┬────────┬────────┬────────────┬─────────┐
│ Code    │ Name               │ Type  │ Discipline │ Dept   │ ScrVis │ RptVis │ Actions    │         │
├─────────┼────────────────────┼───────┼────────────┼────────┼────────┼────────┼────────────┼─────────┤
│ CBC     │ Complete Blood     │ TEST  │ Hematology │ Hema   │ ☑      │ ☑      │ 👁️ ✏️ 🗑️ │         │
│         │ Count              │       │            │        │        │        │            │         │
│ HGB     │ Hemoglobin         │ PARAM │ Hematology │ Hema   │ ☑      │ ☑      │ 👁️ ✏️ 🗑️ │         │
│ CALC_A1C│ A1C Calculated    │ CALC  │ Chemistry  │ Chem   │ ☑      │ ☑      │ 👁️ ✏️ 🗑️ │         │
│ CMP_GRP │ Comprehensive     │ GROUP │ -          │ -      │ ☑      │ ☐      │ 👁️ ✏️ 🗑️ │         │
│         │ Panel              │       │            │        │        │        │            │         │
│ HEADER1 │ Chemistry Results  │ TITLE │ -          │ -      │ ☐      │ ☑      │ 👁️ ✏️ 🗑️ │         │
└─────────┴────────────────────┴───────┴────────────┴────────┴────────┴────────┴────────────┴─────────┘

Test Form Page

Left Sidebar Tabs (Navigation):

┌──────────────┐
│ Basic Info   │
├──────────────┤
│ Tech Details │
├──────────────┤
│ Calculations │ (CALC only)
├──────────────┤
│ Group Memb   │ (GROUP only)
├──────────────┤
│ Mappings     │
├──────────────┤
│ Ref Num      │ (TEST/PARAM/CALC)
├──────────────┤
│ Ref Txt      │ (TEST/PARAM)
└──────────────┘

Tab Visibility Rules:

Tab TEST PARAM CALC GROUP TITLE
Basic Info
Tech Details
Calculations
Group Members
Mappings
Ref Num
Ref Txt

Active Tab Styling:

  • Left border accent color (primary theme color)
  • Light background highlight
  • Bold text
  • Icon indicator

Tab Content Specifications

1. Basic Info Tab

Form Layout (Two-column grid):

┌──────────────────────────────────────────────────────────────────┐
│  Test Code:    [CBC_______]    *Required                     │
│  Test Name:    [Complete Blood Count________]    *Required    │
│  Test Type:    [TEST ▼]      (dropdown)                         │
│  Description:  [Standard hematology test_______________]         │
│                                                               │
│  Site:        [Main Lab ▼]                                     │
│                                                               │
│  Screen Seq:  [1___]  Report Seq:  [1___]                     │
│                                                               │
│  [☑] Visible on Screen    [☑] Visible on Report               │
│                                                               │
│  [☑] Count in Statistics                                     │
│                                                               │
│  Start Date:  [2024-01-01_______]                             │
└──────────────────────────────────────────────────────────────────┘

Fields:

  • TestSiteCode (required, 3-6 chars, uppercase, unique validation)
  • TestSiteName (required, 3-255 chars)
  • TestType (required, dropdown: TEST, PARAM, CALC, GROUP, TITLE)
  • Description (optional, textarea, max 500 chars)
  • SiteID (required, dropdown from sites API)
  • SeqScr (optional, number, default 0)
  • SeqRpt (optional, number, default 0)
  • VisibleScr (checkbox, default true)
  • VisibleRpt (checkbox, default true)
  • CountStat (checkbox, default true)
  • StartDate (optional, datetime, default current)

Dynamic Behavior:

  • When TestType changes → Show/hide relevant tabs
  • When TestType = CALC/PARAM/TEST → Auto-populate defaults
  • TestType change triggers confirmation if form has unsaved changes

2. Tech Details Tab

Form Layout (Three sections):

Section 1: Categorization

┌──────────────────────────────────────────────────────────────┐
│  Discipline:   [Hematology ▼]                             │
│  Department:  [CBC Dept ▼]                                │
└──────────────────────────────────────────────────────────────┘

Section 2: Result Configuration

┌──────────────────────────────────────────────────────────────┐
│  Result Type:  [Numeric ▼]   (dynamic based on TestType)  │
│  Ref Type:     [Range ▼]     (dynamic based on ResultType) │
│  Value Set:    [____________] (if ResultType = VSET)       │
└──────────────────────────────────────────────────────────────┘

Section 3: Units & Conversion

┌──────────────────────────────────────────────────────────────┐
│  Unit 1:       [g/dL ▼]                                 │
│  Factor:       [1.0__]   (conversion factor)               │
│  Unit 2:       [g/L ▼]                                   │
│  Decimal:      [2__]     (decimal places)                 │
└──────────────────────────────────────────────────────────────┘

Section 4: Sample Requirements

┌──────────────────────────────────────────────────────────────┐
│  Required Qty: [5.0__]                                  │
│  Qty Unit:     [mL ▼]                                    │
│  Collection Req: [Fasting required_______________]        │
└──────────────────────────────────────────────────────────────┘

Section 5: Method & TAT

┌──────────────────────────────────────────────────────────────┐
│  Method:       [Automated Analyzer_______________]          │
│  Expected TAT: [60__]   (minutes)                        │
└──────────────────────────────────────────────────────────────┘

Fields:

  • DisciplineID (dropdown, optional)
  • DepartmentID (dropdown, optional)
  • ResultType (dropdown, dynamic options based on TestType)
  • RefType (dropdown, dynamic options based on ResultType)
  • VSet (text input, shown only if ResultType = VSET)
  • Unit1 (dropdown from units valueset)
  • Factor (number, optional)
  • Unit2 (dropdown from units valueset)
  • Decimal (number, default 2, min 0, max 6)
  • ReqQty (number, optional)
  • ReqQtyUnit (dropdown)
  • CollReq (textarea, optional)
  • Method (text input, optional)
  • ExpectedTAT (number, optional)

Dynamic Dropdown Logic:

// TestType → ResultType mapping
const getResultTypeOptions = (testType: string) => {
  switch (testType) {
    case 'CALC': return ['NMRIC'];
    case 'GROUP':
    case 'TITLE': return ['NORES'];
    default: return ['NMRIC', 'RANGE', 'TEXT', 'VSET'];
  }
};

// ResultType → RefType mapping
const getRefTypeOptions = (resultType: string) => {
  switch (resultType) {
    case 'NMRIC':
    case 'RANGE': return ['RANGE', 'THOLD'];
    case 'VSET': return ['VSET'];
    case 'TEXT': return ['TEXT'];
    case 'NORES': return ['NOREF'];
    default: return [];
  }
};

3. Calculations Tab (CALC only)

Form Layout:

┌──────────────────────────────────────────────────────────────┐
│  Formula Input:  [HGB + MCV + MCHC________]               │
│                                                              │
│  Formula Code:   [{HGB} + {MCV} + {MCHC}_____________]     │
│                                                              │
│  Discipline:    [Hematology ▼]                               │
│  Department:    [CBC Dept ▼]                                  │
│                                                              │
│  Method:        [Calculated from components_______]            │
│                                                              │
│  Unit 1:        [g/dL ▼]                                    │
│  Factor:        [1.0__]                                      │
│  Unit 2:        [g/L ▼]                                     │
│  Decimal:       [2__]                                        │
│                                                              │
│  Ref Type:      [Range ▼]                                    │
└──────────────────────────────────────────────────────────────┘

Fields:

  • FormulaInput (text input, description of calculation)
  • FormulaCode (text input, actual formula with placeholders like {A}, {B})
  • DisciplineID (dropdown)
  • DepartmentID (dropdown)
  • Method (text input)
  • Unit1, Factor, Unit2, Decimal (same as Tech Details)
  • RefType (dropdown: RANGE, THOLD)

Validation:

  • FormulaCode must contain valid syntax
  • FormulaCode must reference valid test codes
  • Test codes in formula must exist in system

4. Group Members Tab (GROUP only)

Form Layout:

┌──────────────────────────────────────────────────────────────┐
│  Group: CBC - Complete Blood Count                          │
│                                                              │
│  Current Members:                                            │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Code │ Name            │ Type   │ Seq │ Actions       │   │
│  ├──────┼─────────────────┼────────┼─────┼───────────────┤   │
│  │ HGB  │ Hemoglobin      │ PARAM  │ 1   │ [↑] [↓] [✕] │   │
│  │ RBC  │ Red Blood Cells │ TEST   │ 2   │ [↑] [↓] [✕] │   │
│  │ WBC  │ White Blood Cell│ TEST   │ 3   │ [↑] [↓] [✕] │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  Add Member: [HGB ▼]  [Add Member +]                        │
│                                                              │
│  Available Tests: (searchable dropdown)                         │
│  - HGB - Hemoglobin                                           │
│  - RBC - Red Blood Cells                                     │
│  - WBC - White Blood Cells                                    │
└──────────────────────────────────────────────────────────────┘

Features:

  • List current members with Code, Name, Type
  • Reorder members (drag-and-drop or ↑↓ buttons)
  • Remove member button (with confirmation)
  • Add member dropdown (searchable, excludes current group and members)
  • Prevent circular references (group cannot contain itself)
  • Prevent duplicate members

Member Selection:

  • Dropdown with search
  • Group by TestType (TEST, PARAM, CALC)
  • Show TestSiteCode - TestSiteName format
  • Filter out already added members
  • Filter out current group itself

5. Mappings Tab (All test types)

Form Layout:

┌──────────────────────────────────────────────────────────────┐
│  Test: CBC - Complete Blood Count                          │
│                                                              │
│  Current Mappings:                                          │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Host │ Host Code │ Client │ Client Code │ Actions   │   │
│  ├──────┼───────────┼────────┼────────────┼───────────┤   │
│  │ HIS   │ CBC       │ WST-1  │ CBC01      │ [✏️] [✕] │   │
│  │ SITE  │ CBC       │ INST-1  │ CBC        │ [✏️] [✕] │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  [Add Mapping +]                                             │
└──────────────────────────────────────────────────────────────┘

Add/Edit Mapping Modal:

┌──────────────────────────────────────────────────────────────┐
│  Add/Edit Mapping                                          │
│                                                              │
│  Host System:                                               │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Type:     [HIS ▼]                                    │   │
│  │ ID:       [1____]                                     │   │
│  │ Data Src: [DB____]                                    │   │
│  │ Test Code:[CBC____]                                    │   │
│  │ Test Name:[Complete Blood Count___________]              │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  Client System:                                             │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Type:       [WST ▼]                                │   │
│  │ ID:         [1____]                                  │   │
│  │ Data Src:   [API____]                                │   │
│  │ Container: [Tube1 ▼]                                │   │
│  │ Test Code:  [CBC01____]                               │   │
│  │ Test Name:  [CBC_____________]                         │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│                    [Save] [Cancel]                           │
└──────────────────────────────────────────────────────────────┘

Fields:

  • HostType: dropdown (HIS, SITE, WST, INST)
  • HostID: text/number input
  • HostDataSource: text input
  • HostTestCode: text input
  • HostTestName: text input
  • ClientType: dropdown (HIS, SITE, WST, INST)
  • ClientID: text/number input
  • ClientDataSource: text input
  • ConDefID: dropdown (container definitions)
  • ClientTestCode: text input
  • ClientTestName: text input

Validation:

  • At least one of Host or Client must be specified
  • Test codes must be unique per Host/Client combination

6. Ref Num Tab (Numeric Reference Ranges)

Form Layout:

┌──────────────────────────────────────────────────────────────┐
│  Test: CBC - Complete Blood Count                          │
│  Numeric Reference Ranges                                    │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐   │
│  │Type   │Range │Sex │ Age   │ Low      │High     │Flag │   │
│  │       │      │    │       │Bound     │Bound    │     │   │
│  ├───────┼──────┼────┼───────┼──────────┼─────────┼─────┤   │
│  │Ref    │RANGE │All │0-150  │[≥ 4.0]  │[< 5.5]  │ N   │   │
│  │       │      │    │       │          │         │     │   │
│  │Crtc   │THOLD │All │0-150  │[< 3.5]  │[> 6.0]  │ H/L │   │
│  │       │      │    │       │          │         │     │   │
│  │Ref    │RANGE │F   │18-150 │[≥ 4.5]  │[< 5.0]  │ N   │   │
│  │       │      │    │       │          │         │     │   │
│  │Crtc   │THOLD │M   │18-150 │[< 3.8]  │[> 5.8]  │ H/L │   │
│  │       │      │    │       │          │         │     │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  [Add Range] [Delete Selected] [Copy from Similar Test]        │
│                                                              │
│  Selected Ranges: 0                                          │
└──────────────────────────────────────────────────────────────┘

Add/Edit Reference Range Modal:

┌──────────────────────────────────────────────────────────────┐
│  Numeric Reference Range                                    │
│                                                              │
│  Reference Type:  [Reference ▼]                            │
│                    (Reference, Critical, Validation, Rerun)   │
│                                                              │
│  Range Type:     [Range ▼]                                 │
│                    (Range, Threshold)                        │
│                                                              │
│  Sex:            [All ▼]                                   │
│                    (All, Female, Male)                       │
│                                                              │
│  Age Start:      [0__]  Age End: [150__]                    │
│                                                              │
│  Low Bound:                                                  │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Sign:  [≥ ▼]    Value: [4.0__]                    │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  High Bound:                                                 │
│  ┌────────────────────────────────────────────────────────┐   │
│  │ Sign:  [< ▼]    Value: [5.5__]                    │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  Flag:          [H/L/A/N___]   (High/Low/Abnormal/Normal)   │
│                                                              │
│  Interpretation:[Normal range for hemoglobin_______________]   │
│                                                              │
│                    [Save] [Cancel]                           │
└──────────────────────────────────────────────────────────────┘

Fields:

  • NumRefType: dropdown (REF, CRTC, VAL, RERUN)
  • RangeType: dropdown (RANGE, THOLD)
  • Sex: dropdown (0=All, 1=Female, 2=Male)
  • AgeStart: number input (min 0, max 150)
  • AgeEnd: number input (min 0, max 150)
  • LowSign: dropdown (=, <, <=)
  • Low: number input (optional)
  • HighSign: dropdown (=, >, >=)
  • High: number input (optional)
  • Flag: text input (single char: H, L, A, N)
  • Interpretation: textarea (optional)

Validation:

  • AgeStart must be less than AgeEnd
  • If both Low and High are present: Low must be less than High
  • LowSign must be appropriate for Low value (e.g., if Low = 4.0, LowSign should be >=)
  • HighSign must be appropriate for High value (e.g., if High = 5.5, HighSign should be <=)

Reference Range Logic:

  • Reference (REF): Normal ranges for reporting
  • Critical (CRTC): Critical values requiring immediate notification
  • Validation (VAL): Validation checks for result entry
  • Rerun (RERUN): Conditions triggering automatic rerun

Range Type:

  • RANGE: Standard range (Low to High)
  • THOLD: Threshold (single value with comparison)

7. Ref Txt Tab (Text Reference Ranges)

Form Layout:

┌──────────────────────────────────────────────────────────────┐
│  Test: URINE - Urinalysis                                  │
│  Text Reference Ranges                                      │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐   │
│  │Type   │Sex │ Age   │ Reference Text   │ Flag        │   │
│  ├───────┼────┼───────┼─────────────────┼─────────────┤   │
│  │Normal │All │0-150  │Clear            │ N           │   │
│  │       │    │       │                 │             │   │
│  │Abnml  │All │0-150  │Cloudy           │ A           │   │
│  │       │    │       │                 │             │   │
│  │Crtc   │All │0-150  │Bloody           │ C           │   │
│  │       │    │       │                 │             │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                              │
│  [Add Range] [Delete Selected] [Copy from Similar Test]        │
└──────────────────────────────────────────────────────────────┘

Add/Edit Text Reference Modal:

┌──────────────────────────────────────────────────────────────┐
│  Text Reference Range                                      │
│                                                              │
│  Reference Type:  [Normal ▼]                              │
│                    (Normal, Abnormal, Critical)              │
│                                                              │
│  Sex:            [All ▼]                                   │
│                    (All, Female, Male)                       │
│                                                              │
│  Age Start:      [0__]  Age End: [150__]                    │
│                                                              │
│  Reference Text: [Clear_______________]                        │
│                                                              │
│  Flag:           [N/A/C___]    (Normal/Abnormal/Critical)    │
│                                                              │
│                    [Save] [Cancel]                           │
└──────────────────────────────────────────────────────────────┘

Fields:

  • TxtRefType: dropdown (Normal, Abnormal, Critical)
  • Sex: dropdown (0=All, 1=Female, 2=Male)
  • AgeStart: number input
  • AgeEnd: number input
  • RefTxt: text input
  • Flag: text input (N, A, C)

Validation:

  • AgeStart must be less than AgeEnd
  • RefTxt is required
  • Flag must match TxtRefType (Normal=N, Abnormal=A, Critical=C)

🏗️ Component Implementation

Core Component Requirements

1. State Management with Svelte 5 Runes

testStore.ts:

import { writable } from 'svelte/store';

interface TestFormState {
  TestSiteID?: number;
  TestSiteCode: string;
  TestSiteName: string;
  TestType: 'TEST' | 'PARAM' | 'CALC' | 'GROUP' | 'TITLE';
  Description?: string;
  SiteID: number;
  SeqScr: number;
  SeqRpt: number;
  VisibleScr: number;
  VisibleRpt: number;
  CountStat: number;
  StartDate?: string;
  details?: {
    DisciplineID?: number;
    DepartmentID?: number;
    ResultType?: string;
    RefType?: string;
    VSet?: string;
    Unit1?: string;
    Factor?: number;
    Unit2?: string;
    Decimal?: number;
    ReqQty?: number;
    ReqQtyUnit?: string;
    CollReq?: string;
    Method?: string;
    ExpectedTAT?: number;
    FormulaInput?: string;
    FormulaCode?: string;
    members?: number[];
  };
  refnum?: RefNumRange[];
  reftxt?: RefTxtRange[];
  testmap?: TestMapping[];
}

export const testStore = writable<TestFormState>({
  TestSiteCode: '',
  TestSiteName: '',
  TestType: 'TEST',
  SiteID: 1,
  SeqScr: 0,
  SeqRpt: 0,
  VisibleScr: 1,
  VisibleRpt: 1,
  CountStat: 1,
  refnum: [],
  reftxt: [],
  testmap: [],
});

Derived Stores:

// Derive visible tabs based on TestType
export const visibleTabs = derived(testStore, ($store) => {
  const type = $store.TestType;
  return allTabs.filter(tab => tab.isVisible(type));
});

// Derive valid ResultType options
export const validResultTypes = derived(testStore, ($store) => {
  const type = $store.TestType;
  // Return options based on type
});

// Derive valid RefType options
export const validRefTypes = derived([testStore, validResultTypes], ([$store, $resultTypes]) => {
  const resultType = $store.details?.ResultType;
  // Return options based on resultType
});

2. Reusable UI Components

Button.svelte:

<script lang="ts">
  export let variant: 'primary' | 'secondary' | 'danger' | 'ghost' = 'primary';
  export let size: 'sm' | 'md' | 'lg' = 'md';
  export let disabled = false;
  export let loading = false;
  export let type: 'button' | 'submit' | 'reset' = 'button';
</script>

<button
  {type}
  {disabled}
  class="btn btn-{variant} btn-{size}"
  disabled={disabled || loading}
>
  {#if loading}
    <Spinner />
  {:else}
    <slot />
  {/if}
</button>

Input.svelte:

<script lang="ts">
  export let label = '';
  export let value = '';
  export let type = 'text';
  export let required = false;
  export let error = '';
  export let placeholder = '';
  export let disabled = false;
</script>

<div class="input-group">
  {#if label}
    <label class="input-label">
      {label}
      {#if required}<span class="required">*</span>{/if}
    </label>
  {/if}
  <input
    {type}
    {value}
    {placeholder}
    {disabled}
    {required}
    class="input-field"
    class:has-error={error}
  />
  {#if error}
    <span class="input-error">{error}</span>
  {/if}
</div>

Select.svelte:

<script lang="ts">
  export let label = '';
  export let value = '';
  export let options: Array<{value: string, label: string}> = [];
  export let required = false;
  export let disabled = false;
  export let placeholder = 'Select...';
</script>

<div class="select-group">
  {#if label}
    <label class="select-label">
      {label}
      {#if required}<span class="required">*</span>{/if}
    </label>
  {/if}
  <select
    bind:value
    {disabled}
    {required}
    class="select-field"
  >
    <option value="">{placeholder}</option>
    {#each options as option}
      <option {value}>{option.label}</option>
    {/each}
  </select>
</div>

3. Test Form Component

TestForm.svelte (Main container):

<script lang="ts">
  import { onMount } from 'svelte';
  import { testStore, visibleTabs } from '$lib/stores/testStore';
  import SidebarTabs from '$lib/components/test/SidebarTabs.svelte';
  import BasicInfoTab from '$lib/components/test/tabs/BasicInfoTab.svelte';
  // ... other tabs

  let currentTab = 'basic';
  let isLoading = false;
  let isDirty = false;

  onMount(async () => {
    // Load existing test if editing
    const id = params.id;
    if (id) {
      isLoading = true;
      try {
        const data = await testService.getById(id);
        testStore.set(data);
      } catch (error) {
        // Handle error
      } finally {
        isLoading = false;
      }
    }
  });

  const handleTabChange = (tabId: string) => {
    if (isDirty) {
      // Confirm before changing tab if unsaved changes
      if (!confirm('You have unsaved changes. Continue?')) {
        return;
      }
    }
    currentTab = tabId;
  };

  const handleSave = async () => {
    // Validate form
    // Call API
    // Handle response
  };
</script>

<div class="test-form-layout">
  <SidebarTabs
    tabs={$visibleTabs}
    currentTab={currentTab}
    onTabChange={handleTabChange}
  />

  <main class="test-form-content">
    {#if isLoading}
      <Spinner />
    {:else}
      <h2 class="form-title">
        {$testStore.TestSiteID ? 'Edit Test' : 'New Test'}
      </h2>

      {#if currentTab === 'basic'}
        <BasicInfoTab bind:dirty={isDirty} />
      {:else if currentTab === 'tech'}
        <TechDetailsTab bind:dirty={isDirty} />
      {:else if currentTab === 'calc'}
        <CalcDetailsTab bind:dirty={isDirty} />
      {:else if currentTab === 'group'}
        <GroupMembersTab bind:dirty={isDirty} />
      {:else if currentTab === 'mappings'}
        <MappingsTab bind:dirty={isDirty} />
      {:else if currentTab === 'refnum'}
        <RefNumTab bind:dirty={isDirty} />
      {:else if currentTab === 'reftxt'}
        <RefTxtTab bind:dirty={isDirty} />
      {/if}

      <div class="form-actions">
        <Button variant="primary" on:click={handleSave}>
          Save
        </Button>
        <Button variant="secondary" on:click={() => navigate('/tests')}>
          Cancel
        </Button>
        {#if $testStore.TestSiteID}
          <Button variant="danger" on:click={handleDelete}>
            Delete
          </Button>
        {/if}
      </div>
    {/if}
  </main>
</div>

Validation Requirements

Frontend Validation

TestSiteCode

  • Required
  • 3-6 characters
  • Uppercase only
  • Alphanumeric only
  • Unique (check via API)
  • Regex: ^[A-Z0-9]{3,6}$

TestSiteName

  • Required
  • 3-255 characters
  • No special characters (except hyphen, space, parenthesis)
  • Regex: ^[a-zA-Z0-9\s\-\(\)]{3,255}$

TestType

  • Required
  • Must be one of: TEST, PARAM, CALC, GROUP, TITLE

Type Combination Validation

const validateTypeCombination = (testType: string, resultType: string, refType: string) => {
  const valid = TestValidationService.validate(testType, resultType, refType);
  if (!valid.valid) {
    throw new Error(valid.error);
  }
};

Reference Range Validation

Numeric Ranges:

  • AgeStart < AgeEnd (both 0-150)
  • If Low and High both present: Low < High
  • LowSign appropriate for Low value
  • HighSign appropriate for High value
  • Flag is single character (H, L, A, N)

Text Ranges:

  • AgeStart < AgeEnd
  • RefTxt is required
  • Flag matches TxtRefType

Group Validation

  • Group cannot contain itself
  • No circular references (Group A contains Group B, Group B contains Group A)
  • No duplicate members
  • Minimum 1 member for GROUP type

🎨 Styling & Design System

Color Palette

:root {
  /* Primary */
  --primary-50: #e0f2fe;
  --primary-100: #bae6fd;
  --primary-500: #0ea5e9;
  --primary-600: #0284c7;
  --primary-700: #0369a1;

  /* Secondary */
  --secondary-500: #64748b;
  --secondary-600: #475569;

  /* Success */
  --success-500: #22c55e;
  --success-600: #16a34a;

  /* Danger */
  --danger-500: #ef4444;
  --danger-600: #dc2626;

  /* Warning */
  --warning-500: #f59e0b;
  --warning-600: #d97706;

  /* Neutral */
  --gray-50: #f9fafb;
  --gray-100: #f3f4f6;
  --gray-200: #e5e7eb;
  --gray-300: #d1d5db;
  --gray-500: #6b7280;
  --gray-700: #374151;
  --gray-900: #111827;
}

Typography

/* Font sizes */
--text-xs: 0.75rem;   /* 12px */
--text-sm: 0.875rem;  /* 14px */
--text-base: 1rem;     /* 16px */
--text-lg: 1.125rem;   /* 18px */
--text-xl: 1.25rem;    /* 20px */
--text-2xl: 1.5rem;   /* 24px */
--text-3xl: 1.875rem; /* 30px */

Spacing

--space-1: 0.25rem;  /* 4px */
--space-2: 0.5rem;   /* 8px */
--space-3: 0.75rem;  /* 12px */
--space-4: 1rem;     /* 16px */
--space-6: 1.5rem;   /* 24px */
--space-8: 2rem;     /* 32px */

Components

Buttons:

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-4);
  border-radius: 0.375rem;
  font-weight: 500;
  transition: all 0.2s;
  cursor: pointer;
}

.btn-primary {
  background-color: var(--primary-600);
  color: white;
}

.btn-primary:hover:not(:disabled) {
  background-color: var(--primary-700);
}

.btn-secondary {
  background-color: var(--gray-200);
  color: var(--gray-700);
}

.btn-danger {
  background-color: var(--danger-600);
  color: white;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

Inputs:

.input-field {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--gray-300);
  border-radius: 0.375rem;
  font-size: var(--text-base);
  transition: border-color 0.2s;
}

.input-field:focus {
  outline: none;
  border-color: var(--primary-500);
  box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}

.input-field.has-error {
  border-color: var(--danger-500);
}

Sidebar Tabs:

.sidebar-tab {
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid transparent;
  cursor: pointer;
  transition: all 0.2s;
}

.sidebar-tab:hover {
  background-color: var(--gray-100);
}

.sidebar-tab.active {
  background-color: var(--primary-50);
  border-left-color: var(--primary-600);
  font-weight: 600;
}

📱 Responsive Design

Breakpoints

--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;

Responsive Behavior

Desktop (> 1024px):

  • Sidebar: Fixed width, visible
  • Content: Full width
  • Form: 2-column grid layout

Tablet (768px - 1024px):

  • Sidebar: Collapsible (hamburger menu)
  • Content: Full width
  • Form: Single column layout
  • Table: Horizontal scroll

Mobile (< 768px):

  • Sidebar: Off-canvas drawer
  • Content: Full width
  • Form: Single column, stacked
  • Table: Card view instead of table

🚀 Implementation Checklist

Phase 1: Project Setup & Infrastructure

  • Initialize SvelteKit project with TypeScript
  • Install and configure Tailwind CSS
  • Set up Skeleton UI or Melt UI
  • Configure Axios with interceptors
  • Create type definitions (test.types.ts, api.types.ts)
  • Set up API service layer
  • Create auth store and testStore
  • Set up routing structure

Phase 2: Reusable Components

  • Button component
  • Input component
  • Select component
  • Checkbox component
  • Table component
  • Modal component
  • Badge component
  • Alert component
  • Spinner component
  • Tabs component

Phase 3: Test List Page

  • Test list page layout
  • Filter panel component
  • Test table component
  • Pagination component
  • Search functionality
  • Filter functionality
  • Sort functionality
  • Load test data from API

Phase 4: Test Form - Basic & Tech

  • Test form container with sidebar tabs
  • Basic Info tab
  • Tech Details tab
  • Dynamic dropdown logic
  • Form validation
  • Save functionality
  • Update functionality

Phase 5: Type-Specific Tabs

  • Calculations tab (CALC)
  • Group Members tab (GROUP)
  • Mappings tab (all types)
  • Member selection dropdown
  • Mapping add/edit modal

Phase 6: Reference Ranges

  • RefNum tab with table
  • RefTxt tab with table
  • Reference range modal
  • Reference range validation
  • Add/Edit/Delete operations

Phase 7: Polish & Testing

  • Responsive design
  • Loading states
  • Error handling
  • Form dirty state tracking
  • Confirmation dialogs
  • Toast notifications
  • Accessibility (ARIA labels)
  • Keyboard navigation
  • Cross-browser testing

Phase 8: Documentation

  • Component documentation
  • API integration guide
  • User guide
  • Deployment instructions

📚 Additional Notes

ValueSet Integration

  • Use backend API /api/valueset to fetch dropdown options
  • Cache valuesets locally to reduce API calls
  • Transform labels: API returns both code and label (e.g., TestType and TestTypeLabel)
  • Display labels in UI, use codes for API calls

Authentication

  • JWT token stored in localStorage
  • Include token in Authorization header for all API calls
  • Handle token expiration and refresh
  • Redirect to login if unauthorized

Error Handling

  • Display user-friendly error messages
  • Log technical errors to console
  • Retry logic for failed requests (with backoff)
  • Show appropriate feedback for network errors

Performance

  • Implement debounced search (300ms)
  • Lazy load test data (pagination)
  • Optimize re-renders with Svelte 5 runes
  • Memoize expensive computations

Accessibility

  • ARIA labels for form inputs
  • Keyboard navigation support
  • Screen reader compatibility
  • Focus management in modals
  • Color contrast compliance (WCAG AA)

🔗 References

Backend Documentation

  • API Endpoints: /api/tests
  • Models: TestDefSiteModel, TestDefCalModel, TestDefGrpModel, TestMapModel
  • Validation: TestValidationService

Type System

  • Test types: TEST, PARAM, CALC, GROUP, TITLE
  • Result types: NMRIC, RANGE, TEXT, VSET, NORES
  • Reference types: RANGE, THOLD, TEXT, VSET, NOREF

Business Rules

  • Soft deletes only (set EndDate)
  • Test type + ResultType + RefType must be valid combination
  • Reference ranges are type-specific (numeric vs text)
  • Calculations use formula placeholders like {A}, {B}

🎯 Success Criteria

The frontend is considered complete when:

  1. Functional Requirements

    • All CRUD operations work for all test types
    • Reference ranges can be managed (add/edit/delete)
    • Group members can be added/removed/reordered
    • Mappings can be configured for external systems
    • Form validation prevents invalid data submission
  2. User Experience

    • Intuitive navigation with sidebar tabs
    • Clear visual feedback for actions
    • Responsive design works on all devices
    • Loading states indicate progress
    • Error messages are helpful and actionable
  3. Code Quality

    • TypeScript strict mode with no errors
    • Component reusability and modularity
    • Proper error handling throughout
    • Clean, readable code with comments
    • Efficient state management with Svelte 5 runes
  4. Performance

    • Page load time < 2 seconds
    • Search results appear within 300ms
    • Form submissions complete within 1 second
    • No memory leaks or performance degradation
  5. Testing

    • Unit tests for components
    • Integration tests for API calls
    • E2E tests for critical user flows
    • Cross-browser compatibility verified

📞 Support

For questions or issues during development:

  1. Review backend API documentation in README.md
  2. Check model definitions in app/Models/Test/
  3. Refer to validation service in app/Libraries/TestValidationService.php
  4. Test API endpoints directly using tools like Postman

Last Updated: February 2025 Version: 1.0