338 lines
15 KiB
XML
338 lines
15 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<odoo>
|
||
<data>
|
||
|
||
<template id="uber_tracking_page" name="Uber Delivery Tracking">
|
||
<t t-call="web.layout">
|
||
<t t-set="head_tracking">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>Uber Delivery Tracking – <t t-esc="order_ref"/></title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
background: #f0f2f5;
|
||
min-height: 100vh;
|
||
}
|
||
/* ── Top Bar ── */
|
||
.track-header {
|
||
background: linear-gradient(135deg, #06b6d4 0%, #0369a1 100%);
|
||
color: #fff;
|
||
padding: 18px 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
box-shadow: 0 3px 12px rgba(0,0,0,0.2);
|
||
}
|
||
.track-header .uber-logo {
|
||
font-size: 26px; font-weight: 900; letter-spacing: -1px;
|
||
background: #fff;
|
||
color: #000;
|
||
border-radius: 8px;
|
||
padding: 4px 12px;
|
||
}
|
||
.track-header h1 { font-size: 16px; font-weight: 600; opacity: .9; }
|
||
.track-header .order-ref {
|
||
margin-left: auto;
|
||
background: rgba(255,255,255,0.2);
|
||
border-radius: 20px;
|
||
padding: 4px 14px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
letter-spacing: .5px;
|
||
}
|
||
/* ── Status Banner ── */
|
||
.status-banner {
|
||
margin: 16px 20px 0;
|
||
border-radius: 14px;
|
||
padding: 14px 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-weight: 700;
|
||
font-size: 15px;
|
||
}
|
||
.status-warning { background: #fef3c7; color: #92400e; border: 2px solid #fbbf24; }
|
||
.status-info { background: #dbeafe; color: #1e40af; border: 2px solid #93c5fd; }
|
||
.status-primary { background: #e0f2fe; color: #0369a1; border: 2px solid #38bdf8; }
|
||
.status-success { background: #d1fae5; color: #065f46; border: 2px solid #34d399; }
|
||
.status-danger { background: #fee2e2; color: #991b1b; border: 2px solid #fca5a5; }
|
||
.status-secondary{ background: #f1f5f9; color: #475569; border: 2px solid #94a3b8; }
|
||
.pulse-dot {
|
||
width: 12px; height: 12px; border-radius: 50%;
|
||
background: currentColor; flex-shrink: 0;
|
||
animation: pulse 1.5s infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%,100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: .5; transform: scale(1.3); }
|
||
}
|
||
/* ── Card Grid ── */
|
||
.cards-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 14px;
|
||
margin: 14px 20px;
|
||
}
|
||
@media(max-width: 640px){ .cards-row{ grid-template-columns: 1fr; } }
|
||
.location-card {
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
padding: 18px 20px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,.07);
|
||
border-left: 5px solid #06b6d4;
|
||
position: relative;
|
||
}
|
||
.location-card.dropoff { border-left-color: #f59e0b; }
|
||
.card-icon {
|
||
width: 40px; height: 40px; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 18px; margin-bottom: 10px;
|
||
}
|
||
.card-icon.pickup-icon { background: #cffafe; }
|
||
.card-icon.dropoff-icon { background: #fef3c7; }
|
||
.card-label {
|
||
font-size: 11px; font-weight: 700; text-transform: uppercase;
|
||
letter-spacing: 1px; color: #64748b; margin-bottom: 4px;
|
||
}
|
||
.card-title { font-size: 15px; font-weight: 700; color: #0f172a; margin-bottom: 6px; }
|
||
.card-addr { font-size: 13px; color: #475569; line-height: 1.5; }
|
||
.card-phone { font-size: 12px; color: #64748b; margin-top: 6px; }
|
||
/* ── Map ── */
|
||
.map-section {
|
||
margin: 0 20px 14px;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 16px rgba(0,0,0,.12);
|
||
position: relative;
|
||
}
|
||
.map-section iframe {
|
||
width: 100%; height: 380px; border: 0; display: block;
|
||
}
|
||
.map-overlay-btns {
|
||
position: absolute; top: 12px; right: 12px;
|
||
display: flex; flex-direction: column; gap: 8px;
|
||
}
|
||
.map-btn {
|
||
background: #fff;
|
||
border: none; border-radius: 10px;
|
||
padding: 8px 14px;
|
||
font-size: 13px; font-weight: 600;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
||
cursor: pointer; text-decoration: none;
|
||
color: #0f172a; display: flex; align-items: center; gap: 6px;
|
||
transition: transform .15s, box-shadow .15s;
|
||
}
|
||
.map-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 14px rgba(0,0,0,.2); color: #0f172a; }
|
||
.map-btn.uber-btn { background: #000; color: #fff; }
|
||
/* ── Action Buttons ── */
|
||
.action-bar {
|
||
display: flex; gap: 12px; margin: 0 20px 20px; flex-wrap: wrap;
|
||
}
|
||
.btn-full {
|
||
flex: 1; min-width: 140px;
|
||
padding: 14px 20px;
|
||
border-radius: 14px;
|
||
border: none; cursor: pointer;
|
||
font-size: 14px; font-weight: 700;
|
||
display: flex; align-items: center; justify-content: center;
|
||
gap: 8px; text-decoration: none;
|
||
transition: transform .15s, box-shadow .15s;
|
||
}
|
||
.btn-full:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,.15); }
|
||
.btn-gmaps { background: linear-gradient(135deg,#4285f4,#0f52c0); color:#fff; }
|
||
.btn-uber { background: #000; color: #fff; }
|
||
.btn-back { background: #f1f5f9; color: #0f172a; border: 2px solid #e2e8f0; }
|
||
/* ── ETA Card ── */
|
||
.eta-card {
|
||
background: linear-gradient(135deg, #0f172a, #1e293b);
|
||
color: #fff;
|
||
border-radius: 16px;
|
||
padding: 16px 20px;
|
||
margin: 0 20px 14px;
|
||
display: flex; align-items: center; gap: 14px;
|
||
}
|
||
.eta-icon { font-size: 28px; }
|
||
.eta-label { font-size: 12px; opacity: .7; margin-bottom: 2px; }
|
||
.eta-value { font-size: 17px; font-weight: 700; }
|
||
/* ── Delivery ID ── */
|
||
.delivery-id {
|
||
margin: 0 20px 14px;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 12px 18px;
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,.06);
|
||
display: flex; gap: 8px; align-items: center;
|
||
}
|
||
.delivery-id strong { color: #0f172a; }
|
||
/* ── Auto-refresh badge ── */
|
||
.refresh-badge {
|
||
text-align: center; font-size: 12px; color: #94a3b8;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</t>
|
||
|
||
<t t-set="body_classname">o_web_client</t>
|
||
|
||
<!-- ═══ HEADER ═══════════════════════════════════════════════════ -->
|
||
<div class="track-header">
|
||
<div class="uber-logo">Uber</div>
|
||
<div>
|
||
<div style="font-size:11px;opacity:.7;text-transform:uppercase;letter-spacing:1px;">Delivery Tracking</div>
|
||
<h1>Live Order Status</h1>
|
||
</div>
|
||
<div class="order-ref"># <t t-esc="order_ref"/></div>
|
||
</div>
|
||
|
||
<!-- ═══ STATUS BANNER ════════════════════════════════════════════ -->
|
||
<div t-attf-class="status-banner status-#{status_color}" id="status_banner">
|
||
<div class="pulse-dot" t-if="uber_status not in ['delivered','cancelled']"></div>
|
||
<span t-esc="status_label"/>
|
||
<span style="margin-left:auto;font-size:12px;font-weight:400;opacity:.7;">Auto-refreshes every 30s</span>
|
||
</div>
|
||
|
||
<!-- ═══ ETA ══════════════════════════════════════════════════════ -->
|
||
<div class="eta-card" t-if="uber_eta">
|
||
<div class="eta-icon">⏱️</div>
|
||
<div>
|
||
<div class="eta-label">Estimated Delivery</div>
|
||
<div class="eta-value" t-esc="uber_eta"/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ DELIVERY ID ══════════════════════════════════════════════ -->
|
||
<div class="delivery-id" t-if="uber_delivery_id">
|
||
🔖 <strong>Uber Delivery ID:</strong> <t t-esc="uber_delivery_id"/>
|
||
</div>
|
||
|
||
<!-- ═══ LOCATION CARDS ═══════════════════════════════════════════ -->
|
||
<div class="cards-row">
|
||
<!-- Pickup: Restaurant -->
|
||
<div class="location-card">
|
||
<div class="card-icon pickup-icon">🏪</div>
|
||
<div class="card-label">📍 Pickup Location</div>
|
||
<div class="card-title" t-esc="pickup_name"/>
|
||
<div class="card-addr">
|
||
<t t-if="pickup_street"><t t-esc="pickup_street"/><br/></t>
|
||
<t t-if="pickup_city"><t t-esc="pickup_city"/></t>
|
||
<t t-if="pickup_state">, <t t-esc="pickup_state"/></t>
|
||
<t t-if="pickup_zip"> – <t t-esc="pickup_zip"/></t>
|
||
<t t-if="pickup_country"><br/><t t-esc="pickup_country"/></t>
|
||
</div>
|
||
<div class="card-phone" t-if="pickup_phone">📞 <t t-esc="pickup_phone"/></div>
|
||
</div>
|
||
|
||
<!-- Dropoff: Customer -->
|
||
<div class="location-card dropoff">
|
||
<div class="card-icon dropoff-icon">🏠</div>
|
||
<div class="card-label">🎯 Delivery To</div>
|
||
<div class="card-title" t-esc="customer_name"/>
|
||
<div class="card-addr">
|
||
<t t-if="dropoff_street"><t t-esc="dropoff_street"/><br/></t>
|
||
<t t-if="dropoff_city"><t t-esc="dropoff_city"/></t>
|
||
<t t-if="dropoff_state">, <t t-esc="dropoff_state"/></t>
|
||
<t t-if="dropoff_zip"> – <t t-esc="dropoff_zip"/></t>
|
||
<t t-if="dropoff_country"><br/><t t-esc="dropoff_country"/></t>
|
||
</div>
|
||
<div class="card-phone" t-if="customer_phone">📞 <t t-esc="customer_phone"/></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ MAP ══════════════════════════════════════════════════════ -->
|
||
<div class="map-section">
|
||
<iframe
|
||
t-att-src="maps_embed_url"
|
||
allowfullscreen="1"
|
||
loading="lazy"
|
||
referrerpolicy="no-referrer-when-downgrade"
|
||
title="Delivery Route Map"/>
|
||
|
||
<!-- Map overlay buttons -->
|
||
<div class="map-overlay-btns">
|
||
<a t-att-href="maps_directions_url" target="_blank" class="map-btn">
|
||
🗺️ Open Full Map
|
||
</a>
|
||
<a t-if="uber_tracking_url and 'sandbox' not in uber_tracking_url"
|
||
t-att-href="uber_tracking_url" target="_blank" class="map-btn uber-btn">
|
||
⚡ Uber Live Track
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ ACTION BUTTONS ════════════════════════════════════════════ -->
|
||
<div class="action-bar">
|
||
<a t-att-href="maps_directions_url" target="_blank" class="btn-full btn-gmaps">
|
||
🗺️ Google Maps Directions
|
||
</a>
|
||
<a t-if="uber_tracking_url and 'sandbox' not in uber_tracking_url"
|
||
t-att-href="uber_tracking_url" target="_blank" class="btn-full btn-uber">
|
||
⚡ Track on Uber
|
||
</a>
|
||
<a t-att-href="'/web#id=%s&model=%s.order&view_type=form' % (order_id, model_type)"
|
||
class="btn-full btn-back">
|
||
← Back to Order
|
||
</a>
|
||
</div>
|
||
|
||
<!-- ═══ REFRESH BADGE ════════════════════════════════════════════ -->
|
||
<div class="refresh-badge">Status auto-refreshes every 30 seconds</div>
|
||
|
||
<!-- ═══ AUTO-REFRESH SCRIPT ══════════════════════════════════════ -->
|
||
<script>
|
||
(function(){
|
||
var orderId = <t t-esc="order_id"/>;
|
||
var modelType = "<t t-esc="model_type"/>";
|
||
var statusColors = {
|
||
pending: ["⏳ Waiting for Driver", "warning"],
|
||
pickup: ["🚗 Driver at Restaurant", "info"],
|
||
delivering: ["🛵 On the Way", "primary"],
|
||
delivered: ["✅ Delivered", "success"],
|
||
cancelled: ["❌ Cancelled", "danger"],
|
||
};
|
||
|
||
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(r => r.json())
|
||
.then(data => {
|
||
if(!data.result || !data.result[0]) return;
|
||
var rec = data.result[0];
|
||
var status = rec.uber_status || "";
|
||
var info = statusColors[status] || ["Unknown","secondary"];
|
||
var banner = document.getElementById("status_banner");
|
||
if(banner){
|
||
banner.className = "status-banner status-" + info[1];
|
||
banner.querySelector("span").textContent = info[0];
|
||
}
|
||
if(["delivered","cancelled"].includes(status)){
|
||
clearInterval(timer);
|
||
}
|
||
})
|
||
.catch(function(){});
|
||
}
|
||
|
||
// Refresh every 30 seconds
|
||
var timer = setInterval(refreshStatus, 30000);
|
||
})();
|
||
</script>
|
||
|
||
</t>
|
||
</template>
|
||
|
||
</data>
|
||
</odoo>
|