일시정지 활용
This commit is contained in:
parent
bb3bb7f03a
commit
cc714be650
|
|
@ -1,9 +1,9 @@
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
// 전체 드라이버 효율 산점도 (모던 리디자인).
|
// 전체 UCO 드라이버 산점도.
|
||||||
// x = 이동거리(km), y = 오일 픽업량
|
// x = 이동거리(km), y = 오일 픽업량
|
||||||
// 마커 = 번호 배지 + 진행률 링(완료/목표). 운행중=채운 배지 / 종료=옅은 배지+체크 / 픽업전=고스트
|
// 잔 채움 = 픽업량 / 목표량(고정). 종료=체크 배지 / 일시정지=막대 배지 / 픽업 0=고스트
|
||||||
// 호버: 해당 마커가 맨 위로 + 상세 툴팁(시작/종료 시각 포함). 클릭: onSelect(driverId)
|
// 호버: 해당 마커가 맨 위로 + 상세 툴팁. 클릭: onSelect(driverId)
|
||||||
const W = 720, H = 470, PL = 56, PR = 680, PT = 44, PB = 400, R = 11
|
const W = 720, H = 470, PL = 56, PR = 680, PT = 44, PB = 400, R = 11
|
||||||
const ACCENT = '#2e7d32'
|
const ACCENT = '#2e7d32'
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@ const fmtHM = (s) => {
|
||||||
if (isNaN(d)) return '—'
|
if (isNaN(d)) return '—'
|
||||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
|
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
const statusText = (s) => (s === 'C' ? '운행 종료' : s === 'P' ? '일시정지' : '운행 중')
|
||||||
|
|
||||||
// 단색(BI) 툴팁 아이콘. y = 텍스트 baseline 기준, 약 13px.
|
// 단색(BI) 툴팁 아이콘. y = 텍스트 baseline 기준, 약 13px.
|
||||||
const TipIcon = ({ type, x, y, c }) => {
|
const TipIcon = ({ type, x, y, c }) => {
|
||||||
|
|
@ -69,7 +70,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
targetQty: Number(r.ddsTargetQty ?? 0), // 목표량 (고정) ← 분모
|
targetQty: Number(r.ddsTargetQty ?? 0), // 목표량 (고정) ← 분모
|
||||||
completed: Number(r.ddsCompletedCount ?? 0),
|
completed: Number(r.ddsCompletedCount ?? 0),
|
||||||
targetCount: Number(r.ddsTargetCount ?? 0), // 목표 방문수 (고정)
|
targetCount: Number(r.ddsTargetCount ?? 0), // 목표 방문수 (고정)
|
||||||
running: r.ddsStatus !== 'C', // C = 운행종료
|
status: r.ddsStatus,
|
||||||
startAt: r.ddsStartAt,
|
startAt: r.ddsStartAt,
|
||||||
endAt: r.ddsEndAt,
|
endAt: r.ddsEndAt,
|
||||||
}))
|
}))
|
||||||
|
|
@ -181,7 +182,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
const { items, xTicks, yTicks, yOf, xOf } = model
|
const { items, xTicks, yTicks, yOf, xOf } = model
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
return <div className="dstat-empty">픽업이 기록된 드라이버가 없습니다.</div>
|
return <div className="dstat-empty">목표가 배정된 드라이버가 없습니다.</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 호버된 마커를 맨 마지막에 그려서 위로 올림 (행 단위 식별)
|
// 호버된 마커를 맨 마지막에 그려서 위로 올림 (행 단위 식별)
|
||||||
|
|
@ -197,8 +198,10 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
const k = isHover ? 1.18 : 1
|
const k = isHover ? 1.18 : 1
|
||||||
const r = R * k
|
const r = R * k
|
||||||
const cx = d.mx, cy = d.my
|
const cx = d.mx, cy = d.my
|
||||||
const empty = d.oil <= 0 // 픽업 없음 (산점도엔 거의 안 옴, 안전장치)
|
const empty = d.oil <= 0 // 픽업 0 (목표만 있는 드라이버 등)
|
||||||
const f = d.targetQty > 0 ? Math.min(1, d.oil / d.targetQty) : 1 // 픽업량 / 목표량 (예상 없으면 가득)
|
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}`
|
const clipId = `dstatLiq${d.rowId}`
|
||||||
|
|
||||||
// 액체 표면 (가벼운 물결) + 바닥. f=0/1 이면 평평.
|
// 액체 표면 (가벼운 물결) + 바닥. f=0/1 이면 평평.
|
||||||
|
|
@ -218,13 +221,13 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
{isSel && <circle cx={cx} cy={cy} r={r + 5} fill="none" stroke={ACCENT} strokeWidth="1.5" opacity="0.5" />}
|
{isSel && <circle cx={cx} cy={cy} r={r + 5} fill="none" stroke={ACCENT} strokeWidth="1.5" opacity="0.5" />}
|
||||||
|
|
||||||
{empty ? (
|
{empty ? (
|
||||||
// 픽업 전 / 예상량 없음 = 빈 점선 잔
|
// 픽업 0 / 목표만 있음 = 빈 점선 잔
|
||||||
<>
|
<>
|
||||||
<circle cx={cx} cy={cy} r={r} fill="#ffffff" stroke="#b5c2b8" strokeWidth="1.5" strokeDasharray="2.5 2" />
|
<circle cx={cx} cy={cy} r={r} fill="#ffffff" stroke="#b5c2b8" strokeWidth="1.5" strokeDasharray="2.5 2" />
|
||||||
<text x={cx} y={cy + 4} textAnchor="middle" fontSize="10" fontWeight="600" fill="#9aa5a0">{d.num}</text>
|
<text x={cx} y={cy + 4} textAnchor="middle" fontSize="10" fontWeight="600" fill="#9aa5a0">{d.num}</text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// 액체 잔: 안쪽이 아래에서부터 차오름 (f = 픽업/예상)
|
// 액체 잔: 안쪽이 아래에서부터 차오름 (f = 픽업/목표)
|
||||||
<>
|
<>
|
||||||
<clipPath id={clipId}><circle cx={cx} cy={cy} r={r} /></clipPath>
|
<clipPath id={clipId}><circle cx={cx} cy={cy} r={r} /></clipPath>
|
||||||
<circle cx={cx} cy={cy} r={r} fill="#eef3ef" filter="url(#dstatShadow)" />
|
<circle cx={cx} cy={cy} r={r} fill="#eef3ef" filter="url(#dstatShadow)" />
|
||||||
|
|
@ -232,13 +235,23 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
<circle cx={cx} cy={cy} r={r} fill="none" stroke={ACCENT} strokeWidth="1.6" />
|
<circle cx={cx} cy={cy} r={r} fill="none" stroke={ACCENT} strokeWidth="1.6" />
|
||||||
<text x={cx} y={cy + 4} textAnchor="middle" fontSize="10" fontWeight="700"
|
<text x={cx} y={cy + 4} textAnchor="middle" fontSize="10" fontWeight="700"
|
||||||
fill="#ffffff" stroke="#1f6b3a" strokeWidth="2.4" paintOrder="stroke">{d.num}</text>
|
fill="#ffffff" stroke="#1f6b3a" strokeWidth="2.4" paintOrder="stroke">{d.num}</text>
|
||||||
{!d.running && (
|
</>
|
||||||
<>
|
)}
|
||||||
<circle cx={cx + r * 0.8} cy={cy - r * 0.8} r="5" fill={ACCENT} stroke="#fff" strokeWidth="1" />
|
|
||||||
<path d={`M${(cx + r * 0.8 - 2.3).toFixed(1)} ${(cy - r * 0.8).toFixed(1)} l1.6 1.6 l2.7 -3.2`}
|
{ended && (
|
||||||
fill="none" stroke="#fff" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
|
// 운행 종료 체크
|
||||||
</>
|
<>
|
||||||
)}
|
<circle cx={cx + r * 0.8} cy={cy - r * 0.8} r="5" fill={ACCENT} stroke="#fff" strokeWidth="1" />
|
||||||
|
<path d={`M${(cx + r * 0.8 - 2.3).toFixed(1)} ${(cy - r * 0.8).toFixed(1)} l1.6 1.6 l2.7 -3.2`}
|
||||||
|
fill="none" stroke="#fff" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{paused && (
|
||||||
|
// 일시정지 (두 막대)
|
||||||
|
<>
|
||||||
|
<circle cx={cx + r * 0.8} cy={cy - r * 0.8} r="5" fill="#8a948c" stroke="#fff" strokeWidth="1" />
|
||||||
|
<line x1={cx + r * 0.8 - 1.4} y1={cy - r * 0.8 - 2} x2={cx + r * 0.8 - 1.4} y2={cy - r * 0.8 + 2} stroke="#fff" strokeWidth="1.2" strokeLinecap="round" />
|
||||||
|
<line x1={cx + r * 0.8 + 1.4} y1={cy - r * 0.8 - 2} x2={cx + r * 0.8 + 1.4} y2={cy - r * 0.8 + 2} stroke="#fff" strokeWidth="1.2" strokeLinecap="round" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -263,7 +276,7 @@ export default function EfficiencyScatter({ rows, onSelect, selectedId }) {
|
||||||
: `${Math.round(d.oil).toLocaleString()} L` },
|
: `${Math.round(d.oil).toLocaleString()} L` },
|
||||||
{ ic: 'dist', t: d.kmText || `${d.km} km` },
|
{ ic: 'dist', t: d.kmText || `${d.km} km` },
|
||||||
{ ic: 'visit', t: d.targetCount > 0 ? `${d.completed} / ${d.targetCount}` : '방문 목표 없음' },
|
{ 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 lineH = 16
|
||||||
const tw = 168, th = 24 + rows.length * lineH
|
const tw = 168, th = 24 + rows.length * lineH
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue