Files
PowderCoatingLogix/src/PowderCoating.Web/Controllers/OnboardingProgressController.cs
T
spouliot 8de9cd04b8 Add server-side dismiss persistence and SuperAdmin onboarding progress page
Progress widget dismiss now POSTs to Dashboard/DismissProgressWidget, writing
GuidedActivationDismissedAt to the DB so the widget stays hidden across devices
and cache clears (localStorage alone wasn't enough). BuildShopProgressWidgetAsync
suppresses the widget server-side when AllDone + dismissed.

New SuperAdmin page at /OnboardingProgress shows the activation funnel across
all tenant companies: wizard status, chosen path, milestone progress bar, key
dates (first job/quote, first invoice, workflow completed, widget dismissed),
and a status badge (Not Started / In Progress / Complete / Dismissed). Nav link
added under Users & Activity in the Platform Management sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 09:23:20 -04:00

86 lines
3.6 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
using PowderCoating.Shared.Constants;
using PowderCoating.Web.ViewModels.Platform;
namespace PowderCoating.Web.Controllers;
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class OnboardingProgressController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly UserManager<ApplicationUser> _userManager;
public OnboardingProgressController(IUnitOfWork unitOfWork, UserManager<ApplicationUser> userManager)
{
_unitOfWork = unitOfWork;
_userManager = userManager;
}
/// <summary>
/// Shows onboarding and activation status for every tenant company. SuperAdmin-only.
/// Reads <c>CompanyPreferences</c> fields written by the setup wizard, guided activation,
/// and the jobs/quotes/invoices controllers to give a cross-tenant activation funnel view.
/// </summary>
public async Task<IActionResult> Index()
{
var companies = (await _unitOfWork.Companies.GetAllAsync(
ignoreQueryFilters: true,
c => c.Preferences!))
.Where(c => !c.IsDeleted)
.OrderBy(c => c.CompanyName)
.ToList();
var rows = companies.Select(c => BuildRow(c)).ToList();
return View(new OnboardingProgressIndexViewModel { Rows = rows });
}
private static OnboardingProgressRowViewModel BuildRow(Company company)
{
var prefs = company.Preferences;
var wizardDone = prefs?.SetupWizardCompleted ?? false;
// Mirror the same 6-step logic used in DashboardController.BuildShopProgressWidgetAsync
// Steps: first job/quote, status history (unknown here — omit), first invoice,
// team size (unknown here — omit), customized lookups (unknown — omit), payment defaults.
// We track the 3 date-stamped milestones we can derive from prefs alone.
int steps = 0;
if (prefs?.FirstJobCreatedAt.HasValue == true || prefs?.FirstQuoteCreatedAt.HasValue == true) steps++;
if (prefs?.FirstInvoiceCreatedAt.HasValue == true) steps++;
if (prefs?.FirstWorkflowCompletedAt.HasValue == true) steps++;
const int total = 3;
OnboardingStatus status;
if (!wizardDone)
status = OnboardingStatus.NotStarted;
else if (prefs?.FirstWorkflowCompletedAt.HasValue == true && prefs.GuidedActivationDismissedAt.HasValue)
status = OnboardingStatus.Dismissed;
else if (prefs?.FirstWorkflowCompletedAt.HasValue == true)
status = OnboardingStatus.Complete;
else if (steps > 0)
status = OnboardingStatus.InProgress;
else
status = OnboardingStatus.NotStarted;
return new OnboardingProgressRowViewModel
{
CompanyId = company.Id,
CompanyName = company.CompanyName ?? "Unknown",
WizardCompleted = wizardDone,
OnboardingPath = prefs?.OnboardingPath,
StepsCompleted = steps,
TotalSteps = total,
FirstJobCreatedAt = prefs?.FirstJobCreatedAt,
FirstQuoteCreatedAt = prefs?.FirstQuoteCreatedAt,
FirstInvoiceCreatedAt = prefs?.FirstInvoiceCreatedAt,
FirstWorkflowCompletedAt = prefs?.FirstWorkflowCompletedAt,
GuidedActivationDismissedAt = prefs?.GuidedActivationDismissedAt,
Status = status
};
}
}