|
|
|
|
@ -27,9 +27,6 @@ const DETAIL = {
|
|
|
|
|
date: "2025-06-10 14:30", |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const PHASES = ["list", "detail", "confirm", "done"]; |
|
|
|
|
const PHASE_DURATION = { list: 1800, detail: 2200, confirm: 1800, done: 2000 }; |
|
|
|
|
|
|
|
|
|
function UtmSystemPanel({ phase, activeRow }) { |
|
|
|
|
return ( |
|
|
|
|
<div className="utm-panel"> |
|
|
|
|
@ -72,13 +69,8 @@ function UtmSystemPanel({ phase, activeRow }) {
|
|
|
|
|
))} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
{/* 상세: 아래로 펼쳐짐 */} |
|
|
|
|
<div |
|
|
|
|
className={`utm-panel__detail${ |
|
|
|
|
phase === "detail" || phase === "confirm" || phase === "done" |
|
|
|
|
? " utm-panel__detail--show" |
|
|
|
|
: "" |
|
|
|
|
}`} |
|
|
|
|
className={`utm-panel__detail${phase === "detail" || phase === "confirm" || phase === "done" ? " utm-panel__detail--show" : ""}`} |
|
|
|
|
> |
|
|
|
|
<div className="utm-panel__detail-title">비행계획 상세</div> |
|
|
|
|
<div className="utm-panel__detail-rows"> |
|
|
|
|
@ -109,9 +101,7 @@ function UtmSystemPanel({ phase, activeRow }) {
|
|
|
|
|
<div className="utm-panel__detail-row"> |
|
|
|
|
<span>상태</span> |
|
|
|
|
<span |
|
|
|
|
className={`utm-panel__status utm-panel__status--${ |
|
|
|
|
phase === "done" ? "done" : "wait" |
|
|
|
|
}`} |
|
|
|
|
className={`utm-panel__status utm-panel__status--${phase === "done" ? "done" : "wait"}`} |
|
|
|
|
> |
|
|
|
|
{phase === "done" ? "승인" : "대기"} |
|
|
|
|
</span> |
|
|
|
|
@ -142,7 +132,6 @@ function UtmSystemPanel({ phase, activeRow }) {
|
|
|
|
|
</AnimatePresence> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
{/* 컨펌: 패널 안에서 블러 오버레이 */} |
|
|
|
|
<AnimatePresence> |
|
|
|
|
{phase === "confirm" && ( |
|
|
|
|
<motion.div |
|
|
|
|
@ -185,21 +174,7 @@ function MainUtm() {
|
|
|
|
|
const [activeRow] = useState(0); |
|
|
|
|
const cursorRef = useRef(null); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (!inView) return; |
|
|
|
|
let t; |
|
|
|
|
const run = (current) => { |
|
|
|
|
const next = PHASES[(PHASES.indexOf(current) + 1) % PHASES.length]; |
|
|
|
|
t = setTimeout(() => { |
|
|
|
|
setPhase(next); |
|
|
|
|
run(next); |
|
|
|
|
}, PHASE_DURATION[current]); |
|
|
|
|
}; |
|
|
|
|
t = setTimeout(() => run("list"), 1000); |
|
|
|
|
return () => clearTimeout(t); |
|
|
|
|
}, [inView]); |
|
|
|
|
|
|
|
|
|
// 커서 이동 로직 |
|
|
|
|
// ← useEffect 딱 하나만! |
|
|
|
|
useEffect(() => { |
|
|
|
|
if (!inView) return; |
|
|
|
|
|
|
|
|
|
@ -207,7 +182,13 @@ function MainUtm() {
|
|
|
|
|
const cursor = cursorRef.current; |
|
|
|
|
if (!showcase || !cursor) return; |
|
|
|
|
|
|
|
|
|
let timers = []; |
|
|
|
|
let stopped = false; |
|
|
|
|
const ids = []; |
|
|
|
|
|
|
|
|
|
function sid(id) { |
|
|
|
|
ids.push(id); |
|
|
|
|
return id; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getPos(selector) { |
|
|
|
|
const el = ref.current?.querySelector(selector); |
|
|
|
|
@ -224,64 +205,68 @@ function MainUtm() {
|
|
|
|
|
cursor.style.transform = `translate(${x}px, ${y}px)`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function click() { |
|
|
|
|
function doClick() { |
|
|
|
|
cursor.classList.add("utm-cursor--click"); |
|
|
|
|
setTimeout(() => cursor.classList.remove("utm-cursor--click"), 250); |
|
|
|
|
sid(setTimeout(() => cursor.classList.remove("utm-cursor--click"), 250)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function after(ms, fn) { |
|
|
|
|
const t = setTimeout(fn, ms); |
|
|
|
|
timers.push(t); |
|
|
|
|
function schedule(ms, fn) { |
|
|
|
|
sid( |
|
|
|
|
setTimeout(() => { |
|
|
|
|
if (!stopped) fn(); |
|
|
|
|
}, ms), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function loop() { |
|
|
|
|
timers.forEach(clearTimeout); |
|
|
|
|
timers = []; |
|
|
|
|
function run() { |
|
|
|
|
if (stopped) return; |
|
|
|
|
|
|
|
|
|
setPhase("list"); |
|
|
|
|
after(1000, () => { |
|
|
|
|
moveTo(180, 160); |
|
|
|
|
|
|
|
|
|
// 상세보기로 이동 |
|
|
|
|
schedule(1000, () => { |
|
|
|
|
const p = getPos(".utm-panel__row--active .utm-panel__btn"); |
|
|
|
|
if (p) moveTo(p.x, p.y); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
after(2000, () => { |
|
|
|
|
click(); |
|
|
|
|
after(200, () => setPhase("detail")); |
|
|
|
|
}); |
|
|
|
|
// 상세보기 클릭 → detail |
|
|
|
|
schedule(2000, () => doClick()); |
|
|
|
|
schedule(2200, () => setPhase("detail")); |
|
|
|
|
|
|
|
|
|
after(3200, () => { |
|
|
|
|
// 승인처리로 이동 |
|
|
|
|
schedule(3200, () => { |
|
|
|
|
const p = getPos(".utm-panel__approve-btn"); |
|
|
|
|
if (p) moveTo(p.x, p.y); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
after(4200, () => { |
|
|
|
|
click(); |
|
|
|
|
after(200, () => setPhase("confirm")); |
|
|
|
|
}); |
|
|
|
|
// 승인처리 클릭 → confirm |
|
|
|
|
schedule(4200, () => doClick()); |
|
|
|
|
schedule(4400, () => setPhase("confirm")); |
|
|
|
|
|
|
|
|
|
after(5000, () => { |
|
|
|
|
// 확인 버튼으로 이동 |
|
|
|
|
schedule(5000, () => { |
|
|
|
|
const p = getPos(".utm-confirm__ok"); |
|
|
|
|
if (p) moveTo(p.x, p.y); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
after(5800, () => { |
|
|
|
|
click(); |
|
|
|
|
after(200, () => setPhase("done")); |
|
|
|
|
}); |
|
|
|
|
// 확인 클릭 → done |
|
|
|
|
schedule(5800, () => doClick()); |
|
|
|
|
schedule(6000, () => setPhase("done")); |
|
|
|
|
|
|
|
|
|
after(7500, () => loop()); |
|
|
|
|
// 반복 |
|
|
|
|
schedule(7500, () => run()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
moveTo(180, 160); |
|
|
|
|
after(500, () => loop()); |
|
|
|
|
run(); |
|
|
|
|
|
|
|
|
|
return () => { |
|
|
|
|
timers.forEach(clearTimeout); |
|
|
|
|
timers = []; |
|
|
|
|
stopped = true; |
|
|
|
|
ids.forEach(clearTimeout); |
|
|
|
|
cursor.classList.remove("utm-cursor--click"); |
|
|
|
|
setPhase("list"); // ← cleanup에서만 호출 |
|
|
|
|
}; |
|
|
|
|
}, [inView]); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<section className="utm-hero" ref={ref}> |
|
|
|
|
<div className="utm-hero__blob utm-hero__blob--1" /> |
|
|
|
|
@ -349,7 +334,6 @@ function MainUtm() {
|
|
|
|
|
|
|
|
|
|
<UtmSystemPanel phase={phase} activeRow={activeRow} /> |
|
|
|
|
|
|
|
|
|
{/* 커서 */} |
|
|
|
|
<div className="utm-cursor" ref={cursorRef}> |
|
|
|
|
<div className="utm-cursor__dot" /> |
|
|
|
|
</div> |
|
|
|
|
|