Color each opponent's column by their disposition

Colors read directly from the template's own "Disposition Matrix"
section (Template!B32 header, B33:H37) via its actual XLSX cell fill
colors (not visible through CSV export, and not guessed) — a clean
1:1 mapping in the row/column headers, same 5 canonical dispositions
and spelling as CANONICAL_DISPOSITIONS in main.js:
  Purge the Foe -> #E06666, Take and Hold -> #93C47D,
  Priority Assets -> #FFD966, Disruption -> #4A86E8,
  Reconnaissance -> #76A5AF
(the interior of that section is a separate disposition-vs-disposition
matchup heatmap, not used here).

Whole opponent column (list/faction/name rows) gets colored, per the
requested scope. Needs the new tab's sheetId, which duplicateSheet's
reply already includes (only .title was being read out of it before) —
sent as its own batchUpdate once that's known, since a single
batchUpdate can't both create a sheet and reference the id it produces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
This commit is contained in:
2026-09-04 19:00:26 -05:00
co-authored by Claude Sonnet 5
parent 30276c7ad5
commit 52402d7547
+43 -2
View File
@@ -251,6 +251,24 @@ const a1 = (sheetTitle, col, row) => `'${sheetTitle.replace(/'/g, "''")}'!${colL
/* ---- Row/column value builders ---- */ /* ---- Row/column value builders ---- */
// The template's own "Disposition Matrix" section (Template!B32, header +
// B33:H37) color-codes each of the 5 canonical ITC dispositions — same
// spelling/order as CANONICAL_DISPOSITIONS in main.js. Read directly from
// the template's actual cell fill colors (XLSX export, since the Sheets
// API's formatting isn't visible via CSV) rather than guessed.
const DISPOSITION_COLORS = {
"Purge the Foe": "E06666",
"Take and Hold": "93C47D",
"Priority Assets": "FFD966",
"Disruption": "4A86E8",
"Reconnaissance": "76A5AF",
};
function hexToRgbColor(hex) {
const n = parseInt(hex, 16);
return { red: ((n >> 16) & 255) / 255, green: ((n >> 8) & 255) / 255, blue: (n & 255) / 255 };
}
// fullName() takes the nested BCP user object ({firstName, lastName}), not // fullName() takes the nested BCP user object ({firstName, lastName}), not
// the roster/teamplayer entry itself — same convention main.js's own // the roster/teamplayer entry itself — same convention main.js's own
// playerRowHTML() uses, matched here. // playerRowHTML() uses, matched here.
@@ -305,7 +323,7 @@ export async function generateMatrix({ event, ourTeam, opposingTeams, careerReco
const size = sizes.find(s => s >= Math.max(oppTeam.members.length, ourTeam.members.length)) || sizes[sizes.length - 1]; const size = sizes.find(s => s >= Math.max(oppTeam.members.length, ourTeam.members.length)) || sizes[sizes.length - 1];
const template = templates.get(size); const template = templates.get(size);
let title; let title, newSheetId;
try { try {
const dup = await batchUpdate(token, copy.id, [{ const dup = await batchUpdate(token, copy.id, [{
duplicateSheet: { duplicateSheet: {
@@ -313,12 +331,35 @@ export async function generateMatrix({ event, ourTeam, opposingTeams, careerReco
newSheetName: oppTeam.name.slice(0, 100), newSheetName: oppTeam.name.slice(0, 100),
}, },
}]); }]);
title = dup.replies[0].duplicateSheet.properties.title; ({ title, sheetId: newSheetId } = dup.replies[0].duplicateSheet.properties);
} catch (err) { } catch (err) {
skipped.push({ team: oppTeam.name, reason: `Couldn't duplicate tab: ${err.message}` }); skipped.push({ team: oppTeam.name, reason: `Couldn't duplicate tab: ${err.message}` });
continue; continue;
} }
// Color each opponent's whole column (list/faction/name rows) by their
// disposition, matching the template's own Disposition Matrix colors —
// a separate batchUpdate since it needs newSheetId, only known now.
const colorRequests = oppTeam.members.map((member, i) => {
const col = template.opponentCols[i];
const color = col != null ? DISPOSITION_COLORS[member.subFaction?.name] : null;
if (!color) return null;
return {
repeatCell: {
range: { sheetId: newSheetId, startRowIndex: template.listRow, endRowIndex: template.opponentRow + 1, startColumnIndex: col, endColumnIndex: col + 1 },
cell: { userEnteredFormat: { backgroundColor: hexToRgbColor(color) } },
fields: "userEnteredFormat.backgroundColor",
},
};
}).filter(Boolean);
if (colorRequests.length) {
try {
await batchUpdate(token, copy.id, colorRequests);
} catch (err) {
skipped.push({ team: oppTeam.name, reason: `Tab created but couldn't apply disposition colors: ${err.message}` });
}
}
const data = []; const data = [];
ourTeam.members.forEach((member, i) => { ourTeam.members.forEach((member, i) => {
const row = template.playerRows[i]; const row = template.playerRows[i];