Module: Crm::TimeOffsHelper
- Includes:
- TimeOffPlannedWorkDayHelper
- Defined in:
- app/helpers/crm/time_offs_helper.rb
Overview
View helpers for the CRM time offs area: manager dashboard grids and
badges, vacation summary charts, and the time off type/policy form
options used by +Crm::TimeOffsController+ and related views.
Constant Summary collapse
- MANAGER_GRID_LOCATIONS =
Icon + label for where the employee's latest CRM activity came from,
classified against IpDetector's fixed office blocks. { us_office: { icon: 'building', label: 'LZ office' }, ca_office: { icon: 'building', label: 'CA office' }, in_office: { icon: 'building', label: 'India office' }, remote: { icon: 'house-laptop', label: 'Remote' } }.freeze
- MANAGER_DEPARTMENT_ICON_BY_NAME =
Canonical department label (lowercase) -> Font Awesome icon name for fa_icon (sharp regular).
Used for exact match first, then substring scan (see MANAGER_DEPARTMENT_ICON_FRAGMENTS).
Includes administration / admin / no department; add new departments here only. { 'it' => 'building', 'information technology' => 'building', 'engineering' => 'screwdriver-wrench', 'support' => 'headset', 'customer support' => 'headset', 'sales' => 'handshake', 'marketing' => 'bullhorn', 'finance' => 'coins', 'accounting' => 'file-invoice-dollar', 'hr' => 'people-group', 'human resources' => 'people-group', 'operations' => 'gears', 'warehouse' => 'warehouse', 'shipping' => 'truck', 'logistics' => 'truck-fast', 'legal' => 'scale-balanced', 'executive' => 'briefcase', 'management' => 'user-tie', 'tech 24x7' => 'clock', 'production' => 'industry', 'quality' => 'clipboard-check', 'purchasing' => 'cart-shopping', 'buying' => 'cart-shopping', 'administration' => 'shield-halved', 'admin' => 'shield-halved', 'no department' => 'users' }.freeze
- MANAGER_DEPARTMENT_ICON_FRAGMENT_EXCLUDE_KEYS =
Keys excluded from substring matching: +include?+ would false-positive (e.g. "digital" for "it").
Those labels are still handled by MANAGER_DEPARTMENT_ICON_BY_NAME (exact) or /\bit\b/ / /\bhr\b/ below. %w[it hr].freeze
- MANAGER_DEPARTMENT_ICON_FRAGMENTS =
Derived from MANAGER_DEPARTMENT_ICON_BY_NAME: longest keys first so "customer support" beats "support".
MANAGER_DEPARTMENT_ICON_BY_NAME .reject { |k, _| MANAGER_DEPARTMENT_ICON_FRAGMENT_EXCLUDE_KEYS.include?(k) } .sort_by { |k, _| [-k.length, k] } .freeze
- MANAGER_DEPARTMENT_ICON_FALLBACKS =
Manager department icon fallbacks.
%w[ building briefcase users industry star diagram-project ].freeze
- PENDING_BADGE_VARIANT_CLASSES =
Bootstrap background utilities for +pending_badge+ (+variant+ must be a known key).
{ primary: 'bg-primary', warning: 'bg-warning text-dark' }.freeze
- TIME_OFF_TYPE_DISPLAY_TEXT_CLASS =
Maps time-off row type (TimeOffType#name or the literal "holiday") to Bootstrap 5 text utilities.
Output is always from a fixed allowlist — never interpolates DB color strings into markup. { 'holiday' => 'text-warning', 'Vacation' => 'text-success', 'Birthday' => 'text-warning', 'Unpaid leave' => 'text-secondary', 'Community Service' => 'text-success', 'Short-Term Disability (STD)' => 'text-primary', 'Jury Duty' => 'text-info', 'Bereavement' => 'text-secondary', 'Banked Time' => 'text-primary' }.freeze
- FALLBACK_TEXT_CLASSES =
Fallback text classes.
%w[ text-primary text-secondary text-success text-danger text-warning text-info text-body-secondary ].freeze
- TIME_OFF_TYPE_UNIT_OPTIONS =
Available time off type unit options.
[%w[Hours hour], %w[Days day]].freeze
- TIME_OFF_TYPE_ICON_SUGGESTIONS =
Kebab-case Font Awesome icon names (no
fa-prefix) for Tom Select suggestions. %w[ umbrella-beach calendar-days calendar-xmark calendar-check sun snowflake heart-pulse bandage person-falling plane car gift baby cross house-chimney clock mug-hot briefcase beach-ball ballot-check star sparkles martini-glass cake-candles hands-praying user-doctor stethoscope mask-face thermometer ].freeze
- TIME_OFF_TYPE_COLOR_PICKER_DEFAULT =
Default swatch for
<input type="color">when none is set or value is not a hex the input accepts. '#913120'- TIME_OFF_POLICY_ACCRUAL_FREQUENCY_OPTIONS =
Values match existing policy rows (e.g. seeds) and Employee#accrual handling uses .downcase.
%w[Weekly Monthly Yearly].map { |w| [w, w] }.freeze
- TIME_OFF_POLICY_WEEKLY_DAY_OPTIONS =
Available time off policy weekly day options.
Date::DAYNAMES.each_with_index.map { |day, index| [day, index] }.freeze
- TIME_OFF_POLICY_ANNUAL_ACCRUAL_TIMING_OPTIONS =
Available time off policy annual accrual timing options.
[ ['Beginning of Year', 'beginning_of_year'], ['End of Year', 'end_of_year'] ].freeze
Instance Method Summary collapse
-
#manager_department_icon(department_label) ⇒ String
Icon for a manager dashboard department heading (decorative; label remains plain text).
-
#manager_grid_location_line(employee) ⇒ ActiveSupport::SafeBuffer?
The small location line under a name on the who's-working grid, or nil when the employee has no recent CRM activity (or the grid shows a past date, in which case @employee_last_seen is empty).
-
#manager_schedule_grid_cell_kind(row_data, slot, slot_end) ⇒ Symbol
Row hash from Crm::TimeOffsController#setup_manager_dashboard_data (+ :time_off_request_date).
-
#pending_badge(count, variant: :primary, title: '', extra_classes: '') ⇒ ActiveSupport::SafeBuffer?
Pill badge for manager employee chip pending counts; returns nil when +count+ is not positive.
-
#time_off_policy_parent_options(record) ⇒ Array<Array(String, Integer)>
[name, id] options for a time off policy's parent select, excluding the record itself when persisted.
-
#time_off_type_color_class(type) ⇒ String
Maps a time-off row type to a Bootstrap 5 text utility class, falling back to a stable pick from FALLBACK_TEXT_CLASSES for unknown names.
-
#time_off_type_color_picker_value(record) ⇒ String
Normalizes a TimeOffType color for the HTML color input; falls back when the DB has named colors or invalid values.
-
#time_off_type_icon_tom_select_collection(record) ⇒ Array<Array(String, String)>
Pairs [label, value] for icon Tom Select; includes current value if not in the suggestion list.
-
#vacation_summary_bar_chart_data(summary) ⇒ Hash?
Series for the Summary tab bar chart (horizontal bars, same metrics as the KPI cards).
-
#vacation_summary_hours_display(value) ⇒ Integer, Float
Formats a vacation summary hour value for display; matches TimeOffBalanceCalculator#format_number for raw values.
Methods included from TimeOffPlannedWorkDayHelper
#time_off_planned_work_day_planned_hours, #time_off_planned_work_day_summary, #time_off_planned_work_day_usual_summary
Instance Method Details
#manager_department_icon(department_label) ⇒ String
Icon for a manager dashboard department heading (decorative; label remains plain text).
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'app/helpers/crm/time_offs_helper.rb', line 96 def manager_department_icon(department_label) key = department_label.to_s.strip.downcase return 'users' if key.blank? icon = MANAGER_DEPARTMENT_ICON_BY_NAME[key] return icon if icon MANAGER_DEPARTMENT_ICON_FRAGMENTS.each do |fragment, frag_icon| return frag_icon if key.include?(fragment) end return 'building' if /\bit\b/.match?(key) return 'people-group' if /\bhr\b/.match?(key) idx = key.bytes.sum % MANAGER_DEPARTMENT_ICON_FALLBACKS.size MANAGER_DEPARTMENT_ICON_FALLBACKS[idx] end |
#manager_grid_location_line(employee) ⇒ ActiveSupport::SafeBuffer?
The small location line under a name on the who's-working grid, or nil
when the employee has no recent CRM activity (or the grid shows a past
date, in which case @employee_last_seen is empty).
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'app/helpers/crm/time_offs_helper.rb', line 26 def manager_grid_location_line(employee) seen = @employee_last_seen&.dig(employee.id) return if seen.blank? location = MANAGER_GRID_LOCATIONS[IpDetector.office_location(seen['ip'])] return if location.nil? tag.div(class: 'text-body-secondary', style: 'font-size: 0.7rem;', title: "Last CRM activity #{time_ago_in_words(Time.zone.at(seen['at']))} ago") do fa_icon(location[:icon], class: 'me-1', 'aria-hidden': 'true') + location[:label] end end |
#manager_schedule_grid_cell_kind(row_data, slot, slot_end) ⇒ Symbol
Row hash from Crm::TimeOffsController#setup_manager_dashboard_data (+ :time_off_request_date).
312 313 314 315 316 317 318 319 320 |
# File 'app/helpers/crm/time_offs_helper.rb', line 312 def manager_schedule_grid_cell_kind(row_data, slot, slot_end) Crm::ManagerScheduleGridSlot.cell_kind( slot: slot, slot_end: slot_end, schedule_day: row_data[:schedule_day], time_off_request_date: row_data[:time_off_request_date], ongoing_time_off_request: row_data[:ongoing_request] ) end |
#pending_badge(count, variant: :primary, title: '', extra_classes: '') ⇒ ActiveSupport::SafeBuffer?
Pill badge for manager employee chip pending counts; returns nil when +count+ is not positive.
128 129 130 131 132 133 134 135 136 137 |
# File 'app/helpers/crm/time_offs_helper.rb', line 128 def pending_badge(count, variant: :primary, title: '', extra_classes: '') return nil unless count.respond_to?(:positive?) && count.positive? variant_classes = PENDING_BADGE_VARIANT_CLASSES.fetch(variant.to_sym) class_parts = ['badge', 'rounded-pill', variant_classes] class_parts.concat(extra_classes.to_s.split) if extra_classes.present? attrs = { class: class_parts.join(' ') } attrs[:title] = title if title.present? tag.span(count.to_s, **attrs) end |
#time_off_policy_parent_options(record) ⇒ Array<Array(String, Integer)>
[name, id] options for a time off policy's parent select, excluding the
record itself when persisted.
298 299 300 301 302 |
# File 'app/helpers/crm/time_offs_helper.rb', line 298 def (record) scope = TimeOffPolicy.order(:name) scope = scope.excluding(record) if record.persisted? scope.pluck(:name, :id) end |
#time_off_type_color_class(type) ⇒ String
Maps a time-off row type to a Bootstrap 5 text utility class, falling
back to a stable pick from FALLBACK_TEXT_CLASSES for unknown names.
163 164 165 166 167 168 169 170 |
# File 'app/helpers/crm/time_offs_helper.rb', line 163 def time_off_type_color_class(type) key = type.to_s return TIME_OFF_TYPE_DISPLAY_TEXT_CLASS[key] if TIME_OFF_TYPE_DISPLAY_TEXT_CLASS.key?(key) # Unknown type names (e.g. newly added in admin): pick a stable class from the allowlist only. idx = key.bytes.sum % FALLBACK_TEXT_CLASSES.size FALLBACK_TEXT_CLASSES[idx] end |
#time_off_type_color_picker_value(record) ⇒ String
Normalizes a TimeOffType color for the HTML color input; falls back when
the DB has named colors or invalid values.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'app/helpers/crm/time_offs_helper.rb', line 250 def time_off_type_color_picker_value(record) c = record.color.to_s.strip return TIME_OFF_TYPE_COLOR_PICKER_DEFAULT if c.blank? case c when /\A#[0-9a-fA-F]{6}\z/i c when /\A#[0-9a-fA-F]{3}\z/i "##{c[1]}#{c[1]}#{c[2]}#{c[2]}#{c[3]}#{c[3]}" when /\A#[0-9a-fA-F]{8}\z/i c[0..6] else TIME_OFF_TYPE_COLOR_PICKER_DEFAULT end end |
#time_off_type_icon_tom_select_collection(record) ⇒ Array<Array(String, String)>
Pairs [label, value] for icon Tom Select; includes current value if not in the suggestion list.
270 271 272 273 274 275 276 277 278 |
# File 'app/helpers/crm/time_offs_helper.rb', line 270 def time_off_type_icon_tom_select_collection(record) normalized = record.icon.to_s.strip.sub(/\Afa-/, '').tr('_', '-').downcase pairs = TIME_OFF_TYPE_ICON_SUGGESTIONS.map { |i| [i.tr('-', ' ').titleize, i] } if normalized.present? && TIME_OFF_TYPE_ICON_SUGGESTIONS.exclude?(normalized) pairs = pairs.dup pairs.unshift(["#{normalized.tr('-', ' ').titleize} (current)", normalized]) end pairs end |
#vacation_summary_bar_chart_data(summary) ⇒ Hash?
Series for the Summary tab bar chart (horizontal bars, same metrics as the KPI cards).
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'app/helpers/crm/time_offs_helper.rb', line 189 def (summary) return if summary.blank? rows = [ { label: 'Used time as of today', value: summary[:used_until_today].to_f, icon: 'clock', fill_class: 'bg-danger' }, { label: 'Available time as of today', value: summary[:available_today].to_f, icon: 'circle-check', fill_class: 'bg-success' }, { label: 'Approved planned until year-end', value: summary[:approved_between_today_and_eoy].to_f, icon: 'calendar-check', fill_class: 'bg-warning' }, { label: 'Pending approval until year-end', value: summary[:pending_between_today_and_eoy].to_f, icon: 'hourglass-half', fill_class: 'bg-secondary' }, { label: 'Available at year-end', value: summary[:available_eoy].to_f, icon: 'calendar-xmark', fill_class: 'bg-primary' } ] max_value = rows.pluck(:value).max.to_f max_value = 1.0 if max_value <= 0 { rows:, max_value: } end |
#vacation_summary_hours_display(value) ⇒ Integer, Float
Formats a vacation summary hour value for display; matches
TimeOffBalanceCalculator#format_number for raw values.
177 178 179 180 181 182 183 |
# File 'app/helpers/crm/time_offs_helper.rb', line 177 def vacation_summary_hours_display(value) return 0 if value.blank? n = value.to_f rounded = n.round(2) (rounded % 1).zero? ? rounded.to_i : rounded end |