Wednesday, March 3, 2021

My custom Planning Center Services plan report. You're welcome.

To all my fellow Planning Center Services jockeys out there, I thought I'd share with you a custom Plan report that I created (using one of the built-in reports as a baseline, mind you) and have been using for years now. Now, before you assume that all I've done here is fiddle with colors and fonts and page layout, hear me out. There are some features of this template that I want you to know about.

First—and the primary reason that I love this template so much—is that it handles Plan and Item Note categories dynamically. Unlike the built-in templates, this report template doesn't have to be modified just because you use different Notes categories than the default Planning Center ones. In fact, it doesn't care what specific Notes categories you have at all. Rather, it examines the Plan and its Items and dynamically determines which Note types are actually employed. This means two things: that it doesn't waste any page/screen real estate on unused Note categories, and you can use the same template for entirely different Service Types with entirely different categories defined!

The second, admittedly minor, thing that I like about this template is that it adds a column for icons that appear next to certain Item types. So Songs show up with a music notes icon (♫) next to them; Media items show a "play" triangle (⏵). It's a little thing, but it draws the eye to certain Item types quickly.

Finally, the template itself allows some quick customization of the features it employs via a collection of boolean variables at the top.  This allows you to use the same basic template for multiple custom Reports, but with little easy tweaks to goven the feature set for each one.  So, for example, I have a Report that shows everything that I distribute to my A/V operators and band members.  But I have another one using this same template that I use for our Ushers and Greeters where the variables show_plan_people, show_plan_notes, and show_item_notes have been set to false.

So, without further ado, my template. Please feel free to copy, use, and modify this thing to better suit your church's needs!

<!-- QUICK CUSTOMIZATION: {% assign show_plan_notes = true %} {% assign show_plan_people = true %} {% assign show_item_times = true %} {% assign show_item_length = true %} {% assign show_item_description = true %} {% assign show_item_notes = true %} --> <html> <head> <title>{{ plan.ministry.name }}:: {{ plan.dates }}</title> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,600;1,400;1,600&display=swap" rel="stylesheet"> <style> body { padding: 0; margin: 5px; color: #222; font-family: Roboto, sans-serif; font-size: 14pt; } h1 { font-size: 1.5em; font-weight: normal; text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.9); } h2 { font-size: 1.1em; font-weight: normal; padding: 20px 35px; margin: 0; line-height: 1em; text-shadow: 0 1px 0 white; } h3 { font-size: 0.9em; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } pre { margin: 0; padding: 0; white-space: pre-wrap; font-size: .9em; } table#plan { width: 100%; border-collapse: collapse; margin-top:0px; margin-bottom: 15px; font-size: 10pt; } table#plan tr { border-bottom: 1px solid #ddd; } table#plan thead { display: table-header-group; } table#plan th { color: white; background: #444; font-weight: bold; padding: 0.5em 1em; vertical-align: middle; } table#plan td { text-align: center; padding: 0.5em 1em; } table#plan .icon { padding: 0; } table#plan .preservice .time, table#plan .preservice .length { color: #aaa; } table#plan .time { width: 3em; text-align: center; } table#plan .length { width: 3em; text-align: center; } table#plan .note_column { text-align: left; background: #f8f8f8; border-left: 2px solid white; } table#plan .header { text-align: left; font-weight: bold; color: black; background: #ddd; } table#plan .note { font-weight: bold; text-align: left; vertical-align: middle; min-width: 1in; } table#plan .element { min-width: 3in; text-align: left; } table#plan .element .description { font-size: 0.86em; color: #444; font-style: italic; } table#header { width: 100%; border: none; padding: 0; border-collapse: collapse; } table#header td { padding: 2px; vertical-align: top; } .artwork { float: left; margin-right: 15px; height: 40px; } .note_content { background: yellow; padding: 4px 8px 0; border-radius: 0.3em; } #header { padding: 5px 20px; background: #000; color: white; line-height: .8em; margin: 0; } #header img { float: right; height: 60px; margin-right: -15px; } #titles { color: #333; font-size: 1.1em; font-weight: normal; padding: 10px 20px; margin: 0; line-height: 1.1em; text-shadow: 0 1px 0 white; min-height: 45px; } #plan_title { font-style: normal; font-size: 0.8em; display: block; color: #666; margin: 4px 0 0 0; } #plan_notes, #plan_people { background: #f8f8f8; margin: 1em 0; border-left: 4px solid black; padding: 0.5em 0 1em 2em; } #plan_notes { font-size: 0.8em; background: #ffffd0; } #plan_notes .plan_note { margin-top: 1em; } #plan_notes .plan_note_category { font-weight: bold; } #plan_people { font-size: 0.9em; } #plan_people .person { font-size: 0.7em; margin-right: 15px; display: inline-block; } #plan_people .person_status_U { opacity: 0.5; } #plan_people .person_category { clear: both; padding-top: 1em; } #plan_people .person_category p { font-size: 0.8em; margin: 0; } #plan_people .person_position { font-style: italic; } #plan_people .position { font-weight: bold; } </style> </head> <body> <!-- If showing item notes, first build a list of item note categories in use. --> {% if show_item_notes %} {% assign used_item_note_categories = '' | split: '' %} {% for item in plan.items %} {% for note in item.notes %} {% assign category = note.category.name | split: '|' %} {% assign used_item_note_categories = used_item_note_categories | concat: category %} {% endfor %} {% endfor %} {% assign used_item_note_categories = used_item_note_categories | uniq | sort %} {% endif %} <div id="header"> <h1>{{ plan.ministry.name }} - {{ plan.dates }}</h1> </div> <div id="titles"> <img class="artwork" onerror="this.style.display='none'" src="{{ plan.series_artwork_url }}" /> <div id="series_title">{{ plan.series_title }}</div> <div id="plan_title">{{ plan.plan_title }}</div> </div> <!-- If showing plan notes, first build a list of plan note categories in use, then iterate. --> {% if show_plan_notes %} {% assign used_plan_note_categories = '' | split: '' %} {% for note in plan.notes %} {% assign category = note.category.name | split: '|' %} {% assign used_plan_note_categories = used_plan_note_categories | concat: category %} {% endfor %} {% assign used_plan_note_categories = used_plan_note_categories | uniq | sort %} {% unless used_plan_note_categories == empty %} <div id="plan_notes"> {% for plan_note_category in used_plan_note_categories %} <div class="plan_note"> <div class="plan_note_category">{{plan_note_category}}</div> {% for note in plan.notes %} {% if note.category_name == plan_note_category %} <div>{{note.note}}</div> {% endif %} {% endfor %} </div> {% endfor %} </div> {% endunless %} {% endif %} <table id="plan"> <thead> <tr> {% if show_item_times %} {% for time in plan.plan_times %} <th class="time">@{{ time.starts_at | date: '%I:%M' }}</th> <!-- {% increment colspan %} --> {% endfor %} {% endif %} <th class="icon"></th> <!-- {% increment colspan %} --> <th align="left">Element</th> <!-- {% increment colspan %} --> {% if show_item_notes %} {% for category in used_item_note_categories %} <th class="note">{{ category }}</th> <!-- {% increment colspan %} --> {% endfor %} {% endif %} {% if show_item_length %} <th class="length">Length</th> <!-- {% increment colspan %} --> {% endif %} </tr> </thead> <tbody> {% for item in plan.items %} {% case item.item_type %} {% when 'Header' %} <!-- Header rows --> <tr> <td colspan="{{ colspan }}" class="header">{{ item.title }}</td> </tr> {% else %} <!-- Non-header rows --> {% assign item_class = ''%} {% if item.is_preservice or item.is_postservice %} {% assign item_class = 'preservice ' %} {% endif %} <tr class="{{ item_class }}"> {% if show_item_times %} {% for time in plan.plan_times %} <td class="time item {{ item_class }} {{ item.item_type }}"> {% for item_time in item.times %} {% if item_time.time_id == time.id %} {{ item_time.actual_time | date: '%I:%M' | downcase }} {% endif %} {% endfor %} </td> {% endfor %} {% endif %} <td class="item icon"> {% if item.item_type == "Song" %}&#9835;{% endif %} {% if item.item_type == "Media" %}&#9205;{% endif %} </td> <td class="item element {{ item_class }} {{ item.item_type }}"> {{ item.title }}{% if item.song %} [{{ item.arrangement.music_key }}]{% endif %} {% if show_item_description %}<div class="description">{{ item.description }}</div>{% endif %} </td> {% if show_item_notes %} {% for category in used_item_note_categories %} <td class="item note note_column {{ item_class }} {{ item.item_type }}"> {% for note in item.notes %} {% if note.category_name == category %} {{ note.note }} {% endif %} {% endfor %} </td> {% endfor %} {% endif %} {% if show_item_length %} <td class="item length {{ item_class }} {{ item.item_type }}">{{ item.length }}</td> {% endif %} </tr> {% endcase %} {% endfor %} </tbody> </table> {% if show_plan_people %} <!-- If showing plan people, first build a list of person categories in use, then iterate. --> {% assign used_person_categories = '' | split: '' %} {% for plan_person in plan.plan_people_not_declined %} {% assign category = plan_person.category.name | split: '|' %} {% assign used_person_categories = used_person_categories | concat: category %} {% endfor %} {% assign used_person_categories = used_person_categories | uniq | sort %} {% unless used_person_categories == empty %} <div id="plan_people"> {% for plan_person_category in used_person_categories %} <div class="person_category"> <p><strong>{{ plan_person_category }}</strong></p> {% for plan_person in plan.plan_people_not_declined %} {% if plan_person.person.name != null %}{% if plan_person.category.name == plan_person_category %} <div class="person person_status_{{ plan_person.status }}"> &bull; <span class="person_name">{{ plan_person.person.name }}</span> <span class="person_position">({{ plan_person.position }})</span> </div> {% endif %}{% endif %} {% endfor %} </div> {% endfor %} </div> {% endunless %} {% endif %} </body> </html>

As of a few minutes ago, I've submitted this report template back to the fine folks that develop Planning Center Services so that they may consider integrating its enhancements into the core product itself.