From 52d4fc73222b8acaac8841aec766f5296acfe6f8 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Fri, 20 Feb 2026 16:49:34 +0700 Subject: [PATCH] feat(tests): add modals directory and update test management components --- docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md | 1581 ----------------- src/lib/api/tests.js | 4 +- .../(app)/master-data/tests/+page.svelte | 14 + .../tests/test-modal/TestFormModal.svelte | 39 +- .../modals/TestTypePickerModal.svelte | 68 + .../tests/test-modal/tabs/BasicInfoTab.svelte | 110 +- .../tests/test-modal/tabs/RefNumTab.svelte | 725 ++++---- .../tests/test-modal/tabs/RefTxtTab.svelte | 148 +- .../test-modal/tabs/TechDetailsTab.svelte | 591 +++++- 9 files changed, 1262 insertions(+), 2018 deletions(-) delete mode 100644 docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md create mode 100644 src/routes/(app)/master-data/tests/test-modal/modals/TestTypePickerModal.svelte diff --git a/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md b/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md deleted file mode 100644 index 4f51eb0..0000000 --- a/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md +++ /dev/null @@ -1,1581 +0,0 @@ -# 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[]; - reftxt?: Omit[]; - - // 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({ - 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 - - - -``` - -**Input.svelte**: -```svelte - - -
- {#if label} - - {/if} - - {#if error} - {error} - {/if} -
-``` - -**Select.svelte**: -```svelte - - -
- {#if label} - - {/if} - -
-``` - -#### 3. Test Form Component - -**TestForm.svelte** (Main container): -```svelte - - -
- - -
- {#if isLoading} - - {:else} -

- {$testStore.TestSiteID ? 'Edit Test' : 'New Test'} -

- - {#if currentTab === 'basic'} - - {:else if currentTab === 'tech'} - - {:else if currentTab === 'calc'} - - {:else if currentTab === 'group'} - - {:else if currentTab === 'mappings'} - - {:else if currentTab === 'refnum'} - - {:else if currentTab === 'reftxt'} - - {/if} - -
- - - {#if $testStore.TestSiteID} - - {/if} -
- {/if} -
-
-``` - ---- - -## βœ… 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 diff --git a/src/lib/api/tests.js b/src/lib/api/tests.js index e9c8ac1..7b3e7e6 100644 --- a/src/lib/api/tests.js +++ b/src/lib/api/tests.js @@ -108,7 +108,7 @@ function buildPayload(formData, isUpdate = false) { RangeType: r.RangeType, Sex: r.Sex, AgeStart: parseInt(r.AgeStart) || 0, - AgeEnd: parseInt(r.AgeEnd) || 150, + AgeEnd: parseInt(r.AgeEnd) || 54750, // 150 years in days LowSign: r.LowSign || null, Low: r.Low !== null && r.Low !== undefined ? parseFloat(r.Low) : null, HighSign: r.HighSign || null, @@ -123,7 +123,7 @@ function buildPayload(formData, isUpdate = false) { TxtRefType: r.TxtRefType, Sex: r.Sex, AgeStart: parseInt(r.AgeStart) || 0, - AgeEnd: parseInt(r.AgeEnd) || 150, + AgeEnd: parseInt(r.AgeEnd) || 54750, // 150 years in days RefTxt: r.RefTxt || '', Flag: r.Flag || null })); diff --git a/src/routes/(app)/master-data/tests/+page.svelte b/src/routes/(app)/master-data/tests/+page.svelte index a420623..788ec02 100644 --- a/src/routes/(app)/master-data/tests/+page.svelte +++ b/src/routes/(app)/master-data/tests/+page.svelte @@ -6,6 +6,7 @@ import DataTable from '$lib/components/DataTable.svelte'; import Modal from '$lib/components/Modal.svelte'; import TestFormModal from './test-modal/TestFormModal.svelte'; + import TestTypePickerModal from './test-modal/modals/TestTypePickerModal.svelte'; import { Plus, Edit2, Trash2, ArrowLeft, Search, Microscope, Variable, Calculator, Box, Layers, Loader2, Users } from 'lucide-svelte'; let loading = $state(false); @@ -16,9 +17,11 @@ let modalOpen = $state(false); let modalMode = $state('create'); let selectedTestId = $state(null); + let selectedTestType = $state('TEST'); let deleteConfirmOpen = $state(false); let deleteItem = $state(null); let deleting = $state(false); + let testTypePickerOpen = $state(false); const testTypeConfig = { TEST: { label: 'Test', badgeClass: 'badge-primary', icon: Microscope, color: '#0066CC', bgColor: '#E6F2FF' }, @@ -86,6 +89,11 @@ } function openCreateModal() { + testTypePickerOpen = true; + } + + function handleTestTypeSelect(type) { + selectedTestType = type; modalMode = 'create'; selectedTestId = null; modalOpen = true; @@ -222,10 +230,16 @@ + + import { onMount } from 'svelte'; - import { Info, Settings, Calculator, Users, Link } from 'lucide-svelte'; + import { Info, Settings, Calculator, Users, Link, Hash, Type } from 'lucide-svelte'; import { fetchTest, createTest, updateTest, validateTestCode, validateTestName } from '$lib/api/tests.js'; import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js'; import Modal from '$lib/components/Modal.svelte'; @@ -9,8 +9,10 @@ import CalcDetailsTab from './tabs/CalcDetailsTab.svelte'; import GroupMembersTab from './tabs/GroupMembersTab.svelte'; import MappingsTab from './tabs/MappingsTab.svelte'; + import RefNumTab from './tabs/RefNumTab.svelte'; + import RefTxtTab from './tabs/RefTxtTab.svelte'; - let { open = $bindable(false), mode = 'create', testId = null, disciplines = [], departments = [], tests = [], onsave = null } = $props(); + let { open = $bindable(false), mode = 'create', testId = null, initialTestType = 'TEST', disciplines = [], departments = [], tests = [], onsave = null } = $props(); let currentTab = $state('basic'); let loading = $state(false); @@ -30,16 +32,29 @@ { id: 'tech', label: 'Tech Details', component: Settings }, { id: 'calc', label: 'Calculations', component: Calculator }, { id: 'group', label: 'Group Members', component: Users }, - { id: 'mappings', label: 'Mappings', component: Link } + { id: 'mappings', label: 'Mappings', component: Link }, + { id: 'refnum', label: 'Num Refs', component: Hash }, + { id: 'reftxt', label: 'Txt Refs', component: Type } ]; const visibleTabs = $derived.by(() => { const type = formData.TestType; + const resultType = formData.details?.ResultType; + const refType = formData.details?.RefType; + return tabConfig.filter(tab => { if (tab.id === 'basic' || tab.id === 'mappings') return true; if (tab.id === 'tech') return ['TEST', 'PARAM', 'CALC'].includes(type); if (tab.id === 'calc') return type === 'CALC'; if (tab.id === 'group') return type === 'GROUP'; + if (tab.id === 'refnum') { + // Show for TEST/PARAM with numeric result types and RANGE/THOLD ref types + return ['TEST', 'PARAM'].includes(type) && ['NMRIC', 'RANGE'].includes(resultType) && ['RANGE', 'THOLD'].includes(refType); + } + if (tab.id === 'reftxt') { + // Show for TEST/PARAM with TEXT result type + return ['TEST', 'PARAM'].includes(type) && resultType === 'TEXT' && refType === 'TEXT'; + } return false; }); }); @@ -84,7 +99,7 @@ TestSiteID: null, TestSiteCode: '', TestSiteName: '', - TestType: 'TEST', + TestType: initialTestType, Description: '', SiteID: 1, SeqScr: 0, @@ -220,7 +235,7 @@ if (!codeResult.valid) errors.TestSiteCode = codeResult.error; const nameResult = validateTestName(formData.TestSiteName); - if (!nameResult.valid) errors.TestSiteName = nameResult.valid; + if (!nameResult.valid) errors.TestSiteName = nameResult.error; if (!formData.TestType) { errors.TestType = 'Test type is required'; @@ -288,7 +303,7 @@ {/if} -
+
@@ -320,6 +335,7 @@ bind:formData bind:isDirty onTypeChange={handleTypeChange} + {mode} /> {:else if currentTab === 'tech'} {:else if currentTab === 'calc'} + {:else if currentTab === 'refnum'} + + {:else if currentTab === 'reftxt'} + {/if}
diff --git a/src/routes/(app)/master-data/tests/test-modal/modals/TestTypePickerModal.svelte b/src/routes/(app)/master-data/tests/test-modal/modals/TestTypePickerModal.svelte new file mode 100644 index 0000000..8bf41f1 --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/modals/TestTypePickerModal.svelte @@ -0,0 +1,68 @@ + + + +
+

+ Choose the type of test you want to create. This determines the available options and configuration. +

+ +
+ {#each testTypes as type} + {@const IconComponent = type.icon} + + {/each} +
+
+ + {#snippet footer()} + + {/snippet} +
diff --git a/src/routes/(app)/master-data/tests/test-modal/tabs/BasicInfoTab.svelte b/src/routes/(app)/master-data/tests/test-modal/tabs/BasicInfoTab.svelte index 8d7a719..f0c0b15 100644 --- a/src/routes/(app)/master-data/tests/test-modal/tabs/BasicInfoTab.svelte +++ b/src/routes/(app)/master-data/tests/test-modal/tabs/BasicInfoTab.svelte @@ -2,7 +2,9 @@ import { validateTestCode, validateTestName } from '$lib/api/tests.js'; import { AlertCircle } from 'lucide-svelte'; - let { formData = $bindable(), isDirty = $bindable(false), onTypeChange = null } = $props(); + let { formData = $bindable(), isDirty = $bindable(false), mode = 'create' } = $props(); + + const isEditMode = $derived(mode === 'edit'); let validationErrors = $state({ TestSiteCode: '', @@ -11,11 +13,11 @@ }); const testTypes = [ - { value: 'TEST', label: 'Test - Single Test' }, - { value: 'PARAM', label: 'Parameter - Test Parameter' }, - { value: 'CALC', label: 'Calculated - Formula-based' }, - { value: 'GROUP', label: 'Panel - Test Group' }, - { value: 'TITLE', label: 'Header - Section Header' } + { value: 'TEST', label: 'Test', description: 'Single Test' }, + { value: 'PARAM', label: 'Parameter', description: 'Test Parameter' }, + { value: 'CALC', label: 'Calculated', description: 'Formula-based' }, + { value: 'GROUP', label: 'Panel', description: 'Test Group' }, + { value: 'TITLE', label: 'Header', description: 'Section Header' } ]; function validateField(field) { @@ -77,18 +79,42 @@ handleFieldChange(); validateField('TestSiteName'); } - - function handleTestTypeChange(event) { - const newType = event.target.value; - if (onTypeChange) { - onTypeChange(newType); - } - handleFieldChange(); - validateField('TestType'); - }
+ +
+ {#if isEditMode} + {@const selectedType = testTypes.find(t => t.value === formData.TestType)} +
+
+ {selectedType?.label?.[0] || '?'} +
+
+
+ {selectedType?.label || 'Unknown'} + ({selectedType?.description || 'Unknown'}) +
+ Test type cannot be changed after creation +
+
+ {:else} + {@const selectedType = testTypes.find(t => t.value === formData.TestType)} +
+
+ {selectedType?.label?.[0] || '?'} +
+
+
+ {selectedType?.label || 'Unknown'} + ({selectedType?.description || 'Unknown'}) +
+ {formData.TestType} +
+
+ {/if} +
+

Test Identity

@@ -142,49 +168,19 @@ {/if}
-
- -
-

Classification

-
- -
- - - {#if validationErrors.TestType} - - - {validationErrors.TestType} - - {/if} -
- - -
- - -
+ +
+ +
diff --git a/src/routes/(app)/master-data/tests/test-modal/tabs/RefNumTab.svelte b/src/routes/(app)/master-data/tests/test-modal/tabs/RefNumTab.svelte index 342ba53..751bd56 100644 --- a/src/routes/(app)/master-data/tests/test-modal/tabs/RefNumTab.svelte +++ b/src/routes/(app)/master-data/tests/test-modal/tabs/RefNumTab.svelte @@ -1,39 +1,24 @@
@@ -125,7 +160,6 @@ Sex Age Reference Text - Flag Actions @@ -141,11 +175,8 @@ {getSexLabel(range.Sex)} - {range.AgeStart}-{range.AgeEnd} + {getAgeDisplay(range.AgeStart)}-{getAgeDisplay(range.AgeEnd)} {range.RefTxt || '-'} - - {range.Flag} -
- +
@@ -200,14 +231,49 @@
-
-
- - -
-
- - + +
+

Age Range

+
+ +
+ + + Both age values will use this unit +
+ + +
+
+ +
+ + {editingRange.AgeUnit} +
+
+
+ +
+ + {editingRange.AgeUnit} +
+
+
+ + +
+ Quick presets: +
+ + + + +
+
@@ -224,14 +290,6 @@ maxlength="255" />
- -
- - - -
{#snippet footer()} diff --git a/src/routes/(app)/master-data/tests/test-modal/tabs/TechDetailsTab.svelte b/src/routes/(app)/master-data/tests/test-modal/tabs/TechDetailsTab.svelte index 369d8b1..fd72362 100644 --- a/src/routes/(app)/master-data/tests/test-modal/tabs/TechDetailsTab.svelte +++ b/src/routes/(app)/master-data/tests/test-modal/tabs/TechDetailsTab.svelte @@ -1,13 +1,46 @@