fix: dashboard counters refresh after status changes
When a booking status is changed, confirmed, or deleted in any tab, the dashboard summary cards re-fetch to show updated counts. Previously the dashboard was stale until page reload.
This commit is contained in:
@@ -14,6 +14,7 @@ interface GenericBookingsListProps<T extends BaseBooking> {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
filter: BookingFilter;
|
filter: BookingFilter;
|
||||||
onItemsChange: (fn: (prev: T[]) => T[]) => void;
|
onItemsChange: (fn: (prev: T[]) => T[]) => void;
|
||||||
|
onDataChange?: () => void;
|
||||||
groups?: BookingGroup<T>[];
|
groups?: BookingGroup<T>[];
|
||||||
renderExtra?: (item: T) => React.ReactNode;
|
renderExtra?: (item: T) => React.ReactNode;
|
||||||
onConfirm?: (id: number) => void;
|
onConfirm?: (id: number) => void;
|
||||||
@@ -24,6 +25,7 @@ export function GenericBookingsList<T extends BaseBooking>({
|
|||||||
endpoint,
|
endpoint,
|
||||||
filter,
|
filter,
|
||||||
onItemsChange,
|
onItemsChange,
|
||||||
|
onDataChange,
|
||||||
groups,
|
groups,
|
||||||
renderExtra,
|
renderExtra,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
@@ -47,6 +49,7 @@ export function GenericBookingsList<T extends BaseBooking>({
|
|||||||
body: JSON.stringify({ action: "set-status", id, status }),
|
body: JSON.stringify({ action: "set-status", id, status }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
|
onDataChange?.();
|
||||||
} catch {
|
} catch {
|
||||||
if (prevStatus) onItemsChange((list) => list.map((b) => b.id === id ? { ...b, status: prevStatus } : b));
|
if (prevStatus) onItemsChange((list) => list.map((b) => b.id === id ? { ...b, status: prevStatus } : b));
|
||||||
showError("Не удалось обновить статус");
|
showError("Не удалось обновить статус");
|
||||||
@@ -58,6 +61,7 @@ export function GenericBookingsList<T extends BaseBooking>({
|
|||||||
const res = await adminFetch(`${endpoint}?id=${id}`, { method: "DELETE" });
|
const res = await adminFetch(`${endpoint}?id=${id}`, { method: "DELETE" });
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
onItemsChange((list) => list.filter((b) => b.id !== id));
|
onItemsChange((list) => list.filter((b) => b.id !== id));
|
||||||
|
onDataChange?.();
|
||||||
} catch {
|
} catch {
|
||||||
showError("Не удалось удалить запись");
|
showError("Не удалось удалить запись");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface McRegistration extends BaseBooking {
|
|||||||
interface McSlot { date: string; startTime: string }
|
interface McSlot { date: string; startTime: string }
|
||||||
interface McItem { title: string; slots: McSlot[] }
|
interface McItem { title: string; slots: McSlot[] }
|
||||||
|
|
||||||
export function McRegistrationsTab({ filter }: { filter: BookingFilter }) {
|
export function McRegistrationsTab({ filter, onDataChange }: { filter: BookingFilter; onDataChange?: () => void }) {
|
||||||
const [regs, setRegs] = useState<McRegistration[]>([]);
|
const [regs, setRegs] = useState<McRegistration[]>([]);
|
||||||
const [mcDates, setMcDates] = useState<Record<string, string>>({});
|
const [mcDates, setMcDates] = useState<Record<string, string>>({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -75,6 +75,7 @@ export function McRegistrationsTab({ filter }: { filter: BookingFilter }) {
|
|||||||
endpoint="/api/admin/mc-registrations"
|
endpoint="/api/admin/mc-registrations"
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onItemsChange={setRegs}
|
onItemsChange={setRegs}
|
||||||
|
onDataChange={onDataChange}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface OpenDayBooking extends BaseBooking {
|
|||||||
|
|
||||||
interface EventInfo { id: number; date: string; title?: string }
|
interface EventInfo { id: number; date: string; title?: string }
|
||||||
|
|
||||||
export function OpenDayBookingsTab({ filter }: { filter: BookingFilter }) {
|
export function OpenDayBookingsTab({ filter, onDataChange }: { filter: BookingFilter; onDataChange?: () => void }) {
|
||||||
const [bookings, setBookings] = useState<OpenDayBooking[]>([]);
|
const [bookings, setBookings] = useState<OpenDayBooking[]>([]);
|
||||||
const [events, setEvents] = useState<EventInfo[]>([]);
|
const [events, setEvents] = useState<EventInfo[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -81,6 +81,7 @@ export function OpenDayBookingsTab({ filter }: { filter: BookingFilter }) {
|
|||||||
endpoint="/api/admin/open-day/bookings"
|
endpoint="/api/admin/open-day/bookings"
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onItemsChange={setBookings}
|
onItemsChange={setBookings}
|
||||||
|
onDataChange={onDataChange}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ function ConfirmModal({
|
|||||||
interface ScheduleClassInfo { type: string; trainer: string; time: string; day: string; hall: string; address: string; groupId?: string }
|
interface ScheduleClassInfo { type: string; trainer: string; time: string; day: string; hall: string; address: string; groupId?: string }
|
||||||
interface ScheduleLocation { name: string; address: string; days: { day: string; classes: { time: string; trainer: string; type: string; groupId?: string }[] }[] }
|
interface ScheduleLocation { name: string; address: string; days: { day: string; classes: { time: string; trainer: string; type: string; groupId?: string }[] }[] }
|
||||||
|
|
||||||
function GroupBookingsTab({ filter }: { filter: BookingFilter }) {
|
function GroupBookingsTab({ filter, onDataChange }: { filter: BookingFilter; onDataChange?: () => void }) {
|
||||||
const [bookings, setBookings] = useState<GroupBooking[]>([]);
|
const [bookings, setBookings] = useState<GroupBooking[]>([]);
|
||||||
const [allClasses, setAllClasses] = useState<ScheduleClassInfo[]>([]);
|
const [allClasses, setAllClasses] = useState<ScheduleClassInfo[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -284,6 +284,7 @@ function GroupBookingsTab({ filter }: { filter: BookingFilter }) {
|
|||||||
}) : Promise.resolve(),
|
}) : Promise.resolve(),
|
||||||
]);
|
]);
|
||||||
setConfirmingId(null);
|
setConfirmingId(null);
|
||||||
|
onDataChange?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
if (loading) return <LoadingSpinner />;
|
||||||
@@ -296,6 +297,7 @@ function GroupBookingsTab({ filter }: { filter: BookingFilter }) {
|
|||||||
endpoint="/api/admin/group-bookings"
|
endpoint="/api/admin/group-bookings"
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onItemsChange={setBookings}
|
onItemsChange={setBookings}
|
||||||
|
onDataChange={onDataChange}
|
||||||
onConfirm={(id) => setConfirmingId(id)}
|
onConfirm={(id) => setConfirmingId(id)}
|
||||||
renderExtra={(b) => (
|
renderExtra={(b) => (
|
||||||
<>
|
<>
|
||||||
@@ -700,6 +702,8 @@ function BookingsPageInner() {
|
|||||||
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 [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
const [dashboardKey, setDashboardKey] = useState(0);
|
||||||
|
const refreshDashboard = useCallback(() => setDashboardKey((k) => k + 1), []);
|
||||||
const lastTotalRef = useRef<number | null>(null);
|
const lastTotalRef = useRef<number | null>(null);
|
||||||
const { showError } = useToast();
|
const { showError } = useToast();
|
||||||
|
|
||||||
@@ -814,7 +818,7 @@ function BookingsPageInner() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Dashboard — what needs attention */}
|
{/* Dashboard — what needs attention */}
|
||||||
<DashboardSummary key={refreshKey} onNavigate={setTab} />
|
<DashboardSummary key={`dash-${dashboardKey}-${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">
|
||||||
@@ -839,9 +843,9 @@ function BookingsPageInner() {
|
|||||||
{/* Tab content — auto-refreshes when new bookings detected */}
|
{/* Tab content — auto-refreshes when new bookings detected */}
|
||||||
<div className="mt-4" key={`tab-${refreshKey}`}>
|
<div className="mt-4" key={`tab-${refreshKey}`}>
|
||||||
{tab === "reminders" && <RemindersTab />}
|
{tab === "reminders" && <RemindersTab />}
|
||||||
{tab === "classes" && <GroupBookingsTab filter={statusFilter} />}
|
{tab === "classes" && <GroupBookingsTab filter={statusFilter} onDataChange={refreshDashboard} />}
|
||||||
{tab === "master-classes" && <McRegistrationsTab filter={statusFilter} />}
|
{tab === "master-classes" && <McRegistrationsTab filter={statusFilter} onDataChange={refreshDashboard} />}
|
||||||
{tab === "open-day" && <OpenDayBookingsTab filter={statusFilter} />}
|
{tab === "open-day" && <OpenDayBookingsTab filter={statusFilter} onDataChange={refreshDashboard} />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user