Add invoice SMS notifications and customer intake kiosk
Invoice SMS:
- Send Invoice modal now prompts Email/SMS/Both based on customer contact data
- New /invoice/{token} customer-facing view page with full line items and pay button
- PublicViewToken (permanent) added to Invoice; separate from expiring PaymentLinkToken
- InvoiceSent SMS default template added; customizable via Notification Templates settings
- {{viewUrl}} placeholder documented in template editor
Customer Intake Kiosk:
- Tablet kiosk flow: Contact → Job → Terms/Signature → Confirmation
- Remote link mode for off-site customers (lighter form, no signature)
- KioskHub (AllowAnonymous SignalR) for staff-to-tablet push without login
- Staff activates tablet via cookie; sends remote link manually
- Submitted sessions create Customer + Job automatically; fires in-app notification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
@model List<PowderCoating.Application.DTOs.Kiosk.KioskSessionListDto>
|
||||
@using PowderCoating.Core.Enums
|
||||
@{
|
||||
ViewData["Title"] = "Customer Intakes";
|
||||
string activeFilter = ViewBag.ActiveFilter as string ?? "all";
|
||||
}
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4 flex-wrap gap-2">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-clipboard-check fs-3 text-primary"></i>
|
||||
<div>
|
||||
<h1 class="h3 fw-bold mb-0">Customer Intakes</h1>
|
||||
<p class="text-muted mb-0">Walk-in and remote intake sessions</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="/Kiosk/SendRemoteLink" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-envelope-at me-1"></i> Send Remote Link
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Filter tabs *@
|
||||
<ul class="nav nav-tabs mb-4">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(activeFilter == "all" ? "active" : "")" href="?filter=all">All (@Model.Count)</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(activeFilter == "submitted" ? "active" : "")" href="?filter=submitted">
|
||||
Submitted (@Model.Count(d => d.Status == KioskSessionStatus.Submitted))
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(activeFilter == "active" ? "active" : "")" href="?filter=active">
|
||||
Pending (@Model.Count(d => d.Status == KioskSessionStatus.Active && !d.IsExpired))
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(activeFilter == "expired" ? "active" : "")" href="?filter=expired">
|
||||
Expired (@Model.Count(d => d.IsExpired))
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-inbox fs-1 mb-3 d-block"></i>
|
||||
<p>No intake sessions found.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Customer</th>
|
||||
<th>Contact</th>
|
||||
<th>Project</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>SMS</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-nowrap text-muted small">
|
||||
@(s.SubmittedAt?.ToLocalTime().ToString("MM/dd/yy h:mm tt") ?? s.ExpiresAt.AddHours(-2).ToLocalTime().ToString("MM/dd/yy h:mm tt"))
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-semibold">@s.CustomerFullName</div>
|
||||
@if (s.LinkedCustomerId.HasValue)
|
||||
{
|
||||
<a href="/Customers/Details/@s.LinkedCustomerId" class="small text-success">
|
||||
<i class="bi bi-person-check me-1"></i>Customer matched
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
<td class="small text-muted">
|
||||
@if (!string.IsNullOrEmpty(s.CustomerPhone))
|
||||
{
|
||||
<div><i class="bi bi-telephone me-1"></i>@s.CustomerPhone</div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(s.CustomerEmail))
|
||||
{
|
||||
<div><i class="bi bi-envelope me-1"></i>@s.CustomerEmail</div>
|
||||
}
|
||||
</td>
|
||||
<td style="max-width:280px;">
|
||||
<span class="text-truncate d-block" style="max-width:260px;"
|
||||
title="@s.JobDescription">@s.JobDescriptionSnippet</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (s.SessionType == KioskSessionType.InPerson)
|
||||
{
|
||||
<span class="badge bg-primary-subtle text-primary">
|
||||
<i class="bi bi-tablet me-1"></i>In-Person
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-purple-subtle text-purple" style="background:#ede9fe;color:#6d28d9;">
|
||||
<i class="bi bi-envelope me-1"></i>Remote
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (s.Status == KioskSessionStatus.Submitted && s.IsConverted)
|
||||
{
|
||||
<span class="badge bg-success">Converted</span>
|
||||
}
|
||||
else if (s.Status == KioskSessionStatus.Submitted)
|
||||
{
|
||||
<span class="badge bg-info text-dark">Submitted</span>
|
||||
}
|
||||
else if (s.Status == KioskSessionStatus.Active && !s.IsExpired)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">In Progress</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Expired</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (s.SmsOptIn)
|
||||
{
|
||||
<i class="bi bi-check-circle-fill text-success" title="SMS opt-in"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-dash text-muted"></i>
|
||||
}
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
@if (s.LinkedJobId.HasValue)
|
||||
{
|
||||
<a href="/Jobs/Details/@s.LinkedJobId" class="btn btn-sm btn-outline-success me-1">
|
||||
<i class="bi bi-briefcase me-1"></i>View Job
|
||||
</a>
|
||||
}
|
||||
@if (s.LinkedCustomerId.HasValue)
|
||||
{
|
||||
<a href="/Customers/Details/@s.LinkedCustomerId" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-person me-1"></i>Customer
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
Reference in New Issue
Block a user