범용폼모달 외부소스 지원

This commit is contained in:
kjs
2026-01-07 16:10:11 +09:00
parent 777429af48
commit 47ac9ecd8a
5 changed files with 525 additions and 89 deletions

View File

@@ -710,6 +710,9 @@ interface ColumnSettingItemProps {
displayColumns: string[]; // 검색 설정에서 선택한 표시 컬럼 목록
sourceTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // 소스 테이블 컬럼
sourceTableName: string; // 소스 테이블명
externalTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // 외부 데이터 테이블 컬럼
externalTableName?: string; // 외부 데이터 테이블명
externalDataEnabled?: boolean; // 외부 데이터 소스 활성화 여부
tables: { table_name: string; comment?: string }[]; // 전체 테이블 목록
tableColumns: Record<string, { column_name: string; data_type: string; is_nullable: string; comment?: string }[]>; // 테이블별 컬럼
sections: { id: string; title: string }[]; // 섹션 목록
@@ -731,6 +734,9 @@ function ColumnSettingItem({
displayColumns,
sourceTableColumns,
sourceTableName,
externalTableColumns,
externalTableName,
externalDataEnabled,
tables,
tableColumns,
sections,
@@ -745,6 +751,7 @@ function ColumnSettingItem({
}: ColumnSettingItemProps) {
const [fieldSearchOpen, setFieldSearchOpen] = useState(false);
const [sourceFieldSearchOpen, setSourceFieldSearchOpen] = useState(false);
const [externalFieldSearchOpen, setExternalFieldSearchOpen] = useState(false);
const [parentFieldSearchOpen, setParentFieldSearchOpen] = useState(false);
const [lookupTableOpenMap, setLookupTableOpenMap] = useState<Record<string, boolean>>({});
@@ -1014,6 +1021,88 @@ function ColumnSettingItem({
</Popover>
</div>
{/* 외부 필드 - Combobox (외부 데이터에서 가져올 컬럼) */}
{externalDataEnabled && externalTableName && (
<div>
<Label className="text-xs"> </Label>
<Popover open={externalFieldSearchOpen} onOpenChange={setExternalFieldSearchOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={externalFieldSearchOpen}
className="h-8 w-full justify-between text-xs mt-1"
disabled={externalTableColumns.length === 0}
>
<span className="truncate">
{col.externalField || "(필드명과 동일)"}
</span>
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[250px]" align="start">
<Command>
<CommandInput placeholder="외부 필드 검색..." className="text-xs" />
<CommandList className="max-h-[200px]">
<CommandEmpty className="text-xs py-4 text-center">
.
</CommandEmpty>
<CommandGroup>
{/* 필드명과 동일 옵션 */}
<CommandItem
value="__same_as_field__"
onSelect={() => {
onUpdate({ externalField: undefined });
setExternalFieldSearchOpen(false);
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
!col.externalField ? "opacity-100" : "opacity-0"
)}
/>
<span className="text-muted-foreground">( )</span>
</CommandItem>
{/* 외부 테이블 컬럼 목록 */}
{externalTableColumns.map((extCol) => (
<CommandItem
key={extCol.column_name}
value={extCol.column_name}
onSelect={() => {
onUpdate({ externalField: extCol.column_name });
setExternalFieldSearchOpen(false);
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
col.externalField === extCol.column_name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col flex-1 min-w-0">
<span className="font-medium truncate">{extCol.column_name}</span>
{extCol.comment && (
<span className="text-[10px] text-muted-foreground truncate">
{extCol.comment}
</span>
)}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<p className="text-[10px] text-muted-foreground mt-0.5">
({externalTableName})
</p>
</div>
)}
{/* 라벨 */}
<div>
<Label className="text-xs"></Label>
@@ -2450,6 +2539,7 @@ export function TableSectionSettingsModal({
// 테이블 검색 Combobox 상태
const [tableSearchOpen, setTableSearchOpen] = useState(false);
const [saveTableSearchOpen, setSaveTableSearchOpen] = useState(false);
const [externalTableSearchOpen, setExternalTableSearchOpen] = useState(false);
// 활성 탭
const [activeTab, setActiveTab] = useState("source");
@@ -2623,6 +2713,24 @@ export function TableSectionSettingsModal({
});
};
const updateExternalDataSource = (updates: Partial<NonNullable<TableSectionConfig["externalDataSource"]>>) => {
updateTableConfig({
externalDataSource: { ...tableConfig.externalDataSource, enabled: false, tableName: "", ...updates },
});
};
// 외부 데이터 소스 테이블 컬럼 목록
const externalTableColumns = useMemo(() => {
return tableColumns[tableConfig.externalDataSource?.tableName || ""] || [];
}, [tableColumns, tableConfig.externalDataSource?.tableName]);
// 외부 데이터 소스 테이블 변경 시 컬럼 로드
useEffect(() => {
if (tableConfig.externalDataSource?.enabled && tableConfig.externalDataSource?.tableName) {
onLoadTableColumns(tableConfig.externalDataSource.tableName);
}
}, [tableConfig.externalDataSource?.enabled, tableConfig.externalDataSource?.tableName, onLoadTableColumns]);
// 저장 함수
const handleSave = () => {
onSave({
@@ -2986,6 +3094,98 @@ export function TableSectionSettingsModal({
</div>
</div>
</div>
{/* 외부 데이터 소스 설정 */}
<div className="space-y-3 border rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<h4 className="text-sm font-medium"> </h4>
<p className="text-xs text-muted-foreground">
"데이터 전달 모달열기" .
</p>
</div>
<Switch
checked={tableConfig.externalDataSource?.enabled ?? false}
onCheckedChange={(checked) => {
if (checked) {
updateExternalDataSource({ enabled: true, tableName: "" });
} else {
updateTableConfig({ externalDataSource: undefined });
}
}}
/>
</div>
{tableConfig.externalDataSource?.enabled && (
<div className="space-y-3 pt-2">
<div>
<Label className="text-xs font-medium mb-1.5 block"> </Label>
<Popover open={externalTableSearchOpen} onOpenChange={setExternalTableSearchOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={externalTableSearchOpen}
className="h-9 w-full justify-between text-sm"
>
{tableConfig.externalDataSource?.tableName || "테이블 선택..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-full min-w-[400px]" align="start">
<Command>
<CommandInput placeholder="테이블 검색..." className="text-sm" />
<CommandList className="max-h-[300px]">
<CommandEmpty className="text-sm py-6 text-center">
.
</CommandEmpty>
<CommandGroup>
{tables.map((table) => (
<CommandItem
key={table.table_name}
value={table.table_name}
onSelect={() => {
updateExternalDataSource({ enabled: true, tableName: table.table_name });
onLoadTableColumns(table.table_name);
setExternalTableSearchOpen(false);
}}
className="text-sm"
>
<Check
className={cn(
"mr-2 h-4 w-4",
tableConfig.externalDataSource?.tableName === table.table_name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span className="font-medium">{table.table_name}</span>
{table.comment && (
<span className="text-xs text-muted-foreground">{table.comment}</span>
)}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<HelpText> . (: 수주상세 sales_order_detail)</HelpText>
</div>
{tableConfig.externalDataSource?.tableName && externalTableColumns.length > 0 && (
<div className="bg-muted/30 rounded-lg p-3">
<p className="text-xs text-muted-foreground">
: {externalTableColumns.length}
</p>
<p className="text-[10px] text-muted-foreground mt-1">
"컬럼 설정" "외부 필드" .
</p>
</div>
)}
</div>
)}
</div>
</TabsContent>
{/* 컬럼 설정 탭 */}
@@ -3041,6 +3241,9 @@ export function TableSectionSettingsModal({
displayColumns={tableConfig.source.displayColumns || []}
sourceTableColumns={sourceTableColumns}
sourceTableName={tableConfig.source.tableName}
externalTableColumns={externalTableColumns}
externalTableName={tableConfig.externalDataSource?.tableName}
externalDataEnabled={tableConfig.externalDataSource?.enabled}
tables={tables}
tableColumns={tableColumns}
sections={otherSections}