#324 Make the Add/Edit Meeting dialog fit on a phone screen

After #322 trimmed the dialog, the Save/Cancel footer slid below
the visible 90vh sheet on phones because the whole DialogContent
was a single overflow-y-auto block.

Changes in artifacts/tx-os/src/pages/executive-meetings.tsx:

MeetingFormDialog
- Rebuilt DialogContent as a flex column: shrink-0 header,
  flex-1 min-h-0 overflow-y-auto middle (the only scroller),
  shrink-0 footer. Save/Cancel now stay pinned at the bottom
  even after many attendees are added.
- Reduced mobile padding/gaps (`p-4 sm:p-6`,
  `gap-3 sm:gap-4`, grid `gap-2 sm:gap-3`). Desktop spacing
  unchanged.
- Switched the field grid from `grid-cols-1 sm:grid-cols-2`
  to a flat `grid-cols-2` so the two short fields (التاريخ,
  الرقم اليومي) share a row even on mobile. Title spans both
  columns; time pickers span both on mobile and one each on
  sm+ via the new mobileFull variant.
- Inner scroll wrapper uses negative margins + matching padding
  so scroll content can extend edge-to-edge without losing the
  dialog's gutter.

FormRow helper
- Broadened `full` from `sm:col-span-2` to `col-span-2`
  (works in both 1-col and 2-col grids; only callers using
  `full` are in this file).
- Added `mobileFull` prop → `col-span-2 sm:col-span-1` for
  the time pickers, so AM/PM controls don't get squeezed on
  phones.

Out of scope (untouched): validation, save logic, attendee
model, removed-field defaults from #322, duplicate dialog,
audit, schedule view, translations.
This commit is contained in:
riyadhafraa
2026-05-03 10:09:29 +00:00
parent cfd2a46d39
commit d2dda556d6
2 changed files with 39 additions and 13 deletions
@@ -5399,20 +5399,25 @@ function MeetingFormDialog({
return ( return (
<Dialog open={true} onOpenChange={(o) => !o && onClose()}> <Dialog open={true} onOpenChange={(o) => !o && onClose()}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto"> {/* #324: rebuild DialogContent as a flex column so the header,
<DialogHeader> a single scrollable middle, and the footer stack predictably
on mobile. Without this the whole sheet scrolled and the
Save/Cancel buttons slid below the visible 90vh viewport on
phones. Reduced mobile padding/gaps; desktop unchanged. */}
<DialogContent className="max-w-3xl max-h-[90vh] p-4 sm:p-6 flex flex-col gap-3 sm:gap-4 overflow-hidden">
<DialogHeader className="shrink-0">
<DialogTitle> <DialogTitle>
{state.id == null {state.id == null
? t("executiveMeetings.manage.addMeeting") ? t("executiveMeetings.manage.addMeeting")
: t("executiveMeetings.manage.editMeeting")} : t("executiveMeetings.manage.editMeeting")}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{/* #322 polish: arrange the trimmed form into a tidy grid that <div className="flex-1 min-h-0 overflow-y-auto -mx-4 px-4 sm:-mx-6 sm:px-6 space-y-3 sm:space-y-4">
avoids dangling cells. Title spans full width on every {/* #322 polish (grid): title spans full width. Date + daily
breakpoint so it gets enough room. Date + daily number sit number always share one row (both are short). Time pickers
on one row on tablet/desktop, then start + end times share are full-width on mobile so the AM/PM controls don't get
the next row. On mobile each field stacks. */} cramped, but share a row on tablet/desktop. */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-2 sm:gap-3">
<FormRow <FormRow
label={t("executiveMeetings.manage.field.titleAr")} label={t("executiveMeetings.manage.field.titleAr")}
full full
@@ -5441,7 +5446,10 @@ function MeetingFormDialog({
onChange={(e) => update("dailyNumber", e.target.value)} onChange={(e) => update("dailyNumber", e.target.value)}
/> />
</FormRow> </FormRow>
<FormRow label={t("executiveMeetings.manage.field.startTime")}> <FormRow
label={t("executiveMeetings.manage.field.startTime")}
mobileFull
>
<TimePicker12h <TimePicker12h
ref={startPickerRef} ref={startPickerRef}
value={state.startTime} value={state.startTime}
@@ -5457,7 +5465,10 @@ function MeetingFormDialog({
pmTestId="em-form-startTime-pm" pmTestId="em-form-startTime-pm"
/> />
</FormRow> </FormRow>
<FormRow label={t("executiveMeetings.manage.field.endTime")}> <FormRow
label={t("executiveMeetings.manage.field.endTime")}
mobileFull
>
<TimePicker12h <TimePicker12h
ref={endPickerRef} ref={endPickerRef}
value={state.endTime} value={state.endTime}
@@ -5480,7 +5491,7 @@ function MeetingFormDialog({
preserved for meetings being edited. */} preserved for meetings being edited. */}
</div> </div>
<div className="border-t border-gray-200 pt-3 mt-1"> <div className="border-t border-gray-200 pt-3">
<div className="flex items-center justify-between mb-2 gap-2 flex-wrap"> <div className="flex items-center justify-between mb-2 gap-2 flex-wrap">
<h4 className="font-semibold text-sm text-[#0B1E3F]"> <h4 className="font-semibold text-sm text-[#0B1E3F]">
{t("executiveMeetings.manage.attendees.label")} ( {t("executiveMeetings.manage.attendees.label")} (
@@ -5569,8 +5580,12 @@ function MeetingFormDialog({
)} )}
</div> </div>
</div> </div>
</div>
<DialogFooter> {/* #324: footer sits outside the scroll area so it stays
pinned to the bottom of the dialog even after many
attendees are added. */}
<DialogFooter className="shrink-0">
<Button variant="ghost" onClick={onClose} disabled={saving}> <Button variant="ghost" onClick={onClose} disabled={saving}>
{t("executiveMeetings.common.cancel")} {t("executiveMeetings.common.cancel")}
</Button> </Button>
@@ -5592,15 +5607,26 @@ function FormRow({
label, label,
children, children,
full, full,
mobileFull,
hint, hint,
}: { }: {
label: string; label: string;
children: React.ReactNode; children: React.ReactNode;
/** Always span both columns of a 2-col grid. Safe in single-column
* grids too (col-span-2 just clamps to the available width). */
full?: boolean; full?: boolean;
/** Span both columns on mobile but a single column from sm+. Used
* by #324 so the time pickers don't get squeezed on phones. */
mobileFull?: boolean;
hint?: string; hint?: string;
}) { }) {
const span = full
? "col-span-2"
: mobileFull
? "col-span-2 sm:col-span-1"
: "";
return ( return (
<div className={full ? "sm:col-span-2" : ""}> <div className={span}>
<Label className="text-xs text-gray-700">{label}</Label> <Label className="text-xs text-gray-700">{label}</Label>
<div className="mt-1">{children}</div> <div className="mt-1">{children}</div>
{hint && <p className="text-[11px] text-gray-500 mt-0.5">{hint}</p>} {hint && <p className="text-[11px] text-gray-500 mt-0.5">{hint}</p>}
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB