Better modals and visuals, adding method to re-enabled disabled reminders

This commit is contained in:
Finley Ghosh 2025-12-07 16:53:59 +11:00
parent c4d60bc1c9
commit b62d7fdb17
6 changed files with 446 additions and 90 deletions

View file

@ -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:
/* /*

View file

@ -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)

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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

View file

@ -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');