1582 lines
57 KiB
Markdown
1582 lines
57 KiB
Markdown
|
|
# 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
|