From b62d7fdb17eb20f502d2eb04b53428a5a1788a39 Mon Sep 17 00:00:00 2001 From: Finley Ghosh Date: Sun, 7 Dec 2025 16:53:59 +1100 Subject: [PATCH] Better modals and visuals, adding method to re-enabled disabled reminders --- go/cmd/server/main.go | 1 + go/internal/cmc/db/querier.go | 1 + go/internal/cmc/db/quotes.sql.go | 17 +- go/internal/cmc/handlers/quotes/quotes.go | 78 +++- go/sql/queries/quotes.sql | 10 +- go/templates/quotes/index.html | 429 +++++++++++++++++----- 6 files changed, 446 insertions(+), 90 deletions(-) diff --git a/go/cmd/server/main.go b/go/cmd/server/main.go index 5d073ba6..f2238a62 100644 --- a/go/cmd/server/main.go +++ b/go/cmd/server/main.go @@ -75,6 +75,7 @@ func main() { goRouter.HandleFunc("/quotes", quoteHandler.QuotesOutstandingView).Methods("GET") goRouter.HandleFunc("/quotes/send-reminder", quoteHandler.SendManualReminder).Methods("POST") goRouter.HandleFunc("/quotes/disable-reminders", quoteHandler.DisableReminders).Methods("POST") + goRouter.HandleFunc("/quotes/enable-reminders", quoteHandler.EnableReminders).Methods("POST") // The following routes are currently disabled: /* diff --git a/go/internal/cmc/db/querier.go b/go/internal/cmc/db/querier.go index 920fae0f..0c143a4e 100644 --- a/go/internal/cmc/db/querier.go +++ b/go/internal/cmc/db/querier.go @@ -44,6 +44,7 @@ type Querier interface { DeleteState(ctx context.Context, id int32) error DeleteStatus(ctx context.Context, id int32) error DisableQuoteReminders(ctx context.Context, arg DisableQuoteRemindersParams) (sql.Result, error) + EnableQuoteReminders(ctx context.Context, id int32) (sql.Result, error) GetAddress(ctx context.Context, id int32) (Address, error) GetAllCountries(ctx context.Context) ([]Country, error) GetAllPrinciples(ctx context.Context) ([]Principle, error) diff --git a/go/internal/cmc/db/quotes.sql.go b/go/internal/cmc/db/quotes.sql.go index d9452db2..fc5d9bb3 100644 --- a/go/internal/cmc/db/quotes.sql.go +++ b/go/internal/cmc/db/quotes.sql.go @@ -28,6 +28,18 @@ func (q *Queries) DisableQuoteReminders(ctx context.Context, arg DisableQuoteRem return q.db.ExecContext(ctx, disableQuoteReminders, arg.RemindersDisabledBy, arg.ID) } +const enableQuoteReminders = `-- name: EnableQuoteReminders :execresult +UPDATE quotes +SET reminders_disabled = FALSE, + reminders_disabled_at = NULL, + reminders_disabled_by = NULL +WHERE id = ? +` + +func (q *Queries) EnableQuoteReminders(ctx context.Context, id int32) (sql.Result, error) { + return q.db.ExecContext(ctx, enableQuoteReminders, id) +} + const getExpiringSoonQuotes = `-- name: GetExpiringSoonQuotes :many WITH ranked_reminders AS ( SELECT @@ -64,7 +76,8 @@ SELECT q.date_issued, q.valid_until, COALESCE(lqr.reminder_type, 0) AS latest_reminder_type, - COALESCE(lqr.date_sent, CAST('1970-01-01 00:00:00' AS DATETIME)) AS latest_reminder_sent_time + COALESCE(lqr.date_sent, CAST('1970-01-01 00:00:00' AS DATETIME)) AS latest_reminder_sent_time, + COALESCE(q.reminders_disabled, FALSE) AS reminders_disabled FROM quotes q JOIN documents d ON d.id = q.document_id @@ -95,6 +108,7 @@ type GetExpiringSoonQuotesRow struct { ValidUntil time.Time `json:"valid_until"` LatestReminderType int32 `json:"latest_reminder_type"` LatestReminderSentTime time.Time `json:"latest_reminder_sent_time"` + RemindersDisabled bool `json:"reminders_disabled"` } func (q *Queries) GetExpiringSoonQuotes(ctx context.Context, dateADD interface{}) ([]GetExpiringSoonQuotesRow, error) { @@ -118,6 +132,7 @@ func (q *Queries) GetExpiringSoonQuotes(ctx context.Context, dateADD interface{} &i.ValidUntil, &i.LatestReminderType, &i.LatestReminderSentTime, + &i.RemindersDisabled, ); err != nil { return nil, err } diff --git a/go/internal/cmc/handlers/quotes/quotes.go b/go/internal/cmc/handlers/quotes/quotes.go index d6ee43d2..2a6a54eb 100644 --- a/go/internal/cmc/handlers/quotes/quotes.go +++ b/go/internal/cmc/handlers/quotes/quotes.go @@ -3,6 +3,7 @@ package handlers import ( "context" "database/sql" + "encoding/json" "fmt" "io" "log" @@ -176,6 +177,7 @@ type QuoteQueries interface { GetExpiringSoonQuotesOnDay(ctx context.Context, dateADD interface{}) ([]db.GetExpiringSoonQuotesOnDayRow, error) GetRecentlyExpiredQuotesOnDay(ctx context.Context, dateSUB interface{}) ([]db.GetRecentlyExpiredQuotesOnDayRow, error) DisableQuoteReminders(ctx context.Context, params db.DisableQuoteRemindersParams) (sql.Result, error) + EnableQuoteReminders(ctx context.Context, id int32) (sql.Result, error) } type EmailSender interface { @@ -681,7 +683,18 @@ func (h *QuotesHandler) SendManualReminder(w http.ResponseWriter, r *http.Reques } log.Printf("Manual reminder sent successfully for quote %d (%s) to %s", quoteID, enquiryRef, customerEmail) - // Redirect back to quotes page + + // Check if this is an AJAX request + if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Reminder sent successfully", + }) + return + } + + // Redirect back to quotes page for non-AJAX requests http.Redirect(w, r, "/go/quotes", http.StatusSeeOther) } @@ -726,7 +739,68 @@ func (h *QuotesHandler) DisableReminders(w http.ResponseWriter, r *http.Request) } log.Printf("Reminders disabled for quote %d by %s", quoteID, username) - // Redirect back to quotes page + + // Check if this is an AJAX request + if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Reminders disabled successfully", + }) + return + } + + // Redirect back to quotes page for non-AJAX requests + http.Redirect(w, r, "/go/quotes", http.StatusSeeOther) +} + +// EnableReminders handles POST requests to re-enable automatic reminders for a quote +func (h *QuotesHandler) EnableReminders(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Parse form data + if err := r.ParseForm(); err != nil { + http.Error(w, "Invalid form data", http.StatusBadRequest) + return + } + + quoteIDStr := r.FormValue("quote_id") + if quoteIDStr == "" { + http.Error(w, "Missing quote_id", http.StatusBadRequest) + return + } + + quoteID, err := strconv.ParseInt(quoteIDStr, 10, 32) + if err != nil { + http.Error(w, "Invalid quote ID", http.StatusBadRequest) + return + } + + // Update the database to enable reminders + _, err = h.queries.EnableQuoteReminders(r.Context(), int32(quoteID)) + + if err != nil { + log.Printf("Failed to enable reminders for quote %d: %v", quoteID, err) + http.Error(w, fmt.Sprintf("Failed to enable reminders: %v", err), http.StatusInternalServerError) + return + } + + log.Printf("Reminders enabled for quote %d", quoteID) + + // Check if this is an AJAX request + if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Reminders enabled successfully", + }) + return + } + + // Redirect back to quotes page for non-AJAX requests http.Redirect(w, r, "/go/quotes", http.StatusSeeOther) } diff --git a/go/sql/queries/quotes.sql b/go/sql/queries/quotes.sql index 0acba1f1..1a848366 100644 --- a/go/sql/queries/quotes.sql +++ b/go/sql/queries/quotes.sql @@ -34,7 +34,8 @@ SELECT q.date_issued, q.valid_until, COALESCE(lqr.reminder_type, 0) AS latest_reminder_type, - COALESCE(lqr.date_sent, CAST('1970-01-01 00:00:00' AS DATETIME)) AS latest_reminder_sent_time + COALESCE(lqr.date_sent, CAST('1970-01-01 00:00:00' AS DATETIME)) AS latest_reminder_sent_time, + COALESCE(q.reminders_disabled, FALSE) AS reminders_disabled FROM quotes q JOIN documents d ON d.id = q.document_id @@ -231,6 +232,13 @@ SET reminders_disabled = TRUE, reminders_disabled_by = ? WHERE id = ?; +-- name: EnableQuoteReminders :execresult +UPDATE quotes +SET reminders_disabled = FALSE, + reminders_disabled_at = NULL, + reminders_disabled_by = NULL +WHERE id = ?; + -- name: GetQuoteRemindersDisabled :one SELECT reminders_disabled, reminders_disabled_at, reminders_disabled_by FROM quotes diff --git a/go/templates/quotes/index.html b/go/templates/quotes/index.html index e005f584..ffee15b2 100644 --- a/go/templates/quotes/index.html +++ b/go/templates/quotes/index.html @@ -13,7 +13,18 @@ Expires Reminder Reminder Sent - Actions + +
+ Actions +
+ + +
+
+ @@ -51,30 +62,34 @@ -
- {{if eq .LatestReminderType "No Reminder"}} - - - {{else if eq .LatestReminderType "First Reminder"}} - - - {{else if eq .LatestReminderType "Second Reminder"}} - - - {{else}} - - - {{end}} -
- + {{if .RemindersDisabled}} + + {{else}} +
+ {{if eq .LatestReminderType "No Reminder"}} + + + {{else if eq .LatestReminderType "First Reminder"}} + + + {{else if eq .LatestReminderType "Second Reminder"}} + + + {{else}} + + + {{end}} +
+ + {{end}} @@ -95,7 +110,18 @@ Expires Reminder Reminder Sent - Actions + +
+ Actions +
+ + +
+
+ @@ -133,30 +159,34 @@ -
- {{if eq .LatestReminderType "No Reminder"}} - - - {{else if eq .LatestReminderType "First Reminder"}} - - - {{else if eq .LatestReminderType "Second Reminder"}} - - - {{else}} - - - {{end}} -
- + {{if .RemindersDisabled}} + + {{else}} +
+ {{if eq .LatestReminderType "No Reminder"}} + + + {{else if eq .LatestReminderType "First Reminder"}} + + + {{else if eq .LatestReminderType "Second Reminder"}} + + + {{else}} + + + {{end}} +
+ + {{end}} @@ -167,14 +197,43 @@ - -