#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:
@@ -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 |
Reference in New Issue
Block a user