308 lines
7.6 KiB
Go
308 lines
7.6 KiB
Go
package arrival
|
|
|
|
import (
|
|
"cpone-dashboard/db"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type StationBadge struct {
|
|
Name string
|
|
Tone string // "success" | "warning" | "danger" | "neutral"
|
|
}
|
|
|
|
type StationStat struct {
|
|
Name string
|
|
Count int
|
|
}
|
|
|
|
type ArrivalRow struct {
|
|
PreregisterID int
|
|
NIP string
|
|
Name string
|
|
Department string
|
|
InTime string
|
|
Status string
|
|
StatusTone string
|
|
Stations []StationBadge
|
|
}
|
|
|
|
type DepartmentStat struct {
|
|
Name string
|
|
CheckedIn int
|
|
Pending int
|
|
Total int
|
|
}
|
|
|
|
type ArrivalSummary struct {
|
|
CheckedIn int
|
|
Pending int
|
|
Total int
|
|
}
|
|
|
|
func GetArrivalDates(mcuID int) ([]string, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT DATE_FORMAT(Mcu_PatientScheduleDate, '%Y-%m-%d') AS schedule_date
|
|
FROM mcu_patient_schedule
|
|
WHERE Mcu_PatientSchedulePreregisterID IN (
|
|
SELECT Mcu_PatientPreregisterID
|
|
FROM mcu_patient
|
|
WHERE Mcu_PatientMcuID = ?
|
|
AND Mcu_PatientIsActive = 'Y'
|
|
)
|
|
AND Mcu_PatientScheduleIsActive = 'Y'
|
|
GROUP BY Mcu_PatientScheduleDate
|
|
ORDER BY Mcu_PatientScheduleDate DESC
|
|
`, mcuID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var dates []string
|
|
for rows.Next() {
|
|
var d string
|
|
if rows.Scan(&d) == nil && d != "" {
|
|
dates = append(dates, d)
|
|
}
|
|
}
|
|
return dates, nil
|
|
}
|
|
|
|
func GetArrivalRows(mcuID int, date string) ([]ArrivalRow, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT
|
|
mp.Mcu_PatientPreregisterID,
|
|
COALESCE(NULLIF(TRIM(mp.Mcu_PatientNIP), ''), '') AS nip,
|
|
COALESCE(NULLIF(TRIM(mp.Mcu_PatientName), ''), '') AS patient_name,
|
|
COALESCE(
|
|
NULLIF(TRIM(mp.Mcu_PatientDepartment), ''),
|
|
NULLIF(TRIM(mp.Mcu_PatientDivision), ''),
|
|
NULLIF(TRIM(mp.Mcu_PatientPosisi), ''),
|
|
'-'
|
|
) AS department_name,
|
|
COALESCE(DATE_FORMAT(mc.Mcu_CheckinoutInTime, '%H:%i'), '') AS in_time,
|
|
CASE
|
|
WHEN mc.Mcu_CheckinoutOutTime IS NOT NULL THEN 'Performed'
|
|
WHEN mc.Mcu_CheckinoutInTime IS NOT NULL THEN 'In Progress'
|
|
ELSE 'Not Check-in Yet'
|
|
END AS status_text,
|
|
CASE
|
|
WHEN mc.Mcu_CheckinoutOutTime IS NOT NULL THEN 'success'
|
|
WHEN mc.Mcu_CheckinoutInTime IS NOT NULL THEN 'warning'
|
|
ELSE 'neutral'
|
|
END AS status_tone
|
|
FROM mcu_patient_schedule s
|
|
JOIN mcu_patient mp
|
|
ON mp.Mcu_PatientPreregisterID = s.Mcu_PatientSchedulePreregisterID
|
|
AND mp.Mcu_PatientMcuID = ?
|
|
AND mp.Mcu_PatientIsActive = 'Y'
|
|
LEFT JOIN mcu_checkinout mc
|
|
ON mc.Mcu_CheckinoutPreregisterID = mp.Mcu_PatientPreregisterID
|
|
AND mc.Mcu_CheckinoutMcuID = ?
|
|
AND mc.Mcu_CheckinoutDate = s.Mcu_PatientScheduleDate
|
|
AND mc.Mcu_CheckinoutIsActive = 'Y'
|
|
WHERE s.Mcu_PatientScheduleIsActive = 'Y'
|
|
AND s.Mcu_PatientScheduleDate = ?
|
|
ORDER BY
|
|
CASE WHEN mc.Mcu_CheckinoutInTime IS NULL THEN 1 ELSE 0 END,
|
|
mc.Mcu_CheckinoutInTime DESC,
|
|
mp.Mcu_PatientName ASC
|
|
`, mcuID, mcuID, date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []ArrivalRow
|
|
for rows.Next() {
|
|
var r ArrivalRow
|
|
if err := rows.Scan(&r.PreregisterID, &r.NIP, &r.Name, &r.Department, &r.InTime, &r.Status, &r.StatusTone); err != nil {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(r.NIP) == "" {
|
|
r.NIP = "-"
|
|
}
|
|
if strings.TrimSpace(r.Name) == "" {
|
|
r.Name = "-"
|
|
}
|
|
if strings.TrimSpace(r.Department) == "" {
|
|
r.Department = "-"
|
|
}
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func GetStationProgress(mcuID int, date string) (map[int][]StationBadge, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT
|
|
sp.Mcu_StationProgressPreregisterID,
|
|
sp.Mcu_StationProgressStationName,
|
|
CASE
|
|
WHEN sp.Mcu_StationProgressDoneAt IS NOT NULL THEN 'success'
|
|
WHEN sp.Mcu_StationProgressProcessAt IS NOT NULL
|
|
OR sp.Mcu_StationProgressReceiveAt IS NOT NULL
|
|
OR sp.Mcu_StationProgressSamplingAt IS NOT NULL THEN 'warning'
|
|
ELSE 'neutral'
|
|
END AS tone
|
|
FROM mcu_station_progress sp
|
|
WHERE sp.Mcu_StationProgressMcuID = ?
|
|
AND sp.Mcu_StationProgressPreregisterID IN (
|
|
SELECT mp.Mcu_PatientPreregisterID
|
|
FROM mcu_patient_schedule s
|
|
JOIN mcu_patient mp ON mp.Mcu_PatientPreregisterID = s.Mcu_PatientSchedulePreregisterID
|
|
WHERE mp.Mcu_PatientMcuID = ?
|
|
AND mp.Mcu_PatientIsActive = 'Y'
|
|
AND s.Mcu_PatientScheduleDate = ?
|
|
AND s.Mcu_PatientScheduleIsActive = 'Y'
|
|
)
|
|
ORDER BY sp.Mcu_StationProgressPreregisterID, sp.Mcu_StationProgressStationName
|
|
`, mcuID, mcuID, date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
result := map[int][]StationBadge{}
|
|
for rows.Next() {
|
|
var preregID int
|
|
var name, tone string
|
|
if err := rows.Scan(&preregID, &name, &tone); err != nil {
|
|
continue
|
|
}
|
|
result[preregID] = append(result[preregID], StationBadge{Name: name, Tone: tone})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func BuildArrivalStats(rows []ArrivalRow) (ArrivalSummary, []DepartmentStat) {
|
|
summary := ArrivalSummary{Total: len(rows)}
|
|
deptMap := map[string]*DepartmentStat{}
|
|
|
|
for _, row := range rows {
|
|
if row.InTime != "" {
|
|
summary.CheckedIn++
|
|
}
|
|
dept := row.Department
|
|
if dept == "" {
|
|
dept = "-"
|
|
}
|
|
stat, ok := deptMap[dept]
|
|
if !ok {
|
|
stat = &DepartmentStat{Name: dept}
|
|
deptMap[dept] = stat
|
|
}
|
|
stat.Total++
|
|
if row.InTime != "" {
|
|
stat.CheckedIn++
|
|
}
|
|
}
|
|
|
|
summary.Pending = summary.Total - summary.CheckedIn
|
|
if summary.Pending < 0 {
|
|
summary.Pending = 0
|
|
}
|
|
|
|
stats := make([]DepartmentStat, 0, len(deptMap))
|
|
for _, stat := range deptMap {
|
|
stat.Pending = stat.Total - stat.CheckedIn
|
|
if stat.Pending < 0 {
|
|
stat.Pending = 0
|
|
}
|
|
stats = append(stats, *stat)
|
|
}
|
|
sort.Slice(stats, func(i, j int) bool {
|
|
if stats[i].CheckedIn != stats[j].CheckedIn {
|
|
return stats[i].CheckedIn > stats[j].CheckedIn
|
|
}
|
|
if stats[i].Total != stats[j].Total {
|
|
return stats[i].Total > stats[j].Total
|
|
}
|
|
return stats[i].Name < stats[j].Name
|
|
})
|
|
|
|
return summary, stats
|
|
}
|
|
|
|
func BuildStationChart(stationMap map[int][]StationBadge) []StationStat {
|
|
countMap := map[string]int{}
|
|
for _, badges := range stationMap {
|
|
for _, b := range badges {
|
|
countMap[b.Name]++
|
|
}
|
|
}
|
|
stats := make([]StationStat, 0, len(countMap))
|
|
for name, count := range countMap {
|
|
stats = append(stats, StationStat{Name: name, Count: count})
|
|
}
|
|
sort.Slice(stats, func(i, j int) bool {
|
|
return stats[i].Count > stats[j].Count
|
|
})
|
|
return stats
|
|
}
|
|
|
|
func FilterArrivalRows(rows []ArrivalRow, search, dept string) []ArrivalRow {
|
|
search = strings.ToLower(strings.TrimSpace(search))
|
|
dept = strings.TrimSpace(dept)
|
|
if search == "" && dept == "" {
|
|
return rows
|
|
}
|
|
|
|
out := make([]ArrivalRow, 0, len(rows))
|
|
for _, row := range rows {
|
|
if dept != "" && dept != "All Departments" && row.Department != dept {
|
|
continue
|
|
}
|
|
if search != "" {
|
|
hay := strings.ToLower(row.Name + " " + row.NIP + " " + row.Department)
|
|
if !strings.Contains(hay, search) {
|
|
continue
|
|
}
|
|
}
|
|
out = append(out, row)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func UniqueDepartments(rows []ArrivalRow) []string {
|
|
seen := map[string]struct{}{}
|
|
var out []string
|
|
for _, row := range rows {
|
|
name := strings.TrimSpace(row.Department)
|
|
if name == "" {
|
|
name = "-"
|
|
}
|
|
if _, ok := seen[name]; ok {
|
|
continue
|
|
}
|
|
seen[name] = struct{}{}
|
|
out = append(out, name)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func activeDateOrLatest(dates []string, selected string, fallback string) string {
|
|
selected = strings.TrimSpace(selected)
|
|
if selected != "" {
|
|
for _, d := range dates {
|
|
if d == selected {
|
|
return selected
|
|
}
|
|
}
|
|
}
|
|
if len(dates) > 0 {
|
|
return dates[0]
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func mustDayLabel(date string) string {
|
|
if len(date) >= 10 {
|
|
return fmt.Sprintf("%s/%s/%s", date[8:10], date[5:7], date[0:4])
|
|
}
|
|
return date
|
|
}
|