Made UI pretty!
CI / scan_ruby (push) Failing after 9s
CI / scan_js (push) Failing after 7s
CI / lint (push) Failing after 8s

This commit is contained in:
2026-09-17 20:59:56 -06:00
parent 7c71f4a0ad
commit 87894ad620
6 changed files with 938 additions and 287 deletions
+63 -30
View File
@@ -1,38 +1,71 @@
<main>
<p><%= notice %></p>
<main class="page-shell">
<% if notice.present? %>
<p class="flash flash--notice"><%= notice %></p>
<% end %>
<h1>Vehicles</h1>
<header class="page-header">
<div>
<p class="eyebrow">Fleet dashboard</p>
<h1>Vehicles</h1>
</div>
<%= link_to "New vehicle", new_vehicle_path, class: "button button--primary" %>
</header>
<nav aria-label="Vehicle filters">
<%= link_to "Active", vehicles_path %> |
<%= link_to "Inactive", vehicles_path(status: "inactive") %> |
<%= link_to "All", vehicles_path(status: "all") %>
<nav class="filter-nav" aria-label="Vehicle filters">
<%= link_to "Active", vehicles_path, class: ["filter-pill", ("filter-pill--active" if @status.blank?)] %>
<%= link_to "Inactive", vehicles_path(status: "inactive"), class: ["filter-pill", ("filter-pill--active" if @status == "inactive")] %>
<%= link_to "All", vehicles_path(status: "all"), class: ["filter-pill", ("filter-pill--active" if @status == "all")] %>
</nav>
<p><%= link_to "New vehicle", new_vehicle_path %></p>
<% if @vehicles.any? %>
<table>
<thead>
<tr>
<th>Vehicle</th>
<th>Licence plate</th>
<th>Odometer</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% @vehicles.each do |vehicle| %>
<tr>
<td><%= link_to "#{vehicle.year} #{vehicle.make} #{vehicle.model}", vehicle %></td>
<td><%= vehicle.licence_plate %></td>
<td><%= number_with_delimiter(vehicle.current_odometer) %></td>
<td><%= vehicle.active? ? "Active" : "Inactive" %></td>
</tr>
<% end %>
</tbody>
</table>
<section class="vehicle-grid" aria-label="Vehicles">
<% @vehicles.each do |vehicle| %>
<% statuses = @vehicle_maintenance_statuses.fetch(vehicle, []) %>
<% overdue_statuses = statuses.select(&:overdue?) %>
<% upcoming_statuses = statuses.select(&:upcoming?) %>
<% card_classes = ["vehicle-card"] %>
<% card_classes << "vehicle-card--overdue" if overdue_statuses.any? %>
<% card_classes << "vehicle-card--upcoming" if overdue_statuses.empty? && upcoming_statuses.any? %>
<% card_classes << "vehicle-card--inactive" unless vehicle.active? %>
<article class="<%= card_classes.compact.join(" ") %>">
<%= link_to vehicle, class: "vehicle-card__link" do %>
<div class="vehicle-card__header">
<h2><%= vehicle.year %> <%= vehicle.make %> <%= vehicle.model %></h2>
<span class="status-badge <%= vehicle.active? ? "status-badge--active" : "status-badge--inactive" %>">
<%= vehicle.active? ? "Active" : "Inactive" %>
</span>
</div>
<dl class="vehicle-card__facts">
<div>
<dt>Plate</dt>
<dd><%= vehicle.licence_plate %></dd>
</div>
<div>
<dt>Odometer</dt>
<dd><%= number_with_delimiter(vehicle.current_odometer) %></dd>
</div>
</dl>
<% if overdue_statuses.any? %>
<div class="maintenance-summary maintenance-summary--overdue">
<strong>Overdue maintenance</strong>
<p><%= overdue_statuses.map { |status| status.schedule.maintenance_type }.to_sentence %></p>
</div>
<% elsif upcoming_statuses.any? %>
<div class="maintenance-summary maintenance-summary--upcoming">
<strong>Upcoming maintenance</strong>
<p><%= upcoming_statuses.map { |status| status.schedule.maintenance_type }.to_sentence %></p>
</div>
<% else %>
<p class="maintenance-current">No upcoming maintenance</p>
<% end %>
<% end %>
</article>
<% end %>
</section>
<% else %>
<p>No vehicles found.</p>
<p class="empty-state">No vehicles found.</p>
<% end %>
</main>
+327 -252
View File
@@ -1,90 +1,252 @@
<main>
<p><%= notice %></p>
<p><%= flash[:warning] %></p>
<% overdue_statuses = @maintenance_schedule_statuses.values.select(&:overdue?) %>
<% upcoming_statuses = @maintenance_schedule_statuses.values.select(&:upcoming?) %>
<% no_history_statuses = @maintenance_schedule_statuses.values.select(&:no_history?) %>
<% attention_statuses = overdue_statuses + upcoming_statuses %>
<p><%= link_to "Back to vehicles", vehicles_path %></p>
<main class="page-shell vehicle-show">
<% if notice.present? %>
<p class="flash flash--notice"><%= notice %></p>
<% end %>
<% if flash[:warning].present? %>
<p class="flash flash--warning"><%= flash[:warning] %></p>
<% end %>
<h1><%= @vehicle.year %> <%= @vehicle.make %> <%= @vehicle.model %></h1>
<p><%= link_to "Back to vehicles", vehicles_path, class: "back-link" %></p>
<div>
<%= link_to "Edit", edit_vehicle_path(@vehicle) %>
<% if @vehicle.active? %>
| <%= button_to "Deactivate", @vehicle, method: :delete %>
<header class="vehicle-hero">
<div>
<p class="eyebrow"><%= @vehicle.licence_plate %></p>
<h1><%= @vehicle.year %> <%= @vehicle.make %> <%= @vehicle.model %></h1>
<p><%= number_with_delimiter(@vehicle.current_odometer) %> miles</p>
</div>
</header>
<% if attention_statuses.any? %>
<section class="alert-stack" aria-label="Maintenance attention needed">
<% if overdue_statuses.any? %>
<div class="alert-card alert-card--overdue">
<h2>Overdue maintenance</h2>
<ul>
<% overdue_statuses.each do |status| %>
<li>
<strong><%= status.schedule.maintenance_type %></strong>: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</div>
<% end %>
<% if upcoming_statuses.any? %>
<div class="alert-card alert-card--upcoming">
<h2>Upcoming maintenance</h2>
<ul>
<% upcoming_statuses.each do |status| %>
<li>
<strong><%= status.schedule.maintenance_type %></strong>: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</div>
<% end %>
</section>
<% end %>
<details class="dashboard-section">
<summary>Vehicle Information</summary>
<div class="table-scroll table-scroll--compact">
<table class="details-table">
<tbody>
<tr>
<th>Make</th>
<td><%= @vehicle.make %></td>
</tr>
<tr>
<th>Model</th>
<td><%= @vehicle.model %></td>
</tr>
<tr>
<th>Year</th>
<td><%= @vehicle.year %></td>
</tr>
<tr>
<th>Color</th>
<td><%= @vehicle.color %></td>
</tr>
<tr>
<th>VIN</th>
<td><%= @vehicle.vin %></td>
</tr>
<tr>
<th>Licence plate</th>
<td><%= @vehicle.licence_plate %></td>
</tr>
<tr>
<th>Current odometer</th>
<td><%= number_with_delimiter(@vehicle.current_odometer) %></td>
</tr>
<tr>
<th>Fuel tank size</th>
<td><%= @vehicle.fuel_tank_size %></td>
</tr>
<tr>
<th>Status</th>
<td><%= @vehicle.active? ? "Active" : "Inactive" %></td>
</tr>
</tbody>
</table>
</div>
</details>
<details class="dashboard-section dashboard-section--primary" open>
<summary>Record Fuel</summary>
<%= form_with model: [@vehicle, @fuel_entry], class: "stacked-form" do |form| %>
<% if @fuel_entry.errors.any? %>
<section class="form-errors">
<h3><%= pluralize(@fuel_entry.errors.count, "error") %> prevented this fuel entry from being saved</h3>
<ul>
<% @fuel_entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</section>
<% end %>
<div class="form-grid">
<div class="field">
<%= form.label :odometer %>
<%= form.number_field :odometer, min: @vehicle.current_odometer, required: true %>
</div>
<div class="field">
<%= form.label :gallons_pumped %>
<%= form.number_field :gallons_pumped, min: 0.01, step: "0.01", required: true %>
</div>
<div class="field">
<%= form.label :price_paid %>
<%= form.number_field :price_paid, min: 0.01, step: "0.01", required: true %>
</div>
<div class="field">
<%= form.label :date %>
<%= form.date_field :date, max: Date.current, required: true %>
</div>
</div>
<div>
<%= form.submit "Record fuel", class: "button button--primary" %>
</div>
<% end %>
</div>
</details>
<section>
<h2>Vehicle Details</h2>
<details class="dashboard-section" <%= "open" if attention_statuses.any? %>>
<summary>Record Maintenance</summary>
<dl>
<dt>Make</dt>
<dd><%= @vehicle.make %></dd>
<% if @maintenance_schedules.any? %>
<%= form_with model: [@vehicle, @maintenance_entry], class: "stacked-form", data: {
controller: "maintenance-odometer-warning",
maintenance_odometer_warning_current_odometer_value: @vehicle.current_odometer,
maintenance_odometer_warning_threshold_value: 500
} do |form| %>
<% if @maintenance_entry.errors.any? %>
<section class="form-errors">
<h3><%= pluralize(@maintenance_entry.errors.count, "error") %> prevented this maintenance entry from being saved</h3>
<dt>Model</dt>
<dd><%= @vehicle.model %></dd>
<ul>
<% @maintenance_entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</section>
<% end %>
<dt>Year</dt>
<dd><%= @vehicle.year %></dd>
<div class="form-grid">
<div class="field">
<%= form.label :name, "Maintenance type" %>
<%= form.select :name, options_for_select(@maintenance_schedules.map(&:maintenance_type), @maintenance_entry.name), { prompt: "Choose maintenance type" }, required: true %>
</div>
<dt>Color</dt>
<dd><%= @vehicle.color %></dd>
<div class="field">
<%= form.label :odometer %>
<%= form.number_field :odometer, min: 0, required: true, data: {
maintenance_odometer_warning_target: "odometer",
action: "input->maintenance-odometer-warning#check change->maintenance-odometer-warning#check"
} %>
</div>
<dt>VIN</dt>
<dd><%= @vehicle.vin %></dd>
<div class="field">
<%= form.label :cost %>
<%= form.number_field :cost, min: 0, step: "0.01", required: true %>
</div>
<dt>Licence plate</dt>
<dd><%= @vehicle.licence_plate %></dd>
<div class="field">
<%= form.label :date %>
<%= form.date_field :date, max: Date.current, required: true %>
</div>
</div>
<dt>Current odometer</dt>
<dd><%= number_with_delimiter(@vehicle.current_odometer) %></dd>
<p class="field-warning" hidden data-maintenance-odometer-warning-target="warning">
Warning: this maintenance odometer is more than 500 miles behind the current vehicle odometer.
</p>
<dt>Fuel tank size</dt>
<dd><%= @vehicle.fuel_tank_size %></dd>
<div class="field">
<%= form.label :notes %>
<%= form.text_area :notes, required: true %>
</div>
<dt>Status</dt>
<dd><%= @vehicle.active? ? "Active" : "Inactive" %></dd>
</dl>
</section>
<section>
<h2>Maintenance Schedules</h2>
<p><%= link_to "Manage maintenance schedules", vehicle_maintenance_schedules_path(@vehicle) %></p>
<% overdue_statuses = @maintenance_schedule_statuses.values.select(&:overdue?) %>
<% upcoming_statuses = @maintenance_schedule_statuses.values.select(&:upcoming?) %>
<% no_history_statuses = @maintenance_schedule_statuses.values.select(&:no_history?) %>
<% if overdue_statuses.any? %>
<section>
<h3>Overdue Maintenance</h3>
<ul>
<% overdue_statuses.each do |status| %>
<li>
<%= status.schedule.maintenance_type %> is overdue: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</section>
<div>
<%= form.submit "Record maintenance", class: "button button--primary" %>
</div>
<% end %>
<% else %>
<p class="empty-state">Add a maintenance schedule before recording maintenance.</p>
<% end %>
</details>
<% if upcoming_statuses.any? %>
<section>
<h3>Upcoming Maintenance</h3>
<ul>
<% upcoming_statuses.each do |status| %>
<li>
<%= status.schedule.maintenance_type %> is upcoming: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</section>
<details class="dashboard-section">
<summary>Maintenance Schedules</summary>
<p><%= link_to "Manage maintenance schedules", vehicle_maintenance_schedules_path(@vehicle), class: "button button--secondary" %></p>
<% if attention_statuses.any? %>
<div class="section-alerts">
<% if overdue_statuses.any? %>
<section class="mini-alert mini-alert--overdue">
<h3>Overdue maintenance</h3>
<ul>
<% overdue_statuses.each do |status| %>
<li>
<%= status.schedule.maintenance_type %> is overdue: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</section>
<% end %>
<% if upcoming_statuses.any? %>
<section class="mini-alert mini-alert--upcoming">
<h3>Upcoming maintenance</h3>
<ul>
<% upcoming_statuses.each do |status| %>
<li>
<%= status.schedule.maintenance_type %> is upcoming: <%= status.reason %>.
Due at <%= number_with_delimiter(status.due_odometer) %> miles or <%= status.due_date %>.
</li>
<% end %>
</ul>
</section>
<% end %>
</div>
<% end %>
<% if no_history_statuses.any? %>
<section>
<section class="mini-alert">
<h3>No Maintenance History</h3>
<ul>
<% no_history_statuses.each do |status| %>
@@ -98,201 +260,114 @@
<% end %>
<% if @maintenance_schedules.any? %>
<table>
<thead>
<tr>
<th>Type</th>
<th>Mileage interval</th>
<th>Time interval</th>
<th>Next due odometer</th>
<th>Next due date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% @maintenance_schedules.each do |schedule| %>
<% status = @maintenance_schedule_statuses.fetch(schedule) %>
<div class="table-scroll">
<table>
<thead>
<tr>
<td><%= schedule.maintenance_type %></td>
<td><%= number_with_delimiter(schedule.mileage_interval) %> miles</td>
<td><%= schedule.time_interval %> days</td>
<td><%= number_with_delimiter(status.due_odometer) %></td>
<td><%= status.due_date %></td>
<td><%= status.status_label %></td>
<th>Type</th>
<th>Mileage interval</th>
<th>Time interval</th>
<th>Next due odometer</th>
<th>Next due date</th>
<th>Status</th>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>No maintenance schedules recorded.</p>
<% end %>
</section>
<section>
<h2>Record Fuel</h2>
<%= form_with model: [@vehicle, @fuel_entry] do |form| %>
<% if @fuel_entry.errors.any? %>
<section>
<h3><%= pluralize(@fuel_entry.errors.count, "error") %> prevented this fuel entry from being saved</h3>
<ul>
<% @fuel_entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
</thead>
<tbody>
<% @maintenance_schedules.each do |schedule| %>
<% status = @maintenance_schedule_statuses.fetch(schedule) %>
<tr>
<td><%= schedule.maintenance_type %></td>
<td><%= number_with_delimiter(schedule.mileage_interval) %> miles</td>
<td><%= schedule.time_interval %> days</td>
<td><%= number_with_delimiter(status.due_odometer) %></td>
<td><%= status.due_date %></td>
<td><span class="status-badge status-badge--<%= status.status %>"><%= status.status_label %></span></td>
</tr>
<% end %>
</ul>
</section>
<% end %>
<div>
<%= form.label :odometer %><br>
<%= form.number_field :odometer, min: @vehicle.current_odometer, required: true %>
</tbody>
</table>
</div>
<div>
<%= form.label :gallons_pumped %><br>
<%= form.number_field :gallons_pumped, min: 0.01, step: "0.01", required: true %>
</div>
<div>
<%= form.label :price_paid %><br>
<%= form.number_field :price_paid, min: 0.01, step: "0.01", required: true %>
</div>
<div>
<%= form.label :date %><br>
<%= form.date_field :date, max: Date.current, required: true %>
</div>
<div>
<%= form.submit "Record fuel" %>
</div>
<% end %>
</section>
<section>
<h2>Record Maintenance</h2>
<% if @maintenance_schedules.any? %>
<%= form_with model: [@vehicle, @maintenance_entry], data: {
controller: "maintenance-odometer-warning",
maintenance_odometer_warning_current_odometer_value: @vehicle.current_odometer,
maintenance_odometer_warning_threshold_value: 500
} do |form| %>
<% if @maintenance_entry.errors.any? %>
<section>
<h3><%= pluralize(@maintenance_entry.errors.count, "error") %> prevented this maintenance entry from being saved</h3>
<ul>
<% @maintenance_entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</section>
<% end %>
<div>
<%= form.label :name, "Maintenance type" %><br>
<%= form.select :name, options_for_select(@maintenance_schedules.map(&:maintenance_type), @maintenance_entry.name), { prompt: "Choose maintenance type" }, required: true %>
</div>
<div>
<%= form.label :odometer %><br>
<%= form.number_field :odometer, min: 0, required: true, data: {
maintenance_odometer_warning_target: "odometer",
action: "input->maintenance-odometer-warning#check change->maintenance-odometer-warning#check"
} %>
</div>
<p hidden data-maintenance-odometer-warning-target="warning">
Warning: this maintenance odometer is more than 500 miles behind the current vehicle odometer.
</p>
<div>
<%= form.label :cost %><br>
<%= form.number_field :cost, min: 0, step: "0.01", required: true %>
</div>
<div>
<%= form.label :date %><br>
<%= form.date_field :date, max: Date.current, required: true %>
</div>
<div>
<%= form.label :notes %><br>
<%= form.text_area :notes, required: true %>
</div>
<div>
<%= form.submit "Record maintenance" %>
</div>
<% end %>
<% else %>
<p>Add a maintenance schedule before recording maintenance.</p>
<p class="empty-state">No maintenance schedules recorded.</p>
<% end %>
</section>
</details>
<section>
<h2>Fuel History</h2>
<% if @fuel_entries.any? %>
<table>
<thead>
<tr>
<th>Date</th>
<th>Odometer</th>
<th>Gallons</th>
<th>Price paid</th>
<th>Updated by</th>
</tr>
</thead>
<tbody>
<% @fuel_entries.each do |entry| %>
<tr>
<td><%= entry.date %></td>
<td><%= number_with_delimiter(entry.odometer) %></td>
<td><%= entry.gallons_pumped %></td>
<td><%= number_to_currency(entry.price_paid) %></td>
<td><%= entry.updated_by_user.name %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>No fuel entries recorded.</p>
<% end %>
</section>
<section>
<h2>Maintenance History</h2>
<details class="dashboard-section">
<summary>Maintenance History</summary>
<% if @maintenance_entries.any? %>
<table>
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Odometer</th>
<th>Cost</th>
<th>Notes</th>
<th>Updated by</th>
</tr>
</thead>
<tbody>
<% @maintenance_entries.each do |entry| %>
<div class="table-scroll">
<table>
<thead>
<tr>
<td><%= entry.date %></td>
<td><%= entry.name %></td>
<td><%= number_with_delimiter(entry.odometer) %></td>
<td><%= number_to_currency(entry.cost) %></td>
<td><%= entry.notes %></td>
<td><%= entry.updated_by_user.name %></td>
<th>Date</th>
<th>Name</th>
<th>Odometer</th>
<th>Cost</th>
<th>Notes</th>
<th>Updated by</th>
</tr>
<% end %>
</tbody>
</table>
</thead>
<tbody>
<% @maintenance_entries.each do |entry| %>
<tr>
<td><%= entry.date %></td>
<td><%= entry.name %></td>
<td><%= number_with_delimiter(entry.odometer) %></td>
<td><%= number_to_currency(entry.cost) %></td>
<td><%= entry.notes %></td>
<td><%= entry.updated_by_user.name %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p>No maintenance entries recorded.</p>
<p class="empty-state">No maintenance entries recorded.</p>
<% end %>
</section>
</details>
<details class="dashboard-section">
<summary>Fuel History</summary>
<% if @fuel_entries.any? %>
<div class="table-scroll">
<table>
<thead>
<tr>
<th>Date</th>
<th>Odometer</th>
<th>Gallons</th>
<th>Price paid</th>
<th>Updated by</th>
</tr>
</thead>
<tbody>
<% @fuel_entries.each do |entry| %>
<tr>
<td><%= entry.date %></td>
<td><%= number_with_delimiter(entry.odometer) %></td>
<td><%= entry.gallons_pumped %></td>
<td><%= number_to_currency(entry.price_paid) %></td>
<td><%= entry.updated_by_user.name %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p class="empty-state">No fuel entries recorded.</p>
<% end %>
</details>
<details class="dashboard-section">
<summary>Vehicle Management</summary>
<div class="action-row">
<%= link_to "Edit", edit_vehicle_path(@vehicle), class: "button button--secondary" %>
<% if @vehicle.active? %>
<%= button_to "Deactivate", @vehicle, method: :delete, class: "button button--danger" %>
<% end %>
</div>
</details>
</main>