#265: unify Executive Meetings header into single Settings tab
Header
- Removed the standalone Font Settings nav button and the bilingual
language toggle. The header now exposes only Export PDF.
- Renamed the SECTIONS key `fontSettings` → `settings` (Settings icon
retained) and updated visibility checks + render switch.
Settings tab
- New SettingsSection wraps the existing FontSettingsSection plus a
new ColumnsCustomizerPanel (extracted from the old popover body) so
column visibility / current-meeting highlight live with the rest of
user prefs.
- Removed the old ColumnsCustomizer popover trigger from the schedule
toolbar; the inline panel inside Settings is the single entry point.
State lifting
- columns/setColumns and highlightPrefs/setHighlightPrefs lifted from
ScheduleSection up to ExecutiveMeetingsPage so both tabs read/write
the same state. Storage keys (COLS_STORAGE_KEY, HIGHLIGHT_STORAGE_KEY)
unchanged so existing user prefs continue to load.
- Per code-reviewer follow-up: moved the columns localStorage write
effect up to the page as well so edits made in Settings persist even
when ScheduleSection is unmounted.
Locales
- ar.json + en.json: renamed nav.fontSettings → nav.settings
("الإعدادات" / "Settings"); removed the now-unused
executiveMeetings.fontSettings header label.
- Removed unused Languages and Type lucide imports.
Tests
- Updated executive-meetings-bulk-actions.spec.mjs and
executive-meetings-schedule-features.spec.mjs to navigate via
em-nav-settings instead of the removed em-customize-columns-trigger.
- All 226 sequential api tests pass; both updated playwright specs
pass.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@@ -999,7 +999,6 @@
|
||||
"scheduleHeading": "قائمة بأسماء حضور الاجتماعات",
|
||||
"date": "التاريخ:",
|
||||
"exportPdf": "تصدير PDF",
|
||||
"fontSettings": "إعدادات الخط",
|
||||
"noMeetings": "لا توجد اجتماعات في هذا اليوم",
|
||||
"virtual": "اتصال مرئي",
|
||||
"virtualAttendance": "الحضور عبر الاتصال المرئي",
|
||||
@@ -1089,7 +1088,7 @@
|
||||
"notifications": "التنبيهات",
|
||||
"audit": "سجل التعديلات",
|
||||
"pdf": "تصدير PDF",
|
||||
"fontSettings": "إعدادات الخط"
|
||||
"settings": "الإعدادات"
|
||||
},
|
||||
"common": {
|
||||
"save": "حفظ",
|
||||
|
||||
@@ -900,7 +900,6 @@
|
||||
"scheduleHeading": "Meeting Attendance List",
|
||||
"date": "Date:",
|
||||
"exportPdf": "Export PDF",
|
||||
"fontSettings": "Font Settings",
|
||||
"noMeetings": "No meetings scheduled for this day",
|
||||
"virtual": "Virtual",
|
||||
"virtualAttendance": "Virtual Attendance",
|
||||
@@ -990,7 +989,7 @@
|
||||
"notifications": "Notifications",
|
||||
"audit": "Audit Log",
|
||||
"pdf": "PDF Export",
|
||||
"fontSettings": "Font Settings"
|
||||
"settings": "Settings"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "wouter";
|
||||
@@ -15,8 +17,6 @@ import {
|
||||
ArrowRight,
|
||||
CalendarClock,
|
||||
FileDown,
|
||||
Type,
|
||||
Languages,
|
||||
ListChecks,
|
||||
Bell,
|
||||
ScrollText,
|
||||
@@ -223,7 +223,7 @@ const SECTIONS = [
|
||||
{ key: "notifications", icon: Bell },
|
||||
{ key: "audit", icon: ScrollText },
|
||||
{ key: "pdf", icon: FileText },
|
||||
{ key: "fontSettings", icon: SettingsIcon },
|
||||
{ key: "settings", icon: SettingsIcon },
|
||||
] as const;
|
||||
|
||||
type SectionKey = (typeof SECTIONS)[number]["key"];
|
||||
@@ -247,7 +247,7 @@ function isSectionVisible(
|
||||
switch (key) {
|
||||
case "schedule":
|
||||
case "pdf":
|
||||
case "fontSettings":
|
||||
case "settings":
|
||||
return me.canRead;
|
||||
case "manage":
|
||||
return me.canMutate;
|
||||
@@ -498,6 +498,24 @@ export default function ExecutiveMeetingsPage() {
|
||||
const [section, setSection] = useState<SectionKey>("schedule");
|
||||
const [date, setDate] = useState<string>(todayIso());
|
||||
|
||||
// Lifted from ScheduleSection so the unified Settings tab can edit
|
||||
// the same column visibility / highlight state the schedule consumes.
|
||||
const [columns, setColumns] = useState<ColumnSetting[]>(() =>
|
||||
normalizeColumns(readJsonFromStorage(COLS_STORAGE_KEY, DEFAULT_COLUMNS)),
|
||||
);
|
||||
const [highlightPrefs, setHighlightPrefs] = useState<HighlightPrefs>(() =>
|
||||
readJsonFromStorage<HighlightPrefs>(
|
||||
HIGHLIGHT_STORAGE_KEY,
|
||||
DEFAULT_HIGHLIGHT_PREFS,
|
||||
),
|
||||
);
|
||||
useEffect(() => {
|
||||
writeJsonToStorage(COLS_STORAGE_KEY, columns);
|
||||
}, [columns]);
|
||||
useEffect(() => {
|
||||
writeJsonToStorage(HIGHLIGHT_STORAGE_KEY, highlightPrefs);
|
||||
}, [highlightPrefs]);
|
||||
|
||||
const { data, isLoading } = useQuery<DayResponse>({
|
||||
queryKey: ["/api/executive-meetings", date],
|
||||
queryFn: async () => {
|
||||
@@ -578,24 +596,6 @@ export default function ExecutiveMeetingsPage() {
|
||||
<FileDown className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">{t("executiveMeetings.exportPdf")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-1.5"
|
||||
onClick={() => setSection("fontSettings")}
|
||||
>
|
||||
<Type className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">{t("executiveMeetings.fontSettings")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-1.5"
|
||||
onClick={() => i18n.changeLanguage(isRtl ? "en" : "ar")}
|
||||
>
|
||||
<Languages className="w-4 h-4" />
|
||||
<span className="text-xs">{isRtl ? "EN" : "ع"}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -639,6 +639,9 @@ export default function ExecutiveMeetingsPage() {
|
||||
font={effectiveFont}
|
||||
canMutate={!!me?.canMutate}
|
||||
userId={me?.userId ?? null}
|
||||
columns={columns}
|
||||
onColumnsChange={setColumns}
|
||||
highlightPrefs={highlightPrefs}
|
||||
/>
|
||||
)}
|
||||
{section === "manage" && me && (
|
||||
@@ -658,10 +661,14 @@ export default function ExecutiveMeetingsPage() {
|
||||
{section === "pdf" && (
|
||||
<PdfSection date={date} onDateChange={setDate} lang={lang} t={t} />
|
||||
)}
|
||||
{section === "fontSettings" && me && (
|
||||
<FontSettingsSection
|
||||
{section === "settings" && me && (
|
||||
<SettingsSection
|
||||
font={effectiveFont}
|
||||
canEditGlobal={me.canEditGlobalFontSettings}
|
||||
canEditGlobalFont={me.canEditGlobalFontSettings}
|
||||
columns={columns}
|
||||
onColumnsChange={setColumns}
|
||||
highlightPrefs={highlightPrefs}
|
||||
onHighlightPrefsChange={setHighlightPrefs}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
@@ -682,6 +689,9 @@ function ScheduleSection({
|
||||
font,
|
||||
canMutate,
|
||||
userId,
|
||||
columns,
|
||||
onColumnsChange,
|
||||
highlightPrefs,
|
||||
}: {
|
||||
meetings: Meeting[];
|
||||
date: string;
|
||||
@@ -692,26 +702,17 @@ function ScheduleSection({
|
||||
font: FontPrefs;
|
||||
canMutate: boolean;
|
||||
userId: number | null;
|
||||
columns: ColumnSetting[];
|
||||
onColumnsChange: Dispatch<SetStateAction<ColumnSetting[]>>;
|
||||
highlightPrefs: HighlightPrefs;
|
||||
}) {
|
||||
const qc = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
const tableStyle = buildFontStyle(font);
|
||||
const [columns, setColumns] = useState<ColumnSetting[]>(() =>
|
||||
normalizeColumns(readJsonFromStorage(COLS_STORAGE_KEY, DEFAULT_COLUMNS)),
|
||||
);
|
||||
const setColumns = onColumnsChange;
|
||||
const [rowColors, setRowColors] = useState<Record<number, string>>(() =>
|
||||
readJsonFromStorage<Record<number, string>>(ROW_COLORS_STORAGE_KEY, {}),
|
||||
);
|
||||
const [highlightPrefs, setHighlightPrefs] = useState<HighlightPrefs>(() =>
|
||||
readJsonFromStorage<HighlightPrefs>(
|
||||
HIGHLIGHT_STORAGE_KEY,
|
||||
DEFAULT_HIGHLIGHT_PREFS,
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
writeJsonToStorage(HIGHLIGHT_STORAGE_KEY, highlightPrefs);
|
||||
}, [highlightPrefs]);
|
||||
|
||||
// Global "Edit / View" toggle for the schedule. The default is view mode
|
||||
// (no edit affordances). When a user with edit permission flips it on,
|
||||
@@ -1484,9 +1485,10 @@ function ScheduleSection({
|
||||
[meetings, date, refreshDay, toast, t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
writeJsonToStorage(COLS_STORAGE_KEY, columns);
|
||||
}, [columns]);
|
||||
// #265: columns persistence is owned by ExecutiveMeetingsPage now that
|
||||
// both Schedule and Settings tabs can mutate the array. Keeping the
|
||||
// write effect here would silently drop edits made from the Settings
|
||||
// tab while ScheduleSection is unmounted.
|
||||
|
||||
useEffect(() => {
|
||||
writeJsonToStorage(ROW_COLORS_STORAGE_KEY, rowColors);
|
||||
@@ -1633,13 +1635,6 @@ function ScheduleSection({
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<ColumnsCustomizer
|
||||
columns={columns}
|
||||
onChange={setColumns}
|
||||
t={t}
|
||||
highlightPrefs={highlightPrefs}
|
||||
onHighlightPrefsChange={setHighlightPrefs}
|
||||
/>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<span className="text-muted-foreground">{t("executiveMeetings.date")}</span>
|
||||
<input
|
||||
@@ -1939,7 +1934,7 @@ function ResizeHandle({
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnsCustomizer({
|
||||
function ColumnsCustomizerPanel({
|
||||
columns,
|
||||
onChange,
|
||||
t,
|
||||
@@ -1962,124 +1957,108 @@ function ColumnsCustomizer({
|
||||
const reset = () => onChange(DEFAULT_COLUMNS.map((c) => ({ ...c })));
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-1.5"
|
||||
data-testid="em-customize-columns-trigger"
|
||||
>
|
||||
<Sliders className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">{t("executiveMeetings.customize.title")}</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72 p-3" align="end">
|
||||
<div className="text-sm font-semibold text-[#0B1E3F] mb-2">
|
||||
{t("executiveMeetings.customize.title")}
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mb-3 leading-relaxed">
|
||||
{t("executiveMeetings.customize.hint")}
|
||||
</p>
|
||||
<ul className="space-y-1.5">
|
||||
{columns.map((c) => (
|
||||
<li
|
||||
key={c.id}
|
||||
className="flex items-center gap-2 rounded border border-gray-100 bg-gray-50 px-2 py-1.5"
|
||||
data-testid={`em-customize-row-${c.id}`}
|
||||
>
|
||||
<Checkbox
|
||||
id={`em-col-${c.id}`}
|
||||
checked={c.visible}
|
||||
onCheckedChange={(v) => toggle(c.id, !!v)}
|
||||
data-testid={`em-customize-toggle-${c.id}`}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`em-col-${c.id}`}
|
||||
className="flex-1 text-sm text-[#0B1E3F] cursor-pointer truncate"
|
||||
>
|
||||
{t(`executiveMeetings.col.${c.id}`)}
|
||||
</label>
|
||||
{!c.visible ? (
|
||||
<EyeOff className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-3.5 h-3.5 text-[#0B1E3F]" />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="gap-1.5 text-xs"
|
||||
onClick={reset}
|
||||
data-testid="em-customize-reset"
|
||||
<div data-testid="em-customize-columns-panel">
|
||||
<p className="text-xs text-muted-foreground mb-3 leading-relaxed">
|
||||
{t("executiveMeetings.customize.hint")}
|
||||
</p>
|
||||
<ul className="space-y-1.5">
|
||||
{columns.map((c) => (
|
||||
<li
|
||||
key={c.id}
|
||||
className="flex items-center gap-2 rounded border border-gray-100 bg-gray-50 px-2 py-1.5"
|
||||
data-testid={`em-customize-row-${c.id}`}
|
||||
>
|
||||
<RotateCcw className="w-3.5 h-3.5" />
|
||||
{t("executiveMeetings.customize.reset")}
|
||||
</Button>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{t("executiveMeetings.customize.dragHint")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-4 pt-3 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
htmlFor="em-highlight-toggle"
|
||||
className="text-sm font-semibold text-[#0B1E3F] cursor-pointer"
|
||||
>
|
||||
{t("executiveMeetings.highlight.title")}
|
||||
</label>
|
||||
<Switch
|
||||
id="em-highlight-toggle"
|
||||
checked={highlightPrefs.enabled}
|
||||
onCheckedChange={(v) =>
|
||||
onHighlightPrefsChange({ ...highlightPrefs, enabled: !!v })
|
||||
}
|
||||
data-testid="em-highlight-toggle"
|
||||
<Checkbox
|
||||
id={`em-col-${c.id}`}
|
||||
checked={c.visible}
|
||||
onCheckedChange={(v) => toggle(c.id, !!v)}
|
||||
data-testid={`em-customize-toggle-${c.id}`}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mb-2 leading-relaxed">
|
||||
{t("executiveMeetings.highlight.hint")}
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{HIGHLIGHT_COLOR_SWATCHES.map((c) => {
|
||||
const selected = highlightPrefs.color === c;
|
||||
return (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onHighlightPrefsChange({ ...highlightPrefs, color: c })
|
||||
}
|
||||
className={
|
||||
"w-6 h-6 rounded-full border-2 transition-transform " +
|
||||
(selected
|
||||
? "border-[#0B1E3F] scale-110"
|
||||
: "border-gray-300 hover:scale-110")
|
||||
}
|
||||
style={{ backgroundColor: c }}
|
||||
aria-label={`highlight color ${c}`}
|
||||
data-testid={`em-highlight-color-${c}`}
|
||||
disabled={!highlightPrefs.enabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onHighlightPrefsChange({ ...DEFAULT_HIGHLIGHT_PREFS })
|
||||
}
|
||||
className="ml-1 text-[11px] text-[#0B1E3F] underline hover:no-underline disabled:opacity-50"
|
||||
data-testid="em-highlight-reset"
|
||||
<label
|
||||
htmlFor={`em-col-${c.id}`}
|
||||
className="flex-1 text-sm text-[#0B1E3F] cursor-pointer truncate"
|
||||
>
|
||||
{t("executiveMeetings.highlight.reset")}
|
||||
</button>
|
||||
</div>
|
||||
{t(`executiveMeetings.col.${c.id}`)}
|
||||
</label>
|
||||
{!c.visible ? (
|
||||
<EyeOff className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-3.5 h-3.5 text-[#0B1E3F]" />
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="gap-1.5 text-xs"
|
||||
onClick={reset}
|
||||
data-testid="em-customize-reset"
|
||||
>
|
||||
<RotateCcw className="w-3.5 h-3.5" />
|
||||
{t("executiveMeetings.customize.reset")}
|
||||
</Button>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{t("executiveMeetings.customize.dragHint")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-4 pt-3 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label
|
||||
htmlFor="em-highlight-toggle"
|
||||
className="text-sm font-semibold text-[#0B1E3F] cursor-pointer"
|
||||
>
|
||||
{t("executiveMeetings.highlight.title")}
|
||||
</label>
|
||||
<Switch
|
||||
id="em-highlight-toggle"
|
||||
checked={highlightPrefs.enabled}
|
||||
onCheckedChange={(v) =>
|
||||
onHighlightPrefsChange({ ...highlightPrefs, enabled: !!v })
|
||||
}
|
||||
data-testid="em-highlight-toggle"
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<p className="text-[11px] text-muted-foreground mb-2 leading-relaxed">
|
||||
{t("executiveMeetings.highlight.hint")}
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{HIGHLIGHT_COLOR_SWATCHES.map((c) => {
|
||||
const selected = highlightPrefs.color === c;
|
||||
return (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onHighlightPrefsChange({ ...highlightPrefs, color: c })
|
||||
}
|
||||
className={
|
||||
"w-6 h-6 rounded-full border-2 transition-transform " +
|
||||
(selected
|
||||
? "border-[#0B1E3F] scale-110"
|
||||
: "border-gray-300 hover:scale-110")
|
||||
}
|
||||
style={{ backgroundColor: c }}
|
||||
aria-label={`highlight color ${c}`}
|
||||
data-testid={`em-highlight-color-${c}`}
|
||||
disabled={!highlightPrefs.enabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onHighlightPrefsChange({ ...DEFAULT_HIGHLIGHT_PREFS })
|
||||
}
|
||||
className="ml-1 text-[11px] text-[#0B1E3F] underline hover:no-underline disabled:opacity-50"
|
||||
data-testid="em-highlight-reset"
|
||||
>
|
||||
{t("executiveMeetings.highlight.reset")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5629,6 +5608,53 @@ function PdfSection({
|
||||
);
|
||||
}
|
||||
|
||||
// ============ SETTINGS ============
|
||||
// #265: Unified settings tab — wraps font preferences plus the column /
|
||||
// highlight customizer that used to live in a popover above the schedule.
|
||||
|
||||
function SettingsSection({
|
||||
font,
|
||||
canEditGlobalFont,
|
||||
columns,
|
||||
onColumnsChange,
|
||||
highlightPrefs,
|
||||
onHighlightPrefsChange,
|
||||
t,
|
||||
}: {
|
||||
font: FontPrefs;
|
||||
canEditGlobalFont: boolean;
|
||||
columns: ColumnSetting[];
|
||||
onColumnsChange: (next: ColumnSetting[]) => void;
|
||||
highlightPrefs: HighlightPrefs;
|
||||
onHighlightPrefsChange: (next: HighlightPrefs) => void;
|
||||
t: (k: string) => string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<section className="bg-white border border-gray-200 rounded-lg p-4 sm:p-6">
|
||||
<h2 className="text-base font-semibold text-[#0B1E3F] mb-3 flex items-center gap-2">
|
||||
<Sliders className="w-4 h-4" />
|
||||
{t("executiveMeetings.customize.title")}
|
||||
</h2>
|
||||
<ColumnsCustomizerPanel
|
||||
columns={columns}
|
||||
onChange={onColumnsChange}
|
||||
t={t}
|
||||
highlightPrefs={highlightPrefs}
|
||||
onHighlightPrefsChange={onHighlightPrefsChange}
|
||||
/>
|
||||
</section>
|
||||
<section className="bg-white border border-gray-200 rounded-lg p-4 sm:p-6">
|
||||
<FontSettingsSection
|
||||
font={font}
|
||||
canEditGlobal={canEditGlobalFont}
|
||||
t={t}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============ FONT SETTINGS ============
|
||||
|
||||
function FontSettingsSection({
|
||||
|
||||
@@ -316,11 +316,11 @@ test("Schedule: per-row select checkbox stays usable even when the # column is h
|
||||
});
|
||||
await expect(page.getByTestId(`em-row-${idB}`)).toBeVisible();
|
||||
|
||||
// Hide the # column via the customize-columns popover.
|
||||
await page.getByTestId("em-customize-columns-trigger").click();
|
||||
// #265: Customize-columns now lives in the Settings tab.
|
||||
// Hop over, toggle # off, hop back to the schedule.
|
||||
await page.getByTestId("em-nav-settings").click();
|
||||
await page.getByTestId("em-customize-toggle-number").click();
|
||||
// Close the popover by clicking the table area.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.getByTestId("em-nav-schedule").click();
|
||||
|
||||
// Enter edit mode. Per-row checkboxes must still appear (now as
|
||||
// overlays in the first visible cell), and selecting them must
|
||||
@@ -341,9 +341,9 @@ test("Schedule: per-row select checkbox stays usable even when the # column is h
|
||||
|
||||
// Restore the # column so we leave the persisted UI prefs in a
|
||||
// sane state for the next test.
|
||||
await page.getByTestId("em-customize-columns-trigger").click();
|
||||
await page.getByTestId("em-nav-settings").click();
|
||||
await page.getByTestId("em-customize-toggle-number").click();
|
||||
await page.keyboard.press("Escape");
|
||||
await page.getByTestId("em-nav-schedule").click();
|
||||
});
|
||||
|
||||
// Regression guard for merged rows. Earlier the per-row bulk-select
|
||||
|
||||
@@ -452,8 +452,9 @@ test("Schedule: choosing a custom highlight color paints the current meeting's b
|
||||
// Computed style normalizes hex to rgb; #16a34a → rgb(22, 163, 74).
|
||||
expect(initialShadow).toMatch(/22,\s*163,\s*74/);
|
||||
|
||||
// Now open the customize popover and pick the red swatch (#dc2626).
|
||||
await page.getByTestId("em-customize-columns-trigger").click();
|
||||
// #265: Highlight controls now live in the unified Settings tab.
|
||||
// Hop over, flip the swatch, hop back to the schedule.
|
||||
await page.getByTestId("em-nav-settings").click();
|
||||
const highlightToggle = page.getByTestId("em-highlight-toggle");
|
||||
await expect(highlightToggle).toBeVisible();
|
||||
// Make sure the highlight is enabled (the default is on, but assert
|
||||
@@ -465,8 +466,7 @@ test("Schedule: choosing a custom highlight color paints the current meeting's b
|
||||
}
|
||||
|
||||
await page.getByTestId("em-highlight-color-#dc2626").click();
|
||||
// Close the popover so it doesn't overlap further assertions.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.getByTestId("em-nav-schedule").click();
|
||||
|
||||
// The ring should now reflect the new color: #dc2626 → rgb(220, 38, 38).
|
||||
await expect
|
||||
|
||||
Reference in New Issue
Block a user