-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprogress.html
More file actions
190 lines (170 loc) · 8.79 KB
/
progress.html
File metadata and controls
190 lines (170 loc) · 8.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Shipment Progress - Watraco</title>
<style>
/* Page layout + visual polish */
body{font-family:Segoe UI,Arial,Helvetica,sans-serif;background:linear-gradient(135deg,#eef2f7,#dfeffb);margin:0;padding:28px;color:#233047}
.container{max-width:1100px;margin:20px auto;background:#fff;padding:20px;border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,0.08)}
.summary{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:1px solid #f1f3f6;padding-bottom:12px}
.summary .left h2{margin:0;font-size:20px}
.summary .right{text-align:right;color:#555}
.timeline-wrap{position:relative;margin-top:20px;overflow:auto;padding:18px 8px}
.timeline{display:flex;align-items:center;gap:26px;padding:8px 12px;min-width:700px}
.timeline-step{min-width:160px;display:flex;flex-direction:column;align-items:center;position:relative;padding:6px;transition:transform .35s,opacity .35s}
.timeline-step .icon{width:72px;height:72px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;box-shadow:0 10px 30px rgba(0,0,0,0.12)}
.timeline-step .label{margin-top:10px;text-align:center;font-weight:700;color:#22303f}
.timeline-step.pending .icon{background:linear-gradient(135deg,#e74c3c,#c0392b);filter:grayscale(15%) }
.timeline-step.completed .icon{background:linear-gradient(135deg,#27ae60,#2ecc71);box-shadow:0 12px 28px rgba(46,204,113,0.18)}
/* connector bar (under the icons) */
.timeline-step::after{content:'';position:absolute;right:-18px;top:36px;width:36px;height:8px;border-radius:6px;background:#e6e9ee;z-index:1;transition:background .4s}
.timeline-step:last-child::after{display:none}
.timeline-step.completed::after{background:linear-gradient(90deg,#27ae60,#27ae60)}
/* progress fill bar (below everything, wide) */
.progress-line{position:absolute;left:0;right:0;top:50%;height:8px;background:#e6e9ee;border-radius:8px;z-index:0}
.progress-fill{position:absolute;left:0;top:50%;height:8px;border-radius:8px;background:linear-gradient(90deg,#4caf50,#2e7d32);width:0%;box-shadow:0 6px 22px rgba(46,204,113,0.12);transition:width .6s ease}
.eta{font-size:13px;color:#4b5563}
@media(max-width:720px){ .timeline{min-width:520px} .timeline-step{min-width:120px} .timeline-step .icon{width:56px;height:56px;font-size:20px} }
svg { width: 32px; height: 32px; fill: white; }
</style>
</head>
<body>
<div class="container">
<div class="summary">
<div class="left">
<h2>Shipment Progress</h2>
<div id="meta">Loading shipment...</div>
</div>
<div class="right">
<div id="eta" class="eta">ETA: —</div>
<div style="margin-top:8px"><a href="track.html">← Back to Track</a></div>
</div>
</div>
<div class="timeline-wrap" id="timelineWrap">
<div class="progress-line" aria-hidden="true"></div>
<div class="progress-fill" id="progressFill" aria-hidden="true"></div>
<div id="timeline" class="timeline" role="list" aria-live="polite"></div>
</div>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js";
import { getFirestore, doc, onSnapshot } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-firestore.js";
const firebaseConfig = {
apiKey: "AIzaSyCmHuvaDlbDH1u6SNIN85kJsiHE9Zgle3E",
authDomain: "watraco-1a955.firebaseapp.com",
projectId: "watraco-1a955",
storageBucket: "watraco-1a955.firebasestorage.app",
messagingSenderId: "67246086407",
appId: "1:67246086407:web:4d9d80ff51349def90cda2",
measurementId: "G-SECBFBWMB2"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// SVG icons for stages
const icons = {
box: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21 16V8a1 1 0 0 0-.553-.894l-8-4a1 1 0 0 0-.894 0l-8 4A1 1 0 0 0 3 8v8a1 1 0 0 0 .553.894l8 4a1 1 0 0 0 .894 0l8-4A1 1 0 0 0 21 16zM12 4.236 18.764 8 12 11.764 5.236 8 12 4.236z"/></svg>`,
planeTakeoff: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2.5 19h19v2h-19zM2 15.5l8 1.5 7-5.5 5 1 1-2-6-1-7 5.5-7-1.5z"/></svg>`,
planeAir: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 16l20-5-20-5v4l15 1-15 1z"/></svg>`,
truck: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h13v9h-13zM16 9h4l2 3v3h-6zM5 18a2 2 0 1 0 4 0h6a2 2 0 1 0 4 0h2v-2h-18v2z"/></svg>`,
flag: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 4h1v16h-1zM6 4h12l-3 4 3 4h-12z"/></svg>`
};
const STAGES = [
{ key:'leaving', label:'Leaving Origin', icon: icons.box },
{ key:'airport', label:'Origin Airport', icon: icons.planeTakeoff },
{ key:'air', label:'In Air', icon: icons.planeAir },
{ key:'transit', label:'In Transit (Customs/Warehouse)', icon: icons.truck },
{ key:'arrived', label:'Arrived Destination', icon: icons.flag }
];
const params = new URLSearchParams(window.location.search);
let trackingId = params.get('id') || localStorage.getItem('currentTracking');
if (!trackingId) {
alert('No tracking ID found. Please go to Track page and enter the tracking ID.');
location.href = 'track.html';
throw new Error('No tracking ID provided');
}
const timelineEl = document.getElementById('timeline');
const progressFillEl = document.getElementById('progressFill');
const metaEl = document.getElementById('meta');
const etaEl = document.getElementById('eta');
function renderSkeleton(origin, destination) {
timelineEl.innerHTML = '';
STAGES.forEach((s, i) => {
const div = document.createElement('div');
div.className = 'timeline-step pending';
let label = s.label;
if (i === 1 && origin) label += ` — ${origin}`;
if (i === STAGES.length - 1 && destination) label += ` — ${destination}`;
div.innerHTML = `<div class="icon">${s.icon}</div><div class="label">${label}</div>`;
timelineEl.appendChild(div);
});
}
function updateUIFraction(fraction) {
const clamped = Math.max(0, Math.min(1, fraction));
progressFillEl.style.width = (clamped * 100) + '%';
const completedIdx = Math.floor(clamped * (STAGES.length - 1));
const steps = timelineEl.querySelectorAll('.timeline-step');
steps.forEach((step, i) => {
if (i <= completedIdx) {
step.classList.remove('pending');
step.classList.add('completed');
} else {
step.classList.remove('completed');
step.classList.add('pending');
}
});
}
function humanRemaining(ms) {
if (ms <= 0) return 'Delivered';
const days = Math.floor(ms / (24*60*60*1000));
const hours = Math.floor((ms % (24*60*60*1000)) / (60*60*1000));
const mins = Math.floor((ms % (60*60*1000)) / (60*1000));
return `${days}d ${hours}h ${mins}m`;
}
const docRef = doc(db, 'shipments', trackingId);
let lastData = null;
let localTimer = null;
onSnapshot(docRef, snap => {
if (!snap.exists()) {
metaEl.innerHTML = '<em>Shipment not found</em>';
timelineEl.innerHTML = '';
etaEl.textContent = 'ETA: —';
return;
}
const data = snap.data();
lastData = data;
const startMs = data.startTimeMs || (data.startTime && data.startTime.toMillis && data.startTime.toMillis()) || null;
const durationMs = Number(data.durationMs || 0);
renderSkeleton(data.origin || '', data.destination || '');
if (startMs && durationMs > 0) {
const endMs = startMs + durationMs;
const remainingMs = Math.max(0, endMs - Date.now());
etaEl.textContent = `ETA: ${new Date(endMs).toLocaleString()} — ${humanRemaining(remainingMs)} left`;
} else {
etaEl.textContent = 'ETA: Pending start';
}
if (localTimer) clearInterval(localTimer);
function tick() {
if (!lastData) return;
const sMs = lastData.startTimeMs || (lastData.startTime && lastData.startTime.toMillis && lastData.startTime.toMillis());
const dMs = Number(lastData.durationMs || 0);
if (!sMs || dMs <= 0) {
updateUIFraction(0);
etaEl.textContent = 'ETA: Pending start';
return;
}
const now = Date.now();
const fraction = (now - sMs) / dMs;
updateUIFraction(fraction);
const endMs = sMs + dMs;
const remaining = Math.max(0, endMs - now);
etaEl.textContent = `ETA: ${new Date(endMs).toLocaleString()} — ${humanRemaining(remaining)} left`;
}
tick();
localTimer = setInterval(tick, 1000);
});
localStorage.setItem('currentTracking', trackingId);
</script>
</body>
</html>