Initial commit
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
@{
|
||||
ViewData["Title"] = "Delete Account";
|
||||
ViewData["PageIcon"] = "bi-trash3-fill";
|
||||
var companyName = ViewBag.CompanyName as string ?? "your company";
|
||||
var userCount = (int)(ViewBag.UserCount ?? 0);
|
||||
var jobCount = (int)(ViewBag.JobCount ?? 0);
|
||||
var quoteCount = (int)(ViewBag.QuoteCount ?? 0);
|
||||
var customerCount = (int)(ViewBag.CustomerCount ?? 0);
|
||||
var invoiceCount = (int)(ViewBag.InvoiceCount ?? 0);
|
||||
}
|
||||
|
||||
<div class="container" style="max-width:720px;">
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a asp-controller="CompanySettings" asp-action="Index">Company Settings</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active">Delete Account</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Error alert -->
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<div class="alert alert-danger alert-permanent" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>@TempData["Error"]
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<div class="bg-danger bg-opacity-10 rounded-circle p-3">
|
||||
<i class="bi bi-trash3-fill text-danger fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-muted mb-0">This action is permanent and cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- What happens card -->
|
||||
<div class="card border-danger mb-4">
|
||||
<div class="card-header bg-danger text-white fw-semibold">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>What happens when you delete your account
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-3">
|
||||
Deleting your account will permanently deactivate access to
|
||||
<strong>@companyName</strong> and mark the following data for removal:
|
||||
</p>
|
||||
|
||||
<ul class="list-unstyled mb-3">
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-person-x-fill text-danger me-2"></i>
|
||||
<strong>@userCount user account@(userCount != 1 ? "s" : "")</strong> will be deactivated — no one will be able to log in.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-briefcase-fill text-danger me-2"></i>
|
||||
<strong>@jobCount job@(jobCount != 1 ? "s" : "")</strong>,
|
||||
<strong>@quoteCount quote@(quoteCount != 1 ? "s" : "")</strong>, and
|
||||
<strong>@invoiceCount invoice@(invoiceCount != 1 ? "s" : "")</strong> will be marked deleted.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-people-fill text-danger me-2"></i>
|
||||
<strong>@customerCount customer record@(customerCount != 1 ? "s" : "")</strong> will be marked deleted.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-cloud-slash-fill text-danger me-2"></i>
|
||||
All inventory, equipment, maintenance, and other company data will be marked deleted.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-warning alert-permanent mb-0">
|
||||
<i class="bi bi-lock-fill me-2"></i>
|
||||
<strong>You will lose access to your data immediately.</strong>
|
||||
Once your account is deleted you will no longer be able to export, download,
|
||||
or access any of your data. All records are subject to purge and
|
||||
<strong>cannot be recovered</strong> after deletion.
|
||||
If you need a data export, please do so <em>before</em> deleting your account.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alternatives card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header fw-semibold">
|
||||
<i class="bi bi-lightbulb me-2 text-warning"></i>Consider these alternatives first
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="mb-0">
|
||||
<li class="mb-1">
|
||||
<strong>Pause instead of delete</strong> — Contact support to temporarily suspend your account.
|
||||
</li>
|
||||
<li class="mb-1">
|
||||
<strong>Cancel your subscription</strong> — Stop future billing without deleting your data.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Export your data first</strong> — Go to
|
||||
<a asp-controller="Reports" asp-action="Index">Reports</a>
|
||||
to export jobs, customers, invoices, and more before proceeding.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation form -->
|
||||
<div class="card border-danger">
|
||||
<div class="card-header bg-danger bg-opacity-10 fw-semibold text-danger">
|
||||
<i class="bi bi-shield-exclamation me-2"></i>Confirm account deletion
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form asp-action="DeleteAccount" asp-controller="CompanySettings" method="post" id="deleteAccountForm">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<!-- Acknowledgement checkbox -->
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="acknowledged" name="acknowledged" value="true" required />
|
||||
<label class="form-check-label" for="acknowledged">
|
||||
I understand that deleting my account is <strong>permanent</strong>.
|
||||
I will no longer be able to export any data, and all records will be
|
||||
purged. I accept that this action cannot be undone.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type DELETE -->
|
||||
<div class="mb-4">
|
||||
<label for="confirmationWord" class="form-label fw-semibold">
|
||||
To confirm, type <code class="text-danger fs-6">DELETE</code> in the box below:
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control form-control-lg"
|
||||
id="confirmationWord"
|
||||
name="confirmationWord"
|
||||
placeholder="Type DELETE here"
|
||||
autocomplete="off"
|
||||
spellcheck="false" />
|
||||
<div class="form-text text-muted">This field is case-sensitive. You must type it in all capitals.</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button type="submit" class="btn btn-danger px-4" id="deleteBtn" disabled>
|
||||
<i class="bi bi-trash3 me-1"></i>Delete My Account Permanently
|
||||
</button>
|
||||
<a asp-controller="CompanySettings" asp-action="Index" class="btn btn-outline-secondary px-4">
|
||||
Cancel — Keep My Account
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
(function () {
|
||||
const ackCheckbox = document.getElementById('acknowledged');
|
||||
const confirmInput = document.getElementById('confirmationWord');
|
||||
const deleteBtn = document.getElementById('deleteBtn');
|
||||
|
||||
/// Re-evaluates whether the submit button should be enabled.
|
||||
/// Both the checkbox AND the exact word "DELETE" must be present.
|
||||
function updateButton() {
|
||||
const wordOk = confirmInput.value.trim() === 'DELETE';
|
||||
const ackOk = ackCheckbox.checked;
|
||||
deleteBtn.disabled = !(wordOk && ackOk);
|
||||
}
|
||||
|
||||
confirmInput.addEventListener('input', updateButton);
|
||||
ackCheckbox.addEventListener('change', updateButton);
|
||||
|
||||
// Extra guard: prevent accidental double-submit after the form is submitted.
|
||||
document.getElementById('deleteAccountForm').addEventListener('submit', function () {
|
||||
deleteBtn.disabled = true;
|
||||
deleteBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Deleting…';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
@model PowderCoating.Application.DTOs.Notification.NotificationTemplateDto
|
||||
@{
|
||||
ViewData["Title"] = $"Edit Template — {Model.DisplayName}";
|
||||
ViewData["PageIcon"] = "bi-envelope-gear";
|
||||
var placeholders = ViewBag.Placeholders as List<(string Placeholder, string Description)>
|
||||
?? new List<(string, string)>();
|
||||
var isEmail = Model.IsEmail;
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<style>
|
||||
.placeholder-pill {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.placeholder-pill:hover { background-color: #0d6efd !important; color: white !important; }
|
||||
.copy-feedback { display: none; font-size: 0.75rem; color: #198754; }
|
||||
</style>
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-start mb-4">
|
||||
<a asp-controller="CompanySettings" asp-action="NotificationTemplates" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- LEFT: Edit form -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex align-items-center gap-2">
|
||||
@if (isEmail)
|
||||
{
|
||||
<span class="badge bg-primary"><i class="bi bi-envelope"></i> Email</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success"><i class="bi bi-phone"></i> SMS</span>
|
||||
}
|
||||
<span class="fw-semibold">@Model.NotificationType</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form asp-action="EditTemplate" asp-route-id="@Model.Id" method="post" id="templateForm">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" asp-for="Id" />
|
||||
|
||||
@if (isEmail)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label asp-for="Subject" class="form-label fw-semibold">Subject</label>
|
||||
<input asp-for="Subject" class="form-control" placeholder="Email subject line" />
|
||||
<span asp-validation-for="Subject" class="text-danger small"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">
|
||||
Body
|
||||
@if (!isEmail)
|
||||
{
|
||||
<span class="text-muted fw-normal small ms-2">
|
||||
<span id="charCount">@Model.Body.Length</span> characters
|
||||
(<span id="segCount">@((Model.Body.Length / 160) + 1)</span> SMS segment@(((Model.Body.Length / 160) + 1) != 1 ? "s" : ""))
|
||||
</span>
|
||||
}
|
||||
</label>
|
||||
|
||||
@if (isEmail)
|
||||
{
|
||||
<!-- Raw HTML textarea for email — supports {{placeholders}} and full HTML -->
|
||||
<textarea asp-for="Body" class="form-control font-monospace" rows="16"
|
||||
placeholder="Enter HTML email body..."></textarea>
|
||||
<div class="form-text text-muted mt-1">
|
||||
<i class="bi bi-code-slash me-1"></i>HTML is supported. Use <code>{{placeholder}}</code> tokens anywhere in the body.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Plain textarea for SMS -->
|
||||
<textarea asp-for="Body" class="form-control" rows="5"
|
||||
id="smsBody" maxlength="1000"
|
||||
placeholder="Enter your SMS message text..."></textarea>
|
||||
<span asp-validation-for="Body" class="text-danger small"></span>
|
||||
<div class="form-text text-muted">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Standard SMS segments are 160 characters. Messages over 160 chars are split into multiple segments.
|
||||
Always include <code>Reply STOP to opt out</code> for CTIA compliance.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center pt-2">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-floppy"></i> Save Template
|
||||
</button>
|
||||
<a asp-controller="CompanySettings" asp-action="NotificationTemplates"
|
||||
class="btn btn-outline-secondary">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<form asp-action="ResetTemplate" asp-route-id="@Model.Id" method="post"
|
||||
onsubmit="return confirm('Reset this template to the built-in default? Your customisations will be lost.');">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> Reset to Default
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Placeholder reference -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-braces"></i> Available Placeholders</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">
|
||||
Click any placeholder to copy it to your clipboard, then paste it into the template body.
|
||||
</p>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
@foreach (var (placeholder, description) in placeholders)
|
||||
{
|
||||
<div>
|
||||
<span class="badge bg-light text-dark border placeholder-pill px-2 py-1"
|
||||
onclick="copyPlaceholder('@placeholder', this)"
|
||||
title="@description — click to copy">
|
||||
@placeholder
|
||||
</span>
|
||||
<span class="copy-feedback ms-1">Copied!</span>
|
||||
<div class="text-muted" style="font-size: 0.78rem;">@description</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mt-3">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title"><i class="bi bi-lightbulb"></i> Tips</h6>
|
||||
<ul class="small text-muted mb-0 ps-3">
|
||||
<li>Placeholders are case-insensitive.</li>
|
||||
<li>Unrecognised placeholders are left as-is.</li>
|
||||
@if (isEmail)
|
||||
{
|
||||
<li>Edit raw HTML directly. A plain-text version is generated automatically for email clients that require it.</li>
|
||||
<li>An unsubscribe link is always appended to comply with CAN-SPAM.</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li>Keep messages under 160 characters to avoid splitting.</li>
|
||||
<li>Always include opt-out instructions (e.g. "Reply STOP") to comply with CTIA guidelines.</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@if (!isEmail)
|
||||
{
|
||||
<script>
|
||||
const smsBody = document.getElementById('smsBody');
|
||||
const charCount = document.getElementById('charCount');
|
||||
const segCount = document.getElementById('segCount');
|
||||
|
||||
smsBody?.addEventListener('input', function () {
|
||||
const len = this.value.length;
|
||||
charCount.textContent = len;
|
||||
const segs = Math.ceil(len / 160) || 1;
|
||||
segCount.textContent = segs;
|
||||
const suffixEl = segCount.nextSibling;
|
||||
if (suffixEl) suffixEl.textContent = ` SMS segment${segs !== 1 ? 's' : ''}`;
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
<script>
|
||||
function copyPlaceholder(text, el) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
const feedback = el.nextElementSibling;
|
||||
if (feedback) {
|
||||
feedback.style.display = 'inline';
|
||||
setTimeout(() => { feedback.style.display = 'none'; }, 1800);
|
||||
}
|
||||
}).catch(function () {
|
||||
// Fallback for browsers that don't support clipboard API
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
@model List<PowderCoating.Application.DTOs.Notification.NotificationTemplateDto>
|
||||
@{
|
||||
ViewData["Title"] = "Notification Templates";
|
||||
ViewData["PageIcon"] = "bi-envelope-gear";
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="bi bi-check-circle me-2"></i>@TempData["SuccessMessage"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-end mb-4">
|
||||
<a asp-controller="CompanySettings" asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Settings
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-3">Template</th>
|
||||
<th>Channel</th>
|
||||
<th>Last Modified</th>
|
||||
<th class="text-end pe-3" style="width: 100px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var template in Model.Where(t => t.IsEmail || ViewBag.SmsEnabled == true))
|
||||
{
|
||||
<tr>
|
||||
<td class="ps-3 fw-semibold">@template.DisplayName</td>
|
||||
<td>
|
||||
@if (template.IsEmail)
|
||||
{
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-envelope"></i> Email
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-phone"></i> SMS
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-muted small">
|
||||
@(template.UpdatedAt?.ToString("MMM d, yyyy h:mm tt") ?? "Using defaults")
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<a asp-controller="CompanySettings"
|
||||
asp-action="EditTemplate"
|
||||
asp-route-id="@template.Id"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-muted small">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Templates support <strong>{{placeholder}}</strong> tokens that are replaced
|
||||
with live data when notifications are sent. Click <strong>Edit</strong> to customise any template.
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,500 @@
|
||||
@* Lookup Management Modals *@
|
||||
|
||||
<!-- Job Status Modal -->
|
||||
<div class="modal fade" id="jobStatusModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-list-check me-2"></i><span id="jobStatusModalTitle">Add Job Status</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="jobStatusForm">
|
||||
<input type="hidden" id="jobStatusId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="jobStatusDisplayName" class="form-label">Display Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="jobStatusDisplayName" placeholder="e.g., Custom Status" required maxlength="100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobStatusCode" class="form-label">Status Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="jobStatusCode" placeholder="e.g., CUSTOM_STATUS" required pattern="[A-Z_]+" maxlength="50">
|
||||
<small class="form-text text-muted">Uppercase letters and underscores only. This cannot be changed later.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobStatusColorClass" class="form-label">Badge Color <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="jobStatusColorClass" required>
|
||||
<option value="primary">Primary (Blue)</option>
|
||||
<option value="secondary">Secondary (Gray)</option>
|
||||
<option value="success">Success (Green)</option>
|
||||
<option value="danger">Danger (Red)</option>
|
||||
<option value="warning">Warning (Yellow)</option>
|
||||
<option value="info">Info (Cyan)</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobStatusCategory" class="form-label">Workflow Category</label>
|
||||
<select class="form-select" id="jobStatusCategory">
|
||||
<option value="">Select a category</option>
|
||||
<option value="Pre-Production">Pre-Production</option>
|
||||
<option value="Production">Production</option>
|
||||
<option value="Post-Production">Post-Production</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="jobStatusIsTerminal">
|
||||
<label class="form-check-label" for="jobStatusIsTerminal">
|
||||
Terminal Status
|
||||
<small class="d-block text-muted">Job is completed/closed</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="jobStatusIsWIP">
|
||||
<label class="form-check-label" for="jobStatusIsWIP">
|
||||
Work In Progress
|
||||
<small class="d-block text-muted">Job is actively being worked on</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobStatusDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="jobStatusDescription" rows="2" placeholder="Optional description..." maxlength="500"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveJobStatusBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job Priority Modal -->
|
||||
<div class="modal fade" id="jobPriorityModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-arrow-up-circle me-2"></i><span id="jobPriorityModalTitle">Add Job Priority</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="jobPriorityForm">
|
||||
<input type="hidden" id="jobPriorityId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="jobPriorityDisplayName" class="form-label">Display Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="jobPriorityDisplayName" placeholder="e.g., Custom Priority" required maxlength="100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobPriorityCode" class="form-label">Priority Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="jobPriorityCode" placeholder="e.g., CUSTOM_PRIORITY" required pattern="[A-Z_]+" maxlength="50">
|
||||
<small class="form-text text-muted">Uppercase letters and underscores only. This cannot be changed later.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobPriorityColorClass" class="form-label">Badge Color <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="jobPriorityColorClass" required>
|
||||
<option value="primary">Primary (Blue)</option>
|
||||
<option value="secondary">Secondary (Gray)</option>
|
||||
<option value="success">Success (Green)</option>
|
||||
<option value="danger">Danger (Red)</option>
|
||||
<option value="warning">Warning (Yellow)</option>
|
||||
<option value="info">Info (Cyan)</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="jobPriorityDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="jobPriorityDescription" rows="2" placeholder="Optional description..." maxlength="500"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveJobPriorityBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quote Status Modal -->
|
||||
<div class="modal fade" id="quoteStatusModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-file-text me-2"></i><span id="quoteStatusModalTitle">Add Quote Status</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="quoteStatusForm">
|
||||
<input type="hidden" id="quoteStatusId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="quoteStatusDisplayName" class="form-label">Display Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="quoteStatusDisplayName" placeholder="e.g., Custom Status" required maxlength="100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="quoteStatusCode" class="form-label">Status Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="quoteStatusCode" placeholder="e.g., CUSTOM_STATUS" required pattern="[A-Z_]+" maxlength="50">
|
||||
<small class="form-text text-muted">Uppercase letters and underscores only. This cannot be changed later.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="quoteStatusColorClass" class="form-label">Badge Color <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="quoteStatusColorClass" required>
|
||||
<option value="primary">Primary (Blue)</option>
|
||||
<option value="secondary">Secondary (Gray)</option>
|
||||
<option value="success">Success (Green)</option>
|
||||
<option value="danger">Danger (Red)</option>
|
||||
<option value="warning">Warning (Yellow)</option>
|
||||
<option value="info">Info (Cyan)</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Business Flags</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="quoteStatusIsApproved">
|
||||
<label class="form-check-label" for="quoteStatusIsApproved">
|
||||
<strong>Approved Status</strong>
|
||||
<small class="d-block text-muted">Marks quotes that can be converted to jobs</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="quoteStatusIsConverted">
|
||||
<label class="form-check-label" for="quoteStatusIsConverted">
|
||||
<strong>Converted Status</strong>
|
||||
<small class="d-block text-muted">Set automatically after converting to job</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="quoteStatusIsDraft">
|
||||
<label class="form-check-label" for="quoteStatusIsDraft">
|
||||
<strong>Draft Status</strong>
|
||||
<small class="d-block text-muted">Quote is being prepared</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="quoteStatusDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="quoteStatusDescription" rows="2" placeholder="Optional description..." maxlength="500"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveQuoteStatusBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inventory Category Modal -->
|
||||
<div class="modal fade" id="inventoryCategoryModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-box-seam me-2"></i><span id="inventoryCategoryModalTitle">Add Inventory Category</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="inventoryCategoryForm">
|
||||
<input type="hidden" id="inventoryCategoryId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="inventoryCategoryDisplayName" class="form-label">Display Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="inventoryCategoryDisplayName" placeholder="e.g., Custom Category" required maxlength="100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="inventoryCategoryCode" class="form-label">Category Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="inventoryCategoryCode" placeholder="e.g., CUSTOM_CATEGORY" required pattern="[A-Z_]+" maxlength="50">
|
||||
<small class="form-text text-muted">Uppercase letters and underscores only. This cannot be changed later.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="inventoryCategoryIsCoating">
|
||||
<label class="form-check-label" for="inventoryCategoryIsCoating">
|
||||
<strong>Is Coating</strong>
|
||||
<small class="d-block text-muted">Items in this category are coatings that can be applied to parts</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="inventoryCategoryDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="inventoryCategoryDescription" rows="2" placeholder="Optional description of what this category is used for..." maxlength="500"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveInventoryCategoryBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appointment Type Modal -->
|
||||
<div class="modal fade" id="appointmentTypeModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-calendar-event me-2"></i><span id="appointmentTypeModalTitle">Add Appointment Type</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="appointmentTypeForm">
|
||||
<input type="hidden" id="appointmentTypeId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="appointmentTypeDisplayName" class="form-label">Display Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="appointmentTypeDisplayName" placeholder="e.g., Equipment Delivery" required maxlength="100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="appointmentTypeCode" class="form-label">Type Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="appointmentTypeCode" placeholder="e.g., DELIVERY" required pattern="[A-Z_]+" maxlength="50">
|
||||
<small class="form-text text-muted">Uppercase letters and underscores only. This cannot be changed later.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="appointmentTypeColorClass" class="form-label">Calendar Color <span class="text-danger">*</span></label>
|
||||
<div class="d-flex gap-2 align-items-center mb-2">
|
||||
<span class="badge fs-6 px-3 py-2" id="appointmentTypeColorPreview">
|
||||
<i class="bi bi-calendar-event me-1"></i>Preview
|
||||
</span>
|
||||
<small class="text-muted">Live preview of calendar color</small>
|
||||
</div>
|
||||
<select class="form-select" id="appointmentTypeColorClass" required onchange="updateAppointmentTypeColorPreview()">
|
||||
<option value="purple">🟣 Purple</option>
|
||||
<option value="green">🟢 Green</option>
|
||||
<option value="blue">🔵 Blue</option>
|
||||
<option value="orange">🟠 Orange</option>
|
||||
<option value="red">🔴 Red</option>
|
||||
<option value="yellow">🟡 Yellow</option>
|
||||
<option value="pink">🩷 Pink</option>
|
||||
<option value="cyan">🔷 Cyan</option>
|
||||
<option value="teal">🩵 Teal</option>
|
||||
<option value="indigo">🟣 Indigo</option>
|
||||
<option value="lime">🟢 Lime</option>
|
||||
<option value="brown">🟤 Brown</option>
|
||||
<option value="gray">⚫ Gray</option>
|
||||
<option value="success">✅ Success (Green)</option>
|
||||
<option value="danger">❌ Danger (Red)</option>
|
||||
<option value="warning">⚠️ Warning (Yellow)</option>
|
||||
<option value="info">ℹ️ Info (Blue)</option>
|
||||
<option value="primary">🔷 Primary (Blue)</option>
|
||||
<option value="secondary">⚫ Secondary (Gray)</option>
|
||||
<option value="dark">⬛ Dark</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Choose a color that will appear on the calendar for this appointment type</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="appointmentTypeIconClass" class="form-label">Icon Class (Optional)</label>
|
||||
<input type="text" class="form-control" id="appointmentTypeIconClass" placeholder="e.g., bi-truck, bi-box-arrow-down" maxlength="50">
|
||||
<small class="form-text text-muted">Bootstrap icon class for visual identification</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="appointmentTypeRequiresJob">
|
||||
<label class="form-check-label" for="appointmentTypeRequiresJob">
|
||||
Require Job Link
|
||||
<small class="d-block text-muted">This appointment type must be linked to an existing job</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" id="appointmentTypeActiveField" style="display: none;">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="appointmentTypeIsActive" checked>
|
||||
<label class="form-check-label" for="appointmentTypeIsActive">
|
||||
Active
|
||||
<small class="d-block text-muted">Inactive types won't appear in dropdown menus</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="appointmentTypeDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="appointmentTypeDescription" rows="2" placeholder="Optional description..." maxlength="500"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveAppointmentTypeBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prep Service Modal -->
|
||||
<div class="modal fade" id="prepServiceModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-tools me-2"></i><span id="prepServiceModalTitle">Add Prep Service</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="prepServiceForm">
|
||||
<input type="hidden" id="prepServiceId" value="">
|
||||
<div class="mb-3">
|
||||
<label for="prepServiceName" class="form-label">Service Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="prepServiceName" placeholder="e.g., Sandblasting, Chemical Stripping" required maxlength="100">
|
||||
<small class="form-text text-muted">Enter a descriptive name for this preparation service</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="prepServiceDescription" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="prepServiceDescription" rows="3" placeholder="Optional description of this service..." maxlength="500"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="prepServiceRequiresBlastSetup">
|
||||
<label class="form-check-label" for="prepServiceRequiresBlastSetup">
|
||||
<strong>Requires Blast Setup Selection</strong>
|
||||
<small class="d-block text-muted">When checked, the item wizard shows a blast rig dropdown for this service so the correct throughput rate is used</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="prepServiceIsActive" checked>
|
||||
<label class="form-check-label" for="prepServiceIsActive">
|
||||
<strong>Active</strong>
|
||||
<small class="d-block text-muted">Inactive services won't appear in dropdown menus</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="savePrepServiceBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blast Setup Modal -->
|
||||
<div class="modal fade" id="blastSetupModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="bi bi-fan me-2"></i><span id="blastSetupModalTitle">Add Blast Setup</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="blastSetupForm">
|
||||
<input type="hidden" id="blastSetupId" value="">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label for="blastSetupName" class="form-label">Setup Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="blastSetupName"
|
||||
placeholder="e.g., Main Cabinet, Outdoor Blast Pot, Blast Room" required maxlength="100">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label for="blastSetupModalType" class="form-label">Setup Type <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="blastSetupModalType">
|
||||
<option value="0">Siphon Cabinet</option>
|
||||
<option value="1">Siphon Pot</option>
|
||||
<option value="2" selected>Pressure Pot</option>
|
||||
<option value="3">Wet Blasting</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label for="blastSetupCfm" class="form-label">Compressor CFM <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control blast-modal-input" id="blastSetupCfm"
|
||||
min="0" max="9999" step="0.5" placeholder="e.g. 40">
|
||||
<span class="input-group-text">CFM</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label for="blastSetupNozzleSize" class="form-label">Nozzle Size</label>
|
||||
<select class="form-select blast-modal-input" id="blastSetupNozzleSize">
|
||||
<option value="2">#2 (1/8") — Very small / entry level</option>
|
||||
<option value="3">#3 (3/16") — Small / hobby</option>
|
||||
<option value="4">#4 (1/4") — Light duty</option>
|
||||
<option value="5" selected>#5 (5/16") — Medium (most common)</option>
|
||||
<option value="6">#6 (3/8") — Heavy duty</option>
|
||||
<option value="7">#7 (7/16") — High volume</option>
|
||||
<option value="8">#8 (1/2") — Industrial</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label for="blastSetupSubstrate" class="form-label">Primary Substrate</label>
|
||||
<select class="form-select blast-modal-input" id="blastSetupSubstrate">
|
||||
<option value="1">Paint / light coating</option>
|
||||
<option value="0" selected>Mixed (typical)</option>
|
||||
<option value="2">Rust & scale</option>
|
||||
<option value="3">Existing powder coat</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label for="blastSetupOverride" class="form-label">Rate Override <small class="text-muted">(optional)</small></label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="blastSetupOverride"
|
||||
min="0" max="99999" step="0.1" placeholder="Leave blank to use formula">
|
||||
<span class="input-group-text">sqft/hr</span>
|
||||
</div>
|
||||
<small class="text-muted">Enter your actual measured rate to bypass the formula</small>
|
||||
</div>
|
||||
<div class="col-sm-6 d-flex align-items-end">
|
||||
<div class="w-100 p-3 bg-light rounded text-center">
|
||||
<div class="text-muted small">Derived Rate</div>
|
||||
<div class="fw-bold fs-5" id="blastSetupDerivedRate">—</div>
|
||||
<div class="text-muted small">sqft/hr</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="d-flex gap-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="blastSetupIsDefault">
|
||||
<label class="form-check-label" for="blastSetupIsDefault">
|
||||
<strong>Default</strong>
|
||||
<small class="d-block text-muted">Pre-selected in AI Photo Quotes</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="blastSetupIsActive" checked>
|
||||
<label class="form-check-label" for="blastSetupIsActive">
|
||||
<strong>Active</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveBlastSetupBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user