Files
doormile_backend/doormile-taskboard.html
2026-06-22 17:43:40 +05:30

219 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>