Initial commit
This commit is contained in:
402
app/internal/handlers/admin.go
Normal file
402
app/internal/handlers/admin.go
Normal file
@@ -0,0 +1,402 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user