PageSourceSearch

https://dhqfootball.com/js/shared/saved-reports.js?v=c0601d4b86

js dhqfootball.com collected 2026-10-04 08:01:04 UTC 6,280 bytes, 137 lines download raw bytes

1// ══════════════════════════════════════════════════════════════════
2// saved-reports.js — Saved Alex reports (draft plans, class reads, asks)
3//
4// Dual persistence:
5//   • Local cache (WrStorage) — immediate, offline, survives the mock-auth
6//     preview. Source of truth for instant UI.
7//   • Server sync (window.OD.saveAIAnalysis / loadAIHistory → ai_analysis
8//     table, owner-scoped) — follows the user across devices when a real
9//     session is present. Degrades to a no-op under mock/anon auth.
10//
11// Public API (window.WR.SavedReports):
12//   listLocal(leagueId)                         → Array<Report>  (sync)
13//   save(leagueId, { title, prompt, content, kind }) → Report     (async)
14//   remove(leagueId, id)                        → void
15//   syncFromServer(leagueId)                    → Array<Report>  (async, merged)
16//
17// Report schema:
18//   { id, title, prompt, content, kind, createdAt, server? }
19// ══════════════════════════════════════════════════════════════════
20(function () {
21    // Resolve lazily: this plain script can load before window.App.WrStorage
22    // is initialized, so we must look it up at call time, not at module load.
23    function getStore() { return (window.App && window.App.WrStorage) || null; }
24
25    function key(leagueId) { return 'wr_saved_reports_' + (leagueId || 'global'); }
26
27    function readLocal(leagueId) {
28        const WrStorage = getStore();
29        if (!WrStorage) return [];
30        const raw = WrStorage.get(key(leagueId), []);
31        return Array.isArray(raw) ? raw : [];
32    }
33
34    function writeLocal(leagueId, arr) {
35        const WrStorage = getStore();
36        if (WrStorage) WrStorage.set(key(leagueId), arr.slice(0, 50));
37    }
38
39    function genId() {
40        return 'r_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
41    }
42
43    // Dedupe key so a locally-cached save and its server echo collapse into one row.
44    function dedupeKey(r) {
45        if (r.serverId) return 'sid:' + r.serverId;
46        if (typeof r.id === 'string' && r.id.indexOf('srv_') === 0) return 'sid:' + r.id.slice(4);
47        return (r.kind || '') + '|' + (r.title || '') + '|' + String(r.content || '').slice(0, 60);
48    }
49
50    function sortNewest(arr) {
51        return arr.slice().sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
52    }
53
54    const API = {
55        listLocal(leagueId) {
56            return sortNewest(readLocal(leagueId));
57        },
58
59        async save(leagueId, { title, prompt, content, kind } = {}) {
60            const rec = {
61                id: genId(),
62                title: String(title || 'Report').trim().slice(0, 80),
63                prompt: String(prompt || ''),
64                content: String(content || ''),
65                kind: kind || 'report',
66                createdAt: Date.now(),
67            };
68            const arr = readLocal(leagueId);
69            arr.unshift(rec);
70            writeLocal(leagueId, arr);
71            // Server sync. No-ops if not configured / not authed. Capture the
72            // new row id so this report can be deleted server-side later.
73            try {
74                if (window.OD && typeof window.OD.saveAIAnalysis === 'function') {
75                    const serverId = await window.OD.saveAIAnalysis(leagueId, 'wr-' + rec.kind, rec.title, rec.content);
76                    if (serverId) {
77                        rec.serverId = serverId;
78                        const cur = readLocal(leagueId);
79                        const i = cur.findIndex(r => r.id === rec.id);
80                        if (i >= 0) { cur[i].serverId = serverId; writeLocal(leagueId, cur); }
81                    }
82                }
83            } catch (e) {
84                if (window.wrLog) window.wrLog('saved-reports.sync', e);
85            }
86            return rec;
87        },
88
89        remove(leagueId, id) {
90            const arr = readLocal(leagueId);
91            const rec = arr.find(r => r.id === id);
92            let serverId = (rec && rec.serverId) || null;
93            // Server-only rows (from syncFromServer) carry the id as 'srv_<rowId>'.
94            if (!serverId && typeof id === 'string' && id.indexOf('srv_') === 0) serverId = id.slice(4);
95            if (serverId && window.OD && typeof window.OD.deleteAIAnalysis === 'function') {
96                try { window.OD.deleteAIAnalysis(serverId); } catch (e) { /* local removal still applies */ }
97            }
98            writeLocal(leagueId, arr.filter(r => r.id !== id));
99        },
100
101        async syncFromServer(leagueId) {
102            const local = readLocal(leagueId);
103            let server = [];
104            if (window.OD && typeof window.OD.loadAIHistory === 'function') {
105                try {
106                    const rows = await window.OD.loadAIHistory(leagueId);
107                    server = (Array.isArray(rows) ? rows : [])
108                        .filter(r => typeof r.type === 'string' && r.type.indexOf('wr-') === 0)
109                        .map(r => ({
110                            id: 'srv_' + r.id,
111                            title: r.context_summary || 'Report',
112                            prompt: '',
113                            content: r.analysis || '',
114                            kind: r.type.replace(/^wr-/, ''),
115                            createdAt: r.created_at ? new Date(r.created_at).getTime() : Date.now(),
116                            server: true,
117                        }));
118                } catch (e) {
119                    server = [];
120                }
121            }
122            const seen = new Set();
123            const merged = [];
124            // Local first so local ids win on dedupe (keeps delete working in-session).
125            sortNewest(local.concat(server)).forEach(r => {
126                const k = dedupeKey(r);
127                if (seen.has(k)) return;
128                seen.add(k);
129                merged.push(r);
130            });
131            return merged.slice(0, 50);
132        },
133    };
134
135    window.WR = window.WR || {};
136    window.WR.SavedReports = API;
137})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.