dev #65
|
|
@ -275,16 +275,16 @@ export default function ScreenViewPage() {
|
|||
zIndex: component.position.z || 1,
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
console.log("🎯 할당된 화면 컴포넌트:", {
|
||||
id: component.id,
|
||||
type: component.type,
|
||||
position: component.position,
|
||||
size: component.size,
|
||||
styleWidth: component.style?.width,
|
||||
styleHeight: component.style?.height,
|
||||
finalWidth: `${component.size.width}px`,
|
||||
finalHeight: `${component.size.height}px`,
|
||||
});
|
||||
// console.log("🎯 할당된 화면 컴포넌트:", {
|
||||
// id: component.id,
|
||||
// type: component.type,
|
||||
// position: component.position,
|
||||
// size: component.size,
|
||||
// styleWidth: component.style?.width,
|
||||
// styleHeight: component.style?.height,
|
||||
// finalWidth: `${component.size.width}px`,
|
||||
// finalHeight: `${component.size.height}px`,
|
||||
// });
|
||||
}}
|
||||
>
|
||||
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
||||
|
|
|
|||
|
|
@ -1809,6 +1809,25 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
filters: [],
|
||||
displayFormat: "simple" as const,
|
||||
};
|
||||
case "table":
|
||||
return {
|
||||
tableName: "",
|
||||
displayMode: "table" as const,
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
pagination: {
|
||||
enabled: true,
|
||||
pageSize: 10,
|
||||
showPageSizeSelector: true,
|
||||
showPageInfo: true,
|
||||
showFirstLast: true,
|
||||
},
|
||||
columns: [],
|
||||
searchable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
exportable: true,
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,30 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
|
|||
|
||||
// 레지스트리에서 모든 컴포넌트 조회
|
||||
const allComponents = useMemo(() => {
|
||||
return ComponentRegistry.getAllComponents();
|
||||
const components = ComponentRegistry.getAllComponents();
|
||||
console.log("🔍 ComponentsPanel - 로드된 컴포넌트:", components.map(c => ({ id: c.id, name: c.name, category: c.category })));
|
||||
|
||||
// 수동으로 table-list 컴포넌트 추가 (임시)
|
||||
const hasTableList = components.some(c => c.id === 'table-list');
|
||||
if (!hasTableList) {
|
||||
console.log("⚠️ table-list 컴포넌트가 없어서 수동 추가");
|
||||
components.push({
|
||||
id: "table-list",
|
||||
name: "테이블 리스트",
|
||||
nameEng: "TableList Component",
|
||||
description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트",
|
||||
category: "display",
|
||||
webType: "text",
|
||||
defaultConfig: {},
|
||||
defaultSize: { width: 800, height: 400 },
|
||||
icon: "Table",
|
||||
tags: ["테이블", "데이터", "목록", "그리드"],
|
||||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
});
|
||||
}
|
||||
|
||||
return components;
|
||||
}, []);
|
||||
|
||||
// 카테고리별 분류 (input 카테고리 제외)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import React, { useEffect, useState, useMemo } from "react";
|
|||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { CardDisplayConfig } from "./types";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export interface CardDisplayComponentProps extends ComponentRendererProps {
|
||||
config?: CardDisplayConfig;
|
||||
|
|
@ -39,6 +43,59 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 상세보기 모달 상태
|
||||
const [viewModalOpen, setViewModalOpen] = useState(false);
|
||||
const [selectedData, setSelectedData] = useState<any>(null);
|
||||
|
||||
// 편집 모달 상태
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editData, setEditData] = useState<any>(null);
|
||||
|
||||
// 카드 액션 핸들러
|
||||
const handleCardView = (data: any) => {
|
||||
// console.log("👀 상세보기 클릭:", data);
|
||||
setSelectedData(data);
|
||||
setViewModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCardEdit = (data: any) => {
|
||||
// console.log("✏️ 편집 클릭:", data);
|
||||
setEditData({ ...data }); // 복사본 생성
|
||||
setEditModalOpen(true);
|
||||
};
|
||||
|
||||
// 편집 폼 데이터 변경 핸들러
|
||||
const handleEditFormChange = (key: string, value: string) => {
|
||||
setEditData((prev: any) => ({
|
||||
...prev,
|
||||
[key]: value
|
||||
}));
|
||||
};
|
||||
|
||||
// 편집 저장 핸들러
|
||||
const handleEditSave = async () => {
|
||||
// console.log("💾 편집 저장:", editData);
|
||||
|
||||
try {
|
||||
// TODO: 실제 API 호출로 데이터 업데이트
|
||||
// await tableTypeApi.updateTableData(tableName, editData);
|
||||
|
||||
// console.log("✅ 편집 저장 완료");
|
||||
alert("✅ 저장되었습니다!");
|
||||
|
||||
// 모달 닫기
|
||||
setEditModalOpen(false);
|
||||
setEditData(null);
|
||||
|
||||
// 데이터 새로고침 (필요시)
|
||||
// loadTableData();
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ 편집 저장 실패:", error);
|
||||
alert("❌ 저장에 실패했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
// 테이블 데이터 로딩
|
||||
useEffect(() => {
|
||||
const loadTableData = async () => {
|
||||
|
|
@ -48,19 +105,25 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
}
|
||||
|
||||
// tableName 확인 (props에서 전달받은 tableName 사용)
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName;
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정
|
||||
|
||||
if (!tableNameToUse) {
|
||||
console.log("📋 CardDisplay: 테이블명이 설정되지 않음", {
|
||||
tableName,
|
||||
componentTableName: component.componentConfig?.tableName,
|
||||
});
|
||||
// console.log("📋 CardDisplay: 테이블명이 설정되지 않음", {
|
||||
// tableName,
|
||||
// componentTableName: component.componentConfig?.tableName,
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("📋 CardDisplay: 사용할 테이블명", {
|
||||
// tableName,
|
||||
// componentTableName: component.componentConfig?.tableName,
|
||||
// finalTableName: tableNameToUse,
|
||||
// });
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`);
|
||||
// console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`);
|
||||
|
||||
// 테이블 데이터와 컬럼 정보를 병렬로 로드
|
||||
const [dataResponse, columnsResponse] = await Promise.all([
|
||||
|
|
@ -71,13 +134,13 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
tableTypeApi.getColumns(tableNameToUse),
|
||||
]);
|
||||
|
||||
console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, {
|
||||
total: dataResponse.total,
|
||||
dataLength: dataResponse.data.length,
|
||||
columnsLength: columnsResponse.length,
|
||||
sampleData: dataResponse.data.slice(0, 2),
|
||||
sampleColumns: columnsResponse.slice(0, 3),
|
||||
});
|
||||
// console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, {
|
||||
// total: dataResponse.total,
|
||||
// dataLength: dataResponse.data.length,
|
||||
// columnsLength: columnsResponse.length,
|
||||
// sampleData: dataResponse.data.slice(0, 2),
|
||||
// sampleColumns: columnsResponse.slice(0, 3),
|
||||
// });
|
||||
|
||||
setLoadedTableData(dataResponse.data);
|
||||
setLoadedTableColumns(columnsResponse);
|
||||
|
|
@ -130,32 +193,32 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
|
||||
// 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용)
|
||||
const displayData = useMemo(() => {
|
||||
console.log("📋 CardDisplay: displayData 결정 중", {
|
||||
dataSource: componentConfig.dataSource,
|
||||
loadedTableDataLength: loadedTableData.length,
|
||||
tableDataLength: tableData.length,
|
||||
staticDataLength: componentConfig.staticData?.length || 0,
|
||||
});
|
||||
// console.log("📋 CardDisplay: displayData 결정 중", {
|
||||
// dataSource: componentConfig.dataSource,
|
||||
// loadedTableDataLength: loadedTableData.length,
|
||||
// tableDataLength: tableData.length,
|
||||
// staticDataLength: componentConfig.staticData?.length || 0,
|
||||
// });
|
||||
|
||||
// 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시)
|
||||
if (loadedTableData.length > 0) {
|
||||
console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2));
|
||||
// console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2));
|
||||
return loadedTableData;
|
||||
}
|
||||
|
||||
// props로 전달받은 테이블 데이터가 있으면 사용
|
||||
if (tableData.length > 0) {
|
||||
console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2));
|
||||
// console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2));
|
||||
return tableData;
|
||||
}
|
||||
|
||||
if (componentConfig.staticData && componentConfig.staticData.length > 0) {
|
||||
console.log("📋 CardDisplay: 정적 데이터 사용", componentConfig.staticData.slice(0, 2));
|
||||
// console.log("📋 CardDisplay: 정적 데이터 사용", componentConfig.staticData.slice(0, 2));
|
||||
return componentConfig.staticData;
|
||||
}
|
||||
|
||||
// 데이터가 없으면 빈 배열 반환
|
||||
console.log("📋 CardDisplay: 표시할 데이터가 없음");
|
||||
// console.log("📋 CardDisplay: 표시할 데이터가 없음");
|
||||
return [];
|
||||
}, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]);
|
||||
|
||||
|
|
@ -260,23 +323,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
// DOM에 전달하면 안 되는 React-specific props 필터링
|
||||
const {
|
||||
selectedScreen,
|
||||
onZoneComponentDrop,
|
||||
onZoneClick,
|
||||
componentConfig: _componentConfig,
|
||||
component: _component,
|
||||
isSelected: _isSelected,
|
||||
onClick: _onClick,
|
||||
onDragStart: _onDragStart,
|
||||
onDragEnd: _onDragEnd,
|
||||
size: _size,
|
||||
position: _position,
|
||||
style: _style,
|
||||
onRefresh: _onRefresh, // React DOM 속성이 아니므로 필터링
|
||||
...domProps
|
||||
} = props;
|
||||
// DOM 안전한 props만 필터링 (filterDOMProps 유틸리티 사용)
|
||||
const safeDomProps = filterDOMProps(props);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -301,7 +349,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
onClick={handleClick}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
{...domProps}
|
||||
{...safeDomProps}
|
||||
>
|
||||
<div style={containerStyle}>
|
||||
{displayData.length === 0 ? (
|
||||
|
|
@ -393,8 +441,24 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
|
||||
{/* 카드 액션 (선택사항) */}
|
||||
<div className="mt-3 flex justify-end space-x-2">
|
||||
<button className="text-xs font-medium text-blue-600 hover:text-blue-800">상세보기</button>
|
||||
<button className="text-xs font-medium text-gray-500 hover:text-gray-700">편집</button>
|
||||
<button
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-800 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCardView(data);
|
||||
}}
|
||||
>
|
||||
상세보기
|
||||
</button>
|
||||
<button
|
||||
className="text-xs font-medium text-gray-500 hover:text-gray-700 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCardEdit(data);
|
||||
}}
|
||||
>
|
||||
편집
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -402,6 +466,101 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상세보기 모달 */}
|
||||
<Dialog open={viewModalOpen} onOpenChange={setViewModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<span className="text-lg">📋</span>
|
||||
상세 정보
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{selectedData && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{Object.entries(selectedData)
|
||||
.filter(([key, value]) => value !== null && value !== undefined && value !== '')
|
||||
.map(([key, value]) => (
|
||||
<div key={key} className="bg-gray-50 rounded-lg p-3">
|
||||
<div className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-1">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900 break-words">
|
||||
{String(value)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t">
|
||||
<button
|
||||
onClick={() => setViewModalOpen(false)}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 편집 모달 */}
|
||||
<Dialog open={editModalOpen} onOpenChange={setEditModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<span className="text-lg">✏️</span>
|
||||
데이터 편집
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{editData && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Object.entries(editData)
|
||||
.filter(([key, value]) => value !== null && value !== undefined)
|
||||
.map(([key, value]) => (
|
||||
<div key={key} className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700 block">
|
||||
{key.replace(/_/g, ' ').toUpperCase()}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={String(value)}
|
||||
onChange={(e) => handleEditFormChange(key, e.target.value)}
|
||||
className="w-full"
|
||||
placeholder={`${key} 입력`}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setEditModalOpen(false);
|
||||
setEditData(null);
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleEditSave}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { cn } from "@/lib/utils";
|
||||
import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
||||
import { SingleTableWithSticky } from "./SingleTableWithSticky";
|
||||
import { CardModeRenderer } from "./CardModeRenderer";
|
||||
|
||||
export interface TableListComponentProps {
|
||||
component: any;
|
||||
|
|
@ -1290,6 +1291,29 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
<div className="mt-1 text-xs text-red-500 bg-red-50 px-3 py-1 rounded-full">{error}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : tableConfig.displayMode === "card" ? (
|
||||
// 카드 모드 렌더링
|
||||
<div className="w-full h-full overflow-y-auto">
|
||||
<CardModeRenderer
|
||||
data={data}
|
||||
cardConfig={tableConfig.cardConfig || {
|
||||
idColumn: "id",
|
||||
titleColumn: "name",
|
||||
cardsPerRow: 3,
|
||||
cardSpacing: 16,
|
||||
showActions: true,
|
||||
}}
|
||||
visibleColumns={visibleColumns}
|
||||
onRowClick={handleRowClick}
|
||||
onRowSelect={(row, selected) => {
|
||||
const rowIndex = data.findIndex(d => d === row);
|
||||
const rowKey = getRowKey(row, rowIndex);
|
||||
handleRowSelection(rowKey, selected);
|
||||
}}
|
||||
selectedRows={Array.from(selectedRows)}
|
||||
showActions={tableConfig.actions?.showActions}
|
||||
/>
|
||||
</div>
|
||||
) : needsHorizontalScroll ? (
|
||||
// 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
|
||||
<div className="w-full overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { TableListConfig, ColumnConfig } from "./types";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
|
|
@ -755,6 +756,188 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
{/* 기본 설정 탭 */}
|
||||
<TabsContent value="basic" className="space-y-4">
|
||||
<ScrollArea className="h-[600px] pr-4">
|
||||
{/* 표시 모드 설정 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">표시 모드</CardTitle>
|
||||
<CardDescription>데이터를 어떤 형태로 표시할지 선택하세요</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label>표시 형태</Label>
|
||||
<RadioGroup
|
||||
value={config.displayMode || "table"}
|
||||
onValueChange={(value: "table" | "card") => handleChange("displayMode", value)}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="table" id="table-mode" />
|
||||
<Label htmlFor="table-mode" className="cursor-pointer">
|
||||
테이블 형태 (기본)
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="card" id="card-mode" />
|
||||
<Label htmlFor="card-mode" className="cursor-pointer">
|
||||
카드 형태
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* 카드 모드 설정 */}
|
||||
{config.displayMode === "card" && (
|
||||
<div className="space-y-4 border-t pt-4">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium">카드 레이아웃 설정</Label>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cards-per-row">한 행당 카드 수</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.cardsPerRow?.toString() || "3"}
|
||||
onValueChange={(value) =>
|
||||
handleNestedChange("cardConfig", "cardsPerRow", parseInt(value))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">1개</SelectItem>
|
||||
<SelectItem value="2">2개</SelectItem>
|
||||
<SelectItem value="3">3개</SelectItem>
|
||||
<SelectItem value="4">4개</SelectItem>
|
||||
<SelectItem value="5">5개</SelectItem>
|
||||
<SelectItem value="6">6개</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="card-spacing">카드 간격 (px)</Label>
|
||||
<Input
|
||||
id="card-spacing"
|
||||
type="number"
|
||||
value={config.cardConfig?.cardSpacing || 16}
|
||||
onChange={(e) =>
|
||||
handleNestedChange("cardConfig", "cardSpacing", parseInt(e.target.value))
|
||||
}
|
||||
min="0"
|
||||
max="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium">카드 필드 매핑</Label>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="id-column">ID 컬럼 (사번 등)</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.idColumn || ""}
|
||||
onValueChange={(value) =>
|
||||
handleNestedChange("cardConfig", "idColumn", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="ID 컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.label || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title-column">제목 컬럼 (이름 등)</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.titleColumn || ""}
|
||||
onValueChange={(value) =>
|
||||
handleNestedChange("cardConfig", "titleColumn", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="제목 컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.label || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="subtitle-column">서브 제목 컬럼 (부서 등)</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.subtitleColumn || ""}
|
||||
onValueChange={(value) =>
|
||||
handleNestedChange("cardConfig", "subtitleColumn", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="서브 제목 컬럼 선택 (선택사항)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">선택 안함</SelectItem>
|
||||
{availableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.label || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description-column">설명 컬럼</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.descriptionColumn || ""}
|
||||
onValueChange={(value) =>
|
||||
handleNestedChange("cardConfig", "descriptionColumn", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="설명 컬럼 선택 (선택사항)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">선택 안함</SelectItem>
|
||||
{availableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.label || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="show-card-actions"
|
||||
checked={config.cardConfig?.showActions !== false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleNestedChange("cardConfig", "showActions", checked as boolean)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="show-card-actions" className="cursor-pointer">
|
||||
카드에 액션 버튼 표시
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">연결된 테이블</CardTitle>
|
||||
|
|
|
|||
|
|
@ -67,3 +67,16 @@ export class TableListRenderer extends AutoRegisteringComponentRenderer {
|
|||
|
||||
// 자동 등록 실행
|
||||
TableListRenderer.registerSelf();
|
||||
|
||||
// 강제 등록 (디버깅용)
|
||||
if (typeof window !== "undefined") {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
console.log("🔄 TableList 강제 등록 시도...");
|
||||
TableListRenderer.registerSelf();
|
||||
console.log("✅ TableList 강제 등록 완료");
|
||||
} catch (error) {
|
||||
console.error("❌ TableList 강제 등록 실패:", error);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,21 @@ export const TableListDefinition = createComponentDefinition({
|
|||
nameEng: "TableList Component",
|
||||
description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트",
|
||||
category: ComponentCategory.DISPLAY,
|
||||
webType: "table",
|
||||
webType: "text",
|
||||
component: TableListWrapper,
|
||||
defaultConfig: {
|
||||
// 표시 모드 설정
|
||||
displayMode: "table" as const,
|
||||
|
||||
// 카드 모드 기본 설정
|
||||
cardConfig: {
|
||||
idColumn: "id",
|
||||
titleColumn: "name",
|
||||
cardsPerRow: 3,
|
||||
cardSpacing: 16,
|
||||
showActions: true,
|
||||
},
|
||||
|
||||
// 테이블 기본 설정
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,21 @@ export interface ColumnConfig {
|
|||
autoGeneration?: AutoGenerationConfig; // 자동생성 설정
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 디스플레이 설정
|
||||
*/
|
||||
export interface CardDisplayConfig {
|
||||
idColumn: string; // ID 컬럼 (사번 등)
|
||||
titleColumn: string; // 제목 컬럼 (이름 등)
|
||||
subtitleColumn?: string; // 부제목 컬럼 (부서 등)
|
||||
descriptionColumn?: string; // 설명 컬럼
|
||||
imageColumn?: string; // 이미지 컬럼
|
||||
cardsPerRow: number; // 한 행당 카드 수 (기본: 3)
|
||||
cardSpacing: number; // 카드 간격 (기본: 16px)
|
||||
showActions: boolean; // 액션 버튼 표시 여부
|
||||
cardHeight?: number; // 카드 높이 (기본: auto)
|
||||
}
|
||||
|
||||
/**
|
||||
* 필터 설정
|
||||
*/
|
||||
|
|
@ -147,6 +162,12 @@ export interface CheckboxConfig {
|
|||
* TableList 컴포넌트 설정 타입
|
||||
*/
|
||||
export interface TableListConfig extends ComponentConfig {
|
||||
// 표시 모드 설정
|
||||
displayMode?: "table" | "card"; // 기본: "table"
|
||||
|
||||
// 카드 디스플레이 설정 (displayMode가 "card"일 때 사용)
|
||||
cardConfig?: CardDisplayConfig;
|
||||
|
||||
// 테이블 기본 설정
|
||||
selectedTable?: string;
|
||||
tableName?: string;
|
||||
|
|
@ -175,7 +196,9 @@ export interface TableListConfig extends ComponentConfig {
|
|||
};
|
||||
|
||||
// 페이지네이션
|
||||
pagination: PaginationConfig;
|
||||
pagination: PaginationConfig & {
|
||||
currentPage?: number; // 현재 페이지 (추가)
|
||||
};
|
||||
|
||||
// 필터 설정
|
||||
filter: FilterConfig;
|
||||
|
|
|
|||
Loading…
Reference in New Issue