This commit is contained in:
parent
7d1d4a43bd
commit
fe8eb7a214
21159
mediafiles/geo_scenarios/combined_with_volumes_ue4uTVE.csv
Normal file
21159
mediafiles/geo_scenarios/combined_with_volumes_ue4uTVE.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
250437
mediafiles/opt_scenarios/json/all_vroom_results_Purolzr.json
Normal file
250437
mediafiles/opt_scenarios/json/all_vroom_results_Purolzr.json
Normal file
File diff suppressed because one or more lines are too long
@ -32,7 +32,11 @@ urlpatterns = [
|
|||||||
path('pxy_whatsapp/', include('pxy_whatsapp.urls')),
|
path('pxy_whatsapp/', include('pxy_whatsapp.urls')),
|
||||||
path('bots/', include('pxy_bots.urls')),
|
path('bots/', include('pxy_bots.urls')),
|
||||||
path('pxy_meta_pages/', include('pxy_meta_pages.urls', namespace='pxy_meta_pages')),
|
path('pxy_meta_pages/', include('pxy_meta_pages.urls', namespace='pxy_meta_pages')),
|
||||||
path("building/", include("pxy_building_digital_twins.urls")),
|
path(
|
||||||
|
"building/",
|
||||||
|
include(("pxy_building_digital_twins.urls", "pxy_building_digital_twins"),
|
||||||
|
namespace="pxy_building_digital_twins"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
# 👇 Register a namespace for reverse()
|
||||||
|
app_name = "pxy_building_digital_twins"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("twin/viewer/", views.viewer, name="building_twin_viewer"),
|
path("twin/viewer/", views.viewer, name="viewer"),
|
||||||
path("api/random/stadium/", views.api_random_stadium, name="api_random_stadium"),
|
path("api/random/stadium/", views.api_random_stadium, name="api_random_stadium"),
|
||||||
path("twin/wire/", views.wire_viewer, name="building_twin_wire"),
|
path("twin/wire/", views.wire_viewer, name="wire_viewer"),
|
||||||
path("twin/wire/babylon/", views.wire_babylon, name="building_twin_wire_babylon"),
|
path("twin/wire/babylon/", views.wire_babylon, name="wire_babylon"),
|
||||||
]
|
]
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
|
# pxy_building_digital_twins/views.py
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
import random, math
|
import random, math
|
||||||
|
|
||||||
|
# ---------- page views (allow same-origin iframe) ----------
|
||||||
|
@xframe_options_sameorigin
|
||||||
def viewer(request):
|
def viewer(request):
|
||||||
return render(request, "pxy_building_digital_twins/viewer.html")
|
resp = render(request, "pxy_building_digital_twins/viewer.html")
|
||||||
|
resp["Content-Security-Policy"] = "frame-ancestors 'self'"
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@xframe_options_sameorigin
|
||||||
def wire_viewer(request):
|
def wire_viewer(request):
|
||||||
return render(request, "pxy_building_digital_twins/wire.html")
|
resp = render(request, "pxy_building_digital_twins/wire.html")
|
||||||
|
resp["Content-Security-Policy"] = "frame-ancestors 'self'"
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@xframe_options_sameorigin
|
||||||
def wire_babylon(request):
|
def wire_babylon(request):
|
||||||
return render(request, "pxy_building_digital_twins/wire_babylon.html")
|
resp = render(request, "pxy_building_digital_twins/wire_babylon.html")
|
||||||
|
resp["Content-Security-Policy"] = "frame-ancestors 'self'"
|
||||||
|
return resp
|
||||||
|
|
||||||
# ---------- geometry helpers ----------
|
# ---------- geometry helpers ----------
|
||||||
def rect_poly(cx, cy, w, h):
|
def rect_poly(cx, cy, w, h):
|
||||||
x1, x2 = cx - w/2, cx + w/2
|
x1, x2 = cx - w/2, cx + w/2
|
||||||
y1, y2 = cy - h/2, cy + h/2
|
y1, y2 = cy - h/2, cy + h/2
|
||||||
return {"type":"Polygon","coordinates":[[[x1,y1],[x2,y1],[x2,y2],[x1,y2],[x1,y1]]]}
|
return {"type": "Polygon", "coordinates": [[[x1,y1],[x2,y1],[x2,y2],[x1,y2],[x1,y1]]]}
|
||||||
|
|
||||||
def ept(cx, cy, rx, ry, a):
|
def ept(cx, cy, rx, ry, a):
|
||||||
return [cx + rx*math.cos(a), cy + ry*math.sin(a)]
|
return [cx + rx*math.cos(a), cy + ry*math.sin(a)]
|
||||||
@ -44,53 +56,33 @@ def bins_on_ellipse(cx, cy, rx, ry, count, start_rad=0.0, jitter_r=0.0):
|
|||||||
|
|
||||||
# ---------- API ----------
|
# ---------- API ----------
|
||||||
def api_random_stadium(request):
|
def api_random_stadium(request):
|
||||||
"""
|
|
||||||
Returns a random stadium (spaces + bins) as GeoJSON FeatureCollections.
|
|
||||||
Query params (optional):
|
|
||||||
- seed: int (deterministic output for a given seed)
|
|
||||||
- concourse_bins: int (default random 42..66)
|
|
||||||
- dry_toilets: int (default random 4..8)
|
|
||||||
"""
|
|
||||||
seed = request.GET.get("seed")
|
seed = request.GET.get("seed")
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
try:
|
try:
|
||||||
random.seed(int(seed))
|
random.seed(int(seed))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
random.seed(seed) # allow string seeds too
|
random.seed(seed)
|
||||||
|
|
||||||
concourse_bins = int(request.GET.get("concourse_bins", random.randint(42,66)))
|
concourse_bins = int(request.GET.get("concourse_bins", random.randint(42,66)))
|
||||||
dry_toilets = int(request.GET.get("dry_toilets", random.randint(4,8)))
|
dry_toilets = int(request.GET.get("dry_toilets", random.randint(4,8)))
|
||||||
|
|
||||||
# Field sizes (soccer-ish)
|
|
||||||
FIELD_W = random.uniform(100, 110)
|
FIELD_W = random.uniform(100, 110)
|
||||||
FIELD_H = random.uniform(64, 72)
|
FIELD_H = random.uniform(64, 72)
|
||||||
|
|
||||||
# Ellipse radii for concourse / stands
|
|
||||||
CONC_RX_IN = random.uniform(62, 68); CONC_RY_IN = random.uniform(56, 64)
|
CONC_RX_IN = random.uniform(62, 68); CONC_RY_IN = random.uniform(56, 64)
|
||||||
CONC_RX_OUT = CONC_RX_IN + random.uniform(6,10); CONC_RY_OUT = CONC_RY_IN + random.uniform(6,10)
|
CONC_RX_OUT = CONC_RX_IN + random.uniform(6,10); CONC_RY_OUT = CONC_RY_IN + random.uniform(6,10)
|
||||||
|
|
||||||
STANDS_RX_IN = CONC_RX_OUT + random.uniform(3,5); STANDS_RY_IN = CONC_RY_OUT + random.uniform(3,5)
|
STANDS_RX_IN = CONC_RX_OUT + random.uniform(3,5); STANDS_RY_IN = CONC_RY_OUT + random.uniform(3,5)
|
||||||
STANDS_RX_OUT = STANDS_RX_IN + random.uniform(18,26); STANDS_RY_OUT = STANDS_RY_IN + random.uniform(18,26)
|
STANDS_RX_OUT = STANDS_RX_IN + random.uniform(18,26); STANDS_RY_OUT = STANDS_RY_IN + random.uniform(18,26)
|
||||||
|
|
||||||
# Build spaces
|
|
||||||
spaces = {"type":"FeatureCollection","features":[]}
|
spaces = {"type":"FeatureCollection","features":[]}
|
||||||
spaces["features"].append({
|
spaces["features"].append({"type":"Feature","geometry": rect_poly(0,0, FIELD_W, FIELD_H),
|
||||||
"type":"Feature",
|
"properties": {"type":"field","name":"Field"}})
|
||||||
"geometry": rect_poly(0,0, FIELD_W, FIELD_H),
|
spaces["features"].append({"type":"Feature","geometry": ellipse_ring(0,0, CONC_RX_OUT, CONC_RY_OUT, CONC_RX_IN, CONC_RY_IN, 128),
|
||||||
"properties": {"type":"field","name":"Field"}
|
"properties": {"type":"concourse","name":"Main Concourse"}})
|
||||||
})
|
spaces["features"].append({"type":"Feature","geometry": ellipse_ring(0,0, STANDS_RX_OUT, STANDS_RY_OUT, STANDS_RX_IN, STANDS_RY_IN, 160),
|
||||||
spaces["features"].append({
|
"properties": {"type":"stands","name":"Stands"}})
|
||||||
"type":"Feature",
|
|
||||||
"geometry": ellipse_ring(0,0, CONC_RX_OUT, CONC_RY_OUT, CONC_RX_IN, CONC_RY_IN, 128),
|
|
||||||
"properties": {"type":"concourse","name":"Main Concourse"}
|
|
||||||
})
|
|
||||||
spaces["features"].append({
|
|
||||||
"type":"Feature",
|
|
||||||
"geometry": ellipse_ring(0,0, STANDS_RX_OUT, STANDS_RY_OUT, STANDS_RX_IN, STANDS_RY_IN, 160),
|
|
||||||
"properties": {"type":"stands","name":"Stands"}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Bins on concourse + outside dry toilets
|
|
||||||
RX_MID = (CONC_RX_IN + CONC_RX_OUT)/2
|
RX_MID = (CONC_RX_IN + CONC_RX_OUT)/2
|
||||||
RY_MID = (CONC_RY_IN + CONC_RY_OUT)/2
|
RY_MID = (CONC_RY_IN + CONC_RY_OUT)/2
|
||||||
bins = {"type":"FeatureCollection","features":[]}
|
bins = {"type":"FeatureCollection","features":[]}
|
||||||
@ -101,10 +93,7 @@ def api_random_stadium(request):
|
|||||||
for i in range(dry_toilets):
|
for i in range(dry_toilets):
|
||||||
a = (i/dry_toilets)*2*math.pi + random.uniform(-0.05,0.05)
|
a = (i/dry_toilets)*2*math.pi + random.uniform(-0.05,0.05)
|
||||||
x,y = ept(0,0, OUT_RX, OUT_RY, a)
|
x,y = ept(0,0, OUT_RX, OUT_RY, a)
|
||||||
bins["features"].append({
|
bins["features"].append({"type":"Feature","geometry":{"type":"Point","coordinates":[x,y]},
|
||||||
"type":"Feature",
|
"properties":{"id":f"DT_{i+1}","kind":"dry_toilet","capacity_l":0}})
|
||||||
"geometry":{"type":"Point","coordinates":[x,y]},
|
|
||||||
"properties":{"id":f"DT_{i+1}","kind":"dry_toilet","capacity_l":0}
|
|
||||||
})
|
|
||||||
|
|
||||||
return JsonResponse({"spaces": spaces, "bins": bins})
|
return JsonResponse({"spaces": spaces, "bins": bins})
|
||||||
|
Binary file not shown.
@ -1,68 +1,24 @@
|
|||||||
<div class="row">
|
<div class="row g-3 align-items-stretch">
|
||||||
|
|
||||||
<!-- Card 1: Bot Interaction Volume by Channel -->
|
<!-- Card 1 -->
|
||||||
<div class="col-xl-4 col-lg-6">
|
<div class="col-xxl-4 col-lg-6">
|
||||||
<div class="card">
|
<div class="card h-100">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Bot Interaction Volume by Channel</h4>
|
<h4 class="header-title mb-0">Bot Interaction Volume by Channel</h4>
|
||||||
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
||||||
<thead class="border-top border-bottom bg-light-subtle border-light">
|
<thead class="border-top border-bottom bg-light-subtle border-light">
|
||||||
<tr>
|
<tr><th>Channel</th><th>Messages</th><th style="width:40%;">Load</th></tr>
|
||||||
<th>Channel</th>
|
|
||||||
<th>Messages</th>
|
|
||||||
<th style="width: 40%;">Load</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr><td>WhatsApp</td><td>2,050</td><td><div class="progress" style="height:3px;"><div class="progress-bar bg-success" style="width:65%"></div></div></td></tr>
|
||||||
<td>WhatsApp</td>
|
<tr><td>Telegram</td><td>1,405</td><td><div class="progress" style="height:3px;"><div class="progress-bar bg-info" style="width:45%"></div></div></td></tr>
|
||||||
<td>2,050</td>
|
<tr><td>Facebook Messenger</td><td>750</td><td><div class="progress" style="height:3px;"><div class="progress-bar bg-warning" style="width:30%"></div></div></td></tr>
|
||||||
<td>
|
<tr><td>AR Interface</td><td>540</td><td><div class="progress" style="height:3px;"><div class="progress-bar bg-danger" style="width:25%"></div></div></td></tr>
|
||||||
<div class="progress" style="height: 3px;">
|
<tr><td>Other</td><td>8,965</td><td><div class="progress" style="height:3px;"><div class="progress-bar bg-dark" style="width:30%"></div></div></td></tr>
|
||||||
<div class="progress-bar bg-success" style="width: 65%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Telegram</td>
|
|
||||||
<td>1,405</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar bg-info" style="width: 45%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Facebook Messenger</td>
|
|
||||||
<td>750</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar bg-warning" style="width: 30%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>AR Interface</td>
|
|
||||||
<td>540</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar bg-danger" style="width: 25%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Other</td>
|
|
||||||
<td>8,965</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar bg-dark" style="width: 30%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -70,69 +26,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card 2: Bot Response Engagement by Network -->
|
<!-- Card 2 -->
|
||||||
<div class="col-xl-4 col-lg-6">
|
<div class="col-xxl-4 col-lg-6">
|
||||||
<div class="card">
|
<div class="card h-100">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Bot Response Engagement by Network</h4>
|
<h4 class="header-title mb-0">Bot Response Engagement by Network</h4>
|
||||||
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
||||||
<thead class="border-top border-bottom bg-light-subtle border-light">
|
<thead class="border-top border-bottom bg-light-subtle border-light">
|
||||||
<tr>
|
<tr><th>Network</th><th>Response Rate</th><th style="width:40%;">Completion</th></tr>
|
||||||
<th>Network</th>
|
|
||||||
<th>Response Rate</th>
|
|
||||||
<th style="width: 40%;">Completion</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr><td>Facebook</td><td>2,250</td><td><div class="progress" style="height:3px;"><div class="progress-bar" style="width:65%"></div></div></td></tr>
|
||||||
<td>Facebook</td>
|
<tr><td>Instagram</td><td>1,501</td><td><div class="progress" style="height:3px;"><div class="progress-bar" style="width:45%"></div></div></td></tr>
|
||||||
<td>2,250</td>
|
<tr><td>Twitter</td><td>750</td><td><div class="progress" style="height:3px;"><div class="progress-bar" style="width:30%"></div></div></td></tr>
|
||||||
<td>
|
<tr><td>LinkedIn</td><td>540</td><td><div class="progress" style="height:3px;"><div class="progress-bar" style="width:25%"></div></div></td></tr>
|
||||||
<div class="progress" style="height: 3px;">
|
<tr><td>Other</td><td>13,851</td><td><div class="progress" style="height:3px;"><div class="progress-bar" style="width:52%"></div></div></td></tr>
|
||||||
<div class="progress-bar" style="width: 65%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Instagram</td>
|
|
||||||
<td>1,501</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar" style="width: 45%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Twitter</td>
|
|
||||||
<td>750</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar" style="width: 30%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>LinkedIn</td>
|
|
||||||
<td>540</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar" style="width: 25%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Other</td>
|
|
||||||
<td>13,851</td>
|
|
||||||
<td>
|
|
||||||
<div class="progress" style="height: 3px;">
|
|
||||||
<div class="progress-bar" style="width: 52%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -140,49 +52,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card 3: Session Duration Analytics -->
|
<!-- Card 3 -->
|
||||||
<div class="col-xl-4 col-lg-12">
|
<div class="col-xxl-4 col-lg-12">
|
||||||
<div class="card">
|
<div class="card h-100">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Bot Interaction Duration</h4>
|
<h4 class="header-title mb-0">Bot Interaction Duration</h4>
|
||||||
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
<a href="javascript:void(0);" class="btn btn-sm btn-success">Export <i class="ri-download-line ms-1"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
<table class="table table-sm table-centered table-hover table-borderless mb-0">
|
||||||
<thead class="border-top border-bottom bg-light-subtle border-light">
|
<thead class="border-top border-bottom bg-light-subtle border-light">
|
||||||
<tr>
|
<tr><th>Duration (Sec)</th><th style="width:30%;">Sessions</th><th style="width:30%;">User Prompts</th></tr>
|
||||||
<th>Duration (Sec)</th>
|
|
||||||
<th style="width: 30%;">Sessions</th>
|
|
||||||
<th style="width: 30%;">User Prompts</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr><td>0–30</td><td>2,250</td><td>4,250</td></tr>
|
||||||
<td>0–30</td>
|
<tr><td>31–60</td><td>1,501</td><td>2,050</td></tr>
|
||||||
<td>2,250</td>
|
<tr><td>61–120</td><td>750</td><td>1,600</td></tr>
|
||||||
<td>4,250</td>
|
<tr><td>121–240</td><td>540</td><td>1,040</td></tr>
|
||||||
</tr>
|
<tr><td>241–420</td><td>56</td><td>886</td></tr>
|
||||||
<tr>
|
|
||||||
<td>31–60</td>
|
|
||||||
<td>1,501</td>
|
|
||||||
<td>2,050</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>61–120</td>
|
|
||||||
<td>750</td>
|
|
||||||
<td>1,600</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>121–240</td>
|
|
||||||
<td>540</td>
|
|
||||||
<td>1,040</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>241–420</td>
|
|
||||||
<td>56</td>
|
|
||||||
<td>886</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<!-- Left Card: Collection Efficiency by Region -->
|
<!-- Izquierda: Eficiencia de recolección por macrozona -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Collection Efficiency by Region</h4>
|
<h4 class="header-title">Eficiencia de recolección por macrozona</h4>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="ri-more-2-fill"></i>
|
<i class="ri-more-2-fill"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Efficiency Report</a>
|
<a href="javascript:void(0);" class="dropdown-item">Reporte de eficiencia</a>
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Export</a>
|
<a href="javascript:void(0);" class="dropdown-item">Exportar</a>
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Anomalies</a>
|
<a href="javascript:void(0);" class="dropdown-item">Anomalías</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
<div id="average-sales" class="apex-charts mb-3 mt-n5" data-colors="#4254ba"></div>
|
<div id="average-sales" class="apex-charts mb-3 mt-n5"
|
||||||
|
data-colors="#4254ba"
|
||||||
|
title="Porcentaje de microzonas con recolección completa respecto al plan, por macrozona del estadio."></div>
|
||||||
|
|
||||||
<h5 class="mb-1 mt-0 fw-normal">Centro Histórico</h5>
|
<h5 class="mb-1 mt-0 fw-normal">Cabecera Norte — Anillo 100</h5>
|
||||||
<div class="progress-w-percent">
|
<div class="progress-w-percent">
|
||||||
<span class="progress-value fw-bold">72%</span>
|
<span class="progress-value fw-bold">72%</span>
|
||||||
<div class="progress progress-sm">
|
<div class="progress progress-sm">
|
||||||
@ -29,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="mb-1 mt-0 fw-normal">Santa María la Ribera</h5>
|
<h5 class="mb-1 mt-0 fw-normal">Lateral Oriente — Anillo 200</h5>
|
||||||
<div class="progress-w-percent">
|
<div class="progress-w-percent">
|
||||||
<span class="progress-value fw-bold">39%</span>
|
<span class="progress-value fw-bold">39%</span>
|
||||||
<div class="progress progress-sm">
|
<div class="progress progress-sm">
|
||||||
@ -38,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="mb-1 mt-0 fw-normal">Iztapalapa Norte</h5>
|
<h5 class="mb-1 mt-0 fw-normal">Cabecera Sur — Anillo 300</h5>
|
||||||
<div class="progress-w-percent mb-0">
|
<div class="progress-w-percent mb-0">
|
||||||
<span class="progress-value fw-bold">61%</span>
|
<span class="progress-value fw-bold">61%</span>
|
||||||
<div class="progress progress-sm">
|
<div class="progress progress-sm">
|
||||||
@ -50,20 +52,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Card: Optimization Impact Report -->
|
<!-- Derecha: Reporte de impacto de optimización -->
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Optimization Impact Report</h4>
|
<h4 class="header-title">Impacto de la optimización de residuos</h4>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="ri-more-2-fill"></i>
|
<i class="ri-more-2-fill"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Compare Weeks</a>
|
<a href="javascript:void(0);" class="dropdown-item">Comparar semanas</a>
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Export</a>
|
<a href="javascript:void(0);" class="dropdown-item">Exportar</a>
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Forecast</a>
|
<a href="javascript:void(0);" class="dropdown-item">Pronóstico</a>
|
||||||
<a href="javascript:void(0);" class="dropdown-item">Model Drift</a>
|
<a href="javascript:void(0);" class="dropdown-item">Deriva del modelo</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -72,25 +74,25 @@
|
|||||||
<div class="bg-light-subtle border-top border-bottom border-light">
|
<div class="bg-light-subtle border-top border-bottom border-light">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="text-muted mt-3"><i class="ri-road-map-fill"></i> Km Saved (This Week)</p>
|
<p class="text-muted mt-3"><i class="ri-road-map-fill"></i> Km evitados (esta semana)</p>
|
||||||
<h3 class="fw-normal mb-3">
|
<h3 class="fw-normal mb-3">
|
||||||
<span>114.3 km</span>
|
<span>114.3 km</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="text-muted mt-3"><i class="ri-money-dollar-circle-line"></i> Operational Cost Saved</p>
|
<p class="text-muted mt-3"><i class="ri-money-dollar-circle-line"></i> Costo operativo evitado</p>
|
||||||
<h3 class="fw-normal mb-3">
|
<h3 class="fw-normal mb-3">
|
||||||
<span>$6,523.25 <i class="ri-corner-right-up-fill text-success"></i></span>
|
<span>$6,523.25 <i class="ri-corner-right-up-fill text-success"></i></span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="text-muted mt-3"><i class="ri-message-2-fill"></i> Response Rate</p>
|
<p class="text-muted mt-3"><i class="ri-message-2-fill"></i> Cumplimiento de recolección</p>
|
||||||
<h3 class="fw-normal mb-3">
|
<h3 class="fw-normal mb-3">
|
||||||
<span>82.7%</span>
|
<span>82.7%</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="text-muted mt-3"><i class="ri-shield-check-fill"></i> Twin Sync Accuracy</p>
|
<p class="text-muted mt-3"><i class="ri-shield-check-fill"></i> Exactitud del gemelo</p>
|
||||||
<h3 class="fw-normal mb-3">
|
<h3 class="fw-normal mb-3">
|
||||||
<span>91% <i class="ri-corner-right-down-line text-danger"></i></span>
|
<span>91% <i class="ri-corner-right-down-line text-danger"></i></span>
|
||||||
</h3>
|
</h3>
|
||||||
@ -101,7 +103,8 @@
|
|||||||
|
|
||||||
<div class="card-body pt-0">
|
<div class="card-body pt-0">
|
||||||
<div dir="ltr">
|
<div dir="ltr">
|
||||||
<div id="revenue-chart" class="apex-charts mt-1" data-colors="#4254ba,#17a497"></div>
|
<div id="revenue-chart" class="apex-charts mt-1" data-colors="#4254ba,#17a497"
|
||||||
|
title="Evolución semanal del ahorro operativo y de la distancia recorrida tras la optimización."></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Left Card: Collection Efficiency by Region -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
|
<h4 class="header-title">Collection Efficiency by Region</h4>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="ri-more-2-fill"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Efficiency Report</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Export</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Anomalies</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<div id="average-sales" class="apex-charts mb-3 mt-n5" data-colors="#4254ba"></div>
|
||||||
|
|
||||||
|
<h5 class="mb-1 mt-0 fw-normal">Centro Histórico</h5>
|
||||||
|
<div class="progress-w-percent">
|
||||||
|
<span class="progress-value fw-bold">72%</span>
|
||||||
|
<div class="progress progress-sm">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: 72%;" aria-valuenow="72"
|
||||||
|
aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="mb-1 mt-0 fw-normal">Santa María la Ribera</h5>
|
||||||
|
<div class="progress-w-percent">
|
||||||
|
<span class="progress-value fw-bold">39%</span>
|
||||||
|
<div class="progress progress-sm">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: 39%;" aria-valuenow="39"
|
||||||
|
aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="mb-1 mt-0 fw-normal">Iztapalapa Norte</h5>
|
||||||
|
<div class="progress-w-percent mb-0">
|
||||||
|
<span class="progress-value fw-bold">61%</span>
|
||||||
|
<div class="progress progress-sm">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: 61%;" aria-valuenow="61"
|
||||||
|
aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Card: Optimization Impact Report -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
|
<h4 class="header-title">Optimization Impact Report</h4>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="ri-more-2-fill"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Compare Weeks</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Export</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Forecast</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Model Drift</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="bg-light-subtle border-top border-bottom border-light">
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<p class="text-muted mt-3"><i class="ri-road-map-fill"></i> Km Saved (This Week)</p>
|
||||||
|
<h3 class="fw-normal mb-3">
|
||||||
|
<span>114.3 km</span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<p class="text-muted mt-3"><i class="ri-money-dollar-circle-line"></i> Operational Cost Saved</p>
|
||||||
|
<h3 class="fw-normal mb-3">
|
||||||
|
<span>$6,523.25 <i class="ri-corner-right-up-fill text-success"></i></span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<p class="text-muted mt-3"><i class="ri-message-2-fill"></i> Response Rate</p>
|
||||||
|
<h3 class="fw-normal mb-3">
|
||||||
|
<span>82.7%</span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<p class="text-muted mt-3"><i class="ri-shield-check-fill"></i> Twin Sync Accuracy</p>
|
||||||
|
<h3 class="fw-normal mb-3">
|
||||||
|
<span>91% <i class="ri-corner-right-down-line text-danger"></i></span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<div dir="ltr">
|
||||||
|
<div id="revenue-chart" class="apex-charts mt-1" data-colors="#4254ba,#17a497"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -1,16 +1,19 @@
|
|||||||
<div class="row row-cols-1 row-cols-xxl-6 row-cols-lg-3 row-cols-md-2">
|
<div class="row row-cols-1 row-cols-xxl-6 row-cols-lg-3 row-cols-md-2">
|
||||||
|
|
||||||
<!-- Monitored Collection Points -->
|
<!-- Puntos de acopio monitoreados -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Collection points actively tracked via the urban digital twin with optional AR feedback.">Monitored Collection Points</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Secciones del estadio con contenedores monitoreados en tiempo real mediante el gemelo digital.">
|
||||||
|
Puntos de acopio monitoreados
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">128</h3>
|
<h3 class="my-3">128</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 5%</span>
|
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 5%</span>
|
||||||
<span>Since yesterday</span>
|
<span>vs. ayer</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
@ -23,17 +26,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Routes Optimized Today -->
|
<!-- Circuitos optimizados hoy -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Number of AI-optimized waste collection routes dispatched today.">Routes Optimized Today</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Número de circuitos de recolección optimizados y despachados hoy.">
|
||||||
|
Circuitos optimizados hoy
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">24</h3>
|
<h3 class="my-3">24</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-info me-1"><i class="ri-arrow-up-line"></i> 2</span>
|
<span class="badge bg-info me-1"><i class="ri-arrow-up-line"></i> 2</span>
|
||||||
<span>Since yesterday</span>
|
<span>vs. ayer</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
@ -46,17 +52,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Predicted Waste Volume -->
|
<!-- Residuos previstos (kg) -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Total waste volume predicted for today based on model estimations and citizen input.">Predicted Waste Volume (kg)</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Total estimado de residuos generados hoy por sección (modelo + aforo).">
|
||||||
|
Residuos previstos (kg)
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">13,280</h3>
|
<h3 class="my-3">13,280</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-danger me-1"><i class="ri-arrow-up-line"></i> 3.2%</span>
|
<span class="badge bg-danger me-1"><i class="ri-arrow-up-line"></i> 3.2%</span>
|
||||||
<span>vs. weekly average</span>
|
<span>vs. promedio semanal</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
@ -69,17 +78,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Operational Deviation -->
|
<!-- Desviación operativa (%) -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Gap between expected and real-world route duration or distance.">Operational Deviation (%)</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Brecha entre lo planificado y lo ejecutado en duración y distancia de los circuitos.">
|
||||||
|
Desviación operativa (%)
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">+4.87%</h3>
|
<h3 class="my-3">+4.87%</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-primary me-1"><i class="ri-arrow-up-line"></i> 1.2%</span>
|
<span class="badge bg-primary me-1"><i class="ri-arrow-up-line"></i> 1.2%</span>
|
||||||
<span>vs. target baseline</span>
|
<span>vs. línea base</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
@ -92,17 +104,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Citizen Interactions -->
|
<!-- Reportes y alertas (24h) -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Number of messages processed by the AI bots across WhatsApp, Telegram, and Facebook in the past 24 hours.">Citizen Interactions (24h)</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Reportes ciudadanos y alertas operativas procesadas por los bots en las últimas 24 horas.">
|
||||||
|
Reportes y alertas (24h)
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">310</h3>
|
<h3 class="my-3">310</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-warning me-1"><i class="ri-arrow-up-line"></i> 9.2%</span>
|
<span class="badge bg-warning me-1"><i class="ri-arrow-up-line"></i> 9.2%</span>
|
||||||
<span>vs. average day</span>
|
<span>vs. día promedio</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
@ -115,17 +130,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Twin Confidence Score -->
|
<!-- Índice de confianza del gemelo -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card widget-icon-box">
|
<div class="card widget-icon-box">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<h5 class="text-muted text-uppercase fs-13 mt-0" title="AI twin model confidence based on feedback accuracy and adherence to planned routes.">Twin Confidence Score</h5>
|
<h5 class="text-muted text-uppercase fs-13 mt-0"
|
||||||
|
title="Confianza del gemelo digital basada en precisión de datos y cumplimiento de rutas.">
|
||||||
|
Índice de confianza del gemelo
|
||||||
|
</h5>
|
||||||
<h3 class="my-3">87%</h3>
|
<h3 class="my-3">87%</h3>
|
||||||
<p class="mb-0 text-muted text-truncate">
|
<p class="mb-0 text-muted text-truncate">
|
||||||
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 6%</span>
|
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 6%</span>
|
||||||
<span>since last calibration</span>
|
<span>desde la última calibración</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-sm flex-shrink-0">
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
@ -0,0 +1,141 @@
|
|||||||
|
<div class="row row-cols-1 row-cols-xxl-6 row-cols-lg-3 row-cols-md-2">
|
||||||
|
|
||||||
|
<!-- Monitored Collection Points -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Collection points actively tracked via the urban digital twin with optional AR feedback.">Monitored Collection Points</h5>
|
||||||
|
<h3 class="my-3">128</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 5%</span>
|
||||||
|
<span>Since yesterday</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-success rounded rounded-3 fs-3 widget-icon-box-avatar shadow">
|
||||||
|
<i class="ri-map-pin-2-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Routes Optimized Today -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Number of AI-optimized waste collection routes dispatched today.">Routes Optimized Today</h5>
|
||||||
|
<h3 class="my-3">24</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-info me-1"><i class="ri-arrow-up-line"></i> 2</span>
|
||||||
|
<span>Since yesterday</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-info rounded rounded-3 fs-3 widget-icon-box-avatar shadow">
|
||||||
|
<i class="ri-road-map-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Predicted Waste Volume -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Total waste volume predicted for today based on model estimations and citizen input.">Predicted Waste Volume (kg)</h5>
|
||||||
|
<h3 class="my-3">13,280</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-danger me-1"><i class="ri-arrow-up-line"></i> 3.2%</span>
|
||||||
|
<span>vs. weekly average</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-danger rounded rounded-3 fs-3 widget-icon-box-avatar shadow">
|
||||||
|
<i class="ri-weight-line"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Operational Deviation -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Gap between expected and real-world route duration or distance.">Operational Deviation (%)</h5>
|
||||||
|
<h3 class="my-3">+4.87%</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-primary me-1"><i class="ri-arrow-up-line"></i> 1.2%</span>
|
||||||
|
<span>vs. target baseline</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-primary rounded rounded-3 fs-3 widget-icon-box-avatar shadow">
|
||||||
|
<i class="ri-line-chart-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Citizen Interactions -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="Number of messages processed by the AI bots across WhatsApp, Telegram, and Facebook in the past 24 hours.">Citizen Interactions (24h)</h5>
|
||||||
|
<h3 class="my-3">310</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-warning me-1"><i class="ri-arrow-up-line"></i> 9.2%</span>
|
||||||
|
<span>vs. average day</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-warning rounded rounded-3 fs-3 widget-icon-box-avatar">
|
||||||
|
<i class="ri-message-3-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Twin Confidence Score -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card widget-icon-box">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<h5 class="text-muted text-uppercase fs-13 mt-0" title="AI twin model confidence based on feedback accuracy and adherence to planned routes.">Twin Confidence Score</h5>
|
||||||
|
<h3 class="my-3">87%</h3>
|
||||||
|
<p class="mb-0 text-muted text-truncate">
|
||||||
|
<span class="badge bg-success me-1"><i class="ri-arrow-up-line"></i> 6%</span>
|
||||||
|
<span>since last calibration</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-sm flex-shrink-0">
|
||||||
|
<span class="avatar-title text-bg-dark rounded rounded-3 fs-3 widget-icon-box-avatar">
|
||||||
|
<i class="ri-shield-check-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -4,7 +4,13 @@
|
|||||||
<div class="col-xl-7">
|
<div class="col-xl-7">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="d-flex card-header justify-content-between align-items-center">
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
<h4 class="header-title">Route Activity by Zone</h4>
|
<h4 class="header-title mb-0">Route Activity by Zone</h4>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<a href="{% url 'pxy_building_digital_twins:viewer' %}"
|
||||||
|
class="btn btn-sm btn-outline-primary" target="_blank" rel="noopener">
|
||||||
|
Open Twin Viewer
|
||||||
|
</a>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="ri-more-2-fill"></i>
|
<i class="ri-more-2-fill"></i>
|
||||||
@ -17,14 +23,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row g-3">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div id="world-map-markers" class="mt-3 mb-3" style="height: 317px"></div>
|
<div id="stadium-map" class="mt-1" style="height:317px;"></div>
|
||||||
|
<!-- keep this hidden so theme scripts targeting it won't explode -->
|
||||||
|
<div id="world-map-markers" class="d-none"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4" dir="ltr">
|
<div class="col-lg-4" dir="ltr">
|
||||||
<div id="country-chart" class="apex-charts" data-colors="#17a497"></div>
|
<div class="zone-chart-wrap">
|
||||||
|
<div id="zone-chart" class="apex-charts"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,48 +63,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr><td>Organic</td><td>14.2 kg</td><td>214</td><td>3,452 kg</td><td>AR App</td></tr>
|
||||||
<td>Organic</td>
|
<tr><td>Plastics</td><td>7.9 kg</td><td>182</td><td>1,436 kg</td><td>WhatsApp Bot</td></tr>
|
||||||
<td>14.2 kg</td>
|
<tr><td>Paper</td><td>6.1 kg</td><td>98</td><td>689 kg</td><td>Manual Entry</td></tr>
|
||||||
<td>214</td>
|
<tr><td>Glass</td><td>9.4 kg</td><td>47</td><td>442 kg</td><td>Telegram Bot</td></tr>
|
||||||
<td>3,452 kg</td>
|
<tr><td>Metal</td><td>4.5 kg</td><td>36</td><td>162 kg</td><td>Facebook Pages</td></tr>
|
||||||
<td>AR App</td>
|
<tr><td>Textiles</td><td>3.1 kg</td><td>22</td><td>68 kg</td><td>AR App</td></tr>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Plastics</td>
|
|
||||||
<td>7.9 kg</td>
|
|
||||||
<td>182</td>
|
|
||||||
<td>1,436 kg</td>
|
|
||||||
<td>WhatsApp Bot</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Paper</td>
|
|
||||||
<td>6.1 kg</td>
|
|
||||||
<td>98</td>
|
|
||||||
<td>689 kg</td>
|
|
||||||
<td>Manual Entry</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Glass</td>
|
|
||||||
<td>9.4 kg</td>
|
|
||||||
<td>47</td>
|
|
||||||
<td>442 kg</td>
|
|
||||||
<td>Telegram Bot</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Metal</td>
|
|
||||||
<td>4.5 kg</td>
|
|
||||||
<td>36</td>
|
|
||||||
<td>162 kg</td>
|
|
||||||
<td>Facebook Pages</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Textiles</td>
|
|
||||||
<td>3.1 kg</td>
|
|
||||||
<td>22</td>
|
|
||||||
<td>68 kg</td>
|
|
||||||
<td>AR App</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -105,3 +80,202 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#stadium-map {
|
||||||
|
background:#0b0f19;
|
||||||
|
border-radius:.5rem;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.stadium-svg { width:100%; height:100%; display:block; }
|
||||||
|
.stadium-svg .bin { transition: transform .12s ease; cursor:pointer; }
|
||||||
|
.stadium-svg .bin:hover { transform: scale(1.12); }
|
||||||
|
|
||||||
|
/* donut container: center + cap size so it doesn't spill */
|
||||||
|
.zone-chart-wrap{
|
||||||
|
min-height:317px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
}
|
||||||
|
#zone-chart{ width:100%; max-width:340px; height:280px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* Patch ApexCharts so "Element not found" never hard-errors */
|
||||||
|
(function(){
|
||||||
|
function patch(){
|
||||||
|
if (!window.ApexCharts || window.__ApexSafe__) return;
|
||||||
|
const Orig = window.ApexCharts;
|
||||||
|
function ensure(el){
|
||||||
|
if (typeof el === "string") el = document.querySelector(el);
|
||||||
|
if (el) return el;
|
||||||
|
let sink = document.getElementById("__apx_sink__");
|
||||||
|
if (!sink){
|
||||||
|
sink = document.createElement("div");
|
||||||
|
sink.id="__apx_sink__";
|
||||||
|
sink.style.cssText="position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;overflow:hidden;";
|
||||||
|
document.body.appendChild(sink);
|
||||||
|
}
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
function Safe(el, opts){ return new Orig(ensure(el), opts); }
|
||||||
|
Object.getOwnPropertyNames(Orig).forEach(k=>{ try{ Safe[k]=Orig[k]; }catch(_){} });
|
||||||
|
Safe.prototype = Orig.prototype;
|
||||||
|
window.ApexCharts = Safe;
|
||||||
|
window.__ApexSafe__ = true;
|
||||||
|
}
|
||||||
|
patch(); document.addEventListener("DOMContentLoaded", patch);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* ----------------------- Stadium renderer (pure SVG) + Zone donut ----------------------- */
|
||||||
|
(function(){
|
||||||
|
const macroColors = {
|
||||||
|
"Cabecera Norte":"#60a5fa",
|
||||||
|
"Cabecera Sur":"#34d399",
|
||||||
|
"Lateral Oriente":"#fbbf24",
|
||||||
|
"Lateral Poniente":"#f87171"
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildExampleBins(){
|
||||||
|
const bins = [];
|
||||||
|
const count = 56, cx = 1000, cy = 700, rx = 720, ry = 500;
|
||||||
|
for (let i=0;i<count;i++){
|
||||||
|
const a = (i/count) * Math.PI * 2;
|
||||||
|
const x = cx + rx * Math.cos(a);
|
||||||
|
const y = cy + ry * Math.sin(a);
|
||||||
|
let macro;
|
||||||
|
if (a >= 7*Math.PI/4 || a < Math.PI/4) macro = "Lateral Oriente";
|
||||||
|
else if (a < 3*Math.PI/4) macro = "Cabecera Norte";
|
||||||
|
else if (a < 5*Math.PI/4) macro = "Lateral Poniente";
|
||||||
|
else macro = "Cabecera Sur";
|
||||||
|
const kg = Math.round(8 + 10*Math.abs(Math.cos(a)) + (Math.random()*3|0));
|
||||||
|
bins.push({ id:`SEC-${i+101}`, x, y, kg, macro });
|
||||||
|
}
|
||||||
|
return bins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStadium(host, bins){
|
||||||
|
host.innerHTML = "";
|
||||||
|
const NS = "http://www.w3.org/2000/svg";
|
||||||
|
const svg = document.createElementNS(NS,"svg");
|
||||||
|
svg.setAttribute("viewBox","0 0 2000 1400");
|
||||||
|
svg.setAttribute("preserveAspectRatio","xMidYMid meet");
|
||||||
|
svg.classList.add("stadium-svg");
|
||||||
|
|
||||||
|
const defs = document.createElementNS(NS,"defs");
|
||||||
|
defs.innerHTML = `
|
||||||
|
<radialGradient id="bgGrad">
|
||||||
|
<stop offset="0%" stop-color="#0b0f19"/>
|
||||||
|
<stop offset="70%" stop-color="#0b0f19"/>
|
||||||
|
<stop offset="100%" stop-color="#0a0e18"/>
|
||||||
|
</radialGradient>`;
|
||||||
|
svg.appendChild(defs);
|
||||||
|
|
||||||
|
const bg = document.createElementNS(NS,"rect");
|
||||||
|
bg.setAttribute("width","2000"); bg.setAttribute("height","1400");
|
||||||
|
bg.setAttribute("fill","url(#bgGrad)");
|
||||||
|
svg.appendChild(bg);
|
||||||
|
|
||||||
|
// Field
|
||||||
|
const field = document.createElementNS(NS,"rect");
|
||||||
|
field.setAttribute("x", 1000-550);
|
||||||
|
field.setAttribute("y", 700-360);
|
||||||
|
field.setAttribute("width", 1100);
|
||||||
|
field.setAttribute("height", 720);
|
||||||
|
field.setAttribute("rx", 26);
|
||||||
|
field.setAttribute("fill", "#0d7d3b");
|
||||||
|
field.setAttribute("stroke", "#b8f7c0");
|
||||||
|
field.setAttribute("stroke-width", "8");
|
||||||
|
svg.appendChild(field);
|
||||||
|
|
||||||
|
// Mid line + center circle
|
||||||
|
const mid = document.createElementNS(NS,"line");
|
||||||
|
mid.setAttribute("x1","1000"); mid.setAttribute("y1", 700-360);
|
||||||
|
mid.setAttribute("x2","1000"); mid.setAttribute("y2", 700+360);
|
||||||
|
mid.setAttribute("stroke","#b8f7c0"); mid.setAttribute("stroke-width","6"); mid.setAttribute("opacity","0.8");
|
||||||
|
svg.appendChild(mid);
|
||||||
|
const cc = document.createElementNS(NS,"circle");
|
||||||
|
cc.setAttribute("cx","1000"); cc.setAttribute("cy","700"); cc.setAttribute("r","110");
|
||||||
|
cc.setAttribute("fill","none"); cc.setAttribute("stroke","#b8f7c0"); cc.setAttribute("stroke-width","6"); cc.setAttribute("opacity","0.8");
|
||||||
|
svg.appendChild(cc);
|
||||||
|
|
||||||
|
// Stands rings
|
||||||
|
function ellipse(cx, cy, rx, ry, stroke, sw){
|
||||||
|
const e = document.createElementNS(NS,"ellipse");
|
||||||
|
e.setAttribute("cx", cx); e.setAttribute("cy", cy);
|
||||||
|
e.setAttribute("rx", rx); e.setAttribute("ry", ry);
|
||||||
|
e.setAttribute("fill","none"); e.setAttribute("stroke", stroke);
|
||||||
|
e.setAttribute("stroke-width", sw); return e;
|
||||||
|
}
|
||||||
|
svg.appendChild(ellipse(1000,700, 820, 580, "#2a3142", 30));
|
||||||
|
svg.appendChild(ellipse(1000,700, 900, 640, "#202636", 30));
|
||||||
|
|
||||||
|
// Title
|
||||||
|
function label(y, size, txt, fill, weight=700, opacity=.9){
|
||||||
|
const t=document.createElementNS(NS,"text");
|
||||||
|
t.setAttribute("x","1000"); t.setAttribute("y",String(y));
|
||||||
|
t.setAttribute("text-anchor","middle");
|
||||||
|
t.setAttribute("fill",fill); t.setAttribute("font-size",size);
|
||||||
|
t.setAttribute("font-weight",weight); t.setAttribute("opacity",opacity);
|
||||||
|
t.textContent=txt; return t;
|
||||||
|
}
|
||||||
|
svg.appendChild(label(120,44,"Estadio — Actividad de Recolección","#e5e7eb",700,.95));
|
||||||
|
svg.appendChild(label(165,28,"Burbujas ∝ kg por microzona · color = macrozona","#9ca3af",500,.95));
|
||||||
|
|
||||||
|
// Bins
|
||||||
|
bins.forEach(b=>{
|
||||||
|
const r = Math.max(5, Math.sqrt(b.kg)*3);
|
||||||
|
const c = document.createElementNS(NS,"circle");
|
||||||
|
c.setAttribute("cx",b.x); c.setAttribute("cy",b.y); c.setAttribute("r",r);
|
||||||
|
c.setAttribute("fill", macroColors[b.macro] || "#94a3b8"); c.setAttribute("opacity","0.9");
|
||||||
|
const ring = document.createElementNS(NS,"circle");
|
||||||
|
ring.setAttribute("cx",b.x); ring.setAttribute("cy",b.y); ring.setAttribute("r",r+2.5);
|
||||||
|
ring.setAttribute("fill","none"); ring.setAttribute("stroke","rgba(255,255,255,0.35)"); ring.setAttribute("stroke-width","1.5");
|
||||||
|
const g = document.createElementNS(NS,"g"); g.setAttribute("class","bin");
|
||||||
|
const title = document.createElementNS(NS,"title");
|
||||||
|
title.textContent = `${b.id}\nMacro: ${b.macro}\n${b.kg} kg`;
|
||||||
|
g.appendChild(c); g.appendChild(ring); g.appendChild(title);
|
||||||
|
svg.appendChild(g);
|
||||||
|
});
|
||||||
|
|
||||||
|
host.appendChild(svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDonut(agg){
|
||||||
|
const el = document.getElementById("zone-chart");
|
||||||
|
if (!el || typeof ApexCharts === "undefined") return;
|
||||||
|
const labels = Object.keys(agg);
|
||||||
|
const series = labels.map(k => agg[k]);
|
||||||
|
const colors = labels.map(l => macroColors[l] || "#94a3b8"); // match stadium colors
|
||||||
|
try{
|
||||||
|
new ApexCharts(el, {
|
||||||
|
chart:{ type:"donut", height: 280 },
|
||||||
|
labels, series, colors,
|
||||||
|
legend:{
|
||||||
|
show:true, position:"bottom", horizontalAlign:"center",
|
||||||
|
markers:{ width:8, height:8, radius:8 }
|
||||||
|
},
|
||||||
|
dataLabels:{ enabled:false },
|
||||||
|
plotOptions:{ pie:{ donut:{ size:"68%" } } },
|
||||||
|
tooltip:{ y:{ formatter:v=>`${v} kg` } },
|
||||||
|
responsive:[{ breakpoint: 576, options:{ chart:{ height:240 }, plotOptions:{ pie:{ donut:{ size:"62%" } } } } }]
|
||||||
|
}).render();
|
||||||
|
}catch(e){ console.warn("Donut init failed:", e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(){
|
||||||
|
const host = document.getElementById("stadium-map");
|
||||||
|
if (!host) return;
|
||||||
|
const bins = buildExampleBins();
|
||||||
|
drawStadium(host, bins);
|
||||||
|
|
||||||
|
const agg = {};
|
||||||
|
bins.forEach(b => agg[b.macro] = (agg[b.macro] || 0) + b.kg);
|
||||||
|
drawDonut(agg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState !== "loading") setTimeout(init, 40);
|
||||||
|
else document.addEventListener("DOMContentLoaded", () => setTimeout(init, 40));
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Left Card: Route Activity by Zone -->
|
||||||
|
<div class="col-xl-7">
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
|
<h4 class="header-title">Route Activity by Zone</h4>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle arrow-none card-drop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="ri-more-2-fill"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-animated dropdown-menu-end">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Live Status</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Route Density</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Export KML</a>
|
||||||
|
<a href="javascript:void(0);" class="dropdown-item">Show Anomalies</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div id="world-map-markers" class="mt-3 mb-3" style="height: 317px"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4" dir="ltr">
|
||||||
|
<div id="country-chart" class="apex-charts" data-colors="#17a497"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Card: Material Volume Breakdown -->
|
||||||
|
<div class="col-xl-5">
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex card-header justify-content-between align-items-center">
|
||||||
|
<h4 class="header-title">Material Volume Breakdown</h4>
|
||||||
|
<a href="javascript:void(0);" class="btn btn-sm btn-info">Export <i class="ri-download-line ms-1"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-borderless table-hover table-nowrap table-centered m-0">
|
||||||
|
<thead class="border-top border-bottom bg-light-subtle border-light">
|
||||||
|
<tr>
|
||||||
|
<th class="py-1">Material</th>
|
||||||
|
<th class="py-1">Avg. Weight</th>
|
||||||
|
<th class="py-1">Pickups</th>
|
||||||
|
<th class="py-1">Total Collected</th>
|
||||||
|
<th class="py-1">Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Organic</td>
|
||||||
|
<td>14.2 kg</td>
|
||||||
|
<td>214</td>
|
||||||
|
<td>3,452 kg</td>
|
||||||
|
<td>AR App</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Plastics</td>
|
||||||
|
<td>7.9 kg</td>
|
||||||
|
<td>182</td>
|
||||||
|
<td>1,436 kg</td>
|
||||||
|
<td>WhatsApp Bot</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Paper</td>
|
||||||
|
<td>6.1 kg</td>
|
||||||
|
<td>98</td>
|
||||||
|
<td>689 kg</td>
|
||||||
|
<td>Manual Entry</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Glass</td>
|
||||||
|
<td>9.4 kg</td>
|
||||||
|
<td>47</td>
|
||||||
|
<td>442 kg</td>
|
||||||
|
<td>Telegram Bot</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Metal</td>
|
||||||
|
<td>4.5 kg</td>
|
||||||
|
<td>36</td>
|
||||||
|
<td>162 kg</td>
|
||||||
|
<td>Facebook Pages</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Textiles</td>
|
||||||
|
<td>3.1 kg</td>
|
||||||
|
<td>22</td>
|
||||||
|
<td>68 kg</td>
|
||||||
|
<td>AR App</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="#!" class="text-primary text-decoration-underline fw-bold btn mb-2">View All</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
Loading…
x
Reference in New Issue
Block a user