From 86533981bcb236471ddbcd551f687e97655253b5 Mon Sep 17 00:00:00 2001
From: Rick Barrette
Date: Fri, 7 Aug 2026 21:20:46 -0400
Subject: [PATCH] Refactor appointment model and views to use a single date
field; update tooltip and calendar display for improved functionality
---
.../customer_appointments_controller.rb | 6 +--
app/models/customer_appointment.rb | 22 ++++----
.../customer_appointments/_form.html.erb | 5 +-
.../customer_appointments/_tooltip.html.erb | 2 +-
app/views/customer_appointments/show.html.erb | 49 +----------------
.../{stylesheets => javascripts}/calendar.js | 0
assets/stylesheets/customer_appointments.css | 53 +++++++++++++++++--
.../001_create_customer_appointments.rb | 5 +-
8 files changed, 66 insertions(+), 76 deletions(-)
rename assets/{stylesheets => javascripts}/calendar.js (100%)
diff --git a/app/controllers/customer_appointments_controller.rb b/app/controllers/customer_appointments_controller.rb
index acb80cf..fe31c79 100644
--- a/app/controllers/customer_appointments_controller.rb
+++ b/app/controllers/customer_appointments_controller.rb
@@ -41,8 +41,7 @@ class CustomerAppointmentsController < ApplicationController
def new
@appointment = CustomerAppointment.new
@appointment.project = @project
- @appointment.start_date = (params[:start_date].to_date + 9.hours) if params[:start_date].present?
- @appointment.end_date = @appointment.start_date + 1.hour if @appointment.start_date.present?
+ @appointment.date = (params[:start_date].to_date + 9.hours) if params[:start_date].present?
end
def create
@@ -89,8 +88,7 @@ class CustomerAppointmentsController < ApplicationController
:description,
:status,
:user_id,
- :start_date,
- :end_date,
+ :date,
:estimated_hours,
:estimate_id,
:duration
diff --git a/app/models/customer_appointment.rb b/app/models/customer_appointment.rb
index 908ec76..3209c6f 100644
--- a/app/models/customer_appointment.rb
+++ b/app/models/customer_appointment.rb
@@ -17,8 +17,7 @@ class CustomerAppointment < ActiveRecord::Base
belongs_to :estimate, optional: true
validates :subject, presence: true
- validates :start_date, presence: true
- validate :date_order
+ validates :date, presence: true
STATUSES = %w[
Scheduled
@@ -29,9 +28,15 @@ class CustomerAppointment < ActiveRecord::Base
Cancelled
]
+ # Cast datetime to Date for Redmine Calendar compatibility
+ def start_date
+ date&.to_date
+ end
+
def due_date
- return end_date&.to_date if end_date.present?
- return start_date&.to_date
+ return nil unless date
+ # Accounts for multi-day duration spanning across dates
+ start_date + ([duration.to_i, 1].max - 1).days
end
def self.visible(user)
@@ -40,15 +45,6 @@ class CustomerAppointment < ActiveRecord::Base
private
- def date_order
- return if end_date.blank?
- return if start_date.blank?
-
- if end_date < start_date
- errors.add( :end_date, "cannot be before start date")
- end
- end
-
def log(msg)
Rails.logger.info "[Event] #{msg}"
end
diff --git a/app/views/customer_appointments/_form.html.erb b/app/views/customer_appointments/_form.html.erb
index 0bfc63f..5fb490f 100644
--- a/app/views/customer_appointments/_form.html.erb
+++ b/app/views/customer_appointments/_form.html.erb
@@ -24,10 +24,9 @@
<%= f.collection_select :user_id, User.active.sorted, :id, :name, { include_blank: true }, { label: false } %>
- <%= f.datetime_local_field :start_date %>
- <%= f.date_field :end_date, size: 10 %>
+ <%= f.datetime_local_field :date %>
- <%= f.number_field :estimated_hours, step: 0.1, size: 6 %>
+ <%= f.number_field :estimated_hours, step: 1, size: 6 %>
<%= f.number_field :duration, step: 1, size: 6, label: :label_duration %>
<%= f.select :status, CustomerAppointment::STATUSES, label: :field_status %>
diff --git a/app/views/customer_appointments/_tooltip.html.erb b/app/views/customer_appointments/_tooltip.html.erb
index 94d897d..b77350a 100644
--- a/app/views/customer_appointments/_tooltip.html.erb
+++ b/app/views/customer_appointments/_tooltip.html.erb
@@ -57,7 +57,7 @@
| <%= l(:label_end_date) %> |
- <%= format_date(appointment.end_date || appointment.start_date) %> |
+ <%= format_date(appointment.due_date ) %> |
diff --git a/app/views/customer_appointments/show.html.erb b/app/views/customer_appointments/show.html.erb
index 67de750..bdcffec 100644
--- a/app/views/customer_appointments/show.html.erb
+++ b/app/views/customer_appointments/show.html.erb
@@ -15,57 +15,12 @@
(<%= format_date(@appointment.start_date) %>
- <% if @appointment.end_date && @appointment.start_date != @appointment.end_date %>
- - <%= format_date(@appointment.end_date) %>
+ <% if @appointment.duration > 1 %>
+ - <%= format_date(@appointment.due_date) %>
<% end %>)
-
-
diff --git a/assets/stylesheets/calendar.js b/assets/javascripts/calendar.js
similarity index 100%
rename from assets/stylesheets/calendar.js
rename to assets/javascripts/calendar.js
diff --git a/assets/stylesheets/customer_appointments.css b/assets/stylesheets/customer_appointments.css
index fb81f40..2d5a74b 100644
--- a/assets/stylesheets/customer_appointments.css
+++ b/assets/stylesheets/customer_appointments.css
@@ -23,8 +23,8 @@ table.cal td {
background: #edf4ff;
border: 1px solid #c5d8f3;
border-radius: 3px;
- font-size: 10px;
- line-height: 1.2;
+ font-size: 0.9em; /* Matches Redmine calendar default text size */
+ line-height: 1.3;
overflow: visible !important; /* Allows tooltip popover to extend outside */
width: 100%;
max-width: 100%;
@@ -100,7 +100,7 @@ table.cal td {
.appointment-hours {
flex-shrink: 0;
color: #444;
- font-size: 9px;
+ font-size: 0.85em; /* Slightly smaller than main text to keep layout balanced */
font-weight: bold;
margin-left: auto;
}
@@ -108,11 +108,11 @@ table.cal td {
/* Option B: Multi-line wrapping for details */
.appointment-detail {
color: #555;
- font-size: 9px;
+ font-size: 0.9em; /* Matches calendar item body text */
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
- margin-top: 1px;
+ margin-top: 2px;
min-width: 0;
}
@@ -203,4 +203,47 @@ table.cal td {
.appointment-block .tip .buttons {
margin: 0;
text-align: right;
+}
+
+.calendar-badge {
+ display: inline-block;
+ width: 38px;
+ border: 1px solid #b8c0c8;
+ border-radius: 4px;
+ overflow: hidden;
+ text-align: center;
+ vertical-align: middle;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin-right: 8px;
+ line-height: 1;
+ background: #fff;
+}
+
+.calendar-badge .month {
+ background-color: #c22b2b; /* Redmine primary red tone */
+ color: #ffffff;
+ font-size: 9px;
+ font-weight: bold;
+ padding: 3px 0 2px 0;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.calendar-badge .day {
+ color: #333333;
+ font-size: 16px;
+ font-weight: bold;
+ padding: 4px 0 3px 0;
+}
+
+.appointment-title {
+ vertical-align: middle;
+}
+
+.appointment-dates {
+ font-size: 70%;
+ color: #666;
+ margin-left: 8px;
+ font-weight: normal;
+ vertical-align: middle;
}
\ No newline at end of file
diff --git a/db/migrate/001_create_customer_appointments.rb b/db/migrate/001_create_customer_appointments.rb
index d556171..c8300f3 100644
--- a/db/migrate/001_create_customer_appointments.rb
+++ b/db/migrate/001_create_customer_appointments.rb
@@ -15,8 +15,7 @@ class CreateCustomerAppointments < ActiveRecord::Migration[7.0]
t.text :subject, null: false
t.text :description, null: false
- t.datetime :start_date, null: false
- t.datetime :end_date
+ t.datetime :date, null: false
t.integer :duration, default: 1
t.integer :estimated_hours, default: 1
@@ -31,7 +30,7 @@ class CreateCustomerAppointments < ActiveRecord::Migration[7.0]
end
- add_index :customer_appointments, :start_date
+ add_index :customer_appointments, :date
end
end
\ No newline at end of file