From 541b181c875f52efd0f90549c531f420575c2dd1 Mon Sep 17 00:00:00 2001
From: Nedim Uka
Date: Wed, 23 Jul 2025 15:35:19 +0200
Subject: [PATCH] 12 Added notes and colours to customers
---
app/assets/stylesheets/calendar.css | 124 +++++++++++++++++-
app/controllers/customers_controller.rb | 11 +-
app/controllers/reservations_controller.rb | 8 +-
.../controllers/customer_search_controller.js | 8 +-
.../controllers/main_calendar_controller.js | 11 +-
app/models/customer.rb | 47 +++++++
app/serializers/reservation_serializer.rb | 8 +-
app/views/customers/_customer.html.erb | 8 ++
app/views/customers/_form.html.erb | 12 ++
app/views/layouts/calendar.html.erb | 111 +++++++++++++++-
app/views/reservations/_form.html.erb | 5 +-
app/views/reservations/index.html.erb | 2 +-
config/locales/bs.yml | 5 +
config/locales/en.yml | 5 +
.../20250704091649_add_color_to_customers.rb | 5 +
...4092438_update_existing_customers_color.rb | 9 ++
db/schema.rb | 3 +-
17 files changed, 362 insertions(+), 20 deletions(-)
create mode 100644 db/migrate/20250704091649_add_color_to_customers.rb
create mode 100644 db/migrate/20250704092438_update_existing_customers_color.rb
diff --git a/app/assets/stylesheets/calendar.css b/app/assets/stylesheets/calendar.css
index a841bf2..c15d841 100644
--- a/app/assets/stylesheets/calendar.css
+++ b/app/assets/stylesheets/calendar.css
@@ -27,4 +27,126 @@
.calendar-time-slot {
min-height: 50px; /* Adjust as needed */
-}
\ No newline at end of file
+}
+
+/* ToastUI Calendar Event Styling */
+.toastui-calendar-events {
+ margin-right: 8px;
+ text-wrap: auto;
+}
+
+.toastui-calendar-event-time {
+ position: relative;
+ overflow: visible !important;
+ min-height: 24px !important;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.toastui-calendar-event-time:hover {
+ transform: scale(1.02);
+ z-index: 1000 !important;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ border-radius: 4px !important;
+}
+
+.toastui-calendar-event-time-content {
+ height: 100% !important;
+ overflow: visible !important;
+ padding: 2px 4px !important;
+ text-wrap: auto;
+ word-wrap: break-word;
+ line-height: 1.1 !important;
+}
+
+.toastui-calendar-template-time {
+ font-size: 10px !important;
+ line-height: 1.1 !important;
+ text-wrap: auto;
+ word-wrap: break-word;
+ white-space: normal !important;
+ overflow: visible !important;
+ display: block !important;
+}
+
+.toastui-calendar-template-time strong {
+ font-size: 10px !important;
+ font-weight: 600;
+ display: block;
+ margin-bottom: 1px;
+}
+
+.toastui-calendar-template-time span {
+ font-size: 9px !important;
+ line-height: 1.0 !important;
+ text-wrap: auto;
+ word-wrap: break-word;
+ white-space: normal !important;
+ display: block !important;
+}
+
+/* Hover tooltip effect */
+.toastui-calendar-event-time:hover::after {
+ content: "Click to edit reservation";
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: rgba(0, 0, 0, 0.9);
+ color: white;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-size: 11px;
+ white-space: nowrap;
+ z-index: 2000;
+ pointer-events: none;
+ opacity: 0;
+ animation: tooltipFadeIn 0.3s ease forwards;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+}
+
+.toastui-calendar-event-time:hover::before {
+ content: '';
+ position: absolute;
+ bottom: calc(100% - 6px);
+ left: 50%;
+ transform: translateX(-50%);
+ border: 6px solid transparent;
+ border-top-color: rgba(0, 0, 0, 0.9);
+ z-index: 2000;
+ pointer-events: none;
+ opacity: 0;
+ animation: tooltipFadeIn 0.3s ease forwards;
+}
+
+@keyframes tooltipFadeIn {
+ to {
+ opacity: 1;
+ }
+}
+
+/* Improve visibility for small events */
+.toastui-calendar-event-time[style*="height: calc(1%"] .toastui-calendar-template-time strong,
+.toastui-calendar-event-time[style*="height: calc(2%"] .toastui-calendar-template-time strong {
+ display: none; /* Hide time for very small events */
+}
+
+.toastui-calendar-event-time[style*="height: calc(1%"] .toastui-calendar-template-time span,
+.toastui-calendar-event-time[style*="height: calc(2%"] .toastui-calendar-template-time span {
+ font-size: 8px !important;
+ line-height: 1.0 !important;
+}
+
+/* Ensure minimum visibility */
+.toastui-calendar-event-time {
+ min-width: 60px !important;
+}
+
+/* Better text contrast */
+.toastui-calendar-event-time[style*="background-color: rgb(66, 109, 215)"] {
+ color: white !important;
+}
+
+.toastui-calendar-event-time[style*="background-color: rgb(66, 109, 215)"] .toastui-calendar-template-time {
+ color: white !important;
+}
diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb
index f98a146..81a006c 100644
--- a/app/controllers/customers_controller.rb
+++ b/app/controllers/customers_controller.rb
@@ -64,10 +64,15 @@ class CustomersController < ApplicationController
).limit(10)
render json: @customers.map { |c|
+ label = "#{c.full_name} (#{c.original_phone})"
+ label += " - #{c.notes}" if c.notes.present?
{
id: "#{c.first_name}_#{c.surname}_#{c.original_phone}",
- label: "#{c.full_name} (#{c.original_phone})",
- birthyear: c.birthyear
+ label: label,
+ birthyear: c.birthyear,
+ color: c.color || 'green',
+ color_hex: c.color_hex,
+ notes: c.notes
}
}
end
@@ -86,6 +91,6 @@ class CustomersController < ApplicationController
# Only allow a list of trusted parameters through.
def customer_params
- params.require(:customer).permit(:first_name, :surname, :phone, :notes, :email, :birthyear)
+ params.require(:customer).permit(:first_name, :surname, :phone, :notes, :email, :birthyear, :color)
end
end
diff --git a/app/controllers/reservations_controller.rb b/app/controllers/reservations_controller.rb
index 259bab8..ba47cdf 100644
--- a/app/controllers/reservations_controller.rb
+++ b/app/controllers/reservations_controller.rb
@@ -41,7 +41,10 @@ class ReservationsController < ApplicationController
# POST /reservations or /reservations.json
def create
@reservation = @company.reservations.new(
- reservation_params.except(:customer_id, :customer_birth_year)
+ reservation_params.except(
+ :customer_id, :customer_first_name, :customer_surname,
+ :customer_original_phone, :customer_birth_year, :customer_composite_key
+ )
)
# Find or create customer based on submitted attributes
@@ -68,7 +71,7 @@ class ReservationsController < ApplicationController
customer_attrs = build_customer_attributes # Use existing helper
reservation_attrs = reservation_params.except(
:customer_id, :customer_first_name, :customer_surname,
- :customer_original_phone, :customer_birth_year
+ :customer_original_phone, :customer_birth_year, :customer_composite_key
)
# Find the customer identified by the submitted name/phone
@@ -144,6 +147,7 @@ class ReservationsController < ApplicationController
:customer_surname,
:customer_original_phone,
:customer_birth_year,
+ :customer_composite_key,
:customer_id # Allow this if select still sends it sometimes
)
end
diff --git a/app/javascript/controllers/customer_search_controller.js b/app/javascript/controllers/customer_search_controller.js
index 9aba290..ecc2c01 100644
--- a/app/javascript/controllers/customer_search_controller.js
+++ b/app/javascript/controllers/customer_search_controller.js
@@ -66,7 +66,8 @@ export default class extends Controller {
return 'No customers found. Fill in the details below.
';
},
option: function(item) {
- return `${item.label}
`;
+ const colorStyle = item.color_hex ? `background-color: ${item.color_hex}20; border-left: 4px solid ${item.color_hex};` : '';
+ return `${item.label}
`;
}
},
@@ -98,6 +99,9 @@ export default class extends Controller {
if (initialValue) {
this.newCustomerFieldsTarget.classList.add('hidden');
this.customerSelected(initialValue);
+ } else {
+ // Show new customer fields if no existing customer
+ this.showNewCustomerFields();
}
}
@@ -148,4 +152,4 @@ export default class extends Controller {
this.selectInstance.destroy();
}
}
-}
\ No newline at end of file
+}
diff --git a/app/javascript/controllers/main_calendar_controller.js b/app/javascript/controllers/main_calendar_controller.js
index b063d89..97f2f80 100644
--- a/app/javascript/controllers/main_calendar_controller.js
+++ b/app/javascript/controllers/main_calendar_controller.js
@@ -89,7 +89,8 @@ export default class extends Controller {
return ''; // Empty location as requested
},
popupDetailAttendees(eventObj) {
- return eventObj.attendees[0]; // Show team name
+ const teamName = eventObj.attendees[0]; // Show team name
+ return teamName;
},
popupDetailState(eventObj) {
return '';
@@ -148,11 +149,13 @@ export default class extends Controller {
const startTime = new Date(reservation.start_time);
const endTime = new Date(reservation.end_time);
- // Create the event
+ // Create the event with customer name only
+ const customerName = reservation.customer ? `${reservation.customer.first_name} ${reservation.customer.surname}` : '';
+
const event = {
id: `reservation-${reservation.id}`,
calendarId: calendarId,
- title: reservation.customer ? `${reservation.customer.first_name} ${reservation.customer.surname}` : '',
+ title: customerName,
start: startTime,
end: endTime,
category: 'time',
@@ -276,4 +279,4 @@ export default class extends Controller {
// Update calendar display
window.calendar.render();
}
-}
\ No newline at end of file
+}
diff --git a/app/models/customer.rb b/app/models/customer.rb
index 8dbd71a..07905d5 100644
--- a/app/models/customer.rb
+++ b/app/models/customer.rb
@@ -2,6 +2,8 @@ class Customer < ApplicationRecord
# Use Rails 7.1's native composite primary key
self.primary_key = %i[first_name surname original_phone]
+ attribute :color, :string, default: 'green'
+
belongs_to :company
validates :first_name, presence: true
@@ -21,20 +23,65 @@ class Customer < ApplicationRecord
less_than_or_equal_to: -> { Time.current.year }
}, allow_nil: true
+ validates :color, inclusion: { in: %w[green yellow red] }, allow_nil: true
+
before_validation :set_original_phone, on: :create
+ before_validation :set_default_color, on: :create
def full_name
[first_name, surname].compact_blank.join(' ')
end
+ def green?
+ color == 'green'
+ end
+
+ def yellow?
+ color == 'yellow'
+ end
+
+ def red?
+ color == 'red'
+ end
+
# Add method for URL generation
def to_param
[first_name, surname, original_phone].join('_')
end
+ def color_hex
+ case color || 'green'
+ when 'green'
+ '#22c55e'
+ when 'yellow'
+ '#eab308'
+ when 'red'
+ '#ef4444'
+ else
+ '#22c55e'
+ end
+ end
+
+ def color_emoji
+ case color || 'green'
+ when 'green'
+ '🟩'
+ when 'yellow'
+ '🟨'
+ when 'red'
+ '🟥'
+ else
+ '🟩'
+ end
+ end
+
private
def set_original_phone
self.original_phone = phone if original_phone.blank?
end
+
+ def set_default_color
+ self.color = 'green' if color.blank?
+ end
end
diff --git a/app/serializers/reservation_serializer.rb b/app/serializers/reservation_serializer.rb
index 63df8d1..18b9ccc 100644
--- a/app/serializers/reservation_serializer.rb
+++ b/app/serializers/reservation_serializer.rb
@@ -1,6 +1,10 @@
class ReservationSerializer < ActiveModel::Serializer
- attributes :id, :start_time, :end_time
+ attributes :id, :start_time, :end_time, :customer_color_emoji
belongs_to :customer
belongs_to :team, serializer: TeamSerializer
-end
\ No newline at end of file
+
+ def customer_color_emoji
+ object.customer&.color_emoji || ''
+ end
+end
diff --git a/app/views/customers/_customer.html.erb b/app/views/customers/_customer.html.erb
index a11b2e6..f90f1d6 100644
--- a/app/views/customers/_customer.html.erb
+++ b/app/views/customers/_customer.html.erb
@@ -19,6 +19,14 @@
<%= customer.notes %>
+
+ Color:
+
+ <%= customer.color_emoji %> <%= (customer.color || 'green').humanize %>
+
+
+
Email:
<%= customer.email %>
diff --git a/app/views/customers/_form.html.erb b/app/views/customers/_form.html.erb
index e2bb48b..2d6159e 100644
--- a/app/views/customers/_form.html.erb
+++ b/app/views/customers/_form.html.erb
@@ -31,6 +31,18 @@
<%= form.text_area :notes, rows: 4, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
+
+ <%= form.label :color %>
+ <%= form.select :color,
+ options_for_select([
+ ['Green', 'green'],
+ ['Yellow', 'yellow'],
+ ['Red', 'red']
+ ], customer.color),
+ { prompt: 'Select color' },
+ { class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" } %>
+
+
<%= form.label :email %>
<%= form.text_field :email, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
diff --git a/app/views/layouts/calendar.html.erb b/app/views/layouts/calendar.html.erb
index e398ca6..42bb7dc 100644
--- a/app/views/layouts/calendar.html.erb
+++ b/app/views/layouts/calendar.html.erb
@@ -11,7 +11,6 @@
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
-
<%= javascript_importmap_tags %>
@@ -60,4 +167,4 @@