import { api } from "./api"; export interface AttendanceSite { _key: string; label: string; kind: "store" | "office" | "warehouse"; lat: number; lng: number; status: "checked_in" | "checked_out" | "late"; who: string; city: string; } const SOURCE_CTX = "CANVAS_ATTENDANCE_SITE"; function authHeaders(): Record { return { Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}`, "Content-Type": "application/json", }; } function newRequestId(): string { if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID(); return `att-${Date.now()}-${Math.random().toString(36).slice(2, 12)}`; } async function jsonFetch(path: string, init?: RequestInit) { const res = await fetch(`${api.config.baseUrl}${path}`, { ...init, headers: { ...authHeaders(), ...(init?.headers || {}) } }); if (!res.ok) { const t = await res.text().catch(() => ""); throw new Error(`${path} ${res.status}: ${t.slice(0, 200)}`); } return res.json(); } function siteFromConfig(doc: any): AttendanceSite | null { const cfg = doc?.config?.attendance; if (!cfg) return null; if (typeof cfg.lat !== "number" || typeof cfg.lng !== "number") return null; return { _key: doc._key, label: doc.display_name || cfg.label || "Site", kind: (cfg.kind || "office") as AttendanceSite["kind"], lat: cfg.lat, lng: cfg.lng, status: (cfg.status || "checked_in") as AttendanceSite["status"], who: cfg.who || "—", city: cfg.city || "", }; } const ANCHOR_KEY = "attendance_root"; async function ensureAnchor(): Promise { try { const head = await fetch(`${api.config.baseUrl}/api/ea2/flow/${ANCHOR_KEY}`, { headers: authHeaders() }); if (head.ok) return true; } catch { /* fall through */ } const res = await fetch(`${api.config.baseUrl}/api/ea2/apply-batch`, { method: "POST", headers: authHeaders(), body: JSON.stringify({ request_id: newRequestId(), ops: [ { op: "create", coll: "flow", data: { _key: ANCHOR_KEY, kind: "value", status: "published", name: ANCHOR_KEY, display_name: "Attendance · root", description: "Anchor doc for attendance sites", source_context: "CANVAS_ATTENDANCE_ROOT", }, }, ], }), }); return res.ok || res.status === 409; } export const attendanceApi = { async listSites(signal?: AbortSignal): Promise { await ensureAnchor(); const edges = await jsonFetch(`/api/ea2/edges/defines?from=flow/${ANCHOR_KEY}&limit=200`, { signal }); const childKeys = ((edges?.items || []) as any[]) .filter((e) => e?.role === "presentation") .map((e) => (e._to || "").split("/").pop()) .filter(Boolean) as string[]; const docs = await Promise.all( childKeys.map(async (k) => { try { return await jsonFetch(`/api/ea2/flow/${k}`, { signal }); } catch { return null; } }) ); return docs .filter((d) => d?.source_context === SOURCE_CTX) .map((d) => siteFromConfig(d)) .filter((s): s is AttendanceSite => !!s); }, };