You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
5.2 KiB
TypeScript

// frontend/components/journey/JourneyClient.tsx — 여정 페이지 오케스트레이터 (Topbar 는 셸)
"use client";
import { useEffect, useRef } from "react";
import { useJourney } from "@/lib/hooks/useJourney";
import { Icon } from "@/components/Icon";
import { JourneyBoard } from "./JourneyBoard";
import { PeopleStack } from "./PeopleStack";
import { JourneyTable } from "./JourneyTable";
import { Gauge } from "./Gauge";
export function JourneyClient() {
const { data, isLoading, error, retry } = useJourney();
const rootRef = useRef<HTMLDivElement>(null);
// 진입 애니메이션 (원본 entered)
useEffect(() => {
const el = rootRef.current;
const id = requestAnimationFrame(() => el && el.classList.add("entered"));
return () => cancelAnimationFrame(id);
}, [data]);
if (error && !data) {
return (
<div className="journeypage">
<div className="jx-error" role="alert">
<Icon name="x" />
<button onClick={() => retry()} type="button">
</button>
</div>
</div>
);
}
if (isLoading || !data) {
return (
<div className="journeypage">
<div className="jx">
<div className="jx-panel jx-skeleton" aria-busy="true" />
</div>
</div>
);
}
const { header, meta, people, stages, cards, links, rows, table, gauges, gaugeFoot } = data;
return (
<div className="journeypage">
<div className="jx" ref={rootRef}>
{/* ===== 페이지 헤더 ===== */}
<div className="pagehead">
<div>
<div className="ph-eyebrow">
<span>{header.dateLabel}</span>
<span className="sep" />
<span className="wx">
<Icon name="sun" />
{header.weather}
</span>
</div>
<h1 className="ph-title"> </h1>
</div>
<div className="ph-search">
<Icon name="search" />
<input placeholder="흐름에서 작업·사람 찾기…" aria-label="흐름에서 작업·사람 찾기" />
</div>
</div>
{/* ===== 흐름 패널 ===== */}
<section className="jx-panel">
<div className="jx-top">
<div className="jx-label">
{" "}
<span>
· {meta.stageCount} · {meta.itemCount} · {meta.collabCount}
</span>
</div>
<PeopleStack people={people} more={meta.peopleMore} />
<div className="jx-actions">
<button className="jx-act" type="button" aria-label="추가">
<Icon name="plus" />
</button>
<button className="jx-act" type="button" aria-label="공유">
<Icon name="share" />
</button>
<button className="jx-act fill" type="button" aria-label="일정">
<Icon name="cal" />
</button>
</div>
</div>
<JourneyBoard stages={stages} cards={cards} links={links} people={people} />
<div className="jx-labels">
{stages.map((s) => (
<div
className="cl"
key={s.id}
style={{ color: s.tone === "ink" ? "var(--ink)" : `var(--${s.tone})` }}
>
{s.title}
<span>{s.time}</span>
</div>
))}
</div>
<div className="jx-hint">
<Icon name="link" /> · ·
</div>
</section>
{/* ===== 하단 2열 ===== */}
<section className="jx-bottom">
<div className="card">
<div className="jx-phead">
<div>
<div className="pt"> </div>
<div className="ps">{table.subtitle}</div>
</div>
<div className="pa">
<button className="jx-act" type="button" aria-label="추가">
<Icon name="plus" />
</button>
<button className="jx-act" type="button" aria-label="공유">
<Icon name="share" />
</button>
</div>
</div>
<JourneyTable rows={rows} people={people} />
</div>
<div className="card">
<div className="jx-phead">
<div>
<div className="pt"> </div>
<div className="ps"> · </div>
</div>
<div className="pa">
<button className="jx-act" type="button" aria-label="더보기">
<Icon name="more" />
</button>
</div>
</div>
<div className="gauges">
{gauges.map((g, i) => (
<Gauge key={i} {...g} />
))}
</div>
<div className="gauge-foot">
<Icon name="drop" />
{gaugeFoot}
</div>
</div>
</section>
</div>
</div>
);
}