Rev 36962 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Route Planner - Smart Dukaan</title><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Inter', sans-serif;background: #0f172a;color: #e2e8f0;height: 100vh;overflow: hidden;}/* Top Bar */.top-bar {display: flex;align-items: center;gap: 12px;padding: 8px 16px;background: #1e293b;border-bottom: 1px solid #334155;height: 50px;}.top-bar h1 {font-size: 16px;font-weight: 600;color: #60a5fa;margin-right: 20px;}.top-bar select, .top-bar button {font-family: inherit;font-size: 13px;padding: 6px 10px;border-radius: 6px;border: 1px solid #475569;background: #1e293b;color: #e2e8f0;}.top-bar select:focus {outline: none;border-color: #60a5fa;}.top-bar button {cursor: pointer;font-weight: 500;}.top-bar .btn-primary {background: #3b82f6;border-color: #3b82f6;color: #fff;}.top-bar .btn-primary:hover {background: #2563eb;}/* Main Layout */.main-layout {display: flex;height: calc(100vh - 50px);}/* Left Panel */.left-panel {width: 300px;min-width: 300px;background: #1e293b;border-right: 1px solid #334155;display: flex;flex-direction: column;overflow: hidden;}.panel-tabs {display: flex;border-bottom: 1px solid #334155;}.panel-tab {flex: 1;padding: 10px;text-align: center;font-size: 12px;font-weight: 600;cursor: pointer;color: #94a3b8;border-bottom: 2px solid transparent;transition: all 0.2s;}.panel-tab.active {color: #60a5fa;border-bottom-color: #60a5fa;}.panel-tab:hover {color: #e2e8f0;}.panel-content {flex: 1;overflow-y: auto;padding: 10px;}/* Route List */.route-day {margin-bottom: 12px;}.route-day-header {font-size: 12px;font-weight: 600;color: #60a5fa;padding: 6px 8px;background: #0f172a;border-radius: 6px;display: flex;justify-content: space-between;}.route-stop {display: flex;align-items: center;gap: 8px;padding: 6px 8px;border-left: 2px solid #334155;margin-left: 12px;font-size: 12px;}.route-stop .stop-num {width: 20px;height: 20px;border-radius: 50%;background: #3b82f6;color: #fff;display: flex;align-items: center;justify-content: center;font-size: 10px;font-weight: 700;flex-shrink: 0;}.route-stop .stop-info {flex: 1;}.route-stop .stop-name {font-weight: 500;color: #e2e8f0;}.route-stop .stop-meta {color: #64748b;font-size: 11px;}.route-stop-home {border-left-color: #22c55e;}.route-stop-home .stop-num {background: #22c55e;}.route-stop-clickable:hover {background: #334155;border-radius: 4px;}.stop-remove {color: #64748b;font-size: 16px;font-weight: 700;cursor: pointer;padding: 0 4px;flex-shrink: 0;line-height: 1;}.stop-remove:hover {color: #ef4444;}/* Distance Segment between stops */.route-segment {display: flex;align-items: center;margin-left: 20px;padding: 2px 0;}.seg-line {width: 12px;border-top: 1px dashed #475569;margin-right: 6px;}.seg-line-home {border-top-color: #22c55e;}.seg-dist {font-size: 10px;color: #60a5fa;font-weight: 500;}/* Day Summary */.day-summary {font-size: 11px;color: #94a3b8;padding: 4px 8px;margin-left: 12px;}/* Calendar */.cal-grid {width: 100%;border-collapse: collapse;}.cal-grid th {padding: 6px;font-size: 11px;color: #64748b;text-align: center;}.cal-grid td {padding: 4px;text-align: center;font-size: 12px;height: 70px;vertical-align: top;border: 1px solid #334155;position: relative;cursor: pointer;}.cal-grid td:hover {background: #1e293b;}.cal-grid td.blocked {background: #0f172a;color: #475569;cursor: not-allowed;}.cal-grid td.holiday {background: #422006;}.cal-grid td.today {border: 2px solid #3b82f6;}.cal-date {font-weight: 600;margin-bottom: 2px;}.cal-chip {display: block;font-size: 9px;padding: 1px 4px;border-radius: 3px;color: #fff;margin: 1px 0;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}.cal-chip.running {box-shadow: 0 0 0 2px #ef4444;}.cal-chip.completed {opacity: 0.4;}.cal-chip-remove {margin-left: 4px;cursor: pointer;font-weight: 700;font-size: 11px;opacity: 0.7;}.cal-chip-remove:hover {opacity: 1;color: #fca5a5;}.cal-grid td.past {background: #0c1222 !important;color: #334155 !important;cursor: not-allowed !important;}.cal-grid td.past .cal-date {color: #334155;}.cal-grid td.sunday {background: #1a1a2e;}.cal-grid td.sunday .cal-date {color: #ef4444;}.cal-holiday-label {font-size: 9px;color: #fbbf24;}/* Beat Cards in Calendar Panel */.beat-card {padding: 10px;margin-bottom: 8px;border-radius: 8px;border: 1px solid #334155;cursor: pointer;transition: all 0.2s;}.beat-card:hover {border-color: #60a5fa;}.beat-card.selected {border-color: #3b82f6;box-shadow: 0 0 0 1px #3b82f6;}.beat-card .beat-name {font-weight: 600;font-size: 13px;display: flex;align-items: center;gap: 6px;}.beat-card .beat-meta {font-size: 11px;color: #64748b;margin-top: 4px;}.beat-card .beat-actions {margin-top: 8px;display: flex;gap: 6px;}.beat-card .btn-sm {font-size: 11px;padding: 3px 8px;border-radius: 4px;border: none;cursor: pointer;font-weight: 500;}.btn-schedule {background: #3b82f6;color: #fff;}.btn-repeat {background: #22c55e;color: #fff;}.btn-schedule:hover {background: #2563eb;}.btn-repeat:hover {background: #16a34a;}.status-badge {font-size: 10px;padding: 2px 6px;border-radius: 10px;font-weight: 600;}.status-running {background: #ef4444;color: #fff;}.status-scheduled {background: #3b82f6;color: #fff;}.status-completed {background: #475569;color: #94a3b8;}/* Map */.map-container {flex: 1;position: relative;}#beat-map {width: 100%;height: 100%;}/* Bottom Bar */.bottom-bar {position: absolute;bottom: 0;left: 0;right: 0;display: flex;align-items: center;justify-content: space-between;padding: 8px 16px;background: rgba(15, 23, 42, 0.9);backdrop-filter: blur(8px);border-top: 1px solid #334155;font-size: 12px;z-index: 10;}.bottom-bar .day-info {display: flex;gap: 20px;}.progress-wrap {width: 200px;height: 8px;background: #334155;border-radius: 4px;overflow: hidden;}.progress-fill {height: 100%;border-radius: 4px;transition: width 0.3s;}/* Action Buttons */.bottom-bar .action-btns button {font-family: inherit;font-size: 12px;padding: 6px 14px;border-radius: 6px;border: none;cursor: pointer;font-weight: 500;margin-left: 8px;}.btn-end-day {background: #f59e0b;color: #0f172a;}.btn-finish {background: #22c55e;color: #fff;}.btn-end-day:hover {background: #d97706;}.btn-finish:hover {background: #16a34a;}/* Modal Overlay */.modal-overlay {display: none;position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.6);z-index: 100;justify-content: center;align-items: center;}.modal-overlay.show {display: flex;}.modal-box {background: #1e293b;border: 1px solid #334155;border-radius: 12px;padding: 24px;min-width: 400px;max-width: 600px;max-height: 80vh;overflow-y: auto;}.modal-box h3 {font-size: 16px;font-weight: 600;margin-bottom: 16px;color: #f1f5f9;}.modal-box input, .modal-box select {font-family: inherit;width: 100%;padding: 8px 12px;border: 1px solid #475569;border-radius: 6px;background: #0f172a;color: #e2e8f0;margin-bottom: 10px;font-size: 13px;}.modal-box .modal-actions {display: flex;justify-content: flex-end;gap: 8px;margin-top: 16px;}.modal-box .modal-actions button {font-family: inherit;padding: 8px 16px;border-radius: 6px;border: none;cursor: pointer;font-weight: 500;font-size: 13px;}.modal-cancel {background: #475569;color: #e2e8f0;}.modal-confirm {background: #3b82f6;color: #fff;}/* Info Window custom */.marker-popup {font-family: 'Inter', sans-serif;padding: 8px;min-width: 200px;}.marker-popup h4 {font-size: 14px;font-weight: 700;color: #1e293b;margin-bottom: 6px;}.marker-popup .popup-meta {font-size: 11px;color: #666;margin-bottom: 8px;}.marker-popup .popup-distance {font-size: 12px;font-weight: 500;color: #3b82f6;margin-bottom: 8px;}.marker-popup button {font-size: 11px;padding: 4px 10px;border: none;border-radius: 4px;cursor: pointer;margin-right: 4px;font-weight: 500;}.popup-next {background: #3b82f6;color: #fff;}.popup-last {background: #f59e0b;color: #0f172a;}.popup-nextday {background: #8b5cf6;color: #fff;}/* Nav Month */.month-nav {display: flex;align-items: center;gap: 12px;padding: 10px;border-bottom: 1px solid #334155;}.month-nav button {background: none;border: 1px solid #475569;color: #e2e8f0;padding: 4px 10px;border-radius: 4px;cursor: pointer;font-size: 12px;}.month-nav span {font-weight: 600;font-size: 14px;}</style></head><body><!-- Top Bar --><div class="top-bar"><h1>Route Planner</h1><select id="bp-category" style="width:100px;"><option value="4">Sales</option></select><select id="bp-level" style="width:80px;"><option value="">Level</option>#foreach($et in $escalationTypes)<option value="$et">$et</option>#end</select><select id="bp-auth-user" style="width:180px;"><option value="">Select User</option></select><button class="btn-primary" id="bp-load">Load</button><div style="flex:1;"></div><span id="bp-user-label" style="font-size:12px;color:#94a3b8;"></span></div><!-- Main Layout --><div class="main-layout"><!-- Left Panel --><div class="left-panel"><div class="panel-tabs"><div class="panel-tab active" data-tab="route">Route</div><div class="panel-tab" data-tab="calendar">Calendar</div><div class="panel-tab" data-tab="deferred">Deferred <span id="di-tab-count"style="font-size:10px;color:#94a3b8;"></span></div></div><!-- Route Tab Content --><div class="panel-content" id="panel-route"><!-- Pending Visit Requests for the loaded user. Populated byloadVisitRequests() after #bp-load. Hidden when empty. --><div id="visit-request-panel" style="max-height:350px;overflow:auto;font-size:12px;display:none;margin-bottom:10px;padding:10px;border:1px solid #475569;border-radius:6px;background:#1f2937;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;"><strong id="vr-toggle" style="font-size:12px;color:#fbbf24;cursor:pointer;user-select:none;"><span id="vr-caret" style="display:inline-block;width:10px;">▾</span><i class="icon_bell"></i> Pending Visit Requests <span id="vr-count">0</span></strong><button id="vr-refresh" type="button" style="font-size:11px;padding:2px 8px;border:1px solid #475569;border-radius:4px;background:none;color:#94a3b8;cursor:pointer;">Refresh</button></div><div id="vr-body"><div id="vr-list"></div></div></div><!-- Assigned leads for the loaded user. Populated by loadAssignedLeads()after #bp-load. Hidden when empty. Each row schedules onto a beat(calendar-chip handoff) or spins up a new 1-day beat. --><div id="assigned-leads-panel"style="display:none;margin-bottom:10px;padding:10px;border:1px solid #475569;border-radius:6px;background:#1f2937;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;"><strong id="al-toggle" style="font-size:12px;color:#34d399;cursor:pointer;user-select:none;"><span id="al-caret" style="display:inline-block;width:10px;">▾</span><i class="icon_profile"></i> Assigned Leads <span id="al-count">0</span></strong><button id="al-refresh" type="button"style="font-size:11px;padding:2px 8px;border:1px solid #475569;border-radius:4px;background:none;color:#94a3b8;cursor:pointer;">Refresh</button></div><div id="al-body"><input type="text" id="al-search" placeholder="Filter leads by name, outlet, city or mobile..."style="width:100%;margin-bottom:6px;padding:4px 8px;font-size:11px;height:28px;box-sizing:border-box;background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:4px;"><div id="al-list" style="max-height:350px;overflow-y:auto;font-size:12px"></div></div></div><div id="route-list"><p style="color:#64748b;font-size:12px;text-align:center;padding:40px 10px;">Select a user and click Load to start planning.</p></div></div><!-- Calendar Tab Content --><div class="panel-content" id="panel-calendar" style="display:none;"><div class="month-nav"><button id="cal-prev"><</button><span id="cal-month-label"></span><button id="cal-next">></button></div><div id="cal-beat-cards"></div></div><!-- Deferred Tab Content (separate tab so a long deferred list doesn'tpush the Route beats down). Uses the same /beatPlan/deferred read. --><div class="panel-content" id="panel-deferred" style="display:none;"><div id="deferred-items-panel"style="margin-bottom:10px;padding:10px;border:1px solid #475569;border-radius:6px;background:#1f2937;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;"><strong style="font-size:12px;color:#fca5a5;"><i class="icon_clock"></i> Deferred Partners / Visits <span id="di-count">0</span></strong><button id="di-refresh" type="button"style="font-size:11px;padding:2px 8px;border:1px solid #475569;border-radius:4px;background:none;color:#94a3b8;cursor:pointer;">Refresh</button></div><div id="di-list"><p style="color:#64748b;font-size:12px;text-align:center;padding:20px 10px;">No deferred items for this user.</p></div></div></div></div><!-- Map --><div class="map-container"><div id="beat-map"></div><!-- Bottom Bar (on map) --><div class="bottom-bar" id="bottom-bar" style="display:none;"><div class="day-info"><span id="bb-day-label">Day 1</span><span id="bb-stops">0 stops</span><span id="bb-distance">0 km</span><span id="bb-time">0h 0m</span><div class="progress-wrap"><div class="progress-fill" id="bb-progress" style="width:0%;background:#22c55e;"></div></div></div><div class="action-btns"><button class="btn-finish" id="btn-finish">Finish Plan</button></div></div><!-- Calendar Grid (overlays map when calendar tab active) --><div id="cal-grid-container"style="display:none;position:absolute;top:0;left:0;right:0;bottom:0;background:#0f172a;overflow-y:auto;padding:20px;"><div id="cal-grid-content"></div></div></div></div><!-- Home Location Modal --><div class="modal-overlay" id="modal-home"><div class="modal-box"><h3>Set Home Location</h3><p style="font-size:12px;color:#94a3b8;margin-bottom:12px;">Search or click on map to set the auth user'shome/base location.</p><input type="text" id="home-search" placeholder="Search location..."><div id="home-map" style="height:250px;border-radius:8px;border:1px solid #334155;margin-bottom:12px;"></div><div class="modal-actions"><button class="modal-cancel" id="home-cancel">Cancel</button><button class="modal-confirm" id="home-confirm">Confirm Home</button></div></div></div><!-- Day Break Modal --><div class="modal-overlay" id="modal-daybreak"><div class="modal-box"><h3>End of Day</h3><p id="daybreak-info" style="font-size:13px;margin-bottom:12px;"></p><div style="margin-bottom:12px;"><label style="display:block;padding:8px;cursor:pointer;border-radius:6px;border:1px solid #334155;margin-bottom:6px;"><input type="radio" name="stay" value="HOME" checked> Go back home (base location)</label><label style="display:block;padding:8px;cursor:pointer;border-radius:6px;border:1px solid #334155;"><input type="radio" name="stay" value="HOTEL"> Stay at hotel / other location</label></div><div id="hotel-fields" style="display:none;"><input type="text" id="hotel-search" placeholder="Search hotel/location..."><div id="hotel-map" style="height:200px;border-radius:8px;border:1px solid #334155;"></div></div><div class="modal-actions"><button class="modal-cancel" id="daybreak-cancel">Cancel</button><button class="modal-confirm" id="daybreak-confirm">Continue to Next Day</button></div></div></div><!-- Calendar Schedule Modal --><div class="modal-overlay" id="modal-cal-schedule"><div class="modal-box" style="min-width:500px;"><h3 id="cal-sched-title">Schedule Beat</h3><div id="cal-sched-grid"></div><div id="cal-sched-info" style="font-size:12px;color:#94a3b8;margin-top:10px;"></div><div class="modal-actions"><button class="modal-cancel" id="cal-sched-cancel">Cancel</button><button class="modal-confirm" id="cal-sched-confirm">Confirm Schedule</button></div></div></div><script>var context = "${rc.contextPath}";var canScheduleToday = ${canScheduleToday};</script><script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAckO0y4Z6WhBOuMjNjioWLSYZDhGEvGBc&libraries=places&v=weekly"></script><script src="${rc.contextPath}/resources/js/beat-plan-app.js?v=20260630a"></script></body></html>