diff --git a/src/features/crm/EfficiencyScatter.jsx b/src/features/crm/EfficiencyScatter.jsx index 2ac947f..d8d2d21 100644 --- a/src/features/crm/EfficiencyScatter.jsx +++ b/src/features/crm/EfficiencyScatter.jsx @@ -1,9 +1,9 @@ import { useMemo, useState } from 'react' -// 전체 드라이버 효율 산점도 (모던 리디자인). +// 전체 UCO 드라이버 산점도. // x = 이동거리(km), y = 오일 픽업량 -// 마커 = 번호 배지 + 진행률 링(완료/목표). 운행중=채운 배지 / 종료=옅은 배지+체크 / 픽업전=고스트 -// 호버: 해당 마커가 맨 위로 + 상세 툴팁(시작/종료 시각 포함). 클릭: onSelect(driverId) +// 잔 채움 = 픽업량 / 목표량(고정). 종료=체크 배지 / 일시정지=막대 배지 / 픽업 0=고스트 +// 호버: 해당 마커가 맨 위로 + 상세 툴팁. 클릭: onSelect(driverId) const W = 720, H = 470, PL = 56, PR = 680, PT = 44, PB = 400, R = 11 const ACCENT = '#2e7d32' @@ -17,6 +17,7 @@ const fmtHM = (s) => { if (isNaN(d)) return '—' return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` } +const statusText = (s) => (s === 'C' ? '운행 종료' : s === 'P' ? '일시정지' : '운행 중') // 단색(BI) 툴팁 아이콘. y = 텍스트 baseline 기준, 약 13px. const TipIcon = ({ type, x, y, c }) => { @@ -69,7 +70,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { targetQty: Number(r.ddsTargetQty ?? 0), // 목표량 (고정) ← 분모 completed: Number(r.ddsCompletedCount ?? 0), targetCount: Number(r.ddsTargetCount ?? 0), // 목표 방문수 (고정) - running: r.ddsStatus !== 'C', // C = 운행종료 + status: r.ddsStatus, startAt: r.ddsStartAt, endAt: r.ddsEndAt, })) @@ -181,7 +182,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { const { items, xTicks, yTicks, yOf, xOf } = model if (!items.length) { - return
픽업이 기록된 드라이버가 없습니다.
+ return
목표가 배정된 드라이버가 없습니다.
} // 호버된 마커를 맨 마지막에 그려서 위로 올림 (행 단위 식별) @@ -197,8 +198,10 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { const k = isHover ? 1.18 : 1 const r = R * k const cx = d.mx, cy = d.my - const empty = d.oil <= 0 // 픽업 없음 (산점도엔 거의 안 옴, 안전장치) - const f = d.targetQty > 0 ? Math.min(1, d.oil / d.targetQty) : 1 // 픽업량 / 목표량 (예상 없으면 가득) + const empty = d.oil <= 0 // 픽업 0 (목표만 있는 드라이버 등) + const ended = d.status === 'C' + const paused = d.status === 'P' + const f = d.targetQty > 0 ? Math.min(1, d.oil / d.targetQty) : 1 // 픽업량 / 목표량(고정) const clipId = `dstatLiq${d.rowId}` // 액체 표면 (가벼운 물결) + 바닥. f=0/1 이면 평평. @@ -218,13 +221,13 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { {isSel && } {empty ? ( - // 픽업 전 / 예상량 없음 = 빈 점선 잔 + // 픽업 0 / 목표만 있음 = 빈 점선 잔 <> {d.num} ) : ( - // 액체 잔: 안쪽이 아래에서부터 차오름 (f = 픽업/예상) + // 액체 잔: 안쪽이 아래에서부터 차오름 (f = 픽업/목표) <> @@ -232,13 +235,23 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { {d.num} - {!d.running && ( - <> - - - - )} + + )} + + {ended && ( + // 운행 종료 체크 + <> + + + + )} + {paused && ( + // 일시정지 (두 막대) + <> + + + )} @@ -263,7 +276,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) { : `${Math.round(d.oil).toLocaleString()} L` }, { ic: 'dist', t: d.kmText || `${d.km} km` }, { ic: 'visit', t: d.targetCount > 0 ? `${d.completed} / ${d.targetCount}` : '방문 목표 없음' }, - { ic: 'time', t: d.endAt ? `${fmtHM(d.startAt)} ~ ${fmtHM(d.endAt)}` : `${fmtHM(d.startAt)} ~ 운행 중` }, + { ic: 'time', t: d.endAt ? `${fmtHM(d.startAt)} ~ ${fmtHM(d.endAt)}` : `${fmtHM(d.startAt)} ~ ${statusText(d.status)}` }, ] const lineH = 16 const tw = 168, th = 24 + rows.length * lineH