Files
Poll-system/app/internal/handlers/admin.go
2025-08-26 14:13:09 -06:00

403 lines
10 KiB
Go

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
}