diff --git a/app/internal/handlers/admin_addresses.go b/app/internal/handlers/admin_addresses.go index b79e782..470cadc 100644 --- a/app/internal/handlers/admin_addresses.go +++ b/app/internal/handlers/admin_addresses.go @@ -208,7 +208,7 @@ func AddressHandler(w http.ResponseWriter, r *http.Request) { PageNumbers: pageNumbers, } - utils.Render(w, "address/address.html", map[string]interface{}{ + utils.Render(w, "address.html", map[string]interface{}{ "Title": "Addresses", "IsAuthenticated": true, "ShowAdminNav": true, diff --git a/app/internal/handlers/admin_dashboard.go b/app/internal/handlers/admin_dashboard.go index e1dbc0d..244ea90 100644 --- a/app/internal/handlers/admin_dashboard.go +++ b/app/internal/handlers/admin_dashboard.go @@ -67,7 +67,7 @@ func AdminDashboardHandler(w http.ResponseWriter, r *http.Request) { housesLeftPercent = 0 // Set default value on error } - utils.Render(w, "dashboard/dashboard.html", map[string]interface{}{ + utils.Render(w, "dashboard.html", map[string]interface{}{ "Title": "Admin Dashboard", "IsAuthenticated": true, "VolunteerCount": volunteerCount, diff --git a/app/internal/handlers/admin_reports.go b/app/internal/handlers/admin_reports.go index df88f81..2c3699b 100644 --- a/app/internal/handlers/admin_reports.go +++ b/app/internal/handlers/admin_reports.go @@ -75,7 +75,7 @@ func ReportsHandler(w http.ResponseWriter, r *http.Request) { "ShowAdminNav": role == 1, "ShowVolunteerNav": role != 1, "UserName": username, - "ActiveSection": "reports", + "ActiveSection": "reports", "Category": category, "ReportID": reportID, "DateFrom": dateFrom, @@ -177,20 +177,23 @@ func getAllReportDefinitions() map[string][]ReportDefinition { return map[string][]ReportDefinition{ "users": { { - ID: "users_by_role", - Name: "Users by Role", + ID: "volunteer_participation_rate", // get all the appointment(done, notdone, total) poll(done, not doen, total) + Name: "Volunteer participation rate", Description: "Count of users grouped by their role", SQL: `SELECT - CASE - WHEN role_id = 1 THEN 'Admin' - WHEN role_id = 2 THEN 'Volunteer' - ELSE 'Unknown' - END as role, - COUNT(*) as user_count, - COUNT(CASE WHEN created_at >= ?1 THEN 1 END) as new_this_period - FROM users - GROUP BY role_id - ORDER BY role_id`, + u.user_id, + u.first_name, + u.last_name, + COUNT(p.poll_id) AS total_polls, + COUNT(a.user_id) AS total_appointments, + case + WHEN COUNT(a.user_id) = 0 THEN NULL -- avoid division by zero + ELSE ROUND(CAST(COUNT(p.poll_id) AS numeric) / COUNT(a.user_id), 2) + END AS poll_to_appointment_rate + from users u + LEFT JOIN poll p ON u.user_id = p.user_id + LEFT JOIN appointment a ON u.user_id = a.user_id + GROUP BY u.user_id, u.first_name, u.last_name;`, }, { ID: "volunteer_activity", diff --git a/app/internal/handlers/admin_team_builder.go b/app/internal/handlers/admin_team_builder.go index b05519f..0ab88aa 100644 --- a/app/internal/handlers/admin_team_builder.go +++ b/app/internal/handlers/admin_team_builder.go @@ -81,7 +81,7 @@ func TeamBuilderHandler(w http.ResponseWriter, r *http.Request) { unassignedVolunteers = append(unassignedVolunteers, vol) } - utils.Render(w, "volunteer/team_builder.html", map[string]interface{}{ + utils.Render(w, "team_builder.html", map[string]interface{}{ "Title": "Team Builder", "IsAuthenticated": true, "ShowAdminNav": true, diff --git a/app/internal/handlers/admin_voluteers.go b/app/internal/handlers/admin_voluteers.go index 830beb0..be634f2 100644 --- a/app/internal/handlers/admin_voluteers.go +++ b/app/internal/handlers/admin_voluteers.go @@ -39,7 +39,7 @@ func VolunteerHandler(w http.ResponseWriter, r *http.Request) { user = append(user, b) } - utils.Render(w, "volunteer/volunteer.html", map[string]interface{}{ + utils.Render(w, "volunteer.html", map[string]interface{}{ "Title": "Assigned Volunteers", "IsAuthenticated": true, "ShowAdminNav": true, @@ -66,7 +66,7 @@ func EditVolunteerHandler(w http.ResponseWriter, r *http.Request) { return } - utils.Render(w, "volunteer/edit_volunteer.html", map[string]interface{}{ + utils.Render(w, "edit_volunteer.html", map[string]interface{}{ "Title": "Edit Volunteer", "IsAuthenticated": true, "ShowAdminNav": true, diff --git a/app/internal/handlers/login.go b/app/internal/handlers/login.go index f44c8a3..2ed005c 100644 --- a/app/internal/handlers/login.go +++ b/app/internal/handlers/login.go @@ -30,29 +30,12 @@ func getDefaultRedirectURL(role int) string { } } -// Helper function to render error pages with consistent data -func renderLoginError(w http.ResponseWriter, errorMsg string) { - utils.Render(w, "login.html", map[string]interface{}{ - "Error": errorMsg, - "Title": "Login", - "IsAuthenticated": false, - }) -} - -func renderRegisterError(w http.ResponseWriter, errorMsg string) { - utils.Render(w, "register.html", map[string]interface{}{ - "Error": errorMsg, - "Title": "Register", - "IsAuthenticated": false, - }) -} - // Helper function to create and sign JWT token func createJWTToken(userID, role int) (string, time.Time, error) { err := godotenv.Load() // or specify path: godotenv.Load("/path/to/.env") if err != nil { - log.Fatalf("Error loading .env file: %v", err) + log.Fatalf("Error loading .env file: %v", err) } // Get individual components from environment variables @@ -60,7 +43,6 @@ func createJWTToken(userID, role int) (string, time.Time, error) { var jwtKey = []byte(jwtSecret) - expirationTime := time.Now().Add(12 * time.Hour) claims := &models.Claims{ UserID: userID, @@ -113,7 +95,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { // Input validation if email == "" || password == "" { - http.Redirect(w, r, "/?error=EmailAndPasswordRequired", http.StatusSeeOther) + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -130,7 +112,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { if err != nil { log.Printf("Login failed for email %s: %v", email, err) - http.Redirect(w, r, "/?error=InvalidCredentials", http.StatusSeeOther) + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -138,7 +120,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { err = bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password)) if err != nil { log.Printf("Password verification failed for user ID %d", userID) - http.Redirect(w, r, "/?error=InvalidCredentials", http.StatusSeeOther) + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -146,7 +128,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { tokenString, expirationTime, err := createJWTToken(userID, role) if err != nil { log.Printf("JWT token creation failed for user ID %d: %v", userID, err) - http.Redirect(w, r, "/?error=InternalError", http.StatusSeeOther) + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -159,7 +141,6 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, redirectURL, http.StatusSeeOther) } - func RegisterHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { utils.Render(w, "layout.html", map[string]interface{}{ @@ -179,7 +160,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { // Input validation if firstName == "" || lastName == "" || email == "" || password == "" || role == "" { - renderRegisterError(w, "All fields are required") + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -187,21 +168,21 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { log.Printf("Password hashing failed: %v", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Redirect(w, r, "/", http.StatusSeeOther) return } // Convert role to int roleID, err := strconv.Atoi(role) if err != nil { - renderRegisterError(w, "Invalid role") + http.Redirect(w, r, "/", http.StatusSeeOther) return } var adminID int if roleID == 3 { // volunteer if adminCode == "" { - renderRegisterError(w, "Admin code is required for volunteers") + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -209,11 +190,11 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { err = models.DB.QueryRow(`SELECT user_id FROM users WHERE role_id = 1 AND admin_code = $1`, adminCode).Scan(&adminID) if err != nil { if err == sql.ErrNoRows { - renderRegisterError(w, "Invalid admin code") + http.Redirect(w, r, "/", http.StatusSeeOther) return } log.Printf("DB error checking admin code: %v", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Redirect(w, r, "/", http.StatusSeeOther) return } } @@ -227,7 +208,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { `, firstName, lastName, email, phone, string(hashedPassword), roleID).Scan(&userID) if err != nil { log.Printf("User registration failed: %v", err) - renderRegisterError(w, "Could not create account. Email might already be in use.") + http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -239,7 +220,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { `, adminID, userID) if err != nil { log.Printf("Failed to link volunteer to admin: %v", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Redirect(w, r, "/", http.StatusSeeOther) return } } @@ -248,9 +229,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusSeeOther) } - - func LogoutHandler(w http.ResponseWriter, r *http.Request) { clearSessionCookie(w) http.Redirect(w, r, "/", http.StatusSeeOther) -} +} \ No newline at end of file diff --git a/app/internal/handlers/profile.go b/app/internal/handlers/profile.go index 226e40b..6381f00 100644 --- a/app/internal/handlers/profile.go +++ b/app/internal/handlers/profile.go @@ -47,7 +47,7 @@ func ProfileHandler(w http.ResponseWriter, r *http.Request) { volunteernav = true } - utils.Render(w, "profile/profile.html", map[string]interface{}{ + utils.Render(w, "profile.html", map[string]interface{}{ "Title": "My Profile", "IsAuthenticated": true, "ShowAdminNav": adminnav, diff --git a/app/internal/handlers/volunteer_address.go b/app/internal/handlers/volunteer_address.go index aab1694..92a0a38 100644 --- a/app/internal/handlers/volunteer_address.go +++ b/app/internal/handlers/volunteer_address.go @@ -98,7 +98,7 @@ func VolunteerAppointmentHandler(w http.ResponseWriter, r *http.Request) { } // Render template - utils.Render(w, "/appointment.html", map[string]interface{}{ + utils.Render(w, "appointment.html", map[string]interface{}{ "Title": "My Profile", "IsAuthenticated": true, "ShowAdminNav": adminnav, diff --git a/app/internal/handlers/volunteer_dashboard.go b/app/internal/handlers/volunteer_dashboard.go index 61f3885..eadf60a 100644 --- a/app/internal/handlers/volunteer_dashboard.go +++ b/app/internal/handlers/volunteer_dashboard.go @@ -124,7 +124,7 @@ func VolunteerPostsHandler(w http.ResponseWriter, r *http.Request) { fmt.Printf("Volunteer viewing %d posts\n", len(posts)) - utils.Render(w, "dashboard/volunteer_dashboard.html", map[string]interface{}{ + utils.Render(w, "volunteer_dashboard.html", map[string]interface{}{ "Title": "Volunteer Dashboard", "IsAuthenticated": true, "ShowAdminNav": showAdminNav, diff --git a/app/internal/handlers/volunteer_poll.go b/app/internal/handlers/volunteer_poll.go index 595b7d6..e14f8fd 100644 --- a/app/internal/handlers/volunteer_poll.go +++ b/app/internal/handlers/volunteer_poll.go @@ -65,7 +65,7 @@ func PollHandler(w http.ResponseWriter, r *http.Request) { return } - utils.Render(w, "volunteer/poll_form.html", map[string]interface{}{ + utils.Render(w, "poll_form.html", map[string]interface{}{ "Title": "Poll Questions", "IsAuthenticated": true, "ShowAdminNav": true, @@ -120,7 +120,6 @@ func PollHandler(w http.ResponseWriter, r *http.Request) { } } - // Insert poll response _, err = models.DB.Exec(` INSERT INTO poll_response ( @@ -135,6 +134,22 @@ func PollHandler(w http.ResponseWriter, r *http.Request) { fmt.Print(err) http.Error(w, "Failed to save poll response", http.StatusInternalServerError) return + }else{ + _, err := models.DB.Exec(` + UPDATE address_database + SET visited_validated = true + WHERE address_id IN ( + SELECT address_id + FROM poll + WHERE poll_id = $1 + ) + `, pollID) + 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) diff --git a/app/internal/handlers/volunteer_schedual.go b/app/internal/handlers/volunteer_schedual.go new file mode 100644 index 0000000..6621006 --- /dev/null +++ b/app/internal/handlers/volunteer_schedual.go @@ -0,0 +1,11 @@ +package handlers + +import ( + "fmt" + "net/http" +) + +func VolunteerSchedualHandler(w *http.ResponseWriter, r http.Request) { + + fmt.Print("Not Implementated Yet!!!") +} diff --git a/app/internal/templates/address.html b/app/internal/templates/address.html new file mode 100644 index 0000000..0ae5933 --- /dev/null +++ b/app/internal/templates/address.html @@ -0,0 +1,533 @@ +{{ define "content" }} +
+ +
+
+ +
+ + +
+ + + {{if .Pagination}} +
+
+ + + + +
+
+ + +
+ +
+ + + {{.Pagination.CurrentPage}} / {{.Pagination.TotalPages}} + + +
+
+ {{end}} +
+
+ + +
+
+ + + + + +
+
+ {{ range .Addresses }} +
+ +
+
+ + Address +
+ {{ if .VisitedValidated }} + + Valid + + {{ else }} + + Invalid + + {{ end }} +
+ + +
+ +
+ {{ .Address }} +
+ + + + + +
+ Assigned User +
+ {{ if .UserName }} +
{{ .UserName }}
+
{{ .UserEmail }}
+ {{ else }} + Unassigned + {{ end }} +
+
+ + +
+ Appointment +
+ {{ if .AppointmentDate }} +
{{ .AppointmentDate }}
+
{{ .AppointmentTime }}
+ {{ else }} + No appointment + {{ end }} +
+
+ + +
+ {{ if .Assigned }} + +
+ + + +
+ {{ else }} + + {{ end }} +
+
+
+ {{ else }} +
+
+ +
+

No addresses found

+

Try adjusting your search criteria.

+
+ {{ end }} +
+
+
+
+ + + + + +
+ +
+
+ +

Assign Address

+
+ +
+ + +
+ + + +
+
+ + Selected Address: +
+
None selected
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+ + +
+
+ +
+ + + + + +{{ end }} diff --git a/app/internal/templates/address/address.html b/app/internal/templates/address/address.html deleted file mode 100644 index 8a2e855..0000000 --- a/app/internal/templates/address/address.html +++ /dev/null @@ -1,401 +0,0 @@ -{{ define "content" }} -
- -
-
-
-
- - -
-
- {{if .Pagination}} -
-
- - -
-
- - {{.Pagination.CurrentPage}} / {{.Pagination.TotalPages}} - -
-
- {{end}} -
-
- - -
- - - - - - - - - - - - - - {{ range .Addresses }} - - - - - - - - - - {{ else }} - - - - {{ end }} - -
ValidatedAddressCoordinatesAssigned UserAppointmentAssignRemove
- {{ if .VisitedValidated }} - - Valid - - {{ else }} - - Invalid - - {{ end }} - {{ .Address }} - - ({{ .Latitude }}, {{ .Longitude }}) - - - {{ if .UserName }}{{ .UserName }}
{{ .UserEmail }}{{ else }}Unassigned{{ end }} -
- {{ if .AppointmentDate }} {{ .AppointmentDate }} {{ .AppointmentTime - }} {{ else }} - No appointment - {{ end }} - - {{ if .Assigned }} - - {{ else }} - - {{ end }} - - {{ if .Assigned }} -
- - - -
- {{ else }} - - - {{ end }} -
- No addresses found -
-
- - - -
- - - - - -{{ end }} diff --git a/app/internal/templates/csv-upload.html b/app/internal/templates/csv-upload.html index 8ac4252..2e3ded2 100644 --- a/app/internal/templates/csv-upload.html +++ b/app/internal/templates/csv-upload.html @@ -18,10 +18,10 @@ - Back to Dashboard + Back to Addresses diff --git a/app/internal/templates/dashboard.html b/app/internal/templates/dashboard.html new file mode 100644 index 0000000..fc43433 --- /dev/null +++ b/app/internal/templates/dashboard.html @@ -0,0 +1,387 @@ +{{ define "content" }} + + + + + + +
+ +
+
+ + + +
+
+ + +
+ + +
+
+
+
+ +
+
+

Active Volunteers

+

{{.VolunteerCount}}

+
+
+ +
+
+ +
+
+

Addresses Visited

+

{{.ValidatedCount}}

+

Loading...

+
+
+ +
+
+ +
+
+

Donation

+

${{.TotalDonations}}

+
+
+ +
+
+ +
+
+

Houses Left

+

{{.HousesLeftPercent}}%

+
+
+
+
+
+ + +{{ end }} \ No newline at end of file diff --git a/app/internal/templates/dashboard/dashboard.html b/app/internal/templates/dashboard/dashboard.html deleted file mode 100644 index 5923076..0000000 --- a/app/internal/templates/dashboard/dashboard.html +++ /dev/null @@ -1,374 +0,0 @@ -{{ define "content" }} - - - - - - {{.Title}} - - - - - - - - - -
-
-
-
-
- -
- Dashboard Overview -
-
- - -
-
-
-
- - -
-
-
-
- -
-
-

- Active Volunteers -

-

{{.VolunteerCount}}

-
-
-
- -
-
-
- -
-
-

- Addresses Visited -

-

{{.ValidatedCount}}

-

Loading...

-
-
-
- -
-
-
- -
-
-

Donation

-

${{.TotalDonations}}

-
-
-
- -
-
-
- -
-
-

Houses Left

-

- {{.HousesLeftPercent}}% -

-
-
-
-
- - -
-
- - - -
- - -
- - -
- - - - -{{ end }} diff --git a/app/internal/templates/volunteer/edit_volunteer.html b/app/internal/templates/edit_volunteer.html similarity index 100% rename from app/internal/templates/volunteer/edit_volunteer.html rename to app/internal/templates/edit_volunteer.html diff --git a/app/internal/templates/layout.html b/app/internal/templates/layout.html index 33607da..efa7c76 100644 --- a/app/internal/templates/layout.html +++ b/app/internal/templates/layout.html @@ -9,615 +9,479 @@ {{if .Title}}{{.Title}}{{else}}Poll System{{end}} - + + - + {{ if .IsAuthenticated }} -
+
- - - -
- -
+ +
{{ template "content" . }}
-
+
{{else}} - - - - - - - Linq - Poll System - - - - - -
- - - - - -
-
-

- Simple Polling - Management -

+ + Welcome to Poll System -

- Manage volunteers and track polling operations efficiently. -

- -
- - -
-
-
- - -
-
-
-

Our Products

-

Tools built for modern polling operations

-
- -
-
- Poll Manager -

Poll Manager

-

Complete polling campaign management with real-time tracking and coordination tools.

-
    -
  • • Volunteer coordination
  • -
  • • Address management
  • -
  • • Progress tracking
  • -
-
- -
- Analytics Suite -

Analytics Suite

-

Advanced reporting and analytics dashboard for data-driven insights.

-
    -
  • • Performance metrics
  • -
  • • Custom reports
  • -
  • • Data visualization
  • -
-
- -
- Team Builder -

Team Builder

-

Organize teams and assign roles with automated scheduling and notifications.

-
    -
  • • Role assignment
  • -
  • • Schedule management
  • -
  • • Team communication
  • -
-
-
-
-
- - -
-
-
-
-

About Linq

-

- Built for organizations that need efficient polling management. - Our platform simplifies volunteer coordination and progress tracking. -

- -
-
-
500+
-
Volunteers
-
- -
-
400,000+
-
Addresses
-
-
-
- -
- Dashboard Preview -

Simple Dashboard

-

- Everything you need in one place. Track progress, manage teams, and generate reports. -

-
-
-
-
- - - -
- -