PHASE 0 : 本日の目標設定
SALES RANK
0 / 0 pt
F
▲ タブ・リストを開く
目標を設定してスタートしてください
訪問完了+1 pt
対面+5 pt
フルトーク+15 pt
🎯 アポ獲得 !!+50 pt

検索エリア

未取得

太陽光ポテンシャル目安

年間日照時間: 約1,900時間
推奨パネル: 南向き/東西向き

管轄一般送配電事業者

エリア読込待ち

近隣主要供給発電所

エリア読込待ち

function dist(p1, p2) { return Math.hypot(p1.lat - p2.lat, p1.lon - p2.lon); } window.plotTargetMarkers = function(orderedList = null){ targetMarkers.forEach(m => m.remove()); targetMarkers = []; let listHTML = "", validCount = 0; const targetsToRender = orderedList ? targetBuildings : targetBuildings; targetsToRender.forEach((item) => { let col = item.rank === 'S' ? '#28a745' : (item.rank === 'A' ? '#ff9800' : '#1a73e8'); let dCls = item.isAuto ? '' : ' draggable'; let eCls = item.isExcluded ? (item.isNgRecord ? ' ng-record' : ' excluded') : ''; let pinLabel = ""; if(item.isExcluded) { col = "#888"; pinLabel = item.isNgRecord ? "NG" : "×"; } else { validCount++; pinLabel = validCount; if(orderedList) { let ratio = (validCount - 1) / (orderedList.length - 1 || 1); let val = Math.floor(ratio * 240); col = `rgb(${val},${val},${val})`; } } let textColor = '#fff'; if(orderedList && !item.isExcluded) { let ratio = (validCount - 1) / (orderedList.length - 1 || 1); if(ratio > 0.5) textColor = '#333'; } const icon = L.divIcon({ className: `custom-pin-container${dCls}${eCls}`, html: `
${pinLabel}
`, iconSize: [24, 24], iconAnchor: [12, 12] }); const marker = L.marker([item.lat, item.lon], { icon: icon, draggable: !item.isAuto }).addTo(m); let popupContent = `${item.isNgRecord ? '[過去NG・訪問禁止]' : (item.isExcluded ? '[除外物件]' : '[ランク '+item.rank+'] 物件 #'+pinLabel)}
屋根向き: ${item.roofDir}
面積: ${item.area}㎡
${item.isExcluded?`理由: ${item.excludeReason}
`:''}`; let btnText = item.isExcluded ? "ルートに復帰する" : "手動で除外する"; let btnCol = item.isExcluded ? "#28a745" : "#6c757d"; popupContent += `
`; marker.bindPopup(popupContent); marker.on('dragend', function(e){ if(item.isAuto) return; const pos = e.target.getLatLng(); item.lat = pos.lat; item.lon = pos.lng; if(lastStartLat && lastStartLon) window.generateOptimizedRoute(lastStartLat, lastStartLon); else window.plotTargetMarkers(); }); targetMarkers.push(marker); if(!orderedList) { let tBdg = item.isAuto ? `公式` : `手動`; let cardHTML = ""; if(item.isExcluded) { let cardStyle = item.isNgRecord ? 'ng-record' : 'excluded'; let titleColor = item.isNgRecord ? '#dc3545' : '#6c757d'; let titleText = item.isNgRecord ? 'NG・訪問禁止 (NG)' : '除外物件 (×)'; let badgeClass = item.isNgRecord ? 'ng-badge' : 'exclude-badge'; let badgeText = item.isNgRecord ? '自社アプリ連携' : '非推奨'; cardHTML = `
${titleText} ${badgeText}
理由: ${item.excludeReason}
面積: ${item.area}㎡ / 向き: ${item.roofDir}
`; listHTML += cardHTML; } else { cardHTML = `
物件 #${pinLabel} ランク ${item.rank}${tBdg}${item.roofDir}
面積: ${item.area}㎡
ナビ
`; listHTML = cardHTML + listHTML; } } }); if(!orderedList) document.getElementById('l').innerHTML = listHTML || "

対象の家屋が見つかりません。

"; }; window.deleteBuilding = function(id){ targetBuildings = targetBuildings.filter(b => b.id !== id); window.plotTargetMarkers(); if(lastStartLat && lastStartLon) window.generateOptimizedRoute(lastStartLat, lastStartLon); }; window.generateOptimizedRoute = async function(sLat, sLon){ const valids = targetBuildings.filter(b => !b.isExcluded); if(valids.length === 0) return alert('ターゲットがありません。'); const limit = parseInt(document.getElementById('target-count').value) || 8; document.getElementById('st').innerText = "ルート計算・最適化中..."; if(startMarker) m.removeLayer(startMarker); if(routePolylines && routePolylines.length > 0) { routePolylines.forEach(p => m.removeLayer(p)); routePolylines = []; } startMarker = L.circleMarker([sLat, sLon], {color:'#000', fillColor:'#000', fillOpacity:1, radius:8, zIndexOffset: 1000}).addTo(m).bindPopup("スタート位置"); const candidates = [...valids]; candidates.sort((a,b) => dist({lat:sLat, lon:sLon}, a) - dist({lat:sLat, lon:sLon}, b)); const routeTargets = candidates.slice(0, limit); let cur = {lat: sLat, lon: sLon}, unv = [...routeTargets], ord = []; while(unv.length > 0){ unv.sort((a,b) => dist(cur, a) - dist(cur, b)); const n = unv.shift(); ord.push(n); cur = n; } let fullRoute = [{lat: sLat, lon: sLon}, ...ord]; let improved = true; while(improved) { improved = false; for(let i=1; i < fullRoute.length - 2; i++) { for(let j=i+1; j < fullRoute.length - 1; j++) { let d1 = dist(fullRoute[i-1], fullRoute[i]) + dist(fullRoute[j], fullRoute[j+1]); let d2 = dist(fullRoute[i-1], fullRoute[j]) + dist(fullRoute[i], fullRoute[j+1]); if(d2 < d1) { let reversed = fullRoute.slice(i, j+1).reverse(); fullRoute.splice(i, j - i + 1, ...reversed); improved = true; } } } } ord = fullRoute.slice(1); let allTargetsSorted = [...ord, ...targetBuildings.filter(b => b.isExcluded)]; targetBuildings = allTargetsSorted; window.plotTargetMarkers(ord); let l = ""; ord.forEach((item, i) => { let ratio = i / (ord.length - 1 || 1); let val = Math.floor(ratio * 240); let col = `rgb(${val},${val},${val})`; let textCol = val > 150 ? '#333' : '#fff'; let tBdg = item.isAuto ? `公式` : `手動`; l += `
訪問順 #${i+1} ランク ${item.rank}${tBdg} ${item.roofDir}
面積: ${item.area}㎡
ナビ起動
`; }); document.getElementById('l').innerHTML = l; for(let i = 0; i < fullRoute.length - 1; i++) { let ratio = i / (fullRoute.length - 2 || 1); let val = Math.floor(ratio * 240); let color = `rgb(${val},${val},${val})`; let outline = L.polyline([ [fullRoute[i].lat, fullRoute[i].lon], [fullRoute[i+1].lat, fullRoute[i+1].lon] ], { color: '#1a73e8', weight: 8, opacity: 0.3 }).addTo(m); routePolylines.push(outline); let segment = L.polyline([ [fullRoute[i].lat, fullRoute[i].lon], [fullRoute[i+1].lat, fullRoute[i+1].lon] ], { color: color, weight: 4, opacity: 1, dashArray: '5, 5' }).addTo(m); routePolylines.push(segment); } document.getElementById('st').innerText = `最適化ルート作成完了: 対象 ${ord.length}件`; document.getElementById('drawer').classList.add('open'); window.switchTab('action', {target: document.querySelectorAll('.tab-btn')[0]}); };