Class: Www::ProductCompareTableComponent

Inherits:
ApplicationComponent show all
Defined in:
app/components/www/product_compare_table_component.rb

Overview

Www::ProductCompareTableComponent

Renders a responsive product comparison table with products displayed in columns
and features listed on the left side. Supports mobile swiping and responsive
column display from 1 column on mobile to 5 columns on largest viewports.

Constructor parameters (keyword args)

  • skus: Array of SKUs to compare
  • features: Array of feature keys to display (optional, will auto-detect if not provided)
  • section_title: Optional title for the comparison section
  • section_description: Optional description for the comparison section

Features are automatically detected from the first product if not provided.
Each feature is displayed as a row with checkmarks or X marks for each product.

Constant Summary collapse

SKU_FEATURES =

Feature definitions per SKU - much easier to maintain than nested case statements

{
  # Sensors are never included with standalone controls — they are sold separately.
  # Use :aerial / :slab symbols so the template renders a product link instead of a checkmark.
  'SC-MZ-TOUCH' => {
    'automatic' => true,
    'moisture_sensor' => :aerial,
    'air_temp_sensor' => :aerial,
    'slab_temp_sensor' => :slab,
    'digital_temp_readout' => true,
    'adjust_after_run_time' => true,
    'multi_zone_capability' => true,
    'max_load' => 'N/A max load'
  },
  'SCP-120' => {
    'automatic' => true,
    'outdoor_rated' => true,
    'moisture_sensor' => :aerial,
    'air_temp_sensor' => :aerial,
    'slab_temp_sensor' => :slab,
    'adjust_after_run_time' => true,
    'max_load' => '24 amps max load'
  },
  'SCA-DUAL' => {
    'automatic' => true,
    'outdoor_rated' => true,
    'moisture_sensor' => :aerial,
    'air_temp_sensor' => :aerial,
    'slab_temp_sensor' => :slab,   # supports "pavement" sensor per product features
    'adjust_after_run_time' => true,
    'max_load' => '30 amps max load'
  },
  'SCE-120' => {
    'automatic' => true,
    'moisture_sensor' => :aerial,
    'air_temp_sensor' => :aerial,
    'slab_temp_sensor' => :slab,
    'digital_temp_readout' => true,
    'adjust_after_run_time' => true,
    'max_load' => '3 x 16 amps max load'
  },
  'SCV-DUAL' => {
    'automatic' => true,
    'outdoor_rated' => true,
    'moisture_sensor' => true,   # built-in snow & temperature sensor
    'air_temp_sensor' => true,   # built-in snow & temperature sensor
    'adjust_after_run_time' => true,
    'max_load' => '16 amps max load'
  },
  'SCM-DUAL' => {
    'timer' => true,
    'max_load' => '20 Amp, 120 VAC, 50/60 Hz; 10 Amp, 240 VAC, 50/60 Hz; 10 Amp, 277 VAC, 50/60 Hz amps max load'
  },
  'SCW-120-15A' => {
    'control_remotely_via_iphone_android_or_web_app' => true,
    'max_load' => '15A @ 120V amps max load'
  },
  # ── Panel & Towel Warmer Controls ───────────────────────────────────────
  'GK16-30090-0006' => {
    'countdown_timer'  => true,    # 4 preset durations: 4 hr, 2 hr, 1 hr, 30 min
    'programmable'     => false,
    'schedules'        => false,
    'wifi'             => false,
    'voice_control'    => false,
    'remote_support'   => false,
    'display'          => 'None',
    'max_load'         => '15A @ 120V'
  },
  'GK16-30090-0002' => {
    'countdown_timer'  => false,
    'programmable'     => true,    # astronomical clock + up to 50 on/off events
    'schedules'        => 'Up to 50 on/off events',
    'wifi'             => false,
    'voice_control'    => false,
    'remote_support'   => false,
    'display'          => 'Backlit LCD',
    'max_load'         => '15A @ 120V'
  },
  'GK16-30090-0003' => {
    'countdown_timer'  => false,
    'programmable'     => true,    # schedule via My Leviton app
    'schedules'        => 'Via My Leviton app',
    'wifi'             => true,
    'voice_control'    => true,    # Alexa, Google, Apple HomeKit/Siri, SmartThings, IFTTT
    'remote_support'   => false,
    'display'          => 'None',
    'max_load'         => '15A @ 120V'
  },
  # ── Floor Heating Thermostats ────────────────────────────────────────────
  'UWG5-4999-WY' => {
    'countdown_timer'       => false,
    'wifi'                  => true,
    'remote_support'        => true,
    'voice_control'         => true,
    'programmable'          => true,
    'schedules'             => 'Up to 10 schedules',
    'floor_sensor_included' => true,
    'display'               => 'LED touch',
    'max_load'              => '15A @ 120/240V'
  },
  'UTN5-4999' => {
    'countdown_timer'       => false,
    'wifi'                  => false,
    'remote_support'        => false,
    'voice_control'         => false,
    'programmable'          => false,
    'schedules'             => 'Set-and-hold',
    'floor_sensor_included' => true,
    'display'               => 'LED touch',
    'max_load'              => '15A @ 120/240V'
  },
  'UDG4-4999' => {
    'countdown_timer'       => false,
    'wifi'                  => false,
    'remote_support'        => false,
    'voice_control'         => false,
    'programmable'          => true,
    'schedules'             => 'Up to 6 per day',
    'floor_sensor_included' => true,
    'display'               => '3.5" color touchscreen',
    'max_load'              => '15A @ 120/240V'
  },
  # Canadian variant of nSpire Touch — same features as UDG4-4999
  'UDG4-4999-WY' => {
    'countdown_timer'       => false,
    'wifi'                  => false,
    'remote_support'        => false,
    'voice_control'         => false,
    'programmable'          => true,
    'schedules'             => 'Up to 6 per day',
    'floor_sensor_included' => true,
    'display'               => '3.5" color touchscreen',
    'max_load'              => '15A @ 120/240V'
  },
  # nJoin Power Module — extends thermostat capacity for high-load zones.
  # Not a standalone thermostat: schedules and sensor come from the paired thermostat.
  'USG5-4000' => {
    'countdown_timer'       => false,
    'wifi'                  => false,
    'remote_support'        => false,
    'voice_control'         => false,
    'programmable'          => false,
    'schedules'             => 'Via paired thermostat',
    'floor_sensor_included' => false,
    'display'               => 'None',
    'max_load'              => '15A @ 120/208/240V'
  },
  # Canadian variant — identical specs to USG5-4000
  'USG-4000' => {
    'countdown_timer'       => false,
    'wifi'                  => false,
    'remote_support'        => false,
    'voice_control'         => false,
    'programmable'          => false,
    'schedules'             => 'Via paired thermostat',
    'floor_sensor_included' => false,
    'display'               => 'None',
    'max_load'              => '15A @ 120/208/240V'
  },
  # Pipe Freeze Protection Controls (also used in roof and gutter deicing)
  'AIR-STAT' => {
    # Snow melting features
    'automatic' => true,
    'outdoor_rated' => true,
    'moisture_sensor' => false,
    'air_temp_sensor' => true,
    'slab_temp_sensor' => false,
    'digital_temp_readout' => false,
    'adjust_after_run_time' => false,
    'timer' => false,
    'control_remotely_via_iphone_android_or_web_app' => false,
    # Pipe freeze protection features
    'included_sensor' => '1 capillary bulb, 3\' lead',
    'temperature_setpoint' => '40° F',
    'indicators' => 'None',
    'built_in_gfi' => 'None',
    'waterproofing' => 'NEMA 4X',
    'area_of_use' => 'Non-hazardous locations',
    'max_load' => '22 amps max load'
  },
  'PT-ECONOMY' => {
    'included_sensor' => '1 Sensor (20\' lead)',
    'temperature_setpoint' => '30 °F, 38 °F, 45 °F, or 50 °F',
    'indicators' => 'LED Indicators',
    'built_in_gfi' => 'Self-test GFEP 30mA',
    'waterproofing' => 'NEMA 4X',
    'area_of_use' => 'Non-hazardous locations',
    'max_load' => '30 amps max load'
  },
  'PT-SINGLE' => {
    'included_sensor' => '100k ohms, 20\' lead',
    'temperature_setpoint' => 'Adjustable -99.9 °F to 999 °F',
    'indicators' => '2.7" Display',
    'built_in_gfi' => 'Self-test GFEP (Adjustable 1-300mA)',
    'waterproofing' => 'NEMA 4X',
    'area_of_use' => 'Non-hazardous locations',
    'max_load' => '30 amps max load'
  },
  'PT-DUAL' => {
    'included_sensor' => '2 x 100k ohms, 20\' lead',
    'temperature_setpoint' => 'Adjustable -99.9 °F to 999 °F',
    'indicators' => '2.7" Display',
    'built_in_gfi' => 'Self-test GFEP (Adjustable 1-300mA)',
    'waterproofing' => 'NEMA 4X',
    'area_of_use' => 'Non-hazardous locations',
    'max_load' => '2 x 30 amps max load'
  },
  # ── Snow Melting Sensors ─────────────────────────────────────────────────
  'AIR-SS-2' => {
    'sensor_installation'    => 'Mounted above ground on post or wall',
    'detects_precipitation'  => true,
    'measures_air_temp'      => true,
    'measures_slab_temp'     => false,
    'retrofit_compatible'    => true,
    'compatible_controls'    => 'SC-MZ-TOUCH, SCP-120, SCA-DUAL, SCE-120'
  },
  'SCE-SLAB-SS-KIT' => {
    'sensor_installation'    => 'Embedded in concrete during pour',
    'detects_precipitation'  => false,
    'measures_air_temp'      => false,
    'measures_slab_temp'     => true,
    'retrofit_compatible'    => false,
    'compatible_controls'    => 'SC-MZ-TOUCH, SCP-120, SCA-DUAL, SCE-120'
  }
}.freeze
ADD_ON_PRODUCTS =

Products that can be added to extend a control's capabilities.
The symbol keys are used as feature values in SKU_FEATURES to signal
"this feature is supported but requires a separate purchase".
anchor: scrolls to the sensors comparison section on the same page.

{
  aerial: { sku: 'AIR-SS-2',        label: 'Aerial Sensor',    anchor: '#sensors' },
  slab:   { sku: 'SCE-SLAB-SS-KIT', label: 'Slab Sensor Kit',  anchor: '#sensors' }
}.freeze
SENSOR_FEATURE_KEYS =

Feature keys that represent physical sensors — these get badge treatment
("Included" or "Pair with…") instead of a plain checkmark/X.

%w[moisture_sensor air_temp_sensor slab_temp_sensor].freeze
BEST_FOR =

"Best for" taglines shown in the comparison table header, below the product title.
Authored specifically for the comparison context — more decision-oriented than
generic DB short_description copy. Takes precedence over item.short_description.

{
# ── Floor Heating Thermostats ──────────────────────────────────────────────
'UTN5-4999'       => 'Simple set-and-hold control',
'UDG4-4999'       => 'Touchscreen, no WiFi needed',
'UDG4-4999-WY'    => 'Touchscreen, no WiFi needed',
'UWG5-4999-WY'    => 'WiFi + Alexa / Google / HomeKit',
# ── Radiant Panel & Towel Warmer Controls ──────────────────────────────────
'GK16-30090-0006' => 'Simple timer, no programming',
'GK16-30090-0002' => 'Scheduled daily heating',
'GK16-30090-0003' => 'Smart home & voice control',
# ── Snow Melting Controls ──────────────────────────────────────────────────
'SC-MZ-TOUCH'     => 'Multi-zone touchscreen hub',
'SCP-120'         => 'Basic single-zone manual',
'SCA-DUAL'        => 'Automatic with external sensor',
'SCE-120'         => 'Economical automatic control',
'SCV-DUAL'        => 'Automatic, no sensor needed',
'SCM-DUAL'        => 'Manual timer control',
'SCW-120-15A'     => 'WiFi remote control',
'SMRT-IQ'         => 'WiFi remote monitoring',
# ── Pipe Freeze & Roof/Gutter Controls ────────────────────────────────────
'AIR-STAT'        => 'Temperature-only freeze protection',
'RXSJS2-Z1SP'     => 'Automatic freeze protection'}.freeze
FEATURE_TOOLTIPS =
{
  'automatic'                                    => 'Uses sensors to activate the system automatically when snow or ice is detected—no manual intervention needed.',
  'outdoor_rated'                                => 'The control enclosure (and sensors) are rated to withstand harsh outdoor environments including moisture, frost, and temperature extremes.',
  'moisture_sensor'                              => 'Detects the presence of snow, rain, or moisture above the heated surface. The system only activates when both moisture and a below-freezing air temperature are detected—preventing unnecessary heating.',
  'air_temp_sensor'                              => 'Monitors outdoor air temperature. Combined with moisture detection, it ensures the system only runs when conditions actually require it, saving energy.',
  'slab_temp_sensor'                             => 'Measures the surface temperature of the concrete or pavers directly. The system heats until the slab reaches the target temperature, then shuts off automatically.',
  'digital_temp_readout'                         => 'Shows the current sensor temperature on the control\'s display so you can monitor conditions without a separate thermometer.',
  'adjust_after_run_time'                        => 'Keeps the system running for a set time after precipitation stops to ensure the surface dries fully and prevent re-freezing.',
  'multi_zone_capability'                        => 'Controls multiple independent heating zones from a single unit—useful for driveways, walkways, and steps on the same installation.',
  'timer'                                        => 'A built-in 24-hour timer lets you schedule the system to run during specific hours. No sensors required—ideal for simple on/off control.',
  'countdown_timer'                              => 'Preset countdown durations let you run the system for a fixed period (e.g. 30 min, 1 hr, 2 hr, 4 hr) then shut off automatically—no programming required.',
  'control_remotely_via_iphone_android_or_web_app' => 'Connects via WiFi so you can monitor and control the system from anywhere using a smartphone app or web browser. Compatible with Alexa, Google Home, Nest, and IFTTT.',
  'max_load'                                     => 'The maximum load this control can switch directly.',
  'wifi'                                         => 'Built-in WiFi lets you monitor and adjust settings remotely from a smartphone app.',
  'remote_support'                               => 'Allows WarmlyYours technicians to diagnose issues and push firmware updates over the internet.',
  'voice_control'                                => 'Compatible with Amazon Alexa, Google Assistant, and Apple HomeKit/Siri for hands-free voice commands and home automation routines.',
  'programmable'                                 => 'Set daily on/off schedules so the system runs exactly when you need it without manual intervention.',
  'schedules'                                    => 'Number of independent on/off schedule events available for programming.',
  'floor_sensor_included'                        => 'Includes a floor temperature sensor so the thermostat can regulate the actual floor temperature, not just the air.',
  'included_sensor'                              => 'The temperature sensor shipped in the box with this control.',
  'temperature_setpoint'                         => 'The temperature range you can set for the control to maintain.',
  'built_in_gfi'                                 => 'Ground Fault Equipment Protection trips the circuit if a fault is detected, protecting both the equipment and personnel.',
  'waterproofing'                                => 'The NEMA enclosure rating indicates how well the control resists water, dust, and corrosion.',
  'indicators'                                   => 'Status lights or display that show the current operating state of the control.',
  'area_of_use'                                  => 'The environments where this control is certified for installation (e.g. non-hazardous indoor locations).',
  # Sensor-specific
  'sensor_installation'                          => 'Where and how this sensor is physically installed.',
  'detects_precipitation'                        => 'Senses snow, rain, or ice above the surface. Used with air temperature to decide when the system should activate.',
  'measures_air_temp'                            => 'Reads outdoor air temperature. Prevents the system from running when it\'s too warm to need heating.',
  'measures_slab_temp'                           => 'Reads the surface temperature of the concrete or pavers. The system runs until the slab reaches the set temperature, then shuts off.',
  'retrofit_compatible'                          => 'Can be installed after construction is complete—no need to break up or re-pour concrete.',
  'compatible_controls'                          => 'The WarmlyYours snow melting controls that accept this sensor type.'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationComponent

#cms_link, #fetch_or_fallback, #image_asset_tag, #image_tag, #number_to_currency, #number_with_delimiter, #post_path, #post_url, #strip_tags

Constructor Details

#initialize(skus: [], features: nil, section_title: nil, section_description: nil, sort_by_price: true, image_height: 180, image_overrides: {}) ⇒ ProductCompareTableComponent

Returns a new instance of ProductCompareTableComponent.



319
320
321
322
323
324
325
326
327
328
# File 'app/components/www/product_compare_table_component.rb', line 319

def initialize(skus: [], features: nil, section_title: nil, section_description: nil, sort_by_price: true, image_height: 180, image_overrides: {})
  @skus = Array(skus).compact
  @features = features
  @section_title = section_title
  @section_description = section_description
  @sort_by_price = sort_by_price
  @image_height = image_height
  @image_overrides = image_overrides
  super()
end

Instance Attribute Details

#featuresObject (readonly)

Returns the value of attribute features.



18
19
20
# File 'app/components/www/product_compare_table_component.rb', line 18

def features
  @features
end

#section_descriptionObject (readonly)

Returns the value of attribute section_description.



18
19
20
# File 'app/components/www/product_compare_table_component.rb', line 18

def section_description
  @section_description
end

#section_titleObject (readonly)

Returns the value of attribute section_title.



18
19
20
# File 'app/components/www/product_compare_table_component.rb', line 18

def section_title
  @section_title
end

#skusObject (readonly)

Returns the value of attribute skus.



18
19
20
# File 'app/components/www/product_compare_table_component.rb', line 18

def skus
  @skus
end

Instance Method Details

#add_on_for(value) ⇒ Object

When a feature value is a Symbol, returns the matching add-on product hash
{ sku:, label: } so the template can render a product link.
Returns nil for non-Symbol values.



354
355
356
357
358
# File 'app/components/www/product_compare_table_component.rb', line 354

def add_on_for(value)
  return nil unless value.is_a?(Symbol)

  ADD_ON_PRODUCTS[value]
end

#add_on_url(add_on) ⇒ Object

Returns the URL for an add-on product.
Prefers an on-page anchor (e.g. "#sensors") when set, otherwise falls back
to the product detail URL.



363
364
365
366
367
# File 'app/components/www/product_compare_table_component.rb', line 363

def add_on_url(add_on)
  return add_on[:anchor] if add_on[:anchor].present?

  helpers.catalog_link_for_sku(add_on[:sku])
end

#before_renderObject



330
331
332
333
# File 'app/components/www/product_compare_table_component.rb', line 330

def before_render
  @products = load_products
  @features = detect_features if @features.blank?
end

#feature_display_name(feature_key) ⇒ Object



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'app/components/www/product_compare_table_component.rb', line 369

def feature_display_name(feature_key)
  case feature_key
  when 'automatic'
    'Automatic'
  when 'outdoor_rated'
    'Outdoor Rated'
  when 'moisture_sensor'
    'Moisture Sensor'
  when 'air_temp_sensor'
    'Air Temp Sensor'
  when 'slab_temp_sensor'
    'Slab Temp Sensor'
  when 'digital_temp_readout'
    'Digital Temp Readout'
  when 'adjust_after_run_time'
    'Adjust After Run Time'
  when 'multi_zone_capability'
    'Multi Zone Capability'
  when 'timer'
    'Timer'
  when 'countdown_timer'
    'Countdown Timer'
  when 'control_remotely_via_iphone_android_or_web_app'
    'Control Remotely Via iPhone/Android/Web App'
  when 'max_load'
    'Max Load'
  # Thermostat-specific features
  when 'wifi'
    'WiFi-Enabled'
  when 'remote_support'
    'Remote Troubleshooting/Updates'
  when 'voice_control'
    'Voice Assistant Compatible'
  when 'programmable'
    'Programmable'
  when 'schedules'
    'Schedules'
  when 'floor_sensor_included'
    'Floor Sensor Included'
  when 'display'
    'Display'
  # Pipe Freeze Protection specific features
  when 'included_sensor'
    'Included Sensor'
  when 'temperature_setpoint'
    'Temperature Setpoint'
  when 'indicators'
    'Status Indicators'
  when 'built_in_gfi'
    'Built-in GFI Protection'
  when 'waterproofing'
    'Enclosure Rating'
  when 'area_of_use'
    'Area of Use'
  # Sensor-specific features
  when 'sensor_installation'
    'Installation'
  when 'detects_precipitation'
    'Detects Precipitation'
  when 'measures_air_temp'
    'Measures Air Temperature'
  when 'measures_slab_temp'
    'Measures Slab Temperature'
  when 'retrofit_compatible'
    'Retrofit Compatible'
  when 'compatible_controls'
    'Compatible Controls'
  else
    feature_key.to_s.humanize
  end
end

#feature_tooltip(feature_key) ⇒ Object

Returns a short plain-text tooltip description for the given feature, or nil if none defined.



343
344
345
# File 'app/components/www/product_compare_table_component.rb', line 343

def feature_tooltip(feature_key) # rubocop:disable Metrics/MethodLength
  FEATURE_TOOLTIPS[feature_key]
end

#feature_value(product, feature_key) ⇒ Object



335
336
337
338
339
340
# File 'app/components/www/product_compare_table_component.rb', line 335

def feature_value(product, feature_key)
  sku = product[:sku]

  # Use hash table lookup instead of nested case statements
  SKU_FEATURES.dig(sku, feature_key) || false
end

#sensor_feature?(feature_key) ⇒ Boolean

Returns:

  • (Boolean)


347
348
349
# File 'app/components/www/product_compare_table_component.rb', line 347

def sensor_feature?(feature_key)
  SENSOR_FEATURE_KEYS.include?(feature_key)
end