Merge branch '8-podrska-za-boje-na-kalendaru' into 'master'
Added colours for teams Closes #8 See merge request kbr4/zsterminator!6
This commit was merged in pull request #20.
This commit is contained in:
@@ -6,7 +6,10 @@ class ReservationsController < ApplicationController
|
|||||||
# GET /reservations or /reservations.json
|
# GET /reservations or /reservations.json
|
||||||
def index
|
def index
|
||||||
@reservations = Reservation.includes(:team, :customer).where(company: @company)
|
@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
|
end
|
||||||
|
|
||||||
# GET /reservations/1 or /reservations/1.json
|
# GET /reservations/1 or /reservations/1.json
|
||||||
|
|||||||
73
app/helpers/color_helper.rb
Normal file
73
app/helpers/color_helper.rb
Normal file
@@ -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
|
||||||
@@ -18,6 +18,39 @@ export default class extends Controller {
|
|||||||
// Translation helper
|
// Translation helper
|
||||||
const t = (key) => translations[key] || key;
|
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'), {
|
const calendar = new tui.Calendar(document.getElementById('main-calendar'), {
|
||||||
defaultView: 'week',
|
defaultView: 'week',
|
||||||
usageStatistics: false,
|
usageStatistics: false,
|
||||||
@@ -64,12 +97,12 @@ export default class extends Controller {
|
|||||||
return t('delete');
|
return t('delete');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
calendars: [
|
calendars: teamCalendars.length > 0 ? teamCalendars : [
|
||||||
{
|
{
|
||||||
id: 'cal1',
|
id: 'default',
|
||||||
name: 'Work',
|
name: 'Default',
|
||||||
backgroundColor: '#00a9ff',
|
backgroundColor: '#00a9ff',
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
// Enable the built-in popup
|
// Enable the built-in popup
|
||||||
useDetailPopup: true,
|
useDetailPopup: true,
|
||||||
@@ -173,32 +206,42 @@ export default class extends Controller {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.calendar = calendar;
|
window.calendar = calendar;
|
||||||
this.getCalendardata();
|
|
||||||
|
// Create events for all reservations
|
||||||
|
this.createCalendarEvents(reservations, teamMap);
|
||||||
|
|
||||||
calendar.render();
|
calendar.render();
|
||||||
|
|
||||||
// Update the date display after rendering
|
// Update the date display after rendering
|
||||||
this.updateDateDisplay();
|
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() {
|
getCalendardata() {
|
||||||
var reservations = JSON.parse(document.querySelector("#main-calendar").dataset.reservations);
|
// This method is now replaced by initialization in connect()
|
||||||
window.reservations = reservations;
|
// and createCalendarEvents method
|
||||||
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
|
|
||||||
}
|
|
||||||
])
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get CSRF token from meta tag
|
// Helper function to get CSRF token from meta tag
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class Reservation < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ReservationSerializer < ActiveModel::Serializer
|
# Moved to app/serializers/reservation_serializer.rb
|
||||||
attributes :id, :company, :customer, :team, :start_time, :end_time
|
# class ReservationSerializer < ActiveModel::Serializer
|
||||||
end
|
# attributes :id, :company, :customer, :team, :start_time, :end_time
|
||||||
|
# end
|
||||||
|
|||||||
6
app/serializers/reservation_serializer.rb
Normal file
6
app/serializers/reservation_serializer.rb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class ReservationSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :start_time, :end_time
|
||||||
|
|
||||||
|
belongs_to :customer
|
||||||
|
belongs_to :team, serializer: TeamSerializer
|
||||||
|
end
|
||||||
9
app/serializers/team_serializer.rb
Normal file
9
app/serializers/team_serializer.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class TeamSerializer < ActiveModel::Serializer
|
||||||
|
include ColorHelper
|
||||||
|
|
||||||
|
attributes :id, :name, :color
|
||||||
|
|
||||||
|
def color
|
||||||
|
team_color(object.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user