|
1 | | -import { useEffect, useState } from "react"; |
2 | 1 | import { useAuth } from "../auth/AuthContext.jsx"; |
3 | 2 | import "./AdminPage.css"; |
4 | 3 |
|
5 | 4 | const adminLinks = [ |
6 | 5 | { href: "/admin/stats", icon: "📊", title: "Platform Statistics", desc: "Overview of projects, hours, users, and shop activity" }, |
7 | 6 | { href: "/admin/users", icon: "👥", title: "Users", desc: "Bolts, projects, hours, journals, and shop activity per participant" }, |
8 | | - { href: "/admin/journals", icon: "🧾", title: "Journal entries (CSV)", desc: "All journal rows in one spreadsheet, sorted by project then time" }, |
| 7 | + { href: "/api/admin/journals.csv", icon: "🧾", title: "Journal entries (CSV)", desc: "All journal rows in one spreadsheet, sorted by project then time" }, |
9 | 8 | { href: "/admin/review", icon: "📋", title: "Project Review", desc: "Review shipped projects and approve hours" }, |
10 | 9 | { href: "/admin/shop", icon: "🛒", title: "Shop Admin", desc: "Manage catalog, categories, and pricing" }, |
11 | 10 | { href: "/admin/shop/orders", icon: "📦", title: "Shop orders", desc: "Fulfillment queue, group by user or item, refunds" }, |
12 | | - { href: "/admin/items_request", icon: "📝", title: "Items Request", desc: "View Exchange Desk item requests" }, |
13 | | - { href: "/admin/blazer", icon: "🔍", title: "Blazer", desc: "Database queries and dashboards" }, |
14 | | - { href: "/admin/flipper", icon: "🚩", title: "Flipper", desc: "Feature flags" }, |
15 | | - { href: "/admin/jobs", icon: "⚙️", title: "Solid Queue", desc: "Background job queue" }, |
16 | | - { href: "#admin-airtable-sync", icon: "🔄", title: "Airtable Sync", desc: "Sync status, logs, and diagnostics" }, |
17 | | - { href: "/admin/console", icon: "💎", title: "Ruby Console", desc: "Execute Ruby code on the server" }, |
| 11 | + { href: "/admin/airtable_sync", icon: "🔄", title: "Airtable Sync", desc: "Sync status, logs, and diagnostics" }, |
18 | 12 | ]; |
19 | 13 |
|
20 | 14 | export function AdminPage() { |
21 | 15 | const { user } = useAuth(); |
22 | 16 | const showSuperAdmin = user?.role === "super_admin"; |
23 | | - const [rows, setRows] = useState([]); |
24 | | - const [status, setStatus] = useState("Loading test rows..."); |
25 | | - const [error, setError] = useState(""); |
26 | | - const [syncStatus, setSyncStatus] = useState(null); |
27 | | - const [syncError, setSyncError] = useState(""); |
28 | | - const [syncMessage, setSyncMessage] = useState(""); |
29 | | - const [isSyncing, setIsSyncing] = useState(false); |
30 | | - |
31 | | - useEffect(() => { |
32 | | - let isMounted = true; |
33 | | - |
34 | | - async function bootstrap() { |
35 | | - try { |
36 | | - await Promise.all([loadRows(), loadSyncStatus()]); |
37 | | - if (isMounted) setStatus(""); |
38 | | - } catch (err) { |
39 | | - if (isMounted) { |
40 | | - setError(err.message); |
41 | | - setStatus(""); |
42 | | - } |
43 | | - } |
44 | | - } |
45 | | - |
46 | | - bootstrap(); |
47 | | - return () => { |
48 | | - isMounted = false; |
49 | | - }; |
50 | | - }, []); |
51 | | - |
52 | | - async function loadRows() { |
53 | | - const response = await fetch("/api/test"); |
54 | | - const isJson = response.headers.get("content-type")?.includes("application/json"); |
55 | | - const data = isJson ? await response.json() : {}; |
56 | | - if (!response.ok) { |
57 | | - throw new Error(data.error || "Failed to load test rows."); |
58 | | - } |
59 | | - setRows(data.rows || []); |
60 | | - } |
61 | | - |
62 | | - async function loadSyncStatus() { |
63 | | - const response = await fetch("/api/airtable/status"); |
64 | | - const isJson = response.headers.get("content-type")?.includes("application/json"); |
65 | | - const data = isJson ? await response.json() : {}; |
66 | | - if (!response.ok) { |
67 | | - throw new Error(data.error || "Failed to load sync status."); |
68 | | - } |
69 | | - setSyncStatus(data); |
70 | | - setSyncError(""); |
71 | | - return data; |
72 | | - } |
73 | | - |
74 | | - async function handleSyncNow() { |
75 | | - setIsSyncing(true); |
76 | | - setSyncError(""); |
77 | | - setSyncMessage(""); |
78 | | - try { |
79 | | - const response = await fetch("/api/airtable/sync", { method: "POST" }); |
80 | | - const isJson = response.headers.get("content-type")?.includes("application/json"); |
81 | | - const data = isJson ? await response.json() : {}; |
82 | | - if (!response.ok) { |
83 | | - throw new Error(data.error || data.message || "Failed to sync Airtable."); |
84 | | - } |
85 | | - setSyncMessage("Sync finished."); |
86 | | - await Promise.all([loadRows(), loadSyncStatus()]); |
87 | | - } catch (err) { |
88 | | - setSyncError(err.message); |
89 | | - } finally { |
90 | | - setIsSyncing(false); |
91 | | - } |
92 | | - } |
93 | | - |
94 | | - const lastSyncTime = syncStatus?.lastSync?.syncedAt |
95 | | - ? new Date(syncStatus.lastSync.syncedAt).toLocaleString() |
96 | | - : "Never synced"; |
97 | | - const syncTables = syncStatus?.lastSync?.tables || []; |
98 | | - const lastSyncError = syncStatus?.lastSync?.ok === false ? syncStatus.lastSync.error : ""; |
99 | 17 |
|
100 | 18 | return ( |
101 | 19 | <main className="admin-page" aria-label="Admin page"> |
@@ -129,49 +47,6 @@ export function AdminPage() { |
129 | 47 | </section> |
130 | 48 | ) : null} |
131 | 49 |
|
132 | | - <section id="admin-airtable-sync" className="admin-airtable"> |
133 | | - <h2>Airtable Sync</h2> |
134 | | - <p className="admin-airtable-subtitle">Re-sync Stack Airtable from the latest database state.</p> |
135 | | - |
136 | | - <div className="admin-airtable-sync-row"> |
137 | | - <div> |
138 | | - <strong>Airtable sync</strong> |
139 | | - <span>Last update: {lastSyncTime}</span> |
140 | | - </div> |
141 | | - <button type="button" onClick={handleSyncNow} disabled={isSyncing}> |
142 | | - {isSyncing ? "Syncing..." : "Sync now"} |
143 | | - </button> |
144 | | - </div> |
145 | | - |
146 | | - {syncMessage ? <p className="admin-airtable-success">{syncMessage}</p> : null} |
147 | | - {syncError ? <p className="admin-airtable-error">{syncError}</p> : null} |
148 | | - {lastSyncError ? <p className="admin-airtable-error">Last sync error: {lastSyncError}</p> : null} |
149 | | - {status ? <p>{status}</p> : null} |
150 | | - {error ? <p className="admin-airtable-error">{error}</p> : null} |
151 | | - |
152 | | - {syncTables.length > 0 ? ( |
153 | | - <div className="admin-airtable-details"> |
154 | | - <strong>Sync details</strong> |
155 | | - {syncTables.map((table) => ( |
156 | | - <p key={table.table}> |
157 | | - <code>{table.table}</code>:{" "} |
158 | | - {table.skipped |
159 | | - ? `Skipped (${table.reason})` |
160 | | - : `Synced ${table.synced}/${table.sourceRows} rows using ${table.mergeFields.join(", ")}`} |
161 | | - </p> |
162 | | - ))} |
163 | | - </div> |
164 | | - ) : null} |
165 | | - |
166 | | - {rows.length > 0 ? ( |
167 | | - <div className="admin-airtable-rows"> |
168 | | - {rows.map((row, index) => ( |
169 | | - <pre key={row.id ?? index}>{JSON.stringify(row, null, 2)}</pre> |
170 | | - ))} |
171 | | - </div> |
172 | | - ) : null} |
173 | | - </section> |
174 | | - |
175 | 50 | <section className="admin-silly" aria-hidden="true"> |
176 | 51 | <pre>{`(\\_/)\n(o.o)\n(> <)`}</pre> |
177 | 52 | <p className="admin-emojis">🦄🌈🔮💎🎪🎭🎨🎬🎤🎧🎼🎹🥁🎷🎺🎸🪕🎻🎲🎯🎳🎮🎰🎱🔮💎🌈🦄</p> |
|
0 commit comments