package handlers import ( "log" "net/http" "strconv" "github.com/patel-mann/poll-system/app/internal/models" "github.com/patel-mann/poll-system/app/internal/utils" ) // PaginationInfo holds pagination metadata type PaginationInfo struct { CurrentPage int TotalPages int TotalRecords int PageSize int HasPrevious bool HasNext bool StartRecord int EndRecord int PreviousPage int NextPage int FirstPage int LastPage int PageNumbers []PageNumber } type PageNumber struct { Number int IsCurrent bool } // AddressWithDetails extends AddressDatabase with appointment and user info type AddressWithDetails struct { models.AddressDatabase UserName string UserEmail string AppointmentDate string AppointmentTime string } func AddressHandler(w http.ResponseWriter, r *http.Request) { // Get pagination parameters from query string pageStr := r.URL.Query().Get("page") pageSizeStr := r.URL.Query().Get("pageSize") username,_ := models.GetCurrentUserName(r) page := 1 pageSize := 20 if pageStr != "" { if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { page = p } } if pageSizeStr != "" { if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 && ps <= 100 { pageSize = ps } } offset := (page - 1) * pageSize // Get total count var totalRecords int err := models.DB.QueryRow(`SELECT COUNT(*) FROM "address_database"`).Scan(&totalRecords) if err != nil { log.Println("Count query error:", err) http.Error(w, "Database error", http.StatusInternalServerError) return } totalPages := (totalRecords + pageSize - 1) / pageSize if totalPages == 0 { totalPages = 1 } if page > totalPages { page = totalPages offset = (page - 1) * pageSize } // Query addresses with appointment + user info rows, err := models.DB.Query(` SELECT a.address_id, a.address, a.street_name, a.street_type, a.street_quadrant, a.house_number, COALESCE(a.house_alpha, '') as house_alpha, a.longitude, a.latitude, a.visited_validated, a.created_at, a.updated_at, CASE WHEN ap.sched_id IS NOT NULL THEN true ELSE false END as assigned, COALESCE(u.first_name || ' ' || u.last_name, '') as user_name, COALESCE(u.email, '') as user_email, COALESCE(ap.appointment_date::text, '') as appointment_date, COALESCE(ap.appointment_time::text, '') as appointment_time FROM address_database a LEFT JOIN appointment ap ON a.address_id = ap.address_id LEFT JOIN users u ON ap.user_id = u.user_id WHERE a.street_quadrant = 'ne' ORDER BY a.address_id LIMIT $1 OFFSET $2 `, pageSize, offset) if err != nil { log.Println("Query error:", err) http.Error(w, "Database error", http.StatusInternalServerError) return } defer rows.Close() var addresses []AddressWithDetails for rows.Next() { var a AddressWithDetails var houseAlpha string err := rows.Scan( &a.AddressID, &a.Address, &a.StreetName, &a.StreetType, &a.StreetQuadrant, &a.HouseNumber, &houseAlpha, &a.Longitude, &a.Latitude, &a.VisitedValidated, &a.CreatedAt, &a.UpdatedAt, &a.Assigned, &a.UserName, &a.UserEmail, &a.AppointmentDate, &a.AppointmentTime, ) if err != nil { log.Println("Scan error:", err) continue } // Handle nullable house_alpha if houseAlpha != "" { a.HouseAlpha = &houseAlpha } addresses = append(addresses, a) } // Get users associated with this admin currentAdminID := r.Context().Value("user_id").(int) userRows, err := models.DB.Query(` SELECT u.user_id, u.first_name || ' ' || u.last_name AS name FROM users u JOIN admin_volunteers av ON u.user_id = av.volunteer_id WHERE av.admin_id = $1 AND av.is_active = true `, currentAdminID) if err != nil { log.Println("Failed to fetch users:", err) http.Error(w, "Database error", http.StatusInternalServerError) return } defer userRows.Close() type UserOption struct { ID int Name string } var users []UserOption for userRows.Next() { var u UserOption if err := userRows.Scan(&u.ID, &u.Name); err != nil { log.Println("User scan error:", err) continue } users = append(users, u) } // Pagination info startRecord := offset + 1 endRecord := offset + len(addresses) if totalRecords == 0 { startRecord = 0 } pageNumbers := generatePageNumbers(page, totalPages) pagination := PaginationInfo{ CurrentPage: page, TotalPages: totalPages, TotalRecords: totalRecords, PageSize: pageSize, HasPrevious: page > 1, HasNext: page < totalPages, StartRecord: startRecord, EndRecord: endRecord, PreviousPage: page - 1, NextPage: page + 1, FirstPage: 1, LastPage: totalPages, PageNumbers: pageNumbers, } utils.Render(w, "address/address.html", map[string]interface{}{ "Title": "Addresses", "IsAuthenticated": true, "ShowAdminNav": true, "ActiveSection": "address", "Addresses": addresses, "Users": users, "UserName": username, "Role": "admin", "Pagination": pagination, }) } func generatePageNumbers(currentPage, totalPages int) []PageNumber { var pageNumbers []PageNumber // Generate page numbers to show (max 7 pages) start := currentPage - 3 end := currentPage + 3 if start < 1 { end += 1 - start start = 1 } if end > totalPages { start -= end - totalPages end = totalPages } if start < 1 { start = 1 } for i := start; i <= end; i++ { pageNumbers = append(pageNumbers, PageNumber{ Number: i, IsCurrent: i == currentPage, }) } return pageNumbers } func AssignAddressHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Redirect(w, r, "/addresses", http.StatusSeeOther) return } if err := r.ParseForm(); err != nil { http.Error(w, "Invalid form", http.StatusBadRequest) return } userIDStr := r.FormValue("user_id") addressIDStr := r.FormValue("address_id") if userIDStr == "" || addressIDStr == "" { http.Error(w, "User ID and Address ID are required", http.StatusBadRequest) return } userID, err := strconv.Atoi(userIDStr) if err != nil { http.Error(w, "Invalid user ID", http.StatusBadRequest) return } addressID, err := strconv.Atoi(addressIDStr) if err != nil { http.Error(w, "Invalid address ID", http.StatusBadRequest) return } // Verify the user exists and is associated with the current admin currentAdminID := r.Context().Value("user_id").(int) var userExists int err = models.DB.QueryRow(` SELECT COUNT(*) FROM admin_volunteers av JOIN users u ON av.volunteer_id = u.user_id WHERE av.admin_id = $1 AND u.user_id = $2 AND av.is_active = true `, currentAdminID, userID).Scan(&userExists) if err != nil { log.Println("User verification error:", err) http.Error(w, "Database error", http.StatusInternalServerError) return } if userExists == 0 { http.Error(w, "Invalid user selection", http.StatusBadRequest) return } // Check if this address is already assigned to any user var exists int err = models.DB.QueryRow(` SELECT COUNT(*) FROM appointment WHERE address_id = $1 `, addressID).Scan(&exists) if err != nil { log.Println("Assignment check error:", err) http.Error(w, "Database error", http.StatusInternalServerError) return } if exists > 0 { http.Error(w, "This address is already assigned to another user", http.StatusBadRequest) return } // Assign the address - create appointment _, err = models.DB.Exec(` INSERT INTO appointment (user_id, address_id, appointment_date, appointment_time, created_at, updated_at) VALUES ($1, $2, CURRENT_DATE, CURRENT_TIME, NOW(), NOW()) `, userID, addressID) if err != nil { log.Println("Assignment error:", err) http.Error(w, "Failed to assign address", http.StatusInternalServerError) return } // Redirect back to addresses page with success http.Redirect(w, r, "/addresses?success=assigned", http.StatusSeeOther) }