Merge pull request 'lhj' (#64) from lhj into dev
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/64
This commit is contained in:
commit
74fdaea4ac
|
|
@ -275,16 +275,16 @@ export default function ScreenViewPage() {
|
||||||
zIndex: component.position.z || 1,
|
zIndex: component.position.z || 1,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
console.log("🎯 할당된 화면 컴포넌트:", {
|
// console.log("🎯 할당된 화면 컴포넌트:", {
|
||||||
id: component.id,
|
// id: component.id,
|
||||||
type: component.type,
|
// type: component.type,
|
||||||
position: component.position,
|
// position: component.position,
|
||||||
size: component.size,
|
// size: component.size,
|
||||||
styleWidth: component.style?.width,
|
// styleWidth: component.style?.width,
|
||||||
styleHeight: component.style?.height,
|
// styleHeight: component.style?.height,
|
||||||
finalWidth: `${component.size.width}px`,
|
// finalWidth: `${component.size.width}px`,
|
||||||
finalHeight: `${component.size.height}px`,
|
// finalHeight: `${component.size.height}px`,
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
||||||
|
|
|
||||||
|
|
@ -1809,6 +1809,25 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
filters: [],
|
filters: [],
|
||||||
displayFormat: "simple" as const,
|
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:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,30 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
|
||||||
|
|
||||||
// 레지스트리에서 모든 컴포넌트 조회
|
// 레지스트리에서 모든 컴포넌트 조회
|
||||||
const allComponents = useMemo(() => {
|
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 카테고리 제외)
|
// 카테고리별 분류 (input 카테고리 제외)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ import React, { useEffect, useState, useMemo } from "react";
|
||||||
import { ComponentRendererProps } from "@/types/component";
|
import { ComponentRendererProps } from "@/types/component";
|
||||||
import { CardDisplayConfig } from "./types";
|
import { CardDisplayConfig } from "./types";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
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 {
|
export interface CardDisplayComponentProps extends ComponentRendererProps {
|
||||||
config?: CardDisplayConfig;
|
config?: CardDisplayConfig;
|
||||||
|
|
@ -39,6 +43,59 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const loadTableData = async () => {
|
const loadTableData = async () => {
|
||||||
|
|
@ -48,19 +105,25 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// tableName 확인 (props에서 전달받은 tableName 사용)
|
// tableName 확인 (props에서 전달받은 tableName 사용)
|
||||||
const tableNameToUse = tableName || component.componentConfig?.tableName;
|
const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정
|
||||||
|
|
||||||
if (!tableNameToUse) {
|
if (!tableNameToUse) {
|
||||||
console.log("📋 CardDisplay: 테이블명이 설정되지 않음", {
|
// console.log("📋 CardDisplay: 테이블명이 설정되지 않음", {
|
||||||
tableName,
|
// tableName,
|
||||||
componentTableName: component.componentConfig?.tableName,
|
// componentTableName: component.componentConfig?.tableName,
|
||||||
});
|
// });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log("📋 CardDisplay: 사용할 테이블명", {
|
||||||
|
// tableName,
|
||||||
|
// componentTableName: component.componentConfig?.tableName,
|
||||||
|
// finalTableName: tableNameToUse,
|
||||||
|
// });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`);
|
// console.log(`📋 CardDisplay: ${tableNameToUse} 테이블 데이터 로딩 시작`);
|
||||||
|
|
||||||
// 테이블 데이터와 컬럼 정보를 병렬로 로드
|
// 테이블 데이터와 컬럼 정보를 병렬로 로드
|
||||||
const [dataResponse, columnsResponse] = await Promise.all([
|
const [dataResponse, columnsResponse] = await Promise.all([
|
||||||
|
|
@ -71,13 +134,13 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
tableTypeApi.getColumns(tableNameToUse),
|
tableTypeApi.getColumns(tableNameToUse),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, {
|
// console.log(`📋 CardDisplay: ${tableNameToUse} 데이터 로딩 완료`, {
|
||||||
total: dataResponse.total,
|
// total: dataResponse.total,
|
||||||
dataLength: dataResponse.data.length,
|
// dataLength: dataResponse.data.length,
|
||||||
columnsLength: columnsResponse.length,
|
// columnsLength: columnsResponse.length,
|
||||||
sampleData: dataResponse.data.slice(0, 2),
|
// sampleData: dataResponse.data.slice(0, 2),
|
||||||
sampleColumns: columnsResponse.slice(0, 3),
|
// sampleColumns: columnsResponse.slice(0, 3),
|
||||||
});
|
// });
|
||||||
|
|
||||||
setLoadedTableData(dataResponse.data);
|
setLoadedTableData(dataResponse.data);
|
||||||
setLoadedTableColumns(columnsResponse);
|
setLoadedTableColumns(columnsResponse);
|
||||||
|
|
@ -130,32 +193,32 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
|
|
||||||
// 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용)
|
// 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용)
|
||||||
const displayData = useMemo(() => {
|
const displayData = useMemo(() => {
|
||||||
console.log("📋 CardDisplay: displayData 결정 중", {
|
// console.log("📋 CardDisplay: displayData 결정 중", {
|
||||||
dataSource: componentConfig.dataSource,
|
// dataSource: componentConfig.dataSource,
|
||||||
loadedTableDataLength: loadedTableData.length,
|
// loadedTableDataLength: loadedTableData.length,
|
||||||
tableDataLength: tableData.length,
|
// tableDataLength: tableData.length,
|
||||||
staticDataLength: componentConfig.staticData?.length || 0,
|
// staticDataLength: componentConfig.staticData?.length || 0,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시)
|
// 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시)
|
||||||
if (loadedTableData.length > 0) {
|
if (loadedTableData.length > 0) {
|
||||||
console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2));
|
// console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2));
|
||||||
return loadedTableData;
|
return loadedTableData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// props로 전달받은 테이블 데이터가 있으면 사용
|
// props로 전달받은 테이블 데이터가 있으면 사용
|
||||||
if (tableData.length > 0) {
|
if (tableData.length > 0) {
|
||||||
console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2));
|
// console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2));
|
||||||
return tableData;
|
return tableData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentConfig.staticData && componentConfig.staticData.length > 0) {
|
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;
|
return componentConfig.staticData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 데이터가 없으면 빈 배열 반환
|
// 데이터가 없으면 빈 배열 반환
|
||||||
console.log("📋 CardDisplay: 표시할 데이터가 없음");
|
// console.log("📋 CardDisplay: 표시할 데이터가 없음");
|
||||||
return [];
|
return [];
|
||||||
}, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]);
|
}, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]);
|
||||||
|
|
||||||
|
|
@ -260,23 +323,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// DOM에 전달하면 안 되는 React-specific props 필터링
|
// DOM 안전한 props만 필터링 (filterDOMProps 유틸리티 사용)
|
||||||
const {
|
const safeDomProps = filterDOMProps(props);
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -301,7 +349,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
{...domProps}
|
{...safeDomProps}
|
||||||
>
|
>
|
||||||
<div style={containerStyle}>
|
<div style={containerStyle}>
|
||||||
{displayData.length === 0 ? (
|
{displayData.length === 0 ? (
|
||||||
|
|
@ -393,8 +441,24 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
|
|
||||||
{/* 카드 액션 (선택사항) */}
|
{/* 카드 액션 (선택사항) */}
|
||||||
<div className="mt-3 flex justify-end space-x-2">
|
<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
|
||||||
<button className="text-xs font-medium text-gray-500 hover:text-gray-700">편집</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -402,6 +466,101 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Eye, Edit, Trash2, MoreHorizontal } from "lucide-react";
|
||||||
|
import { CardDisplayConfig, ColumnConfig } from "./types";
|
||||||
|
|
||||||
|
interface CardModeRendererProps {
|
||||||
|
data: Record<string, any>[];
|
||||||
|
cardConfig: CardDisplayConfig;
|
||||||
|
visibleColumns: ColumnConfig[];
|
||||||
|
onRowClick?: (row: Record<string, any>) => void;
|
||||||
|
onRowSelect?: (row: Record<string, any>, selected: boolean) => void;
|
||||||
|
selectedRows?: string[];
|
||||||
|
showActions?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 모드 렌더러
|
||||||
|
* 테이블 데이터를 카드 형태로 표시
|
||||||
|
*/
|
||||||
|
export const CardModeRenderer: React.FC<CardModeRendererProps> = ({
|
||||||
|
data,
|
||||||
|
cardConfig,
|
||||||
|
visibleColumns,
|
||||||
|
onRowClick,
|
||||||
|
onRowSelect,
|
||||||
|
selectedRows = [],
|
||||||
|
showActions = true,
|
||||||
|
}) => {
|
||||||
|
// 기본값 설정
|
||||||
|
const config = {
|
||||||
|
cardsPerRow: 3,
|
||||||
|
cardSpacing: 16,
|
||||||
|
showActions: true,
|
||||||
|
cardHeight: "auto",
|
||||||
|
...cardConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 카드 그리드 스타일 계산
|
||||||
|
const gridStyle: React.CSSProperties = {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: `repeat(${config.cardsPerRow}, 1fr)`,
|
||||||
|
gap: `${config.cardSpacing}px`,
|
||||||
|
padding: `${config.cardSpacing}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 카드 높이 스타일
|
||||||
|
const cardStyle: React.CSSProperties = {
|
||||||
|
height: config.cardHeight === "auto" ? "auto" : `${config.cardHeight}px`,
|
||||||
|
cursor: onRowClick ? "pointer" : "default",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 컬럼 값 가져오기 함수
|
||||||
|
const getColumnValue = (row: Record<string, any>, columnName?: string): string => {
|
||||||
|
if (!columnName || !row) return "";
|
||||||
|
return String(row[columnName] || "");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 액션 버튼 렌더링
|
||||||
|
const renderActions = (row: Record<string, any>) => {
|
||||||
|
if (!showActions || !config.showActions) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-end space-x-1 mt-3 pt-3 border-t border-gray-100">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// 상세보기 액션
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// 편집 액션
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// 삭제 액션
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// 더보기 액션
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 데이터가 없는 경우
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<div className="w-16 h-16 bg-gradient-to-br from-slate-100 to-slate-200 rounded-2xl flex items-center justify-center mb-4">
|
||||||
|
<div className="w-8 h-8 bg-slate-300 rounded-lg"></div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm font-medium text-slate-600 mb-1">표시할 데이터가 없습니다</div>
|
||||||
|
<div className="text-xs text-slate-400">조건을 변경하거나 새로운 데이터를 추가해보세요</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={gridStyle} className="w-full">
|
||||||
|
{data.map((row, index) => {
|
||||||
|
const idValue = getColumnValue(row, config.idColumn);
|
||||||
|
const titleValue = getColumnValue(row, config.titleColumn);
|
||||||
|
const subtitleValue = getColumnValue(row, config.subtitleColumn);
|
||||||
|
const descriptionValue = getColumnValue(row, config.descriptionColumn);
|
||||||
|
const imageValue = getColumnValue(row, config.imageColumn);
|
||||||
|
|
||||||
|
const isSelected = selectedRows.includes(idValue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={`card-${index}-${idValue}`}
|
||||||
|
style={cardStyle}
|
||||||
|
className={`transition-all duration-200 hover:shadow-md ${
|
||||||
|
isSelected ? "ring-2 ring-blue-500 bg-blue-50/30" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => onRowClick?.(row)}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<CardTitle className="text-sm font-medium truncate">
|
||||||
|
{titleValue || "제목 없음"}
|
||||||
|
</CardTitle>
|
||||||
|
{subtitleValue && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1 truncate">
|
||||||
|
{subtitleValue}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ID 뱃지 */}
|
||||||
|
{idValue && (
|
||||||
|
<Badge variant="secondary" className="ml-2 text-xs">
|
||||||
|
{idValue}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="pt-0">
|
||||||
|
{/* 이미지 표시 */}
|
||||||
|
{imageValue && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<img
|
||||||
|
src={imageValue}
|
||||||
|
alt={titleValue}
|
||||||
|
className="w-full h-24 object-cover rounded-md bg-gray-100"
|
||||||
|
onError={(e) => {
|
||||||
|
const target = e.target as HTMLImageElement;
|
||||||
|
target.style.display = "none";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 설명 표시 */}
|
||||||
|
{descriptionValue && (
|
||||||
|
<div className="text-xs text-gray-600 line-clamp-2 mb-3">
|
||||||
|
{descriptionValue}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 추가 필드들 표시 (선택적) */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
{visibleColumns
|
||||||
|
.filter(col =>
|
||||||
|
col.columnName !== config.idColumn &&
|
||||||
|
col.columnName !== config.titleColumn &&
|
||||||
|
col.columnName !== config.subtitleColumn &&
|
||||||
|
col.columnName !== config.descriptionColumn &&
|
||||||
|
col.columnName !== config.imageColumn &&
|
||||||
|
col.columnName !== "__checkbox__" &&
|
||||||
|
col.visible
|
||||||
|
)
|
||||||
|
.slice(0, 3) // 최대 3개 추가 필드만 표시
|
||||||
|
.map((col) => {
|
||||||
|
const value = getColumnValue(row, col.columnName);
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={col.columnName} className="flex justify-between items-center text-xs">
|
||||||
|
<span className="text-gray-500 truncate">{col.displayName}:</span>
|
||||||
|
<span className="font-medium truncate ml-2">{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 액션 버튼들 */}
|
||||||
|
{renderActions(row)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -24,6 +24,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
||||||
import { SingleTableWithSticky } from "./SingleTableWithSticky";
|
import { SingleTableWithSticky } from "./SingleTableWithSticky";
|
||||||
|
import { CardModeRenderer } from "./CardModeRenderer";
|
||||||
|
|
||||||
export interface TableListComponentProps {
|
export interface TableListComponentProps {
|
||||||
component: any;
|
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 className="mt-1 text-xs text-red-500 bg-red-50 px-3 py-1 rounded-full">{error}</div>
|
||||||
</div>
|
</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 ? (
|
) : needsHorizontalScroll ? (
|
||||||
// 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
|
// 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
|
||||||
<div className="w-full overflow-hidden">
|
<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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { TableListConfig, ColumnConfig } from "./types";
|
import { TableListConfig, ColumnConfig } from "./types";
|
||||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
|
|
@ -755,6 +756,188 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
{/* 기본 설정 탭 */}
|
{/* 기본 설정 탭 */}
|
||||||
<TabsContent value="basic" className="space-y-4">
|
<TabsContent value="basic" className="space-y-4">
|
||||||
<ScrollArea className="h-[600px] pr-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>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">연결된 테이블</CardTitle>
|
<CardTitle className="text-base">연결된 테이블</CardTitle>
|
||||||
|
|
|
||||||
|
|
@ -67,3 +67,16 @@ export class TableListRenderer extends AutoRegisteringComponentRenderer {
|
||||||
|
|
||||||
// 자동 등록 실행
|
// 자동 등록 실행
|
||||||
TableListRenderer.registerSelf();
|
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",
|
nameEng: "TableList Component",
|
||||||
description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트",
|
description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트",
|
||||||
category: ComponentCategory.DISPLAY,
|
category: ComponentCategory.DISPLAY,
|
||||||
webType: "table",
|
webType: "text",
|
||||||
component: TableListWrapper,
|
component: TableListWrapper,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
|
// 표시 모드 설정
|
||||||
|
displayMode: "table" as const,
|
||||||
|
|
||||||
|
// 카드 모드 기본 설정
|
||||||
|
cardConfig: {
|
||||||
|
idColumn: "id",
|
||||||
|
titleColumn: "name",
|
||||||
|
cardsPerRow: 3,
|
||||||
|
cardSpacing: 16,
|
||||||
|
showActions: true,
|
||||||
|
},
|
||||||
|
|
||||||
// 테이블 기본 설정
|
// 테이블 기본 설정
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
showFooter: true,
|
showFooter: true,
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,21 @@ export interface ColumnConfig {
|
||||||
autoGeneration?: AutoGenerationConfig; // 자동생성 설정
|
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 컴포넌트 설정 타입
|
* TableList 컴포넌트 설정 타입
|
||||||
*/
|
*/
|
||||||
export interface TableListConfig extends ComponentConfig {
|
export interface TableListConfig extends ComponentConfig {
|
||||||
|
// 표시 모드 설정
|
||||||
|
displayMode?: "table" | "card"; // 기본: "table"
|
||||||
|
|
||||||
|
// 카드 디스플레이 설정 (displayMode가 "card"일 때 사용)
|
||||||
|
cardConfig?: CardDisplayConfig;
|
||||||
|
|
||||||
// 테이블 기본 설정
|
// 테이블 기본 설정
|
||||||
selectedTable?: string;
|
selectedTable?: string;
|
||||||
tableName?: string;
|
tableName?: string;
|
||||||
|
|
@ -175,7 +196,9 @@ export interface TableListConfig extends ComponentConfig {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 페이지네이션
|
// 페이지네이션
|
||||||
pagination: PaginationConfig;
|
pagination: PaginationConfig & {
|
||||||
|
currentPage?: number; // 현재 페이지 (추가)
|
||||||
|
};
|
||||||
|
|
||||||
// 필터 설정
|
// 필터 설정
|
||||||
filter: FilterConfig;
|
filter: FilterConfig;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue