281 lines
9.4 KiB
TypeScript
281 lines
9.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Plus, Star, Loader2 } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
|
import { dataApi } from "@/lib/api/data";
|
|
import type { RelatedDataButtonsConfig, ButtonItem } from "./types";
|
|
|
|
interface RelatedDataButtonsComponentProps {
|
|
config: RelatedDataButtonsConfig;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
}
|
|
|
|
export const RelatedDataButtonsComponent: React.FC<RelatedDataButtonsComponentProps> = ({
|
|
config,
|
|
className,
|
|
style,
|
|
}) => {
|
|
const [buttons, setButtons] = useState<ButtonItem[]>([]);
|
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [masterData, setMasterData] = useState<Record<string, any> | null>(null);
|
|
|
|
// SplitPanel Context 연결
|
|
const splitPanelContext = useSplitPanelContext();
|
|
|
|
// 좌측 패널에서 선택된 데이터 감지
|
|
useEffect(() => {
|
|
if (!splitPanelContext?.selectedLeftData) {
|
|
setMasterData(null);
|
|
setButtons([]);
|
|
setSelectedId(null);
|
|
return;
|
|
}
|
|
|
|
setMasterData(splitPanelContext.selectedLeftData);
|
|
}, [splitPanelContext?.selectedLeftData]);
|
|
|
|
// 버튼 데이터 로드
|
|
const loadButtons = useCallback(async () => {
|
|
if (!masterData || !config.buttonDataSource?.tableName) {
|
|
return;
|
|
}
|
|
|
|
const filterValue = masterData[config.sourceMapping.sourceColumn];
|
|
if (!filterValue) {
|
|
setButtons([]);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const { tableName, filterColumn, displayColumn, valueColumn, orderColumn, orderDirection } = config.buttonDataSource;
|
|
|
|
const response = await dataApi.getTableData(tableName, {
|
|
filters: { [filterColumn]: filterValue },
|
|
sortBy: orderColumn || "created_date",
|
|
sortOrder: (orderDirection?.toLowerCase() || "asc") as "asc" | "desc",
|
|
size: 50,
|
|
});
|
|
|
|
if (response.data && response.data.length > 0) {
|
|
const defaultConfig = config.buttonStyle?.defaultIndicator;
|
|
|
|
const items: ButtonItem[] = response.data.map((row: Record<string, any>) => {
|
|
let isDefault = false;
|
|
if (defaultConfig?.column) {
|
|
const val = row[defaultConfig.column];
|
|
const checkValue = defaultConfig.value || "Y";
|
|
isDefault = val === checkValue || val === true || val === "true";
|
|
}
|
|
|
|
return {
|
|
id: row.id || row[valueColumn || "id"],
|
|
displayText: row[displayColumn] || row.id,
|
|
value: row[valueColumn || "id"],
|
|
isDefault,
|
|
rawData: row,
|
|
};
|
|
});
|
|
|
|
setButtons(items);
|
|
|
|
// 자동 선택: 기본 항목 또는 첫 번째 항목
|
|
if (config.autoSelectFirst && items.length > 0) {
|
|
const defaultItem = items.find(item => item.isDefault);
|
|
const targetItem = defaultItem || items[0];
|
|
setSelectedId(targetItem.id);
|
|
emitSelection(targetItem);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("RelatedDataButtons 데이터 로드 실패:", error);
|
|
setButtons([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [masterData, config.buttonDataSource, config.sourceMapping, config.buttonStyle, config.autoSelectFirst]);
|
|
|
|
// masterData 변경 시 버튼 로드
|
|
useEffect(() => {
|
|
if (masterData) {
|
|
setSelectedId(null); // 마스터 변경 시 선택 초기화
|
|
loadButtons();
|
|
}
|
|
}, [masterData, loadButtons]);
|
|
|
|
// 선택 이벤트 발생
|
|
const emitSelection = useCallback((item: ButtonItem) => {
|
|
if (!config.events?.targetTable || !config.events?.targetFilterColumn) {
|
|
return;
|
|
}
|
|
|
|
// 커스텀 이벤트 발생 (하위 테이블 필터링용)
|
|
window.dispatchEvent(new CustomEvent("related-button-select", {
|
|
detail: {
|
|
targetTable: config.events.targetTable,
|
|
filterColumn: config.events.targetFilterColumn,
|
|
filterValue: item.value,
|
|
selectedData: item.rawData,
|
|
},
|
|
}));
|
|
|
|
console.log("📌 RelatedDataButtons 선택 이벤트:", {
|
|
targetTable: config.events.targetTable,
|
|
filterColumn: config.events.targetFilterColumn,
|
|
filterValue: item.value,
|
|
});
|
|
}, [config.events]);
|
|
|
|
// 버튼 클릭 핸들러
|
|
const handleButtonClick = useCallback((item: ButtonItem) => {
|
|
setSelectedId(item.id);
|
|
emitSelection(item);
|
|
}, [emitSelection]);
|
|
|
|
// 추가 버튼 클릭
|
|
const handleAddClick = useCallback(() => {
|
|
if (!config.addButton?.modalScreenId) return;
|
|
|
|
const filterValue = masterData?.[config.sourceMapping.sourceColumn];
|
|
|
|
window.dispatchEvent(new CustomEvent("open-screen-modal", {
|
|
detail: {
|
|
screenId: config.addButton.modalScreenId,
|
|
initialData: {
|
|
[config.buttonDataSource.filterColumn]: filterValue,
|
|
},
|
|
onSuccess: () => {
|
|
loadButtons(); // 모달 성공 후 새로고침
|
|
},
|
|
},
|
|
}));
|
|
}, [config.addButton, config.buttonDataSource.filterColumn, config.sourceMapping.sourceColumn, masterData, loadButtons]);
|
|
|
|
// 버튼 variant 계산
|
|
const getButtonVariant = useCallback((item: ButtonItem): "default" | "outline" | "secondary" | "ghost" => {
|
|
if (selectedId === item.id) {
|
|
return config.buttonStyle?.activeVariant || "default";
|
|
}
|
|
return config.buttonStyle?.variant || "outline";
|
|
}, [selectedId, config.buttonStyle]);
|
|
|
|
// 마스터 데이터 없음
|
|
if (!masterData) {
|
|
return (
|
|
<div className={cn("rounded-lg border bg-card p-4", className)} style={style}>
|
|
<p className="text-sm text-muted-foreground text-center">
|
|
좌측에서 항목을 선택하세요
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const headerConfig = config.headerDisplay;
|
|
const addButtonConfig = config.addButton;
|
|
|
|
return (
|
|
<div className={cn("rounded-lg border bg-card", className)} style={style}>
|
|
{/* 헤더 영역 */}
|
|
{headerConfig?.show !== false && (
|
|
<div className="flex items-start justify-between p-4 pb-3">
|
|
<div>
|
|
{/* 제목 (품목명 등) */}
|
|
{headerConfig?.titleColumn && masterData[headerConfig.titleColumn] && (
|
|
<h3 className="text-lg font-semibold">
|
|
{masterData[headerConfig.titleColumn]}
|
|
</h3>
|
|
)}
|
|
{/* 부제목 (품목코드 등) */}
|
|
{headerConfig?.subtitleColumn && masterData[headerConfig.subtitleColumn] && (
|
|
<p className="text-sm text-muted-foreground">
|
|
{masterData[headerConfig.subtitleColumn]}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 헤더 위치 추가 버튼 */}
|
|
{addButtonConfig?.show && addButtonConfig?.position === "header" && (
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
onClick={handleAddClick}
|
|
className="bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
<Plus className="mr-1 h-4 w-4" />
|
|
{addButtonConfig.label || "버전 추가"}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* 버튼 영역 */}
|
|
<div className="px-4 pb-4">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-4">
|
|
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : buttons.length === 0 ? (
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-sm text-muted-foreground">
|
|
{config.emptyMessage || "데이터가 없습니다"}
|
|
</p>
|
|
{/* 인라인 추가 버튼 (데이터 없을 때) */}
|
|
{addButtonConfig?.show && addButtonConfig?.position !== "header" && (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleAddClick}
|
|
className="border-dashed"
|
|
>
|
|
<Plus className="mr-1 h-4 w-4" />
|
|
{addButtonConfig.label || "추가"}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{buttons.map((item) => (
|
|
<Button
|
|
key={item.id}
|
|
variant={getButtonVariant(item)}
|
|
size={config.buttonStyle?.size || "default"}
|
|
onClick={() => handleButtonClick(item)}
|
|
className={cn(
|
|
"relative",
|
|
selectedId === item.id && "ring-2 ring-primary ring-offset-1"
|
|
)}
|
|
>
|
|
{/* 기본 버전 별표 */}
|
|
{item.isDefault && config.buttonStyle?.defaultIndicator?.showStar && (
|
|
<Star className="mr-1.5 h-3.5 w-3.5 fill-yellow-400 text-yellow-400" />
|
|
)}
|
|
{item.displayText}
|
|
</Button>
|
|
))}
|
|
|
|
{/* 인라인 추가 버튼 */}
|
|
{addButtonConfig?.show && addButtonConfig?.position !== "header" && (
|
|
<Button
|
|
variant="outline"
|
|
size={config.buttonStyle?.size || "default"}
|
|
onClick={handleAddClick}
|
|
className="border-dashed"
|
|
>
|
|
<Plus className="mr-1 h-4 w-4" />
|
|
{addButtonConfig.label || "추가"}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RelatedDataButtonsComponent;
|