Initial commit
This commit is contained in:
307
cpone-dashboard/menu/arrival/query.go
Normal file
307
cpone-dashboard/menu/arrival/query.go
Normal file
@@ -0,0 +1,307 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user