feat(meetings): center clock, fullscreen toggle, quick-note tab (#583)
- Replace HeaderClock with richer analog+weekday+digital block, always
centered in the header (both president and normal views). Drop the
PresidentClockBar render from the toolbar; the dead helper is left
in place but unused.
- Lift fullscreen state to ExecutiveMeetingsPageInner with
sessionStorage persistence, Esc handler, auto-exit when the user
leaves the schedule tab. Hide the page header when active and show a
floating "Exit fullscreen" pill. New Maximize/Minimize toggle in the
schedule toolbar portal.
- Add MeetingsQuickNoteTab: a fixed amber vertical pill pinned to the
left edge of the schedule view that navigates to /notes?new=1.
- notes.tsx: detect ?new=1, switch to the Active tab, bump a numeric
openTrigger that the top Composer reacts to by opening + focusing
the textarea, then scrub the query param so navigation doesn't
re-trigger.
- i18n: add executiveMeetings.fullscreen.{enter,exit} and
executiveMeetings.quickNote.label in ar.json and en.json.
tsc clean. Push to Gitea still blocked by Tailscale; user to retry.
This commit is contained in:
@@ -918,6 +918,15 @@ function ExecutiveMeetingsPageInner() {
|
||||
if (section !== "schedule" && isFullscreen) setIsFullscreen(false);
|
||||
}, [section, isFullscreen]);
|
||||
|
||||
// The schedule section publishes whether its bulk-action toolbar is
|
||||
// currently visible (≥1 row selected in edit mode) so the page-level
|
||||
// MeetingsQuickNoteTab can step out of the way while the user works
|
||||
// with destructive bulk actions.
|
||||
const [bulkToolbarActive, setBulkToolbarActive] = useState(false);
|
||||
useEffect(() => {
|
||||
if (section !== "schedule" && bulkToolbarActive) setBulkToolbarActive(false);
|
||||
}, [section, bulkToolbarActive]);
|
||||
|
||||
// Lifted from ScheduleSection so the unified Settings tab can edit
|
||||
// the same column visibility / highlight state the schedule consumes.
|
||||
const [columns, setColumns] = useState<ColumnSetting[]>(() =>
|
||||
@@ -1026,7 +1035,7 @@ function ExecutiveMeetingsPageInner() {
|
||||
dir={isRtl ? "rtl" : "ltr"}
|
||||
style={{ ["--em-header-h" as string]: "0px", ["--em-heading-h" as string]: "0px" }}
|
||||
>
|
||||
{section === "schedule" && !isFullscreen && (
|
||||
{section === "schedule" && !isFullscreen && !bulkToolbarActive && (
|
||||
<MeetingsQuickNoteTab
|
||||
lang={lang}
|
||||
isRtl={isRtl}
|
||||
@@ -1131,7 +1140,13 @@ function ExecutiveMeetingsPageInner() {
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className="max-w-screen-2xl mx-auto px-3 sm:px-6 py-4 sm:py-6">
|
||||
<main
|
||||
className={
|
||||
isFullscreen
|
||||
? "w-full px-2 sm:px-3 py-2"
|
||||
: "max-w-screen-2xl mx-auto px-3 sm:px-6 py-4 sm:py-6"
|
||||
}
|
||||
>
|
||||
{section === "schedule" && (
|
||||
<ScheduleSection
|
||||
meetings={meetings}
|
||||
@@ -1150,6 +1165,7 @@ function ExecutiveMeetingsPageInner() {
|
||||
lang={lang}
|
||||
isFullscreen={isFullscreen}
|
||||
onFullscreenChange={setIsFullscreen}
|
||||
onBulkToolbarActiveChange={setBulkToolbarActive}
|
||||
/>
|
||||
)}
|
||||
{section === "manage" && me && (
|
||||
@@ -1212,6 +1228,7 @@ function ScheduleSection({
|
||||
lang = "en",
|
||||
isFullscreen = false,
|
||||
onFullscreenChange,
|
||||
onBulkToolbarActiveChange,
|
||||
}: {
|
||||
meetings: Meeting[];
|
||||
date: string;
|
||||
@@ -1229,6 +1246,7 @@ function ScheduleSection({
|
||||
lang?: "ar" | "en";
|
||||
isFullscreen?: boolean;
|
||||
onFullscreenChange?: (next: boolean) => void;
|
||||
onBulkToolbarActiveChange?: (active: boolean) => void;
|
||||
}) {
|
||||
const qc = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
@@ -1932,6 +1950,14 @@ function ScheduleSection({
|
||||
for (const m of displayedMeetings) if (selectedMeetingIds.has(m.id)) n++;
|
||||
return n;
|
||||
}, [displayedMeetings, selectedMeetingIds]);
|
||||
// Mirror the bulk toolbar's visibility condition (see the
|
||||
// `effectiveCanMutate && visibleSelectedCount > 0` render below) up
|
||||
// to the page so the floating quick-note tab can step aside while
|
||||
// bulk actions are in progress.
|
||||
const bulkToolbarVisible = effectiveCanMutate && visibleSelectedCount > 0;
|
||||
useEffect(() => {
|
||||
onBulkToolbarActiveChange?.(bulkToolbarVisible);
|
||||
}, [bulkToolbarVisible, onBulkToolbarActiveChange]);
|
||||
const [bulkDeleting, setBulkDeleting] = useState(false);
|
||||
// Sequential bulk-delete progress (mirrors duplicate UX).
|
||||
const [bulkDeleteProgress, setBulkDeleteProgress] = useState<
|
||||
|
||||
@@ -284,6 +284,16 @@ export default function NotesPage() {
|
||||
// navigation doesn't keep re-opening it.
|
||||
if (params.get("new") === "1") {
|
||||
setView("active");
|
||||
// The top Composer is only mounted when
|
||||
// `view === "active" && folderSelection.kind !== "shared-folder"`.
|
||||
// If the user arrived from /notes?new=1 while sitting inside a
|
||||
// shared folder, leaving the selection alone would mean the
|
||||
// Composer is never rendered and the open trigger would be lost.
|
||||
// Reset to the default "all" folder so the composer is guaranteed
|
||||
// visible regardless of where they were before.
|
||||
setFolderSelection((cur) =>
|
||||
cur.kind === "shared-folder" ? { kind: "all" } : cur,
|
||||
);
|
||||
setComposerOpenTrigger((n) => n + 1);
|
||||
if (typeof window !== "undefined") {
|
||||
params.delete("new");
|
||||
|
||||
Reference in New Issue
Block a user