feat(medical-store): add mobile apps QR hub on login page, pharmacy ledgers, credit accounts, and 6-tier staff permissions

This commit is contained in:
DevSoftTech Server 2026-09-18 05:11:02 +05:30
parent c49361c0fa
commit ecd596e0fb
5 changed files with 6786 additions and 529 deletions

View File

@ -1726,6 +1726,37 @@ $routes->group('api/v1', ['namespace' => 'App\Controllers\Api\v1'], static funct
$routes->get('medical-store/returns/lookup-invoice', 'MedicalStoreApi::lookupInvoiceForReturn');
$routes->get('medical-store/returns/recent', 'MedicalStoreApi::recentReturns');
$routes->get('medical-store/returns/credit-note/(:num)', 'MedicalStoreApi::getCreditNoteInvoice/$1');
// Stock Audit & Physical Reconciliation
$routes->post('medical-store/stock/reconcile', 'MedicalStoreApi::reconcileStock');
$routes->get('medical-store/stock/audit-list', 'MedicalStoreApi::getStockAuditList');
$routes->post('medical-store/stock/batch-reconcile', 'MedicalStoreApi::batchReconcileStock');
// Dedicated Medical Store User Management & Operator Session
$routes->get('medical-store/users', 'MedicalStoreApi::getStoreUsers');
$routes->post('medical-store/users/save', 'MedicalStoreApi::saveStoreUser');
$routes->post('medical-store/users/switch-operator', 'MedicalStoreApi::switchOperator');
// Staff & Customer Credit Accounts & Ledgers
$routes->get('medical-store/credit/accounts', 'MedicalStoreApi::getCreditAccounts');
$routes->post('medical-store/credit/save-account', 'MedicalStoreApi::saveCreditAccount');
$routes->get('medical-store/credit/ledger/(:num)', 'MedicalStoreApi::getCreditLedger/$1');
$routes->post('medical-store/credit/record-payment', 'MedicalStoreApi::recordCreditPayment');
// Mobile Rack Collector & Draft Bills
$routes->post('medical-store/drafts/save', 'MedicalStoreApi::saveDraftBill');
$routes->get('medical-store/drafts/list', 'MedicalStoreApi::getDraftBills');
$routes->post('medical-store/drafts/cancel', 'MedicalStoreApi::cancelDraftBill');
// Mobile Dashboard & Analytics
$routes->get('medical-store/mobile/dashboard', 'MedicalStoreApi::getMobileDashboardData');
// Server Network Info & QR Pairing
$routes->get('medical-store/server-info', 'MedicalStoreApi::getServerNetworkInfo');
// Purchase Invoice Photo Upload & Verification
$routes->post('medical-store/purchase/upload-photo', 'MedicalStoreApi::uploadPurchasePhoto');
$routes->post('medical-store/purchase/verify', 'MedicalStoreApi::verifyPurchase');
});
// Dedicated Direct Store Links (e.g. /MedicalStore/storeA, /MedicalStore/storeB)

File diff suppressed because it is too large Load Diff

View File

@ -114,12 +114,41 @@
</div>
<?php endif ?>
<!-- Mobile PWA Quick Scan Button inside Login Card -->
<hr class="my-2 opacity-25">
<div class="col-12 text-center">
<button type="button" class="btn btn-outline-primary w-100 py-2 d-flex align-items-center justify-content-center gap-2 fw-semibold shadow-sm" id="btn_open_mobile_qr_modal" data-bs-toggle="modal" data-bs-target="#loginMobileAppsModal" style="border-radius: 8px;">
<i class="bi bi-qr-code-scan fs-5"></i>
<span>Scan QR for Mobile Apps (PWA)</span>
</button>
<div class="small text-muted mt-1" style="font-size: 11px;">
<i class="bi bi-phone me-1"></i> Scan with phone camera &bull; Install to home screen
</div>
</div>
</form>
</div>
</div>
<div class="text-center mt-1">
<!-- Mobile Application QR & PWA Installation Card -->
<div class="card border-0 shadow-sm mt-1 mb-2 w-100 text-center" style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); border-radius: 12px; color: white;">
<div class="card-body p-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge bg-primary px-2 py-1" style="font-size: 10px;"><i class="bi bi-phone me-1"></i> Mobile PWA Suite</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;"><i class="bi bi-download me-1"></i> Installable Apps</span>
</div>
<h6 class="fw-bold text-white mb-1" style="font-size: 14px;"><i class="bi bi-qr-code-scan me-1 text-info"></i> Hospital Mobile Apps &amp; QR Codes</h6>
<p class="text-light small mb-2 opacity-75" style="font-size: 11px; line-height: 1.4;">
Instant home-screen installation on Android &amp; iPhone. Scan QR code to launch Nursing, Doctor, Pharmacy POS &amp; Token Queue apps.
</p>
<button type="button" class="btn btn-primary btn-sm w-100 fw-bold d-flex align-items-center justify-content-center gap-2 shadow-sm py-2" data-bs-toggle="modal" data-bs-target="#loginMobileAppsModal" style="border-radius: 8px;">
<i class="bi bi-qr-code"></i> View All Mobile Apps &amp; QR Codes
</button>
</div>
</div>
<div class="text-center mt-1 mb-3">
<a href="<?= base_url('HMS_FEATURES_PRESENTATION.html') ?>" target="_blank" rel="noopener"
class="text-muted small d-inline-flex align-items-center gap-1" style="text-decoration:none;">
<i class="bi bi-file-earmark-text"></i> View HMS Features &amp; Capabilities
@ -135,6 +164,719 @@
</div>
</main>
<?php
// Detect server network interfaces for LAN QR generation
$detectedNetworkInterfaces = [];
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$netOutput = @shell_exec('ipconfig');
if ($netOutput) {
$netLines = explode("\n", $netOutput);
$curAdapter = '';
foreach ($netLines as $nLine) {
$nLine = trim($nLine);
if (preg_match('/^(adapter|Wireless LAN adapter|Ethernet adapter|Unknown adapter)\s+([^:]+):/i', $nLine, $adM)) {
$curAdapter = trim($adM[2]);
}
if (preg_match('/IPv4 Address[ .:]+([0-9.]+)/i', $nLine, $ipM)) {
$foundIp = trim($ipM[1]);
if ($foundIp !== '127.0.0.1') {
$label = $curAdapter ? ($foundIp . ' (' . $curAdapter . ')') : $foundIp;
$detectedNetworkInterfaces[$foundIp] = $label;
}
}
}
}
} else {
$netOutput = @shell_exec("hostname -I 2>/dev/null");
if ($netOutput) {
$parts = preg_split('/\s+/', trim($netOutput));
foreach ($parts as $foundIp) {
$foundIp = trim($foundIp);
if ($foundIp && $foundIp !== '127.0.0.1') {
$detectedNetworkInterfaces[$foundIp] = $foundIp . ' (LAN)';
}
}
}
}
$serverPort = $_SERVER['SERVER_PORT'] ?? '8080';
$httpHost = $_SERVER['HTTP_HOST'] ?? 'localhost:8080';
$requestScheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
// Best default IP: Wi-Fi if present, else first LAN IP, else current host
$defaultIp = '';
foreach ($detectedNetworkInterfaces as $ipKey => $ipLabel) {
if (stripos($ipLabel, 'Wi-Fi') !== false || stripos($ipLabel, 'Wireless') !== false) {
$defaultIp = $ipKey;
break;
}
}
if (!$defaultIp && !empty($detectedNetworkInterfaces)) {
$defaultIp = array_key_first($detectedNetworkInterfaces);
}
if (!$defaultIp) {
$defaultIp = explode(':', $httpHost)[0];
}
$defaultHost = ($defaultIp === 'localhost' || $defaultIp === '127.0.0.1') ? $httpHost : ($defaultIp . ':' . $serverPort);
?>
<!-- ========================================================================= -->
<!-- MODAL: All Hospital Mobile Applications & QR Codes Hub (PWA Installation) -->
<!-- ========================================================================= -->
<div class="modal fade" id="loginMobileAppsModal" tabindex="-1" aria-labelledby="loginMobileAppsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content shadow-lg border-0" style="border-radius: 16px; overflow: hidden;">
<!-- Modal Header -->
<div class="modal-header text-white py-3 px-4" style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);">
<div class="d-flex align-items-center gap-3">
<img src="<?= base_url('assets/img/logo.png') ?>" alt="HMS Logo" style="height: 36px; width: auto; filter: brightness(0) invert(1);" onError="this.style.display='none'">
<div>
<h5 class="modal-title fw-bold mb-0 text-white d-flex align-items-center gap-2" id="loginMobileAppsModalLabel">
<i class="bi bi-phone text-info"></i> Hospital Mobile Applications &amp; QR Codes Hub
</h5>
<span class="small text-light opacity-75" style="font-size: 12px;">
Progressive Web Applications (PWA) &bull; Native Home Screen Installation Type &bull; No App Store Required
</span>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-light btn-sm fw-semibold d-none d-md-flex align-items-center gap-1" id="btn_print_qr_sheet" onclick="printAllMobileQRs()">
<i class="bi bi-printer"></i> Print Counter Poster
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<!-- Modal Body -->
<div class="modal-body p-4" style="background: #f8fafc;">
<!-- Network & Wi-Fi IP Selector Strip -->
<div class="card border-0 shadow-sm mb-4" style="background: #ffffff; border-radius: 12px; border-left: 4px solid #2563eb !important;">
<div class="card-body p-3">
<div class="row align-items-center g-2">
<div class="col-lg-5 col-md-12">
<div class="d-flex align-items-center gap-2">
<div class="bg-primary-subtle text-primary rounded-circle p-2 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-wifi fs-5"></i>
</div>
<div>
<div class="fw-bold text-dark small">Hospital Wi-Fi / LAN IP for Mobile QR:</div>
<div class="text-muted" style="font-size: 11px;">
Phones on hospital Wi-Fi connect directly using this IP address.
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-7">
<select class="form-select form-select-sm fw-bold border-primary" id="mobile_qr_host_select" onchange="onMobileQrHostChanged(this.value)">
<?php if (!empty($detectedNetworkInterfaces)): ?>
<?php foreach ($detectedNetworkInterfaces as $ipVal => $ipDesc): ?>
<?php $isDef = ($ipVal === $defaultIp); ?>
<option value="<?= esc($ipVal . ':' . $serverPort) ?>" <?= $isDef ? 'selected' : '' ?>>
📶 <?= esc($ipDesc) ?>:<?= esc($serverPort) ?> <?= $isDef ? '★ (Recommended)' : '' ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
<option value="<?= esc($httpHost) ?>">💻 Browser Host (<?= esc($httpHost) ?>)</option>
<option value="custom">✏️ Custom Domain / IP Address...</option>
</select>
</div>
<div class="col-lg-3 col-md-5">
<div class="input-group input-group-sm" id="custom_host_input_group" style="display: none;">
<input type="text" class="form-control" id="custom_host_input" placeholder="e.g. 192.168.1.50:8080" value="<?= esc($defaultHost) ?>">
<button class="btn btn-primary" type="button" onclick="applyCustomHost()">Apply</button>
</div>
<div id="selected_host_badge" class="text-end text-success small fw-bold" style="font-size: 11px;">
<i class="bi bi-check-circle-fill me-1"></i> QR Target: <span id="current_qr_host_display"><?= esc($defaultHost) ?></span>
</div>
</div>
</div>
</div>
</div>
<!-- Navigation Tabs: All QR Grid vs Single App vs PWA Installation Guide -->
<ul class="nav nav-pills mb-3 gap-2" id="mobileAppsTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active fw-bold px-3 py-2" id="tab-all-apps-grid" data-bs-toggle="pill" data-bs-target="#pane-all-apps-grid" type="button" role="tab" aria-controls="pane-all-apps-grid" aria-selected="true">
<i class="bi bi-grid-fill me-1"></i> All Mobile QR Codes (5 Apps Grid)
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-3 py-2" id="tab-single-focus" data-bs-toggle="pill" data-bs-target="#pane-single-focus" type="button" role="tab" aria-controls="pane-single-focus" aria-selected="false">
<i class="bi bi-aspect-ratio me-1"></i> Large QR Focus View
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-3 py-2 bg-white text-dark border" id="tab-install-guide" data-bs-toggle="pill" data-bs-target="#pane-install-guide" type="button" role="tab" aria-controls="pane-install-guide" aria-selected="false">
<i class="bi bi-download text-primary me-1"></i> How to Install App on Mobile (PWA Guide)
</button>
</li>
</ul>
<!-- Tabs Content -->
<div class="tab-content" id="mobileAppsTabContent">
<!-- TAB 1: ALL APPS QR GRID -->
<div class="tab-pane fade show active" id="pane-all-apps-grid" role="tabpanel" aria-labelledby="tab-all-apps-grid">
<div class="row g-3" id="all_apps_qr_container">
<!-- 1. Unified Mobile Hub (All-in-One) -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border shadow-sm p-3 app-qr-card" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #0f172a !important;">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge bg-dark text-white px-2 py-1 small">🌟 Unified Hub</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;">PWA Installable</span>
</div>
<h6 class="fw-bold text-dark mb-1 d-flex align-items-center gap-2">
<i class="bi bi-phone-fill text-primary"></i> HMS Mobile Apps Hub
</h6>
<p class="text-muted small mb-2" style="font-size: 11px; min-height: 32px;">
All-in-one hospital portal. Single home screen installation for Doctor, Nurse, Pharmacy &amp; Queue.
</p>
<div class="text-center my-2 p-2 bg-light rounded border">
<img id="qr_img_hub" class="img-fluid bg-white p-1 rounded shadow-sm border app-qr-image" src="" alt="HMS Apps Hub QR" style="width: 170px; height: 170px; object-fit: contain;">
<div class="mt-1 small fw-semibold text-secondary" style="font-size: 10px;" id="qr_url_txt_hub">/app</div>
</div>
<div class="d-flex gap-1 mt-auto pt-2">
<button type="button" class="btn btn-outline-secondary btn-sm flex-fill" onclick="copyAppUrl('hub')" title="Copy URL">
<i class="bi bi-clipboard"></i> Copy Link
</button>
<a id="qr_link_hub" href="/app" target="_blank" class="btn btn-primary btn-sm flex-fill fw-semibold">
<i class="bi bi-box-arrow-up-right"></i> Open on PC
</a>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="focusAppView('hub')" title="Zoom View">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
</div>
<!-- 2. Medical Store & Multi-Building Pharmacy POS -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border shadow-sm p-3 app-qr-card" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #0891b2 !important;">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge text-white px-2 py-1 small" style="background: #0891b2;">💊 Pharmacy &amp; POS</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;">PWA Installable</span>
</div>
<h6 class="fw-bold text-dark mb-1 d-flex align-items-center gap-2">
<i class="bi bi-capsule" style="color: #0891b2;"></i> Medical Store Companion
</h6>
<p class="text-muted small mb-2" style="font-size: 11px; min-height: 32px;">
Rack/Shelf Draft Bill Collector, Stock Shelf Auditor, Inward Bill Camera &amp; Expiry Monitor.
</p>
<div class="text-center my-2 p-2 bg-light rounded border">
<img id="qr_img_pharmacy" class="img-fluid bg-white p-1 rounded shadow-sm border app-qr-image" src="" alt="Pharmacy Companion QR" style="width: 170px; height: 170px; object-fit: contain;">
<div class="mt-1 small fw-semibold text-secondary" style="font-size: 10px;" id="qr_url_txt_pharmacy">/MedicalStore</div>
</div>
<div class="d-flex gap-1 mt-auto pt-2">
<button type="button" class="btn btn-outline-secondary btn-sm flex-fill" onclick="copyAppUrl('pharmacy')" title="Copy URL">
<i class="bi bi-clipboard"></i> Copy Link
</button>
<a id="qr_link_pharmacy" href="/MedicalStore" target="_blank" class="btn btn-info text-white btn-sm flex-fill fw-semibold" style="background: #0891b2; border-color: #0891b2;">
<i class="bi bi-box-arrow-up-right"></i> Open on PC
</a>
<button type="button" class="btn btn-outline-info btn-sm" onclick="focusAppView('pharmacy')" title="Zoom View">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
</div>
<!-- 3. Nursing Care Bedside Mobile PWA -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border shadow-sm p-3 app-qr-card" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #2563eb !important;">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge bg-primary text-white px-2 py-1 small">❤️ IPD Nursing</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;">PWA Installable</span>
</div>
<h6 class="fw-bold text-dark mb-1 d-flex align-items-center gap-2">
<i class="bi bi-heart-pulse-fill text-primary"></i> Nursing Care Bedside App
</h6>
<p class="text-muted small mb-2" style="font-size: 11px; min-height: 32px;">
IPD Bedside Vitals (BP, SpO2, Pulse), Intake/Output Charting &amp; Paper Document Camera Scanner.
</p>
<div class="text-center my-2 p-2 bg-light rounded border">
<img id="qr_img_nursing" class="img-fluid bg-white p-1 rounded shadow-sm border app-qr-image" src="" alt="Nursing Care QR" style="width: 170px; height: 170px; object-fit: contain;">
<div class="mt-1 small fw-semibold text-secondary" style="font-size: 10px;" id="qr_url_txt_nursing">/app/nursing</div>
</div>
<div class="d-flex gap-1 mt-auto pt-2">
<button type="button" class="btn btn-outline-secondary btn-sm flex-fill" onclick="copyAppUrl('nursing')" title="Copy URL">
<i class="bi bi-clipboard"></i> Copy Link
</button>
<a id="qr_link_nursing" href="/app/nursing" target="_blank" class="btn btn-primary btn-sm flex-fill fw-semibold">
<i class="bi bi-box-arrow-up-right"></i> Open on PC
</a>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="focusAppView('nursing')" title="Zoom View">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
</div>
<!-- 4. DoctorCare EMR & Rounds PWA -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border shadow-sm p-3 app-qr-card" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #059669 !important;">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge text-white px-2 py-1 small" style="background: #059669;">🩺 Doctor EMR</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;">PWA Installable</span>
</div>
<h6 class="fw-bold text-dark mb-1 d-flex align-items-center gap-2">
<i class="bi bi-person-badge-fill text-success"></i> DoctorCare Rounds &amp; EMR
</h6>
<p class="text-muted small mb-2" style="font-size: 11px; min-height: 32px;">
Doctor Ward Rounds, Clinical Notes, OPD Prescriptions, Lab Investigation &amp; Vitals review.
</p>
<div class="text-center my-2 p-2 bg-light rounded border">
<img id="qr_img_doctor" class="img-fluid bg-white p-1 rounded shadow-sm border app-qr-image" src="" alt="Doctor EMR QR" style="width: 170px; height: 170px; object-fit: contain;">
<div class="mt-1 small fw-semibold text-secondary" style="font-size: 10px;" id="qr_url_txt_doctor">/app/doctor</div>
</div>
<div class="d-flex gap-1 mt-auto pt-2">
<button type="button" class="btn btn-outline-secondary btn-sm flex-fill" onclick="copyAppUrl('doctor')" title="Copy URL">
<i class="bi bi-clipboard"></i> Copy Link
</button>
<a id="qr_link_doctor" href="/app/doctor" target="_blank" class="btn btn-success btn-sm flex-fill fw-semibold" style="background: #059669; border-color: #059669;">
<i class="bi bi-box-arrow-up-right"></i> Open on PC
</a>
<button type="button" class="btn btn-outline-success btn-sm" onclick="focusAppView('doctor')" title="Zoom View">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
</div>
<!-- 5. OPD Token Tracker TV Display -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border shadow-sm p-3 app-qr-card" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #d97706 !important;">
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge text-white px-2 py-1 small" style="background: #d97706;">📺 Token Display</span>
<span class="badge bg-success-subtle text-success border border-success-subtle" style="font-size: 10px;">PWA Installable</span>
</div>
<h6 class="fw-bold text-dark mb-1 d-flex align-items-center gap-2">
<i class="bi bi-display-fill text-warning"></i> OPD Token Tracker Display
</h6>
<p class="text-muted small mb-2" style="font-size: 11px; min-height: 32px;">
Waiting Room Token Calling Screen, Live Room Numbers, TTS Audio Voice &amp; TV display mode.
</p>
<div class="text-center my-2 p-2 bg-light rounded border">
<img id="qr_img_queue" class="img-fluid bg-white p-1 rounded shadow-sm border app-qr-image" src="" alt="OPD Queue QR" style="width: 170px; height: 170px; object-fit: contain;">
<div class="mt-1 small fw-semibold text-secondary" style="font-size: 10px;" id="qr_url_txt_queue">/app/queue</div>
</div>
<div class="d-flex gap-1 mt-auto pt-2">
<button type="button" class="btn btn-outline-secondary btn-sm flex-fill" onclick="copyAppUrl('queue')" title="Copy URL">
<i class="bi bi-clipboard"></i> Copy Link
</button>
<a id="qr_link_queue" href="/app/queue" target="_blank" class="btn btn-warning btn-sm flex-fill fw-semibold text-dark">
<i class="bi bi-box-arrow-up-right"></i> Open on PC
</a>
<button type="button" class="btn btn-outline-warning btn-sm text-dark" onclick="focusAppView('queue')" title="Zoom View">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
</div>
<!-- 6. Standee / Print Quick Banner Card -->
<div class="col-xl-4 col-md-6">
<div class="card h-100 border-0 shadow-sm p-4 text-center d-flex flex-column align-items-center justify-content-center" style="border-radius: 14px; background: linear-gradient(135deg, #1e293b 0%, #334155 100%); color: white;">
<i class="bi bi-printer text-info fs-1 mb-2"></i>
<h6 class="fw-bold text-white mb-1">Print Counter QR Posters</h6>
<p class="small text-light opacity-75 mb-3" style="font-size: 11px;">
Print clean acrylic standees / posters for pharmacy rack, nursing station, or OPD desks.
</p>
<button type="button" class="btn btn-light btn-sm fw-bold px-4 py-2 text-dark shadow-sm" onclick="printAllMobileQRs()">
<i class="bi bi-printer me-1"></i> Print All 5 QR Codes
</button>
</div>
</div>
</div>
</div>
<!-- TAB 2: SINGLE APP FOCUS VIEW -->
<div class="tab-pane fade" id="pane-single-focus" role="tabpanel" aria-labelledby="tab-single-focus">
<div class="row g-4 align-items-center justify-content-center py-2">
<div class="col-lg-5 col-md-6 text-center">
<div class="p-3 bg-white rounded-4 shadow-sm border d-inline-block">
<img id="focus_qr_img" src="" alt="Focused QR" style="width: 250px; height: 250px; object-fit: contain;" class="rounded border p-2 bg-white">
</div>
<div class="mt-2 text-muted small" style="font-size: 11px;">
<i class="bi bi-camera me-1"></i> Point phone camera to scan
</div>
</div>
<div class="col-lg-6 col-md-6">
<div class="mb-3">
<label class="form-label fw-bold small text-secondary">SELECT APPLICATION:</label>
<select class="form-select form-select-lg fw-bold" id="focus_app_select" onchange="renderFocusApp(this.value)">
<option value="hub">🌟 HMS Mobile Apps Hub (All-in-One)</option>
<option value="pharmacy">💊 Medical Store &amp; Multi-Building Pharmacy POS</option>
<option value="nursing">❤️ Nursing Care Bedside Assistant</option>
<option value="doctor">🩺 DoctorCare EMR &amp; Rounds</option>
<option value="queue">📺 OPD Token Tracker &amp; TV Queue Display</option>
</select>
</div>
<div class="card border p-3 mb-3 bg-white" style="border-radius: 12px;">
<h5 class="fw-bold text-dark mb-1" id="focus_app_title">HMS Mobile Apps Hub</h5>
<span class="badge bg-primary mb-2 align-self-start" id="focus_app_badge">Active PWA &bull; Installation Type</span>
<p class="text-muted small mb-2" id="focus_app_desc" style="font-size: 12px;">
Central launchpad with 1-click home screen install.
</p>
<div class="input-group input-group-sm mb-2">
<input type="text" class="form-control text-primary font-monospace" id="focus_app_url" readonly>
<button class="btn btn-outline-primary" type="button" onclick="copyFocusUrl()">
<i class="bi bi-clipboard"></i> Copy
</button>
</div>
</div>
<div class="d-flex gap-2">
<a id="focus_app_launch" href="#" target="_blank" class="btn btn-primary btn-sm flex-fill fw-bold py-2">
<i class="bi bi-box-arrow-up-right me-1"></i> Open on Computer
</a>
<button type="button" class="btn btn-outline-dark btn-sm fw-bold py-2" onclick="printSingleQR()">
<i class="bi bi-printer me-1"></i> Print QR Standee
</button>
</div>
</div>
</div>
</div>
<!-- TAB 3: PWA INSTALLATION GUIDE -->
<div class="tab-pane fade" id="pane-install-guide" role="tabpanel" aria-labelledby="tab-install-guide">
<div class="row g-3">
<!-- Android Instructions -->
<div class="col-md-6">
<div class="card h-100 border-0 shadow-sm p-3" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #10b981 !important;">
<div class="d-flex align-items-center gap-2 mb-3">
<div class="bg-success-subtle text-success rounded-circle p-2 d-flex align-items-center justify-content-center" style="width: 42px; height: 42px;">
<i class="bi bi-android2 fs-4"></i>
</div>
<div>
<h6 class="fw-bold text-dark mb-0">Android Installation (Chrome / Edge)</h6>
<small class="text-muted">Samsung Galaxy, OnePlus, Xiaomi, Vivo, Oppo, Google Pixel</small>
</div>
</div>
<ol class="small text-secondary ps-3 mb-0" style="line-height: 1.8;">
<li><strong>Scan QR Code:</strong> Open your smartphone camera or Google Lens and scan the QR code. Tap the link to open in Google Chrome.</li>
<li><strong>Tap Install Banner:</strong> An <em>"Install HMS App"</em> or <em>"Add to Home Screen"</em> banner will automatically pop up at the bottom of the screen. Tap it!</li>
<li><strong>Alternative Menu:</strong> If no banner appears, tap the <strong>three dots menu (⋮)</strong> at the top right of Chrome, and select <strong>"Install app"</strong> or <strong>"Add to Home screen"</strong>.</li>
<li><strong>Finished:</strong> The app icon will be installed directly on your Android home screen. It operates in full-screen standalone mode without any browser URL bar!</li>
</ol>
</div>
</div>
<!-- iPhone / iPad Instructions -->
<div class="col-md-6">
<div class="card h-100 border-0 shadow-sm p-3" style="border-radius: 14px; background: #ffffff; border-top: 4px solid #3b82f6 !important;">
<div class="d-flex align-items-center gap-2 mb-3">
<div class="bg-primary-subtle text-primary rounded-circle p-2 d-flex align-items-center justify-content-center" style="width: 42px; height: 42px;">
<i class="bi bi-apple fs-4"></i>
</div>
<div>
<h6 class="fw-bold text-dark mb-0">iPhone &amp; iPad Installation (Safari)</h6>
<small class="text-muted">Apple iOS 14+ Devices</small>
</div>
</div>
<ol class="small text-secondary ps-3 mb-0" style="line-height: 1.8;">
<li><strong>Scan QR Code:</strong> Open the built-in <strong>Camera app</strong> on your iPhone or iPad, point it at the QR code, and tap the yellow link notification to open in <strong>Safari</strong>.</li>
<li><strong>Tap Share Button:</strong> At the bottom of Safari, tap the <strong>Share</strong> button (a square icon with an arrow pointing upwards <i class="bi bi-box-arrow-up"></i>).</li>
<li><strong>Add to Home Screen:</strong> Scroll down the share sheet and tap <strong>"Add to Home Screen"</strong> (<i class="bi bi-plus-square"></i>).</li>
<li><strong>Tap Add:</strong> In the top-right corner, tap <strong>"Add"</strong>. The HMS app icon is instantly created on your home screen and launches standalone!</li>
</ol>
</div>
</div>
<!-- Why Installation Type (PWA) Benefits -->
<div class="col-12">
<div class="card border-0 shadow-sm p-3 bg-light" style="border-radius: 12px;">
<div class="row align-items-center g-2 text-center text-md-start">
<div class="col-md-3 text-center">
<span class="badge bg-primary-subtle text-primary px-3 py-2 fs-6 rounded-pill">
<i class="bi bi-stars me-1"></i> PWA Technology
</span>
</div>
<div class="col-md-9">
<div class="fw-bold text-dark small mb-1">Why Progressive Web Apps (PWA)?</div>
<div class="text-muted small" style="font-size: 11px;">
&bull; <strong>Zero App Store Setup:</strong> No Apple App Store or Google Play Store accounts needed.<br>
&bull; <strong>Always Up-to-Date:</strong> Whenever hospital software updates, all mobile devices automatically receive latest features on reload.<br>
&bull; <strong>High Security &amp; Speed:</strong> Runs over hospital local Wi-Fi intranet with instant barcode/camera scanning and offline caching.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="modal-footer bg-white py-2 px-4 border-top d-flex align-items-center justify-content-between">
<span class="small text-muted" style="font-size: 11px;">
<i class="bi bi-shield-check text-success me-1"></i> Hospital Intranet &bull; TLS / SSL &bull; Instant Mobile Installation
</span>
<button type="button" class="btn btn-secondary btn-sm px-4 fw-bold" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Hidden Container for Printing QR Posters -->
<div id="print_qr_sheet_container" style="display: none;"></div>
<script>
(function() {
// App definitions
var APP_DEFS = {
hub: {
title: 'HMS Mobile Apps Hub',
path: '/app',
badge: 'Unified Portal • Active PWA',
color: '#0f172a',
desc: 'All-in-one hospital mobile portal. Single home screen install for Doctor, Nurse, Pharmacy POS & Token Queue.'
},
pharmacy: {
title: 'Medical Store & Pharmacy POS',
path: '/MedicalStore',
badge: 'Pharmacy POS • Active PWA',
color: '#0891b2',
desc: 'Rack/Shelf Draft Bill Collector, Stock Shelf Barcode Auditor, Inward Purchase Bill Camera & Expiry Telemetry.'
},
nursing: {
title: 'Nursing Care Bedside App',
path: '/app/nursing',
badge: 'IPD Nursing • Active PWA',
color: '#2563eb',
desc: 'IPD Bedside Vitals (BP, SpO2, Pulse), Intake/Output Charting & Paper Document Camera Scanner.'
},
doctor: {
title: 'DoctorCare Rounds & EMR',
path: '/app/doctor',
badge: 'Doctor EMR • Active PWA',
color: '#059669',
desc: 'Doctor Ward Rounds, Clinical Notes, OPD Prescriptions, Lab Investigation & Diagnostic review.'
},
queue: {
title: 'OPD Token Tracker TV Display',
path: '/app/queue',
badge: 'Token Screen • Active PWA',
color: '#d97706',
desc: 'Waiting Room Token Calling Screen, Live Room Numbers, TTS Audio Voice Announcements & TV display mode.'
}
};
var currentHost = '<?= esc($defaultHost) ?>';
var currentScheme = window.location.protocol ? window.location.protocol.replace(':', '') : 'http';
function getFullAppUrl(appKey) {
var path = APP_DEFS[appKey] ? APP_DEFS[appKey].path : '/app';
return currentScheme + '://' + currentHost + path;
}
function getQrImgSrc(url) {
return 'https://api.qrserver.com/v1/create-qr-code/?size=260x260&margin=1&data=' + encodeURIComponent(url);
}
function refreshAllQrCodes() {
var displaySpan = document.getElementById('current_qr_host_display');
if (displaySpan) displaySpan.textContent = currentHost;
Object.keys(APP_DEFS).forEach(function(key) {
var appUrl = getFullAppUrl(key);
var qrSrc = getQrImgSrc(appUrl);
var imgEl = document.getElementById('qr_img_' + key);
var linkEl = document.getElementById('qr_link_' + key);
var txtEl = document.getElementById('qr_url_txt_' + key);
if (imgEl) imgEl.src = qrSrc;
if (linkEl) linkEl.href = appUrl;
if (txtEl) txtEl.textContent = appUrl;
});
// Update focused view if active
var focusSelect = document.getElementById('focus_app_select');
if (focusSelect) {
renderFocusApp(focusSelect.value);
}
}
window.onMobileQrHostChanged = function(val) {
var customGroup = document.getElementById('custom_host_input_group');
if (val === 'custom') {
if (customGroup) customGroup.style.display = 'flex';
var inp = document.getElementById('custom_host_input');
if (inp) currentHost = inp.value.trim() || window.location.host;
} else {
if (customGroup) customGroup.style.display = 'none';
currentHost = val;
}
refreshAllQrCodes();
};
window.applyCustomHost = function() {
var inp = document.getElementById('custom_host_input');
if (inp && inp.value.trim()) {
currentHost = inp.value.trim();
refreshAllQrCodes();
}
};
window.copyAppUrl = function(appKey) {
var url = getFullAppUrl(appKey);
if (navigator.clipboard) {
navigator.clipboard.writeText(url).then(function() {
alert('Copied URL to clipboard: ' + url);
});
} else {
var t = document.createElement('textarea');
t.value = url;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
alert('Copied URL to clipboard: ' + url);
}
};
window.focusAppView = function(appKey) {
var focusTabBtn = document.getElementById('tab-single-focus');
var select = document.getElementById('focus_app_select');
if (select) select.value = appKey;
if (focusTabBtn && window.bootstrap && window.bootstrap.Tab) {
window.bootstrap.Tab.getOrCreateInstance(focusTabBtn).show();
}
renderFocusApp(appKey);
};
window.renderFocusApp = function(appKey) {
var def = APP_DEFS[appKey] || APP_DEFS.hub;
var appUrl = getFullAppUrl(appKey);
var qrSrc = getQrImgSrc(appUrl);
var img = document.getElementById('focus_qr_img');
var title = document.getElementById('focus_app_title');
var badge = document.getElementById('focus_app_badge');
var desc = document.getElementById('focus_app_desc');
var urlInp = document.getElementById('focus_app_url');
var launch = document.getElementById('focus_app_launch');
if (img) img.src = qrSrc;
if (title) title.textContent = def.title;
if (badge) badge.textContent = def.badge;
if (desc) desc.textContent = def.desc;
if (urlInp) urlInp.value = appUrl;
if (launch) launch.href = appUrl;
};
window.copyFocusUrl = function() {
var urlInp = document.getElementById('focus_app_url');
if (urlInp && urlInp.value) {
navigator.clipboard ? navigator.clipboard.writeText(urlInp.value) : document.execCommand('copy');
alert('Copied URL to clipboard: ' + urlInp.value);
}
};
window.printAllMobileQRs = function() {
var printWin = window.open('', '_blank', 'width=900,height=800');
if (!printWin) {
alert('Please allow popups to print the QR Poster.');
return;
}
var cardsHtml = '';
Object.keys(APP_DEFS).forEach(function(k) {
var def = APP_DEFS[k];
var url = getFullAppUrl(k);
var qrSrc = getQrImgSrc(url);
cardsHtml += '<div style="border: 2px solid #334155; border-radius: 12px; padding: 16px; width: 260px; text-align: center; page-break-inside: avoid; background: #fff; margin: 10px; display: inline-block; vertical-align: top;">' +
'<div style="font-size: 14px; font-weight: 800; color: #0f172a; margin-bottom: 4px;">' + def.title + '</div>' +
'<div style="font-size: 11px; color: #475569; margin-bottom: 10px;">' + def.badge + '</div>' +
'<img src="' + qrSrc + '" style="width: 180px; height: 180px; border: 1px solid #cbd5e1; border-radius: 8px; padding: 4px;" alt="QR Code"/>' +
'<div style="font-size: 10px; font-weight: 700; color: #2563eb; margin-top: 8px; word-break: break-all;">' + url + '</div>' +
'<div style="font-size: 10px; color: #64748b; margin-top: 6px;">1. Connect Phone to Hospital Wi-Fi<br>2. Scan QR &amp; Tap "Add to Home Screen"</div>' +
'</div>';
});
var printContent = '<!DOCTYPE html><html><head><title>HMS Hospital Mobile Apps - Counter QR Poster</title>' +
'<style>' +
'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f8fafc; padding: 20px; text-align: center; }' +
'@media print { body { background: #fff; padding: 0; } }' +
'</style></head><body>' +
'<div style="margin-bottom: 16px;">' +
'<h2 style="margin: 0; color: #0f172a;">Hospital Mobile Applications &amp; QR Directory</h2>' +
'<p style="margin: 4px 0 0 0; color: #64748b; font-size: 13px;">Progressive Web Applications (PWA) &bull; Scan with Camera to Install on Mobile Home Screen</p>' +
'</div>' +
'<div>' + cardsHtml + '</div>' +
'<script>setTimeout(function(){ window.print(); }, 800);<' + '/script>' +
'</body></html>';
printWin.document.open();
printWin.document.write(printContent);
printWin.document.close();
};
window.printSingleQR = function() {
var select = document.getElementById('focus_app_select');
var appKey = select ? select.value : 'hub';
var def = APP_DEFS[appKey] || APP_DEFS.hub;
var url = getFullAppUrl(appKey);
var qrSrc = getQrImgSrc(url);
var printWin = window.open('', '_blank', 'width=600,height=700');
if (!printWin) {
alert('Please allow popups to print the QR Standee.');
return;
}
var printContent = '<!DOCTYPE html><html><head><title>' + def.title + ' - QR Standee</title>' +
'<style>' +
'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; padding: 40px 20px; text-align: center; background: #fff; }' +
'.standee { border: 3px solid #0f172a; border-radius: 18px; padding: 30px; max-width: 420px; margin: 0 auto; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }' +
'@media print { .standee { box-shadow: none; border-color: #000; } }' +
'</style></head><body>' +
'<div class="standee">' +
'<h2 style="margin: 0 0 6px 0; color: #0f172a;">' + def.title + '</h2>' +
'<div style="font-size: 13px; font-weight: bold; color: #2563eb; margin-bottom: 20px;">' + def.badge + '</div>' +
'<img src="' + qrSrc + '" style="width: 260px; height: 260px; border: 2px solid #e2e8f0; border-radius: 12px; padding: 8px;" alt="QR Code"/>' +
'<div style="font-size: 12px; font-weight: 700; color: #0f172a; margin-top: 14px; word-break: break-all;">' + url + '</div>' +
'<div style="margin-top: 16px; padding: 12px; background: #f1f5f9; border-radius: 8px; font-size: 12px; color: #334155; text-align: left;">' +
'<strong>How to use on Phone:</strong><br>' +
'1. Connect your phone to Hospital Wi-Fi.<br>' +
'2. Open phone camera and scan this QR code.<br>' +
'3. Tap "Add to Home Screen" to install application.' +
'</div>' +
'</div>' +
'<script>setTimeout(function(){ window.print(); }, 800);<' + '/script>' +
'</body></html>';
printWin.document.open();
printWin.document.write(printContent);
printWin.document.close();
};
// Initialize when modal is shown
document.addEventListener('DOMContentLoaded', function() {
var modalEl = document.getElementById('loginMobileAppsModal');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', function() {
refreshAllQrCodes();
});
}
refreshAllQrCodes();
});
})();
</script>
<footer class="py-3 text-center text-muted small">
&copy; 2017 - 2026 <strong><a href="https://www.e-atria.in" target="_blank" rel="noopener">E-Atria</a></strong>. All Rights Reserved |
<a href="<?= base_url('software-use-license.html') ?>" target="_blank" rel="noopener">Software Use License</a> |

View File

@ -319,10 +319,10 @@
</div>
</div>
<div class="col-4">
<div class="card p-2 text-center border-secondary bg-light text-muted opacity-75 h-100">
<i class="bi bi-capsule fs-3 mb-1"></i>
<div class="fw-bold small" style="font-size:12px;">Pharmacy App</div>
<span class="badge bg-secondary mt-1" style="font-size:9px;">Coming Soon</span>
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_pharmacy" data-app="pharmacy">
<i class="bi bi-capsule fs-3 mb-1" style="color: #0891b2;"></i>
<div class="fw-bold small" style="font-size:12px;">Pharmacy POS</div>
<span class="badge text-white mt-1" style="font-size:9px; background: #0891b2;">Active PWA</span>
</div>
</div>
</div>
@ -366,6 +366,9 @@
var selectedApp = 'nursing';
function getAppBaseUrl() {
if (selectedApp === 'pharmacy') {
return '<?= base_url('MedicalStore') ?>';
}
return '<?= base_url('app') ?>/' + selectedApp;
}
@ -374,7 +377,7 @@
var code = sel ? sel.value : '';
var appBaseUrl = getAppBaseUrl();
var paramKey = (selectedApp === 'doctor') ? 'doctor_id=' : 'nurse_code=';
var appUrl = appBaseUrl + (code ? '?' + paramKey + encodeURIComponent(code) : '');
var appUrl = (selectedApp === 'pharmacy') ? appBaseUrl : (appBaseUrl + (code ? '?' + paramKey + encodeURIComponent(code) : ''));
var qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(appUrl);
var qrImg = document.getElementById('hdr_apps_qr_img');
@ -387,7 +390,7 @@
if (urlInput) urlInput.value = appUrl;
if (launchLink) launchLink.href = appUrl;
var title = (selectedApp === 'doctor') ? 'DoctorCare EMR App' : 'Nursing Care App';
var title = (selectedApp === 'doctor') ? 'DoctorCare EMR App' : ((selectedApp === 'pharmacy') ? 'Medical Store & Pharmacy POS' : 'Nursing Care App');
if (code && sel && sel.options[sel.selectedIndex]) {
var text = sel.options[sel.selectedIndex].text;
@ -395,7 +398,7 @@
if (targetCode) targetCode.textContent = code;
} else {
if (targetName) targetName.textContent = title;
if (targetCode) targetCode.textContent = '/app/' + selectedApp;
if (targetCode) targetCode.textContent = (selectedApp === 'pharmacy') ? '/MedicalStore' : ('/app/' + selectedApp);
}
}
@ -418,7 +421,12 @@
var label = document.getElementById('hdr_apps_staff_label');
if (!sel) return;
if (selectedApp === 'doctor') {
if (selectedApp === 'pharmacy') {
if (label) label.textContent = '2. PHARMACY COUNTER / STORE ACCESS';
sel.innerHTML = '<option value="">-- All Pharmacy Counters (Auto-detect) --</option>';
updateHdrAppDetails();
return;
} else if (selectedApp === 'doctor') {
if (label) label.textContent = '2. SELECT DOCTOR PROFILE';
fetch('<?= base_url('api/v1/doctor/list') ?>')
.then(function(r) { return r.json(); })

File diff suppressed because it is too large Load Diff