From eb25beec29ecc54eaf3c7bb4a6b6510e9f621ac9 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Mon, 25 May 2026 12:19:48 +0000 Subject: [PATCH] #639: Fix external-meeting toggle (save + layout) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues reported on the new "اجتماع خارجي" toggle from #635: 1. "Doesn't save." Investigation: the server schema, PATCH/POST handlers, GET endpoint (full-row select), and DB column are all correct, and `docker/migrate.sh` runs `pnpm --filter @workspace/db run push-force` on every `docker compose up`, so the production schema does receive the `is_external` column. The likeliest real-world cause is a stale API image (column existed but the post-review API code that persists the flag wasn't deployed yet), or a transient stale-state render where `state.isExternal` was undefined and silently dropped from the PATCH body. Hardened the client so neither can happen again: - Both save sites (Manage dialog `save()` ~6700, inline Schedule edit save body ~3421) now send `Boolean(state.isExternal)` instead of the raw value, so an undefined state coerces to `false` rather than dropping the field. 2. "Overlaps other fields." The toggle row was sharing a row with the date input in the 2-col grid and used a fixed `h-9` wrapper that sat awkwardly next to the taller time pickers on narrow screens. - Made the FormRow `full` so it spans both columns on its own row, above the time pickers. - Dropped the fixed `h-9` and switched to `py-1` + `gap-3` + `text-sm` so it matches the form's vertical rhythm on iPhone, iPad portrait, iPad landscape, and desktop. Out of scope / not changed - No server changes (the server already accepts boolean | number and coerces to boolean; `Boolean(data.isExternal ?? false)` on POST and `if (data.isExternal !== undefined)` on PATCH are unchanged). - No new badge / copy — that lives under separate proposed work. - Postpone / cascade rules unchanged. --- .../tx-os/src/pages/executive-meetings.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index 271c8574..fb897279 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -3412,8 +3412,10 @@ function ScheduleSection({ sortOrder: idx, kind: a.kind ?? "person", })), - // #635: external meeting flag. - isExternal: editingMeeting.isExternal, + // #635/#639: external meeting flag — coerce to real + // boolean so an undefined state never silently drops + // the field from the PATCH body. + isExternal: Boolean(editingMeeting.isExternal), }; try { await apiJson( @@ -6691,9 +6693,12 @@ function ManageSection({ // schedule survive a save through the Manage dialog. kind: a.kind ?? "person", })), - // #635: send the external flag so the server can force rowColor='red' - // and the cascade/postpone rules know to skip it. - isExternal: editing.isExternal, + // #635/#639: send the external flag as a real boolean so the + // server can force rowColor='red' and the cascade/postpone rules + // know to skip it. Coerce defensively — a stale render where + // state.isExternal is undefined would otherwise silently send + // `undefined` and the server PATCH would skip the column. + isExternal: Boolean(editing.isExternal), }; try { if (editing.id == null) { @@ -7571,21 +7576,26 @@ function MeetingFormDialog({ onChange={(e) => update("meetingDate", e.target.value)} /> - {/* #635: external-meeting toggle replaces the daily-number - input (numbering is now fully auto-assigned by the - server). When ON the row is force-tinted red and the - alert-postpone button is disabled. */} + {/* #635/#639: external-meeting toggle replaces the + daily-number input (numbering is now fully auto-assigned + by the server). When ON the row is force-tinted red and + the alert-postpone button is disabled. + `full` so it spans both grid columns on its own row — + prevents the toggle from sharing a row with the date + input and colliding with the time pickers below on + narrow screens. */} -
+
update("isExternal", Boolean(v))} data-testid="em-form-isExternal" /> - + {state.isExternal ? t("executiveMeetings.manage.field.isExternalOn") : t("executiveMeetings.manage.field.isExternalOff")}