328b195127
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>
267 lines
15 KiB
Plaintext
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>
|
|
}
|