164 lines
5.6 KiB
TypeScript
164 lines
5.6 KiB
TypeScript
/** 우측 상세 탭의 표시·분류 모델을 지도 조회 수명에서 분리한다. */
|
|
import type { ExmnMapJobType } from "../exmn/exmnMapTypes";
|
|
import type { SinkholeDetail, SinkholeNearbyPipe } from "../sinkhole/sinkholeTypes";
|
|
|
|
export type RightPanelTabKind = "feature" | "sinkhole" | "gisEdit";
|
|
export type RightPanelTabCategory =
|
|
| "sinkhole"
|
|
| "pipe"
|
|
| "exmnSurvey"
|
|
| "treatmentFacility"
|
|
| "treatmentZone"
|
|
| "defaultFeature"
|
|
| "gisEdit";
|
|
|
|
export type RightPanelTab = {
|
|
id: string;
|
|
kind: RightPanelTabKind;
|
|
category: RightPanelTabCategory;
|
|
displayLabel: string;
|
|
surveyJobType?: ExmnMapJobType;
|
|
parentTabId?: string | null;
|
|
featureData: any;
|
|
sinkholeDetail?: SinkholeDetail | null;
|
|
nearbyPipes?: SinkholeNearbyPipe[];
|
|
gisEditChangeId?: string | null;
|
|
lastActiveAt: number;
|
|
};
|
|
|
|
export type RightPanelTabStyle = {
|
|
active: string;
|
|
inactive: string;
|
|
};
|
|
|
|
export type RightPanelBookmarkTabsProps = {
|
|
tabs: RightPanelTab[];
|
|
activeTabId: string | null;
|
|
onActivate: (tabId: string) => void;
|
|
onBack: (tabId: string) => void;
|
|
};
|
|
|
|
// 지도 객체 유형과 조사구분을 식별하는 범례 의미색이므로 테마와 무관한 정적 class를 유지한다.
|
|
export const RIGHT_PANEL_TAB_STYLE_BY_CATEGORY: Record<
|
|
RightPanelTabCategory,
|
|
RightPanelTabStyle
|
|
> = {
|
|
sinkhole: {
|
|
active: "border-[#d97706] bg-[#d97706] text-white",
|
|
inactive: "border-[#fdba74] bg-[#fff7ed] text-[#9a3412] hover:bg-[#ffedd5]",
|
|
},
|
|
pipe: {
|
|
active: "border-[#2563eb] bg-[#2563eb] text-white",
|
|
inactive: "border-[#93c5fd] bg-[#eff6ff] text-[#1e40af] hover:bg-[#dbeafe]",
|
|
},
|
|
exmnSurvey: {
|
|
active: "border-[#0f766e] bg-[#0f766e] text-white",
|
|
inactive: "border-[#5eead4] bg-[#f0fdfa] text-[#115e59] hover:bg-[#ccfbf1]",
|
|
},
|
|
treatmentFacility: {
|
|
active: "border-[#15803d] bg-[#15803d] text-white",
|
|
inactive: "border-[#86efac] bg-[#f0fdf4] text-[#166534] hover:bg-[#dcfce7]",
|
|
},
|
|
treatmentZone: {
|
|
active: "border-[#7e22ce] bg-[#7e22ce] text-white",
|
|
inactive: "border-[#d8b4fe] bg-[#faf5ff] text-[#6b21a8] hover:bg-[#f3e8ff]",
|
|
},
|
|
gisEdit: {
|
|
active: "border-[#0f766e] bg-[#0f766e] text-white",
|
|
inactive: "border-[#5eead4] bg-[#f0fdfa] text-[#115e59] hover:bg-[#ccfbf1]",
|
|
},
|
|
defaultFeature: {
|
|
active: "border-[#475569] bg-[#475569] text-white",
|
|
inactive: "border-[#cbd5e1] bg-[#f8fafc] text-[#334155] hover:bg-[#f1f5f9]",
|
|
},
|
|
};
|
|
|
|
export const RIGHT_PANEL_EXMN_TAB_STYLE_BY_JOB_TYPE: Record<
|
|
ExmnMapJobType,
|
|
RightPanelTabStyle
|
|
> = {
|
|
PPL: {
|
|
active: "border-[#eab308] bg-[#facc15] text-[#713f12]",
|
|
inactive: "border-[#fde68a] bg-[#fefce8] text-[#854d0e] hover:bg-[#fef9c3]",
|
|
},
|
|
MNHL: {
|
|
active: "border-[#8b5cf6] bg-[#8b5cf6] text-white",
|
|
inactive: "border-[#c4b5fd] bg-[#f5f3ff] text-[#5b21b6] hover:bg-[#ede9fe]",
|
|
},
|
|
};
|
|
|
|
// 공용 우측 탭은 유형과 활성 상태에 관계없이 먼저 열린 탭부터 제거한다.
|
|
export const findRightPanelTabOverflowCandidate = (
|
|
tabs: RightPanelTab[],
|
|
nextTabId: string,
|
|
) => {
|
|
return tabs.find((tab) => tab.id !== nextTabId) ?? null;
|
|
};
|
|
|
|
// 부모 지반침하가 제거되면 그 부모에서 열린 연계 관로도 같은 탭 그룹으로 제거한다.
|
|
export const removeRightPanelTabGroup = (
|
|
tabs: RightPanelTab[],
|
|
removedTabId: string,
|
|
) =>
|
|
tabs.filter(
|
|
(tab) => tab.id !== removedTabId && tab.parentTabId !== removedTabId,
|
|
);
|
|
|
|
// 오른쪽 상세 패널에 상세가 2개 이상 쌓였을 때만 책갈피형 전환 버튼을 보여준다.
|
|
export const RightPanelBookmarkTabs = ({
|
|
tabs,
|
|
activeTabId,
|
|
onActivate,
|
|
onBack,
|
|
}: RightPanelBookmarkTabsProps) => {
|
|
if (tabs.length < 2) return null;
|
|
|
|
return (
|
|
<div className="pointer-events-auto absolute right-[350px] top-[92px] z-[70] flex w-[112px] flex-col items-end gap-2">
|
|
{tabs.map((tab) => {
|
|
const isActive = tab.id === activeTabId;
|
|
const style =
|
|
tab.category === "exmnSurvey" && tab.surveyJobType
|
|
? RIGHT_PANEL_EXMN_TAB_STYLE_BY_JOB_TYPE[tab.surveyJobType]
|
|
: RIGHT_PANEL_TAB_STYLE_BY_CATEGORY[tab.category];
|
|
const parentTab = tab.parentTabId
|
|
? tabs.find((candidate) => candidate.id === tab.parentTabId)
|
|
: null;
|
|
|
|
return (
|
|
<div
|
|
key={tab.id}
|
|
className={[
|
|
"flex min-h-[42px] w-[112px] items-center border border-r-0 px-1 py-1 shadow-sm transition-colors",
|
|
isActive ? style.active : style.inactive,
|
|
].join(" ")}
|
|
>
|
|
{tab.category === "pipe" && parentTab ? (
|
|
<button
|
|
type="button"
|
|
className="mr-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-sm font-bold transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1"
|
|
title={parentTab.displayLabel + "로 돌아가기"}
|
|
aria-label={parentTab.displayLabel + "로 돌아가기"}
|
|
onClick={() => onBack(parentTab.id)}
|
|
>
|
|
←
|
|
</button>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className="inline-flex min-w-0 flex-1 items-center justify-center self-stretch px-1 text-center text-[0.82rem] font-semibold leading-tight"
|
|
title={tab.displayLabel}
|
|
aria-pressed={isActive}
|
|
onClick={() => onActivate(tab.id)}
|
|
>
|
|
<span className="block max-h-[2.5rem] overflow-hidden break-all">
|
|
{tab.displayLabel}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|