clqms-fe1/AGENTS.md
mahdahar 99d622ad05 refactor(tests): consolidate test management modal components
- Consolidate fragmented test modal components into unified TestFormModal.svelte
- Reorganize reference range components into organized tabs (RefNumTab, RefTxtTab)
- Add new tab components: BasicInfoTab, TechDetailsTab, CalcDetailsTab, MappingsTab, GroupMembersTab
- Move test-related type definitions to src/lib/types/test.types.ts for better type organization
- Delete old component files: TestModal.svelte and 10+ sub-components
- Add backup directory for old component preservation
- Update AGENTS.md with coding guidelines and project conventions
- Update tests.js API client with improved structure
- Add documentation for frontend test management architecture
2026-02-20 13:51:54 +07:00

278 lines
7.4 KiB
Markdown

# AGENTS.md - Coding Guidelines for CLQMS Frontend
## Project Overview
SvelteKit frontend for Clinical Laboratory Quality Management System (CLQMS). Uses Svelte 5 runes, TailwindCSS 4, DaisyUI, and Lucide icons.
## Build Commands
```bash
# Development server
pnpm run dev
# Production build
pnpm run build
# Preview production build
pnpm run preview
# Sync SvelteKit (runs automatically on install)
pnpm run prepare
```
## Testing
**No test framework configured yet.** When adding tests:
- Use Vitest for unit tests (recommended with SvelteKit)
- Use Playwright for E2E tests
- Run single test: `vitest run src/path/to/test.js`
- Run tests in watch mode: `vitest`
## Code Style Guidelines
### JavaScript/TypeScript
- **ES Modules**: Always use `import`/`export` (type: "module" in package.json)
- **Semicolons**: Use semicolons consistently
- **Quotes**: Use single quotes for strings
- **Indentation**: 2 spaces
- **Trailing commas**: Use in multi-line objects/arrays
- **JSDoc**: Document all exported functions with JSDoc comments
### Svelte Components
```svelte
<script>
// 1. Imports - Svelte, $app, $lib, external
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { auth } from '$lib/stores/auth.js';
import { login } from '$lib/api/auth.js';
import { User, Lock } from 'lucide-svelte';
// 2. Props with $bindable for two-way binding
let { open = $bindable(false), title = '', children, footer } = $props();
// 3. State
let loading = $state(false);
let error = $state('');
// 4. Derived state (if needed)
let isValid = $derived(username.length > 0);
// 5. Effects
$effect(() => { /* side effects */ });
// 6. Functions - prefix handlers with 'handle'
function handleSubmit() { /* implementation */ }
</script>
```
### Naming Conventions
- **Components**: PascalCase (`LoginForm.svelte`, `PatientFormModal.svelte`)
- **Files/Routes**: lowercase with hyphens (`+page.svelte`, `user-profile/`)
- **Variables**: camelCase (`isLoading`, `userName`)
- **Constants**: UPPER_SNAKE_CASE (`API_URL`, `STORAGE_KEY`)
- **Stores**: camelCase, descriptive (`auth`, `userStore`)
- **Event handlers**: prefix with `handle` (`handleSubmit`, `handleClick`)
- **Form state**: `formLoading`, `formError`, `deleteConfirmOpen`
### Imports Order
1. Svelte imports (`svelte`, `$app/*`)
2. $lib aliases (`$lib/stores/*`, `$lib/api/*`, `$lib/components/*`, `$lib/utils/*`)
3. External libraries (`lucide-svelte`)
4. Relative imports (minimize these, prefer `$lib`)
### Project Structure
```
src/
lib/
api/ # API client and endpoints (per feature)
stores/ # Svelte stores (auth, config, valuesets)
components/ # Reusable components (DataTable, Modal, Sidebar)
utils/ # Utilities (toast, helpers)
assets/ # Static assets
routes/ # SvelteKit routes
(app)/ # Route groups (protected)
login/
dashboard/
```
### API Client Patterns
```javascript
// src/lib/api/client.js - Base client handles auth, 401 redirects
import { apiClient, get, post, put, patch, del } from '$lib/api/client.js';
// src/lib/api/feature.js - Feature-specific endpoints with JSDoc
export async function fetchItems(params = {}) {
const query = new URLSearchParams(params).toString();
return get(query ? `/api/items?${query}` : '/api/items');
}
export async function createItem(data) {
return post('/api/items', data);
}
export async function updateItem(data) {
return patch('/api/items', data);
}
export async function deleteItem(id) {
return del(`/api/items/${id}`);
}
```
### Store Patterns
```javascript
// Use writable for stores with localStorage persistence
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
function createStore() {
const getInitialState = () => {
if (!browser) return { data: null };
return { data: JSON.parse(localStorage.getItem('key')) };
};
const { subscribe, set, update } = writable(getInitialState());
return {
subscribe,
setData: (data) => {
if (browser) localStorage.setItem('key', JSON.stringify(data));
set({ data });
}
};
}
```
### Component Patterns
```svelte
<!-- Use $bindable for two-way props -->
let { open = $bindable(false), selected = $bindable(null) } = $props();
<!-- Use snippets for slot content -->
{@render children?.()}
{@render footer()}
<!-- Dialog with backdrop handling -->
<dialog class="modal" class:modal-open={open}>
<div class="modal-box">
<button onclick={close}>X</button>
{@render children?.()}
</div>
<form method="dialog" class="modal-backdrop" onclick={handleBackdropClick}>
<button>close</button>
</form>
</dialog>
```
### Error Handling
```javascript
try {
const result = await api.fetchData();
toastSuccess('Operation successful');
} catch (err) {
const message = err.message || 'An unexpected error occurred';
toastError(message);
console.error('Operation failed:', err);
}
```
### Styling with Tailwind & DaisyUI
- DaisyUI components: `btn`, `card`, `alert`, `input`, `navbar`, `modal`, `dropdown`, `menu`
- Color scheme: `primary` (emerald), `secondary` (dark blue), `accent` (royal blue)
- Custom compact classes: `.compact-y`, `.compact-p`, `.compact-input`, `.compact-btn`, `.compact-card`
- Size modifiers: `.btn-sm`, `.input-sm`, `.select-sm` for compact forms
### Authentication Patterns
- Auth state in `$lib/stores/auth.js` using writable store
- Check `$auth.isAuthenticated` in layout `onMount`
- Redirect to `/login` if unauthenticated using `goto('/login')`
- API client auto-redirects on 401 responses
### Runtime Config Pattern
```javascript
// Use runtime config for API URL that can be changed at runtime
import { config } from '$lib/stores/config.js';
function getApiUrl() {
return config.getApiUrl() || import.meta.env.VITE_API_URL || '';
}
```
### LocalStorage
- Only access in browser: check `browser` from `$app/environment`
- Use descriptive keys: `clqms_username`, `clqms_remember`, `auth_token`
### Form Handling Patterns
```javascript
// Form state with validation
let formLoading = $state(false);
let formError = $state('');
let formData = $state({ username: '', password: '' });
function validateForm() {
formError = '';
if (!formData.username.trim()) {
formError = 'Username is required';
return false;
}
return true;
}
async function handleSubmit() {
if (!validateForm()) return;
formLoading = true;
try {
await api.submit(formData);
toastSuccess('Success');
} catch (err) {
formError = err.message;
} finally {
formLoading = false;
}
}
```
### Toast/Notification System
```javascript
import { success, error, info, warning } from '$lib/utils/toast.js';
// Use toast notifications for user feedback
success('Item created successfully');
error('Failed to create item');
info('New message received');
warning('Action requires confirmation');
```
## Proxy Configuration
API requests to `/api` are proxied to `http://localhost:8000` in dev. See `vite.config.js`.
## API Documentation
API Reference (Swagger UI): https://clqms01-api.services-summit.my.id/swagger/
## Important Notes
- No ESLint or Prettier configured yet - add if needed
- No test framework configured yet
- Uses Svelte 5 runes: `$props`, `$state`, `$derived`, `$effect`, `$bindable`
- SvelteKit file-based routing with `+page.svelte`, `+layout.svelte`
- Static adapter configured for static export
- Runtime config allows API URL changes without rebuild