446 lines
21 KiB
Python
446 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Uber Delivery Tracking Page – standalone HTML, no API key required.
|
||
Uses OpenStreetMap (free, no key) + Leaflet.js for live route map.
|
||
"""
|
||
from odoo import http
|
||
from odoo.http import request, Response
|
||
import urllib.parse
|
||
import logging
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class UberTrackingController(http.Controller):
|
||
|
||
@http.route([
|
||
'/uber/track/sale/<int:order_id>',
|
||
'/uber/track/pos/<int:order_id>',
|
||
], type='http', auth='user', website=False)
|
||
def track_delivery(self, order_id, **kwargs):
|
||
path = request.httprequest.path
|
||
|
||
# ── Load order ─────────────────────────────────────────────
|
||
if '/sale/' in path:
|
||
order = request.env['sale.order'].sudo().browse(order_id)
|
||
model_type = 'sale'
|
||
partner = order.partner_shipping_id or order.partner_id
|
||
order_ref = order.name or f'SO{order_id}'
|
||
customer_name = partner.name if partner else 'Customer'
|
||
customer_phone = (partner.phone or partner.mobile or '') if partner else ''
|
||
dropoff_street = (partner.street or '') if partner else ''
|
||
dropoff_city = (partner.city or '') if partner else ''
|
||
dropoff_state = (partner.state_id.name if partner and partner.state_id else '')
|
||
dropoff_zip = (partner.zip or '') if partner else ''
|
||
dropoff_country = (partner.country_id.name if partner and partner.country_id else '')
|
||
uber_status = order.uber_status or ''
|
||
uber_tracking_url = order.uber_tracking_url or ''
|
||
uber_delivery_id = order.uber_delivery_id or ''
|
||
uber_eta = str(order.uber_eta)[:16] if order.uber_eta else ''
|
||
else:
|
||
order = request.env['pos.order'].sudo().browse(order_id)
|
||
model_type = 'pos'
|
||
partner = order.partner_id
|
||
order_ref = order.pos_reference or order.name or f'PO{order_id}'
|
||
customer_name = partner.name if partner else 'Customer'
|
||
customer_phone = (partner.phone or partner.mobile or '') if partner else ''
|
||
dropoff_street = getattr(order, 'delivery_street', '') or ((partner.street or '') if partner else '')
|
||
dropoff_city = getattr(order, 'delivery_city', '') or ((partner.city or '') if partner else '')
|
||
dropoff_state = (partner.state_id.name if partner and partner.state_id else '') if partner else ''
|
||
dropoff_zip = getattr(order, 'delivery_zip', '') or ((partner.zip or '') if partner else '')
|
||
dropoff_country = (partner.country_id.name if partner and partner.country_id else '') if partner else ''
|
||
uber_status = order.uber_status or ''
|
||
uber_tracking_url = order.uber_tracking_url or ''
|
||
uber_delivery_id = order.uber_delivery_id or ''
|
||
uber_eta = str(order.uber_eta)[:16] if getattr(order, 'uber_eta', False) else ''
|
||
|
||
# ── Restaurant pickup ───────────────────────────────────────
|
||
company = request.env.company
|
||
pickup_name = company.name or 'Restaurant'
|
||
pickup_street = company.street or ''
|
||
pickup_city = company.city or ''
|
||
pickup_state = company.state_id.name if company.state_id else ''
|
||
pickup_zip = company.zip or ''
|
||
pickup_country = company.country_id.name if company.country_id else ''
|
||
pickup_phone = company.phone or ''
|
||
|
||
def _addr(*parts):
|
||
return ', '.join(p for p in parts if p)
|
||
|
||
pickup_addr = _addr(pickup_street, pickup_city, pickup_state, pickup_zip, pickup_country)
|
||
dropoff_addr = _addr(dropoff_street, dropoff_city, dropoff_state, dropoff_zip, dropoff_country)
|
||
|
||
# Google Maps directions URL (opens in browser, no key needed)
|
||
gmaps_url = (
|
||
'https://www.google.com/maps/dir/?api=1'
|
||
f'&origin={urllib.parse.quote(pickup_addr)}'
|
||
f'&destination={urllib.parse.quote(dropoff_addr)}'
|
||
'&travelmode=driving'
|
||
)
|
||
|
||
# OpenStreetMap static route embed (no API key)
|
||
osm_embed = (
|
||
'https://www.openstreetmap.org/export/embed.html'
|
||
f'?bbox={self._bbox(pickup_addr, dropoff_addr)}'
|
||
'&layer=mapnik'
|
||
'&marker=yes'
|
||
)
|
||
|
||
# Status info
|
||
status_map = {
|
||
'pending': ('#f59e0b', '⏳ Waiting for Driver', '⚠️'),
|
||
'pickup': ('#3b82f6', '🚗 Driver at Restaurant', 'ℹ️'),
|
||
'delivering': ('#06b6d4', '🛵 On the Way to You', '🔵'),
|
||
'delivered': ('#10b981', '✅ Order Delivered!', '✅'),
|
||
'cancelled': ('#ef4444', '❌ Delivery Cancelled', '❌'),
|
||
}
|
||
st_color, st_label, st_icon = status_map.get(uber_status, ('#64748b', '❓ Unknown', '❓'))
|
||
|
||
back_url = f'/odoo/{model_type}-orders/{order_id}' if model_type == 'sale' else f'/odoo/point-of-sale/orders/{order_id}'
|
||
|
||
html = self._render_page(
|
||
order_ref=order_ref, model_type=model_type, order_id=order_id,
|
||
pickup_name=pickup_name, pickup_street=pickup_street,
|
||
pickup_city=pickup_city, pickup_state=pickup_state,
|
||
pickup_zip=pickup_zip, pickup_country=pickup_country,
|
||
pickup_phone=pickup_phone, pickup_addr=pickup_addr,
|
||
customer_name=customer_name, customer_phone=customer_phone,
|
||
dropoff_street=dropoff_street, dropoff_city=dropoff_city,
|
||
dropoff_state=dropoff_state, dropoff_zip=dropoff_zip,
|
||
dropoff_country=dropoff_country, dropoff_addr=dropoff_addr,
|
||
uber_status=uber_status, st_color=st_color, st_label=st_label,
|
||
uber_delivery_id=uber_delivery_id, uber_eta=uber_eta,
|
||
uber_tracking_url=uber_tracking_url, gmaps_url=gmaps_url,
|
||
)
|
||
return Response(html, content_type='text/html; charset=utf-8')
|
||
|
||
def _bbox(self, addr1, addr2):
|
||
"""Placeholder bbox — Leaflet geocodes addresses on client side."""
|
||
return '-180,-90,180,90'
|
||
|
||
def _render_page(self, **v):
|
||
# Safe helper
|
||
def esc(s):
|
||
return str(s or '').replace('&', '&').replace('<', '<').replace('"', '"')
|
||
|
||
show_uber_btn = v['uber_tracking_url'] and 'sandbox' not in v['uber_tracking_url']
|
||
|
||
# Pre-build strings that contain backslashes (not allowed inside f-string expressions)
|
||
pickup_icon_html = '<div style="background:#06b6d4;width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:18px;box-shadow:0 2px 8px rgba(0,0,0,.3);border:3px solid #fff;">🏪</div>'
|
||
dropoff_icon_html = '<div style="background:#f59e0b;width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:18px;box-shadow:0 2px 8px rgba(0,0,0,.3);border:3px solid #fff;">🏠</div>'
|
||
|
||
eta_chip = ('<div class="info-chip"><div class="lbl">⏱ Estimated Delivery</div><div class="val">' + esc(v['uber_eta']) + '</div></div>') if v['uber_eta'] else ''
|
||
id_chip = ('<div class="info-chip"><div class="lbl">🔖 Uber Delivery ID</div><div class="val">' + esc(v['uber_delivery_id']) + '</div></div>') if v['uber_delivery_id'] else ''
|
||
pickup_phone_html = ('<div class="card-phone">📞 ' + esc(v['pickup_phone']) + '</div>') if v['pickup_phone'] else ''
|
||
dropoff_phone_html = ('<div class="card-phone">📞 ' + esc(v['customer_phone']) + '</div>') if v['customer_phone'] else ''
|
||
pickup_br = '<br/>' if v['pickup_street'] else ''
|
||
dropoff_br = '<br/>' if v['dropoff_street'] else ''
|
||
pickup_state_part = (', ' + esc(v['pickup_state'])) if v['pickup_state'] else ''
|
||
pickup_zip_part = (' – ' + esc(v['pickup_zip'])) if v['pickup_zip'] else ''
|
||
pickup_country_part = ('<br/>' + esc(v['pickup_country'])) if v['pickup_country'] else ''
|
||
dropoff_state_part = (', ' + esc(v['dropoff_state'])) if v['dropoff_state'] else ''
|
||
dropoff_zip_part = (' – ' + esc(v['dropoff_zip'])) if v['dropoff_zip'] else ''
|
||
dropoff_country_part = ('<br/>' + esc(v['dropoff_country'])) if v['dropoff_country'] else ''
|
||
uber_fab = ('<a href="' + esc(v['uber_tracking_url']) + '" target="_blank" class="fab fab-uber">⚡ Uber Live</a>') if show_uber_btn else ''
|
||
uber_btn = ('<a href="' + esc(v['uber_tracking_url']) + '" target="_blank" class="btn btn-dark">⚡ Live Track on Uber</a>') if show_uber_btn else ''
|
||
st_color = esc(v['st_color'])
|
||
|
||
return f'''<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||
<title>Uber Delivery Tracking – {esc(v["order_ref"])}</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
||
<style>
|
||
*{{box-sizing:border-box;margin:0;padding:0}}
|
||
body{{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f4f8;min-height:100vh}}
|
||
|
||
/* ── Header ───────────────────────── */
|
||
.hdr{{background:linear-gradient(135deg,#0f172a 0%,#1e3a5f 100%);
|
||
color:#fff;padding:14px 20px;display:flex;align-items:center;
|
||
gap:14px;box-shadow:0 4px 16px rgba(0,0,0,.3)}}
|
||
.hdr-logo{{background:#fff;color:#000;font-weight:900;font-size:22px;
|
||
letter-spacing:-1px;border-radius:8px;padding:4px 12px}}
|
||
.hdr-title{{font-size:13px;opacity:.7;text-transform:uppercase;letter-spacing:.8px}}
|
||
.hdr-ref{{margin-left:auto;background:rgba(255,255,255,.15);
|
||
border-radius:20px;padding:5px 14px;font-size:13px;font-weight:700}}
|
||
|
||
/* ── Status banner ─────────────────── */
|
||
.status-bar{{margin:14px 16px 0;border-radius:14px;padding:14px 18px;
|
||
display:flex;align-items:center;gap:10px;
|
||
border:2px solid {st_color}33;
|
||
background:{st_color}18;
|
||
color:{st_color};font-weight:700;font-size:15px}}
|
||
.dot{{width:10px;height:10px;border-radius:50%;background:{st_color};
|
||
animation:pulse 1.5s infinite;flex-shrink:0}}
|
||
@keyframes pulse{{0%,100%{{opacity:1;transform:scale(1)}}50%{{opacity:.5;transform:scale(1.4)}}}}
|
||
.status-bar .rhs{{margin-left:auto;font-size:11px;font-weight:400;opacity:.7}}
|
||
|
||
/* ── Info cards ────────────────────── */
|
||
.cards{{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 16px}}
|
||
@media(max-width:580px){{.cards{{grid-template-columns:1fr}}}}
|
||
.card{{background:#fff;border-radius:16px;padding:16px 18px;
|
||
box-shadow:0 2px 10px rgba(0,0,0,.07)}}
|
||
.card.pickup{{border-top:4px solid #06b6d4}}
|
||
.card.dropoff{{border-top:4px solid #f59e0b}}
|
||
.card-badge{{display:inline-block;font-size:10px;font-weight:700;
|
||
letter-spacing:.8px;text-transform:uppercase;
|
||
border-radius:20px;padding:3px 10px;margin-bottom:8px}}
|
||
.badge-pickup{{background:#cffafe;color:#0e7490}}
|
||
.badge-dropoff{{background:#fef3c7;color:#92400e}}
|
||
.card-name{{font-size:15px;font-weight:700;color:#0f172a;margin-bottom:4px}}
|
||
.card-addr{{font-size:13px;color:#475569;line-height:1.6}}
|
||
.card-phone{{font-size:12px;color:#64748b;margin-top:6px}}
|
||
|
||
/* ── Map ───────────────────────────── */
|
||
.map-wrap{{margin:12px 16px;border-radius:16px;overflow:hidden;
|
||
box-shadow:0 4px 20px rgba(0,0,0,.14);position:relative}}
|
||
#map{{width:100%;height:400px}}
|
||
.map-fab{{position:absolute;top:10px;right:10px;z-index:999;
|
||
display:flex;flex-direction:column;gap:6px}}
|
||
.fab{{background:#fff;border:none;border-radius:10px;
|
||
padding:8px 12px;font-size:12px;font-weight:700;
|
||
box-shadow:0 2px 8px rgba(0,0,0,.18);cursor:pointer;
|
||
text-decoration:none;color:#0f172a;display:flex;
|
||
align-items:center;gap:6px;white-space:nowrap}}
|
||
.fab:hover{{box-shadow:0 4px 14px rgba(0,0,0,.22);transform:translateY(-1px)}}
|
||
.fab-uber{{background:#000;color:#fff}}
|
||
|
||
/* ── ETA / ID ──────────────────────── */
|
||
.info-row{{display:flex;gap:12px;margin:0 16px 12px;flex-wrap:wrap}}
|
||
.info-chip{{background:#fff;border-radius:12px;padding:10px 16px;
|
||
flex:1;min-width:140px;box-shadow:0 1px 6px rgba(0,0,0,.07)}}
|
||
.info-chip .lbl{{font-size:10px;font-weight:700;letter-spacing:.7px;
|
||
text-transform:uppercase;color:#64748b;margin-bottom:3px}}
|
||
.info-chip .val{{font-size:14px;font-weight:700;color:#0f172a;word-break:break-all}}
|
||
|
||
/* ── Action bar ────────────────────── */
|
||
.actions{{display:flex;gap:10px;margin:0 16px 20px;flex-wrap:wrap}}
|
||
.btn{{flex:1;min-width:130px;padding:13px 18px;border-radius:14px;
|
||
border:none;cursor:pointer;font-size:13px;font-weight:700;
|
||
display:flex;align-items:center;justify-content:center;gap:8px;
|
||
text-decoration:none;transition:transform .15s,box-shadow .15s}}
|
||
.btn:hover{{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.15)}}
|
||
.btn-gmaps{{background:linear-gradient(135deg,#4285f4,#0d47a1);color:#fff}}
|
||
.btn-dark{{background:#111;color:#fff}}
|
||
.btn-back{{background:#f1f5f9;color:#0f172a;border:2px solid #e2e8f0}}
|
||
|
||
.refresh-note{{text-align:center;font-size:11px;color:#94a3b8;margin-bottom:16px}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- HEADER -->
|
||
<div class="hdr">
|
||
<div class="hdr-logo">Uber</div>
|
||
<div>
|
||
<div class="hdr-title">Delivery Tracking</div>
|
||
<div style="font-size:16px;font-weight:700;">Live Order Status</div>
|
||
</div>
|
||
<div class="hdr-ref"># {esc(v["order_ref"])}</div>
|
||
</div>
|
||
|
||
<!-- STATUS -->
|
||
<div class="status-bar" id="status_bar">
|
||
<div class="dot" id="status_dot"></div>
|
||
<span id="status_label">{esc(v["st_label"])}</span>
|
||
<span class="rhs">🔄 Auto-refreshes every 30s</span>
|
||
</div>
|
||
|
||
<!-- ETA + DELIVERY ID -->
|
||
<div class="info-row">
|
||
{eta_chip}
|
||
{id_chip}
|
||
</div>
|
||
|
||
<!-- LOCATION CARDS -->
|
||
<div class="cards">
|
||
<div class="card pickup">
|
||
<div class="card-badge badge-pickup">📍 Pickup Location</div>
|
||
<div class="card-name">{esc(v["pickup_name"])}</div>
|
||
<div class="card-addr">
|
||
{esc(v['pickup_street'])}{pickup_br}
|
||
{esc(v['pickup_city'])}{pickup_state_part}{pickup_zip_part}
|
||
{pickup_country_part}
|
||
</div>
|
||
{pickup_phone_html}
|
||
</div>
|
||
<div class="card dropoff">
|
||
<div class="card-badge badge-dropoff">🎯 Delivering To</div>
|
||
<div class="card-name">{esc(v['customer_name'])}</div>
|
||
<div class="card-addr">
|
||
{esc(v['dropoff_street'])}{dropoff_br}
|
||
{esc(v['dropoff_city'])}{dropoff_state_part}{dropoff_zip_part}
|
||
{dropoff_country_part}
|
||
</div>
|
||
{dropoff_phone_html}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAP -->
|
||
<div class="map-wrap">
|
||
<div id="map"></div>
|
||
<div class="map-fab">
|
||
<a href="{esc(v["gmaps_url"])}" target="_blank" class="fab">🗺️ Google Maps</a>
|
||
{uber_fab}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ACTION BUTTONS -->
|
||
<div class="actions">
|
||
<a href="{esc(v['gmaps_url'])}" target="_blank" class="btn btn-gmaps">🗺 Google Maps Directions</a>
|
||
{uber_btn}
|
||
<a href="javascript:history.back()" class="btn btn-back">← Back to Order</a>
|
||
</div>
|
||
|
||
<div class="refresh-note">Status auto-refreshes every 30 seconds</div>
|
||
|
||
<!-- Leaflet + routing -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
(function(){{
|
||
// ── Geocode via Nominatim (OSM free) ────────────────────────────
|
||
var pickupAddr = "{esc(v["pickup_addr"])}";
|
||
var dropoffAddr = "{esc(v["dropoff_addr"])}";
|
||
var orderId = {v["order_id"]};
|
||
var modelType = "{esc(v["model_type"])}";
|
||
|
||
var map = L.map("map", {{zoomControl:true}}).setView([20, 0], 2);
|
||
|
||
L.tileLayer("https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png", {{
|
||
attribution: "© OpenStreetMap contributors",
|
||
maxZoom: 19
|
||
}}).addTo(map);
|
||
|
||
// Custom icons
|
||
var pickupIcon = L.divIcon({{
|
||
html: '{pickup_icon_html}',
|
||
iconSize:[36,36], iconAnchor:[18,18], popupAnchor:[0,-18], className:''
|
||
}});
|
||
var dropoffIcon = L.divIcon({{
|
||
html: '{dropoff_icon_html}',
|
||
iconSize:[36,36], iconAnchor:[18,18], popupAnchor:[0,-18], className:''
|
||
}});
|
||
|
||
var pickupMarker = null;
|
||
var dropoffMarker = null;
|
||
var routeLine = null;
|
||
|
||
function geocode(addr, callback){{
|
||
var url = "https://nominatim.openstreetmap.org/search?format=json&q=" + encodeURIComponent(addr) + "&limit=1";
|
||
fetch(url, {{headers:{{"Accept-Language":"en", "User-Agent":"Dine360/1.0"}}}} )
|
||
.then(function(r){{return r.json();}})
|
||
.then(function(data){{
|
||
if(data && data.length > 0){{
|
||
callback(parseFloat(data[0].lat), parseFloat(data[0].lon), data[0].display_name);
|
||
}} else {{
|
||
callback(null, null, addr);
|
||
}}
|
||
}}).catch(function(){{callback(null, null, addr);}});
|
||
}}
|
||
|
||
function drawRoute(lat1, lon1, lat2, lon2){{
|
||
// OSRM free routing
|
||
var url = "https://router.project-osrm.org/route/v1/driving/"
|
||
+ lon1 + "," + lat1 + ";" + lon2 + "," + lat2
|
||
+ "?overview=simplified&geometries=geojson";
|
||
fetch(url)
|
||
.then(function(r){{return r.json();}})
|
||
.then(function(data){{
|
||
if(data.routes && data.routes.length > 0){{
|
||
if(routeLine) map.removeLayer(routeLine);
|
||
routeLine = L.geoJSON(data.routes[0].geometry, {{
|
||
style:{{color:"#3b82f6", weight:5, opacity:.85,
|
||
dashArray:null, lineCap:"round", lineJoin:"round"}}
|
||
}}).addTo(map);
|
||
}}
|
||
}}).catch(function(){{}});
|
||
}}
|
||
|
||
// Geocode pickup first, then dropoff
|
||
geocode(pickupAddr, function(plat, plon, pname){{
|
||
if(plat !== null){{
|
||
pickupMarker = L.marker([plat, plon], {{icon:pickupIcon}})
|
||
.addTo(map)
|
||
.bindPopup("<b>📍 Pickup: {esc(v["pickup_name"])}</b><br/>" + (pname||pickupAddr));
|
||
|
||
geocode(dropoffAddr, function(dlat, dlon, dname){{
|
||
if(dlat !== null){{
|
||
dropoffMarker = L.marker([dlat, dlon], {{icon:dropoffIcon}})
|
||
.addTo(map)
|
||
.bindPopup("<b>🏠 Delivery: {esc(v["customer_name"])}</b><br/>" + (dname||dropoffAddr));
|
||
|
||
// Fit map to both markers
|
||
var bounds = L.latLngBounds([[plat,plon],[dlat,dlon]]);
|
||
map.fitBounds(bounds.pad(0.2));
|
||
|
||
// Draw driving route
|
||
drawRoute(plat, plon, dlat, dlon);
|
||
}} else {{
|
||
map.setView([plat, plon], 13);
|
||
pickupMarker.openPopup();
|
||
}}
|
||
}});
|
||
}} else {{
|
||
// Try just dropoff
|
||
geocode(dropoffAddr, function(dlat, dlon, dname){{
|
||
if(dlat !== null){{
|
||
dropoffMarker = L.marker([dlat, dlon], {{icon:dropoffIcon}}).addTo(map)
|
||
.bindPopup("<b>🏠 Delivery: {esc(v["customer_name"])}</b><br/>" + (dname||dropoffAddr));
|
||
map.setView([dlat, dlon], 13);
|
||
dropoffMarker.openPopup();
|
||
}} else {{
|
||
// Default world view
|
||
map.setView([20, 80], 4);
|
||
}}
|
||
}});
|
||
}}
|
||
}});
|
||
|
||
// ── Auto-refresh status every 30s ──────────────────────────────
|
||
var statusColors = {{
|
||
pending: ["#f59e0b", "⏳ Waiting for Driver"],
|
||
pickup: ["#3b82f6", "🚗 Driver at Restaurant"],
|
||
delivering: ["#06b6d4", "🛵 On the Way to You"],
|
||
delivered: ["#10b981", "✅ Order Delivered!"],
|
||
cancelled: ["#ef4444", "❌ Delivery Cancelled"],
|
||
}};
|
||
|
||
function refreshStatus(){{
|
||
fetch("/web/dataset/call_kw", {{
|
||
method:"POST",
|
||
headers:{{"Content-Type":"application/json"}},
|
||
body:JSON.stringify({{
|
||
jsonrpc:"2.0",method:"call",id:1,
|
||
params:{{
|
||
model: modelType + ".order",
|
||
method:"read",
|
||
args:[[orderId],["uber_status","uber_tracking_url","uber_eta"]],
|
||
kwargs:{{}}
|
||
}}
|
||
}})
|
||
}}).then(function(r){{return r.json();}}).then(function(d){{
|
||
if(!d.result || !d.result[0]) return;
|
||
var rec = d.result[0];
|
||
var status = rec.uber_status || "";
|
||
var info = statusColors[status] || ["#64748b", "❓ Unknown"];
|
||
var bar = document.getElementById("status_bar");
|
||
var dot = document.getElementById("status_dot");
|
||
var lbl = document.getElementById("status_label");
|
||
if(bar){{
|
||
bar.style.borderColor = info[0] + "33";
|
||
bar.style.background = info[0] + "18";
|
||
bar.style.color = info[0];
|
||
}}
|
||
if(dot) dot.style.background = info[0];
|
||
if(lbl) lbl.textContent = info[1];
|
||
if(["delivered","cancelled"].includes(status)) clearInterval(timer);
|
||
}}).catch(function(){{}});
|
||
}}
|
||
var timer = setInterval(refreshStatus, 30000);
|
||
}})();
|
||
</script>
|
||
</body>
|
||
</html>'''
|