fix: auto-refresh bookings silently instead of showing update banner

Polling detects new bookings and silently re-fetches tab data + dashboard
counts. No more "press to update" banner — data appears automatically.
This commit is contained in:
2026-03-24 17:41:08 +03:00
parent 18c11d0611
commit 42be063c7f

View File

@@ -699,29 +699,24 @@ function BookingsPageInner() {
const [addOpen, setAddOpen] = useState(false); const [addOpen, setAddOpen] = useState(false);
const [searchResults, setSearchResults] = useState<SearchResult[] | null>(null); const [searchResults, setSearchResults] = useState<SearchResult[] | null>(null);
const [statusFilter, setStatusFilter] = useState<BookingFilter>("all"); const [statusFilter, setStatusFilter] = useState<BookingFilter>("all");
const [newBookingsBanner, setNewBookingsBanner] = useState(false); const [refreshKey, setRefreshKey] = useState(0);
const lastTotalRef = useRef<number | null>(null); const lastTotalRef = useRef<number | null>(null);
const { showError } = useToast(); const { showError } = useToast();
// #10: Pause polling when browser tab not visible // Poll for new bookings, auto-refresh silently
useEffect(() => { useEffect(() => {
let id: ReturnType<typeof setInterval>; const id = setInterval(() => {
function startPolling() {
id = setInterval(() => {
if (document.hidden) return; if (document.hidden) return;
adminFetch("/api/admin/unread-counts") adminFetch("/api/admin/unread-counts")
.then((r) => r.json()) .then((r) => r.json())
.then((data: { total: number }) => { .then((data: { total: number }) => {
if (lastTotalRef.current !== null && data.total !== lastTotalRef.current) { if (lastTotalRef.current !== null && data.total !== lastTotalRef.current) {
// #6: Show banner instead of remounting with key setRefreshKey((k) => k + 1);
setNewBookingsBanner(true);
} }
lastTotalRef.current = data.total; lastTotalRef.current = data.total;
}) })
.catch(() => {}); .catch(() => {});
}, 10000); }, 10000);
}
startPolling();
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
@@ -772,16 +767,6 @@ function BookingsPageInner() {
</button> </button>
</div> </div>
{/* #6: New bookings banner instead of full remount */}
{newBookingsBanner && (
<button
onClick={() => { setNewBookingsBanner(false); window.location.reload(); }}
className="mt-3 w-full rounded-lg border border-gold/30 bg-gold/10 px-4 py-2 text-sm text-gold hover:bg-gold/20 transition-all text-center"
>
Появились новые записи нажмите для обновления
</button>
)}
{/* Search */} {/* Search */}
<div className="mt-3"> <div className="mt-3">
<SearchBar <SearchBar
@@ -829,7 +814,7 @@ function BookingsPageInner() {
) : ( ) : (
<> <>
{/* Dashboard — what needs attention */} {/* Dashboard — what needs attention */}
<DashboardSummary onNavigate={setTab} /> <DashboardSummary key={refreshKey} onNavigate={setTab} />
{/* Tabs */} {/* Tabs */}
<div className="mt-5 flex border-b border-white/10"> <div className="mt-5 flex border-b border-white/10">
@@ -851,8 +836,8 @@ function BookingsPageInner() {
))} ))}
</div> </div>
{/* Tab content — no key={refreshKey}, banner handles new data */} {/* Tab content — auto-refreshes when new bookings detected */}
<div className="mt-4"> <div className="mt-4" key={refreshKey}>
{tab === "reminders" && <RemindersTab />} {tab === "reminders" && <RemindersTab />}
{tab === "classes" && <GroupBookingsTab filter={statusFilter} />} {tab === "classes" && <GroupBookingsTab filter={statusFilter} />}
{tab === "master-classes" && <McRegistrationsTab filter={statusFilter} />} {tab === "master-classes" && <McRegistrationsTab filter={statusFilter} />}
@@ -864,7 +849,7 @@ function BookingsPageInner() {
<AddBookingModal <AddBookingModal
open={addOpen} open={addOpen}
onClose={() => setAddOpen(false)} onClose={() => setAddOpen(false)}
onAdded={() => setNewBookingsBanner(true)} onAdded={() => setRefreshKey((k) => k + 1)}
/> />
</div> </div>
); );