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) }