clqms-fe1/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md

1582 lines
57 KiB
Markdown
Raw Normal View History

# 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)
### Recommended Dependencies
```json
{
"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**:
```typescript
{
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**:
```typescript
{
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**:
```typescript
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**:
```typescript
{
status: "created";
message: "Test created successfully";
data: { TestSiteId: number };
}
```
#### 4. Update Test
```
PATCH /api/tests
Content-Type: application/json
Body: CreateTestPayload & { TestSiteID: number }
```
**Response**:
```typescript
{
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**:
```typescript
{
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**:
```typescript
// 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**:
```typescript
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**:
```typescript
// 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**:
```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**:
```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**:
```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):
```svelte
<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
```typescript
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
```css
: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
```css
/* 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
```css
--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**:
```css
.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**:
```css
.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**:
```css
.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
```css
--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