Initial commit
This commit is contained in:
107
docs/superpowers/specs/2026-04-27-mcu-dashboard-design.md
Normal file
107
docs/superpowers/specs/2026-04-27-mcu-dashboard-design.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# MCU Dashboard (cpone-dashboard) — Design Spec
|
||||
Date: 2026-04-27
|
||||
|
||||
## Overview
|
||||
Dashboard live monitoring MCU (Medical Check-Up) untuk laboratorium klinik CpOne. Menampilkan data real-time dari kegiatan MCU korporat: KPI harian, TAT, status station, arrival tracking, progress pemeriksaan, abnormal monitoring, dan laporan hasil.
|
||||
|
||||
## Data Architecture
|
||||
```
|
||||
cpone (main DB, Server A)
|
||||
↓ inject/ETL (proyek terpisah)
|
||||
cpone_dashboard (Server A)
|
||||
↓ MySQL replication (otomatis)
|
||||
cpone_dashboard (Server B — production)
|
||||
↓ dibaca oleh
|
||||
Go Dashboard App (Server B)
|
||||
```
|
||||
|
||||
Dashboard app **hanya** konek ke `cpone_dashboard` lokal. Zero dependency ke `cpone`.
|
||||
|
||||
## Tech Stack
|
||||
- **Backend**: Go 1.21, framework Chi (router lightweight)
|
||||
- **Frontend**: Go HTML templates (embed ke binary), HTMX via CDN, ECharts via CDN, Tailwind via CDN
|
||||
- **Database**: MySQL 8.0, single connection ke `cpone_dashboard`
|
||||
- **Build**: Cross-compile di Mac (`GOOS=linux GOARCH=amd64`), deploy binary ke server
|
||||
- **Primary color**: `#3b50a0`
|
||||
|
||||
## Pages
|
||||
1. **Login** — autentikasi user
|
||||
2. **Dashboard** — KPI cards, TAT harian, station status table, arrival list, trend chart (HTMX polling tiap 10s)
|
||||
3. **Arrival Tracking** — daftar peserta check-in
|
||||
4. **Observation Progress** — progress per station pemeriksaan
|
||||
5. **Abnormal Monitoring** — hasil pemeriksaan dengan flag abnormal
|
||||
6. **Result Reports** — laporan hasil konsolidasi per peserta
|
||||
|
||||
## Folder Structure
|
||||
```
|
||||
cpone-dashboard/
|
||||
├── main.go
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── .env ← DB DSN, port, dll
|
||||
├── .env.example
|
||||
├── Makefile ← make build, make deploy
|
||||
│
|
||||
├── config/
|
||||
│ └── config.go
|
||||
│
|
||||
├── db/
|
||||
│ └── db.go ← single connection ke cpone_dashboard
|
||||
│
|
||||
├── menu/
|
||||
│ ├── dashboard/
|
||||
│ │ ├── handler.go
|
||||
│ │ ├── query.go
|
||||
│ │ └── route.go
|
||||
│ ├── arrival/
|
||||
│ │ ├── handler.go
|
||||
│ │ ├── query.go
|
||||
│ │ └── route.go
|
||||
│ ├── progress/
|
||||
│ │ ├── handler.go
|
||||
│ │ ├── query.go
|
||||
│ │ └── route.go
|
||||
│ ├── abnormal/
|
||||
│ │ ├── handler.go
|
||||
│ │ ├── query.go
|
||||
│ │ └── route.go
|
||||
│ └── result/
|
||||
│ ├── handler.go
|
||||
│ ├── query.go
|
||||
│ └── route.go
|
||||
│
|
||||
├── templates/
|
||||
│ ├── layout/
|
||||
│ │ └── base.html
|
||||
│ ├── dashboard/
|
||||
│ │ ├── index.html
|
||||
│ │ └── partials/
|
||||
│ │ ├── kpi.html
|
||||
│ │ ├── stations.html
|
||||
│ │ └── arrivals.html
|
||||
│ ├── arrival/
|
||||
│ │ └── index.html
|
||||
│ ├── progress/
|
||||
│ │ └── index.html
|
||||
│ ├── abnormal/
|
||||
│ │ └── index.html
|
||||
│ └── result/
|
||||
│ └── index.html
|
||||
│
|
||||
└── static/
|
||||
└── css/
|
||||
└── custom.css
|
||||
```
|
||||
|
||||
## Deploy Flow
|
||||
```bash
|
||||
make deploy
|
||||
# = GOOS=linux GOARCH=amd64 go build -o cpone-dashboard .
|
||||
# + scp cpone-dashboard one@devcpone.aplikasi.web.id:/home/one/project/cpone-dashboard/
|
||||
# + ssh ... restart process
|
||||
```
|
||||
|
||||
## Out of Scope
|
||||
- Inject/ETL dari `cpone` ke `cpone_dashboard` (proyek terpisah)
|
||||
- MySQL replication setup
|
||||
- Multi-tenancy / multi-server config
|
||||
114
docs/superpowers/specs/2026-04-30-result-menu-design.md
Normal file
114
docs/superpowers/specs/2026-04-30-result-menu-design.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Result Menu — Design Spec
|
||||
Date: 2026-04-30
|
||||
|
||||
## Overview
|
||||
Halaman `/result` menampilkan daftar peserta MCU beserta tombol "View PDF" untuk membuka laporan hasil konsolidasi. Data diambil sepenuhnya dari `cpone_dashboard` (zero dependency ke `cpone`).
|
||||
|
||||
## Data Sources
|
||||
Semua tabel ada di `cpone_dashboard`:
|
||||
- `mcu_patient` — data peserta (NIP, nama, posisi/dept, order ID)
|
||||
- `published_mcu_dashboard_sync` — file URL PDF per peserta
|
||||
|
||||
Join key: `mcu_patient.Mcu_PatientOrderID = published_mcu_dashboard_sync.Published_McuDasboardT_OrderHeaderID`
|
||||
|
||||
## Config / Env
|
||||
Tambah key baru ke `.env`, `.env.example`, dan `config/config.go`:
|
||||
```
|
||||
PDF_BASE_URL=http://devcpone.aplikasi.web.id/dashboard-files/
|
||||
```
|
||||
Field `PDFBaseURL string` ditambah ke struct `Config`. Nilai ini di-passing ke `result` handler saat setup di `main.go`.
|
||||
|
||||
## Backend — `menu/result/`
|
||||
|
||||
### query.go
|
||||
```go
|
||||
type ResultRow struct {
|
||||
NIP string
|
||||
Name string
|
||||
Posisi string
|
||||
FileUrl string // kosong jika belum ada PDF
|
||||
ReportDate string // Published_McuDasboardLastUpdated
|
||||
}
|
||||
|
||||
type ResultSummary struct {
|
||||
Total int
|
||||
HasPDF int
|
||||
}
|
||||
```
|
||||
|
||||
Query:
|
||||
```sql
|
||||
SELECT
|
||||
COALESCE(NULLIF(TRIM(mp.Mcu_PatientNIP), ''), '-') AS nip,
|
||||
COALESCE(NULLIF(TRIM(mp.Mcu_PatientName), ''), '-') AS name,
|
||||
COALESCE(
|
||||
NULLIF(TRIM(mp.Mcu_PatientDepartment), ''),
|
||||
NULLIF(TRIM(mp.Mcu_PatientDivision), ''),
|
||||
NULLIF(TRIM(mp.Mcu_PatientPosisi), ''),
|
||||
'-'
|
||||
) AS posisi,
|
||||
COALESCE(p.Published_McuDasboardFileUrl, '') AS file_url,
|
||||
CASE
|
||||
WHEN p.Published_McuDasboardFileUrl IS NOT NULL AND p.Published_McuDasboardFileUrl != ''
|
||||
THEN COALESCE(p.Published_McuDasboardLastUpdated, '')
|
||||
ELSE ''
|
||||
END AS report_date
|
||||
FROM mcu_patient mp
|
||||
LEFT JOIN published_mcu_dashboard_sync p
|
||||
ON p.Published_McuDasboardT_OrderHeaderID = mp.Mcu_PatientOrderID
|
||||
WHERE mp.Mcu_PatientMcuID = ?
|
||||
AND mp.Mcu_PatientIsActive = 'Y'
|
||||
ORDER BY
|
||||
(p.Published_McuDasboardFileUrl IS NOT NULL AND p.Published_McuDasboardFileUrl != '') DESC,
|
||||
mp.Mcu_PatientName ASC
|
||||
```
|
||||
|
||||
Helper functions:
|
||||
- `BuildResultSummary(rows []ResultRow) ResultSummary`
|
||||
- `FilterResultRows(rows []ResultRow, search, filter string) []ResultRow`
|
||||
- filter values: `""` (all), `"has_pdf"`, `"no_pdf"`
|
||||
|
||||
### handler.go
|
||||
`pageData` struct:
|
||||
```go
|
||||
type pageData struct {
|
||||
Username string
|
||||
CurrentProject projects.ProjectItem
|
||||
Search string
|
||||
Filter string
|
||||
Rows []ResultRow
|
||||
FilteredRows []ResultRow
|
||||
Summary ResultSummary
|
||||
PDFBaseURL string
|
||||
}
|
||||
```
|
||||
|
||||
Handler `Index` mengikuti pola progress: redirect ke `/projects` jika belum pilih project, fetch rows, build summary, apply filter, render template.
|
||||
|
||||
`PDFBaseURL` di-inject saat `SetTemplates` — tambah fungsi `SetPDFBaseURL(url string)` di package result.
|
||||
|
||||
### route.go
|
||||
Tidak berubah — sudah ada `r.Get("/", Index)`.
|
||||
|
||||
## Template — `templates/result/index.html`
|
||||
|
||||
**Section 1 — Current project card**
|
||||
Sama persis dengan progress/arrival: nama project, nomor, tombol "Ganti project".
|
||||
|
||||
**Section 2 — Summary cards (2 cards)**
|
||||
- Total Patients
|
||||
- Has PDF (count `FileUrl != ""`)
|
||||
|
||||
**Section 3 — Filter form**
|
||||
- Search input (nama atau NIP)
|
||||
- Dropdown: All / Has PDF / No PDF
|
||||
- Tombol Filter
|
||||
|
||||
**Section 4 — Patient list**
|
||||
- Desktop: table dengan kolom NIP, Nama, Posisi/Dept, Report Date, Action
|
||||
- Mobile: card stack
|
||||
- Action: tombol `View PDF` (buka tab baru) jika `FileUrl != ""`, teks `—` jika kosong
|
||||
- PDF full URL: `PDFBaseURL + FileUrl`
|
||||
|
||||
## Referensi Visual
|
||||
`/PLAN/draft-cpone/06-result.html` — warna dan layout mengikuti color scheme brand yang ada (`brand-500`, `slate-*`), bukan warna `diagnos-*` dari draft.
|
||||
Reference in New Issue
Block a user