Add all of Ben's mocks for the new root pages

This commit is contained in:
Eli Ribble 2026-02-27 16:51:41 +00:00
parent 5d8366015c
commit 9613cac11a
No known key found for this signature in database
21 changed files with 1593 additions and 141 deletions

View 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 }}

View file

@ -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>

View 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 }}

View 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 }}

View 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
View 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
}

View file

@ -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
View 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
View 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
View 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
View 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
}

View file

@ -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

View file

@ -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 ()

View file

@ -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