306 lines
10 KiB
PHP
306 lines
10 KiB
PHP
<?= $this->extend('layouts/v2') ?>
|
|
|
|
<?= $this->section('content') ?>
|
|
|
|
<div x-data="patientView">
|
|
|
|
<!-- Loading State -->
|
|
<template x-if="isLoading">
|
|
<div class="flex justify-center items-center py-24">
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Error State -->
|
|
<template x-if="error && !isLoading">
|
|
<div class="max-w-lg mx-auto">
|
|
<div class="alert alert-error">
|
|
<i data-lucide="alert-circle" class="w-5 h-5"></i>
|
|
<span x-text="error"></span>
|
|
</div>
|
|
<div class="text-center mt-4">
|
|
<a href="<?= site_url('v2/patients') ?>" class="btn btn-ghost">Back to Patient List</a>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Patient Detail -->
|
|
<template x-if="!isLoading && !error && patient">
|
|
<div>
|
|
|
|
<!-- Page Header -->
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
|
|
<div class="flex items-center gap-4">
|
|
<a href="<?= site_url('v2/patients') ?>" class="btn btn-ghost btn-sm btn-square">
|
|
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
|
</a>
|
|
<div>
|
|
<h2 class="text-2xl font-bold" x-text="fullName"></h2>
|
|
<div class="flex items-center gap-2 text-base-content/60">
|
|
<span class="badge badge-primary" x-text="patient.PatientID || 'No MRN'"></span>
|
|
<span x-text="patient.Gender || ''"></span>
|
|
<span>•</span>
|
|
<span x-text="patient.Age || ''"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<a :href="'<?= site_url('v2/patients/edit/') ?>' + patient.InternalPID" class="btn btn-primary gap-2">
|
|
<i data-lucide="pencil" class="w-4 h-4"></i>
|
|
Edit
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
<!-- Left Column: Main Info -->
|
|
<div class="lg:col-span-2 space-y-6">
|
|
|
|
<!-- Personal Information -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="user" class="w-5 h-5"></i>
|
|
Personal Information
|
|
</h3>
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mt-4">
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Full Name</div>
|
|
<div class="font-medium" x-text="fullName"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Gender</div>
|
|
<div class="font-medium" x-text="patient.Gender || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Birthdate</div>
|
|
<div class="font-medium" x-text="patient.BirthdateConversion || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Age</div>
|
|
<div class="font-medium" x-text="patient.Age || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Place of Birth</div>
|
|
<div class="font-medium" x-text="patient.PlaceOfBirth || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Marital Status</div>
|
|
<div class="font-medium" x-text="patient.MaritalStatus || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Religion</div>
|
|
<div class="font-medium" x-text="patient.Religion || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Ethnic</div>
|
|
<div class="font-medium" x-text="patient.Ethnic || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Citizenship</div>
|
|
<div class="font-medium" x-text="patient.Citizenship || '-'"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Information -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="phone" class="w-5 h-5"></i>
|
|
Contact Information
|
|
</h3>
|
|
|
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Mobile Phone</div>
|
|
<div class="font-medium" x-text="patient.MobilePhone || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Phone</div>
|
|
<div class="font-medium" x-text="patient.Phone || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Email</div>
|
|
<div class="font-medium" x-text="patient.EmailAddress1 || '-'"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Alternate Email</div>
|
|
<div class="font-medium" x-text="patient.EmailAddress2 || '-'"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="map-pin" class="w-5 h-5"></i>
|
|
Address
|
|
</h3>
|
|
|
|
<div class="mt-4">
|
|
<div class="font-medium" x-text="fullAddress || 'No address on file'"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Right Column: Sidebar Info -->
|
|
<div class="space-y-6">
|
|
|
|
<!-- Identifier -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="id-card" class="w-5 h-5"></i>
|
|
Identifier
|
|
</h3>
|
|
|
|
<template x-if="patient.PatIdt">
|
|
<div class="mt-4">
|
|
<div class="text-sm text-base-content/60" x-text="patient.PatIdt.IdentifierType"></div>
|
|
<div class="font-mono font-medium text-lg" x-text="patient.PatIdt.Identifier"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<template x-if="!patient.PatIdt">
|
|
<div class="text-base-content/50 mt-4">No identifier on file</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Info -->
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="info" class="w-5 h-5"></i>
|
|
System Info
|
|
</h3>
|
|
|
|
<div class="space-y-3 mt-4">
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Internal ID</div>
|
|
<div class="font-mono" x-text="patient.InternalPID"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Created</div>
|
|
<div x-text="patient.CreateDate || '-'"></div>
|
|
</div>
|
|
<template x-if="patient.LinkTo">
|
|
<div>
|
|
<div class="text-sm text-base-content/60">Linked Patients</div>
|
|
<template x-for="link in patient.LinkTo" :key="link.InternalPID">
|
|
<a
|
|
:href="'<?= site_url('v2/patients/') ?>' + link.InternalPID"
|
|
class="link link-primary"
|
|
x-text="link.PatientID"
|
|
></a>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Comments -->
|
|
<template x-if="patient.PatCom">
|
|
<div class="card bg-base-100 shadow">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<i data-lucide="message-square" class="w-5 h-5"></i>
|
|
Comments
|
|
</h3>
|
|
<div class="mt-4 whitespace-pre-wrap" x-text="patient.PatCom"></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
</div>
|
|
|
|
<?= $this->endSection() ?>
|
|
|
|
<?= $this->section('script') ?>
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('patientView', () => ({
|
|
patient: null,
|
|
isLoading: true,
|
|
error: null,
|
|
|
|
async init() {
|
|
await this.loadPatient();
|
|
},
|
|
|
|
get fullName() {
|
|
if (!this.patient) return '';
|
|
return [
|
|
this.patient.Prefix,
|
|
this.patient.NameFirst,
|
|
this.patient.NameMiddle,
|
|
this.patient.NameLast,
|
|
this.patient.Suffix
|
|
].filter(Boolean).join(' ');
|
|
},
|
|
|
|
get fullAddress() {
|
|
if (!this.patient) return '';
|
|
return [
|
|
this.patient.Street_1,
|
|
this.patient.Street_2,
|
|
this.patient.Street_3,
|
|
this.patient.City,
|
|
this.patient.Province,
|
|
this.patient.ZIP,
|
|
this.patient.Country
|
|
].filter(Boolean).join(', ');
|
|
},
|
|
|
|
async loadPatient() {
|
|
const patientId = <?= json_encode($patientId ?? null) ?>;
|
|
|
|
if (!patientId) {
|
|
this.error = 'Patient ID not provided';
|
|
this.isLoading = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('<?= site_url('api/patient/') ?>' + patientId, {
|
|
credentials: 'include'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'success') {
|
|
this.patient = data.data;
|
|
} else {
|
|
this.error = data.message || 'Failed to load patient';
|
|
}
|
|
} catch (err) {
|
|
this.error = 'Connection error: ' + err.message;
|
|
} finally {
|
|
this.isLoading = false;
|
|
// Re-init icons when loading state changes
|
|
setTimeout(() => {
|
|
if(window.lucide) window.lucide.createIcons();
|
|
}, 50);
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
</script>
|
|
<?= $this->endSection() ?>
|