update to volunteer

This commit is contained in:
Mann Patel
2025-08-28 17:09:23 -06:00
parent 9dd24e71e7
commit 1955407d7c
16 changed files with 1075 additions and 306 deletions

View File

@@ -4,6 +4,7 @@ import (
"log"
"net/http"
"strconv"
"time"
"github.com/patel-mann/poll-system/app/internal/models"
"github.com/patel-mann/poll-system/app/internal/utils"
@@ -262,12 +263,19 @@ func AssignAddressHandler(w http.ResponseWriter, r *http.Request) {
userIDStr := r.FormValue("user_id")
addressIDStr := r.FormValue("address_id")
appointmentDate := r.FormValue("appointment_date")
startTime := r.FormValue("time")
if userIDStr == "" || addressIDStr == "" {
http.Error(w, "User ID and Address ID are required", http.StatusBadRequest)
return
}
if appointmentDate == "" || startTime == "" {
http.Error(w, "Appointment date and start time are required", http.StatusBadRequest)
return
}
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
@@ -280,6 +288,27 @@ func AssignAddressHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Parse and validate the appointment date
parsedDate, err := time.Parse("2006-01-02", appointmentDate)
if err != nil {
http.Error(w, "Invalid appointment date format", http.StatusBadRequest)
return
}
// Validate that the appointment date is not in the past
today := time.Now().Truncate(24 * time.Hour)
if parsedDate.Before(today) {
http.Error(w, "Appointment date cannot be in the past", http.StatusBadRequest)
return
}
// Parse and validate the start time
_, err = time.Parse("15:04", startTime)
if err != nil {
http.Error(w, "Invalid start time format", http.StatusBadRequest)
return
}
// Verify the user exists and is associated with the current admin
currentAdminID := r.Context().Value("user_id").(int)
var userExists int
@@ -314,11 +343,27 @@ func AssignAddressHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Assign the address - create appointment
// Check if the user already has an appointment at the same date and time
var timeConflict int
err = models.DB.QueryRow(`
SELECT COUNT(*) FROM appointment
WHERE user_id = $1 AND appointment_date = $2 AND appointment_time = $3
`, userID, appointmentDate, startTime).Scan(&timeConflict)
if err != nil {
log.Println("Time conflict check error:", err)
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
if timeConflict > 0 {
http.Error(w, "User already has an appointment at this date and time", http.StatusBadRequest)
return
}
// Assign the address - create appointment with specific date and time
_, 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)
VALUES ($1, $2, $3, $4, NOW(), NOW())
`, userID, addressID, appointmentDate, startTime)
if err != nil {
log.Println("Assignment error:", err)
http.Error(w, "Failed to assign address", http.StatusInternalServerError)

View File

@@ -33,7 +33,7 @@ func AdminDashboardHandler(w http.ResponseWriter, r *http.Request) {
// 2. Total donations from polls
err = models.DB.QueryRow(`
SELECT COALESCE(SUM(amount_donated), 0)
FROM poll;
FROM poll_responce;
`).Scan(&totalDonations)
if err != nil {
log.Println("Donation query error:", err)

View File

@@ -8,73 +8,103 @@ import (
"github.com/patel-mann/poll-system/app/internal/utils"
)
func VolunteerAppointmentHandler(w http.ResponseWriter, r *http.Request) {
// Fetch appointments joined with address info
// Fetch appointments joined with address info
currentUserID := models.GetCurrentUserID(w, r)
username, _ := models.GetCurrentUserName(r)
currentUserID := models.GetCurrentUserID(w,r)
username,_ := models.GetCurrentUserName(r)
rows, err := models.DB.Query(`
SELECT
a.sched_id,
a.user_id,
ad.address_id,
ad.address,
ad.latitude,
ad.longitude,
a.appointment_date,
a.appointment_time
FROM appointment a
JOIN address_database ad ON a.address_id = ad.address_id
WHERE a.user_id = $1
`, currentUserID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
rows, err := models.DB.Query(`
SELECT
a.sched_id,
a.user_id,
ad.address,
ad.latitude,
ad.longitude,
a.appointment_date,
a.appointment_time
FROM appointment a
JOIN address_database ad ON a.address_id = ad.address_id
WHERE a.user_id = $1
`, currentUserID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
// Struct to hold appointment + address info
type AppointmentWithAddress struct {
SchedID int
UserID int
AddressID int
Address string
Latitude float64
Longitude float64
AppointmentDate time.Time
AppointmentTime time.Time
HasPollResponse bool // New field to track poll status
PollButtonText string // New field for button text
PollButtonClass string // New field for button styling
}
// Struct to hold appointment + address info
type AppointmentWithAddress struct {
SchedID int
UserID int
Address string
Latitude float64
Longitude float64
AppointmentDate time.Time
AppointmentTime time.Time
}
var appointments []AppointmentWithAddress
for rows.Next() {
var a AppointmentWithAddress
if err := rows.Scan(&a.SchedID, &a.UserID, &a.AddressID, &a.Address, &a.Latitude, &a.Longitude, &a.AppointmentDate, &a.AppointmentTime); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Check if poll response exists for this address
var pollResponseExists bool
err = models.DB.QueryRow(`
SELECT EXISTS(
SELECT 1
FROM poll p
JOIN poll_response pr ON p.poll_id = pr.poll_id
WHERE p.address_id = $1 AND p.user_id = $2
)
`, a.AddressID, currentUserID).Scan(&pollResponseExists)
if err != nil {
// If there's an error checking, default to no response
pollResponseExists = false
}
// Set button properties based on poll response status
a.HasPollResponse = pollResponseExists
if pollResponseExists {
a.PollButtonText = "Poll Taken"
a.PollButtonClass = "px-3 py-1 bg-green-600 text-white text-sm rounded cursor-not-allowed"
} else {
a.PollButtonText = "Ask Poll"
a.PollButtonClass = "px-3 py-1 bg-blue-600 text-white text-sm hover:bg-blue-700 rounded"
}
appointments = append(appointments, a)
}
var appointments []AppointmentWithAddress
for rows.Next() {
var a AppointmentWithAddress
if err := rows.Scan(&a.SchedID, &a.UserID, &a.Address, &a.Latitude, &a.Longitude, &a.AppointmentDate, &a.AppointmentTime); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
appointments = append(appointments, a)
}
role := r.Context().Value("user_role").(int)
adminnav := false
volunteernav := false
if role == 1{
if role == 1 {
adminnav = true
volunteernav = false
}else{
} else {
adminnav = false
volunteernav = true
}
// Render template
utils.Render(w, "/appointment.html", map[string]interface{}{
"Title": "My Profile",
"IsAuthenticated": true,
"ShowAdminNav": adminnav, // your existing variable
"ShowVolunteerNav": volunteernav, // your existing variable
"ActiveSection": "address",
"UserName": username,
"Appointments": appointments, // pass the fetched appointments
})
}
// Render template
utils.Render(w, "/appointment.html", map[string]interface{}{
"Title": "My Profile",
"IsAuthenticated": true,
"ShowAdminNav": adminnav,
"ShowVolunteerNav": volunteernav,
"ActiveSection": "address",
"UserName": username,
"Appointments": appointments,
})
}

View File

@@ -0,0 +1,138 @@
package handlers
import (
"database/sql"
"fmt"
"log"
"net/http"
"strconv"
"github.com/patel-mann/poll-system/app/internal/models"
"github.com/patel-mann/poll-system/app/internal/utils"
)
func PollHandler(w http.ResponseWriter, r *http.Request) {
username, _ := models.GetCurrentUserName(r)
if r.Method == http.MethodGet {
addressID := r.URL.Query().Get("address_id")
if addressID == "" {
http.Error(w, "Address ID required", http.StatusBadRequest)
return
}
// Get address details
var address string
var userID int
fmt.Print(addressID, userID)
err := models.DB.QueryRow(`
SELECT a.address, ap.user_id
FROM appointment AS ap
JOIN address_database a ON a.address_id = ap.address_id
WHERE ap.address_id = $1
`, addressID).Scan(&address, &userID)
if err != nil {
http.Error(w, "Address not found", http.StatusNotFound)
return
}
// Check if poll already exists for this address
var pollID int
err = models.DB.QueryRow(`
SELECT poll_id
FROM poll
WHERE address_id = $1 AND user_id = $2
`, addressID, userID).Scan(&pollID)
// If no poll exists, create one
if err == sql.ErrNoRows {
err = models.DB.QueryRow(`
INSERT INTO poll (user_id, address_id, poll_title, poll_description, is_active)
VALUES ($1, $2, 'Door-to-Door Poll', 'Campaign polling questions', true)
RETURNING poll_id
`, userID, addressID).Scan(&pollID)
if err != nil {
log.Printf("Failed to create poll: %v", err)
http.Error(w, "Failed to create poll", http.StatusInternalServerError)
return
}
} else if err != nil {
log.Printf("Database error: %v", err)
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
utils.Render(w, "volunteer/poll_form.html", map[string]interface{}{
"Title": "Poll Questions",
"IsAuthenticated": true,
"ShowAdminNav": true,
"UserName": username,
"ActiveSection": "appointments",
"PollID": pollID,
"AddressID": addressID,
"Address": address,
"PageIcon": "fas fa-poll",
})
return
}
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
http.Error(w, "Invalid form", http.StatusBadRequest)
return
}
pollID := r.FormValue("poll_id")
postalCode := r.FormValue("postal_code")
question5Thoughts := r.FormValue("question5_thoughts")
// Parse boolean values
var question1VotedBefore *bool
if val := r.FormValue("question1_voted_before"); val != "" {
if val == "true" {
b := true
question1VotedBefore = &b
} else if val == "false" {
b := false
question1VotedBefore = &b
}
}
var question2VoteAgain *bool
if val := r.FormValue("question2_vote_again"); val != "" {
if val == "true" {
b := true
question2VoteAgain = &b
} else if val == "false" {
b := false
question2VoteAgain = &b
}
}
// Parse integer values
question3LawnSigns, _ := strconv.Atoi(r.FormValue("question3_lawn_signs"))
question4BannerSigns, _ := strconv.Atoi(r.FormValue("question4_banner_signs"))
// Insert poll response
_, err = models.DB.Exec(`
INSERT INTO poll_response (
poll_id, respondent_postal_code, question1_voted_before,
question2_vote_again, question3_lawn_signs, question4_banner_signs,
question5_thoughts
) VALUES ($1, $2, $3, $4, $5, $6, $7)
`, pollID, postalCode, question1VotedBefore, question2VoteAgain,
question3LawnSigns, question4BannerSigns, question5Thoughts)
if err != nil {
fmt.Print(err)
http.Error(w, "Failed to save poll response", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/volunteer/Addresses", http.StatusSeeOther)
}
}

View File

@@ -1,5 +1,4 @@
// Add this to your handlers package (create volunteer_posts.go or add to existing file)
package handlers
import (
@@ -12,7 +11,18 @@ import (
"github.com/patel-mann/poll-system/app/internal/utils"
)
// VolunteerPostsHandler - Read-only posts view for volunteers
type VolunteerStatistics struct {
AppointmentsToday int
AppointmentsThisWeek int
TotalAppointments int
PollsCompleted int
PollsRemaining int
LawnSignsRequested int
BannerSignsRequested int
PollCompletionPercent int
}
// VolunteerPostsHandler - Dashboard view for volunteers with posts and statistics
func VolunteerPostsHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET requests for volunteers
if r.Method != http.MethodGet {
@@ -23,7 +33,7 @@ func VolunteerPostsHandler(w http.ResponseWriter, r *http.Request) {
// Get user info from context
role := r.Context().Value("user_role").(int)
CurrentUserID := models.GetCurrentUserID(w, r)
username,_ := models.GetCurrentUserName(r)
username, _ := models.GetCurrentUserName(r)
// Fetch posts from database
rows, err := models.DB.Query(`
@@ -34,7 +44,7 @@ func VolunteerPostsHandler(w http.ResponseWriter, r *http.Request) {
JOIN admin_volunteers x ON u.user_id = x.admin_id
WHERE x.volunteer_id = $1
ORDER BY p.created_at DESC
`,CurrentUserID)
`, CurrentUserID)
if err != nil {
fmt.Printf("Database query error: %v\n", err)
http.Error(w, "Failed to fetch posts", http.StatusInternalServerError)
@@ -60,20 +70,107 @@ func VolunteerPostsHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Get volunteer statistics
stats, err := getVolunteerStatistics(CurrentUserID)
if err != nil {
fmt.Printf("Failed to fetch statistics: %v\n", err)
// Continue with empty stats rather than failing
stats = &VolunteerStatistics{}
}
// Get navigation flags
showAdminNav, showVolunteerNav := getNavFlags(role)
fmt.Printf("Volunteer viewing %d posts\n", len(posts))
utils.Render(w, "dashboard/volunteer_dashboard.html", map[string]interface{}{
"Title": "Community Posts",
"Title": "Volunteer Dashboard",
"IsAuthenticated": true,
"ShowAdminNav": showAdminNav,
"ShowVolunteerNav": showVolunteerNav,
"UserName": username,
"UserName": username,
"Posts": posts,
"ActiveSection": "posts",
"IsVolunteer": true, // Flag to indicate this is volunteer view
"Statistics": stats,
"ActiveSection": "dashboard",
"IsVolunteer": true,
})
}
func getVolunteerStatistics(userID int) (*VolunteerStatistics, error) {
stats := &VolunteerStatistics{}
today := time.Now().Format("2006-01-02")
// Get start of current week (Monday)
now := time.Now()
weekday := now.Weekday()
if weekday == time.Sunday {
weekday = 7
}
weekStart := now.AddDate(0, 0, -int(weekday)+1).Format("2006-01-02")
// Appointments today
err := models.DB.QueryRow(`
SELECT COUNT(*)
FROM appointment
WHERE user_id = $1 AND DATE(appointment_date) = $2
`, userID, today).Scan(&stats.AppointmentsToday)
if err != nil {
return nil, err
}
// Appointments this week
err = models.DB.QueryRow(`
SELECT COUNT(*)
FROM appointment
WHERE user_id = $1 AND DATE(appointment_date) >= $2 AND DATE(appointment_date) <= $3
`, userID, weekStart, today).Scan(&stats.AppointmentsThisWeek)
if err != nil {
return nil, err
}
// Total appointments
err = models.DB.QueryRow(`
SELECT COUNT(*)
FROM appointment
WHERE user_id = $1
`, userID).Scan(&stats.TotalAppointments)
if err != nil {
return nil, err
}
// Polls completed
err = models.DB.QueryRow(`
SELECT COUNT(DISTINCT pr.poll_response_id)
FROM poll p
JOIN poll_response pr ON p.poll_id = pr.poll_id
WHERE p.user_id = $1
`, userID).Scan(&stats.PollsCompleted)
if err != nil {
return nil, err
}
// Polls remaining (appointments without poll responses)
stats.PollsRemaining = stats.TotalAppointments - stats.PollsCompleted
// Calculate completion percentage
if stats.TotalAppointments > 0 {
stats.PollCompletionPercent = (stats.PollsCompleted * 100) / stats.TotalAppointments
} else {
stats.PollCompletionPercent = 0
}
// Signs requested
err = models.DB.QueryRow(`
SELECT
COALESCE(SUM(pr.question3_lawn_signs), 0),
COALESCE(SUM(pr.question4_banner_signs), 0)
FROM poll p
JOIN poll_response pr ON p.poll_id = pr.poll_id
WHERE p.user_id = $1
`, userID).Scan(&stats.LawnSignsRequested, &stats.BannerSignsRequested)
if err != nil {
return nil, err
}
return stats, nil
}