Fix passkey login tracking, add email opt-out UI guards, and add Quick/Full quote mode toggle

- PasskeyController: set LastLoginDate on passkey sign-in so Company Health
  and audit pages show accurate last-login times (was always showing 'Never')
- Jobs/Index status modal: disable 'Notify customer' email toggle and show
  warning when customer has notifications turned off; CustomerNotifyByEmail
  added to JobListDto + JobProfile mapping + data-customer-notify attribute
- Quotes/Create: disable 'Send quote via email' checkbox with 'Notifications
  off' badge when selected customer has email opt-out; ViewBag.CustomerEmailOptOutIds
  added alongside existing CustomerTaxExemptIds pattern
- Quotes/Create: Quick Quote / Full Quote segmented toggle at top of form;
  hides non-essential fields (dates, notes, tags, oven, discount, photos) in
  Quick mode; selection persisted in localStorage
- InvoicesController Send action: improved error logging and user-facing
  warning when PDF generation or email dispatch fails after status is saved
- item-wizard.js: guard item restoration with try/catch; ensure writeHiddenFields
  always runs on form submit via capture-phase listener
- Help docs and AI knowledge base updated for all new features

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 13:32:34 -04:00
parent 0ea192d55b
commit cad728ba66
11 changed files with 194 additions and 18 deletions
@@ -195,6 +195,7 @@
data-job-number="@job.JobNumber"
data-status-id="@job.JobStatusId"
data-status-name="@job.StatusDisplayName"
data-customer-notify="@job.CustomerNotifyByEmail.ToString().ToLower()"
title="Click to change status">
<span class="pcl-chip-dot"></span>@job.StatusDisplayName
</span>
@@ -511,6 +512,9 @@
<i class="bi bi-envelope me-1"></i>Notify customer via email
</label>
</div>
<div id="statusModalEmailOptOutNote" class="alert alert-warning alert-permanent py-1 px-2 mt-2 small" style="display:none;">
<i class="bi bi-bell-slash me-1"></i>This customer has email notifications turned off.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
@@ -745,12 +749,22 @@
currentJobStatusId = this.getAttribute('data-status-id');
const jobNumber = this.getAttribute('data-job-number');
const statusName = this.getAttribute('data-status-name');
const customerNotify = this.getAttribute('data-customer-notify') !== 'false';
// Update modal content
document.getElementById('modalStatusJobNumber').textContent = jobNumber;
document.getElementById('modalCurrentStatus').textContent = statusName;
document.getElementById('statusSelect').value = currentJobStatusId;
// Reflect customer email opt-out preference
const emailCheckbox = document.getElementById('statusModalSendEmail');
const emailOptOutNote = document.getElementById('statusModalEmailOptOutNote');
if (emailCheckbox) {
emailCheckbox.disabled = !customerNotify;
if (!customerNotify) emailCheckbox.checked = false;
}
if (emailOptOutNote) emailOptOutNote.style.display = customerNotify ? 'none' : 'block';
// Show modal
const modal = new bootstrap.Modal(document.getElementById('statusModal'));
modal.show();