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:
@@ -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": "فشل"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user