RTT Code - Frontend


This page provides lovelace code examples for the frontend of my recurring task tracking feature (RTT). This is the code that is responsible for the output shown in the image below.

There are dependencies on several HACS plugins (listed below), so keep an eye out for their references in the examples.

The UI for this feature is intended to be dynamic. Things like labels, icon colors, badges, and overdue indicators, all will update according to task state, task_action state, and date. To get the same UI treatment across all task items the decluttering card plugin is used to avoid unneccessary code repetition as much as possible. In the three code blocks that follow, the first is the main Lovelace yaml file (…probably good time to mention my UI is in yaml mode) but it is mostly used as a pointer to the decluttering templates that contain all of the formatting elements. These templates make use of the other plugin listed above and contain some templating for the dynamic processing.

This is the first block from the main UI file (note the two pointers to different decluttering templates):

...
    cards:

        # **** HOUSE TASK STACK ***
        - type: custom:vertical-stack-in-card
        cards:
            - type: custom:decluttering-card
            template: task_header
            variables:
                - header_title: House Tasks
                - overdue_chore_count: sensor.house_chore_overdue_count
                - std_chore_count: sensor.house_chore_count
                
            - type: custom:decluttering-card
            template: task_item
            variables:
                - chore: binary_sensor.chore_run_vacuum_bot
                - status: input_boolean.status_run_vacuum_bot
                - chore_label: Pickup for Vacuum Bot
                - chore_icon: mdi:robot-vacuum
                - chore_what: Run the autobot



Code for the task section header:

task_header:
  card:
    type: custom:vertical-stack-in-card

    cards:
      - type: custom:layout-card
        layout_type: grid
        layout:
          grid-template-columns: 55% 45%
          grid-template-rows: 1
          grid-template-areas: |
            "z01 z02"
        cards:
        - type: custom:mushroom-title-card
          title: "[[header_title]]"
          view_layout:
            grid-area: z01
        - type: custom:mushroom-chips-card
          chips:
            - type: conditional
              conditions:
                - condition: numeric_state
                  entity: "[[std_chore_count]]"
                  below: 1
                - condition: numeric_state
                  entity: "[[overdue_chore_count]]"
                  below: 1
              chip:
                type: template

                icon: mdi:thumb-up
                icon_color: green
                card_mod:
                  style: |
                    ha-card {
                      justify-content: center;
                      --chip-icon-size: 25px;
                      margin-top: 0.6em                      
                    }
            - type: conditional
              conditions:
                - condition: numeric_state
                  entity: "[[std_chore_count]]"
                  above: 0
              chip:
                type: template
                entity: "[[std_chore_count]]"
                content: |
                  {{ states('[[std_chore_count]]') }}
                card_mod:
                  style: |
                    ha-card {
                      justify-content: center;
                      font-variant: small-caps;
                      --chip-background: orange;
                      --chip-height: 25px;
                      --chip-font-size: 14px;
                      --text-color: white;
                      margin-top: 1.1em 
                    }
            - type: conditional
              conditions:
                - condition: numeric_state
                  entity: "[[overdue_chore_count]]"
                  above: 0
              chip:
                type: template
                entity: "[[overdue_chore_count]]"
                content: "{{ states('[[overdue_chore_count]]') }}"
                card_mod:
                  style: |
                    ha-card {
                      justify-content: center;
                      font-variant: small-caps;
                      --chip-background: red;
                      --chip-height: 25px;
                      --chip-font-size: 14px;
                      
                      --text-color: white;
                      margin-top: 1.1em
                      
                    }
          view_layout:
            grid-area: z02



Task item code (Note: to avoid possible confusion, this is a Decluttering Card “template” that uses Jinja templating):

task_item:
  card:
    type: custom:layout-card
    layout_type: grid
    layout:    
      grid-template-columns: 80% 20%
      grid-template-rows: 1
      grid-template-areas: |
        "z01 z02"

    cards:
      - type: custom:mushroom-template-card
        entity: "[[chore]]"
        icon: "[[chore_icon]]"
        icon_color: |
          {% if is_state('[[chore]]', 'on') and is_state('[[status]]', 'off') %}
            {% if state_attr('[[chore]]', 'overdue') == 'true' %}
              red
            {% else %}
              orange
            {% endif %}
          {% elif is_state('[[chore]]', 'off') and is_state('[[status]]', 'off')%}
            grey
          {% else %}
            green
          {% endif %}        
        badge_icon: |
          {% set next_trigger = strptime(state_attr('[[chore]]', 'next_trigger')[:10], '%Y-%m-%d') %}
          {% set days_b4_trigger = (next_trigger.date() - now().date()).days %}
          {% if (days_b4_trigger == 1 and now().weekday() != 5) or (days_b4_trigger == 2 and now().weekday() == 4) %}
            mdi:clock-alert-outline
          {% endif %}
        badge_color: |
          {% set next_trigger = strptime(state_attr('[[chore]]', 'next_trigger')[:10], '%Y-%m-%d') %}
          {% set days_b4_trigger = (next_trigger.date() - now().date()).days %}
          {% if (days_b4_trigger == 1 and now().weekday() != 5) or (days_b4_trigger == 2 and now().weekday() == 4) %}
            orange
          {% endif %}
        primary: "[[chore_label]]"
        secondary: |
          {% set last_triggered = strptime(state_attr('[[chore]]', 'last_triggered')[:10], '%Y-%m-%d') %}
          {% set days_since = (now().date() - last_triggered.date()).days %}
          {% set day_number = now().day %}
          {% set last_completed = strptime(state_attr('[[chore]]', 'last_completed')[:10], '%Y-%m-%d') %}
          {% set days_since_completed = (now().date() - last_completed.date()).days %}
          {% set trigger_type = state_attr('[[chore]]','trigger_type') %}
          {% set days_b4_trigger = "na" %}
          
          {% if trigger_type == "time" %}
            {% set next_trigger = strptime(state_attr('[[chore]]', 'next_trigger')[:10], '%Y-%m-%d') %}      
            {% set days_b4_trigger = (next_trigger.date() - now().date()).days %}          
          {% endif %}

          {% if is_state('[[chore]]', 'on') and is_state('[[status]]', 'off') %}
            {% if state_attr('[[chore]]', 'overdue') == 'true' %}
              Overdue - {{days_since | round(0) }} {{'day' if days_since == 1 else 'days' }}
            {% else %}
              Due
            {% endif %} 
          {% elif days_since_completed == 0  %}
            Done: today
          {% elif days_since_completed == 1 and (days_b4_trigger == "na" or days_b4_trigger >= 2) %}
            Done: yesterday
          {% elif (days_b4_trigger == "na" or days_b4_trigger >= 3) %}
            Last done: {{ as_timestamp(state_attr('[[chore]]','last_completed')) | timestamp_custom('%Y-%m-%d') }}
          {% elif days_b4_trigger == 2 and day_number == 4 %}
            On deck for this weekend
          {% elif days_b4_trigger == 1 and day_number == 5 %}
            Due tomorrow
          {% else %}
            On deck for tomorrow
          {% endif %}
        tap_action:
          action: fire-dom-event
          browser_mod:
            service: browser_mod.popup
            data:
              title: Task detail
              content:
                type: markdown
                content: |
                  {% set last_complete = strptime(state_attr('[[chore]]', 'last_completed')[:10], '%Y-%m-%d') %}
                  {% set day_diff = (now().date() - last_complete.date()).days %}
                  {% set trigger_type = state_attr('[[chore]]', 'trigger_type') %}
                  {% set trigger_interval = state_attr('[[chore]]', 'trigger_interval') %}

                  {% if trigger_type == "time" %}
                    {% set next_trigger = strptime(state_attr('[[chore]]', 'next_trigger')[:10], '%Y-%m-%d') %}
                    {% set days_b4_trigger = (next_trigger.date() - now().date()).days %}
                  {% endif %}

                  {% if day_diff == 0 %}
                    {% set last_value = 'Today' %}
                  {% elif day_diff == 1 %}
                    {% set last_value = 'Yesterday' %}
                  {% else %}
                    {% set last_value = as_timestamp(state_attr('[[chore]]','last_completed')) | timestamp_custom('%A, %Y-%m-%d') %}
                  {% endif %}
                  
                  {% if trigger_type == "time" %}
                    {% if days_b4_trigger == 0 %}
                      {% set next_value = 'Today' %}
                    {% elif days_b4_trigger == 1 %}
                      {% set next_value = 'Tomorrow' %}
                    {% else %}
                      {% set next_value = as_timestamp(state_attr('[[chore]]','next_trigger')) | timestamp_custom('%A, %Y-%m-%d') %}
                    {% endif %}
                  {% else %}
                    {% if states('[[chore]]') == "on" %}
                      {% set next_value = 'Today' %}
                    {% else %}
                      {% set next_value = state_attr('[[chore]]', 'next_trigger') %}
                    {% endif %}
                  {% endif %}

                  **What**: [[chore_what]]
                  **When**: {{ trigger_interval }}
                  **Last**: {{ last_value }}
                  **Next**: {{ next_value }}
                card_mod:
                  style: |
                    ha-card {
                      border: none !important;                                     
                      box-shadow: none !important;
                    }
        card_mod:
          style: |
            ha-card {
              {% set day_trigger = strptime(state_attr('[[chore]]', 'next_trigger')[:10], '%Y-%m-%d') %}      
              {% set day_away = (day_trigger.date() - now().date()).days %}
              
              {% if is_state('[[chore]]', 'on')  and is_state('[[status]]', 'off') %}
                {% if state_attr('[[chore]]', 'overdue') == 'true' %}
                  --card-secondary-color: red;
                {% else %}
                  --card-secondary-color: orange;
                {% endif %} 
              {% else %}
                --card-secondary-color: grey;
              {% endif %}

              {% if day_away < 2 %}
                
              {% endif %}
            }
        view_layout:
          grid-area: z01

      - type: custom:mushroom-chips-card
        chips:
          #task is on but the task_action status has not been performed
          - type: conditional
            conditions:
              - entity: "[[chore]]"
                state: 'on'
              - entity: "[[status]]"
                state: 'off'
            chip:
              type: template
              entity: "[[status]]"
              icon_color: |
                {% if state_attr('[[chore]]', 'overdue') == 'true' %}
                  red
                {% else %}
                  orange
                {% endif %}
              icon: mdi:checkbox-blank-circle-outline
              tap_action:
                action: toggle

          #task is off b/c the task_action status has recently been marked as done
          - type: conditional
            conditions:
              - entity: "[[chore]]"
                state: 'off'
              - entity: "[[status]]"
                state: 'on'
            chip:
              type: template
              #entity: "[[status]]"
              icon_color: green
              icon: mdi:check-circle-outline
              tap_action:
                action: none

          #task is off and task_action status is off. Implies no action on either for over a day      
          - type: conditional
            conditions:
              - entity: "[[chore]]"
                state: 'off'
              - entity: "[[status]]"
                state: 'off'
            chip:
              type: template
              entity: "[[status]]"
              icon_color: grey
              icon: mdi:check-circle-outline
              tap_action:
                action: none
        card_mod:
          style: |
            ha-card {
              margin-top: 0.8em !important;
              margin-left: 2.0em !important;
            } 
        view_layout:
          grid-area: z02
    card_mod:
      style: |
        ha-card {
          {% if is_state('[[chore]]', 'on') %}
            background: rgba(191, 142, 6, 0.4);
          {% endif %}
        }