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 @@ -want \ No newline at end of file +want diff --git a/app/views/reservations/_form.html.erb b/app/views/reservations/_form.html.erb index 7e4e573..ee62d77 100644 --- a/app/views/reservations/_form.html.erb +++ b/app/views/reservations/_form.html.erb @@ -28,14 +28,13 @@ } %>
+
<%= form.label :phone_number, t('reservations.form.phone_number') %> <%= form.telephone_field :customer_original_phone, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full", data: { customer_search_target: "phoneField" } %>
- -
diff --git a/app/views/reservations/index.html.erb b/app/views/reservations/index.html.erb index 65317d5..b89d592 100644 --- a/app/views/reservations/index.html.erb +++ b/app/views/reservations/index.html.erb @@ -65,4 +65,4 @@ %>
- \ No newline at end of file + diff --git a/config/locales/bs.yml b/config/locales/bs.yml index e3fc467..5ea51a5 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -33,6 +33,11 @@ bs: first_name: "Ime" surname: "Prezime" birth_year: "Godina rođenja" + customer_color: "Boja klijenta" + select_color: "Odaberite boju klijenta" + green: "Zelena" + yellow: "Žuta" + red: "Crvena" team: "Tim" select_team: "Odaberite tim" start_time: "Vrijeme početka" diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a0ea99..a521798 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -77,6 +77,11 @@ en: first_name: "First name" surname: "Surname" birth_year: "Birth year" + customer_color: "Customer color" + select_color: "Select customer color" + green: "Green" + yellow: "Yellow" + red: "Red" team: "Team" select_team: "Select a team" start_time: "Start time" diff --git a/db/migrate/20250704091649_add_color_to_customers.rb b/db/migrate/20250704091649_add_color_to_customers.rb new file mode 100644 index 0000000..8b506dd --- /dev/null +++ b/db/migrate/20250704091649_add_color_to_customers.rb @@ -0,0 +1,5 @@ +class AddColorToCustomers < ActiveRecord::Migration[7.1] + def change + add_column :customers, :color, :string + end +end diff --git a/db/migrate/20250704092438_update_existing_customers_color.rb b/db/migrate/20250704092438_update_existing_customers_color.rb new file mode 100644 index 0000000..cd4e1a2 --- /dev/null +++ b/db/migrate/20250704092438_update_existing_customers_color.rb @@ -0,0 +1,9 @@ +class UpdateExistingCustomersColor < ActiveRecord::Migration[7.1] + def up + Customer.where(color: nil).update_all(color: 'green') + end + + def down + # No rollback needed - we don't want to set colors back to nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 50d4281..58fb5bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_02_18_071800) do +ActiveRecord::Schema[7.1].define(version: 2025_07_04_092438) do create_table "companies", force: :cascade do |t| t.string "name" t.string "id_number" @@ -36,6 +36,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_02_18_071800) do t.string "first_name" t.string "surname", null: false t.string "original_phone", null: false + t.string "color" t.index ["company_id"], name: "index_customers_on_company_id" t.index ["first_name", "surname", "original_phone", "company_id"], name: "index_customers_on_composite_key_and_company", unique: true t.index ["first_name", "surname", "original_phone"], name: "index_customers_on_composite_key", unique: true