update to volunteer
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
138
app/internal/handlers/volunteer_poll.go
Normal file
138
app/internal/handlers/volunteer_poll.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user