Refine KPI card highlights and demo checkout flow
This commit is contained in:
@@ -6,11 +6,12 @@
|
|||||||
# ./demo_live.sh 3 # 1 action every 3 seconds (faster)
|
# ./demo_live.sh 3 # 1 action every 3 seconds (faster)
|
||||||
# ./demo_live.sh 15 # 1 action every 15 seconds (slower)
|
# ./demo_live.sh 15 # 1 action every 15 seconds (slower)
|
||||||
#
|
#
|
||||||
# What it simulates (cycles through 4 action types):
|
# What it simulates (cycles through 5 action types):
|
||||||
# A — New patient check-in for today (April 30, patients 811–825)
|
# A — New patient check-in for today (April 30, patients 811–825)
|
||||||
# B — Station progress update for today's in-progress patients
|
# B — Station progress update for today's in-progress patients
|
||||||
# C — Doctor validates a yesterday resume (April 29)
|
# C — Checkout patient today when all required stations are done
|
||||||
# D — Publish result PDF (April 29 patient gets a file URL)
|
# D — Doctor validates a yesterday resume (April 29)
|
||||||
|
# E — Publish result PDF (April 29 patient gets a file URL)
|
||||||
#
|
#
|
||||||
# Press Ctrl+C to stop.
|
# Press Ctrl+C to stop.
|
||||||
|
|
||||||
@@ -181,6 +182,49 @@ action_station() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── ACTION C: validate resume ─────────────────────────────────────────────────
|
# ── ACTION C: validate resume ─────────────────────────────────────────────────
|
||||||
|
action_checkout() {
|
||||||
|
local ALL_STATIONS="1,31,17,4,5,2,7,33"
|
||||||
|
local patient
|
||||||
|
patient=$(DB "
|
||||||
|
SELECT c.Mcu_CheckinoutPreregisterID
|
||||||
|
FROM mcu_checkinout c
|
||||||
|
WHERE c.Mcu_CheckinoutMcuID = $MCU_ID
|
||||||
|
AND c.Mcu_CheckinoutDate = '$TODAY'
|
||||||
|
AND c.Mcu_CheckinoutOutTime IS NULL
|
||||||
|
AND (
|
||||||
|
SELECT COUNT(DISTINCT sp.Mcu_StationProgressStationID)
|
||||||
|
FROM mcu_station_progress sp
|
||||||
|
WHERE sp.Mcu_StationProgressPreregisterID = c.Mcu_CheckinoutPreregisterID
|
||||||
|
AND sp.Mcu_StationProgressMcuID = $MCU_ID
|
||||||
|
AND sp.Mcu_StationProgressStationID IN ($ALL_STATIONS)
|
||||||
|
) >= 8
|
||||||
|
ORDER BY c.Mcu_CheckinoutPreregisterID
|
||||||
|
LIMIT 1;
|
||||||
|
")
|
||||||
|
|
||||||
|
if [ -z "$patient" ]; then
|
||||||
|
LOG "⏩ Belum ada pasien eligible untuk CHECK-OUT"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local now_time
|
||||||
|
now_time=$(date '+%H:%M:%S')
|
||||||
|
local name
|
||||||
|
name=$(DB "SELECT Mcu_PatientName FROM mcu_patient WHERE Mcu_PatientPreregisterID = $patient;")
|
||||||
|
|
||||||
|
DB_EXEC "
|
||||||
|
UPDATE mcu_checkinout
|
||||||
|
SET Mcu_CheckinoutOutTime = '$now_time',
|
||||||
|
Mcu_CheckinoutSyncedAt = NOW()
|
||||||
|
WHERE Mcu_CheckinoutMcuID = $MCU_ID
|
||||||
|
AND Mcu_CheckinoutDate = '$TODAY'
|
||||||
|
AND Mcu_CheckinoutPreregisterID = $patient
|
||||||
|
AND Mcu_CheckinoutOutTime IS NULL;
|
||||||
|
"
|
||||||
|
LOG "🏁 CHECK-OUT │ $name │ keluar pukul $now_time"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── ACTION D: validate resume ─────────────────────────────────────────────────
|
||||||
action_validate() {
|
action_validate() {
|
||||||
local patient
|
local patient
|
||||||
patient=$(DB "
|
patient=$(DB "
|
||||||
@@ -213,7 +257,7 @@ action_validate() {
|
|||||||
LOG "✔ VALIDASI │ $name │ resume divalidasi dokter"
|
LOG "✔ VALIDASI │ $name │ resume divalidasi dokter"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── ACTION D: publish PDF ─────────────────────────────────────────────────────
|
# ── ACTION E: publish PDF ─────────────────────────────────────────────────────
|
||||||
action_publish() {
|
action_publish() {
|
||||||
local order_id
|
local order_id
|
||||||
order_id=$(DB "
|
order_id=$(DB "
|
||||||
@@ -273,20 +317,21 @@ echo "║ CpOne — DEMO LIVE SIMULATION ║"
|
|||||||
echo "║ MCU PROJECT DEMO 2026 │ ID: $MCU_ID │ Hari ini: $TODAY ║"
|
echo "║ MCU PROJECT DEMO 2026 │ ID: $MCU_ID │ Hari ini: $TODAY ║"
|
||||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Aksi: A=Check-in B=Station C=Validasi D=Publish PDF"
|
echo " Aksi: A=Check-in B=Station C=Checkout D=Validasi E=Publish PDF"
|
||||||
echo " Interval: ${SPEED}s │ Ctrl+C untuk berhenti"
|
echo " Interval: ${SPEED}s │ Ctrl+C untuk berhenti"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
actions=(A B C D)
|
actions=(A B C D E)
|
||||||
idx=0
|
idx=0
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
act="${actions[$((idx % 4))]}"
|
act="${actions[$((idx % 5))]}"
|
||||||
case "$act" in
|
case "$act" in
|
||||||
A) action_checkin ;;
|
A) action_checkin ;;
|
||||||
B) action_station ;;
|
B) action_station ;;
|
||||||
C) action_validate ;;
|
C) action_checkout ;;
|
||||||
D) action_publish ;;
|
D) action_validate ;;
|
||||||
|
E) action_publish ;;
|
||||||
esac
|
esac
|
||||||
idx=$(( idx + 1 ))
|
idx=$(( idx + 1 ))
|
||||||
sleep "$SPEED"
|
sleep "$SPEED"
|
||||||
|
|||||||
@@ -154,6 +154,11 @@
|
|||||||
#sse-arrivals .arrival-row-updated {
|
#sse-arrivals .arrival-row-updated {
|
||||||
animation: arrivalRowPulse 2.6s ease-out;
|
animation: arrivalRowPulse 2.6s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sse-kpi .kpi-card-updated {
|
||||||
|
animation: sseFlash 2.6s ease-out, ssePop 0.9s cubic-bezier(.22,.61,.36,1);
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 80, 160, 0.35), 0 10px 22px -14px rgba(59, 80, 160, 0.55);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- SSE wrapper — satu koneksi, semua section dapat update otomatis -->
|
<!-- SSE wrapper — satu koneksi, semua section dapat update otomatis -->
|
||||||
@@ -383,6 +388,7 @@ function openPatientsModal() {
|
|||||||
stations: 'sse-stations',
|
stations: 'sse-stations',
|
||||||
arrivals: 'sse-arrivals'
|
arrivals: 'sse-arrivals'
|
||||||
};
|
};
|
||||||
|
const kpiSnapshot = new Map();
|
||||||
const stationSnapshot = new Map();
|
const stationSnapshot = new Map();
|
||||||
const arrivalSnapshot = new Set();
|
const arrivalSnapshot = new Set();
|
||||||
let audioCtx = null;
|
let audioCtx = null;
|
||||||
@@ -504,6 +510,30 @@ function openPatientsModal() {
|
|||||||
return highlighted > 0;
|
return highlighted > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerKpiCards(target) {
|
||||||
|
const cards = target.querySelectorAll('.kpi-card');
|
||||||
|
if (!cards || !cards.length) return false;
|
||||||
|
|
||||||
|
let highlighted = 0;
|
||||||
|
cards.forEach(function (card) {
|
||||||
|
const kind = card.dataset.kpiKind || '';
|
||||||
|
if (kind !== 'inprogress' && kind !== 'checkedout') return;
|
||||||
|
const value = parseInt(card.dataset.kpiValue || '0', 10);
|
||||||
|
const prev = kpiSnapshot.has(kind) ? kpiSnapshot.get(kind) : null;
|
||||||
|
kpiSnapshot.set(kind, value);
|
||||||
|
if (prev === null || value === prev) return;
|
||||||
|
|
||||||
|
highlighted++;
|
||||||
|
card.classList.remove('kpi-card-updated');
|
||||||
|
void card.offsetWidth;
|
||||||
|
card.classList.add('kpi-card-updated');
|
||||||
|
setTimeout(function () {
|
||||||
|
card.classList.remove('kpi-card-updated');
|
||||||
|
}, 2600);
|
||||||
|
});
|
||||||
|
return highlighted > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function triggerHighlight(target) {
|
function triggerHighlight(target) {
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const isStations = target.id === 'sse-stations';
|
const isStations = target.id === 'sse-stations';
|
||||||
@@ -511,6 +541,8 @@ function openPatientsModal() {
|
|||||||
if (!isStations) {
|
if (!isStations) {
|
||||||
if (target.id === 'sse-arrivals') {
|
if (target.id === 'sse-arrivals') {
|
||||||
didHighlightRows = !!triggerArrivalRows(target);
|
didHighlightRows = !!triggerArrivalRows(target);
|
||||||
|
} else if (target.id === 'sse-kpi') {
|
||||||
|
didHighlightRows = !!triggerKpiCards(target);
|
||||||
} else {
|
} else {
|
||||||
target.classList.remove('sse-updated');
|
target.classList.remove('sse-updated');
|
||||||
void target.offsetWidth;
|
void target.offsetWidth;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- Checked In -->
|
<!-- Checked In -->
|
||||||
<article class="card border-l-4 border-l-brand-500 p-4">
|
<article class="kpi-card kpi-card-inprogress card border-l-4 border-l-brand-500 p-4" data-kpi-kind="inprogress" data-kpi-value="{{.CheckedIn}}">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">In Progress</p>
|
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">In Progress</p>
|
||||||
<svg class="h-4 w-4 text-brand-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg class="h-4 w-4 text-brand-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- Checked Out -->
|
<!-- Checked Out -->
|
||||||
<article class="card border-l-4 border-l-emerald-500 p-4">
|
<article class="kpi-card kpi-card-checkedout card border-l-4 border-l-emerald-500 p-4" data-kpi-kind="checkedout" data-kpi-value="{{.CheckedOut}}">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Checked Out</p>
|
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Checked Out</p>
|
||||||
<svg class="h-4 w-4 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg class="h-4 w-4 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
|||||||
Reference in New Issue
Block a user