first commit
This commit is contained in:
commit
bae48fab29
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
/.serena
|
||||
162
AGENTS.md
Normal file
162
AGENTS.md
Normal file
@ -0,0 +1,162 @@
|
||||
# CLQMS Frontend - Agent Guidelines
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
pnpm dev # Start dev server (http://localhost:5173)
|
||||
# Production
|
||||
pnpm build # Build for production (outputs to build/)
|
||||
pnpm preview # Preview production build locally
|
||||
# Type Checking
|
||||
pnpm check # Run svelte-check TypeScript validation
|
||||
pnpm check:watch # Run type checker in watch mode
|
||||
# Package Management
|
||||
pnpm install # Install dependencies
|
||||
pnpm prepare # Sync SvelteKit (runs automatically)
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### TypeScript & Svelte
|
||||
- Use TypeScript strict mode (configured in tsconfig.json)
|
||||
- Prefer explicit types over `any`
|
||||
- Use Svelte 5 runes: `$state`, `$derived`, `$effect`, `$props`
|
||||
- Components use `.svelte` extension with `<script lang="ts">`
|
||||
|
||||
### Imports & Aliases
|
||||
Use path aliases (defined in svelte.config.js):
|
||||
- `$components/*` → `src/lib/components/*`
|
||||
- `$stores/*` → `src/lib/stores/*`
|
||||
- `$types/*` → `src/lib/types/*`
|
||||
- `$server/*` → `src/lib/server/*`
|
||||
- `$lib/*` → `src/lib/*` (built-in)
|
||||
Order: External libs → SvelteKit → Internal aliases → Relative imports
|
||||
|
||||
### Naming Conventions
|
||||
- Components: PascalCase (e.g., `Sidebar.svelte`, `LoginForm.svelte`)
|
||||
- Files: kebab-case for non-components (e.g., `api-client.ts`)
|
||||
- Variables/functions: camelCase
|
||||
- Constants: UPPER_SNAKE_CASE
|
||||
- Types/Interfaces: PascalCase with descriptive names
|
||||
- Svelte stores: camelCase (e.g., `auth`, `userStore`)
|
||||
|
||||
### Component Structure
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
// 1. Imports
|
||||
import { goto } from '$app/navigation';
|
||||
import { auth } from '$stores/auth';
|
||||
// 2. Props
|
||||
let { title, data } = $props<{ title: string; data: SomeType }>();
|
||||
// 3. State
|
||||
let loading = $state(false);
|
||||
// 4. Derived
|
||||
let isValid = $derived(data && data.length > 0);
|
||||
// 5. Effects
|
||||
$effect(() => { if (isValid) console.log('Data loaded'); });
|
||||
</script>
|
||||
<svelte:head><title>{title}</title></svelte:head>
|
||||
<!-- Template -->
|
||||
<div class="card"><slot /></div>
|
||||
```
|
||||
|
||||
### Styling (Tailwind + DaisyUI)
|
||||
- Use Tailwind utility classes
|
||||
- DaisyUI components: `btn`, `card`, `input`, `modal`, etc.
|
||||
- Theme: Forest (medical green primary: #2d6a4f)
|
||||
- Color utilities: `text-emerald-600`, `bg-emerald-100`
|
||||
- Responsive: `sm:`, `md:`, `lg:` prefixes
|
||||
- Spacing: 4px base unit (e.g., `p-4` = 16px)
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
import { api } from '$server/api';
|
||||
async function fetchData() {
|
||||
const { data, error, success } = await api.get<Patient[]>('/api/patients');
|
||||
if (!success || error) {
|
||||
console.error('Failed to fetch:', error?.message);
|
||||
return;
|
||||
}
|
||||
// Use data
|
||||
}
|
||||
```
|
||||
|
||||
### Form Handling (Superforms + Zod)
|
||||
```typescript
|
||||
import { superForm } from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { z } from 'zod';
|
||||
const schema = z.object({
|
||||
username: z.string().min(1, 'Required'),
|
||||
email: z.string().email('Invalid email')
|
||||
});
|
||||
const { form, errors, enhance } = superForm(data?.form, {
|
||||
validators: zodClient(schema)
|
||||
});
|
||||
```
|
||||
|
||||
### State Management
|
||||
```typescript
|
||||
import { writable } from 'svelte/store';
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
function createAuthStore() {
|
||||
const { subscribe, set, update } = writable<AuthState>({
|
||||
user: null,
|
||||
isAuthenticated: false
|
||||
});
|
||||
return {
|
||||
subscribe,
|
||||
login: (user: User) => update(s => ({ ...s, user, isAuthenticated: true })),
|
||||
logout: () => set({ user: null, isAuthenticated: false })
|
||||
};
|
||||
}
|
||||
export const auth = createAuthStore();
|
||||
```
|
||||
|
||||
### Route Structure
|
||||
```
|
||||
src/routes/
|
||||
├── +layout.svelte # Root layout
|
||||
├── +page.svelte # Landing page (redirects)
|
||||
├── (app)/ # Group: Protected routes
|
||||
│ ├── +layout.svelte # App layout with sidebar
|
||||
│ └── dashboard/
|
||||
│ └── +page.svelte
|
||||
└── (auth)/ # Group: Public routes
|
||||
├── +layout.svelte # Auth layout (centered)
|
||||
└── login/
|
||||
└── +page.svelte
|
||||
```
|
||||
|
||||
### API Client Usage
|
||||
```typescript
|
||||
import { api } from '$server/api';
|
||||
// GET
|
||||
const { data } = await api.get<User[]>('/api/users');
|
||||
// POST
|
||||
const { data } = await api.post<User>('/api/users', { name: 'John' });
|
||||
// PATCH
|
||||
const { data } = await api.patch<User>('/api/users/1', { name: 'Jane' });
|
||||
// DELETE
|
||||
const { data } = await api.delete('/api/users/1');
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```typescript
|
||||
// Use only PUBLIC_ prefix for client-side
|
||||
import { PUBLIC_API_BASE_URL } from '$env/static/public';
|
||||
```
|
||||
|
||||
### Git Workflow
|
||||
- Commit messages: imperative mood (e.g., "Add patient form validation")
|
||||
- Branch naming: `feature/`, `fix/`, `refactor/` prefixes
|
||||
- Never commit secrets or `.env.local`
|
||||
|
||||
## Testing (To Be Configured)
|
||||
Test framework not yet configured. When added:
|
||||
- Unit tests: Vitest (planned per checklist)
|
||||
- E2E tests: Playwright (planned per checklist)
|
||||
734
CLQMS-CHECKLIST.md
Normal file
734
CLQMS-CHECKLIST.md
Normal file
@ -0,0 +1,734 @@
|
||||
# CLQMS Frontend Development Checklist
|
||||
|
||||
> **Framework:** SvelteKit
|
||||
> **Key Change:** Value Sets moved to Phase 3 (from Phase 10) - needed for dropdowns in all forms
|
||||
|
||||
## Phase 1: Project Setup
|
||||
|
||||
- [x] Initialize project with SvelteKit
|
||||
- [x] Install and configure TypeScript
|
||||
- [ ] Set up ESLint and Prettier
|
||||
- [x] Create SvelteKit folder structure (`src/routes/`, `src/lib/components/`, `src/lib/stores/`, `src/lib/server/`)
|
||||
- [x] Configure SvelteKit adapter (auto/vercel/node for deployment target)
|
||||
- [x] Set up API client using SvelteKit's fetch or axios wrapper
|
||||
- [x] Configure handle hook for auth middleware (`src/hooks.server.ts`)
|
||||
- [x] Set up Svelte stores for state management
|
||||
- [x] Install UI component library or design system (Skeleton, Flowbite, or custom)
|
||||
- [x] Create base layout components (`+layout.svelte` with Header, Sidebar, Footer)
|
||||
- [x] Configure environment variables (`.env`, `.env.local`)
|
||||
- [x] Set up build scripts (`vite build`, `vite preview`)
|
||||
- [x] Configure form validation library (Superforms with Zod)
|
||||
- [ ] Configure CI/CD pipeline (GitHub Actions/GitLab CI)
|
||||
- [x] Create README with setup instructions
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [x] Project builds without errors (`npm run build`)
|
||||
- [ ] Linting and formatting configured
|
||||
- [ ] API client successfully connects to backend
|
||||
- [x] SvelteKit file-based routing works
|
||||
- [x] Handle hook properly intercepts requests
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Authentication Module
|
||||
|
||||
- [x] Create login page component
|
||||
- [x] Implement login form with username/password fields
|
||||
- [x] Add form validation (required fields)
|
||||
- [ ] Implement login API call to `/api/auth/login`
|
||||
- [ ] Handle JWT token from HTTP-only cookie
|
||||
- [ ] Create logout functionality (call `/api/auth/logout`)
|
||||
- [ ] Implement auth state management (store user data, auth status)
|
||||
- [ ] Create protected route using SvelteKit `+page.server.ts` load functions or `+layout.server.ts`
|
||||
- [ ] Implement auth check using SvelteKit hooks (`src/hooks.server.ts`)
|
||||
- [ ] Handle 401 errors globally (redirect to login)
|
||||
- [ ] Create password change form (current password, new password)
|
||||
- [ ] Implement password change API call to `/api/auth/change_pass`
|
||||
- [ ] Add loading states for auth operations
|
||||
- [ ] Add error messages for failed login attempts
|
||||
- [ ] Create registration page (optional, if needed)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- User can login successfully
|
||||
- JWT token is stored/managed correctly
|
||||
- Protected routes redirect unauthenticated users
|
||||
- Logout clears auth state and token
|
||||
- Password change works with proper validation
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Organization Module
|
||||
|
||||
### Accounts
|
||||
- [ ] Create accounts list page
|
||||
- [ ] Implement account creation form
|
||||
- [ ] Implement account edit form
|
||||
- [ ] Implement account delete with confirmation
|
||||
- [ ] Add search/filter for accounts
|
||||
|
||||
### Sites
|
||||
- [ ] Create sites list page (filtered by selected account)
|
||||
- [ ] Implement site creation form (with AccountID)
|
||||
- [ ] Implement site edit form
|
||||
- [ ] Implement site delete with confirmation
|
||||
- [ ] Add site search/filter
|
||||
|
||||
### Disciplines
|
||||
- [ ] Create disciplines list page
|
||||
- [ ] Implement discipline creation form
|
||||
- [ ] Implement discipline edit form
|
||||
- [ ] Implement discipline delete with confirmation
|
||||
- [ ] Add discipline search/filter
|
||||
|
||||
### Departments
|
||||
- [ ] Create departments list page (filtered by site)
|
||||
- [ ] Implement department creation form (with SiteID)
|
||||
- [ ] Implement department edit form
|
||||
- [ ] Implement department delete with confirmation
|
||||
- [ ] Add department search/filter
|
||||
|
||||
### Workstations
|
||||
- [ ] Create workstations list page (filtered by department)
|
||||
- [ ] Implement workstation creation form (with SiteID, DepartmentID)
|
||||
- [ ] Implement workstation edit form
|
||||
- [ ] Implement workstation delete with confirmation
|
||||
- [ ] Add workstation search/filter
|
||||
|
||||
### Hierarchy Navigation
|
||||
- [ ] Create organization tree view component
|
||||
- [ ] Implement breadcrumb navigation
|
||||
- [ ] Add filters to cascade (Account → Site → Department → Workstation)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- All CRUD operations work for each entity
|
||||
- Filtering and cascading selection works
|
||||
- Delete operations have confirmation dialogs
|
||||
- List pages support pagination
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Master Data Module
|
||||
|
||||
### Locations
|
||||
- [ ] Create locations list page
|
||||
- [ ] Implement location creation/edit form
|
||||
- [ ] Implement location delete with confirmation
|
||||
- [ ] Add search/filter by location code, name, type
|
||||
|
||||
### Contacts
|
||||
- [ ] Create contacts list page
|
||||
- [ ] Implement contact creation/edit form
|
||||
- [ ] Implement contact delete with confirmation
|
||||
- [ ] Add search/filter by name, type, specialty
|
||||
|
||||
### Occupations
|
||||
- [ ] Create occupations list page
|
||||
- [ ] Implement occupation creation/edit form
|
||||
- [ ] Implement occupation delete with confirmation
|
||||
|
||||
### Medical Specialties
|
||||
- [ ] Create medical specialties list page
|
||||
- [ ] Implement specialty creation/edit form
|
||||
- [ ] Implement specialty delete with confirmation
|
||||
|
||||
### Counters
|
||||
- [ ] Create counters list page
|
||||
- [ ] Implement counter creation/edit form
|
||||
- [ ] Implement counter delete with confirmation
|
||||
- [ ] Add ability to reset counter value
|
||||
|
||||
### Geographical Areas
|
||||
- [ ] Create provinces list page (API: `/api/areageo/provinces`)
|
||||
- [ ] Create cities list page (API: `/api/areageo/cities` with province_id filter)
|
||||
- [ ] Create province → city dropdown components
|
||||
- [ ] Add caching for geographical data
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- All master data CRUD operations work
|
||||
- Province/city dropdowns cascade correctly
|
||||
- Search and filtering works across all entities
|
||||
- Geographical data is cached appropriately
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Patient Management Module
|
||||
|
||||
### Patient List & Search
|
||||
- [ ] Create patients list page
|
||||
- [ ] Implement search by PatientID or name (API parameter)
|
||||
- [ ] Add pagination controls
|
||||
- [ ] Implement patient detail view (click to view)
|
||||
- [ ] Add quick actions (view, edit, delete)
|
||||
|
||||
### Patient Registration
|
||||
- [ ] Create patient registration form with all fields
|
||||
- [ ] Implement field validation:
|
||||
- [ ] PatientID: alphanumeric, max 30 chars, pattern validation
|
||||
- [ ] Sex: required, dropdown (1=Female, 2=Male)
|
||||
- [ ] NameFirst: required, 1-60 chars, letters/spaces/periods
|
||||
- [ ] Birthdate: required, date-time picker
|
||||
- [ ] Email: format validation
|
||||
- [ ] Phone/Mobile: regex validation (8-15 digits, optional +)
|
||||
- [ ] ZIP: numeric only, max 10 chars
|
||||
- [ ] Add optional fields (NameMiddle, NameMaiden, NameLast, etc.)
|
||||
- [ ] Add identifier section (IdentifierType, Identifier)
|
||||
- [ ] Implement patient check existence (API: `/api/patient/check`)
|
||||
- [ ] Add duplicate patient warning if exists
|
||||
|
||||
### Patient Edit
|
||||
- [ ] Create patient edit form (same as registration)
|
||||
- [ ] Pre-fill with existing patient data
|
||||
- [ ] Implement PATCH API call
|
||||
- [ ] Add save confirmation
|
||||
|
||||
### Patient Delete
|
||||
- [ ] Implement delete confirmation dialog
|
||||
- [ ] Call DELETE API with PatientID
|
||||
- [ ] Show success/error feedback
|
||||
|
||||
### Patient Visits
|
||||
- [ ] Create visits list page
|
||||
- [ ] Filter visits by PatientID
|
||||
- [ ] Implement visit creation form
|
||||
- [ ] Implement visit edit form
|
||||
- [ ] Implement visit delete with confirmation
|
||||
- [ ] Add visit detail view
|
||||
|
||||
### ADT Events
|
||||
- [ ] Create ADT event form
|
||||
- [ ] Implement ADT event types:
|
||||
- [ ] A01: Admit
|
||||
- [ ] A02: Transfer
|
||||
- [ ] A03: Discharge
|
||||
- [ ] A04: Register
|
||||
- [ ] A08: Update
|
||||
- [ ] Add validation for ADT events
|
||||
- [ ] Call `/api/patvisitadt` endpoint
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Patient CRUD operations work with proper validation
|
||||
- Search finds patients by ID or name
|
||||
- Duplicate patient check prevents duplicates
|
||||
- Visit management works correctly
|
||||
- ADT events are handled properly
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Test Catalog Module
|
||||
|
||||
### Test Definitions
|
||||
- [ ] Create test definitions list page
|
||||
- [ ] Add filter by TestType (TEST, PARAM, CALC, GROUP, TITLE)
|
||||
- [ ] Add filter by DisciplineID
|
||||
- [ ] Add filter by DepartmentID
|
||||
- [ ] Add pagination
|
||||
- [ ] Implement test detail view
|
||||
- [ ] Display test metadata (code, name, unit, formula, specimen type)
|
||||
|
||||
### Test Browser
|
||||
- [ ] Create test browser/search component
|
||||
- [ ] Add search by TestCode or TestName
|
||||
- [ ] Add filter by SpecimenType
|
||||
- [ ] Add multi-select functionality (for ordering)
|
||||
- [ ] Display test details on hover/click
|
||||
|
||||
### Panels & Groups
|
||||
- [ ] Identify and display test groups (TestType = GROUP)
|
||||
- [ ] Expand/collapse group functionality
|
||||
- [ ] Show tests within groups
|
||||
- [ ] Add section headers (TestType = TITLE)
|
||||
|
||||
### Test Mapping
|
||||
- [ ] Create test mapping view (if needed for admin)
|
||||
- [ ] Display HostCode ↔ ClientCode mappings
|
||||
- [ ] Filter by HostType or ClientType
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Test catalog browsable with filters
|
||||
- Multi-select works for test ordering
|
||||
- Groups and panels display correctly
|
||||
- Search returns relevant results
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Specimen Module
|
||||
|
||||
### Specimen Management
|
||||
- [ ] Create specimens list page
|
||||
- [ ] Add filter by SpecimenStatus
|
||||
- [ ] Add filter by PatientID
|
||||
- [ ] Add filter by SpecimenType
|
||||
- [ ] Implement specimen creation form
|
||||
- [ ] Implement specimen edit form
|
||||
- [ ] Implement specimen status update
|
||||
- [ ] Add specimen detail view
|
||||
|
||||
### Container Definitions
|
||||
- [ ] Create container definitions list page
|
||||
- [ ] Implement container creation form (code, name, category, size, cap color)
|
||||
- [ ] Implement container edit form
|
||||
- [ ] Implement container delete with confirmation
|
||||
- [ ] Display container metadata in specimen view
|
||||
|
||||
### Specimen Preparations
|
||||
- [ ] Create preparations list page
|
||||
- [ ] Implement preparation creation form
|
||||
- [ ] Implement preparation edit form
|
||||
- [ ] Implement preparation delete
|
||||
|
||||
### Specimen Statuses
|
||||
- [ ] Create specimen statuses list page
|
||||
- [ ] Implement status creation form
|
||||
- [ ] Implement status edit form
|
||||
- [ ] Display status activity labels
|
||||
|
||||
### Collection Methods
|
||||
- [ ] Create collection methods list page
|
||||
- [ ] Implement method creation form (code, name, method, additive, role)
|
||||
- [ ] Implement method edit form
|
||||
- [ ] Display method metadata
|
||||
|
||||
### Specimen Tracking
|
||||
- [ ] Create specimen timeline/status view
|
||||
- [ ] Visual status indicators (color-coded)
|
||||
- [ ] History log display (if available)
|
||||
- [ ] Barcode display (SpecimenID)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Specimen CRUD operations work
|
||||
- Containers, preparations, statuses, methods manageable
|
||||
- Specimen status tracking is visible
|
||||
- Filters work correctly
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Orders Module
|
||||
|
||||
### Orders List
|
||||
- [ ] Create orders list page
|
||||
- [ ] Add filter by OrderStatus (pending, in-progress, completed, cancelled)
|
||||
- [ ] Add filter by PatientID
|
||||
- [ ] Add filter by date range
|
||||
- [ ] Add pagination
|
||||
- [ ] Display order priority (color-coded: stat=red, urgent=orange, routine=gray)
|
||||
- [ ] Display order status badges
|
||||
- [ ] Add quick actions (view, edit, delete)
|
||||
|
||||
### Order Creation
|
||||
- [ ] Create order creation form
|
||||
- [ ] Patient selection (search by PatientID)
|
||||
- [ ] Visit selection (optional, dropdown)
|
||||
- [ ] Priority selection (routine, stat, urgent)
|
||||
- [ ] Site selection (dropdown)
|
||||
- [ ] Requesting physician (text input)
|
||||
- [ ] Test selection (multi-select from test catalog)
|
||||
- [ ] Specimen type selection per test (if needed)
|
||||
- [ ] Validate required fields (PatientID, Tests)
|
||||
- [ ] Call POST `/api/ordertest`
|
||||
|
||||
### Order Detail View
|
||||
- [ ] Display order information (OrderID, PatientID, VisitID, Date, Status, Priority)
|
||||
- [ ] Display ordered tests with specimen types
|
||||
- [ ] Display current status
|
||||
- [ ] Add edit button
|
||||
- [ ] Add delete button
|
||||
- [ ] Add status change action
|
||||
|
||||
### Order Status Update
|
||||
- [ ] Implement status change dropdown
|
||||
- [ ] Call POST `/api/ordertest/status` with OrderID and OrderStatus
|
||||
- [ ] Update local state on success
|
||||
- [ ] Add confirmation for status changes
|
||||
|
||||
### Order Cancellation
|
||||
- [ ] Add cancel button
|
||||
- [ ] Show confirmation dialog
|
||||
- [ ] Update status to 'cancelled'
|
||||
- [ ] Show success/error feedback
|
||||
|
||||
### Order Deletion
|
||||
- [ ] Implement delete confirmation
|
||||
- [ ] Call DELETE `/api/ordertest`
|
||||
- [ ] Show success/error feedback
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Order creation works with all required fields
|
||||
- Order list filters work
|
||||
- Priority and status display correctly
|
||||
- Status updates work
|
||||
- Cancellation and deletion work with confirmation
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Results Module
|
||||
|
||||
### Results List
|
||||
- [ ] Create results list page
|
||||
- [ ] Add filter by PatientID
|
||||
- [ ] Add filter by date range
|
||||
- [ ] Add pagination
|
||||
- [ ] Display patient info with results
|
||||
- [ ] Add click to view details
|
||||
|
||||
### Results Detail View
|
||||
- [ ] Display patient information
|
||||
- [ ] Display order information
|
||||
- [ ] Display test results in table format
|
||||
- [ ] Show result values with units
|
||||
- [ ] Display flags (H=High, L=Low, N=Normal, A=Abnormal) with color coding
|
||||
- [ ] Highlight abnormal/critical results
|
||||
- [ ] Add result reference ranges (if available)
|
||||
|
||||
### Results Entry (Manual)
|
||||
- [ ] Create results entry form
|
||||
- [ ] Select order (from pending orders)
|
||||
- [ ] Display tests for order
|
||||
- [ ] Input result values for each test
|
||||
- [ ] Validate result values (numeric for quantitative tests)
|
||||
- [ ] Auto-calculate flags (H/L/N/A) if ranges available
|
||||
- [ ] Add units display
|
||||
- [ ] Add remarks/comment field
|
||||
- [ ] Save results to API
|
||||
|
||||
### Results Approval Workflow
|
||||
- [ ] Add "Review" status for results
|
||||
- [ ] Add "Approved" status
|
||||
- [ ] Implement approve/reject actions (if required)
|
||||
- [ ] Show reviewer info
|
||||
- [ ] Lock approved results (if needed)
|
||||
|
||||
### Critical Results Alerting
|
||||
- [ ] Identify critical results (flagged as critical)
|
||||
- [ ] Show prominent alerts/warnings
|
||||
- [ ] Add "acknowledge" action for critical results
|
||||
- [ ] Log critical result acknowledgments
|
||||
|
||||
### Results Export/Print
|
||||
- [ ] Add print results button
|
||||
- [ ] Generate printable report format
|
||||
- [ ] Add export to PDF
|
||||
- [ ] Add export to CSV (if needed)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Results display correctly with flags
|
||||
- Abnormal results highlighted
|
||||
- Manual entry works with validation
|
||||
- Approval workflow works (if implemented)
|
||||
- Critical results are prominent
|
||||
- Print/export functions work
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Value Sets Module
|
||||
|
||||
### Value Set Definitions
|
||||
- [ ] Create value sets list page
|
||||
- [ ] Display VSetCode, VSetName, Description, Category
|
||||
- [ ] Add search/filter by code or name
|
||||
- [ ] Add refresh button (call `/api/valueset/refresh`)
|
||||
- [ ] Show last refresh time
|
||||
|
||||
### Value Set Items
|
||||
- [ ] Create value set items page
|
||||
- [ ] Filter by VSetID
|
||||
- [ ] Display items in list/table (VValue, VLabel, VSeq, IsActive)
|
||||
- [ ] Implement item creation form
|
||||
- [ ] Implement item edit form
|
||||
- [ ] Implement item delete with confirmation
|
||||
- [ ] Add active/inactive toggle
|
||||
- [ ] Implement sorting by VSeq
|
||||
|
||||
### Dynamic Dropdowns
|
||||
- [ ] Create reusable dropdown component using value sets
|
||||
- [ ] Load dropdown options from `/api/valueset/items?VSetID=X`
|
||||
- [ ] Cache dropdown options in store
|
||||
- [ ] Implement refresh/reload dropdown data
|
||||
- [ ] Use value sets for:
|
||||
- [ ] Patient prefixes
|
||||
- [ ] Patient marital status
|
||||
- [ ] Specimen types
|
||||
- [ ] Collection methods
|
||||
- [ ] Specimen statuses
|
||||
- [ ] Order priorities
|
||||
- [ ] Order statuses
|
||||
- [ ] Test types
|
||||
- [ ] Any other configurable dropdowns
|
||||
|
||||
### Value Set Caching
|
||||
- [ ] Implement cache strategy for value sets
|
||||
- [ ] Load value sets on app initialization
|
||||
- [ ] Refresh cache when `/api/valueset/refresh` called
|
||||
- [ ] Use stale-while-revalidate pattern for dropdowns
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Value sets display correctly
|
||||
- Items CRUD works
|
||||
- Refresh clears cache and reloads
|
||||
- Dynamic dropdowns populate from value sets
|
||||
- Caching reduces API calls
|
||||
|
||||
---
|
||||
|
||||
## Phase 11: Dashboard Module
|
||||
|
||||
### Dashboard Summary Cards
|
||||
- [ ] Create dashboard page
|
||||
- [ ] Display pending orders count (API: `/api/dashboard`)
|
||||
- [ ] Display today's results count
|
||||
- [ ] Display critical results count
|
||||
- [ ] Display active patients count
|
||||
- [ ] Add refresh button
|
||||
|
||||
### Orders Trend Chart
|
||||
- [ ] Create orders over time chart
|
||||
- [ ] Filter by date range
|
||||
- [ ] Group by status
|
||||
- [ ] Use charting library (Chart.js/Recharts/etc.)
|
||||
|
||||
### Results Volume Chart
|
||||
- [ ] Create results volume chart
|
||||
- [ ] Filter by date range
|
||||
- [ ] Group by test type or department
|
||||
- [ ] Use charting library
|
||||
|
||||
### Real-time Status Indicators
|
||||
- [ ] Implement polling or WebSocket for real-time updates
|
||||
- [ ] Update dashboard metrics automatically
|
||||
- [ ] Show last update time
|
||||
- [ ] Add auto-refresh toggle (e.g., every 30 seconds)
|
||||
|
||||
### Department Performance
|
||||
- [ ] Display department-wise metrics
|
||||
- [ ] Orders processed per department
|
||||
- [ ] Average turnaround time
|
||||
- [ ] Results volume per department
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Dashboard displays all summary metrics
|
||||
- Charts render correctly
|
||||
- Real-time updates work
|
||||
- Filters apply to charts
|
||||
- Performance metrics are accurate
|
||||
|
||||
---
|
||||
|
||||
## Phase 12: Edge API Integration
|
||||
|
||||
### Instrument Status Monitoring
|
||||
- [ ] Create instrument status page
|
||||
- [ ] Display instrument list with status
|
||||
- [ ] Color-code status (online=green, offline=gray, error=red, maintenance=orange)
|
||||
- [ ] Implement status polling or WebSocket
|
||||
- [ ] Display last status update timestamp
|
||||
- [ ] Display status message (if available)
|
||||
|
||||
### Real-time Result Display
|
||||
- [ ] Implement result streaming (WebSocket or SSE polling)
|
||||
- [ ] Display incoming results as they arrive
|
||||
- [ ] Parse EdgeResultRequest format (sample_id, instrument_id, results array)
|
||||
- [ ] Display results in real-time feed
|
||||
- [ ] Show test_code, result_value, unit, flags
|
||||
- [ ] Add sound/notification for new results
|
||||
|
||||
### Order Acknowledgment
|
||||
- [ ] Create edge orders list (API: `/api/edge/orders`)
|
||||
- [ ] Filter by instrument_id
|
||||
- [ ] Filter by status (pending, acknowledged)
|
||||
- [ ] Display orders for instruments
|
||||
- [ ] Implement acknowledge action (POST `/api/edge/orders/{orderId}/ack`)
|
||||
- [ ] Update status to acknowledged
|
||||
- [ ] Show acknowledgment confirmation
|
||||
|
||||
### Instrument Status Logging
|
||||
- [ ] Create status log view
|
||||
- [ ] Display status history per instrument
|
||||
- [ ] Show timestamp, status, message
|
||||
- [ ] Call `/api/edge/status` when receiving status updates
|
||||
|
||||
### Result Queue Management
|
||||
- [ ] Display queued results (EdgeResultResponse: edge_res_id)
|
||||
- [ ] Show queue status
|
||||
- [ ] Manual trigger for processing (if needed)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Instrument status displays correctly
|
||||
- Real-time results stream in
|
||||
- Order acknowledgment works
|
||||
- Status history is visible
|
||||
- Result queue is manageable
|
||||
|
||||
---
|
||||
|
||||
## Phase 13: Testing & QA
|
||||
|
||||
### Unit Tests
|
||||
- [ ] Set up testing framework (Jest, Vitest, etc.)
|
||||
- [ ] Write unit tests for components
|
||||
- [ ] Write unit tests for services/API calls
|
||||
- [ ] Write unit tests for utility functions
|
||||
- [ ] Write unit tests for state management
|
||||
- [ ] Achieve >80% code coverage
|
||||
|
||||
### Integration Tests
|
||||
- [ ] Set up API mocking (MSW, Mirage, etc.)
|
||||
- [ ] Write tests for authentication flow
|
||||
- [ ] Write tests for patient CRUD operations
|
||||
- [ ] Write tests for order creation flow
|
||||
- [ ] Write tests for specimen tracking
|
||||
- [ ] Write tests for results entry
|
||||
|
||||
### E2E Tests (Critical Flows)
|
||||
- [ ] Set up E2E testing (Cypress, Playwright)
|
||||
- [ ] Test login → dashboard flow
|
||||
- [ ] Test patient registration → order creation flow
|
||||
- [ ] Test order → specimen → results flow
|
||||
- [ ] Test critical results alerting
|
||||
- [ ] Test logout
|
||||
|
||||
### Form Validation Testing
|
||||
- [ ] Test all required field validations
|
||||
- [ ] Test format validations (email, phone, regex)
|
||||
- [ ] Test duplicate detection
|
||||
- [ ] Test error message display
|
||||
|
||||
### Error Handling Testing
|
||||
- [ ] Test 401 error handling (redirect to login)
|
||||
- [ ] Test 404 error handling
|
||||
- [ ] Test 500 error handling
|
||||
- [ ] Test network error handling
|
||||
- [ ] Test timeout handling
|
||||
|
||||
### Performance Testing
|
||||
- [ ] Measure page load times
|
||||
- [ ] Measure list rendering with large datasets
|
||||
- [ ] Optimize bundle size (code splitting, lazy loading)
|
||||
- [ ] Test with slow network (3G)
|
||||
- [ ] Audit with Lighthouse (score >90)
|
||||
|
||||
### Accessibility Audit
|
||||
- [ ] Test keyboard navigation
|
||||
- [ ] Test screen reader compatibility
|
||||
- [ ] Check color contrast ratios
|
||||
- [ ] Ensure proper ARIA labels
|
||||
- [ ] Test with accessibility tools (axe, WAVE)
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- All tests pass
|
||||
- Code coverage >80%
|
||||
- E2E tests cover critical flows
|
||||
- Lighthouse score >90
|
||||
- Accessibility audit passes
|
||||
|
||||
---
|
||||
|
||||
## Phase 14: Deployment
|
||||
|
||||
### Production Build
|
||||
- [ ] Create production build
|
||||
- [ ] Optimize assets (minify, compress)
|
||||
- [ ] Generate source maps
|
||||
- [ ] Verify build output
|
||||
- [ ] Check bundle size
|
||||
|
||||
### Environment Configuration
|
||||
- [ ] Set up production environment variables
|
||||
- [ ] Configure production API base URL
|
||||
- [ ] Disable development tools
|
||||
- [ ] Enable error tracking (Sentry, etc.)
|
||||
- [ ] Configure analytics (if needed)
|
||||
|
||||
### API Endpoint Verification
|
||||
- [ ] Verify all endpoints are accessible
|
||||
- [ ] Verify CORS configuration
|
||||
- [ ] Test authentication flow with production backend
|
||||
- [ ] Test with production data
|
||||
|
||||
### Database/Storage (if needed)
|
||||
- [ ] Configure any client-side storage (IndexedDB, localStorage)
|
||||
- [ ] Set up data migration scripts (if needed)
|
||||
- [ ] Test storage operations
|
||||
|
||||
### Pre-deployment Checklist
|
||||
- [ ] Run all tests and ensure they pass
|
||||
- [ ] Review and merge all PRs
|
||||
- [ ] Update version number
|
||||
- [ ] Update CHANGELOG
|
||||
- [ ] Create release notes
|
||||
|
||||
### Deployment
|
||||
- [ ] Deploy to staging environment
|
||||
- [ ] Smoke test on staging
|
||||
- [ ] Deploy to production
|
||||
- [ ] Verify production deployment
|
||||
- [ ] Monitor error logs
|
||||
- [ ] Monitor performance metrics
|
||||
|
||||
### Post-deployment
|
||||
- [ ] Verify critical flows work in production
|
||||
- [ ] Check user feedback
|
||||
- [ ] Monitor for errors
|
||||
- [ ] Rollback plan in case of issues
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- Production build completes without errors
|
||||
- Application works in production environment
|
||||
- All critical features verified
|
||||
- No critical errors in logs
|
||||
- Performance meets targets
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
### API Base URL
|
||||
- Development: `http://localhost/clqms01/`
|
||||
- Production: `https://clqms01-api.services-summit.my.id/`
|
||||
|
||||
### Authentication
|
||||
- JWT token stored in HTTP-only cookie
|
||||
- Bearer token in Authorization header for API calls
|
||||
- Check auth status on app load
|
||||
|
||||
### Common Patterns
|
||||
- All list pages support pagination
|
||||
- All forms include validation
|
||||
- All delete operations require confirmation
|
||||
- All API calls include error handling
|
||||
- Loading states for async operations
|
||||
|
||||
### Dependencies Between Phases
|
||||
- Phase 2 (Authentication) must be completed before protected routes
|
||||
- Phase 3 (Value Sets) needed for dropdowns across all modules - **do this early!**
|
||||
- Phase 4 (Organization) data needed for filtering in other modules
|
||||
- Phase 6 (Patients) needed for Phase 9 (Orders)
|
||||
- Phase 7 (Test Catalog) needed for Phase 9 (Orders)
|
||||
- Phase 9 (Orders) needed for Phase 10 (Results)
|
||||
|
||||
### Testing Priority
|
||||
- E2E: Login → Dashboard, Patient → Order → Result flow
|
||||
- Unit: All services, all components with logic
|
||||
- Integration: API calls, auth flows
|
||||
|
||||
---
|
||||
|
||||
## Completed Phases Tracker
|
||||
|
||||
- [x] Phase 1: Project Setup
|
||||
- [ ] Phase 2: Authentication Module
|
||||
- [ ] Phase 3: Value Sets Module
|
||||
- [ ] Phase 4: Organization Module
|
||||
- [ ] Phase 5: Master Data Module
|
||||
- [ ] Phase 6: Patient Management Module
|
||||
- [ ] Phase 7: Test Catalog Module
|
||||
- [ ] Phase 8: Specimen Module
|
||||
- [ ] Phase 9: Orders Module
|
||||
- [ ] Phase 10: Results Module
|
||||
- [ ] Phase 11: Dashboard Module
|
||||
- [ ] Phase 12: Edge API Integration
|
||||
- [ ] Phase 13: Testing & QA
|
||||
- [ ] Phase 14: Deployment
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-02-08
|
||||
**Version:** 1.0.0
|
||||
108
README.md
Normal file
108
README.md
Normal file
@ -0,0 +1,108 @@
|
||||
# CLQMS Frontend - Phase 1 Complete
|
||||
|
||||
## Project Setup Summary
|
||||
|
||||
**Framework:** SvelteKit with TypeScript
|
||||
**Styling:** Tailwind CSS v4 + DaisyUI v5
|
||||
**Package Manager:** pnpm
|
||||
**Deployment:** Static adapter (SPA mode)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
|
||||
# Preview production build
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── app.css # Global styles with DaisyUI theme
|
||||
├── app.d.ts # TypeScript declarations
|
||||
├── app.html # HTML template
|
||||
├── hooks.server.ts # Server hooks (auth middleware)
|
||||
├── lib/
|
||||
│ ├── components/
|
||||
│ │ ├── index.ts # Component exports
|
||||
│ │ ├── layout/
|
||||
│ │ │ ├── Header.svelte # App header with user menu
|
||||
│ │ │ └── Sidebar.svelte # Navigation sidebar
|
||||
│ ├── server/
|
||||
│ │ └── api.ts # API client wrapper
|
||||
│ ├── stores/
|
||||
│ │ ├── auth.ts # Auth state management
|
||||
│ │ └── index.ts # Store exports
|
||||
│ └── types/ # TypeScript types (to be added)
|
||||
└── routes/
|
||||
├── +layout.svelte # Root layout
|
||||
├── +page.svelte # Landing page (redirects to login)
|
||||
├── (app)/ # Protected app routes
|
||||
│ ├── +layout.svelte # App layout with sidebar
|
||||
│ └── dashboard/
|
||||
│ └── +page.svelte # Dashboard page
|
||||
└── (auth)/ # Public auth routes
|
||||
├── +layout.svelte # Auth layout (no sidebar)
|
||||
└── login/
|
||||
└── +page.svelte # Login page
|
||||
|
||||
build/ # Production build output
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- `svelte.config.js` - SvelteKit config with static adapter
|
||||
- `vite.config.ts` - Vite config with Tailwind plugin
|
||||
- `tsconfig.json` - TypeScript configuration
|
||||
- `.env` - Environment variables
|
||||
|
||||
## Features Implemented
|
||||
|
||||
- SvelteKit project initialized with TypeScript
|
||||
- Tailwind CSS v4 + DaisyUI v5 configured
|
||||
- Forest theme with medical green colors
|
||||
- Static adapter for static hosting
|
||||
- API client with fetch wrapper
|
||||
- Auth store for state management
|
||||
- Server hooks for auth middleware
|
||||
- Login page (converted from template)
|
||||
- Dashboard page with stats cards
|
||||
- Sidebar navigation component
|
||||
- Header component with user menu
|
||||
- Responsive layout
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env.local` file for local overrides:
|
||||
|
||||
```bash
|
||||
PUBLIC_API_BASE_URL=http://localhost/clqms01/
|
||||
PUBLIC_API_BASE_URL_PROD=https://clqms01-api.services-summit.my.id/
|
||||
```
|
||||
|
||||
## Next Steps (Phase 2)
|
||||
|
||||
1. Implement actual login API integration
|
||||
2. Add JWT token handling
|
||||
3. Create protected routes
|
||||
4. Add logout functionality
|
||||
5. Implement password change
|
||||
|
||||
## Build Output
|
||||
|
||||
The production build is generated in the `build/` folder and can be deployed to any static hosting service (Netlify, Vercel, GitHub Pages, etc.).
|
||||
|
||||
## Notes
|
||||
|
||||
- DaisyUI v5 uses Tailwind v4's new CSS-first configuration
|
||||
- The forest theme uses medical green (#2d6a4f) as primary color
|
||||
- Static adapter configured with fallback for SPA routing
|
||||
2826
api-docs.yaml
Normal file
2826
api-docs.yaml
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "fe",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"daisyui": "^5.5.18",
|
||||
"lucide-svelte": "^0.563.0",
|
||||
"svelte": "^5.49.2",
|
||||
"svelte-check": "^4.3.6",
|
||||
"sveltekit-superforms": "^2.29.1",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
1744
pnpm-lock.yaml
generated
Normal file
1744
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
91
src/app.css
Normal file
91
src/app.css
Normal file
@ -0,0 +1,91 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* DaisyUI Import */
|
||||
@plugin "daisyui" {
|
||||
themes: light --default, dark --prefersdark;
|
||||
}
|
||||
|
||||
/* Custom Theme Configuration */
|
||||
@plugin "daisyui/theme" {
|
||||
name: "forest";
|
||||
default: true;
|
||||
prefersdark: false;
|
||||
color-scheme: "light";
|
||||
|
||||
/* Primary - Medical Green */
|
||||
--color-primary: #2d6a4f;
|
||||
--color-primary-content: #ffffff;
|
||||
|
||||
/* Secondary - Light Green */
|
||||
--color-secondary: #52b788;
|
||||
--color-secondary-content: #ffffff;
|
||||
|
||||
/* Accent */
|
||||
--color-accent: #d8f3dc;
|
||||
--color-accent-content: #1b4332;
|
||||
|
||||
/* Neutral */
|
||||
--color-neutral: #40916c;
|
||||
--color-neutral-content: #ffffff;
|
||||
|
||||
/* Base colors */
|
||||
--color-base-100: #ffffff;
|
||||
--color-base-200: #f8fafc;
|
||||
--color-base-300: #e2e8f0;
|
||||
--color-base-content: #1e293b;
|
||||
|
||||
/* Info */
|
||||
--color-info: #3b82f6;
|
||||
--color-info-content: #ffffff;
|
||||
|
||||
/* Success */
|
||||
--color-success: #22c55e;
|
||||
--color-success-content: #ffffff;
|
||||
|
||||
/* Warning */
|
||||
--color-warning: #f59e0b;
|
||||
--color-warning-content: #ffffff;
|
||||
|
||||
/* Error */
|
||||
--color-error: #ef4444;
|
||||
--color-error-content: #ffffff;
|
||||
}
|
||||
|
||||
/* Custom utilities */
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
* {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-base-200);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-neutral);
|
||||
}
|
||||
15
src/app.d.ts
vendored
Normal file
15
src/app.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
user: { authenticated: boolean } | null;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
11
src/app.html
Normal file
11
src/app.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
12
src/hooks.server.ts
Normal file
12
src/hooks.server.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// Get auth token from cookies
|
||||
const authToken = event.cookies.get('auth_token');
|
||||
|
||||
// Add auth token to locals for use in load functions
|
||||
event.locals.user = authToken ? { authenticated: true } : null;
|
||||
|
||||
const response = await resolve(event);
|
||||
return response;
|
||||
};
|
||||
1
src/lib/assets/favicon.svg
Normal file
1
src/lib/assets/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
2
src/lib/components/index.ts
Normal file
2
src/lib/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Sidebar } from './layout/Sidebar.svelte';
|
||||
export { default as Header } from './layout/Header.svelte';
|
||||
45
src/lib/components/layout/Header.svelte
Normal file
45
src/lib/components/layout/Header.svelte
Normal file
@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { Bell, Search, Menu } from 'lucide-svelte';
|
||||
import { auth } from '$stores/auth';
|
||||
|
||||
let user: any = null;
|
||||
|
||||
auth.subscribe(state => {
|
||||
user = state.user;
|
||||
});
|
||||
</script>
|
||||
|
||||
<header class="h-16 bg-base-100 border-b border-base-300 flex items-center justify-between px-6 sticky top-0 z-40">
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn btn-ghost btn-sm btn-circle lg:hidden">
|
||||
<Menu class="w-5 h-5" />
|
||||
</button>
|
||||
<div class="relative">
|
||||
<Search class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-base-content/40" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
class="input input-sm input-bordered w-64 pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn btn-ghost btn-circle relative">
|
||||
<Bell class="w-5 h-5" />
|
||||
<span class="badge badge-sm badge-error absolute -top-1 -right-1">3</span>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="text-right hidden sm:block">
|
||||
<p class="text-sm font-medium">{user?.username || 'User'}</p>
|
||||
<p class="text-xs text-base-content/60 capitalize">{user?.role || 'User'}</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center">
|
||||
<span class="text-emerald-600 font-semibold">
|
||||
{user?.username?.charAt(0).toUpperCase() || 'U'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
79
src/lib/components/layout/Sidebar.svelte
Normal file
79
src/lib/components/layout/Sidebar.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { auth } from '$stores/auth';
|
||||
import { LayoutDashboard, Users, FlaskConical, ClipboardList, TestTube, Settings, LogOut } from 'lucide-svelte';
|
||||
|
||||
let user: any = null;
|
||||
|
||||
auth.subscribe(state => {
|
||||
user = state.user;
|
||||
});
|
||||
|
||||
function handleLogout() {
|
||||
auth.logout();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
|
||||
{ name: 'Patients', href: '/patients', icon: Users },
|
||||
{ name: 'Orders', href: '/orders', icon: ClipboardList },
|
||||
{ name: 'Tests', href: '/tests', icon: FlaskConical },
|
||||
{ name: 'Results', href: '/results', icon: TestTube },
|
||||
{ name: 'Settings', href: '/settings', icon: Settings },
|
||||
];
|
||||
</script>
|
||||
|
||||
<aside class="fixed left-0 top-0 h-full w-64 bg-base-200 border-r border-base-300 flex flex-col z-50">
|
||||
<!-- Logo -->
|
||||
<div class="p-6 border-b border-base-300">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-emerald-600">CLQMS</h1>
|
||||
<p class="text-xs text-base-content/60">Laboratory System</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 p-4">
|
||||
<ul class="menu menu-vertical gap-2">
|
||||
{#each menuItems as item}
|
||||
<li>
|
||||
<a href={item.href} class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-emerald-50 hover:text-emerald-600 transition-colors">
|
||||
<svelte:component this={item.icon} class="w-5 h-5" />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- User Section -->
|
||||
<div class="p-4 border-t border-base-300">
|
||||
<div class="flex items-center gap-3 p-3 rounded-lg bg-base-100">
|
||||
<div class="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center">
|
||||
<span class="text-emerald-600 font-semibold">
|
||||
{user?.username?.charAt(0).toUpperCase() || 'U'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium truncate">{user?.username || 'User'}</p>
|
||||
<p class="text-xs text-base-content/60 capitalize">{user?.role || 'User'}</p>
|
||||
</div>
|
||||
<button
|
||||
on:click={handleLogout}
|
||||
class="btn btn-ghost btn-sm btn-circle"
|
||||
title="Logout"
|
||||
>
|
||||
<LogOut class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
139
src/lib/server/api.ts
Normal file
139
src/lib/server/api.ts
Normal file
@ -0,0 +1,139 @@
|
||||
// API Client for CLQMS
|
||||
// Wrapper around fetch for making API calls to the backend
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { PUBLIC_API_BASE_URL, PUBLIC_API_BASE_URL_PROD } from '$env/static/public';
|
||||
|
||||
const API_BASE_URL = browser
|
||||
? (import.meta.env.DEV ? PUBLIC_API_BASE_URL : PUBLIC_API_BASE_URL_PROD)
|
||||
: PUBLIC_API_BASE_URL;
|
||||
|
||||
interface ApiError {
|
||||
message: string;
|
||||
status: number;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
data: T | null;
|
||||
error: ApiError | null;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private baseUrl: string;
|
||||
private defaultHeaders: Record<string, string>;
|
||||
|
||||
constructor(baseUrl: string = API_BASE_URL) {
|
||||
this.baseUrl = baseUrl.replace(/\/$/, '');
|
||||
this.defaultHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<ApiResponse<T>> {
|
||||
const url = `${this.baseUrl}/${endpoint.replace(/^\//, '')}`;
|
||||
|
||||
const config: RequestInit = {
|
||||
...options,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
...options.headers
|
||||
},
|
||||
credentials: 'include'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
return {
|
||||
data: null,
|
||||
error: {
|
||||
message: errorData.message || `HTTP Error: ${response.status}`,
|
||||
status: response.status,
|
||||
code: errorData.code
|
||||
},
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
let data: T;
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
data = await response.json();
|
||||
} else {
|
||||
data = await response.text() as unknown as T;
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
error: null,
|
||||
success: true
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
data: null,
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : 'Network error',
|
||||
status: 0
|
||||
},
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, { method: 'GET', headers });
|
||||
}
|
||||
|
||||
async post<T>(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
headers?: Record<string, string>
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
async patch<T>(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
headers?: Record<string, string>
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(body),
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
async put<T>(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
headers?: Record<string, string>
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
async delete<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, { method: 'DELETE', headers });
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new ApiClient();
|
||||
export type { ApiResponse, ApiError };
|
||||
47
src/lib/stores/auth.ts
Normal file
47
src/lib/stores/auth.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
function createAuthStore() {
|
||||
const { subscribe, set, update } = writable<AuthState>({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: true
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
login: (user: User) => {
|
||||
update(state => ({
|
||||
...state,
|
||||
user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false
|
||||
}));
|
||||
},
|
||||
logout: () => {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false
|
||||
});
|
||||
},
|
||||
setLoading: (loading: boolean) => {
|
||||
update(state => ({ ...state, isLoading: loading }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const auth = createAuthStore();
|
||||
export type { User, AuthState };
|
||||
1
src/lib/stores/index.ts
Normal file
1
src/lib/stores/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { auth, type User, type AuthState } from './auth';
|
||||
15
src/routes/(app)/+layout.svelte
Normal file
15
src/routes/(app)/+layout.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import '../../app.css';
|
||||
import Sidebar from '$components/layout/Sidebar.svelte';
|
||||
import Header from '$components/layout/Header.svelte';
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-base-100 flex">
|
||||
<Sidebar />
|
||||
<div class="flex-1 flex flex-col ml-64">
|
||||
<Header />
|
||||
<main class="flex-1 p-6 overflow-auto">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
184
src/routes/(app)/dashboard/+page.svelte
Normal file
184
src/routes/(app)/dashboard/+page.svelte
Normal file
@ -0,0 +1,184 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { auth } from '$stores/auth';
|
||||
import {
|
||||
Users,
|
||||
ClipboardList,
|
||||
TestTube,
|
||||
AlertCircle,
|
||||
TrendingUp,
|
||||
Calendar
|
||||
} from 'lucide-svelte';
|
||||
|
||||
let stats = {
|
||||
totalPatients: 1234,
|
||||
pendingOrders: 45,
|
||||
todayResults: 128,
|
||||
criticalResults: 3
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
auth.setLoading(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>CLQMS - Dashboard</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-base-content">Dashboard</h1>
|
||||
<p class="text-base-content/60 flex items-center gap-2 mt-1">
|
||||
<Calendar class="w-4 h-4" />
|
||||
{new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-primary gap-2">
|
||||
<TrendingUp class="w-4 h-4" />
|
||||
View Reports
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- Total Patients -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-base-content/60">Total Patients</p>
|
||||
<p class="text-3xl font-bold text-emerald-600">{stats.totalPatients}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-xl bg-emerald-100 flex items-center justify-center">
|
||||
<Users class="w-6 h-6 text-emerald-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center text-sm text-success">
|
||||
<TrendingUp class="w-4 h-4 mr-1" />
|
||||
<span>+12% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Orders -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-base-content/60">Pending Orders</p>
|
||||
<p class="text-3xl font-bold text-warning">{stats.pendingOrders}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-xl bg-warning/10 flex items-center justify-center">
|
||||
<ClipboardList class="w-6 h-6 text-warning" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<span class="text-sm text-base-content/60">Requires attention</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Today's Results -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-base-content/60">Today's Results</p>
|
||||
<p class="text-3xl font-bold text-info">{stats.todayResults}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-xl bg-info/10 flex items-center justify-center">
|
||||
<TestTube class="w-6 h-6 text-info" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<span class="text-sm text-base-content/60">Processed today</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Critical Results -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-base-content/60">Critical Results</p>
|
||||
<p class="text-3xl font-bold text-error">{stats.criticalResults}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-xl bg-error/10 flex items-center justify-center">
|
||||
<AlertCircle class="w-6 h-6 text-error" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<span class="text-sm text-error font-medium">Immediate attention required</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-lg mb-4">Quick Actions</h2>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<a href="/patients/new" class="btn btn-outline btn-primary h-auto py-4 flex flex-col gap-2">
|
||||
<Users class="w-6 h-6" />
|
||||
<span>New Patient</span>
|
||||
</a>
|
||||
<a href="/orders/new" class="btn btn-outline btn-primary h-auto py-4 flex flex-col gap-2">
|
||||
<ClipboardList class="w-6 h-6" />
|
||||
<span>New Order</span>
|
||||
</a>
|
||||
<a href="/results" class="btn btn-outline btn-primary h-auto py-4 flex flex-col gap-2">
|
||||
<TestTube class="w-6 h-6" />
|
||||
<span>View Results</span>
|
||||
</a>
|
||||
<a href="/settings" class="btn btn-outline btn-primary h-auto py-4 flex flex-col gap-2">
|
||||
<span class="text-2xl">⚙️</span>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="card bg-base-100 shadow-lg border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-lg mb-4">Recent Activity</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Action</th>
|
||||
<th>User</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>10:30 AM</td>
|
||||
<td>New patient registered</td>
|
||||
<td>Dr. Smith</td>
|
||||
<td><span class="badge badge-success">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10:15 AM</td>
|
||||
<td>Lab order created</td>
|
||||
<td>Nurse Johnson</td>
|
||||
<td><span class="badge badge-warning">Pending</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>09:45 AM</td>
|
||||
<td>Results uploaded</td>
|
||||
<td>Lab Tech Mike</td>
|
||||
<td><span class="badge badge-success">Completed</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
7
src/routes/(auth)/+layout.svelte
Normal file
7
src/routes/(auth)/+layout.svelte
Normal file
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import '../../app.css';
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20">
|
||||
<slot />
|
||||
</div>
|
||||
162
src/routes/(auth)/login/+page.svelte
Normal file
162
src/routes/(auth)/login/+page.svelte
Normal file
@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { superForm } from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { z } from 'zod';
|
||||
import { auth } from '$stores/auth';
|
||||
import { goto } from '$app/navigation';
|
||||
import { User, Lock, LogIn } from 'lucide-svelte';
|
||||
|
||||
const loginSchema = z.object({
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
remember: z.boolean().default(false)
|
||||
});
|
||||
|
||||
type LoginForm = z.infer<typeof loginSchema>;
|
||||
|
||||
let loading = false;
|
||||
let errorMessage = '';
|
||||
|
||||
async function handleSubmit(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
loading = true;
|
||||
errorMessage = '';
|
||||
|
||||
const formData = new FormData(event.target as HTMLFormElement);
|
||||
const username = formData.get('username') as string;
|
||||
const password = formData.get('password') as string;
|
||||
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
// const response = await api.post('/api/auth/login', { username, password });
|
||||
|
||||
// Simulate successful login
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
auth.login({
|
||||
id: '1',
|
||||
username: username,
|
||||
role: 'admin'
|
||||
});
|
||||
|
||||
goto('/dashboard');
|
||||
} catch (error) {
|
||||
errorMessage = 'Invalid username or password';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>CLQMS - Login</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="hero min-h-screen">
|
||||
<div class="hero-content flex-col">
|
||||
<!-- Logo Section -->
|
||||
<div class="text-center mb-6">
|
||||
<div class="w-20 h-20 mx-auto rounded-2xl bg-emerald-100 flex items-center justify-center mb-4 shadow-lg border-2 border-emerald-200">
|
||||
<svg class="w-12 h-12 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold text-emerald-600 mb-2">CLQMS</h1>
|
||||
<p class="text-base-content/60">Clinical Laboratory Quality Management System</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Card -->
|
||||
<div class="card bg-base-100 w-full max-w-sm shadow-2xl border-t-4 border-emerald-500">
|
||||
<div class="card-body p-8">
|
||||
<h2 class="text-2xl font-bold text-center text-emerald-700 mb-6">Welcome Back</h2>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="alert alert-error mb-4">
|
||||
<span>{errorMessage}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form on:submit={handleSubmit}>
|
||||
<!-- Username Field -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label" for="username">
|
||||
<span class="label-text font-medium">Username</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<User class="w-5 h-5 text-emerald-500" />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Enter your username"
|
||||
class="input input-bordered w-full pl-10 focus:border-emerald-500 focus:ring-emerald-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label" for="password">
|
||||
<span class="label-text font-medium">Password</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Lock class="w-5 h-5 text-emerald-500" />
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
class="input input-bordered w-full pl-10 focus:border-emerald-500 focus:ring-emerald-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me & Forgot Password -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<label class="label cursor-pointer flex items-center gap-2">
|
||||
<input type="checkbox" name="remember" class="checkbox checkbox-sm checkbox-primary" />
|
||||
<span class="label-text text-sm">Remember me</span>
|
||||
</label>
|
||||
<a href="#" class="text-sm text-emerald-600 hover:text-emerald-700 hover:underline">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<!-- Login Button -->
|
||||
<div class="form-control">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn bg-emerald-600 hover:bg-emerald-700 text-white shadow-lg hover:shadow-xl transition-all"
|
||||
disabled={loading}
|
||||
>
|
||||
{#if loading}
|
||||
<span class="loading loading-spinner loading-sm mr-2"></span>
|
||||
Signing in...
|
||||
{:else}
|
||||
<LogIn class="w-5 h-5 mr-2" />
|
||||
Sign In
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="divider text-sm text-base-content/40">or</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<p class="text-center text-sm text-base-content/60">
|
||||
Need help? Contact your system administrator
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-sm text-base-content/40">2024 CLQMS. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
src/routes/+layout.svelte
Normal file
12
src/routes/+layout.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import '../app.css';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
19
src/routes/+page.svelte
Normal file
19
src/routes/+page.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
goto('/login');
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta http-equiv="refresh" content="0;url=/login">
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center bg-base-100">
|
||||
<div class="text-center">
|
||||
<div class="loading loading-spinner loading-lg text-primary mb-4"></div>
|
||||
<p class="text-base-content/60">Redirecting...</p>
|
||||
</div>
|
||||
</div>
|
||||
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
22
svelte.config.js
Normal file
22
svelte.config.js
Normal file
@ -0,0 +1,22 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: 'index.html',
|
||||
precompress: false,
|
||||
strict: false
|
||||
}),
|
||||
alias: {
|
||||
'$components': 'src/lib/components',
|
||||
'$stores': 'src/lib/stores',
|
||||
'$types': 'src/lib/types',
|
||||
'$server': 'src/lib/server'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
297
templates/dashboard.html
Normal file
297
templates/dashboard.html
Normal file
@ -0,0 +1,297 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CLQMS - Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
<style>
|
||||
/* Mobile: drawer overlay behavior */
|
||||
@media (max-width: 1023px) {
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 40;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .sidebar-container {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 30;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .overlay {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop: slide and push behavior */
|
||||
@media (min-width: 1024px) {
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: relative;
|
||||
width: 0;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .page-wrapper .sidebar-container {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20">
|
||||
<input id="sidebar-toggle" type="checkbox" class="hidden" />
|
||||
<label for="sidebar-toggle" class="overlay"></label>
|
||||
|
||||
<div class="page-wrapper min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar-container">
|
||||
<div class="h-full w-80 bg-base-200 shadow-xl border-r border-base-300">
|
||||
<div class="p-4">
|
||||
<div class="flex items-center gap-3 px-2 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-800">CLQMS</span>
|
||||
</div>
|
||||
|
||||
<ul class="menu w-full">
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-2">Main</li>
|
||||
<li><a href="dashboard.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 active:bg-emerald-100">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a></li>
|
||||
<li><a href="master-data.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>
|
||||
Master Data
|
||||
</a></li>
|
||||
<li><a href="result-entry.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Result Entry
|
||||
</a></li>
|
||||
<li><a href="report-print.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
|
||||
Reports
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Laboratory</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Patients
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/></svg>
|
||||
Orders
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
Specimens
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Results
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Administration</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Organization
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
Users
|
||||
</a></li>
|
||||
<li class="mt-4 pt-4 border-t border-gray-200"><a href="login.html" class="text-red-500 hover:bg-red-50">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
Logout
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-sm border-b border-base-300">
|
||||
<div class="flex-none">
|
||||
<label for="sidebar-toggle" class="btn btn-square btn-ghost text-gray-600 hover:bg-emerald-50 hover:text-emerald-600 cursor-pointer">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl font-bold text-gray-800 hover:bg-gray-100">
|
||||
<svg class="w-6 h-6 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
||||
CLQMS Dashboard
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none gap-2">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar placeholder hover:bg-emerald-50">
|
||||
<div class="bg-emerald-100 text-emerald-700 rounded-full w-10 border-2 border-emerald-200 flex items-center justify-center">
|
||||
<span class="font-semibold">DL</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-emerald-200">
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>Profile</a></li>
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>Settings</a></li>
|
||||
<div class="divider my-0"></div>
|
||||
<li><a href="login.html" class="text-error hover:bg-red-50"><svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<!-- Summary Stats -->
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow-xl w-full mb-6 bg-base-100/80 backdrop-blur">
|
||||
<div class="stat border-l-4 border-emerald-500 hover:bg-emerald-50/50 transition-colors">
|
||||
<div class="stat-figure text-emerald-500">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="stat-title text-emerald-700">Pending Orders</div>
|
||||
<div class="stat-value text-emerald-600">24</div>
|
||||
<div class="stat-desc text-emerald-600/70">Jan 1 - Feb 8</div>
|
||||
</div>
|
||||
<div class="stat border-l-4 border-green-500 hover:bg-green-50/50 transition-colors">
|
||||
<div class="stat-figure text-green-500">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="stat-title text-green-700">Today's Results</div>
|
||||
<div class="stat-value text-green-600">156</div>
|
||||
<div class="stat-desc text-green-600/70">↗︎ 14% more than yesterday</div>
|
||||
</div>
|
||||
<div class="stat border-l-4 border-red-500 hover:bg-red-50/50 transition-colors">
|
||||
<div class="stat-figure text-red-500">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
|
||||
</div>
|
||||
<div class="stat-title text-red-700">Critical Results</div>
|
||||
<div class="stat-value text-red-600">3</div>
|
||||
<div class="stat-desc text-red-600/70">Requires attention</div>
|
||||
</div>
|
||||
<div class="stat border-l-4 border-teal-500 hover:bg-teal-50/50 transition-colors">
|
||||
<div class="stat-figure text-teal-500">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
</div>
|
||||
<div class="stat-title text-teal-700">Active Patients</div>
|
||||
<div class="stat-value text-teal-600">89</div>
|
||||
<div class="stat-desc text-teal-600/70">Currently in system</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border-t-4 border-emerald-500 hover:shadow-2xl transition-shadow">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-emerald-700">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/></svg>
|
||||
Orders Trend
|
||||
</h2>
|
||||
<div class="h-64 flex items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-50 rounded-lg border border-emerald-100">
|
||||
<p class="text-emerald-600/60">[Chart: Orders over time]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-100 shadow-xl border-t-4 border-teal-500 hover:shadow-2xl transition-shadow">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-teal-700">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/></svg>
|
||||
Results Volume
|
||||
</h2>
|
||||
<div class="h-64 flex items-center justify-center bg-gradient-to-br from-teal-50 to-cyan-50 rounded-lg border border-teal-100">
|
||||
<p class="text-teal-600/60">[Chart: Results by department]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="card bg-base-100 shadow-xl border-t-4 border-green-500 hover:shadow-2xl transition-shadow">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4 text-green-700">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Recent Activity
|
||||
</h2>
|
||||
<ul class="timeline timeline-vertical">
|
||||
<li>
|
||||
<div class="timeline-middle">
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-start timeline-box bg-gradient-to-r from-emerald-50 to-white border-l-4 border-emerald-500">
|
||||
<time class="text-sm font-mono text-emerald-600">09:30 AM</time>
|
||||
<div class="text-lg font-black text-emerald-800">Order #12345 created</div>
|
||||
<div class="text-sm text-emerald-600/80">Patient: John Doe (P-1001)</div>
|
||||
</div>
|
||||
<hr class="bg-emerald-300"/>
|
||||
</li>
|
||||
<li>
|
||||
<div class="timeline-middle">
|
||||
<div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-start timeline-box bg-gradient-to-r from-green-50 to-white border-l-4 border-green-500">
|
||||
<time class="text-sm font-mono text-green-600">09:15 AM</time>
|
||||
<div class="text-lg font-black text-green-800">Result received</div>
|
||||
<div class="text-sm text-green-600/80">Sample: ABC123 - Instrument: CBC-M01</div>
|
||||
</div>
|
||||
<hr class="bg-green-300"/>
|
||||
</li>
|
||||
<li>
|
||||
<div class="timeline-middle">
|
||||
<div class="w-8 h-8 rounded-full bg-teal-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-start timeline-box bg-gradient-to-r from-teal-50 to-white border-l-4 border-teal-500">
|
||||
<time class="text-sm font-mono text-teal-600">09:00 AM</time>
|
||||
<div class="text-lg font-black text-teal-800">Patient registered</div>
|
||||
<div class="text-sm text-teal-600/80">Patient ID: P-1001 - Jane Smith</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
98
templates/login.html
Normal file
98
templates/login.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CLQMS - Login</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
</head>
|
||||
<body class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20 flex items-center justify-center">
|
||||
<div class="hero min-h-screen">
|
||||
<div class="hero-content flex-col">
|
||||
<!-- Logo Section -->
|
||||
<div class="text-center mb-6">
|
||||
<div class="w-20 h-20 mx-auto rounded-2xl bg-emerald-100 flex items-center justify-center mb-4 shadow-lg border-2 border-emerald-200">
|
||||
<svg class="w-12 h-12 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-4xl font-bold text-emerald-600 mb-2">CLQMS</h1>
|
||||
<p class="text-base-content/60">Clinical Laboratory Quality Management System</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Card -->
|
||||
<div class="card bg-base-100 w-full max-w-sm shadow-2xl border-t-4 border-emerald-500">
|
||||
<div class="card-body p-8">
|
||||
<h2 class="text-2xl font-bold text-center text-emerald-700 mb-6">Welcome Back</h2>
|
||||
|
||||
<form>
|
||||
<!-- Username Field -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">Username</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" placeholder="Enter your username" class="input input-bordered w-full pl-10 focus:border-emerald-500 focus:ring-emerald-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">Password</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="password" placeholder="Enter your password" class="input input-bordered w-full pl-10 focus:border-emerald-500 focus:ring-emerald-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me & Forgot Password -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<label class="label cursor-pointer flex items-center gap-2">
|
||||
<input type="checkbox" class="checkbox checkbox-sm checkbox-emerald" />
|
||||
<span class="label-text text-sm">Remember me</span>
|
||||
</label>
|
||||
<a href="#" class="text-sm text-emerald-600 hover:text-emerald-700 hover:underline">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<!-- Login Button -->
|
||||
<div class="form-control">
|
||||
<button class="btn bg-emerald-600 hover:bg-emerald-700 text-white shadow-lg hover:shadow-xl transition-all">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="divider text-sm text-base-content/40">or</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<p class="text-center text-sm text-base-content/60">
|
||||
Need help? Contact your system administrator
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-sm text-base-content/40">© 2024 CLQMS. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
325
templates/master-data.html
Normal file
325
templates/master-data.html
Normal file
@ -0,0 +1,325 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CLQMS - Master Data - Locations</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
<style>
|
||||
/* Mobile: drawer overlay behavior */
|
||||
@media (max-width: 1023px) {
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 40;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .sidebar-container {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 30;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .overlay {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop: slide and push behavior */
|
||||
@media (min-width: 1024px) {
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: relative;
|
||||
width: 0;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .page-wrapper .sidebar-container {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20">
|
||||
<input id="sidebar-toggle" type="checkbox" class="hidden" />
|
||||
<label for="sidebar-toggle" class="overlay"></label>
|
||||
|
||||
<div class="page-wrapper min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar-container">
|
||||
<div class="h-full w-80 bg-base-200 shadow-xl border-r border-base-300">
|
||||
<div class="p-4">
|
||||
<div class="flex items-center gap-3 px-2 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-800">CLQMS</span>
|
||||
</div>
|
||||
|
||||
<ul class="menu w-full">
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-2">Main</li>
|
||||
<li><a href="dashboard.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a></li>
|
||||
<li><a href="master-data.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 active bg-emerald-100">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>
|
||||
Master Data
|
||||
</a></li>
|
||||
<li><a href="result-entry.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Result Entry
|
||||
</a></li>
|
||||
<li><a href="report-print.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
|
||||
Reports
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Laboratory</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Patients
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/></svg>
|
||||
Orders
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
Specimens
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Results
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Administration</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Organization
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
Users
|
||||
</a></li>
|
||||
<li class="mt-4 pt-4 border-t border-gray-200"><a href="login.html" class="text-red-500 hover:bg-red-50">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
Logout
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-sm border-b border-base-300">
|
||||
<div class="flex-none">
|
||||
<label for="sidebar-toggle" class="btn btn-square btn-ghost text-gray-600 hover:bg-emerald-50 hover:text-emerald-600 cursor-pointer">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl font-bold text-gray-800 hover:bg-gray-100">
|
||||
<svg class="w-6 h-6 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>
|
||||
Master Data - Locations
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none gap-2">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar placeholder hover:bg-emerald-50">
|
||||
<div class="bg-emerald-100 text-emerald-700 rounded-full w-10 border-2 border-emerald-200 flex items-center justify-center">
|
||||
<span class="font-semibold">DL</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-emerald-200">
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>Profile</a></li>
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>Settings</a></li>
|
||||
<div class="divider my-0"></div>
|
||||
<li><a href="login.html" class="text-error hover:bg-red-50"><svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<!-- Toolbar -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<div class="flex flex-col lg:flex-row gap-4 justify-between">
|
||||
<div class="join w-full lg:w-auto">
|
||||
<input type="text" placeholder="Search locations..." class="input input-bordered join-item" />
|
||||
<select class="select select-bordered join-item">
|
||||
<option>All Types</option>
|
||||
<option>Ward</option>
|
||||
<option>Clinic</option>
|
||||
<option>Laboratory</option>
|
||||
<option>Pharmacy</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="document.getElementById('location_modal').showModal()">Add New Location</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Location Code</th>
|
||||
<th>Location Name</th>
|
||||
<th>Location Type</th>
|
||||
<th>Site</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>LOC-001</th>
|
||||
<td>Main Ward</td>
|
||||
<td><div class="badge badge-ghost">Ward</div></td>
|
||||
<td>Site A</td>
|
||||
<td class="text-right">
|
||||
<div class="join justify-end">
|
||||
<button class="btn btn-sm btn-ghost">Edit</button>
|
||||
<button class="btn btn-sm btn-error btn-ghost">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>LOC-002</th>
|
||||
<td>Clinic A</td>
|
||||
<td><div class="badge badge-info">Clinic</div></td>
|
||||
<td>Site A</td>
|
||||
<td class="text-right">
|
||||
<div class="join justify-end">
|
||||
<button class="btn btn-sm btn-ghost">Edit</button>
|
||||
<button class="btn btn-sm btn-error btn-ghost">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>LOC-003</th>
|
||||
<td>Emergency Lab</td>
|
||||
<td><div class="badge badge-success">Laboratory</div></td>
|
||||
<td>Site B</td>
|
||||
<td class="text-right">
|
||||
<div class="join justify-end">
|
||||
<button class="btn btn-sm btn-ghost">Edit</button>
|
||||
<button class="btn btn-sm btn-error btn-ghost">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>LOC-004</th>
|
||||
<td>Main Pharmacy</td>
|
||||
<td><div class="badge badge-accent">Pharmacy</div></td>
|
||||
<td>Site A</td>
|
||||
<td class="text-right">
|
||||
<div class="join justify-end">
|
||||
<button class="btn btn-sm btn-ghost">Edit</button>
|
||||
<button class="btn btn-sm btn-error btn-ghost">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="join justify-center mt-4">
|
||||
<button class="join-item btn btn-sm" disabled>Previous</button>
|
||||
<button class="join-item btn btn-sm btn-active">1</button>
|
||||
<button class="join-item btn btn-sm">2</button>
|
||||
<button class="join-item btn btn-sm">3</button>
|
||||
<button class="join-item btn btn-sm">...</button>
|
||||
<button class="join-item btn btn-sm">10</button>
|
||||
<button class="join-item btn btn-sm">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Modal -->
|
||||
<dialog id="location_modal" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg text-primary mb-4">Add Location</h3>
|
||||
<form>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Location Code</span></label>
|
||||
<input type="text" placeholder="e.g., LOC-001" class="input input-bordered" />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Location Name</span></label>
|
||||
<input type="text" placeholder="e.g., Main Ward" class="input input-bordered" />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Location Type</span></label>
|
||||
<select class="select select-bordered">
|
||||
<option>Ward</option>
|
||||
<option>Clinic</option>
|
||||
<option>Laboratory</option>
|
||||
<option>Pharmacy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Site</span></label>
|
||||
<select class="select select-bordered">
|
||||
<option>Site A</option>
|
||||
<option>Site B</option>
|
||||
<option>Site C</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">Cancel</button>
|
||||
</form>
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
||||
331
templates/report-print.html
Normal file
331
templates/report-print.html
Normal file
@ -0,0 +1,331 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CLQMS - Results Report</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
<style>
|
||||
/* Mobile: drawer overlay behavior */
|
||||
@media (max-width: 1023px) {
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 40;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .sidebar-container {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 30;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .overlay {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop: slide and push behavior */
|
||||
@media (min-width: 1024px) {
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: relative;
|
||||
width: 0;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .page-wrapper .sidebar-container {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
.sidebar-container { display: none !important; }
|
||||
.navbar { display: none !important; }
|
||||
body { background: white; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20">
|
||||
<input id="sidebar-toggle" type="checkbox" class="hidden" />
|
||||
<label for="sidebar-toggle" class="overlay"></label>
|
||||
|
||||
<div class="page-wrapper min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar-container no-print">
|
||||
<div class="h-full w-80 bg-base-200 shadow-xl border-r border-base-300">
|
||||
<div class="p-4">
|
||||
<div class="flex items-center gap-3 px-2 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-800">CLQMS</span>
|
||||
</div>
|
||||
|
||||
<ul class="menu w-full">
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-2">Main</li>
|
||||
<li><a href="dashboard.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 active:bg-emerald-100">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a></li>
|
||||
<li><a href="master-data.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>
|
||||
Master Data
|
||||
</a></li>
|
||||
<li><a href="result-entry.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Result Entry
|
||||
</a></li>
|
||||
<li><a href="report-print.html" class="active text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
|
||||
Reports
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Laboratory</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Patients
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/></svg>
|
||||
Orders
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
Specimens
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Results
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Administration</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Organization
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
Users
|
||||
</a></li>
|
||||
<li class="mt-4 pt-4 border-t border-gray-200"><a href="login.html" class="text-red-500 hover:bg-red-50">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
Logout
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-sm border-b border-base-300 no-print">
|
||||
<div class="flex-none">
|
||||
<label for="sidebar-toggle" class="btn btn-square btn-ghost text-gray-600 hover:bg-emerald-50 hover:text-emerald-600 cursor-pointer">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl font-bold text-gray-800 hover:bg-gray-100">
|
||||
<svg class="w-6 h-6 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
|
||||
Results Report
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none gap-2 no-print">
|
||||
<button onclick="window.print()" class="btn btn-primary">Print Report</button>
|
||||
<button class="btn btn-ghost ml-2">Back to Entry</button>
|
||||
</div>
|
||||
<div class="flex-none gap-2 no-print">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar placeholder hover:bg-emerald-50">
|
||||
<div class="bg-emerald-100 text-emerald-700 rounded-full w-10 border-2 border-emerald-200 flex items-center justify-center">
|
||||
<span class="font-semibold">DL</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-emerald-200">
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>Profile</a></li>
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>Settings</a></li>
|
||||
<div class="divider my-0"></div>
|
||||
<li><a href="login.html" class="text-error hover:bg-red-50"><svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Content -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<div class="mockup-window bg-base-100 shadow-2xl max-w-4xl mx-auto">
|
||||
<div class="p-8">
|
||||
<!-- Report Header -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-primary mb-2">CLQMS</h1>
|
||||
<p class="text-lg text-base-content/70">Clinical Laboratory Quality Management System</p>
|
||||
<div class="divider"></div>
|
||||
<h2 class="text-2xl font-bold">Laboratory Results Report</h2>
|
||||
</div>
|
||||
|
||||
<!-- Patient Information -->
|
||||
<div class="mb-6">
|
||||
<h3 class="font-bold text-lg text-primary mb-3">Patient Information</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Patient Name:</span>
|
||||
<span class="ml-2 font-bold">John Doe</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Patient ID:</span>
|
||||
<span class="ml-2 font-bold">P-1001</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Date of Birth:</span>
|
||||
<span class="ml-2">1985-03-15</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Sex:</span>
|
||||
<span class="ml-2">Male</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Order Information -->
|
||||
<div class="mb-6">
|
||||
<h3 class="font-bold text-lg text-primary mb-3">Order Information</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Order ID:</span>
|
||||
<span class="ml-2 font-bold">ORD-2024001</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Order Date:</span>
|
||||
<span class="ml-2">2024-02-08 10:30</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Priority:</span>
|
||||
<div class="badge badge-success ml-2">Routine</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-base-content/50">Requesting Physician:</span>
|
||||
<span class="ml-2">Dr. Smith</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Results Table -->
|
||||
<div class="mb-6">
|
||||
<h3 class="font-bold text-lg text-primary mb-3">Test Results</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Test Code</th>
|
||||
<th>Test Name</th>
|
||||
<th>Result</th>
|
||||
<th>Unit</th>
|
||||
<th>Flag</th>
|
||||
<th>Reference Range</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="font-normal">CBC-HGB</th>
|
||||
<td>Hemoglobin</td>
|
||||
<td class="font-bold">145</td>
|
||||
<td>g/L</td>
|
||||
<td><div class="badge badge-success">N</div></td>
|
||||
<td>130-170</td>
|
||||
</tr>
|
||||
<tr class="bg-error/10">
|
||||
<th class="font-normal">CBC-WBC</th>
|
||||
<td>White Blood Cell Count</td>
|
||||
<td class="font-bold text-error">12.5</td>
|
||||
<td>10^9/L</td>
|
||||
<td><div class="badge badge-error">H</div></td>
|
||||
<td>4.0-11.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="font-normal">CBC-RBC</th>
|
||||
<td>Red Blood Cell Count</td>
|
||||
<td class="font-bold">4.8</td>
|
||||
<td>10^12/L</td>
|
||||
<td><div class="badge badge-success">N</div></td>
|
||||
<td>4.5-5.5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="font-normal">CBC-PLT</th>
|
||||
<td>Platelet Count</td>
|
||||
<td class="font-bold">250</td>
|
||||
<td>10^9/L</td>
|
||||
<td><div class="badge badge-success">N</div></td>
|
||||
<td>150-400</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="font-normal">CBC-HCT</th>
|
||||
<td>Hematocrit</td>
|
||||
<td class="font-bold">42</td>
|
||||
<td>%</td>
|
||||
<td><div class="badge badge-success">N</div></td>
|
||||
<td>40-50</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<div class="text-sm text-base-content/70 mb-4">
|
||||
<p><strong>Report Generated:</strong> 2024-02-08 14:30:00</p>
|
||||
<p><strong>Verified By:</strong> Dr. Lab User</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<svg class="w-6 h-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<span><strong>Interpretation:</strong> Results show elevated WBC count, may indicate infection. Follow-up recommended.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
318
templates/result-entry.html
Normal file
318
templates/result-entry.html
Normal file
@ -0,0 +1,318 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="forest">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CLQMS - Result Entry</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="shared.css">
|
||||
<style>
|
||||
/* Mobile: drawer overlay behavior */
|
||||
@media (max-width: 1023px) {
|
||||
.sidebar-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 40;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .sidebar-container {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 30;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .overlay {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop: slide and push behavior */
|
||||
@media (min-width: 1024px) {
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: relative;
|
||||
width: 0;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#sidebar-toggle:checked ~ .page-wrapper .sidebar-container {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-base-200 via-base-100 to-emerald-50/20">
|
||||
<input id="sidebar-toggle" type="checkbox" class="hidden" />
|
||||
<label for="sidebar-toggle" class="overlay"></label>
|
||||
|
||||
<div class="page-wrapper min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar-container">
|
||||
<div class="h-full w-80 bg-base-200 shadow-xl border-r border-base-300">
|
||||
<div class="p-4">
|
||||
<div class="flex items-center gap-3 px-2 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-800">CLQMS</span>
|
||||
</div>
|
||||
|
||||
<ul class="menu w-full">
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-2">Main</li>
|
||||
<li><a href="dashboard.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 active:bg-emerald-100">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
|
||||
Dashboard
|
||||
</a></li>
|
||||
<li><a href="master-data.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/></svg>
|
||||
Master Data
|
||||
</a></li>
|
||||
<li><a href="result-entry.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 bg-emerald-50 text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Result Entry
|
||||
</a></li>
|
||||
<li><a href="report-print.html" class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
|
||||
Reports
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Laboratory</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||
Patients
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/></svg>
|
||||
Orders
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/></svg>
|
||||
Specimens
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Results
|
||||
</a></li>
|
||||
|
||||
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-4">Administration</li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||
Organization
|
||||
</a></li>
|
||||
<li><a class="text-gray-700 hover:bg-emerald-50 hover:text-emerald-700">
|
||||
<svg class="w-5 h-5 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
Users
|
||||
</a></li>
|
||||
<li class="mt-4 pt-4 border-t border-gray-200"><a href="login.html" class="text-red-500 hover:bg-red-50">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
Logout
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="navbar bg-base-100 shadow-sm border-b border-base-300">
|
||||
<div class="flex-none">
|
||||
<label for="sidebar-toggle" class="btn btn-square btn-ghost text-gray-600 hover:bg-emerald-50 hover:text-emerald-600 cursor-pointer">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl font-bold text-gray-800 hover:bg-gray-100">
|
||||
<svg class="w-6 h-6 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Result Entry
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none gap-2">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar placeholder hover:bg-emerald-50">
|
||||
<div class="bg-emerald-100 text-emerald-700 rounded-full w-10 border-2 border-emerald-200 flex items-center justify-center">
|
||||
<span class="font-semibold">DL</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-emerald-200">
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>Profile</a></li>
|
||||
<li><a class="hover:bg-emerald-50"><svg class="w-4 h-4 mr-2 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>Settings</a></li>
|
||||
<div class="divider my-0"></div>
|
||||
<li><a href="login.html" class="text-error hover:bg-red-50"><svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Entry Content -->
|
||||
<main class="flex-1 overflow-auto p-6">
|
||||
<!-- Order Selection -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<h3 class="font-bold text-lg text-primary mb-4">Select Order</h3>
|
||||
<div class="flex flex-col lg:flex-row gap-4">
|
||||
<div class="form-control flex-1">
|
||||
<label class="label"><span class="label-text">Order ID</span></label>
|
||||
<input type="text" placeholder="Enter order ID" class="input input-bordered" />
|
||||
</div>
|
||||
<div class="form-control flex-1">
|
||||
<label class="label"><span class="label-text">Patient ID</span></label>
|
||||
<input type="text" placeholder="Enter patient ID" class="input input-bordered" />
|
||||
</div>
|
||||
<div class="form-control flex-none">
|
||||
<label class="label"> </label>
|
||||
<button class="btn btn-primary">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Patient & Order Info -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<h3 class="font-bold text-lg text-primary mb-4">Order Information</h3>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Patient Name</span></label>
|
||||
<input type="text" value="John Doe" class="input input-bordered input-disabled" disabled />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Patient ID</span></label>
|
||||
<input type="text" value="P-1001" class="input input-bordered input-disabled" disabled />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Order ID</span></label>
|
||||
<input type="text" value="ORD-2024001" class="input input-bordered input-disabled" disabled />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Priority</span></label>
|
||||
<div class="badge badge-success">Routine</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Entry Form -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-lg text-primary">Test Results</h3>
|
||||
<div role="alert" class="alert alert-warning">
|
||||
<svg class="w-6 h-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<span>1 critical result detected</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Test Code</th>
|
||||
<th>Test Name</th>
|
||||
<th>Result Value</th>
|
||||
<th>Unit</th>
|
||||
<th>Flag</th>
|
||||
<th>Reference Range</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hover">
|
||||
<th class="font-normal">CBC-HGB</th>
|
||||
<td>Hemoglobin</td>
|
||||
<td>
|
||||
<input type="text" value="145" class="input input-bordered input-sm w-full" />
|
||||
</td>
|
||||
<td>g/L</td>
|
||||
<td><div class="badge badge-success">N</div></td>
|
||||
<td class="text-sm">130-170</td>
|
||||
</tr>
|
||||
<tr class="hover">
|
||||
<th class="font-normal">CBC-WBC</th>
|
||||
<td>White Blood Cell Count</td>
|
||||
<td>
|
||||
<input type="text" value="12.5" class="input input-bordered input-sm w-full input-error" />
|
||||
</td>
|
||||
<td>10^9/L</td>
|
||||
<td><div class="badge badge-error">H</div></td>
|
||||
<td class="text-sm">4.0-11.0</td>
|
||||
</tr>
|
||||
<tr class="hover">
|
||||
<th class="font-normal">CBC-RBC</th>
|
||||
<td>Red Blood Cell Count</td>
|
||||
<td>
|
||||
<input type="text" placeholder="Enter value" class="input input-bordered input-sm w-full" />
|
||||
</td>
|
||||
<td>10^12/L</td>
|
||||
<td><div class="badge badge-ghost">-</div></td>
|
||||
<td class="text-sm">4.5-5.5</td>
|
||||
</tr>
|
||||
<tr class="hover">
|
||||
<th class="font-normal">CBC-PLT</th>
|
||||
<td>Platelet Count</td>
|
||||
<td>
|
||||
<input type="text" placeholder="Enter value" class="input input-bordered input-sm w-full" />
|
||||
</td>
|
||||
<td>10^9/L</td>
|
||||
<td><div class="badge badge-ghost">-</div></td>
|
||||
<td class="text-sm">150-400</td>
|
||||
</tr>
|
||||
<tr class="hover">
|
||||
<th class="font-normal">CBC-HCT</th>
|
||||
<td>Hematocrit</td>
|
||||
<td>
|
||||
<input type="text" placeholder="Enter value" class="input input-bordered input-sm w-full" />
|
||||
</td>
|
||||
<td>%</td>
|
||||
<td><div class="badge badge-ghost">-</div></td>
|
||||
<td class="text-sm">40-50</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex flex-col lg:flex-row gap-4 justify-end mt-6">
|
||||
<button class="btn btn-ghost">Clear</button>
|
||||
<button class="btn btn-ghost">View Report</button>
|
||||
<button class="btn btn-primary">Save Results</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
5
templates/shared.css
Normal file
5
templates/shared.css
Normal file
@ -0,0 +1,5 @@
|
||||
/* shared.css - Override forest theme colors with hex */
|
||||
[data-theme="forest"] {
|
||||
--color-primary: #2d6a4f; /* Dark medical green */
|
||||
--color-primary-content: #ffffff;
|
||||
}
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
7
vite.config.ts
Normal file
7
vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()]
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user