Spaces:
Running
Running
v2.1: SWIA rebrand, bell-ringer + accounting email removal, House Standard split, domain self-signup, superadmin self-service
8933003 | {% extends "base.html" %} | |
| {% block title %}Data Refresh — Admin{% endblock %} | |
| {% block content %} | |
| <div class="space-y-4"> | |
| <a href="/admin/?token={{ token }}" class="text-sm text-gray-400 hover:text-gray-600">← Admin Panel</a> | |
| <h1 class="text-2xl font-bold sw-blue-text">Data Refresh</h1> | |
| <p class="text-gray-500 text-sm"> | |
| Upload <code>refresh_data.json</code> exported from the BigQuery → producers/clients pipeline. | |
| Existing entries are upserted by <code>code</code> / <code>lookup_code</code>; rows missing from | |
| the upload are marked inactive (data preserved). | |
| </p> | |
| <div class="bg-white rounded-lg shadow p-5 grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <h3 class="font-semibold sw-blue-text mb-2">Current state</h3> | |
| <ul class="text-sm text-gray-600 space-y-1"> | |
| <li>Active producers: <span class="font-bold sw-blue-text">{{ active_producers }}</span></li> | |
| <li>Active clients: <span class="font-bold sw-blue-text">{{ active_clients }}</span></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold sw-blue-text mb-2">Expected JSON shape</h3> | |
| <pre class="text-xs bg-gray-50 border border-gray-200 rounded p-3 overflow-x-auto"><code>{ | |
| "producers": [ | |
| {"code": "DOEJA1", "prefix": "DOEJA", "suffix": 1, "name": "Jane Doe"} | |
| ], | |
| "clients": [ | |
| {"lookup_code": "ACMECO-01", "name": "Acme Corp"} | |
| ] | |
| }</code></pre> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-lg shadow p-5"> | |
| <h3 class="font-semibold sw-blue-text mb-2">Upload</h3> | |
| <input type="file" id="file_input" accept="application/json,.json" | |
| class="w-full text-sm text-gray-600 file:mr-3 file:py-2 file:px-4 file:rounded file:border-0 file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"> | |
| <button onclick="uploadRefresh()" | |
| class="mt-3 bg-green-600 text-white px-6 py-2 rounded-lg text-sm font-semibold hover:bg-green-700 transition"> | |
| Run Refresh | |
| </button> | |
| <span id="status" class="ml-3 text-sm"></span> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| const authToken = '{{ token }}'; | |
| async function uploadRefresh() { | |
| const fileInput = document.getElementById('file_input'); | |
| const status = document.getElementById('status'); | |
| if (!fileInput.files || !fileInput.files[0]) { | |
| status.textContent = 'Choose a JSON file first.'; | |
| status.className = 'ml-3 text-sm text-red-600'; | |
| return; | |
| } | |
| status.textContent = 'Uploading…'; | |
| status.className = 'ml-3 text-sm text-gray-500'; | |
| const fd = new FormData(); | |
| fd.append('file', fileInput.files[0]); | |
| try { | |
| const resp = await fetch('/admin/api/data-refresh', { | |
| method: 'POST', | |
| headers: {'Authorization': 'Bearer ' + authToken}, | |
| body: fd, | |
| }); | |
| const data = await resp.json(); | |
| if (data.success) { | |
| status.textContent = '✅ ' + data.producers_imported + ' producers, ' + | |
| data.clients_imported + ' clients imported. Reloading…'; | |
| status.className = 'ml-3 text-sm text-green-600'; | |
| setTimeout(() => location.reload(), 1500); | |
| } else { | |
| status.textContent = '❌ ' + (data.error || 'Failed'); | |
| status.className = 'ml-3 text-sm text-red-600'; | |
| } | |
| } catch (e) { | |
| status.textContent = '❌ ' + e.message; | |
| status.className = 'ml-3 text-sm text-red-600'; | |
| } | |
| } | |
| </script> | |
| {% endblock %} | |