Class: Www::FloorHeatingCalculatorComponent

Inherits:
ApplicationComponent show all
Includes:
ActionView::Helpers::TagHelper, ActionView::Helpers::UrlHelper
Defined in:
app/components/www/floor_heating_calculator_component.rb

Overview

Floor Heating Operating Cost Calculator
Calculates estimated operating costs based on square footage, usage hours, and electricity rates

Usage:

<%# Basic usage %>
<%= render Www::FloorHeatingCalculatorComponent.new %>

<%# Spanish locale %>
<%= render Www::FloorHeatingCalculatorComponent.new(locale: :es) %>

<%# Custom defaults %>
<%= render Www::FloorHeatingCalculatorComponent.new(default_sqft: 100, default_hours: 8) %>

<%# With integrated SmartPlan CTA %>
<%= render Www::FloorHeatingCalculatorComponent.new(layout: :horizontal,
smartplan: { product_type: :floor_heating, area_name: 'laminate floor', project_link: '/showcases' }
) %>

Constant Summary collapse

IMAGEKIT_BASE =

ImageKit base URL for product images

'https://ik.warmlyyours.com'
TRANSLATIONS =

Translations.

{
  en: {
    title: 'Operating Cost Calculator for Floor Heating',
    location: 'Location',
    location_description: 'Your location is used to find the energy rate in your area.',
    my_location: 'My Location',
    zip_postal_code: 'Zip/Postal Code',
    cents_per_kwh: 'Rate',
    current_rate: 'Current rate',
    square_footage: 'Square Footage',
    sq_ft: 'sq. ft.',
    usage: 'Usage',
    estimated_cost: 'Estimated Cost',
    per_hour: 'per hour',
    per_day: 'per day',
    per_month: 'per month',
    per_year: 'per year',
    daily_usage: 'Daily Usage',
    hours_day: 'hours/day',
    hour: 'hour',
    hours: 'hours',
    surface_type: 'What surface are you heating?',
    room_size: 'Room Size',
    energy_rates: 'Energy Rates',
    energy_rates_description: 'Your location helps us find the average energy rate in your area to ensure our calculation is accurate.',
    consider_usage: 'Consider your room type and if this will be a supplemental or primary heat source.',
    bathroom: 'Bathroom',
    kitchen: 'Kitchen',
    basement: 'Basement',
    custom: 'Square Footage',
    calculator_settings: 'Calculator Settings',
    rae_quote: "Most homeowners are surprised how affordable radiant floor heating is to operate—often just pennies a day for a bathroom. It's the kind of everyday luxury that pays for itself in comfort!"
  },
  es: {
    title: 'Calculadora de Costos Operativos para Calefacción de Piso',
    location: 'Ubicación',
    location_description: 'Tu ubicación nos ayuda a encontrar la tasa de energía promedio en el área.',
    my_location: 'Mi ubicación actual',
    zip_postal_code: 'Código Postal',
    cents_per_kwh: 'Tarifa',
    current_rate: 'Tasa actual',
    square_footage: 'Pies Cuadrados',
    sq_ft: 'pies²',
    usage: 'Uso diario',
    estimated_cost: 'Costo estimado',
    per_hour: 'por hora',
    per_day: 'por día',
    per_month: 'por mes',
    per_year: 'por año',
    daily_usage: 'Uso diario',
    hours_day: 'horas por día',
    hour: 'hora',
    hours: 'horas',
    surface_type: '¿Qué superficie está calentando?',
    room_size: 'Tamaño de la Habitación',
    energy_rates: 'Tarifas de energía',
    energy_rates_description: 'Tu ubicación nos ayuda a encontrar la tasa de energía promedio en el área.',
    consider_usage: 'Considera tu tipo de habitación y si esta será una fuente de calor suplementaria o primaria.',
    bathroom: 'Baño',
    kitchen: 'Cocina',
    basement: 'Sótano',
    custom: 'Pies Cuadrados',
    calculator_settings: 'Configuración del Calculador',
    rae_quote: 'La mayoría de los propietarios se sorprenden de lo económico que es operar la calefacción por piso radiante—a menudo solo centavos al día para un baño. ¡Es el tipo de lujo cotidiano que se paga solo en comodidad!'
  }
}.freeze
SURFACE_TYPES =

Surface types map to heating systems
Surface type drives the product recommendation

[
  { id: 'tile_stone', name: 'Tile or Stone', product: 'TempZone™', wattage: 15 },
  { id: 'laminate_floating', name: 'Laminate, Floating Wood, or Carpet', product: 'Environ™', wattage: 12 },
  { id: 'concrete_embedded', name: 'Concrete Slab (New Pour)', product: 'Embedded Slab Heat Cable or Mat', wattage: 20 }
].freeze
ROOM_TYPES =

Room types with typical square footage for dropdown

[
  { id: 'bathroom', name: 'Bathroom', sqft: 35 },
  { id: 'master_bath', name: 'Master Bathroom', sqft: 80 },
  { id: 'powder_room', name: 'Powder Room', sqft: 25 },
  { id: 'kitchen', name: 'Kitchen', sqft: 150 },
  { id: 'entryway', name: 'Entryway/Foyer', sqft: 40 },
  { id: 'mudroom', name: 'Mudroom', sqft: 50 },
  { id: 'laundry', name: 'Laundry Room', sqft: 60 },
  { id: 'bedroom', name: 'Bedroom', sqft: 150 },
  { id: 'master_bed', name: 'Master Bedroom', sqft: 250 },
  { id: 'living_room', name: 'Living Room', sqft: 300 },
  { id: 'family_room', name: 'Family Room', sqft: 350 },
  { id: 'dining_room', name: 'Dining Room', sqft: 180 },
  { id: 'basement', name: 'Basement', sqft: 400 },
  { id: 'sunroom', name: 'Sunroom', sqft: 200 },
  { id: 'garage', name: 'Garage', sqft: 400 },
  { id: 'office', name: 'Home Office', sqft: 120 },
  { id: 'custom', name: 'Custom Size', sqft: nil }
].freeze
DEFAULT_KWH_CENTS =

Default electricity rate in cents/kWh (US average ~16.26 cents)

16.26

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(locale: :en, default_sqft: 35, default_hours: 4, default_kwh: nil, default_wattage: nil, default_surface_type: 'tile_stone', default_room_type: 'bathroom', layout: :vertical, smartplan: nil) ⇒ FloorHeatingCalculatorComponent

Returns a new instance of FloorHeatingCalculatorComponent.

Parameters:

  • locale (Symbol) (defaults to: :en)

    :en or :es for language

  • default_sqft (Integer) (defaults to: 35)

    Default square footage

  • default_hours (Integer) (defaults to: 4)

    Default hours per day

  • default_kwh (Float, nil) (defaults to: nil)

    Default electricity rate in cents per kWh (falls back to the locale average)

  • default_wattage (Integer, nil) (defaults to: nil)

    Default wattage per square foot (auto-derived from default_surface_type when provided)

  • default_surface_type (String) (defaults to: 'tile_stone')

    Pre-selected surface id from SURFACE_TYPES (e.g. 'tile_stone', 'laminate_floating', 'concrete_embedded')

  • default_room_type (String) (defaults to: 'bathroom')

    Pre-selected room id from ROOM_TYPES (e.g. 'bathroom', 'kitchen', 'bedroom')

  • layout (Symbol) (defaults to: :vertical)

    :vertical (default, card-style) or :horizontal (side-by-side for inline use)

  • smartplan (Hash, nil) (defaults to: nil)

    Optional SmartPlan CTA config (:product_type, :area_name, :project_link)



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/components/www/floor_heating_calculator_component.rb', line 139

def initialize(locale: :en, default_sqft: 35, default_hours: 4, default_kwh: nil, default_wattage: nil,
               default_surface_type: 'tile_stone', default_room_type: 'bathroom', layout: :vertical,
               smartplan: nil)
  super()
  @locale = locale.to_sym
  @default_surface_type = default_surface_type.to_s
  @default_room_type = default_room_type.to_s
  @default_sqft = default_sqft
  @default_hours = default_hours
  @default_kwh = default_kwh || calculate_default_kwh
  @default_wattage = default_wattage || wattage_for_surface(@default_surface_type)
  @layout = layout.to_sym
  @smartplan = smartplan
  resolve_smartplan_config if @smartplan
end

Instance Attribute Details

#sp_area_nameString (readonly)

Returns display name of the heated area (e.g. 'laminate floor').

Returns:

  • (String)

    display name of the heated area (e.g. 'laminate floor')



185
186
187
# File 'app/components/www/floor_heating_calculator_component.rb', line 185

def sp_area_name
  @sp_area_name
end

Returns the value of attribute sp_project_link.



185
# File 'app/components/www/floor_heating_calculator_component.rb', line 185

attr_reader :sp_area_name, :sp_project_link

Instance Method Details

#calculate_default_kwhFloat

Default electricity rate in cents per kWh for the component locale.

Returns:

  • (Float)


204
205
206
207
208
# File 'app/components/www/floor_heating_calculator_component.rb', line 204

def calculate_default_kwh
  # ElectricityRate returns rate in dollars, we need cents
  rate = ElectricityRate.get_average_rate_for_locale(@locale)
  rate ? (rate * 100).round(2) : DEFAULT_KWH_CENTS
end

#currency_symbolActiveSupport::SafeBuffer

Currency symbol wrapped in a superscript-style span.

Returns:

  • (ActiveSupport::SafeBuffer)


249
250
251
252
# File 'app/components/www/floor_heating_calculator_component.rb', line 249

def currency_symbol
  symbol = helpers.canada? ? 'C$' : '$'
  %(<span style="font-size:.55em;vertical-align:baseline;position:relative;top:-.45em;font-weight:500;color:#6c757d">#{symbol}</span>).html_safe
end

#horizontal?Boolean

Whether the component renders the side-by-side layout.

Returns:

  • (Boolean)


157
158
159
# File 'app/components/www/floor_heating_calculator_component.rb', line 157

def horizontal?
  @layout == :horizontal
end

#initial_daily_costFloat

Initial daily cost for the configured defaults.

Returns:

  • (Float)

    cost in dollars



237
238
239
# File 'app/components/www/floor_heating_calculator_component.rb', line 237

def initial_daily_cost
  calculate_cost(@default_sqft, @default_hours, @default_kwh, @default_wattage)
end

#initial_hourly_costFloat

Initial hourly cost for server-side rendering.

Returns:

  • (Float)

    cost in dollars



231
232
233
# File 'app/components/www/floor_heating_calculator_component.rb', line 231

def initial_hourly_cost
  initial_daily_cost / @default_hours
end

#initial_monthly_costFloat

Initial monthly cost (30 days of the daily cost).

Returns:

  • (Float)

    cost in dollars



243
244
245
# File 'app/components/www/floor_heating_calculator_component.rb', line 243

def initial_monthly_cost
  initial_daily_cost * 30
end

#room_typesArray<Hash>

Room types for the dropdown.

Returns:

  • (Array<Hash>)


225
226
227
# File 'app/components/www/floor_heating_calculator_component.rb', line 225

def room_types
  ROOM_TYPES.dup
end

#smartplan?Boolean

Whether a SmartPlan CTA section was configured.

Returns:

  • (Boolean)


163
164
165
# File 'app/components/www/floor_heating_calculator_component.rb', line 163

def smartplan?
  @smartplan.present?
end

#sp_app_typeString?

Returns SmartPlan application type.

Returns:

  • (String, nil)

    SmartPlan application type



172
173
# File 'app/components/www/floor_heating_calculator_component.rb', line 172

def sp_app_type     = @sp_config&.dig(:application_type)
# @return [String, nil] SmartPlan request link URL

Splide options for the SmartPlan slide carousel.

Returns:

  • (Hash)


189
190
191
192
# File 'app/components/www/floor_heating_calculator_component.rb', line 189

def sp_carousel_options
  { type: 'fade', perPage: 1, perMove: 1, arrows: true, pagination: true,
    autoplay: true, interval: 4000, pauseOnHover: true, rewind: true, speed: 600 }
end

#sp_iconString?

Returns SmartPlan icon name.

Returns:

  • (String, nil)

    SmartPlan icon name



170
171
# File 'app/components/www/floor_heating_calculator_component.rb', line 170

def sp_icon         = @sp_config&.dig(:icon)
# @return [String, nil] SmartPlan application type

Returns SmartPlan request link URL.

Returns:

  • (String, nil)

    SmartPlan request link URL



174
175
# File 'app/components/www/floor_heating_calculator_component.rb', line 174

def sp_link         = @sp_config&.dig(:smartplan_link)
# @return [String, nil] Rae avatar image URL

#sp_rae_avatarString?

Returns Rae avatar image URL.

Returns:

  • (String, nil)

    Rae avatar image URL



176
177
# File 'app/components/www/floor_heating_calculator_component.rb', line 176

def sp_rae_avatar   = @sp_config&.dig(:rae_avatar)
# @return [String, nil] Rae testimonial quote

#sp_rae_quoteString?

Returns Rae testimonial quote.

Returns:

  • (String, nil)

    Rae testimonial quote



178
179
# File 'app/components/www/floor_heating_calculator_component.rb', line 178

def sp_rae_quote    = @sp_config&.dig(:rae_quote)
# @return [Array<Hash>] SmartPlan carousel slide configs

#sp_slidesArray<Hash>

Returns SmartPlan carousel slide configs.

Returns:

  • (Array<Hash>)

    SmartPlan carousel slide configs



180
181
182
183
184
# File 'app/components/www/floor_heating_calculator_component.rb', line 180

def sp_slides       = @sp_config&.dig(:carousel_slides) || []
# @!attribute [r] sp_area_name
#   @return [String] display name of the heated area (e.g. 'laminate floor')
# @!attribute [r] sp_project_link
#   @return [String, nil] URL of a related project showcase

#sp_titleString?

Returns SmartPlan section title.

Returns:

  • (String, nil)

    SmartPlan section title



168
169
# File 'app/components/www/floor_heating_calculator_component.rb', line 168

def sp_title        = @sp_config&.dig(:title)
# @return [String, nil] SmartPlan icon name

#surface_typesArray<Hash>

Surface types for the dropdown.

Returns:

  • (Array<Hash>)


219
220
221
# File 'app/components/www/floor_heating_calculator_component.rb', line 219

def surface_types
  SURFACE_TYPES.dup
end

#t(key) ⇒ String

Translate a key in the component locale, falling back to English.

Parameters:

  • key (Symbol)

    translation key from TRANSLATIONS

Returns:

  • (String)


213
214
215
# File 'app/components/www/floor_heating_calculator_component.rb', line 213

def t(key)
  TRANSLATIONS[@locale][key] || TRANSLATIONS[:en][key] || key.to_s.humanize
end

#wattage_for_surface(surface_id) ⇒ Integer

Wattage per square foot for a surface type.

Parameters:

  • surface_id (String)

    surface id from SURFACE_TYPES

Returns:

  • (Integer)

    watts per square foot (15 when the surface is unknown)



197
198
199
200
# File 'app/components/www/floor_heating_calculator_component.rb', line 197

def wattage_for_surface(surface_id)
  match = SURFACE_TYPES.find { |s| s[:id] == surface_id }
  match ? match[:wattage] : 15
end