Add all of Ben's mocks for the new root pages
This commit is contained in:
parent
5d8366015c
commit
9613cac11a
21 changed files with 1593 additions and 141 deletions
302
html/template/sync/communication-root.html
Normal file
302
html/template/sync/communication-root.html
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
{{ template "sync/layout/authenticated.html" . }}
|
||||
|
||||
{{ define "title" }}Planning{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js"
|
||||
></script>
|
||||
<script>
|
||||
function onLoad() {}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<script>
|
||||
const DATA = [
|
||||
{
|
||||
id: "PR-1",
|
||||
type: "Adult",
|
||||
unread: true,
|
||||
narrative: "Large number of mosquitoes in backyard.",
|
||||
messages: [
|
||||
{ dir: "inbound", author: "Public", text: "Mosquitoes everywhere." },
|
||||
{
|
||||
dir: "outbound",
|
||||
author: "Admin",
|
||||
text: "We are reviewing your report.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "PR-2",
|
||||
type: "Standing Water",
|
||||
unread: false,
|
||||
narrative: "Green pool visible.",
|
||||
messages: [
|
||||
{
|
||||
dir: "inbound",
|
||||
author: "Public",
|
||||
text: "Pool has been untreated.",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function renderInbox() {
|
||||
const list = document.getElementById("inboxList");
|
||||
list.innerHTML = "";
|
||||
DATA.forEach((r) => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "list-group-item";
|
||||
if (r.unread) item.classList.add("unread");
|
||||
item.innerHTML = `${r.unread ? '<span class="badge-dot"></span>' : ""}${r.id} <div class="small-muted">${r.type}</div>`;
|
||||
item.onclick = () => selectThread(r.id);
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function selectThread(id) {
|
||||
const r = DATA.find((x) => x.id === id);
|
||||
if (!r) return;
|
||||
r.unread = false;
|
||||
document.getElementById("threadTitle").textContent =
|
||||
`${r.id} (${r.type})`;
|
||||
document.getElementById("reportNarrative").textContent = r.narrative;
|
||||
|
||||
const thread = document.getElementById("threadMessages");
|
||||
thread.innerHTML = "";
|
||||
|
||||
r.messages.forEach((m) => {
|
||||
const div = document.createElement("div");
|
||||
div.className = "timeline-msg " + (m.dir || "internal");
|
||||
div.innerHTML = `<strong>${m.author}</strong>: ${m.text}`;
|
||||
thread.appendChild(div);
|
||||
});
|
||||
|
||||
renderInbox();
|
||||
}
|
||||
|
||||
renderInbox();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 360px 1fr 420px;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
.panel {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
font-weight: 600;
|
||||
}
|
||||
.body {
|
||||
padding: 14px;
|
||||
overflow: auto;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.card-lite {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.unread {
|
||||
font-weight: 600;
|
||||
}
|
||||
.badge-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #0d6efd;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.small-muted {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.timeline-msg {
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.inbound {
|
||||
background: #eef5ff;
|
||||
}
|
||||
.outbound {
|
||||
background: #e9fbe9;
|
||||
}
|
||||
.internal {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.map-preview {
|
||||
height: 200px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(13, 110, 253, 0.06),
|
||||
rgba(13, 110, 253, 0.01)
|
||||
);
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="app">
|
||||
<!-- LEFT: INBOX / SIGNAL FEED -->
|
||||
<section class="panel">
|
||||
<div class="header">Signal Feed</div>
|
||||
<div class="body">
|
||||
<div class="btn-group btn-group-sm w-100 mb-2">
|
||||
<button class="btn btn-outline-secondary active">All Coms</button>
|
||||
<button class="btn btn-outline-secondary">Reports</button>
|
||||
<button class="btn btn-outline-secondary">Internal</button>
|
||||
<button class="btn btn-outline-secondary">System</button>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Show archived</label>
|
||||
</div>
|
||||
|
||||
<div class="list-group" id="inboxList"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENTER: SIGNAL DETAIL -->
|
||||
<section class="panel">
|
||||
<div class="header" id="threadTitle">Select a signal</div>
|
||||
<div class="body">
|
||||
<!-- Map Preview -->
|
||||
<div class="map-preview">Map Preview</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- LEFT HALF: CONVERSATION + REPLY -->
|
||||
<div class="col-md-6">
|
||||
<div class="section-title">Report Narrative</div>
|
||||
<div id="reportNarrative" class="mb-3"></div>
|
||||
|
||||
<div class="section-title">Conversation</div>
|
||||
<div id="threadMessages" class="mb-3"></div>
|
||||
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="4"
|
||||
placeholder="Reply to reporter..."
|
||||
></textarea>
|
||||
<button class="btn btn-outline-primary btn-sm mt-2 w-100">
|
||||
Send Reply
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT HALF: CONTEXT -->
|
||||
<div class="col-md-6">
|
||||
<div class="section-title">Context Summary</div>
|
||||
|
||||
<div class="card-lite">
|
||||
<strong>Signal Type</strong>
|
||||
<div class="small-muted">Adult / Standing Water</div>
|
||||
</div>
|
||||
|
||||
<div class="card-lite">
|
||||
<strong>Site Attachment</strong>
|
||||
<div class="small-muted">1234 Maple Ave</div>
|
||||
<button class="btn btn-sm btn-outline-secondary mt-2">
|
||||
Attach / Modify Site
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-lite">
|
||||
<strong>Nearby Reports (7 day window)</strong>
|
||||
<div class="small-muted">3 related adult reports</div>
|
||||
<button class="btn btn-sm btn-outline-secondary mt-2">
|
||||
View Related
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-lite">
|
||||
<strong>Existing Lead Suggestions</strong>
|
||||
<div class="small-muted">
|
||||
💡 Possible matching lead in same H3
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary mt-2">
|
||||
Review Leads
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-lite">
|
||||
<strong>Reporter Reliability</strong>
|
||||
<div class="small-muted">High , 12 prior reports</div>
|
||||
<button class="btn btn-sm btn-outline-secondary mt-2">
|
||||
View Reporter History
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- RIGHT: SIGNAL SHAPING TO LEAD -->
|
||||
<section class="panel">
|
||||
<div class="header">Signal , Lead Tools</div>
|
||||
<div class="body">
|
||||
<div class="section-title">Lead Formation</div>
|
||||
<button class="btn btn-primary w-100 mb-2">Create Lead</button>
|
||||
<button class="btn btn-outline-secondary w-100 mb-2">
|
||||
Add to Existing Lead 💡
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary w-100 mb-3">
|
||||
Merge with Similar Signals
|
||||
</button>
|
||||
|
||||
<div class="section-title">Communication Routing</div>
|
||||
<button class="btn btn-outline-secondary w-100 mb-2">
|
||||
Forward / Ping Planner
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary w-100 mb-2">
|
||||
Forward / Ping Technician
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary w-100 mb-3">
|
||||
Forward / Ping Supervisor
|
||||
</button>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Requires More Information</label>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="section-title">Disposition</div>
|
||||
<button class="btn btn-outline-warning btn-sm w-100 mb-2">
|
||||
Defer (with reason)
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm w-100">
|
||||
Archive (when resolved)
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{{ template "sync/layout/authenticated.html" . }}
|
||||
|
||||
{{ define "title" }}Settings{{ end }}
|
||||
{{ define "title" }}Planning{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
|
|
@ -110,21 +110,24 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications Card -->
|
||||
<!-- Import Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-notifications">
|
||||
<i class="bi bi-bell-fill"></i>
|
||||
<i class="bi bi-upload"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">Notifications</h2>
|
||||
<h2 class="h4 mb-2">Uploads</h2>
|
||||
<p class="text-muted mb-4">
|
||||
Configure email alerts, SMS notifications, and reporting
|
||||
preferences.
|
||||
Upload files (spreadsheets, scans, notes) to make the data
|
||||
available to Nidus
|
||||
</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a class="btn btn-outline-warning disabled" disabled>
|
||||
Manage Notifications
|
||||
<a
|
||||
class="btn btn-outline-warning"
|
||||
href="{{ .URL.Configuration.Upload }}"
|
||||
>
|
||||
Manage Uploads
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
295
html/template/sync/intelligence-root.html
Normal file
295
html/template/sync/intelligence-root.html
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
{{ template "sync/layout/authenticated.html" . }}
|
||||
|
||||
{{ define "title" }}Planning{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js"
|
||||
></script>
|
||||
<script src="/static/js/map-aggregate.js"></script>
|
||||
<script>
|
||||
function onLoad() {}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<style>
|
||||
.pane-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
.workbench-map {
|
||||
height: 320px;
|
||||
background-color: #e9ecef;
|
||||
border: 1px dashed #adb5bd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: #6c757d;
|
||||
}
|
||||
.scroll-pane {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.signal-item:hover {
|
||||
background-color: #f1f3f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tool-button {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.filter-label {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: #6c757d;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h3 class="mb-1">Daily Planning Workbench</h3>
|
||||
<div class="text-muted small">
|
||||
Signals and leads enter from the left, are investigated in the center,
|
||||
and transformed into structured field assignments using tools on the
|
||||
right.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- LEFT: Incoming Signals & Leads -->
|
||||
<div class="col-xl-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white pane-header">
|
||||
Incoming Signals & Leads
|
||||
</div>
|
||||
<div class="card-body scroll-pane">
|
||||
<!-- FILTERS -->
|
||||
<div class="mb-3">
|
||||
<div class="filter-label mb-1">Species</div>
|
||||
<select class="form-select form-select-sm mb-2">
|
||||
<option>All Species</option>
|
||||
<option>Aedes aegypti</option>
|
||||
<option>Aedes albopictus</option>
|
||||
<option>Culex pipiens</option>
|
||||
<option>Culex tarsalis</option>
|
||||
</select>
|
||||
|
||||
<div class="filter-label mb-1">Signal Type</div>
|
||||
<select class="form-select form-select-sm mb-2">
|
||||
<option>All Types</option>
|
||||
<option>Public Report</option>
|
||||
<option>Trap Spike</option>
|
||||
<option>Surveillance Observation</option>
|
||||
<option>Residual Expiring</option>
|
||||
<option>Plan Follow-Up</option>
|
||||
</select>
|
||||
|
||||
<div class="filter-label mb-1">Sort By</div>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>Newest First</option>
|
||||
<option>Highest Priority</option>
|
||||
<option>Most Signals Linked</option>
|
||||
<option>Strongest Species Signal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Signals -->
|
||||
<div class="mb-3">
|
||||
<div class="fw-semibold mb-2">Signals</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">
|
||||
Public Report – Service Request #1024
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Aedes aegypti • Standing Water Complaint
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">
|
||||
Trap Spike – H3 8828308281fffff
|
||||
</div>
|
||||
<div class="text-muted small">Culex pipiens • Adult Surge</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">
|
||||
Residual Expiring – Parcel 45-233-01
|
||||
</div>
|
||||
<div class="text-muted small">Reinspection Due</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Mosquito Control Plan Followups -->
|
||||
<div class="mb-3">
|
||||
<div class="fw-semibold mb-2">Mosquito Control Plan Follow-Ups</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">
|
||||
Plan Follow-Up – Greenway HOA Basin
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Residential Section • Verification Required
|
||||
</div>
|
||||
<span class="badge bg-secondary">Plan</span>
|
||||
</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">
|
||||
Plan Follow-Up – Ag Irrigation Canal 7B
|
||||
</div>
|
||||
<div class="text-muted small">Ag Section • Monitoring Window</div>
|
||||
<span class="badge bg-secondary">Plan</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Leads -->
|
||||
<div>
|
||||
<div class="fw-semibold mb-2">Existing Leads</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">Lead #L-204</div>
|
||||
<div class="text-muted small">3 Signals • Aedes aegypti</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded p-2 mb-2 signal-item">
|
||||
<div class="small fw-semibold">Lead #L-198</div>
|
||||
<div class="text-muted small">2 Signals • Culex pipiens</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER: Active Workbench -->
|
||||
<div class="col-xl-6">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header bg-white pane-header">
|
||||
Active Investigation Workbench
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="workbench-map mb-3">
|
||||
Map Placeholder<br />
|
||||
H3 Cells • Parcels • Signal Density • Lead Clusters
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold">Selected Signals</div>
|
||||
<div class="text-muted small">3 Signals Selected</div>
|
||||
<ul class="small mt-2">
|
||||
<li>Public Report – Aedes aegypti</li>
|
||||
<li>Trap Spike – Culex pipiens</li>
|
||||
<li>Plan Follow-Up – HOA Basin</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold">Derived Lead Strength</div>
|
||||
<div class="text-muted small">Signal Convergence Score</div>
|
||||
<div class="mt-2">
|
||||
<span class="badge bg-danger">High Confidence</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold">Related Communications</div>
|
||||
<div class="text-muted small">Inbound & outbound contact</div>
|
||||
<ul class="small mt-2">
|
||||
<li>Resident follow-up requested</li>
|
||||
<li>HOA notification sent</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold">Priority Context</div>
|
||||
<div class="text-muted small">Risk synthesis</div>
|
||||
<span class="badge bg-warning text-dark"
|
||||
>Elevated Aedes Risk</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Transformation Tools -->
|
||||
<div class="col-xl-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white pane-header">Transformation Tools</div>
|
||||
<div class="card-body scroll-pane">
|
||||
<div class="mb-3">
|
||||
<div class="text-muted small mb-2">Signal → Lead</div>
|
||||
<button class="btn btn-outline-primary tool-button">
|
||||
Create New Lead from Selection
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button">
|
||||
Add Signals to Existing Lead
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button">
|
||||
Mark Signal as Addressed
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted small mb-2">Lead → Field Assignment</div>
|
||||
<button class="btn btn-outline-success tool-button">
|
||||
Create Proposed Assignment
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button">
|
||||
Add Leads to Existing Assignment
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button">
|
||||
Split Lead
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted small mb-2">Assignment Controls</div>
|
||||
<button class="btn btn-outline-dark tool-button">
|
||||
Set Priority
|
||||
</button>
|
||||
<button class="btn btn-outline-dark tool-button">
|
||||
Estimate Effort
|
||||
</button>
|
||||
<button class="btn btn-outline-dark tool-button">
|
||||
Send to Operations
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
432
html/template/sync/operations-root.html
Normal file
432
html/template/sync/operations-root.html
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
{{ template "sync/layout/authenticated.html" . }}
|
||||
|
||||
{{ define "title" }}Planning{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js"
|
||||
></script>
|
||||
<script src="/static/js/map-aggregate.js"></script>
|
||||
<script>
|
||||
function onLoad() {}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<style>
|
||||
.card {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.overload {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
.map-placeholder {
|
||||
height: 520px;
|
||||
background: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
color: #6c757d;
|
||||
}
|
||||
.live-map {
|
||||
height: 620px;
|
||||
background: #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
color: #6c757d;
|
||||
}
|
||||
.scroll-panel {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.mode-toggle .nav-link {
|
||||
font-weight: 600;
|
||||
}
|
||||
.route-card {
|
||||
border-left: 4px solid #0d6efd;
|
||||
}
|
||||
.send-routes-btn {
|
||||
font-size: 1.1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
}
|
||||
.selection-counter.valid {
|
||||
color: #198754;
|
||||
}
|
||||
.selection-counter.invalid {
|
||||
color: #dc3545;
|
||||
}
|
||||
.selection-counter.warning {
|
||||
color: #fd7e14;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h4 class="fw-bold mb-0">Operations Command Center</h4>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<button class="btn btn-outline-primary me-2">
|
||||
Add Emergent Assignment
|
||||
</button>
|
||||
<button class="btn btn-outline-danger">Close Day</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODE TOGGLE -->
|
||||
<ul class="nav nav-tabs mode-toggle mb-4" role="tablist">
|
||||
<li class="nav-item">
|
||||
<button
|
||||
class="nav-link active"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#planning"
|
||||
>
|
||||
Planning Mode
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#live">
|
||||
Live Mode
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- ================= PLANNING MODE ================= -->
|
||||
<div class="tab-pane fade show active" id="planning">
|
||||
<div class="row mb-3">
|
||||
<!-- LEFT: ASSIGNMENTS -->
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div
|
||||
class="card-header d-flex justify-content-between align-items-center fw-semibold"
|
||||
>
|
||||
<span>Assignments & Work Requests</span>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label small">Select All</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body scroll-panel">
|
||||
<input
|
||||
class="form-control form-control-sm mb-2"
|
||||
placeholder="Filter by section, equipment, expertise"
|
||||
/>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="form-check me-2">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Larval Habitat Inspection</strong>
|
||||
<span class="badge bg-primary">Planned</span>
|
||||
</div>
|
||||
<small>Residential · Backpack Blower</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="form-check me-2">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Green Pool Treatment</strong>
|
||||
<span class="badge bg-warning text-dark">Emergent</span>
|
||||
</div>
|
||||
<small>Residential · Larvicide · Access Clearance</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER: MAP -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header fw-semibold">Routing Map</div>
|
||||
<div class="map-placeholder">
|
||||
Map: Selected Assignments, Selected Technicians, Proposed Routes
|
||||
</div>
|
||||
<div
|
||||
class="card-footer d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<div class="selection-counter warning small">
|
||||
12 Assignments Selected · 5 Technicians Selected
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-success">Compute Optimal Routes</button>
|
||||
<button class="btn btn-outline-primary">
|
||||
Manual Assignment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: TECHNICIANS -->
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div
|
||||
class="card-header d-flex justify-content-between align-items-center fw-semibold"
|
||||
>
|
||||
<span>Technicians</span>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label small">Select All</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body scroll-panel">
|
||||
<input
|
||||
class="form-control form-control-sm mb-2"
|
||||
placeholder="Filter technicians"
|
||||
/>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="form-check me-2">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Technician A</strong>
|
||||
<span
|
||||
><span class="status-dot bg-success"></span
|
||||
>Available</span
|
||||
>
|
||||
</div>
|
||||
<small>Residential · ULV · Backpack</small>
|
||||
<div class="mt-2">
|
||||
<label class="form-label form-label-sm mb-1"
|
||||
>Assigned Vehicle</label
|
||||
>
|
||||
<select class="form-select form-select-sm">
|
||||
<option selected>
|
||||
Truck 12 · ULV · Backpack · Larvicide Kit
|
||||
</option>
|
||||
<option>ATV 3 · Dipper · Granular Spreader</option>
|
||||
<option>Reserve Vehicle · Minimal Equipment</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item overload d-flex">
|
||||
<div class="form-check me-2">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Technician B</strong>
|
||||
<span
|
||||
><span class="status-dot bg-warning"></span>In
|
||||
Field</span
|
||||
>
|
||||
</div>
|
||||
<small>Agricultural · Capacity Exceeded</small>
|
||||
<div class="mt-2">
|
||||
<label class="form-label form-label-sm mb-1"
|
||||
>Assigned Vehicle</label
|
||||
>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>
|
||||
Truck 12 · ULV · Backpack · Larvicide Kit
|
||||
</option>
|
||||
<option selected>
|
||||
ATV 3 · Dipper · Granular Spreader
|
||||
</option>
|
||||
<option>Reserve Vehicle · Minimal Equipment</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROUTES LIST (STACKED) -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header fw-semibold">Proposed Routes</div>
|
||||
<div class="card-body">
|
||||
<div class="card route-card p-3 mb-3">
|
||||
<strong>Route: Technician A</strong><br />
|
||||
<small
|
||||
>5 Assignments · Est. 4.5 hrs · Equipment: Backpack</small
|
||||
>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
View Assignments
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
Modify Route
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
Shift Assignment
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
Swap Technician
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card route-card p-3">
|
||||
<strong>Route: Technician B</strong><br />
|
||||
<small>6 Assignments · Est. 6 hrs · Equipment: ATV</small>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
View Assignments
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
Modify Route
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
Shift Assignment
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
Swap Technician
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<button class="btn btn-primary send-routes-btn">
|
||||
Send Routes to Technicians and Begin Live Operations
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= LIVE MODE ================= -->
|
||||
<div class="tab-pane fade" id="live">
|
||||
<div class="row mb-3">
|
||||
<!-- LEFT: ASSIGNMENTS IN ROUTE ORDER -->
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-header fw-semibold">
|
||||
Assignments in Route Order
|
||||
</div>
|
||||
<div class="card-body scroll-panel">
|
||||
<div class="alert alert-warning">
|
||||
<strong>Unassigned Assignments</strong><br />
|
||||
2 awaiting routing
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<strong>Green Pool Reinspection</strong><br />
|
||||
Communication Pending
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Storm Drain Treatment</strong><br />
|
||||
In Progress
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER: LIVE MAP -->
|
||||
<div class="col-lg-6">
|
||||
<div class="live-map">
|
||||
Live Map: Active Routes, Technician Position, Route Progress
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: TECHNICIAN STATUS -->
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-header fw-semibold">Technician Status</div>
|
||||
<div class="card-body scroll-panel">
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Technician A</strong>
|
||||
<span class="badge bg-success">On Track</span>
|
||||
</div>
|
||||
<small>72% Complete · 1.5 hrs Remaining</small>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Technician C</strong>
|
||||
<span class="badge bg-danger">Support Requested</span>
|
||||
</div>
|
||||
<small>Equipment Issue</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROUTE INTELLIGENCE -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header fw-semibold">
|
||||
Active Routes Intelligence
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Technician</th>
|
||||
<th>Assignments</th>
|
||||
<th>Estimated Completion</th>
|
||||
<th>Remaining Time</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Technician A</td>
|
||||
<td>5</td>
|
||||
<td>3:45 PM</td>
|
||||
<td>1 hr 30 min</td>
|
||||
<td><span class="badge bg-success">On Track</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
View Route
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
Reallocate
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Technician C</td>
|
||||
<td>4</td>
|
||||
<td>4:30 PM</td>
|
||||
<td>2 hrs</td>
|
||||
<td><span class="badge bg-danger">Blocked</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
View Route
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
Assist
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
350
html/template/sync/review-root.html
Normal file
350
html/template/sync/review-root.html
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
{{ template "sync/layout/authenticated.html" . }}
|
||||
|
||||
{{ define "title" }}Planning{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js"
|
||||
></script>
|
||||
<script src="/static/js/map-aggregate.js"></script>
|
||||
<script>
|
||||
function onLoad() {}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<style>
|
||||
.metric-card h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
.status-badge {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.table-hover tbody tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.anomaly {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
.flagged {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
.reviewed {
|
||||
background-color: #e2f0e9;
|
||||
}
|
||||
.modal-lg {
|
||||
max-width: 90%;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
|
||||
<div class="row">
|
||||
<!-- LEFT PANE -->
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Daily Overview</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<div>Total Records</div>
|
||||
<strong>142</strong>
|
||||
</div>
|
||||
<span class="text-success"
|
||||
><i class="bi bi-arrow-up-right"></i> +8%</span
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3 d-flex justify-content-between">
|
||||
<div>
|
||||
<div>Acres Treated</div>
|
||||
<strong>318.4</strong>
|
||||
</div>
|
||||
<span class="text-danger"
|
||||
><i class="bi bi-arrow-down-right"></i> -5%</span
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<div>Anomalies</div>
|
||||
<strong>9</strong>
|
||||
</div>
|
||||
<span class="text-warning">Above Avg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">System Signals</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex justify-content-between">
|
||||
High acreage deviation
|
||||
<span class="badge bg-warning text-dark">System</span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between">
|
||||
Product-to-acre variance
|
||||
<span class="badge bg-warning text-dark">System</span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between">
|
||||
Rare task combination
|
||||
<span class="badge bg-info text-dark">Review</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Process Performance</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<strong>Recommendation Acceptance:</strong> 78%
|
||||
</div>
|
||||
<div class="mb-2"><strong>Time Saved vs Plan:</strong> +6%</div>
|
||||
<div><strong>Override Logs:</strong> 5</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER PANE -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Geospatial Review</div>
|
||||
<div class="card-body p-0">
|
||||
<div
|
||||
class="bg-light d-flex align-items-center justify-content-center"
|
||||
style="height:350px;"
|
||||
>
|
||||
<div class="text-center text-muted">
|
||||
<i class="bi bi-geo-alt-fill fs-1"></i>
|
||||
<div>Interactive Map Placeholder</div>
|
||||
<small
|
||||
>Spatial context for records, anomalies, and follow-ups.</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Technician Records</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th><input type="checkbox" /></th>
|
||||
<th>ID</th>
|
||||
<th>Technician</th>
|
||||
<th>Section</th>
|
||||
<th>Task</th>
|
||||
<th>Acres</th>
|
||||
<th>Product</th>
|
||||
<th>Status</th>
|
||||
<th><i class="bi bi-exclamation-triangle"></i></th>
|
||||
<th>Comments</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#recordDetailModal"
|
||||
class="anomaly"
|
||||
>
|
||||
<td><input type="checkbox" /></td>
|
||||
<td>REC-0142</td>
|
||||
<td>J. Alvarez</td>
|
||||
<td>Agricultural</td>
|
||||
<td>Treatment</td>
|
||||
<td>24.5</td>
|
||||
<td>Bti</td>
|
||||
<td><span class="badge bg-warning">Flagged</span></td>
|
||||
<td>
|
||||
<i class="bi bi-exclamation-triangle-fill text-danger"></i>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">3</span></td>
|
||||
<td>
|
||||
<span class="badge bg-primary">Training</span>
|
||||
<span class="badge bg-secondary">Data Quality</span>
|
||||
<span class="badge bg-warning text-dark"
|
||||
>System: Acreage Outlier</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT PANE -->
|
||||
<div class="col-lg-3 mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Filters</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Date</label>
|
||||
<input type="date" class="form-control" value="2026-02-26" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Section</label>
|
||||
<select class="form-select">
|
||||
<option>All Sections</option>
|
||||
<option>Residential</option>
|
||||
<option>Agricultural</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Technician</label>
|
||||
<select class="form-select">
|
||||
<option>All Technicians</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Show only flagged</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" />
|
||||
<label class="form-check-label">Open questions</label>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label class="form-label">Filter by Tag</label>
|
||||
<select class="form-select">
|
||||
<option>All Tags</option>
|
||||
<option>Training</option>
|
||||
<option>Data Quality</option>
|
||||
<option>System: Acreage Outlier</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Bulk Actions</div>
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button class="btn btn-warning btn-sm">Flag for Follow-up</button>
|
||||
<button class="btn btn-success btn-sm">Mark as Reviewed</button>
|
||||
<button class="btn btn-outline-secondary btn-sm">Add Comment</button>
|
||||
<button class="btn btn-outline-dark btn-sm">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Record Detail Modal -->
|
||||
<div class="modal fade" id="recordDetailModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Record Detail: REC-2026-0142</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Structured Details</h6>
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">Origin Assignment: AG-Canal-12B</li>
|
||||
<li class="list-group-item">
|
||||
Origin Signals: High Larval Counts
|
||||
</li>
|
||||
<li class="list-group-item">Equipment Used: Backpack Blower</li>
|
||||
<li class="list-group-item">Duration: 3.2 hrs</li>
|
||||
<li class="list-group-item">GPS Location: Map Placeholder</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Products & Media</h6>
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">Product Applied: Bti Granular</li>
|
||||
<li class="list-group-item text-muted">Photo Placeholder</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="mt-3">Reviewer & Technician Questions</h6>
|
||||
<div
|
||||
class="border rounded p-3 mb-4"
|
||||
style="max-height:180px; overflow-y:auto;"
|
||||
>
|
||||
<p class="mb-2">
|
||||
<strong>Reviewer:</strong> Confirm acreage measurement method.
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<strong>Technician:</strong> Measured using GPS track log.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h6>Tags</h6>
|
||||
<div class="border rounded p-3 mb-3">
|
||||
<span class="badge bg-primary me-1">Training</span>
|
||||
<span class="badge bg-secondary me-1">Data Quality</span>
|
||||
<span class="badge bg-warning text-dark me-1"
|
||||
>System: Acreage Outlier</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Add Tag</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Enter new or existing tag"
|
||||
/>
|
||||
<button class="btn btn-outline-secondary">Add</button>
|
||||
</div>
|
||||
<small class="text-muted"
|
||||
>Tags may be user-defined or system-generated. System tags assist
|
||||
review but can be overridden.</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<h6>Review Comments</h6>
|
||||
<div
|
||||
class="border rounded p-3 mb-3"
|
||||
style="max-height:200px; overflow-y:auto;"
|
||||
>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Operations Review</strong>
|
||||
<small class="text-muted">2026-02-26 14:12</small>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Checked pesticide rate against label requirements. Within
|
||||
tolerance.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<strong>Data Quality</strong>
|
||||
<small class="text-muted">2026-02-26 15:02</small>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Acreage appears high relative to canal width. Monitor similar
|
||||
entries this week.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Add Review Comment</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="3"
|
||||
placeholder="Enter internal review comment regarding data quality, process observations, or training notes."
|
||||
></textarea>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary btn-sm">Save Comment</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-outline-secondary">Post Question</button>
|
||||
<button class="btn btn-warning">Flag for Follow-up</button>
|
||||
<button class="btn btn-success">Mark as Reviewed</button>
|
||||
<button class="btn btn-primary">Edit Record</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
14
sync/communication.go
Normal file
14
sync/communication.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentCommunicationRoot struct{}
|
||||
|
||||
func getCommunicationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentCommunicationRoot], *errorWithStatus) {
|
||||
return newResponse("sync/communication-root.html", contentCommunicationRoot{}), nil
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
)
|
||||
|
||||
type contentConfig struct {
|
||||
IsProductionEnvironment bool
|
||||
}
|
||||
|
||||
func newContentConfig() contentConfig {
|
||||
return contentConfig{
|
||||
IsProductionEnvironment: config.IsProductionEnvironment(),
|
||||
}
|
||||
}
|
||||
117
sync/configuration.go
Normal file
117
sync/configuration.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/arcgis"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentConfig struct {
|
||||
IsProductionEnvironment bool
|
||||
}
|
||||
|
||||
func newContentConfig() contentConfig {
|
||||
return contentConfig{
|
||||
IsProductionEnvironment: config.IsProductionEnvironment(),
|
||||
}
|
||||
}
|
||||
|
||||
type contentConfigurationRoot struct{}
|
||||
|
||||
func getConfigurationRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentConfigurationRoot], *errorWithStatus) {
|
||||
return newResponse("sync/configuration/root.html", contentConfigurationRoot{}), nil
|
||||
}
|
||||
|
||||
type contentSettingOrganization struct {
|
||||
Organization *models.Organization
|
||||
}
|
||||
|
||||
type contentSettingIntegration struct {
|
||||
ArcGISOAuth *models.OauthToken
|
||||
}
|
||||
|
||||
func getConfigurationOrganization(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingOrganization], *errorWithStatus) {
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, newError("get organization: %w", err)
|
||||
}
|
||||
/*
|
||||
var district contentDistrict
|
||||
district, err = bob.One[contentDistrict](ctx, db.PGInstance.BobDB, psql.Select(
|
||||
sm.From("import.district"),
|
||||
sm.Columns(
|
||||
"address",
|
||||
"agency",
|
||||
"area_4326_sqm",
|
||||
"city1",
|
||||
"city2",
|
||||
"contact",
|
||||
"fax1",
|
||||
"general_mg",
|
||||
"gid",
|
||||
"phone1",
|
||||
"phone2",
|
||||
"postal_c_1",
|
||||
"website",
|
||||
psql.F("ST_AsGeoJSON", "centroid_4326"),
|
||||
psql.F("ST_XMin", "extent_4326"),
|
||||
psql.F("ST_YMin", "extent_4326"),
|
||||
psql.F("ST_XMax", "extent_4326"),
|
||||
psql.F("ST_YMax", "extent_4326"),
|
||||
),
|
||||
sm.Where(psql.Quote("gid").EQ(psql.Arg(gid))),
|
||||
), scan.StructMapper[contentDistrict]())
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get extents", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
*/
|
||||
data := contentSettingOrganization{
|
||||
Organization: org,
|
||||
}
|
||||
return newResponse("sync/configuration/organization.html", data), nil
|
||||
}
|
||||
func getConfigurationIntegration(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) {
|
||||
oauth, err := arcgis.GetOAuthForUser(ctx, u)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get oauth: %w", err)
|
||||
}
|
||||
data := contentSettingIntegration{
|
||||
ArcGISOAuth: oauth,
|
||||
}
|
||||
return newResponse("sync/configuration/integration.html", data), nil
|
||||
}
|
||||
func getConfigurationIntegrationArcgis(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) {
|
||||
oauth, err := arcgis.GetOAuthForUser(ctx, u)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get oauth: %w", err)
|
||||
}
|
||||
data := contentSettingIntegration{
|
||||
ArcGISOAuth: oauth,
|
||||
}
|
||||
return newResponse("sync/configuration/integration-arcgis.html", data), nil
|
||||
}
|
||||
|
||||
type contentSettingPlaceholder struct{}
|
||||
|
||||
func getConfigurationPesticide(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/configuration/pesticide.html", content), nil
|
||||
}
|
||||
func getConfigurationPesticideAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/configuration/pesticide-add.html", content), nil
|
||||
}
|
||||
func getConfigurationUserAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/configuration/user-add.html", content), nil
|
||||
}
|
||||
func getConfigurationUserList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/configuration/user-list.html", content), nil
|
||||
}
|
||||
14
sync/intelligence.go
Normal file
14
sync/intelligence.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentIntelligenceRoot struct{}
|
||||
|
||||
func getIntelligenceRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentIntelligenceRoot], *errorWithStatus) {
|
||||
return newResponse("sync/intelligence-root.html", contentIntelligenceRoot{}), nil
|
||||
}
|
||||
14
sync/operations.go
Normal file
14
sync/operations.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentOperationsRoot struct{}
|
||||
|
||||
func getOperationsRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentOperationsRoot], *errorWithStatus) {
|
||||
return newResponse("sync/operations-root.html", contentOperationsRoot{}), nil
|
||||
}
|
||||
14
sync/review.go
Normal file
14
sync/review.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
)
|
||||
|
||||
type contentReviewRoot struct{}
|
||||
|
||||
func getReviewRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentReviewRoot], *errorWithStatus) {
|
||||
return newResponse("sync/review-root.html", contentReviewRoot{}), nil
|
||||
}
|
||||
|
|
@ -48,25 +48,34 @@ func Router() chi.Router {
|
|||
r.Route("/api", api.AddRoutes)
|
||||
r.Method("GET", "/admin", authenticatedHandler(getAdminDash))
|
||||
r.Method("GET", "/cell/{cell}", authenticatedHandler(getCellDetails))
|
||||
r.Method("GET", "/communication", authenticatedHandler(getCommunicationRoot))
|
||||
r.Method("GET", "/configuration", authenticatedHandler(getConfigurationRoot))
|
||||
r.Method("GET", "/configuration/upload", authenticatedHandler(getUploadList))
|
||||
r.Method("GET", "/configuration/upload/pool", authenticatedHandler(getUploadPoolCreate))
|
||||
r.Method("POST", "/configuration/upload/pool", authenticatedHandlerPostMultipart(postUploadPoolCreate))
|
||||
r.Method("GET", "/configuration/upload/{id}", authenticatedHandler(getUploadByID))
|
||||
r.Method("POST", "/configuration/upload/{id}/discard", authenticatedHandlerPost(postUploadDiscard))
|
||||
r.Method("GET", "/download", authenticatedHandler(getDownloadList))
|
||||
r.Method("GET", "/intelligence", authenticatedHandler(getIntelligenceRoot))
|
||||
r.Method("GET", "/layout-test", authenticatedHandler(getLayoutTest))
|
||||
r.Method("GET", "/message", authenticatedHandler(getMessageList))
|
||||
r.Method("GET", "/notification", authenticatedHandler(getNotificationList))
|
||||
r.Method("GET", "/operations", authenticatedHandler(getOperationsRoot))
|
||||
r.Method("GET", "/planning", authenticatedHandler(getPlanningRoot))
|
||||
r.Method("GET", "/pool", authenticatedHandler(getPoolList))
|
||||
r.Method("GET", "/pool/create", authenticatedHandler(getPoolCreate))
|
||||
r.Method("GET", "/pool/{id}", authenticatedHandler(getPoolByID))
|
||||
r.Method("GET", "/radar", authenticatedHandler(getRadar))
|
||||
r.Method("GET", "/review", authenticatedHandler(getReviewRoot))
|
||||
r.Method("GET", "/service-request", authenticatedHandler(getServiceRequestList))
|
||||
r.Method("GET", "/service-request/{id}", authenticatedHandler(getServiceRequestDetail))
|
||||
r.Method("GET", "/setting", authenticatedHandler(getSetting))
|
||||
r.Method("GET", "/setting/integration", authenticatedHandler(getSettingIntegration))
|
||||
r.Method("GET", "/setting/integration/arcgis", authenticatedHandler(getSettingIntegrationArcgis))
|
||||
r.Method("GET", "/setting/organization", authenticatedHandler(getSettingOrganization))
|
||||
r.Method("GET", "/setting/pesticide", authenticatedHandler(getSettingPesticide))
|
||||
r.Method("GET", "/setting/pesticide/add", authenticatedHandler(getSettingPesticideAdd))
|
||||
r.Method("GET", "/setting/user", authenticatedHandler(getSettingUserList))
|
||||
r.Method("GET", "/setting/user/add", authenticatedHandler(getSettingUserAdd))
|
||||
r.Method("GET", "/configuration/integration", authenticatedHandler(getConfigurationIntegration))
|
||||
r.Method("GET", "/configuration/integration/arcgis", authenticatedHandler(getConfigurationIntegrationArcgis))
|
||||
r.Method("GET", "/configuration/organization", authenticatedHandler(getConfigurationOrganization))
|
||||
r.Method("GET", "/configuration/pesticide", authenticatedHandler(getConfigurationPesticide))
|
||||
r.Method("GET", "/configuration/pesticide/add", authenticatedHandler(getConfigurationPesticideAdd))
|
||||
r.Method("GET", "/configuration/user", authenticatedHandler(getConfigurationUserList))
|
||||
r.Method("GET", "/configuration/user/add", authenticatedHandler(getConfigurationUserAdd))
|
||||
r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout))
|
||||
r.Method("GET", "/source/{globalid}", authenticatedHandler(getSource))
|
||||
r.Method("GET", "/stadia", authenticatedHandler(getStadia))
|
||||
|
|
@ -75,11 +84,6 @@ func Router() chi.Router {
|
|||
r.Method("POST", "/sudo/sms", authenticatedHandlerPost(postSudoSMS))
|
||||
r.Method("GET", "/trap/{globalid}", authenticatedHandler(getTrap))
|
||||
r.Method("GET", "/text/{destination}", authenticatedHandler(getTextMessages))
|
||||
r.Method("GET", "/upload", authenticatedHandler(getUploadList))
|
||||
r.Method("GET", "/upload/pool", authenticatedHandler(getUploadPoolCreate))
|
||||
r.Method("POST", "/upload/pool", authenticatedHandlerPostMultipart(postUploadPoolCreate))
|
||||
r.Method("GET", "/upload/{id}", authenticatedHandler(getUploadByID))
|
||||
r.Method("POST", "/upload/{id}/discard", authenticatedHandlerPost(postUploadDiscard))
|
||||
|
||||
html.AddStaticRoute(r, "/static")
|
||||
return r
|
||||
|
|
|
|||
106
sync/setting.go
106
sync/setting.go
|
|
@ -1,107 +1,3 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/arcgis"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentSettingOrganization struct {
|
||||
Organization *models.Organization
|
||||
}
|
||||
|
||||
type contentSettingIntegration struct {
|
||||
ArcGISOAuth *models.OauthToken
|
||||
}
|
||||
|
||||
type contentAuthenticatedPlaceholder struct {
|
||||
}
|
||||
|
||||
func getSetting(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentAuthenticatedPlaceholder], *errorWithStatus) {
|
||||
data := contentAuthenticatedPlaceholder{}
|
||||
return newResponse("sync/settings.html", data), nil
|
||||
}
|
||||
func getSettingOrganization(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingOrganization], *errorWithStatus) {
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, newError("get organization: %w", err)
|
||||
}
|
||||
/*
|
||||
var district contentDistrict
|
||||
district, err = bob.One[contentDistrict](ctx, db.PGInstance.BobDB, psql.Select(
|
||||
sm.From("import.district"),
|
||||
sm.Columns(
|
||||
"address",
|
||||
"agency",
|
||||
"area_4326_sqm",
|
||||
"city1",
|
||||
"city2",
|
||||
"contact",
|
||||
"fax1",
|
||||
"general_mg",
|
||||
"gid",
|
||||
"phone1",
|
||||
"phone2",
|
||||
"postal_c_1",
|
||||
"website",
|
||||
psql.F("ST_AsGeoJSON", "centroid_4326"),
|
||||
psql.F("ST_XMin", "extent_4326"),
|
||||
psql.F("ST_YMin", "extent_4326"),
|
||||
psql.F("ST_XMax", "extent_4326"),
|
||||
psql.F("ST_YMax", "extent_4326"),
|
||||
),
|
||||
sm.Where(psql.Quote("gid").EQ(psql.Arg(gid))),
|
||||
), scan.StructMapper[contentDistrict]())
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get extents", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
*/
|
||||
data := contentSettingOrganization{
|
||||
Organization: org,
|
||||
}
|
||||
return newResponse("sync/setting-organization.html", data), nil
|
||||
}
|
||||
func getSettingIntegration(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) {
|
||||
oauth, err := arcgis.GetOAuthForUser(ctx, u)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get oauth: %w", err)
|
||||
}
|
||||
data := contentSettingIntegration{
|
||||
ArcGISOAuth: oauth,
|
||||
}
|
||||
return newResponse("sync/setting-integration.html", data), nil
|
||||
}
|
||||
func getSettingIntegrationArcgis(ctx context.Context, r *http.Request, org *models.Organization, u *models.User) (*response[contentSettingIntegration], *errorWithStatus) {
|
||||
oauth, err := arcgis.GetOAuthForUser(ctx, u)
|
||||
if err != nil {
|
||||
return nil, newError("Failed to get oauth: %w", err)
|
||||
}
|
||||
data := contentSettingIntegration{
|
||||
ArcGISOAuth: oauth,
|
||||
}
|
||||
return newResponse("sync/setting-integration-arcgis.html", data), nil
|
||||
}
|
||||
|
||||
type contentSettingPlaceholder struct{}
|
||||
|
||||
func getSettingPesticide(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/setting-pesticide.html", content), nil
|
||||
}
|
||||
func getSettingPesticideAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/setting-pesticide-add.html", content), nil
|
||||
}
|
||||
func getSettingUserAdd(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/setting-user-add.html", content), nil
|
||||
}
|
||||
func getSettingUserList(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentSettingPlaceholder], *errorWithStatus) {
|
||||
content := contentSettingPlaceholder{}
|
||||
return newResponse("sync/setting-user-list.html", content), nil
|
||||
}
|
||||
import ()
|
||||
|
|
|
|||
12
sync/url.go
12
sync/url.go
|
|
@ -5,6 +5,7 @@ import (
|
|||
)
|
||||
|
||||
type contentURL struct {
|
||||
Configuration contentURLConfiguration
|
||||
OAuthRefreshArcGIS string
|
||||
Root string
|
||||
Route string
|
||||
|
|
@ -17,6 +18,7 @@ type contentURL struct {
|
|||
|
||||
func newContentURL() contentURL {
|
||||
return contentURL{
|
||||
Configuration: newContentURLConfiguration(),
|
||||
OAuthRefreshArcGIS: config.MakeURLNidus("/arcgis/oauth/begin"),
|
||||
Root: config.MakeURLNidus("/"),
|
||||
Route: config.MakeURLNidus("/route"),
|
||||
|
|
@ -28,6 +30,16 @@ func newContentURL() contentURL {
|
|||
}
|
||||
}
|
||||
|
||||
type contentURLConfiguration struct {
|
||||
Upload string
|
||||
}
|
||||
|
||||
func newContentURLConfiguration() contentURLConfiguration {
|
||||
return contentURLConfiguration{
|
||||
Upload: config.MakeURLNidus("/configuration/upload"),
|
||||
}
|
||||
}
|
||||
|
||||
type contentURLSidebar struct {
|
||||
Communication string
|
||||
Configuration string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue