Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,172 @@
@using PowderCoating.Web.Controllers
@model BroadcastForm
@{
ViewData["Title"] = "Email Broadcast";
}
@section Styles {
<style>
[data-bs-theme="dark"] .card {
border-color: var(--bs-border-color) !important;
}
[data-bs-theme="dark"] .card-header {
background-color: var(--bs-secondary-bg) !important;
border-color: var(--bs-border-color) !important;
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .alert-info {
background-color: rgba(13,202,240,.1);
border-color: rgba(13,202,240,.3);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .alert-warning {
background-color: rgba(255,193,7,.1);
border-color: rgba(255,193,7,.3);
color: var(--bs-body-color);
}
</style>
}
<div class="container-fluid py-3" style="max-width:860px">
<div class="d-flex align-items-center gap-3 mb-3">
<h4 class="mb-0"><i class="bi bi-broadcast me-2 text-primary"></i>Email Broadcast</h4>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-permanent mb-3">@TempData["Success"]</div>
}
@if (TempData["Error"] != null)
{
<div class="alert alert-danger alert-permanent mb-3">@TempData["Error"]</div>
}
<form method="post" asp-action="Send" id="broadcast-form">
@Html.AntiForgeryToken()
<div class="row g-3">
@* Left: recipients *@
<div class="col-md-5">
<div class="card shadow-sm h-100">
<div class="card-header fw-semibold py-2">Recipients</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label fw-medium">Send to</label>
<select name="Target" id="target-select" class="form-select" onchange="onTargetChange()">
<option value="active" selected="@(Model.Target == "active")">All active companies</option>
<option value="all" selected="@(Model.Target == "all")">All companies (incl. expired)</option>
<option value="status_grace" selected="@(Model.Target == "status_grace")">Grace period companies</option>
<option value="status_expired" selected="@(Model.Target == "status_expired")">Expired companies</option>
<option value="plan" selected="@(Model.Target == "plan")">By subscription plan</option>
<option value="specific" selected="@(Model.Target == "specific")">Specific companies</option>
</select>
</div>
<div id="plan-filter" class="mb-3" style="display:none">
<label class="form-label fw-medium">Plan</label>
<select name="PlanFilter" class="form-select">
@foreach (var p in (IEnumerable<dynamic>)ViewBag.PlanConfigs)
{
<option value="@p.Plan">@p.DisplayName</option>
}
</select>
</div>
<div id="specific-filter" class="mb-3" style="display:none">
<label class="form-label fw-medium">Companies</label>
<select name="CompanyIds" multiple class="form-select" style="height:160px">
@foreach (var c in (IEnumerable<dynamic>)ViewBag.Companies)
{
<option value="@c.Id">@c.CompanyName</option>
}
</select>
<div class="form-text">Hold Ctrl / Cmd to select multiple.</div>
</div>
<div class="alert alert-info py-2 small mb-0" id="recipient-preview">
<span id="recipient-count">@ViewBag.ActiveCount</span> company email(s) will receive this message.
</div>
</div>
</div>
</div>
@* Right: compose *@
<div class="col-md-7">
<div class="card shadow-sm">
<div class="card-header fw-semibold py-2">Compose</div>
<div class="card-body">
<div class="mb-3">
<label asp-for="Subject" class="form-label fw-medium">Subject</label>
<input asp-for="Subject" class="form-control" placeholder="e.g. Scheduled maintenance this Saturday" />
<span asp-validation-for="Subject" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="Body" class="form-label fw-medium">Message</label>
<textarea asp-for="Body" class="form-control" rows="12"
placeholder="Write your message here. Plain text — line breaks will be preserved."></textarea>
<span asp-validation-for="Body" class="text-danger small"></span>
</div>
<div class="alert alert-warning py-2 small mb-3">
<i class="bi bi-exclamation-triangle me-1"></i>
This will send a real email to the primary contact address of each matching company. Double-check your recipient selection before sending.
</div>
<button type="submit" class="btn btn-primary" id="send-btn">
<i class="bi bi-send me-1"></i>Send Broadcast
</button>
</div>
</div>
</div>
</div>
</form>
</div>
@section Scripts {
<script>
(function () {
const targetSelect = document.getElementById('target-select');
const planFilter = document.getElementById('plan-filter');
const specificFilter = document.getElementById('specific-filter');
const countEl = document.getElementById('recipient-count');
function onTargetChange() {
const val = targetSelect.value;
planFilter.style.display = val === 'plan' ? '' : 'none';
specificFilter.style.display = val === 'specific' ? '' : 'none';
updateCount();
}
async function updateCount() {
const val = targetSelect.value;
const planSel = document.querySelector('[name="PlanFilter"]');
const companySel = document.querySelector('[name="CompanyIds"]');
const params = new URLSearchParams({ target: val });
if (val === 'plan' && planSel) params.set('plan', planSel.value);
if (val === 'specific' && companySel) {
Array.from(companySel.selectedOptions).forEach(o => params.append('companyIds', o.value));
}
try {
const resp = await fetch('@Url.Action("RecipientCount")?' + params.toString());
const data = await resp.json();
countEl.textContent = data.count;
} catch { countEl.textContent = '?'; }
}
window.onTargetChange = onTargetChange;
// Wire up change events for sub-filters
document.querySelector('[name="PlanFilter"]')?.addEventListener('change', updateCount);
document.querySelector('[name="CompanyIds"]')?.addEventListener('change', updateCount);
// Init visibility
onTargetChange();
// Confirm before send
document.getElementById('send-btn').addEventListener('click', function (e) {
const count = countEl.textContent;
if (!confirm(`Send this email to ${count} company recipient(s)?`)) e.preventDefault();
});
})();
</script>
}