package controller import ( "math" "strconv" "strings" "time" "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations" "gitlab.com/mbugroup/lti-api.git/internal/response" "github.com/gofiber/fiber/v2" ) type DashboardController struct { DashboardService service.DashboardService } func NewDashboardController(dashboardService service.DashboardService) *DashboardController { return &DashboardController{ DashboardService: dashboardService, } } func (u *DashboardController) GetAll(c *fiber.Ctx) error { parseStringListParam := func(param string) ([]string, error) { if param == "" { return nil, nil } parts := strings.Split(param, ",") result := make([]string, 0, len(parts)) for _, part := range parts { trimmed := strings.TrimSpace(part) if trimmed == "" { return nil, strconv.ErrSyntax } result = append(result, trimmed) } return result, nil } parseUintListParam := func(param string) ([]uint, error) { if param == "" { return nil, nil } parts := strings.Split(param, ",") ids := make([]uint, 0, len(parts)) for _, part := range parts { trimmed := strings.TrimSpace(part) if trimmed == "" { return nil, strconv.ErrSyntax } parsed, err := strconv.ParseUint(trimmed, 10, 64) if err != nil { return nil, err } ids = append(ids, uint(parsed)) } return ids, nil } lokasiIds, err := parseUintListParam(c.Query("location_ids", "")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid location_ids") } flockIds, err := parseUintListParam(c.Query("flock_ids", "")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid flock_ids") } kandangIds, err := parseUintListParam(c.Query("kandang_ids", "")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_ids") } include, err := parseStringListParam(strings.ToLower(c.Query("include", ""))) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid include") } analysisMode := strings.ToUpper(strings.TrimSpace(c.Query("analysis_mode", validation.AnalysisModeOverview))) metric := strings.ToLower(strings.TrimSpace(c.Query("metric", ""))) query := &validation.Query{ Page: c.QueryInt("page", 1), Limit: c.QueryInt("limit", 10), Search: strings.TrimSpace(c.Query("search", "")), PerformanceOverviewFilter: validation.PerformanceOverviewFilter{ StartDate: c.Query("start_date", ""), EndDate: c.Query("end_date", ""), AnalysisMode: analysisMode, ComparisonType: strings.ToUpper(strings.TrimSpace(c.Query("comparison_type", ""))), Metric: metric, LokasiIds: lokasiIds, FlockIds: flockIds, KandangIds: kandangIds, Include: include, }, } if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } if query.AnalysisMode == validation.AnalysisModeComparison && query.ComparisonType == "" { return fiber.NewError(fiber.StatusBadRequest, "comparison_type is required for comparison mode") } location, err := time.LoadLocation("Asia/Jakarta") if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration") } startDate, endDate, endExclusive, err := parsePeriodDates(query.StartDate, query.EndDate, location) if err != nil { return err } query.PeriodStart = startDate query.PeriodEnd = endDate query.PeriodEndExclusive = endExclusive result, totalResults, err := u.DashboardService.GetAll(c.Context(), query) if err != nil { return err } hasFilter := query.StartDate != "" || query.EndDate != "" || len(query.LokasiIds) > 0 || len(query.FlockIds) > 0 || len(query.KandangIds) > 0 || len(query.Include) > 0 || query.ComparisonType != "" || query.Metric != "" || query.AnalysisMode != validation.AnalysisModeOverview var filters interface{} if hasFilter { filters = dto.DashboardFiltersDTO{ StartDate: query.StartDate, EndDate: query.EndDate, AnalysisMode: query.AnalysisMode, ComparisonType: query.ComparisonType, Metric: query.Metric, LokasiIds: defaultUintSlice(query.LokasiIds), FlockIds: defaultUintSlice(query.FlockIds), KandangIds: defaultUintSlice(query.KandangIds), Include: query.Include, } } return c.Status(fiber.StatusOK). JSON(response.SuccessWithMeta{ Code: fiber.StatusOK, Status: "success", Message: "Get dashboard successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, Filters: filters, }, Data: result, }) } func defaultUintSlice(values []uint) []uint { if values == nil { return []uint{} } return values } func parsePeriodDates(startDateRaw, endDateRaw string, location *time.Location) (time.Time, time.Time, time.Time, error) { now := time.Now().In(location) startDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, location) endDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) if startDateRaw != "" { parsed, err := time.ParseInLocation("2006-01-02", startDateRaw, location) if err != nil { return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "start_date must follow format YYYY-MM-DD") } startDate = parsed } if endDateRaw != "" { parsed, err := time.ParseInLocation("2006-01-02", endDateRaw, location) if err != nil { return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "end_date must follow format YYYY-MM-DD") } endDate = parsed } if endDate.Before(startDate) { return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "end_date must be greater than or equal to start_date") } endExclusive := endDate.AddDate(0, 0, 1) return startDate, endDate, endExclusive, nil }