diff --git a/docs/frontend-implementation-plan.md b/docs/frontend-implementation-plan.md index 88a902a..2f03681 100644 --- a/docs/frontend-implementation-plan.md +++ b/docs/frontend-implementation-plan.md @@ -166,7 +166,7 @@ System ValueSets are pre-defined lookup values used throughout the application ( --- -### Phase 2: Patient Management\n**Priority:** High | **Time Estimate:** 3-4 hours\n\n**Dependencies:** Master Data (locations, contacts, occupations, provinces/cities)\n\n#### 2a. Patient CRUD\n- [ ] Patients list page with pagination and search\n- [ ] Patient create form with validation\n- [ ] Patient edit form\n- [ ] Patient detail view\n- [ ] Patient delete with confirmation\n\n**API Endpoints:**\n- `GET /api/patient` - List patients (query: page, perPage, InternalPID, PatientID, Name, Birthdate)\n- `POST /api/patient` - Create patient\n- `PATCH /api/patient` - Update patient\n- `DELETE /api/patient` - Delete patient (soft delete, body: { InternalPID })\n- `GET /api/patient/{id}` - Get patient by ID\n- `GET /api/patient/check` - Check if patient exists (query: PatientID, EmailAddress1) +### Phase 2: Patient Management\n**Priority:** High | **Time Estimate:** 3-4 hours\n\n**Dependencies:** Master Data (locations, contacts, occupations, provinces/cities)\n\n#### 2a. Patient CRUD ✅ COMPLETED\n- [x] Patients list page with pagination and search\n- [x] Patient create form with validation (multi-step)\n- [x] Patient edit form (multi-step modal)\n- [x] Patient detail view (via edit modal)\n- [x] Patient delete with confirmation\n\n**API Endpoints:**\n- `GET /api/patient` - List patients (query: page, perPage, InternalPID, PatientID, Name, Birthdate)\n- `POST /api/patient` - Create patient\n- `PATCH /api/patient` - Update patient\n- `DELETE /api/patient` - Delete patient (soft delete, body: { InternalPID })\n- `GET /api/patient/{id}` - Get patient by ID\n- `GET /api/patient/check` - Check if patient exists (query: PatientID, EmailAddress1) #### 2b. Advanced Patient Features - [ ] Patient identifier management (KTP, PASS, SSN, etc.) diff --git a/src/lib/api/patients.js b/src/lib/api/patients.js new file mode 100644 index 0000000..8b89090 --- /dev/null +++ b/src/lib/api/patients.js @@ -0,0 +1,98 @@ +import { get, post, patch, del } from './client.js'; + +/** + * Fetch patients list with optional filters and pagination + * @param {Object} params - Query parameters + * @param {number} [params.page=1] - Page number + * @param {number} [params.perPage=20] - Items per page + * @param {number} [params.InternalPID] - Filter by internal patient ID + * @param {string} [params.PatientID] - Filter by patient ID + * @param {string} [params.Name] - Search by patient name + * @param {string} [params.Birthdate] - Filter by birthdate (YYYY-MM-DD) + * @returns {Promise} API response with patient data and pagination + */ +export async function fetchPatients(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/patient?${query}` : '/api/patient'); +} + +/** + * Fetch a single patient by ID + * @param {number} id - Internal patient ID + * @returns {Promise} API response with patient details + */ +export async function fetchPatient(id) { + return get(`/api/patient/${id}`); +} + +/** + * Check if a patient exists by PatientID or Email + * @param {Object} params - Check parameters + * @param {string} [params.PatientID] - Patient ID to check + * @param {string} [params.EmailAddress1] - Email to check + * @returns {Promise} API response with exists flag + */ +export async function checkPatientExists(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/patient/check?${query}` : '/api/patient/check'); +} + +/** + * Create a new patient + * @param {Object} data - Patient data + * @param {string} data.PatientID - Patient identifier (required) + * @param {string} data.NameFirst - First name (required) + * @param {string} data.Sex - Sex code: '1' (Female) or '2' (Male) (required) + * @param {string} data.Birthdate - Birthdate in ISO 8601 format (required) + * @param {string} [data.AlternatePID] - Alternate patient ID + * @param {string} [data.Prefix] - Title prefix (Mr, Mrs, Ms, Dr, Prof) + * @param {string} [data.NameMiddle] - Middle name + * @param {string} [data.NameLast] - Last name + * @param {string} [data.NameMaiden] - Maiden name + * @param {string} [data.Suffix] - Name suffix + * @param {string} [data.PlaceOfBirth] - Place of birth + * @param {string} [data.Citizenship] - Citizenship + * @param {string} [data.Street_1] - Street address line 1 + * @param {string} [data.Street_2] - Street address line 2 + * @param {string} [data.Street_3] - Street address line 3 + * @param {string} [data.ZIP] - ZIP code + * @param {string} [data.Province] - Province area code + * @param {string} [data.City] - City area code + * @param {string} [data.Country] - Country + * @param {string} [data.Phone] - Phone number + * @param {string} [data.MobilePhone] - Mobile phone number + * @param {string} [data.EmailAddress1] - Primary email + * @param {string} [data.EmailAddress2] - Secondary email + * @param {Object} [data.PatIdt] - Patient identifier object + * @param {string} [data.PatIdt.IdentifierType] - Identifier type (KTP, PASS, SSN, SIM, KTAS) + * @param {string} [data.PatIdt.Identifier] - Identifier value + * @param {string} [data.Race] - Race + * @param {string} [data.MaritalStatus] - Marital status (A, B, D, M, S, W) + * @param {string} [data.Religion] - Religion + * @param {string} [data.Ethnic] - Ethnicity + * @param {string} [data.DeathIndicator] - Death indicator (Y/N) + * @param {string} [data.TimeOfDeath] - Time of death + * @param {string} [data.PatCom] - Patient comments + * @returns {Promise} API response + */ +export async function createPatient(data) { + return post('/api/patient', data); +} + +/** + * Update an existing patient + * @param {Object} data - Patient data (must include PatientID) + * @returns {Promise} API response + */ +export async function updatePatient(data) { + return patch('/api/patient', data); +} + +/** + * Delete a patient (soft delete) + * @param {number} internalPid - Internal patient ID + * @returns {Promise} API response + */ +export async function deletePatient(internalPid) { + return del('/api/patient', { body: JSON.stringify({ InternalPID: internalPid }) }); +} diff --git a/src/routes/(app)/patients/+page.svelte b/src/routes/(app)/patients/+page.svelte new file mode 100644 index 0000000..0cae175 --- /dev/null +++ b/src/routes/(app)/patients/+page.svelte @@ -0,0 +1,1007 @@ + + +
+
+
+

Patients

+

Manage patient records

+
+ +
+ + +
+
+
+ e.key === 'Enter' && handleSearch()} + /> + +
+ +
+
+ + +
+ ({ + ...p, + FullName: [p.Prefix, p.NameFirst, p.NameMiddle, p.NameLast, p.Suffix] + .filter(Boolean) + .join(' '), + SexLabel: p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-', + BirthdateFormatted: p.Birthdate + ? new Date(p.Birthdate).toLocaleDateString() + : '-', + }))} + {loading} + emptyMessage="No patients found" + hover={true} + bordered={false} + > + {#snippet cell({ column, row, value })} + {#if column.key === 'actions'} +
+ + +
+ {:else} + {value} + {/if} + {/snippet} +
+ + + {#if totalPages > 1} +
+
+ Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems} patients +
+
+ + + Page {currentPage} of {totalPages} + + +
+
+ {/if} +
+
+ + + + +
+
+ +
+ +
+ +
+ +
+
+ +
e.preventDefault()}> + + {#if currentStep === 1} +
+
+
+ + + {#if formErrors.PatientID} + {formErrors.PatientID} + {/if} +
+ +
+ + +
+
+ +
+
+ + + {#if formErrors.NameFirst} + {formErrors.NameFirst} + {/if} +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + {#if formErrors.Sex} + {formErrors.Sex} + {/if} +
+ + + {#if formErrors.Birthdate} + {formErrors.Birthdate} + {/if} +
+
+ +
+
+ + +
+
+ + +
+
+
+ {/if} + + + {#if currentStep === 2} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ + +
+ +
+ + +
+
+ {/if} + + + {#if currentStep === 3} +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+

Identification Document

+
+ +
+ + +
+
+
+
+ {/if} + + + {#if currentStep === 4} +
+
+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+

Other Information

+
+ + {#if formData.DeathIndicator === 'Y'} +
+ + +
+ {/if} +
+
+ +
+ + +
+
+ {/if} +
+ + {#snippet footer()} +
+
+ {#if currentStep > 1} + + {/if} +
+
+ + {#if currentStep < 4} + + {:else} + + {/if} +
+
+ {/snippet} +
+ + + +
+

+ Are you sure you want to delete patient {deleteItem?.PatientID}? +

+

This action cannot be undone.

+
+ {#snippet footer()} + + + {/snippet} +