219 lines
13 KiB
HTML
219 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Doormile — Task Board</title>
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0;font-family:Inter,system-ui,sans-serif}
|
||
body{background:#f8fafc;padding:20px}
|
||
h1{font-size:20px;font-weight:700;color:#0f172a;margin-bottom:4px}
|
||
.subtitle{font-size:13px;color:#64748b;margin-bottom:20px}
|
||
.summary{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap}
|
||
.stat{background:white;border:1px solid #e2e8f0;border-radius:10px;padding:10px 16px;text-align:center;min-width:70px}
|
||
.stat-num{font-size:22px;font-weight:700}
|
||
.stat-label{font-size:11px;color:#64748b;margin-top:2px}
|
||
.phase{margin-bottom:18px;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;background:white}
|
||
.phase-header{padding:14px 18px;background:#f1f5f9;display:flex;align-items:center;justify-content:space-between}
|
||
.phase-header h2{font-size:15px;font-weight:600;color:#0f172a}
|
||
.badge{padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600}
|
||
.badge-blue{background:#dbeafe;color:#1d4ed8}
|
||
.badge-green{background:#dcfce7;color:#15803d}
|
||
.badge-orange{background:#ffedd5;color:#c2410c}
|
||
.badge-purple{background:#ede9fe;color:#6d28d9}
|
||
.badge-gray{background:#f1f5f9;color:#475569}
|
||
.progress-bar{height:3px;background:#e2e8f0}
|
||
.progress-fill{height:100%;background:#22c55e;transition:width .3s}
|
||
.task{display:flex;gap:12px;padding:11px 18px;border-top:1px solid #f1f5f9;align-items:flex-start;cursor:pointer;transition:background .1s}
|
||
.task:hover{background:#f8fafc}
|
||
.task input[type=checkbox]{width:17px;height:17px;margin-top:2px;cursor:pointer;accent-color:#22c55e;flex-shrink:0}
|
||
.task-body{flex:1}
|
||
.task-title{font-size:14px;font-weight:500;color:#0f172a;transition:color .15s}
|
||
.task-title.done{text-decoration:line-through;color:#94a3b8}
|
||
.task-meta{font-size:12px;color:#64748b;margin-top:3px}
|
||
.task-meta.done{color:#cbd5e1}
|
||
.tag{display:inline-block;font-size:10px;font-weight:600;padding:1px 6px;border-radius:8px;margin-right:4px}
|
||
.tag-done{background:#dcfce7;color:#15803d}
|
||
.tag-partial{background:#fef9c3;color:#a16207}
|
||
.tag-todo{background:#f1f5f9;color:#64748b}
|
||
.section-note{font-size:11px;color:#94a3b8;padding:6px 18px;background:#fafafa;border-top:1px solid #f1f5f9;font-style:italic}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Doormile — Build Tracker</h1>
|
||
<p class="subtitle">Click any task to toggle. Progress synced from both Claude sessions.</p>
|
||
<div class="summary" id="summary"></div>
|
||
<div id="board"></div>
|
||
|
||
<script>
|
||
const phases = [
|
||
{
|
||
title: "🔥 Priority 1 — Launch Blockers",
|
||
badge: "Highest Priority", badgeClass: "badge-orange",
|
||
tasks: [
|
||
{ title: "Connect booking estimates to DoormilePricing", meta: "Replace legacy Pricing table usage in CreateCustomerBooking", done: false },
|
||
{ title: "Fix multi-parcel processing", meta: "Process all parcels instead of First(&parcel) in pickup complete", done: false },
|
||
{ title: "Verify Redis GEO queries work end to end", meta: "milers:locations GEO index exists — validate GEORADIUS query", done: true },
|
||
{ title: "Define launch cities", meta: "Hyderabad, Mumbai, additional launch locations — decision needed", done: false },
|
||
{ title: "Add city/state fields to rider profiles", meta: "Database migration + API support — ALTER TABLE milerprofiles", done: false },
|
||
{ title: "Reject bookings outside supported cities", meta: "City validation middleware in Go Fiber", done: false },
|
||
{ title: "Reverse geocode GPS to pincode", meta: "Required for pricing check and city validation in Flutter", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "✅ Core Backend — Completed",
|
||
badge: "Done", badgeClass: "badge-green",
|
||
note: "Completed in the Go backend session",
|
||
tasks: [
|
||
{ title: "Customer: register, login (2-step PIN), JWT token", meta: "✓ Working and tested", done: true },
|
||
{ title: "Customer: create booking with parcels + service option", meta: "✓ Zero-friction flow. Price from old table (fix in Priority 1)", done: true },
|
||
{ title: "Customer: booking list, detail, cancel, consignment tracking", meta: "✓ All working", done: true },
|
||
{ title: "Customer: saved locations CRUD (max 10)", meta: "✓ Fully working", done: true },
|
||
{ title: "Redis pricing system — 55 rules, zone/category/weight", meta: "✓ Built + cache-warmed on startup. Not yet wired to booking estimate", done: true },
|
||
{ title: "Miler: login, PIN verify, GPS update, availability toggle", meta: "✓ GPS → Postgres + Redis GEO (milers:locations) 30min TTL", done: true },
|
||
{ title: "Miler: full pickup workflow → auto-converts to consignment", meta: "✓ Accept → weigh → collect payment → complete → DM-TRK-XXXX", done: true },
|
||
{ title: "Admin: full CRUD users, hubs, vehicles, milers, bookings, tripsheets", meta: "✓ 95% complete", done: true },
|
||
{ title: "NATS JetStream — BOOKINGS stream + 3 consumers on doormile-nats", meta: "✓ api.v1.bookings.create · update · cancel all live at port 4223", done: true },
|
||
{ title: "Redis doormile — hashsets, booking cache, pricing cache", meta: "✓ Running at port 6380 on 66.116.226.255", done: true },
|
||
{ title: "Go backend deployed at api.doormile.com", meta: "✓ All tables migrated, JWT, Redis + Postgres connected", done: true },
|
||
]
|
||
},
|
||
{
|
||
title: "✅ Customer Flutter App — Completed",
|
||
badge: "Done", badgeClass: "badge-green",
|
||
note: "Completed in the Flutter session",
|
||
tasks: [
|
||
{ title: "Zero-friction booking flow (4-step premium UI)", meta: "✓ Bottom sheet validation, smooth progression, JWT auth", done: true },
|
||
{ title: "Dynamic pricing display — ₹45–₹90 range from Redis API", meta: "✓ Hits POST /api/v1/pricing/check, renders range on Review screen", done: true },
|
||
{ title: "Live dashboard — active orders from database, no dummy data", meta: "✓ Glowing gradient card, real-time data", done: true },
|
||
{ title: "Financial summary — total deliveries + total spent aggregated", meta: "✓ Live from Postgres, hardcoded data removed", done: true },
|
||
{ title: "Booking tracking screen — polls every 10 seconds", meta: "✓ Timer-based polling GET /api/v1/customer/bookings", done: true },
|
||
{ title: "Smart category → backend enum mapping (Electronics → Electronics)", meta: "✓ Prevents 400 errors from pretty UI labels", done: true },
|
||
]
|
||
},
|
||
{
|
||
title: "🚚 Rider Assignment Engine",
|
||
badge: "Core MVP", badgeClass: "badge-blue",
|
||
tasks: [
|
||
{ title: "Create Nearby Riders API — GET /riders/nearby", meta: "GEORADIUS milers:locations · filter Available + low load", done: false },
|
||
{ title: "Build rider scoring engine", meta: "score = dist*1.0 + active_bookings*2.0 - rating*0.5", done: false },
|
||
{ title: "Automatic assignment service", meta: "NATS trigger on api.v1.bookings.create → assign best rider", done: false },
|
||
{ title: "Atomic Redis assignment update", meta: "HSET booking:{id} + increment rider active_bookings atomically", done: false },
|
||
{ title: "Assignment retry workflow", meta: "Retry every 2 mins × 5 → escalate to admin via NATS", done: false },
|
||
{ title: "ETA calculation — port RealisticETACalculator to Go", meta: "distance / avg_speed + pickup buffer + delivery buffer", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "📱 Push Notifications (FCM)",
|
||
badge: "Customer Experience", badgeClass: "badge-blue",
|
||
tasks: [
|
||
{ title: "Firebase Admin SDK integration in Go", meta: "Store device tokens in milerprofiles + appcustomers tables", done: false },
|
||
{ title: "Rider notifications — new booking assignment alert", meta: "FCM push when auto-assignment picks the rider", done: false },
|
||
{ title: "Customer notifications — assignment, pickup, delivery updates", meta: "Every status milestone triggers customer FCM push", done: false },
|
||
{ title: "Miler app: Accept / Reject button on job notification", meta: "Currently miler must open app and find booking manually", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "🛰 Live Tracking",
|
||
badge: "Rapido Style", badgeClass: "badge-purple",
|
||
tasks: [
|
||
{ title: "Deploy EMQX MQTT broker", meta: "Docker compose · ports 1883 + 8083 · rider GPS streaming", done: false },
|
||
{ title: "Miler app: background GPS publish to MQTT while job active", meta: "Battery-aware — only stream when booking assigned", done: false },
|
||
{ title: "WebSocket tracking API — /ws/bookings/{id}/track", meta: "Push miler GPS to customer every 2s from Redis GEO", done: false },
|
||
{ title: "Customer live map — animated rider marker + ETA countdown", meta: "google_maps_flutter · smooth marker animation · Rapido-style", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "💳 Payments",
|
||
badge: "Monetization", badgeClass: "badge-orange",
|
||
tasks: [
|
||
{ title: "Razorpay integration — UPI + prepaid booking flow", meta: "Currently only records payment manually — no actual gateway", done: false },
|
||
{ title: "Payment verification webhooks", meta: "Confirm successful transactions before booking confirms", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "💬 Communication Bridge",
|
||
badge: "Phase 2", badgeClass: "badge-gray",
|
||
tasks: [
|
||
{ title: "Customer ↔ Rider in-app chat", meta: "NATS subject per booking + WebSocket · neither sees real phone", done: false },
|
||
{ title: "Masked calling via Exotel", meta: "Virtual number hides personal phone · ₹1-2/min · later phase", done: false },
|
||
]
|
||
},
|
||
{
|
||
title: "🚀 MVP Launch Checklist",
|
||
badge: "Go Live", badgeClass: "badge-green",
|
||
tasks: [
|
||
{ title: "Booking Creation", meta: "Customer creates booking via Flutter app", done: true },
|
||
{ title: "Pricing Calculation", meta: "Dynamic Redis pricing displayed to customer", done: true },
|
||
{ title: "Auto Assignment", meta: "System finds + assigns nearest available miler", done: false },
|
||
{ title: "Push Notifications", meta: "Miler and customer both notified", done: false },
|
||
{ title: "Live Tracking", meta: "Customer sees miler moving on map", done: false },
|
||
{ title: "Booking Completion", meta: "Miler completes pickup → consignment created", done: true },
|
||
{ title: "Admin Monitoring", meta: "Admin can see and manage all bookings", done: true },
|
||
{ title: "Payment Collection", meta: "Customer pays via Razorpay/UPI", done: false },
|
||
]
|
||
},
|
||
];
|
||
|
||
const KEY = 'dm_board_v1';
|
||
function save() {
|
||
try { localStorage.setItem(KEY, JSON.stringify(phases.map(p => p.tasks.map(t => t.done)))); } catch(e){}
|
||
}
|
||
function load() {
|
||
try {
|
||
const d = JSON.parse(localStorage.getItem(KEY));
|
||
if (d) d.forEach((pd, pi) => pd.forEach((done, ti) => {
|
||
if (phases[pi] && phases[pi].tasks[ti] !== undefined) phases[pi].tasks[ti].done = done;
|
||
}));
|
||
} catch(e){}
|
||
}
|
||
load();
|
||
|
||
function toggle(pi, ti) {
|
||
phases[pi].tasks[ti].done = !phases[pi].tasks[ti].done;
|
||
save(); render();
|
||
}
|
||
|
||
function render() {
|
||
let total = 0, done = 0;
|
||
phases.forEach(p => p.tasks.forEach(t => { total++; if (t.done) done++; }));
|
||
const pct = Math.round(done / total * 100);
|
||
|
||
document.getElementById('summary').innerHTML = `
|
||
<div class="stat"><div class="stat-num" style="color:#0f172a">${total}</div><div class="stat-label">total</div></div>
|
||
<div class="stat"><div class="stat-num" style="color:#16a34a">${done}</div><div class="stat-label">done</div></div>
|
||
<div class="stat"><div class="stat-num" style="color:#dc2626">${total - done}</div><div class="stat-label">remaining</div></div>
|
||
<div class="stat"><div class="stat-num" style="color:#2563eb">${pct}%</div><div class="stat-label">complete</div></div>
|
||
`;
|
||
|
||
document.getElementById('board').innerHTML = phases.map((p, pi) => {
|
||
const pdone = p.tasks.filter(t => t.done).length;
|
||
const pct2 = Math.round(pdone / p.tasks.length * 100);
|
||
return `
|
||
<div class="phase">
|
||
<div class="phase-header">
|
||
<h2>${p.title}</h2>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span style="font-size:11px;color:#64748b">${pdone}/${p.tasks.length}</span>
|
||
<span class="badge ${p.badgeClass}">${p.badge}</span>
|
||
</div>
|
||
</div>
|
||
<div class="progress-bar"><div class="progress-fill" style="width:${pct2}%"></div></div>
|
||
${p.note ? `<div class="section-note">${p.note}</div>` : ''}
|
||
${p.tasks.map((t, ti) => `
|
||
<div class="task" onclick="toggle(${pi},${ti})">
|
||
<input type="checkbox" ${t.done ? 'checked' : ''} onclick="event.stopPropagation();toggle(${pi},${ti})">
|
||
<div class="task-body">
|
||
<div class="task-title ${t.done ? 'done' : ''}">${t.title}</div>
|
||
<div class="task-meta ${t.done ? 'done' : ''}">${t.meta}</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|