Update dashboard filters and packet views
This commit is contained in:
@@ -24,13 +24,16 @@ type pageData struct {
|
||||
AvailableDates []string
|
||||
Search string
|
||||
Department string
|
||||
Paket string
|
||||
Rows []ArrivalRow
|
||||
FilteredRows []ArrivalRow
|
||||
Summary ArrivalSummary
|
||||
Departments []DepartmentStat
|
||||
DepartmentOptions []string
|
||||
PaketOptions []PaketOption
|
||||
OverviewJSON template.JS
|
||||
DepartmentJSON template.JS
|
||||
PaketChartJSON template.JS
|
||||
Page int
|
||||
PageSize int
|
||||
HasMore bool
|
||||
@@ -76,15 +79,27 @@ func buildArrivalData(r *http.Request) (pageData, int, error) {
|
||||
return pageData{}, http.StatusInternalServerError, err
|
||||
}
|
||||
summary, deptStats := BuildArrivalStats(rows)
|
||||
filteredRows := FilterArrivalRows(rows, r.URL.Query().Get("search"), r.URL.Query().Get("dept"))
|
||||
filteredRows := FilterArrivalRows(rows, r.URL.Query().Get("search"), r.URL.Query().Get("dept"), r.URL.Query().Get("paket"))
|
||||
deptOptions := UniqueDepartments(rows)
|
||||
paketOptions := UniquePakets(rows)
|
||||
|
||||
stationMap, err := GetStationProgress(mcuID, selectedDate)
|
||||
if err != nil {
|
||||
return pageData{}, http.StatusInternalServerError, err
|
||||
}
|
||||
for i := range filteredRows {
|
||||
filteredRows[i].Stations = stationMap[filteredRows[i].PreregisterID]
|
||||
stations := stationMap[filteredRows[i].PreregisterID]
|
||||
filteredRows[i].Stations = stations
|
||||
for _, s := range stations {
|
||||
switch s.Tone {
|
||||
case "success":
|
||||
filteredRows[i].DoneCount++
|
||||
case "warning":
|
||||
filteredRows[i].ProgressCount++
|
||||
default:
|
||||
filteredRows[i].PendingCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outerDepts := []map[string]any{}
|
||||
@@ -111,6 +126,26 @@ func buildArrivalData(r *http.Request) (pageData, int, error) {
|
||||
"counts": stationCounts,
|
||||
}
|
||||
|
||||
paketStats, err := GetPaketStats(mcuID, selectedDate)
|
||||
if err != nil {
|
||||
return pageData{}, http.StatusInternalServerError, err
|
||||
}
|
||||
paketLabels := make([]string, len(paketStats))
|
||||
paketTotal := make([]int, len(paketStats))
|
||||
paketDone := make([]int, len(paketStats))
|
||||
paketRemaining := make([]int, len(paketStats))
|
||||
for i, p := range paketStats {
|
||||
paketLabels[i] = p.Name
|
||||
paketTotal[i] = p.Total
|
||||
paketDone[i] = p.Completed
|
||||
paketRemaining[i] = p.Total - p.Completed
|
||||
}
|
||||
paketChart := map[string]any{
|
||||
"labels": paketLabels,
|
||||
"done": paketDone,
|
||||
"remaining": paketRemaining,
|
||||
}
|
||||
|
||||
page := parsePage(r.URL.Query().Get("page"))
|
||||
pagedRows, hasMore := PaginateArrivalRows(filteredRows, page, defaultPageSize)
|
||||
visibleCount := page * defaultPageSize
|
||||
@@ -125,13 +160,16 @@ func buildArrivalData(r *http.Request) (pageData, int, error) {
|
||||
AvailableDates: dates,
|
||||
Search: r.URL.Query().Get("search"),
|
||||
Department: r.URL.Query().Get("dept"),
|
||||
Paket: r.URL.Query().Get("paket"),
|
||||
Rows: rows,
|
||||
FilteredRows: pagedRows,
|
||||
Summary: summary,
|
||||
Departments: deptStats,
|
||||
DepartmentOptions: deptOptions,
|
||||
PaketOptions: paketOptions,
|
||||
OverviewJSON: toJS(overview),
|
||||
DepartmentJSON: toJS(stationChart),
|
||||
PaketChartJSON: toJS(paketChart),
|
||||
Page: page,
|
||||
PageSize: defaultPageSize,
|
||||
HasMore: hasMore,
|
||||
|
||||
@@ -13,6 +13,12 @@ type StationBadge struct {
|
||||
Tone string // "success" | "warning" | "danger" | "neutral"
|
||||
}
|
||||
|
||||
type PaketStat struct {
|
||||
Name string
|
||||
Total int
|
||||
Completed int
|
||||
}
|
||||
|
||||
type StationStat struct {
|
||||
Name string
|
||||
Count int
|
||||
@@ -23,10 +29,16 @@ type ArrivalRow struct {
|
||||
NIP string
|
||||
Name string
|
||||
Department string
|
||||
Pemeriksaan string
|
||||
PaketCode string
|
||||
PaketName string
|
||||
InTime string
|
||||
Status string
|
||||
StatusTone string
|
||||
Stations []StationBadge
|
||||
DoneCount int
|
||||
ProgressCount int
|
||||
PendingCount int
|
||||
}
|
||||
|
||||
type DepartmentStat struct {
|
||||
@@ -83,6 +95,17 @@ func GetArrivalRows(mcuID int, date string) ([]ArrivalRow, error) {
|
||||
NULLIF(TRIM(mp.Mcu_PatientPosisi), ''),
|
||||
'-'
|
||||
) AS department_name,
|
||||
COALESCE(NULLIF(TRIM(mp.Mcu_PatientPemeriksaan), ''), '') AS pemeriksaan,
|
||||
COALESCE((
|
||||
SELECT GROUP_CONCAT(DISTINCT COALESCE(NULLIF(TRIM(pp.Mcu_PatientPacketCode), ''), pp.Mcu_PatientPacketName) ORDER BY COALESCE(NULLIF(TRIM(pp.Mcu_PatientPacketCode), ''), pp.Mcu_PatientPacketName) SEPARATOR ',')
|
||||
FROM mcu_patient_packet pp
|
||||
WHERE pp.Mcu_PatientPacketPreregisterID = mp.Mcu_PatientPreregisterID
|
||||
), '') AS paket_code,
|
||||
COALESCE((
|
||||
SELECT GROUP_CONCAT(DISTINCT pp.Mcu_PatientPacketName ORDER BY pp.Mcu_PatientPacketName SEPARATOR ', ')
|
||||
FROM mcu_patient_packet pp
|
||||
WHERE pp.Mcu_PatientPacketPreregisterID = mp.Mcu_PatientPreregisterID
|
||||
), '') AS paket_name,
|
||||
COALESCE(DATE_FORMAT(mc.Mcu_CheckinoutInTime, '%H:%i'), '') AS in_time,
|
||||
CASE
|
||||
WHEN mc.Mcu_CheckinoutOutTime IS NOT NULL THEN 'Performed'
|
||||
@@ -119,7 +142,7 @@ func GetArrivalRows(mcuID int, date string) ([]ArrivalRow, error) {
|
||||
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 {
|
||||
if err := rows.Scan(&r.PreregisterID, &r.NIP, &r.Name, &r.Department, &r.Pemeriksaan, &r.PaketCode, &r.PaketName, &r.InTime, &r.Status, &r.StatusTone); err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(r.NIP) == "" {
|
||||
@@ -139,31 +162,59 @@ func GetArrivalRows(mcuID int, date string) ([]ArrivalRow, error) {
|
||||
func GetStationProgress(mcuID int, date string) (map[int][]StationBadge, error) {
|
||||
rows, err := db.DB.Query(`
|
||||
SELECT
|
||||
sp.Mcu_StationProgressPreregisterID,
|
||||
sp.Mcu_StationProgressStationName,
|
||||
rs.preregister_id,
|
||||
rs.station_name,
|
||||
CASE
|
||||
WHEN sp.Mcu_StationProgressSource = 'lab'
|
||||
AND sp.Mcu_StationProgressReceiveAt IS NOT NULL THEN 'success'
|
||||
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
||||
AND 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'
|
||||
WHEN MAX(CASE
|
||||
WHEN sp.Mcu_StationProgressSource = 'lab'
|
||||
AND sp.Mcu_StationProgressReceiveAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressReceiveAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
||||
AND sp.Mcu_StationProgressProcessAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressProcessAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
||||
AND sp.Mcu_StationProgressDoneAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressDoneAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
ELSE 0
|
||||
END) = 1 THEN 'success'
|
||||
WHEN MAX(CASE
|
||||
WHEN sp.Mcu_StationProgressProcessAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressProcessAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
WHEN sp.Mcu_StationProgressReceiveAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressReceiveAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
WHEN sp.Mcu_StationProgressSamplingAt IS NOT NULL
|
||||
AND DATE(sp.Mcu_StationProgressSamplingAt) <= mc.Mcu_CheckinoutDate THEN 1
|
||||
ELSE 0
|
||||
END) = 1 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)
|
||||
FROM mcu_patient_required_station rs
|
||||
JOIN mcu_checkinout mc
|
||||
ON mc.Mcu_CheckinoutPreregisterID = rs.preregister_id
|
||||
AND mc.Mcu_CheckinoutMcuID = rs.mcu_id
|
||||
AND mc.Mcu_CheckinoutDate = ?
|
||||
AND mc.Mcu_CheckinoutIsActive = 'Y'
|
||||
LEFT JOIN mcu_station_progress sp
|
||||
ON sp.Mcu_StationProgressPreregisterID = rs.preregister_id
|
||||
AND sp.Mcu_StationProgressStationID = rs.sample_station_id
|
||||
AND sp.Mcu_StationProgressMcuID = rs.mcu_id
|
||||
AND (
|
||||
(sp.Mcu_StationProgressCheckinDate IS NOT NULL
|
||||
AND sp.Mcu_StationProgressCheckinDate <= mc.Mcu_CheckinoutDate)
|
||||
OR
|
||||
(sp.Mcu_StationProgressCheckinDate IS NULL
|
||||
AND (
|
||||
(sp.Mcu_StationProgressProcessAt IS NOT NULL AND DATE(sp.Mcu_StationProgressProcessAt) <= mc.Mcu_CheckinoutDate)
|
||||
OR (sp.Mcu_StationProgressDoneAt IS NOT NULL AND DATE(sp.Mcu_StationProgressDoneAt) <= mc.Mcu_CheckinoutDate)
|
||||
OR (sp.Mcu_StationProgressReceiveAt IS NOT NULL AND DATE(sp.Mcu_StationProgressReceiveAt) <= mc.Mcu_CheckinoutDate)
|
||||
OR (sp.Mcu_StationProgressSamplingAt IS NOT NULL AND DATE(sp.Mcu_StationProgressSamplingAt) <= mc.Mcu_CheckinoutDate)
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE rs.mcu_id = ?
|
||||
GROUP BY rs.preregister_id, rs.station_name
|
||||
ORDER BY rs.preregister_id, rs.station_name
|
||||
`, date, mcuID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -247,16 +298,20 @@ func BuildStationChart(stationMap map[int][]StationBadge) []StationStat {
|
||||
return stats
|
||||
}
|
||||
|
||||
func FilterArrivalRows(rows []ArrivalRow, search, dept string) []ArrivalRow {
|
||||
func FilterArrivalRows(rows []ArrivalRow, search, dept, paket string) []ArrivalRow {
|
||||
search = strings.ToLower(strings.TrimSpace(search))
|
||||
dept = strings.TrimSpace(dept)
|
||||
if search == "" && dept == "" {
|
||||
paket = strings.TrimSpace(paket)
|
||||
if search == "" && dept == "" && paket == "" {
|
||||
return rows
|
||||
}
|
||||
|
||||
out := make([]ArrivalRow, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if dept != "" && dept != "All Departments" && row.Department != dept {
|
||||
if dept != "" && row.Department != dept {
|
||||
continue
|
||||
}
|
||||
if paket != "" && row.PaketCode != paket {
|
||||
continue
|
||||
}
|
||||
if search != "" {
|
||||
@@ -270,6 +325,33 @@ func FilterArrivalRows(rows []ArrivalRow, search, dept string) []ArrivalRow {
|
||||
return out
|
||||
}
|
||||
|
||||
type PaketOption struct {
|
||||
Code string
|
||||
Name string
|
||||
}
|
||||
|
||||
func UniquePakets(rows []ArrivalRow) []PaketOption {
|
||||
seen := map[string]struct{}{}
|
||||
var out []PaketOption
|
||||
for _, row := range rows {
|
||||
code := strings.TrimSpace(row.PaketCode)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[code]; ok {
|
||||
continue
|
||||
}
|
||||
seen[code] = struct{}{}
|
||||
name := strings.TrimSpace(row.PaketName)
|
||||
if name == "" {
|
||||
name = code
|
||||
}
|
||||
out = append(out, PaketOption{Code: code, Name: name})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
|
||||
return out
|
||||
}
|
||||
|
||||
func PaginateArrivalRows(rows []ArrivalRow, page, pageSize int) ([]ArrivalRow, bool) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@@ -306,6 +388,64 @@ func UniqueDepartments(rows []ArrivalRow) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func GetPaketStats(mcuID int, date string) ([]PaketStat, error) {
|
||||
rows, err := db.DB.Query(`
|
||||
SELECT
|
||||
pp.Mcu_PatientPacketName,
|
||||
COUNT(DISTINCT s.Mcu_PatientSchedulePreregisterID) AS total,
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN st.total_required > 0 AND st.total_required = st.total_done
|
||||
THEN s.Mcu_PatientSchedulePreregisterID
|
||||
END) AS completed
|
||||
FROM mcu_patient_packet pp
|
||||
JOIN mcu_patient_schedule s
|
||||
ON s.Mcu_PatientSchedulePreregisterID = pp.Mcu_PatientPacketPreregisterID
|
||||
AND s.Mcu_PatientScheduleDate = ?
|
||||
AND s.Mcu_PatientScheduleIsActive = 'Y'
|
||||
JOIN mcu_patient mp
|
||||
ON mp.Mcu_PatientPreregisterID = s.Mcu_PatientSchedulePreregisterID
|
||||
AND mp.Mcu_PatientMcuID = ?
|
||||
AND mp.Mcu_PatientIsActive = 'Y'
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
rs.preregister_id,
|
||||
COUNT(rs.sample_station_id) AS total_required,
|
||||
SUM(CASE
|
||||
WHEN sp.Mcu_StationProgressSource = 'lab'
|
||||
AND sp.Mcu_StationProgressReceiveAt IS NOT NULL THEN 1
|
||||
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
||||
AND sp.Mcu_StationProgressProcessAt IS NOT NULL THEN 1
|
||||
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
||||
AND sp.Mcu_StationProgressDoneAt IS NOT NULL THEN 1
|
||||
ELSE 0
|
||||
END) AS total_done
|
||||
FROM mcu_patient_required_station rs
|
||||
LEFT JOIN mcu_station_progress sp
|
||||
ON sp.Mcu_StationProgressPreregisterID = rs.preregister_id
|
||||
AND sp.Mcu_StationProgressStationID = rs.sample_station_id
|
||||
AND sp.Mcu_StationProgressMcuID = rs.mcu_id
|
||||
WHERE rs.mcu_id = ?
|
||||
GROUP BY rs.preregister_id
|
||||
) st ON st.preregister_id = s.Mcu_PatientSchedulePreregisterID
|
||||
GROUP BY pp.Mcu_PatientPacketName
|
||||
ORDER BY total DESC
|
||||
`, date, mcuID, mcuID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []PaketStat
|
||||
for rows.Next() {
|
||||
var p PaketStat
|
||||
if err := rows.Scan(&p.Name, &p.Total, &p.Completed); err != nil {
|
||||
continue
|
||||
}
|
||||
result = append(result, p)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func activeDateOrLatest(dates []string, selected string, fallback string) string {
|
||||
selected = strings.TrimSpace(selected)
|
||||
if selected != "" {
|
||||
|
||||
Reference in New Issue
Block a user