From aa2a8b9f26202b983d81ce03b9135cc31d26c3d2 Mon Sep 17 00:00:00 2001 From: Nedim Uka Date: Thu, 24 Apr 2025 07:04:42 +0200 Subject: [PATCH] Added colours for teams --- app/controllers/reservations_controller.rb | 5 +- app/helpers/color_helper.rb | 73 ++++++++++++++++ .../controllers/main_calendar_controller.js | 87 ++++++++++++++----- app/models/reservation.rb | 7 +- app/serializers/reservation_serializer.rb | 6 ++ app/serializers/team_serializer.rb | 9 ++ 6 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 app/helpers/color_helper.rb create mode 100644 app/serializers/reservation_serializer.rb create mode 100644 app/serializers/team_serializer.rb diff --git a/app/controllers/reservations_controller.rb b/app/controllers/reservations_controller.rb index ad76429..3494c61 100644 --- a/app/controllers/reservations_controller.rb +++ b/app/controllers/reservations_controller.rb @@ -6,7 +6,10 @@ class ReservationsController < ApplicationController # GET /reservations or /reservations.json def index @reservations = Reservation.includes(:team, :customer).where(company: @company) - @reservations = ActiveModelSerializers::SerializableResource.new(@reservations).as_json + @reservations = ActiveModelSerializers::SerializableResource.new( + @reservations, + each_serializer: ReservationSerializer + ).as_json end # GET /reservations/1 or /reservations/1.json diff --git a/app/helpers/color_helper.rb b/app/helpers/color_helper.rb new file mode 100644 index 0000000..2ce5e8d --- /dev/null +++ b/app/helpers/color_helper.rb @@ -0,0 +1,73 @@ +module ColorHelper + # Generates a consistent, visually pleasing color based on an ID + # Uses the golden ratio to ensure good color distribution + def team_color(team_id) + # Use the golden ratio to create a well-distributed sequence of hues + golden_ratio_conjugate = 0.618033988749895 + + # Use the team_id as a seed for the hue + h = (team_id.to_i * golden_ratio_conjugate) % 1 + + # Convert to HSL color with fixed saturation and lightness for good UI colors + # Saturation: 65% - vibrant but not too intense + # Lightness: 55% - visible on both light and dark backgrounds + hsl_to_hex(h, 0.65, 0.55) + end + + # Returns a color object with various formats for a team + # This allows for more flexibility in how the color is used + def team_color_object(team_id) + h = (team_id.to_i * 0.618033988749895) % 1 + s = 0.65 + l = 0.55 + + # Calculate RGB values + rgb = hsl_to_rgb(h, s, l) + hex = hsl_to_hex(h, s, l) + + { + hex: hex, # #RRGGBB + rgb: "rgb(#{rgb[0]}, #{rgb[1]}, #{rgb[2]})", # rgb(r,g,b) + hsl: "hsl(#{(h*360).round}, #{(s*100).round}%, #{(l*100).round}%)", # hsl(h,s%,l%) + light_bg: hsl_to_hex(h, s, 0.9), # Lighter version for backgrounds + dark_bg: hsl_to_hex(h, s, 0.2), # Darker version for backgrounds + border: hsl_to_hex(h, s, 0.4) # Border color + } + end + + private + + # Convert HSL to Hex color + def hsl_to_hex(h, s, l) + r, g, b = hsl_to_rgb(h, s, l) + "##{r.to_s(16).rjust(2, '0')}#{g.to_s(16).rjust(2, '0')}#{b.to_s(16).rjust(2, '0')}" + end + + # Convert HSL to RGB values + def hsl_to_rgb(h, s, l) + # Convert HSL to RGB using standard algorithm + if s == 0 + r = g = b = (l * 255).round + else + q = l < 0.5 ? l * (1 + s) : l + s - l * s + p = 2 * l - q + + r = (hue_to_rgb(p, q, h + 1/3.0) * 255).round + g = (hue_to_rgb(p, q, h) * 255).round + b = (hue_to_rgb(p, q, h - 1/3.0) * 255).round + end + + [r, g, b] + end + + # Helper function for HSL to RGB conversion + def hue_to_rgb(p, q, t) + t += 1 if t < 0 + t -= 1 if t > 1 + + return p + (q - p) * 6 * t if t < 1/6.0 + return q if t < 1/2.0 + return p + (q - p) * (2/3.0 - t) * 6 if t < 2/3.0 + return p + end +end \ 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 475db88..69e73b7 100644 --- a/app/javascript/controllers/main_calendar_controller.js +++ b/app/javascript/controllers/main_calendar_controller.js @@ -18,6 +18,39 @@ export default class extends Controller { // Translation helper const t = (key) => translations[key] || key; + // Pre-process reservations to set up team calendars + const reservations = JSON.parse(document.querySelector("#main-calendar").dataset.reservations); + window.reservations = reservations; + + // Debug: Log the first reservation to inspect color format + if (reservations && reservations.length > 0) { + console.log("First reservation team color:", reservations[0].team.color); + } + + // Extract unique teams and create calendar configurations + const teamCalendars = []; + const teamMap = {}; + + // Create calendar configs for each team + reservations.forEach(reservation => { + const teamId = reservation.team.id; + if (!teamMap[teamId]) { + const calendarId = `team-${teamId}`; + teamMap[teamId] = calendarId; + + // Use color directly - it should be a hex string from the TeamSerializer + const teamColor = reservation.team.color || '#00a9ff'; + + teamCalendars.push({ + id: calendarId, + name: reservation.team.name, + backgroundColor: teamColor, + borderColor: teamColor + }); + } + }); + + // Initialize calendar with all team calendars const calendar = new tui.Calendar(document.getElementById('main-calendar'), { defaultView: 'week', usageStatistics: false, @@ -64,12 +97,12 @@ export default class extends Controller { return t('delete'); } }, - calendars: [ + calendars: teamCalendars.length > 0 ? teamCalendars : [ { - id: 'cal1', - name: 'Work', + id: 'default', + name: 'Default', backgroundColor: '#00a9ff', - }, + } ], // Enable the built-in popup useDetailPopup: true, @@ -173,32 +206,42 @@ export default class extends Controller { }); window.calendar = calendar; - this.getCalendardata(); + + // Create events for all reservations + this.createCalendarEvents(reservations, teamMap); calendar.render(); // Update the date display after rendering this.updateDateDisplay(); } + + // Create events for all reservations + createCalendarEvents(reservations, teamMap) { + // Create events with their team's calendar ID + const events = reservations.map(reservation => { + const teamId = reservation.team.id; + const calendarId = teamMap[teamId] || 'default'; + + return { + id: reservation.id, + calendarId: calendarId, + title: reservation.customer.first_name + ' ' + reservation.customer.surname + ' (' + reservation.customer.phone + ')', + category: 'time', + dueDateClass: reservation.dueDateClass, + location: '', // Empty location as requested + attendees: [reservation.team.name], // Team name as attendee + start: reservation.start_time, + end: reservation.end_time + }; + }); + + window.calendar.createEvents(events); + } getCalendardata() { - var reservations = JSON.parse(document.querySelector("#main-calendar").dataset.reservations); - window.reservations = reservations; - reservations.forEach(reservation => { - window.calendar.createEvents([ - { - id: reservation.id, - calendarId: 'cal1', - title: reservation.customer.first_name + ' ' + reservation.customer.surname + ' (' + reservation.customer.phone + ')', - category: 'time', - dueDateClass: reservation.dueDateClass, - location: '', // Empty location as requested - attendees: [reservation.team.name], // Team name as attendee - start: reservation.start_time, - end: reservation.end_time - } - ]) - }); + // This method is now replaced by initialization in connect() + // and createCalendarEvents method } // Helper function to get CSRF token from meta tag diff --git a/app/models/reservation.rb b/app/models/reservation.rb index 1a91622..50adc27 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -25,6 +25,7 @@ class Reservation < ApplicationRecord end end -class ReservationSerializer < ActiveModel::Serializer - attributes :id, :company, :customer, :team, :start_time, :end_time -end +# Moved to app/serializers/reservation_serializer.rb +# class ReservationSerializer < ActiveModel::Serializer +# attributes :id, :company, :customer, :team, :start_time, :end_time +# end diff --git a/app/serializers/reservation_serializer.rb b/app/serializers/reservation_serializer.rb new file mode 100644 index 0000000..63df8d1 --- /dev/null +++ b/app/serializers/reservation_serializer.rb @@ -0,0 +1,6 @@ +class ReservationSerializer < ActiveModel::Serializer + attributes :id, :start_time, :end_time + + belongs_to :customer + belongs_to :team, serializer: TeamSerializer +end \ No newline at end of file diff --git a/app/serializers/team_serializer.rb b/app/serializers/team_serializer.rb new file mode 100644 index 0000000..2e93c57 --- /dev/null +++ b/app/serializers/team_serializer.rb @@ -0,0 +1,9 @@ +class TeamSerializer < ActiveModel::Serializer + include ColorHelper + + attributes :id, :name, :color + + def color + team_color(object.id) + end +end \ No newline at end of file