Remove Notifications tab from Executive Meetings (Task #616)

The Notifications tab inside Executive Meetings only ever rendered a
log of `meeting_created` fan-out rows (the only event type currently
wired), was filtered to the selected schedule date, and overlapped
with the Audit log. For real users it was almost always empty and
just took space in the tab strip — see attached iPhone screenshot in
the conversation. User asked explicitly to delete it.

Changes:

1. `artifacts/tx-os/src/pages/executive-meetings.tsx`
   - Removed the `{ key: "notifications", icon: Bell }` entry from
     the `SECTIONS` array so the tab no longer appears in the strip.
   - Removed the `case "notifications": return me.canRead;` branch
     from `isSectionVisible`.
   - Removed the `{section === "notifications" && …}` render branch
     and the `<NotificationsSection …/>` JSX.
   - Deleted the `NotificationsSection` component entirely (~90 lines)
     and the unused `NotificationRow` type.
   - Left the `Bell` import in place — still used by the bell button
     elsewhere in the page (line ~8580).

2. `artifacts/tx-os/src/locales/{ar,en}.json`
   - Removed the entire `executiveMeetings.notificationsPage` block
     (headers, status labels, type labels, empty state). All keys
     were verified to only be consumed by the just-deleted component.
   - Left `executiveMeetings.notifications.*` alone — those are the
     per-user preferences UI strings and are unrelated.

3. Backend left untouched on purpose:
   - `GET /api/executive-meetings/notifications` still exists.
   - `recordExecutiveMeetingNotifications` still runs on meeting
     create and still writes to `executive_meeting_notifications`.
   - The bell icon, push, and notification preferences UI are
     unaffected because they read `notificationsTable`, not the
     executive-meeting-specific table.

The orphaned `GET /executive-meetings/notifications` route now has
no frontend caller. I'm leaving it in this commit (it's harmless,
still permission-gated) and proposing a follow-up to remove it
cleanly instead of expanding scope here.

Code review: not run yet — will run after committing per the
standard flow.
This commit is contained in:
riyadhafraa
2026-05-19 12:02:41 +00:00
parent d50dd8dbb4
commit d17587e096
3 changed files with 0 additions and 162 deletions
-22
View File
@@ -1642,28 +1642,6 @@
"save": "حفظ الإعدادات",
"saved": "تم الحفظ",
"reset": "إعادة الافتراضي"
},
"notificationsPage": {
"empty": "لا توجد تنبيهات بعد.",
"col": {
"meeting": "الاجتماع",
"user": "المستخدم",
"type": "النوع",
"scheduledAt": "الموعد",
"status": "الحالة"
},
"type": {
"reminder": "تذكير",
"change": "تنبيه تعديل",
"cancel": "تنبيه إلغاء",
"meeting_created": "اجتماع جديد"
},
"status": {
"pending": "بالانتظار",
"sent": "أُرسل",
"skipped": "متخطى",
"failed": "فشل"
}
}
}
}
-22
View File
@@ -1515,28 +1515,6 @@
"save": "Save settings",
"saved": "Saved",
"reset": "Reset to default"
},
"notificationsPage": {
"empty": "No notifications yet.",
"col": {
"meeting": "Meeting",
"user": "User",
"type": "Type",
"scheduledAt": "Scheduled",
"status": "Status"
},
"type": {
"reminder": "Reminder",
"change": "Change notice",
"cancel": "Cancellation notice",
"meeting_created": "Meeting created"
},
"status": {
"pending": "Pending",
"sent": "Sent",
"skipped": "Skipped",
"failed": "Failed"
}
}
}
}
@@ -209,22 +209,6 @@ type AuditEntry = {
} | null;
};
type NotificationRow = {
id: number;
meetingId: number | null;
userId: number | null;
notificationType: string;
scheduledAt: string | null;
sentAt: string | null;
status: string;
createdAt: string;
user: {
username: string;
displayNameAr: string | null;
displayNameEn: string | null;
} | null;
};
type FontPrefs = {
fontFamily: string;
fontSize: number;
@@ -286,7 +270,6 @@ function formatBytesForDisplay(bytes: number): string {
const SECTIONS = [
{ key: "schedule", icon: CalendarClock },
{ key: "manage", icon: ListChecks },
{ key: "notifications", icon: Bell },
{ key: "audit", icon: ScrollText },
{ key: "pdf", icon: FileText },
{ key: "settings", icon: SettingsIcon },
@@ -317,8 +300,6 @@ function isSectionVisible(
return me.canRead;
case "manage":
return me.canMutate;
case "notifications":
return me.canRead;
case "audit":
return me.canViewAudit;
default:
@@ -1266,14 +1247,6 @@ function ExecutiveMeetingsPageInner() {
{section === "manage" && me && (
<ManageSection date={date} onDateChange={setDate} canMutate={me.canMutate} isRtl={isRtl} t={t} />
)}
{section === "notifications" && me && (
<NotificationsSection
canView={me.canRead}
date={date}
isRtl={isRtl}
t={t}
/>
)}
{section === "audit" && me && (
<AuditSection canView={me.canViewAudit} isRtl={isRtl} t={t} />
)}
@@ -8237,97 +8210,6 @@ function AuditSection({
);
}
// ============ NOTIFICATIONS ============
function NotificationsSection({
canView,
date,
isRtl,
t,
}: {
canView: boolean;
date: string;
isRtl: boolean;
t: (k: string) => string;
}) {
const { data, isLoading } = useQuery<{ notifications: NotificationRow[] }>({
queryKey: ["/api/executive-meetings/notifications", date],
queryFn: () =>
apiJson(
`/api/executive-meetings/notifications${
date ? `?date=${encodeURIComponent(date)}` : ""
}`,
),
enabled: canView,
});
if (!canView) return <NoPermission t={t} />;
const rows = data?.notifications ?? [];
return (
<div className="space-y-4">
<div className="bg-white border border-gray-200 rounded-md overflow-hidden">
<table className="w-full text-sm" dir={isRtl ? "rtl" : "ltr"}>
<thead className="bg-gray-50 text-[#0B1E3F]">
<tr>
<th className="px-3 py-2 text-start w-24">{t("executiveMeetings.notificationsPage.col.meeting")}</th>
<th className="px-3 py-2 text-start">{t("executiveMeetings.notificationsPage.col.user")}</th>
<th className="px-3 py-2 text-start w-28">{t("executiveMeetings.notificationsPage.col.type")}</th>
<th className="px-3 py-2 text-start w-44">{t("executiveMeetings.notificationsPage.col.scheduledAt")}</th>
<th className="px-3 py-2 text-start w-24">{t("executiveMeetings.notificationsPage.col.status")}</th>
</tr>
</thead>
<tbody>
{isLoading && (
<tr>
<td colSpan={5} className="py-8 text-center text-gray-500">
{t("common.loading")}
</td>
</tr>
)}
{!isLoading && rows.length === 0 && (
<tr>
<td colSpan={5} className="py-8 text-center text-gray-500">
{t("executiveMeetings.notificationsPage.empty")}
</td>
</tr>
)}
{rows.map((n) => {
const user = n.user
? isRtl
? n.user.displayNameAr || n.user.username
: n.user.displayNameEn || n.user.username
: "—";
return (
<tr key={n.id} className="border-t border-gray-100">
<td className="px-3 py-2">{n.meetingId ? `#${n.meetingId}` : "—"}</td>
<td className="px-3 py-2">{user}</td>
<td className="px-3 py-2 text-xs">
{tWithFallback(
t,
`executiveMeetings.notificationsPage.type.${n.notificationType}`,
n.notificationType,
)}
</td>
<td className="px-3 py-2 text-xs">
{n.scheduledAt ? new Date(n.scheduledAt).toLocaleString() : "—"}
</td>
<td className="px-3 py-2 text-xs">
{tWithFallback(
t,
`executiveMeetings.notificationsPage.status.${n.status}`,
n.status,
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
// ============ PDF / PRINT ============
function PdfSection({