Task #380: Darken cell borders to match row color in PDF and web UI

When a row has a color (e.g. red), the cell borders now use a darker
shade of the same color instead of the default gray (#d1d5db).

PDF renderer (pdf-html-renderer.ts):
- Added ROW_COLOR_BORDER map with darker shades for each color
- Colored rows now get both background-color and border-color inline
- Non-merged rows: # and meeting cells get bg+border, attendees and
  time cells get border only (preserving partial coloring behavior)
- Merged rows: all cells get bg+border via coloredStyle

Web UI (executive-meetings.tsx):
- Added 'border' field to ROW_COLOR_OPTIONS with matching darker shades
- tintedCellStyle always applies borderColor when rowBorder exists
- Number cell inline style includes borderColor in both rowBg and
  tintBg (current meeting highlight) branches
- Merge cell style includes borderColor in both tint and rowBg branches
- Border color persists even when current-meeting highlight is active

Color mapping:
  red:    fill #fee2e2 → border #fca5a5
  amber:  fill #fef3c7 → border #fcd34d
  green:  fill #dcfce7 → border #86efac
  blue:   fill #dbeafe → border #93c5fd
  violet: fill #ede9fe → border #c4b5fd
  gray:   fill #f3f4f6 → border #d1d5db
This commit is contained in:
Riyadh
2026-05-04 11:33:56 +00:00
parent e04404171e
commit dd58ba230b
2 changed files with 41 additions and 19 deletions
@@ -22,6 +22,15 @@ const ROW_COLOR_FILL: Record<string, string> = {
gray: "#f3f4f6",
};
const ROW_COLOR_BORDER: Record<string, string> = {
red: "#fca5a5",
amber: "#fcd34d",
green: "#86efac",
blue: "#93c5fd",
violet: "#c4b5fd",
gray: "#d1d5db",
};
function fontsDir(): string {
const here =
typeof __dirname === "string"
@@ -196,27 +205,30 @@ function buildMeetingRow(meeting: PdfMeeting, input: RenderPdfInput): string {
const location = meeting.location ? `<br/><span style="font-size:90%">${esc(meeting.location)}</span>` : "";
const time = formatTimeRange(meeting.startTime, meeting.endTime, input.lang);
const attendeesHtml = buildAttendeesHtml(meeting.attendees);
const bgStyle = meeting.rowColor && ROW_COLOR_FILL[meeting.rowColor]
? `background-color:${ROW_COLOR_FILL[meeting.rowColor]};`
const hasFill = !!(meeting.rowColor && ROW_COLOR_FILL[meeting.rowColor]);
const bgStyle = hasFill ? `background-color:${ROW_COLOR_FILL[meeting.rowColor!]};` : "";
const borderStyle = hasFill && ROW_COLOR_BORDER[meeting.rowColor!]
? `border-color:${ROW_COLOR_BORDER[meeting.rowColor!]};`
: "";
const coloredStyle = bgStyle + borderStyle;
const MERGE_COL_INDEX: Record<string, number> = {
number: 0, meeting: 1, attendees: 2, time: 3,
};
const cellsLtr: TableCell[] = [
{ html: esc(String(meeting.dailyNumber)), cls: "num-cell", style: bgStyle },
{ html: esc(title) + location, cls: "meeting-cell", style: bgStyle },
{ html: attendeesHtml, cls: "attendees-cell", style: "" },
{ html: `<span dir="ltr">${esc(time)}</span>`, cls: "time-cell", style: "" },
{ html: esc(String(meeting.dailyNumber)), cls: "num-cell", style: coloredStyle },
{ html: esc(title) + location, cls: "meeting-cell", style: coloredStyle },
{ html: attendeesHtml, cls: "attendees-cell", style: borderStyle },
{ html: `<span dir="ltr">${esc(time)}</span>`, cls: "time-cell", style: borderStyle },
];
const renderCells = (cells: TableCell[], allColored: boolean): string =>
cells
.map((c) => {
const cs = c.colspan ? ` colspan="${c.colspan}"` : "";
const bg = allColored ? bgStyle : c.style;
return `<td class="${c.cls}" style="${bg}"${cs}>${c.html}</td>`;
const st = allColored ? coloredStyle : c.style;
return `<td class="${c.cls}" style="${st}"${cs}>${c.html}</td>`;
})
.join("");