Refine KPI card highlights and demo checkout flow

This commit is contained in:
sas.fajri
2026-04-30 15:48:16 +07:00
parent ea5571e1cb
commit af7e0d3560
3 changed files with 88 additions and 11 deletions

View File

@@ -6,11 +6,12 @@
# ./demo_live.sh 3 # 1 action every 3 seconds (faster)
# ./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 811825)
# B — Station progress update for today's in-progress patients
# C — Doctor validates a yesterday resume (April 29)
# D — Publish result PDF (April 29 patient gets a file URL)
# C — Checkout patient today when all required stations are done
# D — Doctor validates a yesterday resume (April 29)
# E — Publish result PDF (April 29 patient gets a file URL)
#
# Press Ctrl+C to stop.
@@ -181,6 +182,49 @@ action_station() {
}
# ── 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() {
local patient
patient=$(DB "
@@ -213,7 +257,7 @@ action_validate() {
LOG "✔ VALIDASI │ $name │ resume divalidasi dokter"
}
# ── ACTION D: publish PDF ─────────────────────────────────────────────────────
# ── ACTION E: publish PDF ─────────────────────────────────────────────────────
action_publish() {
local order_id
order_id=$(DB "
@@ -273,20 +317,21 @@ echo "║ CpOne — DEMO LIVE SIMULATION ║"
echo "║ MCU PROJECT DEMO 2026 │ ID: $MCU_ID │ Hari ini: $TODAY"
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 ""
actions=(A B C D)
actions=(A B C D E)
idx=0
while true; do
act="${actions[$((idx % 4))]}"
act="${actions[$((idx % 5))]}"
case "$act" in
A) action_checkin ;;
B) action_station ;;
C) action_validate ;;
D) action_publish ;;
C) action_checkout ;;
D) action_validate ;;
E) action_publish ;;
esac
idx=$(( idx + 1 ))
sleep "$SPEED"

View File

@@ -154,6 +154,11 @@
#sse-arrivals .arrival-row-updated {
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>
<!-- SSE wrapper — satu koneksi, semua section dapat update otomatis -->
@@ -383,6 +388,7 @@ function openPatientsModal() {
stations: 'sse-stations',
arrivals: 'sse-arrivals'
};
const kpiSnapshot = new Map();
const stationSnapshot = new Map();
const arrivalSnapshot = new Set();
let audioCtx = null;
@@ -504,6 +510,30 @@ function openPatientsModal() {
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) {
if (!target) return;
const isStations = target.id === 'sse-stations';
@@ -511,6 +541,8 @@ function openPatientsModal() {
if (!isStations) {
if (target.id === 'sse-arrivals') {
didHighlightRows = !!triggerArrivalRows(target);
} else if (target.id === 'sse-kpi') {
didHighlightRows = !!triggerKpiCards(target);
} else {
target.classList.remove('sse-updated');
void target.offsetWidth;

View File

@@ -16,7 +16,7 @@
</article>
<!-- 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">
<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">
@@ -28,7 +28,7 @@
</article>
<!-- 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">
<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">