Add page for showing pool uploads

This commit is contained in:
Eli Ribble 2026-02-07 18:26:47 +00:00
parent 874d49c6a5
commit 59076e9eb0
No known key found for this signature in database
7 changed files with 232 additions and 193 deletions

View file

@ -17,9 +17,9 @@
</a>
</li>
<li>
<a href="#">
<div class="menu-icon"><i class="bi bi-graph-up"></i></div>
<span class="menu-text ms-2">Analytics</span>
<a href="/pool">
<div class="menu-icon"><i class="bi bi-water"></i></div>
<span class="menu-text ms-2">Pool</span>
</a>
</li>
<li>

View file

@ -19,174 +19,172 @@
</script>
{{ end }}
{{ define "content" }}
<div id="content">
<!-- Dashboard Header -->
<div class="row mb-4">
<div class="col-md-6">
<h1>{{ .User.Organization.Name }} Dashboard</h1>
<p class="text-muted">
Overview of mosquito control activities in your district
<!-- Dashboard Header -->
<div class="row mb-4">
<div class="col-md-6">
<h1>{{ .User.Organization.Name }} Dashboard</h1>
<p class="text-muted">
Overview of mosquito control activities in your district
</p>
</div>
<div
class="col-md-6 text-md-end d-flex align-items-center justify-content-md-end"
>
{{ if .IsSyncOngoing }}
<p class="last-refreshed mb-0">
<i class="fas fa-sync-alt me-2 syncing"></i>Syncing now...
</p>
</div>
<div
class="col-md-6 text-md-end d-flex align-items-center justify-content-md-end"
>
{{ if .IsSyncOngoing }}
<p class="last-refreshed mb-0">
<i class="fas fa-sync-alt me-2 syncing"></i>Syncing now...
</p>
{{ else }}
<p class="last-refreshed mb-0">
<i class="fas fa-sync-alt me-2"></i>Last updated:
<span id="last-refreshed-time">{{ .LastSync | timeSincePtr }}</span>
<button class="btn btn-sm btn-outline-primary ms-3">
Refresh Data
</button>
</p>
{{ end }}
{{ else }}
<p class="last-refreshed mb-0">
<i class="fas fa-sync-alt me-2"></i>Last updated:
<span id="last-refreshed-time">{{ .LastSync | timeSincePtr }}</span>
<button class="btn btn-sm btn-outline-primary ms-3">
Refresh Data
</button>
</p>
{{ end }}
</div>
</div>
<!-- Key Metrics -->
<div class="row g-4">
<!-- Last Refreshed -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-success bg-opacity-10 text-white">
<i class="fas fa-clock"></i>
</div>
<h5 class="card-title">Last Data Refresh</h5>
<p class="metric-value">{{ .LastSync | timeSincePtr }}</p>
<!-- <p class="card-text text-muted">Last sync: 12:45 PM</p> -->
</div>
</div>
</div>
<!-- Key Metrics -->
<div class="row g-4">
<!-- Last Refreshed -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-success bg-opacity-10 text-white">
<i class="fas fa-clock"></i>
</div>
<h5 class="card-title">Last Data Refresh</h5>
<p class="metric-value">{{ .LastSync | timeSincePtr }}</p>
<!-- <p class="card-text text-muted">Last sync: 12:45 PM</p> -->
<!-- Service Requests -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-warning bg-opacity-10 text-white">
<i class="fas fa-ticket-alt"></i>
</div>
</div>
</div>
<!-- Service Requests -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-warning bg-opacity-10 text-white">
<i class="fas fa-ticket-alt"></i>
</div>
<h5 class="card-title">Service Requests</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">
{{ .CountServiceRequests | bigNumber }}...?
</p>
{{ else }}
<p class="metric-value">
{{ .CountServiceRequests | bigNumber }}
</p>
{{ end }}
<!--<p class="card-text text-muted">
<h5 class="card-title">Service Requests</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">
{{ .CountServiceRequests | bigNumber }}...?
</p>
{{ else }}
<p class="metric-value">
{{ .CountServiceRequests | bigNumber }}
</p>
{{ end }}
<!--<p class="card-text text-muted">
<span class="text-success">
<i class="fas fa-arrow-up"></i> 12%
</span> since last week
</p>-->
</div>
</div>
</div>
</div>
<!-- Mosquito Sources -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-danger bg-opacity-10 text-white">
<i class="fas fa-bug"></i>
</div>
<h5 class="card-title">Mosquito Sources</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">
{{ .CountMosquitoSources | bigNumber }}..?
</p>
{{ else }}
<p class="metric-value">
{{ .CountMosquitoSources | bigNumber }}
</p>
{{ end }}
<!-- <p class="card-text text-muted">
<!-- Mosquito Sources -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-danger bg-opacity-10 text-white">
<i class="fas fa-bug"></i>
</div>
<h5 class="card-title">Mosquito Sources</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">
{{ .CountMosquitoSources | bigNumber }}..?
</p>
{{ else }}
<p class="metric-value">
{{ .CountMosquitoSources | bigNumber }}
</p>
{{ end }}
<!-- <p class="card-text text-muted">
<span class="text-danger">
<i class="fas fa-arrow-up"></i> 8%
</span> since last month
</p> -->
</div>
</div>
</div>
</div>
<!-- Inspections -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-info bg-opacity-10 text-white">
<i class="fas fa-clipboard-check"></i>
</div>
<h5 class="card-title">Traps</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">{{ .CountTraps | bigNumber }}...?</p>
{{ else }}
<p class="metric-value">{{ .CountTraps | bigNumber }}</p>
{{ end }}
<!-- <p class="card-text text-muted">
<!-- Inspections -->
<div class="col-md-3">
<div class="card stats-card h-100">
<div class="card-body text-center">
<div class="metric-icon bg-info bg-opacity-10 text-white">
<i class="fas fa-clipboard-check"></i>
</div>
<h5 class="card-title">Traps</h5>
{{ if .IsSyncOngoing }}
<p class="metric-value">{{ .CountTraps | bigNumber }}...?</p>
{{ else }}
<p class="metric-value">{{ .CountTraps | bigNumber }}</p>
{{ end }}
<!-- <p class="card-text text-muted">
<span class="text-success">
<i class="fas fa-arrow-up"></i> 15%
</span> since last week
</p> -->
</div>
</div>
</div>
</div>
</div>
<!-- Map Section -->
<h3 class="section-title">Mosquito Activity Heatmap</h3>
<div class="row">
<div class="col-12">
<map-aggregate
api-key="{{ .MapData.MapboxToken }}"
latitude="36.3"
longitude="-119.2"
organization-id="{{ .User.Organization.ID }}"
tegola="{{ .Config.URLTegola }}"
zoom="9"
/>
</div>
<!-- Map Section -->
<h3 class="section-title">Mosquito Activity Heatmap</h3>
<div class="row">
<div class="col-12">
<map-aggregate
api-key="{{ .MapData.MapboxToken }}"
latitude="36.3"
longitude="-119.2"
organization-id="{{ .User.Organization.ID }}"
tegola="{{ .Config.URLTegola }}"
zoom="9"
/>
</div>
</div>
<!-- Recent Activity Section -->
<h3 class="section-title">Recent Activity</h3>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<!-- Recent Activity Section -->
<h3 class="section-title">Recent Activity</h3>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>Location</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{{ range $i, $sr := .RecentRequests }}
<tr>
<th>Date</th>
<th>Type</th>
<th>Location</th>
<th>Status</th>
<th>Action</th>
<td>{{ $sr.Date | timeSincePtr }}</td>
<td>Service Request</td>
<td>{{ $sr.Location }}</td>
<td><span class="badge bg-success">Completed</span></td>
<td>
<a href="#" class="btn btn-sm btn-outline-primary"
>View</a
>
</td>
</tr>
</thead>
<tbody>
{{ range $i, $sr := .RecentRequests }}
<tr>
<td>{{ $sr.Date | timeSincePtr }}</td>
<td>Service Request</td>
<td>{{ $sr.Location }}</td>
<td><span class="badge bg-success">Completed</span></td>
<td>
<a href="#" class="btn btn-sm btn-outline-primary"
>View</a
>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>

View file

@ -14,64 +14,62 @@
{{ end }}
{{ define "content" }}
<!-- Main Content -->
<div id="content">
<div class="container-fluid">
<div class="row">
<div class="bd-example">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<div class="container-fluid">
<div class="row">
<div class="bd-example">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn btn-link">Link</button>
</div>
<button type="button" class="btn btn-link">Link</button>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 1</h5>
<p class="card-text">Some example content for the first card.</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 2</h5>
<p class="card-text">Some example content for the second card.</p>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 1</h5>
<p class="card-text">Some example content for the first card.</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="p-3 mb-3 bg-primary text-white">Primary</div>
<div class="p-3 mb-3 bg-primary-100 text-white">Primary-100</div>
<div class="p-3 mb-3 bg-primary-200 text-white">Primary-200</div>
<div class="p-3 mb-3 bg-primary-300 text-white">Primary-300</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-secondary text-white">Secondary</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-success text-white">Success</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="p-3 mb-3 bg-danger text-white">Danger</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-warning text-white">Warning</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-info text-white">Info</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 2</h5>
<p class="card-text">Some example content for the second card.</p>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="p-3 mb-3 bg-primary text-white">Primary</div>
<div class="p-3 mb-3 bg-primary-100 text-white">Primary-100</div>
<div class="p-3 mb-3 bg-primary-200 text-white">Primary-200</div>
<div class="p-3 mb-3 bg-primary-300 text-white">Primary-300</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-secondary text-white">Secondary</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-success text-white">Success</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="p-3 mb-3 bg-danger text-white">Danger</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-warning text-white">Warning</div>
</div>
<div class="col-md-4">
<div class="p-3 mb-3 bg-info text-white">Info</div>
</div>
</div>
{{ end }}

View file

@ -28,7 +28,9 @@
{{ end }}
</div>
{{ template "content" . }}
<div id="content">
{{ template "content" . }}
</div>
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
<script>
document

25
sync/pool.go Normal file
View file

@ -0,0 +1,25 @@
package sync
import (
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
"net/http"
)
type ContentPoolList struct {
URL ContentURL
User User
}
func getPoolList(w http.ResponseWriter, r *http.Request, u *models.User) {
userContent, err := contentForUser(r.Context(), u)
if err != nil {
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
return
}
data := ContentPoolList{
URL: newContentURL(),
User: userContent,
}
html.RenderOrError(w, "sync/pool-list.html", data)
}

View file

@ -62,6 +62,7 @@ func Router() chi.Router {
r.Route("/api", api.AddRoutes)
r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails))
r.Method("GET", "/layout-test", auth.NewEnsureAuth(getLayoutTest))
r.Method("GET", "/pool", auth.NewEnsureAuth(getPoolList))
r.Method("GET", "/settings", auth.NewEnsureAuth(getSettings))
r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout))
r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource))

15
sync/url.go Normal file
View file

@ -0,0 +1,15 @@
package sync
import (
"github.com/Gleipnir-Technology/nidus-sync/config"
)
type ContentURL struct {
PoolCSVUpload string
}
func newContentURL() ContentURL {
return ContentURL{
PoolCSVUpload: config.MakeURLNidus("/pool/upload"),
}
}