PCO Plan 1.7.0: a Teams tile — who is scheduled, with photos #23

Merged
michael merged 2 commits from claude/pco-teams-tile into main 2026-09-24 15:28:05 -04:00
3 changed files with 38 additions and 86 deletions
Showing only changes of commit 6c0aee5abf - Show all commits
+34 -56
View File
@@ -411,21 +411,35 @@
"h": 2
},
"instanceSchema": {
"columns": {
"type": "select",
"label": "Columns",
"default": "auto",
"options": [
{
"value": "auto",
"label": "Fit the tile"
},
{
"value": "1",
"label": "One"
},
{
"value": "2",
"label": "Two"
},
{
"value": "3",
"label": "Three"
}
]
},
"teams": {
"type": "multiselect",
"label": "Teams",
"group": "Teams",
"optionsRoute": "/teams",
"columns": 1,
"allByDefault": true
},
"positions": {
"type": "multiselect",
"label": "Positions",
"group": "Positions",
"optionsRoute": "/positions",
"columns": 2,
"allByDefault": true
"allByDefault": true,
"optionsRoute": "/teams"
},
"headerTemplate": {
"type": "template",
@@ -484,12 +498,6 @@
"default": false,
"group": "Colors"
},
"headerGradient": {
"type": "boolean",
"label": "Header fade",
"default": true,
"group": "Colors"
},
"tintRows": {
"type": "boolean",
"label": "Tint rows too",
@@ -497,23 +505,29 @@
"group": "Colors",
"showWhen": "tintTeams"
},
"headerGradient": {
"type": "boolean",
"label": "Header fade",
"default": true,
"group": "Colors"
},
"confirmedColor": {
"type": "color",
"label": "Confirmed",
"default": "#2ee59a",
"group": "Status colors"
"group": "Colors"
},
"unconfirmedColor": {
"type": "color",
"label": "Unconfirmed",
"default": "#f0b429",
"group": "Status colors"
"group": "Colors"
},
"declinedColor": {
"type": "color",
"label": "Declined",
"default": "#f4433c",
"group": "Status colors"
"group": "Colors"
},
"showTopBar": {
"type": "boolean",
@@ -580,52 +594,16 @@
"label": "Teams with nobody",
"default": false,
"group": "Show"
},
"columns": {
"type": "select",
"label": "Columns",
"default": "auto",
"group": "Show",
"options": [
{
"value": "auto",
"label": "Fit the tile"
},
{
"value": "1",
"label": "One"
},
{
"value": "2",
"label": "Two"
},
{
"value": "3",
"label": "Three"
}
]
}
},
"instanceGroups": {
"Top bar": {
"collapsed": false
},
"Teams": {
"collapsed": true,
"help": "Untick a team to leave it out of this tile."
},
"Positions": {
"collapsed": true,
"help": "Untick a position to leave it out. Positions follow the teams above."
},
"Colors": {
"collapsed": false,
"columns": 2
},
"Status colors": {
"collapsed": true,
"columns": 3
},
"Show": {
"collapsed": true,
"columns": 2
+4 -28
View File
@@ -18,7 +18,6 @@
* GET /note-categories → { options } for a tile's "Item notes" categories:
* the service type's item note categories, by name
* GET /teams → { options } for a Teams tile's "Teams" switches
* GET /positions → { options } for a Teams tile's "Positions" switches
* GET /team-avatar/<planPersonId> → that person's photo, proxied + cached
* GET /timers → snapshot for the Timers module (guide: "Offering
* GET /timers/stream → SSE, `timers` events timers to the Timers module")
@@ -821,24 +820,6 @@ module.exports = {
return { options: state.teams.map((t) => ({ value: t.id, label: t.name })) };
}
/**
* Options for a Teams tile's "Positions" switches. Positions are named,
* not numbered, in Planning Center, so the name is the value; the team
* name groups them so two "Leader" positions stay apart in the list.
*/
function positionOptions() {
const seen = new Set();
const options = [];
for (const team of state.teams) {
for (const m of team.members) {
const value = m.position || '(no position)';
if (seen.has(value)) continue;
seen.add(value);
options.push({ value, label: value, group: team.name });
}
}
return { options };
}
async function readPlan() {
if (!pco.configured || !serviceTypeId) return;
@@ -1589,7 +1570,6 @@ module.exports = {
documentOptions,
noteCategoryOptions,
teamOptions,
positionOptions,
avatarUrlFor: (id) => teamPhotos.get(String(id)) || "",
};
@@ -1684,19 +1664,15 @@ module.exports = {
},
/**
* Options for a Teams tile's "Teams" and "Positions" switches. Like the
* note categories, always a 200 with a list: an empty one simply means
* there is nothing scheduled to choose from yet, which the gear window
* says plainly.
* Options for a Teams tile's "Teams" switches. Like the note
* categories, always a 200 with a list: an empty one simply means there
* is nothing scheduled to choose from yet, which the gear window says
* plainly.
*/
'GET /teams': (req, res) => {
json(res, 200, current ? current.teamOptions() : { options: [] });
},
'GET /positions': (req, res) => {
json(res, 200, current ? current.positionOptions() : { options: [] });
},
/**
* A scheduled person's photo, proxied from Planning Center and cached.
* The tile asks by plan-person id and never sees the CDN URL; an id the
-2
View File
@@ -94,12 +94,10 @@ export default function createTeams({ root, moduleApi }) {
function visibleTeams() {
const teams = Array.isArray(state?.teams) ? state.teams : [];
const wantTeams = chosen('teams');
const wantPositions = chosen('positions');
const out = [];
for (const team of teams) {
if (wantTeams && !wantTeams.includes(team.id)) continue;
const members = team.members.filter((m) => {
if (wantPositions && !wantPositions.includes(m.position || '(no position)')) return false;
if (m.status === 'declined' && !on('showDeclined')) return false;
if (m.status === 'unconfirmed' && !on('showUnconfirmed')) return false;
return true;