403 lines
11 KiB
Go
403 lines
11 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
|
||
|
|
// }
|