first commit

This commit is contained in:
Mateusz Gruszczyński
2026-03-23 15:56:18 +01:00
commit c5cc2efbac
106 changed files with 10254 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
export * from "./useAnalytics";
export * from "./useDashboardConfig";
export * from "./useHistoricalImport";
export * from "./useRealtimeHistory";
export * from "./useRealtimeSocket";

View File

@@ -0,0 +1,26 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../api/client";
export function useAnalytics(
rangeKey: string,
bucket: string,
compare: string,
enabled = true,
options?: { start?: string; end?: string; publicKiosk?: boolean; compareRanges?: Array<{ start: string; end: string; label: string; key?: string }> },
) {
const production = useQuery({
queryKey: ["analytics", rangeKey, bucket, compare, options?.start, options?.end, options?.publicKiosk, JSON.stringify(options?.compareRanges ?? [])],
queryFn: () => api.getAnalytics(rangeKey, bucket, compare, options),
staleTime: 60 * 1000,
enabled,
});
const distribution = useQuery({
queryKey: ["distribution", rangeKey, bucket, options?.start, options?.end, options?.publicKiosk],
queryFn: () => api.getDistribution(rangeKey, bucket, options),
staleTime: 60 * 1000,
enabled,
});
return { production, distribution };
}

View File

@@ -0,0 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../api/client";
export function useDashboardConfig(enabled = true) {
return useQuery({
queryKey: ["dashboard-config"],
queryFn: api.getConfig,
staleTime: 5 * 60 * 1000,
enabled,
});
}

View File

@@ -0,0 +1,40 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api } from "../api/client";
import type { HistoricalStartPayload } from "../types";
export function useHistoricalImport(enabled = true) {
const queryClient = useQueryClient();
const status = useQuery({
queryKey: ["historical-status"],
queryFn: api.getHistoricalStatus,
staleTime: 5 * 1000,
enabled,
refetchInterval: (query) => (query.state.data?.running ? 3000 : 15000),
});
const start = useMutation({
mutationFn: (payload: HistoricalStartPayload) => api.startHistoricalImport(payload),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["historical-status"] });
await queryClient.invalidateQueries({ queryKey: ["analytics"] });
await queryClient.invalidateQueries({ queryKey: ["distribution"] });
},
});
const syncNow = useMutation({
mutationFn: api.syncHistoricalNow,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["historical-status"] });
},
});
const cancel = useMutation({
mutationFn: api.cancelHistoricalImport,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["historical-status"] });
},
});
return { status, start, syncNow, cancel };
}

View File

@@ -0,0 +1,16 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../api/client";
export function useRealtimeHistory(
rangeKey: string,
enabled = true,
options?: { start?: string; end?: string; metrics?: string[]; publicKiosk?: boolean },
) {
return useQuery({
queryKey: ["realtime-history", rangeKey, options?.start, options?.end, options?.metrics?.join(","), options?.publicKiosk],
queryFn: () => api.getRealtimeHistory(rangeKey, options),
staleTime: 20 * 1000,
refetchInterval: options?.start || options?.end ? false : 30 * 1000,
enabled,
});
}

View File

@@ -0,0 +1,66 @@
import { useEffect, useMemo, useState } from "react";
import { api } from "../api/client";
import type { SnapshotPayload } from "../types";
const EMPTY_SNAPSHOT: SnapshotPayload = {
hero_cards: [],
kpis: {},
strings: [],
phases: [],
status: [],
faults: [],
};
const POLL_MS = Number(import.meta.env.VITE_LIVE_POLL_MS ?? 8000);
export function useRealtimeSocket(enabled = true) {
const [snapshot, setSnapshot] = useState<SnapshotPayload>(EMPTY_SNAPSHOT);
const [connected, setConnected] = useState(false);
useEffect(() => {
if (!enabled) {
setSnapshot(EMPTY_SNAPSHOT);
setConnected(false);
return;
}
let isActive = true;
let timer: number | null = null;
const poll = async () => {
try {
const data = await api.getRealtimeSnapshot();
if (!isActive) {
return;
}
setSnapshot(data);
setConnected(true);
} catch {
if (isActive) {
setConnected(false);
}
} finally {
if (isActive) {
timer = window.setTimeout(poll, POLL_MS);
}
}
};
void poll();
return () => {
isActive = false;
if (timer !== null) {
window.clearTimeout(timer);
}
};
}, [enabled]);
const lastUpdated = useMemo(() => snapshot.updated_at ?? null, [snapshot.updated_at]);
return {
snapshot,
connected,
lastUpdated,
};
}