Commit Graph

703 Commits

Author SHA1 Message Date
Riyadh 3899a4ab57 Task #391: Live clock in Executive Meetings header
Added a `HeaderClock` component to artifacts/tx-os/src/pages/executive-meetings.tsx
that renders next to the "Export PDF" button in the sticky page header.

- Updates every 1s via setInterval (cleared on unmount)
- Locale-aware formatting via Intl.DateTimeFormat (ar / en) with hour12
- Hidden on <sm screens (`hidden sm:flex`) and on print
- Uses existing palette (text-[#0B1E3F]) + Clock lucide icon
- tabular-nums to prevent jitter as digits change
- data-testid="em-header-clock" for future tests

No deviations from plan. tx-os typecheck passes.
2026-05-05 09:48:22 +00:00
Riyadh c8ab4165ba Task #390: Always play notification sounds (foreground + background)
Removed the `document.visibilityState === "visible"` early-return from
two playback paths so notification sounds fire even when the Tx OS tab
is the active foreground tab:

- `use-notifications-socket.ts` — order + meeting socket events.
- `upcoming-meeting-alert.tsx` — 5-min upcoming-meeting alert chime.

All other gates remain unchanged: global mute, per-channel sound and
vibration toggles, the ~600ms throttle in the audio player, and the
per-meeting `playedRef` dedupe by `meetingId`.

Verified `pnpm --filter @workspace/tx-os typecheck` passes.
2026-05-05 08:31:27 +00:00
Riyadh e9c094f029 Transitioned from Plan to Build mode 2026-05-05 08:30:46 +00:00
Riyadh dc94be41ee Task #389: Notification sounds + vibration with per-user customization
DB
- Added user pref columns; vibration is per-channel
  (vibrationEnabledOrder + vibrationEnabledMeeting). Defaults: ding/chime
  sounds, vibration on, mute off. Volume uses the device system level —
  no persisted volume field.

API
- PATCH /auth/me/notification-preferences (requireAuth, whitelisted partial
  update, integer guard for volume). buildAuthUser exposes all new fields.
- OpenAPI spec updated; orval client + zod regenerated.

Frontend
- Static sound library: 8 short WAV assets under public/sounds/ + manifest
  (notification-sound-manifest.ts) mapping id -> labelKey -> URL.
- HTMLAudio-based player (notification-sounds.ts) with throttle, unlock,
  cache, and AUTOPLAY_BLOCKED_EVENT dispatch when play is denied pre-gesture.
- Settings popover: global mute + slot tabs (orders/meetings) with
  per-channel sound + per-channel vibration + per-channel toggle, sound list
  with one-tap previews. Concurrency-safe optimistic updates (monotonic seq
  counter). RTL-correct toggle knob transforms.
- QuickMuteButton: one-tap topbar mute control (Volume2/VolumeX) with
  confirmation toast.
- useAudioUnlock: unlocks audio on first interaction.
- useAutoplayHint: toasts a localized "click anywhere to enable" hint when
  the player reports a blocked play attempt.
- Socket hook plays the right per-channel sound + vibration on
  notification_created (orders, executive_meeting) when the tab is hidden.
- UpcomingMeetingAlert: plays meeting reminder once per new eligible
  meeting (deduped by meetingId, survives 30s polling refetches). Skipped
  when the tab is currently visible — sound only fires when backgrounded.
- AR/EN translations added.

Pre-existing TS errors in executive-meetings.ts (font_settings) and
pre-existing test workflow failures are unrelated.
2026-05-05 08:24:27 +00:00
Riyadh 88cbbcfc46 Task #389: Notification sounds + vibration with per-user customization
DB
- Added 7 user pref columns; vibration is now per-channel
  (vibrationEnabledOrder + vibrationEnabledMeeting). Defaults: ding/chime
  sounds, vibration on, mute off, volume 70.

API
- PATCH /auth/me/notification-preferences (requireAuth, whitelisted partial
  update, integer guard for volume). buildAuthUser exposes all new fields.
- OpenAPI spec updated; orval client + zod regenerated.

Frontend
- Static sound library: 8 short WAV assets under public/sounds/ + manifest
  (notification-sound-manifest.ts) mapping id -> labelKey -> URL.
- HTMLAudio-based player (notification-sounds.ts) with throttle, unlock,
  cache, and AUTOPLAY_BLOCKED_EVENT dispatch when play is denied pre-gesture.
- Settings popover: global mute, volume, slot tabs (orders/meetings) with
  per-channel sound + per-channel vibration + per-channel toggle, sound list
  with one-tap previews. Concurrency-safe optimistic updates (monotonic seq
  counter). RTL-correct toggle knob transforms.
- QuickMuteButton: one-tap topbar mute control (Volume2/VolumeX) with
  confirmation toast.
- useAudioUnlock: unlocks audio on first interaction.
- useAutoplayHint: toasts a localized "click anywhere to enable" hint when
  the player reports a blocked play attempt.
- Socket hook plays the right per-channel sound + vibration on
  notification_created (orders, executive_meeting) when the tab is hidden.
- UpcomingMeetingAlert: plays meeting reminder once per new eligible
  meeting (deduped by meetingId, survives 30s polling refetches).
- AR/EN translations added.

Pre-existing TS errors in executive-meetings.ts (font_settings) and
pre-existing test workflow failures are unrelated.
2026-05-05 08:20:54 +00:00
Riyadh 876719f0eb Task #389: Notification sounds + vibration with per-user customization
- DB: 7 new user prefs (sound per slot, per-type toggles, vibration, mute, volume).
- API: PATCH /auth/me/notification-preferences with whitelisted partial update,
  integer guard for volume, and hydrated AuthUser response. AuthUser now exposes
  the 7 new fields.
- Frontend: Web Audio synth library (8 sounds), settings popover with global
  mute/vibration/volume/per-type toggles, slot tabs, sound preview buttons,
  shift+click on bell for quick mute. Concurrency-safe optimistic updates with
  monotonic seq counter. RTL-correct toggle knob transforms.
- Socket hook plays sound on notification_created (orders/meetings only) when
  tab is hidden, respecting global mute and per-type toggles.
- Audio unlock hook mounted globally to satisfy autoplay restrictions.
- AR/EN translations added.

Pre-existing TS errors in executive-meetings.ts (font_settings) and pre-existing
test failures in test workflow are unrelated to this task.
2026-05-05 08:13:38 +00:00
Riyadh f7b8de4eab Transitioned from Plan to Build mode 2026-05-05 08:04:57 +00:00
Riyadh c1d5ccd771 Update site colors and add order notification badge
Updates primary color to navy, adjusts icon tile shadows, and adds a real-time notification badge for incoming orders to the home page.
2026-05-05 07:51:36 +00:00
Riyadh fdc9d804e9 Thicken table borders to improve PDF visibility
Adjust border thickness in `pdf-html-renderer.ts` and `pdf-renderer.ts` from 1px to 1.5px to ensure navy borders are clearly rendered in PDF outputs.
2026-05-05 07:41:57 +00:00
Riyadh 08329539d5 Update PDF table borders to dark navy color
Modify `pdf-html-renderer.ts` and `pdf-renderer.ts` to change table border colors from light gray to dark navy.
2026-05-05 07:35:42 +00:00
Riyadh ba02fa28db Add progress tracking and cancel functionality to bulk delete
Update localization files and executive-meetings.tsx to implement sequential bulk deletion with progress tracking and cancellation, mirroring the UX of the bulk duplicate feature.
2026-05-05 07:27:21 +00:00
Riyadh 0338e22df8 Add progress bar and cancel option to bulk meeting duplication
Integrates a progress bar and cancel functionality into the bulk duplicate process for executive meetings, migrating from `Promise.allSettled` to a sequential loop to prevent race conditions and updating UI components and localization files.
2026-05-05 07:22:12 +00:00
Riyadh 96cfea312c Update coffee offerings and remove unwanted drinks
Update Arabic coffee image to show brown coffee, add black coffee as a new service, and remove water, Nescafe, and soft drinks.
2026-05-04 18:57:17 +00:00
Riyadh 2e6f249c26 Remove zoom and fullscreen features from the executive meetings page
Revert changes related to zoom and fullscreen functionality on the executive meetings page, including associated UI elements and state management.
2026-05-04 18:12:56 +00:00
Riyadh e688e02a3f Add zoom and fullscreen controls to the meeting schedule table
Add zoom and fullscreen controls to the executive meeting schedule page, including zoom in/out buttons, a fullscreen toggle, and Escape key handling for exiting fullscreen. The heading height calculation has been removed and hardcoded to 0px.
2026-05-04 17:55:36 +00:00
Riyadh d2b697d3d2 Move toolbar controls to the top navigation bar
Moves the edit, search, and date controls from the Schedule section's sticky header to the main navigation bar using React portals.
2026-05-04 17:50:11 +00:00
Riyadh 2836d815d8 Update meeting page controls with new icons and search functionality
Replaces existing edit and search controls with icon-based components, introducing a collapsible search input.
2026-05-04 16:22:41 +00:00
Riyadh 0a6a19d277 Improve meeting settings by adding collapsible sections and removing redundant text
Refactor executive meetings page to use collapsible sections for alert preferences and font settings, and remove descriptive text from locale files.
2026-05-04 16:15:49 +00:00
Riyadh 4101124784 Fix toggle switch display for Arabic language users
Adjust translation classes in the Switch component to correctly display the toggle switch in RTL layouts.
2026-05-04 16:09:18 +00:00
Riyadh 657d74be91 Make the customize table section collapsible in settings
Introduce collapsible functionality to the "Customize Table" section in executive meetings settings, allowing users to expand or collapse the panel.
2026-05-04 16:04:55 +00:00
Riyadh 8d26e314f7 Update PDF filename generation to use Arabic day names
Corrected a variable name typo from `langRaw` to `lang` in the server-side PDF filename generation logic for `executive-meetings.ts`, ensuring consistent use of Arabic day names in the output filename.
2026-05-04 16:01:59 +00:00
Riyadh 4dff005c35 Update PDF export to use localized filenames and remove extra text
Modify the API server and frontend to generate dynamic, localized PDF filenames for executive meetings, and remove descriptive text from the PDF export page.
2026-05-04 15:57:11 +00:00
Riyadh 2cf8ff7da0 Adjust PDF footer layout to position elements correctly
Update CSS in pdf-html-renderer.ts to ensure 'recorded by' text is centered and the date is aligned to the left within the PDF footer.
2026-05-04 15:12:17 +00:00
Riyadh 5ddbd410cd Center the title and move the recorded by text to the footer
Modify the header to center the title and relocate the "recorded by" text to the footer with a specific color.
2026-05-04 15:04:40 +00:00
Riyadh 0416534b84 Improve PDF attendee and header formatting
Modify `buildAttendeesHtml` to use `htmlToSafeHtml` for preserving attendee name formatting, adjust PDF header layout for centered title and logo, and relocate "recorded by" text.
2026-05-04 15:01:25 +00:00
Riyadh ca5d4aa860 Improve PDF generation by preserving text formatting and updating header layout
Introduce `htmlToSafeHtml` function to retain text formatting (color, bold) in PDFs and restructure the header layout, moving the logo to the right and title to the left.
2026-05-04 14:51:45 +00:00
Riyadh f7c29426fa Make number cells in PDFs consistently bold
Add `font-weight: bold` to the `.num-cell` CSS class in the PDF renderer to match the web interface's styling.
2026-05-04 14:34:49 +00:00
Riyadh 0f30aef532 Update meeting row styling for dark backgrounds and white text
Apply darker background and white text styling to the meeting number cell in both web and PDF outputs, ensuring consistency.
2026-05-04 14:20:12 +00:00
Riyadh 7d369efbf8 Apply row styling only to meeting number and title columns
Removes row background and border styling from the attendees and time columns in the PDF renderer and the web interface. Adjusts the `tintedCellStyle` function to conditionally apply border styling based on `applyRowColor` flag.
2026-05-04 14:11:15 +00:00
Riyadh 365f8137c2 Update open graph image for the project
Replace existing open graph image file with an updated version.
2026-05-04 14:01:47 +00:00
Riyadh eed69bc8b8 Update images for improved visual presentation and branding
Update opengraph image and other attached assets.
2026-05-04 13:22:13 +00:00
Riyadh 317d19db8d Unify row color styling across all web table cells
Previously, when a meeting row had a color assigned (e.g. red), the
attendees and time cells only received the colored border but not the
background fill. The meeting cell used `tintedCellStyle(col, true)` which
always applies rowBg, while attendees and time used
`tintedCellStyle(col, isMerged)` — meaning they only got the background
when the row was merged.

Changed both attendees and time cells to use `tintedCellStyle(col, true)`
so all four cells (number, meeting, attendees, time) display the same
background fill and border color when a row color is set.

This mirrors the PDF fix from Task #385 which addressed the same
inconsistency in the PDF renderer.

File changed: artifacts/tx-os/src/pages/executive-meetings.tsx (lines 3804, 3825)
2026-05-04 13:07:48 +00:00
Riyadh d1b1570780 Unify row color styling across all PDF table cells
Previously, when a meeting row had a color assigned (e.g. red), only the
number and meeting title cells received both the background fill and colored
border, while the attendees and time cells received only the colored border
(no background fill). This made the row styling inconsistent.

Changed attendees-cell and time-cell to use `coloredStyle` (background +
border) instead of `borderStyle` (border only), matching the number and
meeting cells. All four cells in a colored row now display uniformly.

Rows without a color assignment are unaffected (default gray border, no fill).

File changed: artifacts/api-server/src/lib/pdf-html-renderer.ts (lines 222-223)
2026-05-04 13:03:42 +00:00
Riyadh a934b56bd3 Fix PDF font color not reflecting system settings via per-field merge
Root cause: resolveFontPrefsForUser() used `userRow ?? globalRow` whole-row
precedence. When an admin saved font settings with scope="global", only the
global row was updated. If the admin also had a user-scope row (created by
prior saves with the default scope="user"), ALL fields from the user-scope
row overrode the global row — including fontColor — causing the PDF to show
the old color even after changing global settings.

Schema change (executive-meetings.ts):
- Made fontFamily, fontSize, fontWeight, alignment, fontColor nullable.
  User-scope rows now store NULL for fields that inherit from global,
  and only store non-null values for fields the user explicitly overrode.

Backend fix (executive-meetings.ts):
- resolveFontPrefsForUser: per-field merge —
  user non-null → global non-null → schema default.
- PATCH handler for user-scope: after upsert, compares each field with the
  current global values. Fields matching global are set to NULL (= inherit).
  The post-nullification row is returned in the API response and audit log.
- No user-scope rows are deleted; scope isolation is preserved.

Frontend fix (executive-meetings.tsx):
- effectiveFont computed via per-field merge (u?.field ?? g?.field ?? default)
- FontSettingsResponse type updated for nullable fields (FontSettingsRow)
- Scope switching in FontSettingsSection loads the selected scope's values
  (global → globalFont ?? DEFAULT_FONT; user → effective font)
- globalFont prop threaded through SettingsSection → FontSettingsSection

Data migration: existing user-scope rows normalized via SQL — fields matching
global values set to NULL so per-field inheritance applies immediately.

Verified: TypeScript clean, e2e Playwright test passes, API tests confirm
per-field merge, nullification, and PDF color propagation.
2026-05-04 12:30:00 +00:00
Riyadh 557e692e8e Fix PDF font color not reflecting system settings
Root cause: resolveFontPrefsForUser() uses `userRow ?? globalRow` precedence.
When an admin saved font settings with scope="global", only the global row
was updated. If the admin also had a user-scope row (created by prior saves
with the default scope="user"), the user-scope row still overrode the global
row during PDF generation, causing the PDF to show the old color.

Backend fix (executive-meetings.ts):
- When saving with scope="global", delete the current admin's user-scope row
  within the same transaction. This ensures global settings immediately apply
  to the admin who set them. Other users' personal rows are unaffected.

Frontend fix (executive-meetings.tsx):
- Pass both `globalFont` and effective `font` to FontSettingsSection.
- When the scope dropdown changes, the form now loads the selected scope's
  actual saved values instead of always showing the effective (user-override)
  values. This prevents confusion where the admin edits "global" but sees
  their personal values in the form.

Verified end-to-end:
- PATCH font-settings with scope=user + fontColor=#ff0000 → DB updated → PDF uses red
- PATCH font-settings with scope=global + fontColor=#0000ff → global updated,
  user-scope row deleted → PDF resolves to global blue
- TypeScript compiles cleanly, e2e Playwright test passes
2026-05-04 12:17:40 +00:00
Riyadh 27531b788a Transitioned from Plan to Build mode 2026-05-04 12:04:17 +00:00
Riyadh 8b8f705cc2 Make row borders darker and more prominent
Update border colors and thickness in PDF renderer and UI to ensure visibility and consistency.
2026-05-04 11:57:42 +00:00
Riyadh dd58ba230b 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
2026-05-04 11:33:56 +00:00
Riyadh e04404171e Transitioned from Plan to Build mode 2026-05-04 11:27:50 +00:00
Riyadh 8fe113ef17 Task #377: Improve PDF cell spacing, line-height, and use system fontColor
Changes in artifacts/api-server/src/lib/pdf-html-renderer.ts:

1. Increased cell padding from 2px 4px to 10px 12px for both header
   and body cells for better readability
2. Increased line-height from 1.05 to 1.8 (body cells) and 1.2 to 1.6
   (header cells and body baseline)
3. Page title (h1) now uses system fontColor instead of hardcoded #000
4. Footer text now uses system fontColor for consistency
5. Empty-state text uses system fontColor with opacity instead of #666
6. Attendee items get direction-aware margin (12px) for better spacing
7. Logo header uses conditional min-height (55px when logo present,
   auto when absent) to prevent overlap and empty space
8. Footer date-line margin increased from 2px to 4px

Header background (#0B1E3F), header text (white), and border color
(#d1d5db) remain as branded defaults — no DB columns exist for these
yet (follow-up task #378 proposed for that).

No changes to web UI, schema, or legacy pdf-renderer.ts.
Tested: AR and EN PDFs generate successfully with correct sizes.
E2E test passed: login, PDF download (both languages), and UI verified.
2026-05-04 11:17:12 +00:00
Riyadh 992f8ba147 Task #377: Improve PDF cell spacing, line-height, and use system fontColor
Changes in artifacts/api-server/src/lib/pdf-html-renderer.ts:

1. Increased cell padding from 2px 4px to 10px 12px (body cells) and
   3px 4px to 8px 12px (header cells) for better readability
2. Increased line-height from 1.05 to 1.8 (body cells) and 1.2 to 1.6
   (header cells and body baseline)
3. Page title (h1) now uses system fontColor instead of hardcoded #000
4. Footer text now uses system fontColor for consistency
5. Attendee items get direction-aware margin (12px) for better spacing
6. Logo header uses conditional min-height (55px when logo present,
   auto when absent) to prevent overlap and empty space
7. Footer date-line margin increased from 2px to 4px

Header background (#0B1E3F), header text (white), and border color
(#d1d5db) remain as branded defaults — no DB columns exist for these
yet (follow-up task #378 proposed for that).

No changes to web UI, schema, or legacy pdf-renderer.ts.
Tested: AR and EN PDFs generate successfully with correct sizes.
E2E test passed: login, PDF download (both languages), and UI verified.
2026-05-04 11:15:55 +00:00
Riyadh 5d20799ad8 Apply row coloring consistently across the web and PDF views
Adjust cell styling to ensure row background colors are applied uniformly to both individual and merged rows, matching the PDF rendering behavior in the web interface.
2026-05-04 11:05:57 +00:00
Riyadh 919691fd22 PDF row color: apply only to # and meeting columns, full row on merge
Task #375: Changed row coloring behavior in the HTML-table PDF renderer
so that normal rows only color the # (number) and الاجتماع (meeting)
cells, leaving الحضور (attendees) and الوقت (time) cells white.

When a row has merged columns (colspan), the entire row is colored
as before.

Implementation: `buildMeetingRow` now assigns `bgStyle` directly to
the first two cells' `style` field. `renderCells` takes an `allColored`
flag — true for merged rows (applies bgStyle to all cells), false for
normal rows (uses each cell's own style). The `<tr>` element no longer
carries the background style.

File changed: artifacts/api-server/src/lib/pdf-html-renderer.ts
2026-05-04 10:46:10 +00:00
Riyadh 35676cba66 Transitioned from Plan to Build mode 2026-05-04 10:43:58 +00:00
Riyadh 691b5f9245 Adjust report layout for title, columns, and date format
Remove manual column reversal for RTL rendering and enforce LTR for footer date to fix display issues.
2026-05-04 10:32:22 +00:00
Riyadh f9e78202ed Update attendee display to prevent text overflow
Modify CSS for attendee lines to allow text wrapping and change attendee item display to inline to ensure long attendee names fit within the PDF rendering.
2026-05-04 10:28:39 +00:00
Riyadh 2f73280a1d Rebuild PDF generation: HTML <table> + Playwright replaces PDFKit manual drawing
Task #372: Replaced the manual PDFKit coordinate-based PDF renderer with an
HTML table-based approach using Playwright's headless Chromium.

Key changes:
- Created `pdf-html-renderer.ts` with `buildScheduleHtml()` (generates full
  HTML with embedded base64 fonts, proper `<table>`, RTL/LTR support, row
  colors, merged cells, attendees formatting) and `htmlToPdfBuffer()` (renders
  HTML to PDF via Playwright Chromium).
- Updated `pdf-renderer.ts`: `renderSchedulePdf()` now delegates to the new
  HTML renderer via dynamic import. Legacy PDFKit code preserved as
  `renderSchedulePdf_LEGACY()` for fallback reference.
- Added `playwright-core` dependency and configured esbuild externals.
- Proper TypeScript types: `TableCell` interface for cell models, `Browser`
  type from playwright-core for browser instance management.
- Chromium provisioning: searches `.cache/ms-playwright/` with env var override
  (`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`), auto-installs via
  `npx playwright-core install chromium` if not found.
- Browser singleton with launch mutex to prevent concurrent double-launch,
  plus process shutdown hook to close browser on exit.

Requirements met:
- Each meeting in exactly one `<tr>` with 4 `<td>` cells
- No text outside table, attendees ~3 per line inline
- Time always in its column, `page-break-inside: avoid`, `table-layout: fixed`
- Matches reference design (blue header, centered text, DIN Next LT Arabic)
2026-05-04 10:26:34 +00:00
Riyadh 7c5a9748a2 Rebuild PDF generation: HTML <table> + Playwright replaces PDFKit manual drawing
Task #372: Replaced the manual PDFKit coordinate-based PDF renderer with an
HTML table-based approach using Playwright's headless Chromium.

Key changes:
- Created `pdf-html-renderer.ts` with `buildScheduleHtml()` (generates full
  HTML with embedded base64 fonts, proper `<table>`, RTL/LTR support, row
  colors, merged cells, attendees formatting) and `htmlToPdfBuffer()` (renders
  HTML to PDF via Playwright Chromium).
- Updated `pdf-renderer.ts`: `renderSchedulePdf()` now delegates to the new
  HTML renderer via dynamic import. Legacy PDFKit code preserved as
  `renderSchedulePdf_LEGACY()` for fallback reference.
- Added `playwright-core` dependency and configured esbuild externals.
- Chromium binary discovery: searches `.cache/ms-playwright/` directories with
  env var override (`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`). Browser instance
  is cached as singleton with launch mutex to prevent concurrent double-launch.
- Process shutdown hook closes browser on exit.

Requirements met:
- Each meeting in exactly one `<tr>` with 4 `<td>` cells
- No text outside table, attendees ~3 per line inline
- Time always in its column, `page-break-inside: avoid`, `table-layout: fixed`
- Matches reference design (blue header, centered text, DIN Next LT Arabic)
2026-05-04 10:24:08 +00:00
Riyadh 044126ccb0 Transitioned from Plan to Build mode 2026-05-04 10:13:20 +00:00
Riyadh 260716a33f Update PDF generation to match reference design and improve alignment
Revert PDF cell padding and text alignment to match the reference design, ensuring centered content and vertical alignment.
2026-05-04 10:06:22 +00:00