package handlers import ( "database/sql" "errors" "log" "net/http" "strconv" "time" "github.com/patel-mann/poll-system/app/internal/models" "github.com/patel-mann/poll-system/app/internal/utils" ) // View model for listing/assigning schedules type AssignmentVM struct { ID int VolunteerID int VolunteerName string AddressID int Address string Date string // YYYY-MM-DD (for input[type=date]) AppointmentTime string // HH:MM VisitedValidated bool } // GET + POST in one handler: // - GET: show assignments + form to assign // - POST: create a new assignment func AdminAssignmentsHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: if err := createAssignmentFromForm(r); err != nil { log.Println("create assignment error:", err) volunteers, _ := fetchVolunteers() addresses, _ := fetchAddresses() assignments, _ := fetchAssignments() utils.Render(w, "schedual/assignments.html", map[string]interface{}{ "Title": "Admin — Assign Addresses", "IsAuthenticated": true, "ActiveSection": "admin_assignments", "Volunteers": volunteers, "Addresses": addresses, "Assignments": assignments, "Error": err.Error(), }) return } http.Redirect(w, r, "/admin/assignments", http.StatusSeeOther) return } // GET: fetch volunteers, addresses, and existing assignments volunteers, err := fetchVolunteers() if err != nil { log.Println("fetch volunteers error:", err) http.Error(w, "Failed to load volunteers", http.StatusInternalServerError) return } addresses, err := fetchAddresses() if err != nil { log.Println("fetch addresses error:", err) http.Error(w, "Failed to load addresses", http.StatusInternalServerError) return } assignments, err := fetchAssignments() if err != nil { log.Println("fetch assignments error:", err) http.Error(w, "Failed to load assignments", http.StatusInternalServerError) return } utils.Render(w, "assignments.html", map[string]interface{}{ "Title": "Admin — Assign Addresses", "IsAuthenticated": true, "ActiveSection": "admin_assignments", "Volunteers": volunteers, "Addresses": addresses, "Assignments": assignments, }) } // GET (edit form) + POST (update/delete) func AdminAssignmentEditHandler(w http.ResponseWriter, r *http.Request) { idStr := r.URL.Query().Get("id") id, _ := strconv.Atoi(idStr) if id <= 0 { http.NotFound(w, r) return } if r.Method == http.MethodPost { action := r.FormValue("action") switch action { case "delete": if err := deleteAssignment(id); err != nil { log.Println("delete assignment error:", err) http.Error(w, "Failed to delete assignment", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/assignments", http.StatusSeeOther) return case "update": if err := updateAssignmentFromForm(id, r); err != nil { log.Println("update assignment error:", err) vm, _ := fetchAssignmentByID(id) volunteers, _ := fetchVolunteers() addresses, _ := fetchAddresses() utils.Render(w, "assignment_edit.html", map[string]interface{}{ "Title": "Edit Assignment", "Assignment": vm, "Volunteers": volunteers, "Addresses": addresses, "Error": err.Error(), }) return } http.Redirect(w, r, "/admin/assignments", http.StatusSeeOther) return default: http.Error(w, "Unknown action", http.StatusBadRequest) return } } // GET edit vm, err := fetchAssignmentByID(id) if err != nil { if err == sql.ErrNoRows { http.NotFound(w, r) return } log.Println("fetch assignment by ID error:", err) http.Error(w, "Failed to load assignment", http.StatusInternalServerError) return } volunteers, err := fetchVolunteers() if err != nil { log.Println("fetch volunteers error:", err) http.Error(w, "Failed to load volunteers", http.StatusInternalServerError) return } addresses, err := fetchAddresses() if err != nil { log.Println("fetch addresses error:", err) http.Error(w, "Failed to load addresses", http.StatusInternalServerError) return } utils.Render(w, "assignment_edit.html", map[string]interface{}{ "Title": "Edit Assignment", "Assignment": vm, "Volunteers": volunteers, "Addresses": addresses, }) } // ----- Helpers ----- func createAssignmentFromForm(r *http.Request) error { volID, _ := strconv.Atoi(r.FormValue("volunteer_id")) addrID, _ := strconv.Atoi(r.FormValue("address_id")) dateStr := r.FormValue("date") timeStr := r.FormValue("appointment_time") if volID <= 0 || addrID <= 0 || dateStr == "" || timeStr == "" { return errors.New("please fill all required fields") } if _, err := time.Parse("2006-01-02", dateStr); err != nil { return errors.New("invalid date format") } if _, err := time.Parse("15:04", timeStr); err != nil { return errors.New("invalid time format") } _, err := models.DB.Exec(` INSERT INTO schedual (user_id, address_id, appointment_date, appointment_time, created_at, updated_at) VALUES ($1,$2,$3,$4,NOW(),NOW()) `, volID, addrID, dateStr, timeStr) if err != nil { log.Println("database insert error:", err) return errors.New("failed to create assignment") } return nil } func updateAssignmentFromForm(id int, r *http.Request) error { volID, _ := strconv.Atoi(r.FormValue("volunteer_id")) addrID, _ := strconv.Atoi(r.FormValue("address_id")) dateStr := r.FormValue("date") timeStr := r.FormValue("appointment_time") if volID <= 0 || addrID <= 0 || dateStr == "" || timeStr == "" { return errors.New("please fill all required fields") } if _, err := time.Parse("2006-01-02", dateStr); err != nil { return errors.New("invalid date format") } if _, err := time.Parse("15:04", timeStr); err != nil { return errors.New("invalid time format") } result, err := models.DB.Exec(` UPDATE schedual SET user_id=$1, address_id=$2, appointment_date=$3, appointment_time=$4, updated_at=NOW() WHERE schedual_id=$5 `, volID, addrID, dateStr, timeStr, id) if err != nil { log.Println("database update error:", err) return errors.New("failed to update assignment") } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { return errors.New("assignment not found") } return nil } func deleteAssignment(id int) error { result, err := models.DB.Exec(`DELETE FROM schedual WHERE schedual_id=$1`, id) if err != nil { log.Println("database delete error:", err) return errors.New("failed to delete assignment") } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { return errors.New("assignment not found") } return nil } // Fetch volunteers type VolunteerPick struct { ID int FirstName string LastName string Email string } func fetchVolunteers() ([]VolunteerPick, error) { rows, err := models.DB.Query(` SELECT users_id, first_name, last_name, email FROM "user" WHERE role='volunteer' ORDER BY first_name, last_name `) if err != nil { return nil, err } defer rows.Close() var out []VolunteerPick for rows.Next() { var v VolunteerPick if err := rows.Scan(&v.ID, &v.FirstName, &v.LastName, &v.Email); err != nil { log.Println("fetchVolunteers scan:", err) continue } out = append(out, v) } return out, rows.Err() } // Fetch addresses type AddressPick struct { ID int Label string VisitedValidated bool } func fetchAddresses() ([]AddressPick, error) { rows, err := models.DB.Query(` SELECT address_id, address, street_name, street_type, street_quadrant, house_number, house_alpha, longitude, latitude, visited_validated FROM address_database ORDER BY address_id DESC `) if err != nil { return nil, err } defer rows.Close() var out []AddressPick for rows.Next() { var addr models.AddressDatabase if err := rows.Scan( &addr.AddressID, &addr.Address, &addr.StreetName, &addr.StreetType, &addr.StreetQuadrant, &addr.HouseNumber, &addr.HouseAlpha, &addr.Longitude, &addr.Latitude, &addr.VisitedValidated, ); err != nil { log.Println("fetchAddresses scan:", err) continue } label := addr.Address if label == "" { label = addr.HouseNumber if addr.StreetName != "" { if label != "" { label += " " } label += addr.StreetName } if addr.StreetType != "" { label += " " + addr.StreetType } if addr.StreetQuadrant != "" { label += " " + addr.StreetQuadrant } if addr.HouseAlpha != nil { label += " " + *addr.HouseAlpha } } out = append(out, AddressPick{ ID: addr.AddressID, Label: label, VisitedValidated: addr.VisitedValidated, }) } return out, rows.Err() } // Add this missing function func fetchAssignments() ([]AssignmentVM, error) { rows, err := models.DB.Query(` SELECT s.schedual_id, u.users_id, COALESCE(u.first_name,'') || ' ' || COALESCE(u.last_name,'') AS volunteer_name, a.address_id, COALESCE(a.address,'') AS address, s.appointment_date, s.appointment_time FROM schedual s JOIN "user" u ON u.users_id = s.user_id JOIN address_database a ON a.address_id = s.address_id ORDER BY s.appointment_date DESC, s.appointment_time DESC `) if err != nil { return nil, err } defer rows.Close() var assignments []AssignmentVM for rows.Next() { var vm AssignmentVM if err := rows.Scan(&vm.ID, &vm.VolunteerID, &vm.VolunteerName, &vm.AddressID, &vm.Address, &vm.Date, &vm.AppointmentTime); err != nil { log.Println("fetchAssignments scan:", err) continue } assignments = append(assignments, vm) } return assignments, rows.Err() } func fetchAssignmentByID(id int) (AssignmentVM, error) { var vm AssignmentVM err := models.DB.QueryRow(` SELECT s.schedual_id, u.users_id, COALESCE(u.first_name,'') || ' ' || COALESCE(u.last_name,'') AS volunteer_name, a.address_id, COALESCE(a.address,'') AS address, s.appointment_date, s.appointment_time FROM schedual s JOIN "user" u ON u.users_id = s.user_id JOIN address_database a ON a.address_id = s.address_id WHERE s.schedual_id = $1 `, id).Scan(&vm.ID, &vm.VolunteerID, &vm.VolunteerName, &vm.AddressID, &vm.Address, &vm.Date, &vm.AppointmentTime) return vm, err }