Better modals and visuals, adding method to re-enabled disabled reminders
This commit is contained in:
parent
c4d60bc1c9
commit
b62d7fdb17
|
|
@ -75,6 +75,7 @@ func main() {
|
||||||
goRouter.HandleFunc("/quotes", quoteHandler.QuotesOutstandingView).Methods("GET")
|
goRouter.HandleFunc("/quotes", quoteHandler.QuotesOutstandingView).Methods("GET")
|
||||||
goRouter.HandleFunc("/quotes/send-reminder", quoteHandler.SendManualReminder).Methods("POST")
|
goRouter.HandleFunc("/quotes/send-reminder", quoteHandler.SendManualReminder).Methods("POST")
|
||||||
goRouter.HandleFunc("/quotes/disable-reminders", quoteHandler.DisableReminders).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:
|
// The following routes are currently disabled:
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ type Querier interface {
|
||||||
DeleteState(ctx context.Context, id int32) error
|
DeleteState(ctx context.Context, id int32) error
|
||||||
DeleteStatus(ctx context.Context, id int32) error
|
DeleteStatus(ctx context.Context, id int32) error
|
||||||
DisableQuoteReminders(ctx context.Context, arg DisableQuoteRemindersParams) (sql.Result, 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)
|
GetAddress(ctx context.Context, id int32) (Address, error)
|
||||||
GetAllCountries(ctx context.Context) ([]Country, error)
|
GetAllCountries(ctx context.Context) ([]Country, error)
|
||||||
GetAllPrinciples(ctx context.Context) ([]Principle, error)
|
GetAllPrinciples(ctx context.Context) ([]Principle, error)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,18 @@ func (q *Queries) DisableQuoteReminders(ctx context.Context, arg DisableQuoteRem
|
||||||
return q.db.ExecContext(ctx, disableQuoteReminders, arg.RemindersDisabledBy, arg.ID)
|
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
|
const getExpiringSoonQuotes = `-- name: GetExpiringSoonQuotes :many
|
||||||
WITH ranked_reminders AS (
|
WITH ranked_reminders AS (
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -64,7 +76,8 @@ SELECT
|
||||||
q.date_issued,
|
q.date_issued,
|
||||||
q.valid_until,
|
q.valid_until,
|
||||||
COALESCE(lqr.reminder_type, 0) AS latest_reminder_type,
|
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
|
FROM quotes q
|
||||||
JOIN documents d ON d.id = q.document_id
|
JOIN documents d ON d.id = q.document_id
|
||||||
|
|
@ -95,6 +108,7 @@ type GetExpiringSoonQuotesRow struct {
|
||||||
ValidUntil time.Time `json:"valid_until"`
|
ValidUntil time.Time `json:"valid_until"`
|
||||||
LatestReminderType int32 `json:"latest_reminder_type"`
|
LatestReminderType int32 `json:"latest_reminder_type"`
|
||||||
LatestReminderSentTime time.Time `json:"latest_reminder_sent_time"`
|
LatestReminderSentTime time.Time `json:"latest_reminder_sent_time"`
|
||||||
|
RemindersDisabled bool `json:"reminders_disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetExpiringSoonQuotes(ctx context.Context, dateADD interface{}) ([]GetExpiringSoonQuotesRow, error) {
|
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.ValidUntil,
|
||||||
&i.LatestReminderType,
|
&i.LatestReminderType,
|
||||||
&i.LatestReminderSentTime,
|
&i.LatestReminderSentTime,
|
||||||
|
&i.RemindersDisabled,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -176,6 +177,7 @@ type QuoteQueries interface {
|
||||||
GetExpiringSoonQuotesOnDay(ctx context.Context, dateADD interface{}) ([]db.GetExpiringSoonQuotesOnDayRow, error)
|
GetExpiringSoonQuotesOnDay(ctx context.Context, dateADD interface{}) ([]db.GetExpiringSoonQuotesOnDayRow, error)
|
||||||
GetRecentlyExpiredQuotesOnDay(ctx context.Context, dateSUB interface{}) ([]db.GetRecentlyExpiredQuotesOnDayRow, error)
|
GetRecentlyExpiredQuotesOnDay(ctx context.Context, dateSUB interface{}) ([]db.GetRecentlyExpiredQuotesOnDayRow, error)
|
||||||
DisableQuoteReminders(ctx context.Context, params db.DisableQuoteRemindersParams) (sql.Result, error)
|
DisableQuoteReminders(ctx context.Context, params db.DisableQuoteRemindersParams) (sql.Result, error)
|
||||||
|
EnableQuoteReminders(ctx context.Context, id int32) (sql.Result, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailSender interface {
|
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)
|
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)
|
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)
|
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)
|
http.Redirect(w, r, "/go/quotes", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ SELECT
|
||||||
q.date_issued,
|
q.date_issued,
|
||||||
q.valid_until,
|
q.valid_until,
|
||||||
COALESCE(lqr.reminder_type, 0) AS latest_reminder_type,
|
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
|
FROM quotes q
|
||||||
JOIN documents d ON d.id = q.document_id
|
JOIN documents d ON d.id = q.document_id
|
||||||
|
|
@ -231,6 +232,13 @@ SET reminders_disabled = TRUE,
|
||||||
reminders_disabled_by = ?
|
reminders_disabled_by = ?
|
||||||
WHERE id = ?;
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: EnableQuoteReminders :execresult
|
||||||
|
UPDATE quotes
|
||||||
|
SET reminders_disabled = FALSE,
|
||||||
|
reminders_disabled_at = NULL,
|
||||||
|
reminders_disabled_by = NULL
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
-- name: GetQuoteRemindersDisabled :one
|
-- name: GetQuoteRemindersDisabled :one
|
||||||
SELECT reminders_disabled, reminders_disabled_at, reminders_disabled_by
|
SELECT reminders_disabled, reminders_disabled_at, reminders_disabled_by
|
||||||
FROM quotes
|
FROM quotes
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,18 @@
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Expires</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Expires</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder Sent</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder Sent</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Actions</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<span>Actions</span>
|
||||||
|
<div class="relative group">
|
||||||
|
<i class="fas fa-info-circle text-blue-500 cursor-help"></i>
|
||||||
|
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block w-64 p-2 bg-gray-900 text-white text-xs rounded shadow-lg z-10">
|
||||||
|
Manually sent reminders will not specify the number of days until or since expiry
|
||||||
|
<div class="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1 border-4 border-transparent border-t-gray-900"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -51,6 +62,9 @@
|
||||||
<input type="hidden" name="customer_name" value="{{.CustomerName}}">
|
<input type="hidden" name="customer_name" value="{{.CustomerName}}">
|
||||||
<input type="hidden" name="date_issued" value="{{.DateIssued}}">
|
<input type="hidden" name="date_issued" value="{{.DateIssued}}">
|
||||||
<input type="hidden" name="valid_until" value="{{.ValidUntil}}">
|
<input type="hidden" name="valid_until" value="{{.ValidUntil}}">
|
||||||
|
{{if .RemindersDisabled}}
|
||||||
|
<button type="button" onclick="showEnableModal('{{.ID}}', '{{.EnquiryRef}}')" class="px-4 py-1.5 text-xs font-medium text-white bg-green-600 rounded-md hover:bg-green-700 focus:z-10">Re-enable Reminders</button>
|
||||||
|
{{else}}
|
||||||
<div class="inline-flex rounded-md shadow-sm" role="group">
|
<div class="inline-flex rounded-md shadow-sm" role="group">
|
||||||
{{if eq .LatestReminderType "No Reminder"}}
|
{{if eq .LatestReminderType "No Reminder"}}
|
||||||
<button type="button" onclick="showConfirmModal(this, 1, '{{.EnquiryRef}}', '{{.CustomerName}}', 'First Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send First Reminder</button>
|
<button type="button" onclick="showConfirmModal(this, 1, '{{.EnquiryRef}}', '{{.CustomerName}}', 'First Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send First Reminder</button>
|
||||||
|
|
@ -75,6 +89,7 @@
|
||||||
<li><button type="button" onclick="showDisableModal('{{.ID}}', '{{.EnquiryRef}}')" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Disable Future Reminders</button></li>
|
<li><button type="button" onclick="showDisableModal('{{.ID}}', '{{.EnquiryRef}}')" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Disable Future Reminders</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -95,7 +110,18 @@
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Expires</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Expires</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder Sent</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Reminder Sent</th>
|
||||||
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">Actions</th>
|
<th class="px-4 py-3 border font-semibold text-gray-700 align-middle">
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<span>Actions</span>
|
||||||
|
<div class="relative group">
|
||||||
|
<i class="fas fa-info-circle text-blue-500 cursor-help"></i>
|
||||||
|
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block w-64 p-2 bg-gray-900 text-white text-xs rounded shadow-lg z-10">
|
||||||
|
Manually sent reminders will not specify the number of days until or since expiry
|
||||||
|
<div class="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1 border-4 border-transparent border-t-gray-900"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -133,6 +159,9 @@
|
||||||
<input type="hidden" name="customer_name" value="{{.CustomerName}}">
|
<input type="hidden" name="customer_name" value="{{.CustomerName}}">
|
||||||
<input type="hidden" name="date_issued" value="{{.DateIssued}}">
|
<input type="hidden" name="date_issued" value="{{.DateIssued}}">
|
||||||
<input type="hidden" name="valid_until" value="{{.ValidUntil}}">
|
<input type="hidden" name="valid_until" value="{{.ValidUntil}}">
|
||||||
|
{{if .RemindersDisabled}}
|
||||||
|
<button type="button" onclick="showEnableModal('{{.ID}}', '{{.EnquiryRef}}')" class="px-4 py-1.5 text-xs font-medium text-white bg-green-600 rounded-md hover:bg-green-700 focus:z-10">Re-enable Reminders</button>
|
||||||
|
{{else}}
|
||||||
<div class="inline-flex rounded-md shadow-sm" role="group">
|
<div class="inline-flex rounded-md shadow-sm" role="group">
|
||||||
{{if eq .LatestReminderType "No Reminder"}}
|
{{if eq .LatestReminderType "No Reminder"}}
|
||||||
<button type="button" onclick="showConfirmModal(this, 1, '{{.EnquiryRef}}', '{{.CustomerName}}', 'First Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send First Reminder</button>
|
<button type="button" onclick="showConfirmModal(this, 1, '{{.EnquiryRef}}', '{{.CustomerName}}', 'First Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send First Reminder</button>
|
||||||
|
|
@ -157,6 +186,7 @@
|
||||||
<li><button type="button" onclick="showDisableModal('{{.ID}}', '{{.EnquiryRef}}')" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Disable Future Reminders</button></li>
|
<li><button type="button" onclick="showDisableModal('{{.ID}}', '{{.EnquiryRef}}')" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Disable Future Reminders</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -167,14 +197,43 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Confirmation Modal -->
|
<!-- Enable Reminders Modal -->
|
||||||
<div id="confirmModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div id="enableModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="flex items-center justify-center w-12 h-12 mx-auto bg-cmcblue rounded-full">
|
<div class="flex items-center justify-center gap-3 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-green-600 rounded-full flex-shrink-0">
|
||||||
|
<i class="fas fa-check text-white text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg leading-6 font-large font-bold text-gray-900">Re-enable reminders?</h3>
|
||||||
|
</div>
|
||||||
|
<div class="px-7 py-3">
|
||||||
|
<p class="text-sm text-gray-600 text-center">
|
||||||
|
This will allow automatic reminders to be sent again for enquiry <strong id="enableModalEnquiryRef"></strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-3 px-4 py-3">
|
||||||
|
<button id="enableModalCancelBtn" class="flex-1 px-4 py-2 bg-gray-200 text-gray-800 text-sm font-medium rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button id="enableModalConfirmBtn" class="flex-1 px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2">
|
||||||
|
Re-enable Reminders
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Modal -->
|
||||||
|
<div id="confirmModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||||
|
<div class="relative top-20 mx-auto p-5 border w-[500px] shadow-lg rounded-md bg-white">
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="flex items-center justify-center gap-3 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-cmcblue rounded-full flex-shrink-0">
|
||||||
<i class="fas fa-envelope text-white text-xl"></i>
|
<i class="fas fa-envelope text-white text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg leading-6 font-large font-bold text-gray-900 text-center mt-4">Are you sure?</h3>
|
<h3 class="text-lg leading-6 font-large font-bold text-gray-900">Are you sure?</h3>
|
||||||
|
</div>
|
||||||
<div class="px-7 py-3">
|
<div class="px-7 py-3">
|
||||||
<p class="text-sm text-gray-600 text-center">
|
<p class="text-sm text-gray-600 text-center">
|
||||||
Send <strong id="modalReminderType"></strong> for enquiry <strong id="modalEnquiryRef"></strong> to <strong id="modalCustomerName"></strong>?
|
Send <strong id="modalReminderType"></strong> for enquiry <strong id="modalEnquiryRef"></strong> to <strong id="modalCustomerName"></strong>?
|
||||||
|
|
@ -194,22 +253,24 @@
|
||||||
|
|
||||||
<!-- Disable Reminders Modal -->
|
<!-- Disable Reminders Modal -->
|
||||||
<div id="disableModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div id="disableModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
<div class="relative top-20 mx-auto p-5 border w-[500px] shadow-lg rounded-md bg-white">
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="flex items-center justify-center w-12 h-12 mx-auto bg-red-600 rounded-full">
|
<div class="flex items-center justify-center gap-3 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-12 h-12 bg-red-600 rounded-full flex-shrink-0">
|
||||||
<i class="fas fa-ban text-white text-xl"></i>
|
<i class="fas fa-ban text-white text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg leading-6 font-large font-bold text-gray-900 text-center mt-4">Are you sure?</h3>
|
<h3 class="text-lg leading-6 font-large font-bold text-gray-900">Are you sure?</h3>
|
||||||
|
</div>
|
||||||
<div class="px-7 py-3">
|
<div class="px-7 py-3">
|
||||||
<p class="text-sm text-gray-600 text-center">
|
<p class="text-sm text-gray-600 text-center">
|
||||||
This will prevent future automatic reminders from being sent for enquiry <strong id="disableModalEnquiryRef"></strong>.
|
This will prevent future automatic reminders for quote <strong id="disableModalQuoteID"></strong> in enquiry <strong id="disableModalEnquiryRef"></strong>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3 px-4 py-3">
|
<div class="flex gap-3 px-4 py-3">
|
||||||
<button id="disableModalCancelBtn" class="flex-1 px-4 py-2 bg-gray-200 text-gray-800 text-sm font-medium rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300">
|
<button id="disableModalCancelBtn" class="flex-1 px-4 py-2 bg-gray-200 text-gray-800 text-sm font-medium rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 whitespace-nowrap">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button id="disableModalConfirmBtn" class="flex-1 px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2">
|
<button id="disableModalConfirmBtn" class="flex-1 px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 whitespace-nowrap">
|
||||||
Disable Reminders
|
Disable Reminders
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -226,9 +287,44 @@ function showConfirmModal(button, reminderType, enquiryRef, customerName, remind
|
||||||
currentForm = button.closest('form');
|
currentForm = button.closest('form');
|
||||||
currentReminderType = reminderType;
|
currentReminderType = reminderType;
|
||||||
|
|
||||||
document.getElementById('modalReminderType').textContent = reminderTypeName;
|
// Get the current reminder type from the row
|
||||||
document.getElementById('modalEnquiryRef').textContent = enquiryRef;
|
const row = button.closest('tr');
|
||||||
document.getElementById('modalCustomerName').textContent = customerName;
|
const reminderCell = row.querySelector('td:nth-child(6)');
|
||||||
|
const currentReminderText = reminderCell.textContent.trim();
|
||||||
|
|
||||||
|
// Check if trying to send same or earlier reminder than what's already been sent
|
||||||
|
const currentLevel = currentReminderText.includes('Final Reminder') ? 3 :
|
||||||
|
currentReminderText.includes('Second Reminder') ? 2 :
|
||||||
|
currentReminderText.includes('First Reminder') ? 1 : 0;
|
||||||
|
|
||||||
|
const modalBody = document.querySelector('#confirmModal .px-7.py-3 p');
|
||||||
|
|
||||||
|
if (currentLevel >= reminderType) {
|
||||||
|
// Warning: trying to send same or earlier reminder
|
||||||
|
const currentReminderName = currentLevel === 3 ? 'Final Reminder' :
|
||||||
|
currentLevel === 2 ? 'Second Reminder' : 'First Reminder';
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<span class="text-sm text-gray-600 text-center">
|
||||||
|
Send <strong>${reminderTypeName}</strong> for enquiry <strong>${enquiryRef}</strong> to <strong>${customerName}</strong>?
|
||||||
|
</span>
|
||||||
|
<div class="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<i class="fas fa-exclamation-triangle text-yellow-600 mt-0.5 flex-shrink-0"></i>
|
||||||
|
<span class="text-sm text-yellow-800">
|
||||||
|
<strong>Warning:</strong> The customer has already received their <strong>${currentReminderName}</strong>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// Normal confirmation
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<span class="text-sm text-gray-600 text-center">
|
||||||
|
Send <strong>${reminderTypeName}</strong> for enquiry <strong>${enquiryRef}</strong> to <strong>${customerName}</strong>?
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('confirmModal').classList.remove('hidden');
|
document.getElementById('confirmModal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,6 +336,7 @@ function hideConfirmModal() {
|
||||||
|
|
||||||
function showDisableModal(quoteID, enquiryRef) {
|
function showDisableModal(quoteID, enquiryRef) {
|
||||||
currentQuoteID = quoteID;
|
currentQuoteID = quoteID;
|
||||||
|
document.getElementById('disableModalQuoteID').textContent = quoteID;
|
||||||
document.getElementById('disableModalEnquiryRef').textContent = enquiryRef;
|
document.getElementById('disableModalEnquiryRef').textContent = enquiryRef;
|
||||||
document.getElementById('disableModal').classList.remove('hidden');
|
document.getElementById('disableModal').classList.remove('hidden');
|
||||||
|
|
||||||
|
|
@ -252,38 +349,109 @@ function hideDisableModal() {
|
||||||
currentQuoteID = null;
|
currentQuoteID = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showEnableModal(quoteID, enquiryRef) {
|
||||||
|
currentQuoteID = quoteID;
|
||||||
|
document.getElementById('enableModalEnquiryRef').textContent = enquiryRef;
|
||||||
|
document.getElementById('enableModal').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideEnableModal() {
|
||||||
|
document.getElementById('enableModal').classList.add('hidden');
|
||||||
|
currentQuoteID = null;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('modalCancelBtn').addEventListener('click', hideConfirmModal);
|
document.getElementById('modalCancelBtn').addEventListener('click', hideConfirmModal);
|
||||||
|
|
||||||
document.getElementById('modalConfirmBtn').addEventListener('click', function() {
|
document.getElementById('modalConfirmBtn').addEventListener('click', async function() {
|
||||||
if (currentForm && currentReminderType) {
|
if (currentForm && currentReminderType) {
|
||||||
// Create a hidden input for reminder_type and submit the form
|
const formData = new FormData(currentForm);
|
||||||
const input = document.createElement('input');
|
formData.append('reminder_type', currentReminderType);
|
||||||
input.type = 'hidden';
|
|
||||||
input.name = 'reminder_type';
|
try {
|
||||||
input.value = currentReminderType;
|
const response = await fetch('/go/quotes/send-reminder', {
|
||||||
currentForm.appendChild(input);
|
method: 'POST',
|
||||||
currentForm.submit();
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Update the row to show the reminder was sent
|
||||||
|
const row = currentForm.closest('tr');
|
||||||
|
const reminderCell = row.querySelector('td:nth-child(6)');
|
||||||
|
const reminderSentCell = row.querySelector('td:nth-child(7)');
|
||||||
|
|
||||||
|
// Update reminder type badge
|
||||||
|
const reminderTypeName = currentReminderType === 1 ? 'First Reminder' :
|
||||||
|
currentReminderType === 2 ? 'Second Reminder' : 'Final Reminder';
|
||||||
|
const colorClass = currentReminderType === 1 ? 'bg-blue-100 text-blue-700 border-blue-200' :
|
||||||
|
currentReminderType === 2 ? 'bg-yellow-100 text-yellow-700 border-yellow-200' :
|
||||||
|
'bg-red-100 text-red-700 border-red-200';
|
||||||
|
reminderCell.innerHTML = `<span class="inline-block px-3 py-1 rounded-full text-xs font-semibold ${colorClass}">${reminderTypeName}</span>`;
|
||||||
|
|
||||||
|
// Update reminder sent time to "just now"
|
||||||
|
reminderSentCell.innerHTML = '<span class="localdatetime">just now</span>';
|
||||||
|
|
||||||
|
// Update the button to show next reminder type
|
||||||
|
const buttonGroup = currentForm.querySelector('.inline-flex');
|
||||||
|
const mainButton = buttonGroup.querySelector('button:first-child');
|
||||||
|
if (currentReminderType === 1) {
|
||||||
|
mainButton.textContent = 'Send Second Reminder';
|
||||||
|
mainButton.setAttribute('onclick', `showConfirmModal(this, 2, '${formData.get('enquiry_ref')}', '${formData.get('customer_name')}', 'Second Reminder')`);
|
||||||
|
} else if (currentReminderType === 2) {
|
||||||
|
mainButton.textContent = 'Send Final Reminder';
|
||||||
|
mainButton.setAttribute('onclick', `showConfirmModal(this, 3, '${formData.get('enquiry_ref')}', '${formData.get('customer_name')}', 'Final Reminder')`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Failed to send reminder. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error sending reminder: ' + error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hideConfirmModal();
|
hideConfirmModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('disableModalCancelBtn').addEventListener('click', hideDisableModal);
|
document.getElementById('disableModalCancelBtn').addEventListener('click', hideDisableModal);
|
||||||
|
|
||||||
document.getElementById('disableModalConfirmBtn').addEventListener('click', function() {
|
document.getElementById('disableModalConfirmBtn').addEventListener('click', async function() {
|
||||||
if (currentQuoteID) {
|
if (currentQuoteID) {
|
||||||
// Create a form to POST the disable request
|
const formData = new FormData();
|
||||||
const form = document.createElement('form');
|
formData.append('quote_id', currentQuoteID);
|
||||||
form.method = 'POST';
|
|
||||||
form.action = '/go/quotes/disable-reminders';
|
|
||||||
|
|
||||||
const input = document.createElement('input');
|
try {
|
||||||
input.type = 'hidden';
|
const response = await fetch('/go/quotes/disable-reminders', {
|
||||||
input.name = 'quote_id';
|
method: 'POST',
|
||||||
input.value = currentQuoteID;
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
form.appendChild(input);
|
if (response.ok) {
|
||||||
document.body.appendChild(form);
|
// Find and update all rows for this quote
|
||||||
form.submit();
|
const rows = document.querySelectorAll(`tr`);
|
||||||
|
rows.forEach(row => {
|
||||||
|
const form = row.querySelector('form');
|
||||||
|
if (form && form.querySelector(`input[name="quote_id"][value="${currentQuoteID}"]`)) {
|
||||||
|
// Update reminder badge to show "Reminders Disabled"
|
||||||
|
const reminderCell = row.querySelector('td:nth-child(6)');
|
||||||
|
reminderCell.innerHTML = '<span class="inline-block px-3 py-1 rounded-full text-xs font-semibold bg-gray-200 text-gray-700 border border-gray-300">Reminders Disabled</span>';
|
||||||
|
|
||||||
|
// Disable the action buttons
|
||||||
|
const buttonGroup = row.querySelector('.inline-flex');
|
||||||
|
if (buttonGroup) {
|
||||||
|
buttonGroup.innerHTML = '<span class="text-gray-500 text-xs">Disabled</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Failed to disable reminders. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error disabling reminders: ' + error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hideDisableModal();
|
hideDisableModal();
|
||||||
});
|
});
|
||||||
|
|
@ -301,6 +469,95 @@ document.getElementById('disableModal').addEventListener('click', function(e) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('enableModalCancelBtn').addEventListener('click', hideEnableModal);
|
||||||
|
|
||||||
|
document.getElementById('enableModalConfirmBtn').addEventListener('click', async function() {
|
||||||
|
if (currentQuoteID) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('quote_id', currentQuoteID);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/go/quotes/enable-reminders', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Find and update all rows for this quote
|
||||||
|
const rows = document.querySelectorAll(`tr`);
|
||||||
|
rows.forEach(row => {
|
||||||
|
const form = row.querySelector('form');
|
||||||
|
if (form && form.querySelector(`input[name="quote_id"][value="${currentQuoteID}"]`)) {
|
||||||
|
// Get the latest reminder type to show appropriate button
|
||||||
|
const reminderCell = row.querySelector('td:nth-child(6)');
|
||||||
|
const reminderText = reminderCell.textContent.trim();
|
||||||
|
|
||||||
|
// Update reminder badge back to current state (remove "Disabled")
|
||||||
|
// Keep the existing reminder type badge
|
||||||
|
|
||||||
|
// Re-enable the action buttons
|
||||||
|
const actionsCell = row.querySelector('td:nth-child(8)');
|
||||||
|
const currentReminderLevel = reminderText.includes('Final') ? 3 :
|
||||||
|
reminderText.includes('Second') ? 2 :
|
||||||
|
reminderText.includes('First') ? 1 : 0;
|
||||||
|
|
||||||
|
const enquiryRef = form.querySelector('input[name="enquiry_ref"]').value;
|
||||||
|
const customerName = form.querySelector('input[name="customer_name"]').value;
|
||||||
|
|
||||||
|
let buttonHTML = '';
|
||||||
|
if (currentReminderLevel === 0) {
|
||||||
|
buttonHTML = `<button type="button" onclick="showConfirmModal(this, 1, '${enquiryRef}', '${customerName}', 'First Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send First Reminder</button>`;
|
||||||
|
} else if (currentReminderLevel === 1) {
|
||||||
|
buttonHTML = `<button type="button" onclick="showConfirmModal(this, 2, '${enquiryRef}', '${customerName}', 'Second Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send Second Reminder</button>`;
|
||||||
|
} else {
|
||||||
|
buttonHTML = `<button type="button" onclick="showConfirmModal(this, 3, '${enquiryRef}', '${customerName}', 'Final Reminder')" class="w-44 px-3 py-1.5 text-xs font-medium text-white bg-cmcblue rounded-l-md hover:bg-cmcblue/90 focus:z-10">Send Final Reminder</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdownHTML = `<button type="button" onclick="toggleDropdown(this)" class="px-2 py-1.5 text-xs font-medium text-white bg-cmcblue border-l border-cmcblue/80 rounded-r-md hover:bg-cmcblue/90 focus:z-10">▼</button>`;
|
||||||
|
|
||||||
|
actionsCell.querySelector('form').innerHTML = `
|
||||||
|
<input type="hidden" name="quote_id" value="${currentQuoteID}">
|
||||||
|
<input type="hidden" name="customer_email" value="${form.querySelector('input[name="customer_email"]').value}">
|
||||||
|
<input type="hidden" name="user_email" value="${form.querySelector('input[name="user_email"]').value}">
|
||||||
|
<input type="hidden" name="enquiry_ref" value="${enquiryRef}">
|
||||||
|
<input type="hidden" name="customer_name" value="${customerName}">
|
||||||
|
<input type="hidden" name="date_issued" value="${form.querySelector('input[name="date_issued"]').value}">
|
||||||
|
<input type="hidden" name="valid_until" value="${form.querySelector('input[name="valid_until"]').value}">
|
||||||
|
<div class="inline-flex rounded-md shadow-sm" role="group">
|
||||||
|
${buttonHTML}
|
||||||
|
${dropdownHTML}
|
||||||
|
</div>
|
||||||
|
<div class="hidden absolute z-10 mt-1 bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black ring-opacity-5" style="min-width: 150px;">
|
||||||
|
<ul class="py-1 text-xs text-gray-700">
|
||||||
|
<li><button type="button" onclick="showConfirmModal(this, 1, '${enquiryRef}', '${customerName}', 'First Reminder')" class="block w-full text-left px-4 py-2 hover:bg-gray-100">Send First Reminder</button></li>
|
||||||
|
<li><button type="button" onclick="showConfirmModal(this, 2, '${enquiryRef}', '${customerName}', 'Second Reminder')" class="block w-full text-left px-4 py-2 hover:bg-gray-100">Send Second Reminder</button></li>
|
||||||
|
<li><button type="button" onclick="showConfirmModal(this, 3, '${enquiryRef}', '${customerName}', 'Final Reminder')" class="block w-full text-left px-4 py-2 hover:bg-gray-100">Send Final Reminder</button></li>
|
||||||
|
<li class="border-t border-gray-200"></li>
|
||||||
|
<li><button type="button" onclick="showDisableModal('${currentQuoteID}', '${enquiryRef}')" class="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">Disable Future Reminders</button></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Failed to enable reminders. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error enabling reminders: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hideEnableModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('enableModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
hideEnableModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function toggleDropdown(button) {
|
function toggleDropdown(button) {
|
||||||
const dropdown = button.parentElement.nextElementSibling;
|
const dropdown = button.parentElement.nextElementSibling;
|
||||||
const allDropdowns = document.querySelectorAll('form > div.absolute');
|
const allDropdowns = document.querySelectorAll('form > div.absolute');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue