Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Appointments/Create.cshtml
T
spouliot 328b195127 Design consistency audit fixes: alerts, cards, dark mode, utilities
Alert sweep (113 alerts, 79 files):
  All persistent static banners now carry alert-permanent so the
  layout's 5-second auto-dismiss cannot swallow guidance, warnings,
  or validation errors. Transient dismissible toasts left untouched.

CSS fixes (site.css):
  .card.shadow-sm      — strips rogue border from ~40 drifted cards
  .card-header.bg-white — rebinds to var(--bs-body-bg) so card
                          headers follow dark/light theme correctly
  Typography utilities  — .text-2xs (.68rem), .text-xs (.73rem)
  Token color classes   — .text-ember, .text-ok, .text-bad,
                          .text-warn, .text-cool, .bg-paper-2
  Layout utilities      — .mw-xs/sm/md/lg replace inline max-width
  Comment              — documents text-ember vs text-primary intent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 18:05:29 -04:00

267 lines
15 KiB
Plaintext

@model PowderCoating.Application.DTOs.Appointment.CreateAppointmentDto
@{
ViewData["Title"] = "New Appointment";
ViewData["PageIcon"] = "bi-calendar-plus";
ViewData["PageHelpTitle"] = "New Appointment";
ViewData["PageHelpContent"] = "Create an appointment to schedule a customer visit, drop-off, pick-up, or consultation. Select the Type first — the Linked Job field appears once a type is chosen. Reminder notifications fire before the scheduled start time.";
}
<div class="d-flex justify-content-end align-items-center mb-4">
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back to List
</a>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-body">
<form asp-action="Create" method="post">
@if (!ViewData.ModelState.IsValid)
{
<div class="alert alert-danger alert-permanent">
<h6 class="alert-heading"><i class="bi bi-exclamation-triangle me-2"></i>Please correct the following errors:</h6>
<partial name="_ValidationSummary" />
</div>
}
<!-- Title -->
<div class="mb-3">
<label asp-for="Title" class="form-label">Title <span class="text-danger">*</span></label>
<input asp-for="Title" class="form-control" placeholder="e.g., John's Rims - Drop Off" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<!-- Description -->
<div class="mb-3">
<label asp-for="Description" class="form-label">Description</label>
<textarea asp-for="Description" class="form-control" rows="3" placeholder="Additional details about the appointment..."></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="row">
<!-- Customer -->
<div class="col-md-6 mb-3">
<label asp-for="CustomerId" class="form-label">Customer</label>
<select asp-for="CustomerId" class="form-select" asp-items="ViewBag.Customers">
<option value="">-- Select Customer (Optional) --</option>
</select>
<span asp-validation-for="CustomerId" class="text-danger"></span>
</div>
<!-- Appointment Type -->
<div class="col-md-6 mb-3">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="AppointmentTypeId" class="form-label mb-0">Type <span class="text-danger">*</span></label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Appointment Type"
data-bs-content="Drop-Off: customer brings items in. Pick-Up: customer collects completed work. Consultation/Quote: meeting to discuss pricing. Job Work: block time for a specific job. The Linked Job field appears after you select a type.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<select asp-for="AppointmentTypeId" class="form-select" asp-items="ViewBag.AppointmentTypes" id="appointmentType">
<option value="">-- Select Type --</option>
</select>
<span asp-validation-for="AppointmentTypeId" class="text-danger"></span>
</div>
</div>
<!-- Job (conditional) -->
<div class="mb-3" id="jobField" style="display: none;">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="JobId" class="form-label mb-0">Linked Job <span class="text-danger" id="jobRequired">*</span></label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Linked Job"
data-bs-content="Connect this appointment to an active job so it appears on the job timeline. Useful for Drop-Offs, Pick-Ups, and scheduled job work. Optional for consultations and internal meetings.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<select asp-for="JobId" class="form-select" asp-items="ViewBag.Jobs">
<option value="">-- Select Job (Optional) --</option>
</select>
<span asp-validation-for="JobId" class="text-danger"></span>
<small class="text-muted">Link this appointment to an existing job.</small>
</div>
<!-- All Day Checkbox -->
<div class="mb-3 form-check">
<input asp-for="IsAllDay" class="form-check-input" id="isAllDay" />
<label asp-for="IsAllDay" class="form-check-label">
All Day Event
</label>
</div>
<div class="row">
<!-- Start Date/Time -->
<div class="col-md-6 mb-3">
<label asp-for="ScheduledStartTime" class="form-label">Start <span class="text-danger">*</span></label>
<input asp-for="ScheduledStartTime" type="datetime-local" class="form-control" id="startTime" />
<span asp-validation-for="ScheduledStartTime" class="text-danger"></span>
</div>
<!-- End Date/Time -->
<div class="col-md-6 mb-3">
<label asp-for="ScheduledEndTime" class="form-label">End <span class="text-danger">*</span></label>
<input asp-for="ScheduledEndTime" type="datetime-local" class="form-control" id="endTime" />
<span asp-validation-for="ScheduledEndTime" class="text-danger"></span>
</div>
</div>
<div class="row">
<!-- Assigned Worker -->
<div class="col-md-6 mb-3">
<label asp-for="AssignedUserId" class="form-label">Assign To Worker</label>
<select asp-for="AssignedUserId" class="form-select" asp-items="ViewBag.Workers">
<option value="">-- No Assignment --</option>
</select>
<span asp-validation-for="AssignedUserId" class="text-danger"></span>
</div>
<!-- Location -->
<div class="col-md-6 mb-3">
<label asp-for="Location" class="form-label">Location</label>
<input asp-for="Location" class="form-control" placeholder="e.g., Main Office, Loading Dock" />
<span asp-validation-for="Location" class="text-danger"></span>
</div>
</div>
<!-- Internal Notes -->
<div class="mb-3">
<label asp-for="Notes" class="form-label">Internal Notes</label>
<textarea asp-for="Notes" class="form-control" rows="2" placeholder="Notes for staff (not visible to customer)..."></textarea>
<span asp-validation-for="Notes" class="text-danger"></span>
</div>
<!-- Reminder Settings -->
<div class="card mb-3">
<div class="card-header">
<div class="d-flex align-items-center gap-2">
<h6 class="mb-0"><i class="bi bi-bell me-2"></i>Reminder Settings</h6>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Reminder Settings"
data-bs-content="Enable a reminder to receive an in-app notification before the appointment. Set how many minutes in advance — e.g., 30 for a brief heads-up, 1440 for a full day before. Reminders are per-appointment and do not send external emails or SMS.">
<i class="bi bi-question-circle"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="mb-3 form-check">
<input asp-for="IsReminderEnabled" class="form-check-input" id="reminderEnabled" checked />
<label asp-for="IsReminderEnabled" class="form-check-label">
Send reminder notification
</label>
</div>
<div class="mb-0" id="reminderTime">
<label asp-for="ReminderMinutesBefore" class="form-label">Remind me</label>
<div class="input-group">
<input asp-for="ReminderMinutesBefore" type="number" class="form-control" min="5" max="1440" value="30" />
<span class="input-group-text">minutes before</span>
</div>
<span asp-validation-for="ReminderMinutesBefore" class="text-danger"></span>
</div>
</div>
</div>
<!-- Actions -->
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Create Appointment
</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
<!-- Sidebar - Help -->
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="bi bi-info-circle me-2"></i>Tips</h6>
</div>
<div class="card-body">
<h6>Appointment Types:</h6>
<ul class="small mb-3">
<li><strong>Customer Drop-Off:</strong> Customer bringing items to the shop</li>
<li><strong>Customer Pick-Up:</strong> Customer collecting completed items</li>
<li><strong>Consultation/Quote:</strong> Meeting to discuss pricing and requirements</li>
<li><strong>Scheduled Job Work:</strong> Blocking time for working on a specific job</li>
</ul>
<h6>Reminders:</h6>
<p class="small mb-0">Set a reminder to receive a notification before the appointment starts. Useful for preparing materials or coordinating with staff.</p>
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>
// Show/hide job field based on appointment type
document.getElementById('appointmentType').addEventListener('change', function() {
const jobField = document.getElementById('jobField');
const jobRequired = document.getElementById('jobRequired');
// You can add logic here to check if selected type requires job link
// For now, we'll show it for all types but mark required only for JOB_WORK
if (this.value) {
jobField.style.display = 'block';
} else {
jobField.style.display = 'none';
}
});
// Toggle reminder time visibility
document.getElementById('reminderEnabled').addEventListener('change', function() {
const reminderTime = document.getElementById('reminderTime');
reminderTime.style.display = this.checked ? 'block' : 'none';
});
// Set default start time to tomorrow at 9 AM if empty
const startTimeInput = document.getElementById('startTime');
const endTimeInput = document.getElementById('endTime');
if (!startTimeInput.value) {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0, 0);
startTimeInput.value = tomorrow.toISOString().slice(0, 16);
const endTime = new Date(tomorrow);
endTime.setHours(10, 0, 0, 0);
endTimeInput.value = endTime.toISOString().slice(0, 16);
}
// Auto-update end time when start time changes
startTimeInput.addEventListener('change', function() {
const isAllDay = document.getElementById('isAllDay').checked;
const newEndTime = new Date(this.value);
if (isAllDay) {
// For all-day events, set end date to same as start date
endTimeInput.value = this.value;
} else {
// For timed events, set end time to 1 hour after start time
newEndTime.setHours(newEndTime.getHours() + 1);
endTimeInput.value = newEndTime.toISOString().slice(0, 16);
}
});
// Hide time inputs when "All Day" is checked
document.getElementById('isAllDay').addEventListener('change', function() {
const timeInputs = document.querySelectorAll('#startTime, #endTime');
timeInputs.forEach(input => {
if (this.checked) {
input.type = 'date';
} else {
input.type = 'datetime-local';
}
});
});
</script>
}