Files
frontend/apps/sn/app/(map-layout)/map/components/gisEdit/GisEditSessionPanel.tsx
T

211 lines
7.1 KiB
TypeScript

/**
* 화면명: 하수관로 편집 세션
* 현재 편집 대상과 변경 상태를 표시하고 저장·취소 동작을 제공한다.
*/
"use client";
import { Button } from "@common/components";
import type { GisEditSessionSnapshot, GisEditTool } from "./gisEditTypes";
import { useGisEditOperationLabels } from "./useGisEditCommonCodes";
type Props = GisEditSessionSnapshot & {
onRemoveFocusedChange: () => void;
onRemoveLastChange: () => void;
onClearChanges: () => void;
onFocusChange: (changeId: string) => void;
onOpenPopup?: () => void;
variant?: "inline" | "popup";
className?: string;
};
const buildChangeItemClassName = (isFocused: boolean) =>
`block w-full rounded-md border px-3 py-2.5 text-left text-[1.1rem] transition ${
isFocused
? "border-primary bg-primary-transparent shadow-[inset_0_0_0_1px_var(--primary-transparent)]"
: "border-border bg-background hover:border-primary hover:bg-background"
}`;
const getChangeLabel = (
change: GisEditSessionSnapshot["changes"][number],
operationLabels: Record<GisEditTool, string>,
) =>
change.attributeOnly
? "상세정보수정"
: operationLabels[change.editType];
const getChangeSummary = (
change: GisEditSessionSnapshot["changes"][number],
) => {
if (change.attributeOnly) return "변경한 상세정보를 확인하세요.";
if (change.editType === "ADD") return "추가한 객체 위치를 확인하세요.";
if (change.editType === "MOVE") return "객체를 드래그해 위치를 조정하세요.";
if (change.editType === "VERTEX_EDIT")
return "점을 옮기거나 선분을 끌어 추가하세요.";
return "삭제할 객체가 맞는지 확인하세요.";
};
type ChangeListProps = {
changes: GisEditSessionSnapshot["changes"];
focusedChangeId: string | null;
operationLabels: Record<GisEditTool, string>;
onFocusChange: (changeId: string) => void;
};
const ChangeList = ({
changes,
focusedChangeId,
operationLabels,
onFocusChange,
}: ChangeListProps) => (
<>
{changes.map((change, index) => (
<button
key={change.changeId}
type="button"
className={buildChangeItemClassName(
focusedChangeId === change.changeId,
)}
onClick={() => onFocusChange(change.changeId)}
>
<div className="flex items-center gap-1">
<div className="shrink-0 whitespace-nowrap text-[1.1rem] font-semibold leading-4 text-label">
{index + 1}. {getChangeLabel(change, operationLabels)} /{" "}
{change.objectLabel}
</div>
<div className="ml-auto flex shrink-0 items-center gap-1">
{change.basicInfoSavedAt && focusedChangeId !== change.changeId ? (
<span className="whitespace-nowrap rounded-full border border-emerald-200 bg-background px-1.5 py-0.5 text-[0.9rem] font-semibold text-emerald-700">
정보저장
</span>
) : null}
{focusedChangeId === change.changeId ? (
<span className="whitespace-nowrap rounded-full border border-primary bg-background px-1.5 py-0.5 text-[0.9rem] font-semibold text-primary">
선택됨
</span>
) : null}
</div>
</div>
<div className="mt-1 truncate text-[1rem] text-muted-foreground">
{getChangeSummary(change)}
</div>
</button>
))}
</>
);
export default function GisEditSessionPanel({
isEditMode,
selectedLayerName,
activeTool,
changes,
canUndo,
focusedChangeId,
onRemoveFocusedChange,
onRemoveLastChange,
onClearChanges,
onFocusChange,
onOpenPopup,
variant = "inline",
className = "",
}: Props) {
// 인라인·윈도우 팝업 세션 모두 동일한 CMS 공통코드 라벨을 공유한다.
const operationLabels = useGisEditOperationLabels();
const isCompactInline = variant === "inline" && changes.length === 0;
return (
<div
className={`flex min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-[rgba(255,255,255,0.98)] text-label shadow-[0_10px_26px_rgba(23,42,70,0.16)] ${isCompactInline ? "min-h-[68px]" : "p-3"} ${className}`.trim()}
>
<div
className={`flex shrink-0 justify-between gap-2 ${isCompactInline ? "items-center bg-container px-3 py-2.5" : "mb-3 items-start rounded-md bg-container px-3 py-2.5"}`}
>
<div className="min-w-0">
{variant === "inline" ? (
<div className="mb-1 flex items-center gap-2 text-[1.3rem] font-semibold text-label">
<span>편집 세션</span>
<span className="rounded-full bg-primary px-2 py-0.5 text-[0.9rem] font-semibold text-primary-foreground">
{changes.length}건
</span>
</div>
) : null}
<div className="truncate text-[1.05rem] text-muted-foreground">
{selectedLayerName || "레이어 미선택"} ·{" "}
{isEditMode
? activeTool
? operationLabels[activeTool]
: "대기"
: "편집 모드 종료"}
</div>
</div>
{onOpenPopup ? (
<Button
type="button"
color="white"
onClick={onOpenPopup}
title="편집 세션을 기존 윈도우 팝업으로 열기"
>
<span aria-hidden="true" className="text-[1.1rem] leading-none">
↗
</span>
<span>팝업</span>
</Button>
) : null}
</div>
<div
className={`${isCompactInline ? "hidden" : "mb-3 flex"} shrink-0 flex-wrap items-center gap-2 rounded-md border border-border bg-background p-2`}
>
<Button
type="button"
color="white"
disabled={!focusedChangeId}
onClick={onRemoveFocusedChange}
>
선택 취소
</Button>
<Button
type="button"
color="white"
disabled={!canUndo}
onClick={onRemoveLastChange}
>
최근 취소
</Button>
<Button
type="button"
color="white"
disabled={!canUndo}
onClick={onClearChanges}
>
전체 초기화
</Button>
<div className="ml-auto rounded-full border border-border bg-background px-2.5 py-1 text-[1rem] font-semibold text-muted-foreground">
총 {changes.length}건
</div>
</div>
<div
className={`${isCompactInline ? "hidden" : "min-h-0 flex-1"} overflow-hidden`}
>
{changes.length === 0 ? (
<div className="rounded-md border border-dashed border-border bg-background px-3 py-5 text-center text-[1.1rem] text-muted-foreground">
저장할 편집 변경분이 없습니다.
</div>
) : (
<div className="h-full overflow-y-auto pr-1">
<div className="space-y-2">
<ChangeList
changes={changes}
focusedChangeId={focusedChangeId}
operationLabels={operationLabels}
onFocusChange={onFocusChange}
/>
</div>
</div>
)}
</div>
</div>
);
}