Files
cpone_dashboard/cpone-dashboard/menu/dashboard/handler.go
2026-04-30 14:27:01 +07:00

288 lines
7.8 KiB
Go

package dashboard
import (
"cpone-dashboard/menu/auth"
"cpone-dashboard/menu/projects"
"embed"
"encoding/json"
"html/template"
"log"
"net/http"
"strconv"
"time"
)
var templateFS *embed.FS
var funcMap template.FuncMap
type StationsPartial struct {
Rows []StationRow
IsLive bool
}
type ArrivalsPartial struct {
Rows []ArrivalRow
IsLive bool
}
type PatientsPartial struct {
Patients []PatientDetail
IsRange bool
}
type PageData struct {
Username string
McuID int
Project ProjectInfo
CurrentProject projects.ProjectItem
AvailableDates []string
Mode string
DateFrom string
DateTo string
IsRange bool
IsLive bool
KPI KPIData
TAT TATData
Stations []StationRow
Arrivals []ArrivalRow
TATChart template.JS
TrendChart template.JS
}
var basePath string
func SetTemplateFS(fs *embed.FS) { templateFS = fs }
func SetTemplateFuncs(fm template.FuncMap) { funcMap = fm }
func SetBasePath(p string) { basePath = p }
func parse(files ...string) *template.Template {
return template.Must(template.New("").Funcs(funcMap).ParseFS(templateFS, files...))
}
func defaultDailyDate(project ProjectInfo) string {
today := time.Now().Format("2006-01-02")
if project.StartDate == "" {
return today
}
if project.EndDate != "" && today > project.EndDate {
return project.StartDate
}
if today < project.StartDate {
return project.StartDate
}
return today
}
func containsDate(dates []string, target string) bool {
for _, d := range dates {
if d == target {
return true
}
}
return false
}
func activeDateRange(r *http.Request, project ProjectInfo, availableDates []string) (mode, from, to string) {
mode = "daily"
reqDate := r.URL.Query().Get("date")
switch {
case reqDate != "" && containsDate(availableDates, reqDate):
from = reqDate
case containsDate(availableDates, time.Now().Format("2006-01-02")):
from = time.Now().Format("2006-01-02")
case len(availableDates) > 0:
from = availableDates[0]
default:
from = defaultDailyDate(project)
}
to = from
return
}
func activeMcuID(r *http.Request) int {
if id, _ := strconv.Atoi(r.URL.Query().Get("mcu_id")); id > 0 {
return id
}
return auth.SelectedProjectID(r)
}
func toJS(v interface{}) template.JS {
b, _ := json.Marshal(v)
return template.JS(b)
}
func Index(w http.ResponseWriter, r *http.Request) {
mcuID := activeMcuID(r)
username := auth.Username(r)
if mcuID == 0 {
http.Redirect(w, r, basePath+"/projects", http.StatusSeeOther)
return
}
ok, err := projects.HasAccess(username, mcuID)
if err != nil {
log.Printf("[dashboard] HasAccess error: %v", err)
http.Error(w, "query error", http.StatusInternalServerError)
return
}
if !ok {
http.Redirect(w, r, basePath+"/projects", http.StatusSeeOther)
return
}
project, err := GetProject(mcuID)
if err != nil {
log.Printf("[dashboard] GetActiveProject error: %v", err)
http.Error(w, "query error", http.StatusInternalServerError)
return
}
currentProject := projects.ProjectItem{}
if item, ok, err := projects.GetUserProject(username, mcuID); err != nil {
log.Printf("[dashboard] GetUserProject error: %v", err)
} else if ok {
currentProject = item
auth.SetSelectedProject(w, mcuID)
}
availableDates, err := GetCheckinDates(project.McuID)
if err != nil {
log.Printf("[dashboard] GetCheckinDates error: %v", err)
}
mode, dateFrom, dateTo := activeDateRange(r, project, availableDates)
isRange := dateFrom != dateTo
kpi, err := GetKPI(project.McuID, dateFrom, dateTo)
if err != nil {
log.Printf("[dashboard] GetKPI error: %v", err)
}
tat, err := GetTAT(project.McuID, dateFrom, dateTo)
if err != nil {
log.Printf("[dashboard] GetTAT error: %v", err)
}
stations, err := GetStations(project.McuID, dateFrom, dateTo)
if err != nil {
log.Printf("[dashboard] GetStations error: %v", err)
}
arrivals, err := GetArrivals(project.McuID, dateFrom, dateTo, 8)
if err != nil {
log.Printf("[dashboard] GetArrivals error: %v", err)
}
hourlyTAT, err := GetPeriodTAT(project.McuID, dateFrom, dateTo, isRange)
if err != nil {
log.Printf("[dashboard] GetPeriodTAT error: %v", err)
}
trend, err := GetPeriodTrend(project.McuID, dateFrom, dateTo, isRange)
if err != nil {
log.Printf("[dashboard] GetPeriodTrend error: %v", err)
}
// Build chart JSON payloads
tatLabels, tatValues := []string{}, []float64{}
for _, p := range hourlyTAT {
tatLabels = append(tatLabels, p.Label)
tatValues = append(tatValues, p.Value)
}
trendLabels, trendCI, trendCO := []string{}, []int{}, []int{}
for _, p := range trend {
trendLabels = append(trendLabels, p.Label)
trendCI = append(trendCI, p.CheckedIn)
trendCO = append(trendCO, p.CheckedOut)
}
today := time.Now().Format("2006-01-02")
data := PageData{
Username: username,
McuID: project.McuID,
Project: project,
CurrentProject: currentProject,
AvailableDates: availableDates,
Mode: mode,
DateFrom: dateFrom,
DateTo: dateTo,
IsRange: isRange,
IsLive: mode == "daily" && dateFrom == today,
KPI: kpi,
TAT: tat,
Stations: stations,
Arrivals: arrivals,
TATChart: toJS(map[string]interface{}{
"labels": tatLabels,
"values": tatValues,
}),
TrendChart: toJS(map[string]interface{}{
"labels": trendLabels,
"checkedIn": trendCI,
"checkedOut": trendCO,
}),
}
t := parse("templates/layout/base.html", "templates/dashboard/index.html")
if err := t.ExecuteTemplate(w, "base", data); err != nil {
log.Printf("[dashboard] template error: %v", err)
}
}
func Patients(w http.ResponseWriter, r *http.Request) {
project, err := GetProject(activeMcuID(r))
if err != nil {
http.Error(w, "query error", http.StatusInternalServerError)
return
}
availableDates, _ := GetCheckinDates(project.McuID)
mode, dateFrom, dateTo := activeDateRange(r, project, availableDates)
patients, err := GetAllPatients(project.McuID, dateFrom, dateTo)
if err != nil {
log.Printf("[dashboard] GetAllPatients error: %v", err)
http.Error(w, "query error", http.StatusInternalServerError)
return
}
data := PatientsPartial{
Patients: patients,
IsRange: mode != "daily" || dateFrom != dateTo,
}
t := parse("templates/dashboard/partials/patients.html")
if err := t.ExecuteTemplate(w, "patients", data); err != nil {
log.Printf("[dashboard] patients template error: %v", err)
}
}
func KPI(w http.ResponseWriter, r *http.Request) {
project, _ := GetProject(activeMcuID(r))
availableDates, _ := GetCheckinDates(project.McuID)
_, from, to := activeDateRange(r, project, availableDates)
data, err := GetKPI(project.McuID, from, to)
if err != nil {
http.Error(w, "query error", http.StatusInternalServerError)
return
}
t := parse("templates/dashboard/partials/kpi.html")
t.ExecuteTemplate(w, "kpi", data)
}
func Stations(w http.ResponseWriter, r *http.Request) {
project, _ := GetProject(activeMcuID(r))
availableDates, _ := GetCheckinDates(project.McuID)
_, from, to := activeDateRange(r, project, availableDates)
rows, err := GetStations(project.McuID, from, to)
if err != nil {
http.Error(w, "query error", http.StatusInternalServerError)
return
}
t := parse("templates/dashboard/partials/stations.html")
t.ExecuteTemplate(w, "stations", StationsPartial{Rows: rows, IsLive: from == time.Now().Format("2006-01-02")})
}
func Arrivals(w http.ResponseWriter, r *http.Request) {
project, _ := GetProject(activeMcuID(r))
availableDates, _ := GetCheckinDates(project.McuID)
_, from, to := activeDateRange(r, project, availableDates)
rows, err := GetArrivals(project.McuID, from, to, 8)
if err != nil {
http.Error(w, "query error", http.StatusInternalServerError)
return
}
t := parse("templates/dashboard/partials/arrivals.html")
t.ExecuteTemplate(w, "arrivals", rows)
}