190 lines
5.0 KiB
Markdown
190 lines
5.0 KiB
Markdown
|
|
# CLQMS Frontend - Code Style & Conventions
|
||
|
|
|
||
|
|
## File Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
src/
|
||
|
|
lib/
|
||
|
|
api/ # API client and endpoints (per feature)
|
||
|
|
stores/ # Svelte stores (auth, config, valuesets)
|
||
|
|
components/ # Reusable components (DataTable, Modal, Sidebar)
|
||
|
|
utils/ # Utility functions (toast, helpers)
|
||
|
|
assets/ # Static assets (favicon, etc.)
|
||
|
|
routes/ # SvelteKit routes
|
||
|
|
(app)/ # Protected routes (authenticated users)
|
||
|
|
dashboard/
|
||
|
|
patients/
|
||
|
|
master-data/
|
||
|
|
login/ # Public routes
|
||
|
|
```
|
||
|
|
|
||
|
|
## Svelte Component Conventions
|
||
|
|
|
||
|
|
### Component Structure Order
|
||
|
|
1. Imports (Svelte, $app, $lib, external)
|
||
|
|
2. Props with `$bindable` for two-way binding
|
||
|
|
3. State (`$state`)
|
||
|
|
4. Derived state (`$derived`)
|
||
|
|
5. Effects (`$effect`)
|
||
|
|
6. Functions (prefix handlers with `handle`)
|
||
|
|
|
||
|
|
### Props and State
|
||
|
|
```svelte
|
||
|
|
let { open = $bindable(false), title = '', children, footer } = $props();
|
||
|
|
let loading = $state(false);
|
||
|
|
let error = $state('');
|
||
|
|
```
|
||
|
|
|
||
|
|
### 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, prefer `$lib`)
|
||
|
|
|
||
|
|
## 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`
|
||
|
|
|
||
|
|
## Code Style
|
||
|
|
|
||
|
|
- **ES Modules**: Always use `import`/`export`
|
||
|
|
- **Semicolons**: Use 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
|
||
|
|
|
||
|
|
## Component Patterns
|
||
|
|
|
||
|
|
### Modal Pattern
|
||
|
|
```svelte
|
||
|
|
<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>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Form State with Validation
|
||
|
|
```javascript
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Client Patterns
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// src/lib/api/client.js - Base client
|
||
|
|
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 });
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 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
|
||
|
|
|
||
|
|
## LocalStorage
|
||
|
|
|
||
|
|
- Only access in browser: check `browser` from `$app/environment`
|
||
|
|
- Use descriptive keys: `clqms_username`, `clqms_remember`, `auth_token`
|
||
|
|
|
||
|
|
## Runtime Config Pattern
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
import { config } from '$lib/stores/config.js';
|
||
|
|
function getApiUrl() {
|
||
|
|
return config.getApiUrl() || import.meta.env.VITE_API_URL || '';
|
||
|
|
}
|
||
|
|
```
|