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:
riyadhafraa
2026-05-18 09:28:02 +00:00
parent f39d8edea2
commit e296216c71
2 changed files with 38 additions and 2 deletions
@@ -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<
+10
View File
@@ -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");