This commit is contained in:
kjs 2025-09-26 17:17:03 +09:00
commit f59fcb56b0
42 changed files with 1044 additions and 271 deletions

View File

@ -186,7 +186,7 @@ export default function BatchManagementPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div>

View File

@ -163,7 +163,7 @@ export default function CollectionManagementPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div>

View File

@ -12,7 +12,7 @@ export default function CommonCodeManagementPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -6,7 +6,7 @@ import { CompanyManagement } from "@/components/admin/CompanyManagement";
export default function CompanyPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -59,7 +59,7 @@ export default function DataFlowEditPage() {
}
return (
<div className="space-y-6">
<div className="space-y-8">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">

View File

@ -162,7 +162,7 @@ export default function ExternalCallConfigsPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 헤더 */}
<div className="flex items-center justify-between">
<div>

View File

@ -221,7 +221,7 @@ export default function ExternalConnectionsPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -5,7 +5,7 @@ import MultiLang from "@/components/admin/MultiLang";
export default function I18nPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6">
<div className="w-full max-w-none px-4 py-8">
<MultiLang />
</div>
</div>

View File

@ -221,7 +221,7 @@ export default function LayoutManagementPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -5,7 +5,7 @@ import { MenuManagement } from "@/components/admin/MenuManagement";
export default function MenuPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -6,7 +6,7 @@ import MonitoringDashboard from "@/components/admin/MonitoringDashboard";
export default function MonitoringPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div>
<h1 className="text-2xl font-bold"></h1>

View File

@ -1,18 +1,27 @@
import { Users, Shield, Settings, BarChart3, Palette, Layout, Database, Package } from "lucide-react";
import {
Users, Shield, Settings, BarChart3, Palette, Layout, Database, Package
} from "lucide-react";
import Link from "next/link";
/**
*
*/
export default function AdminPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
{/* 관리자 기능 카드들 */}
<div className="mx-auto max-w-7xl grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div className="w-full max-w-none px-4 pt-12 pb-16 space-y-16">
{/* 주요 관리 기능 */}
<div className="mx-auto max-w-7xl space-y-10">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2"> </h2>
<p className="text-gray-600"> </p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Link href="/admin/userMng" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-blue-200 to-blue-300">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-blue-100">
<Users className="h-6 w-6 text-blue-600" />
</div>
<div>
@ -25,8 +34,8 @@ export default function AdminPage() {
<div className="rounded-lg border bg-white p-6 shadow-sm">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-200 to-emerald-300">
<Shield className="h-6 w-6 text-emerald-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-emerald-100">
<Shield className="h-6 w-6 text-emerald-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -37,8 +46,8 @@ export default function AdminPage() {
<div className="rounded-lg border bg-white p-6 shadow-sm">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-violet-200 to-violet-300">
<Settings className="h-6 w-6 text-violet-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-violet-100">
<Settings className="h-6 w-6 text-violet-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -49,8 +58,8 @@ export default function AdminPage() {
<div className="rounded-lg border bg-white p-6 shadow-sm">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-amber-200 to-amber-300">
<BarChart3 className="h-6 w-6 text-amber-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-amber-100">
<BarChart3 className="h-6 w-6 text-amber-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -62,7 +71,7 @@ export default function AdminPage() {
<Link href="/admin/screenMng" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-200 to-indigo-300">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-100">
<Palette className="h-6 w-6 text-indigo-600" />
</div>
<div>
@ -72,17 +81,21 @@ export default function AdminPage() {
</div>
</div>
</Link>
</div>
</div>
{/* 표준 관리 섹션 */}
<div className="mx-auto max-w-7xl space-y-4">
<h2 className="text-xl font-semibold text-gray-900"> </h2>
<div className="mx-auto max-w-7xl space-y-10">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2"> </h2>
<p className="text-gray-600"> </p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
<Link href="/admin/standards" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<Link href="/admin/standards" className="block h-full">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50 h-full">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-teal-200 to-teal-300">
<Database className="h-6 w-6 text-teal-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-teal-100">
<Database className="h-6 w-6 text-teal-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -92,11 +105,11 @@ export default function AdminPage() {
</div>
</Link>
<Link href="/admin/templates" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<Link href="/admin/templates" className="block h-full">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50 h-full">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-200 to-emerald-300">
<Layout className="h-6 w-6 text-emerald-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-emerald-100">
<Layout className="h-6 w-6 text-emerald-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900">릿 </h3>
@ -106,11 +119,11 @@ export default function AdminPage() {
</div>
</Link>
<Link href="/admin/tableMng" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<Link href="/admin/tableMng" className="block h-full">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50 h-full">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-200 to-cyan-300">
<Database className="h-6 w-6 text-cyan-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-cyan-100">
<Database className="h-6 w-6 text-cyan-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -120,11 +133,11 @@ export default function AdminPage() {
</div>
</Link>
<Link href="/admin/components" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<Link href="/admin/components" className="block h-full">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50 h-full">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-violet-200 to-violet-300">
<Package className="h-6 w-6 text-violet-600" />
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-violet-100">
<Package className="h-6 w-6 text-violet-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
@ -136,31 +149,54 @@ export default function AdminPage() {
</div>
</div>
{/* 최근 활동 */}
<div className="rounded-lg border bg-white p-6 shadow-sm">
<h3 className="mb-4 text-lg font-semibold"> </h3>
<div className="space-y-4">
<div className="flex items-center justify-between border-b border-gray-100 py-2 last:border-0">
<div>
<p className="font-medium text-gray-900"> </p>
<p className="text-sm text-gray-600"> .</p>
{/* 빠른 액세스 */}
<div className="mx-auto max-w-7xl space-y-10">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2"> </h2>
<p className="text-gray-600"> </p>
</div>
<div className="grid gap-6 md:grid-cols-3">
<Link href="/admin/menu" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-blue-100">
<Layout className="h-6 w-6 text-blue-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
<p className="text-sm text-gray-600"> </p>
</div>
</div>
</div>
<span className="text-sm text-gray-500">2 </span>
</div>
<div className="flex items-center justify-between border-b border-gray-100 py-2 last:border-0">
<div>
<p className="font-medium text-gray-900"> </p>
<p className="text-sm text-gray-600"> .</p>
</Link>
<Link href="/admin/external-connections" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-100">
<Database className="h-6 w-6 text-green-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
<p className="text-sm text-gray-600"> </p>
</div>
</div>
</div>
<span className="text-sm text-gray-500">15 </span>
</div>
<div className="flex items-center justify-between border-b border-gray-100 py-2 last:border-0">
<div>
<p className="font-medium text-gray-900"> </p>
<p className="text-sm text-gray-600"> .</p>
</Link>
<Link href="/admin/commonCode" className="block">
<div className="rounded-lg border bg-white p-6 shadow-sm transition-colors hover:bg-gray-50">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-purple-100">
<Settings className="h-6 w-6 text-purple-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900"> </h3>
<p className="text-sm text-gray-600"> </p>
</div>
</div>
</div>
<span className="text-sm text-gray-500">1 </span>
</div>
</Link>
</div>
</div>
</div>

View File

@ -67,7 +67,7 @@ export default function ScreenManagementPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-6">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>
@ -80,7 +80,7 @@ export default function ScreenManagementPage() {
<div className="flex-1 overflow-hidden">
{/* 화면 목록 단계 */}
{currentStep === "list" && (
<div className="space-y-6">
<div className="space-y-8">
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-4">
<h2 className="text-xl font-semibold text-gray-800">{stepConfig.list.title}</h2>
<Button className="bg-blue-600 hover:bg-blue-700 shadow-sm" onClick={() => goToNextStep("design")}>
@ -100,7 +100,7 @@ export default function ScreenManagementPage() {
{/* 화면 설계 단계 */}
{currentStep === "design" && (
<div className="space-y-6">
<div className="space-y-8">
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-4">
<h2 className="text-xl font-semibold text-gray-800">{stepConfig.design.title}</h2>
<Button variant="outline" className="shadow-sm" onClick={() => goToStep("list")}>
@ -113,7 +113,7 @@ export default function ScreenManagementPage() {
{/* 템플릿 관리 단계 */}
{currentStep === "template" && (
<div className="space-y-6">
<div className="space-y-8">
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-4">
<h2 className="text-xl font-semibold text-gray-800">{stepConfig.template.title}</h2>
<div className="flex gap-2">

View File

@ -204,7 +204,7 @@ export default function EditWebTypePage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center gap-4">
<Link href={`/admin/standards/${webType}`}>
@ -231,7 +231,7 @@ export default function EditWebTypePage() {
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<CardContent className="space-y-8">
{/* 웹타입 코드 (읽기 전용) */}
<div className="space-y-2">
<Label htmlFor="web_type"> </Label>

View File

@ -81,7 +81,7 @@ export default function WebTypeDetailPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-4">
@ -114,7 +114,7 @@ export default function WebTypeDetailPage() {
</div>
</div>
<Tabs defaultValue="overview" className="space-y-6">
<Tabs defaultValue="overview" className="space-y-8">
<TabsList>
<TabsTrigger value="overview" className="flex items-center gap-2">
<Eye className="h-4 w-4" />
@ -131,7 +131,7 @@ export default function WebTypeDetailPage() {
</TabsList>
{/* 개요 탭 */}
<TabsContent value="overview" className="space-y-6">
<TabsContent value="overview" className="space-y-8">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* 기본 정보 */}
<Card>
@ -212,7 +212,7 @@ export default function WebTypeDetailPage() {
</TabsContent>
{/* 설정 탭 */}
<TabsContent value="config" className="space-y-6">
<TabsContent value="config" className="space-y-8">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* 기본 설정 */}
<Card>
@ -269,7 +269,7 @@ export default function WebTypeDetailPage() {
</TabsContent>
{/* JSON 데이터 탭 */}
<TabsContent value="json" className="space-y-6">
<TabsContent value="json" className="space-y-8">
<Card>
<CardHeader>
<CardTitle> JSON </CardTitle>

View File

@ -160,7 +160,7 @@ export default function NewWebTypePage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center gap-4">
<Link href="/admin/standards">
@ -182,7 +182,7 @@ export default function NewWebTypePage() {
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<CardContent className="space-y-8">
{/* 웹타입 코드 */}
<div className="space-y-2">
<Label htmlFor="web_type">

View File

@ -128,7 +128,7 @@ export default function WebTypesManagePage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -541,7 +541,7 @@ export default function TableManagementPage() {
}, [selectedTable, columns.length, totalColumns, columnsLoading, pageSize, loadColumnTypes]);
return (
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -130,7 +130,7 @@ export default function TemplatesManagePage() {
if (error) {
return (
<div className="container mx-auto p-6">
<div className="w-full max-w-none px-4 py-8">
<Card>
<CardContent className="flex flex-col items-center justify-center py-8">
<p className="mb-4 text-red-600">릿 .</p>
@ -146,7 +146,7 @@ export default function TemplatesManagePage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -9,7 +9,7 @@ import { UserManagement } from "@/components/admin/UserManagement";
export default function UserMngPage() {
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto p-6 space-y-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>

View File

@ -357,7 +357,7 @@ export default function ValidationDemoPage() {
};
return (
<div className="container mx-auto space-y-6 py-6">
<div className="container mx-auto space-y-8 py-6">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div>

View File

@ -9,7 +9,7 @@ import { Badge } from "@/components/ui/badge";
*/
export default function MainPage() {
return (
<div className="space-y-6">
<div className="pt-10 space-y-6">
{/* 메인 컨텐츠 */}
{/* Welcome Message */}
<Card>

View File

@ -219,7 +219,7 @@ export default function MultiLangPage() {
const filteredLangKeys = getFilteredLangKeys();
return (
<div className="container mx-auto space-y-6 p-6">
<div className="w-full max-w-none px-4 py-8 space-y-8">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold"> </h1>
<Button> </Button>

View File

@ -1,6 +1,6 @@
export default function MainHomePage() {
return (
<div className="space-y-6">
<div className="pt-10 space-y-6">
{/* 대시보드 컨텐츠 */}
<div className="rounded-lg border bg-white p-6 shadow-sm">
<h3 className="mb-4 text-lg font-semibold">WACE !</h3>

View File

@ -147,7 +147,7 @@ export default function ScreenViewPage() {
const screenHeight = layout?.screenResolution?.height || 800;
return (
<div className="h-full w-full overflow-auto bg-white">
<div className="h-full w-full overflow-auto bg-white pt-10">
{layout && layout.components.length > 0 ? (
// 캔버스 컴포넌트들을 정확한 해상도로 표시
<div
@ -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 사용 */}

View File

@ -240,7 +240,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
const isAdminMode = pathname.startsWith("/admin") || searchParams.get("mode") === "admin";
// 현재 모드에 따라 표시할 메뉴 결정
const currentMenus = isAdminMode ? adminMenus : userMenus;
// 관리자 모드에서는 관리자 메뉴 + 사용자 메뉴(툴 생성 메뉴 포함)를 모두 표시
const currentMenus = isAdminMode ? [...adminMenus, ...userMenus] : userMenus;
// 메뉴 토글 함수
const toggleMenu = (menuId: string) => {
@ -451,8 +452,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
</div>
</aside>
{/* 가운데 컨텐츠 영역 */}
<main className="flex-1 min-w-0 bg-white overflow-hidden">{children}</main>
{/* 가운데 컨텐츠 영역 - overflow 문제 해결 */}
<main className="flex-1 min-w-0 bg-white overflow-auto">{children}</main>
</div>
{/* 프로필 수정 모달 */}

View File

@ -92,10 +92,10 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
left: `${position.x}px`,
top: `${position.y}px`,
width: component.componentConfig?.type === "table-list"
? `${Math.max(size?.width || 400, 400)}px` // table-list는 최소 400px
? `${Math.max(size?.width || 120, 120)}px` // table-list 디폴트를 그리드 1컬럼 크기로 축소 (120px)
: `${size?.width || 100}px`,
height: component.componentConfig?.type === "table-list"
? `${Math.max(size?.height || 300, 300)}px` // table-list는 최소 300px
? `${Math.max(size?.height || 200, 200)}px` // table-list 디폴트 높이도 축소 (200px)
: `${size?.height || 36}px`,
zIndex: component.type === "layout" ? 1 : position.z || 2, // 레이아웃은 z-index 1, 다른 컴포넌트는 2 이상
...componentStyle,

View File

@ -1518,12 +1518,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
defaultConfig: component.defaultConfig,
});
// 카드 디스플레이 컴포넌트의 경우 gridColumns에 맞는 width 계산
// 컴포넌트별 gridColumns 설정 및 크기 계산
let componentSize = component.defaultSize;
const isCardDisplay = component.id === "card-display";
const gridColumns = isCardDisplay ? 8 : 1;
const isTableList = component.id === "table-list";
// 컴포넌트별 기본 그리드 컬럼 수 설정
const gridColumns = isCardDisplay ? 8 : isTableList ? 1 : 1;
if (isCardDisplay && layout.gridSettings?.snapToGrid && gridInfo) {
if ((isCardDisplay || isTableList) && layout.gridSettings?.snapToGrid && gridInfo) {
// gridColumns에 맞는 정확한 너비 계산
const calculatedWidth = calculateWidthFromColumns(
gridColumns,
@ -1531,12 +1534,15 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
layout.gridSettings as GridUtilSettings,
);
// 컴포넌트별 최소 크기 보장
const minWidth = isTableList ? 120 : isCardDisplay ? 400 : 100;
componentSize = {
...component.defaultSize,
width: calculatedWidth,
width: Math.max(calculatedWidth, minWidth),
};
console.log("📐 카드 디스플레이 초기 크기 자동 조정:", {
console.log(`📐 ${component.name} 초기 크기 자동 조정:`, {
componentId: component.id,
gridColumns,
defaultWidth: component.defaultSize.width,
@ -1554,7 +1560,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
componentType: component.id, // 새 컴포넌트 시스템의 ID (DynamicComponentRenderer용)
position: snappedPosition,
size: componentSize,
gridColumns: gridColumns, // 카드 디스플레이 컴포넌트는 기본 8그리드
gridColumns: gridColumns, // 컴포넌트별 그리드 컬럼 수 적용
componentConfig: {
type: component.id, // 새 컴포넌트 시스템의 ID 사용
webType: component.webType, // 웹타입 정보 추가
@ -1803,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;
}
@ -1998,22 +2023,12 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
// 마지막 선택된 컴포넌트를 selectedComponent로 설정
if (!isSelected) {
console.log("🎯 컴포넌트 선택 (다중 모드):", {
componentId: component.id,
componentType: component.type,
webTypeConfig: component.type === "widget" ? (component as any).webTypeConfig : null,
timestamp: new Date().toISOString(),
});
// console.log("🎯 컴포넌트 선택 (다중 모드):", component.id);
setSelectedComponent(component);
}
} else {
// 단일 선택 모드
console.log("🎯 컴포넌트 선택 (단일 모드):", {
componentId: component.id,
componentType: component.type,
webTypeConfig: component.type === "widget" ? (component as any).webTypeConfig : null,
timestamp: new Date().toISOString(),
});
// console.log("🎯 컴포넌트 선택 (단일 모드):", component.id);
setSelectedComponent(component);
setGroupState((prev) => ({
...prev,
@ -3144,9 +3159,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
isSaving={isSaving}
/>
{/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) */}
<div className="relative flex-1 overflow-auto bg-gray-100 p-8">
{/* 해상도 정보 표시 */}
{/* 메인 캔버스 영역 (스크롤 가능한 컨테이너) - 좌우 최소화, 위아래 넉넉한 여유 */}
<div className="relative flex-1 overflow-auto bg-gray-100 px-2 py-6">
{/* 해상도 정보 표시 - 적당한 여백 */}
<div className="mb-4 flex items-center justify-center">
<div className="rounded-lg border bg-white px-4 py-2 shadow-sm">
<span className="text-sm font-medium text-gray-700">
@ -3155,11 +3170,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
</div>
</div>
{/* 실제 작업 캔버스 (해상도 크기) */}
{/* 실제 작업 캔버스 (해상도 크기) - 반응형 개선 */}
<div
className="mx-auto bg-white shadow-lg"
style={{
width: screenResolution.width,
width: screenResolution.width,
height: Math.max(screenResolution.height, 800), // 최소 높이 보장
minHeight: screenResolution.height
}}
@ -3277,10 +3292,34 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
onZoneClick={handleZoneClick}
// 설정 변경 핸들러 (테이블 페이지 크기 등 설정을 상세설정에 반영)
onConfigChange={(config) => {
console.log("📤 테이블 설정 변경을 상세설정에 알림:", config);
// 여기서 DetailSettingsPanel의 상태를 업데이트하거나
// 컴포넌트의 componentConfig를 업데이트할 수 있습니다
// TODO: 실제 구현은 DetailSettingsPanel과의 연동 필요
console.log("📤 테이블 설정 변경을 상세설정에 반영:", config);
// 컴포넌트의 componentConfig 업데이트
const updatedComponents = layout.components.map(comp => {
if (comp.id === component.id) {
return {
...comp,
componentConfig: {
...comp.componentConfig,
...config
}
};
}
return comp;
});
const newLayout = {
...layout,
components: updatedComponents
};
setLayout(newLayout);
saveToHistory(newLayout);
console.log("✅ 컴포넌트 설정 업데이트 완료:", {
componentId: component.id,
updatedConfig: config
});
}}
>
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */}

View File

@ -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 카테고리 제외)

View File

@ -71,10 +71,7 @@ export const usePanelState = (panels: PanelConfig[]) => {
// 패널 열기
const openPanel = useCallback((panelId: string) => {
console.log("📂 패널 열기:", {
panelId,
timestamp: new Date().toISOString(),
});
// console.log("📂 패널 열기:", panelId);
setPanelStates((prev) => ({
...prev,
[panelId]: {

View File

@ -169,20 +169,21 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
};
}
console.log("🔧 버튼 컴포넌트 설정:", {
originalConfig: componentConfig,
processedConfig,
actionConfig: processedConfig.action,
webTypeConfig: component.webTypeConfig,
enableDataflowControl: component.webTypeConfig?.enableDataflowControl,
dataflowConfig: component.webTypeConfig?.dataflowConfig,
screenId,
tableName,
onRefresh,
onClose,
selectedRows,
selectedRowsData,
});
// 디버그 로그 (필요시 주석 해제)
// console.log("🔧 버튼 컴포넌트 설정:", {
// originalConfig: componentConfig,
// processedConfig,
// actionConfig: processedConfig.action,
// webTypeConfig: component.webTypeConfig,
// enableDataflowControl: component.webTypeConfig?.enableDataflowControl,
// dataflowConfig: component.webTypeConfig?.dataflowConfig,
// screenId,
// tableName,
// onRefresh,
// onClose,
// selectedRows,
// selectedRowsData,
// });
// 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외)
const componentStyle: React.CSSProperties = {
@ -203,7 +204,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 실제 액션 실행 함수
const executeAction = async (actionConfig: any, context: ButtonActionContext) => {
console.log("🚀 executeAction 시작:", { actionConfig, context });
// console.log("🚀 executeAction 시작:", { actionConfig, context });
try {
// 기존 토스트가 있다면 먼저 제거

View File

@ -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>
</>
);
};

View File

@ -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>
);
};

View File

@ -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;
@ -390,7 +391,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
console.log("🔗 Entity 조인 컬럼:", entityJoinColumns);
console.log("🔗 조인 탭 컬럼:", joinTabColumns);
console.log("🔗 추가 Entity 조인 컬럼:", additionalJoinColumns);
console.log("🎯 화면별 엔티티 설정:", screenEntityConfigs);
// console.log("🎯 화면별 엔티티 설정:", screenEntityConfigs);
const result = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, {
page: currentPage,
@ -463,10 +464,10 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
});
if (result) {
console.log("🎯 API 응답 결과:", result);
console.log("🎯 데이터 개수:", result.data?.length || 0);
console.log("🎯 전체 페이지:", result.totalPages);
console.log("🎯 총 아이템:", result.total);
// console.log("🎯 API 응답 결과:", result);
// console.log("🎯 데이터 개수:", result.data?.length || 0);
// console.log("🎯 전체 페이지:", result.totalPages);
// console.log("🎯 총 아이템:", result.total);
setData(result.data || []);
setTotalPages(result.totalPages || 1);
setTotalItems(result.total || 0);
@ -642,9 +643,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 🎯 표시할 컬럼 상태 업데이트
setDisplayColumns(processedColumns);
console.log("🎯 displayColumns 업데이트됨:", processedColumns);
console.log("🎯 데이터 개수:", result.data?.length || 0);
console.log("🎯 전체 데이터:", result.data);
// console.log("🎯 displayColumns 업데이트됨:", processedColumns);
// console.log("🎯 데이터 개수:", result.data?.length || 0);
// console.log("🎯 전체 데이터:", result.data);
}
} catch (err) {
console.error("테이블 데이터 로딩 오류:", err);
@ -661,7 +662,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 상세설정에 현재 페이지 정보 알림 (필요한 경우)
if (onConfigChange && tableConfig.pagination) {
console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage);
// console.log("📤 테이블에서 페이지 변경을 상세설정에 알림:", newPage);
onConfigChange({
...tableConfig,
pagination: {
@ -670,7 +671,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
},
});
} else if (!onConfigChange) {
console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가");
// console.warn("⚠️ onConfigChange가 정의되지 않음 - 페이지 변경 상세설정과 연동 불가");
}
};
@ -839,14 +840,14 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
useEffect(() => {
// 페이지 크기 동기화
if (tableConfig.pagination?.pageSize && tableConfig.pagination.pageSize !== localPageSize) {
console.log("🔄 상세설정에서 페이지 크기 변경 감지:", tableConfig.pagination.pageSize);
// console.log("🔄 상세설정에서 페이지 크기 변경 감지:", tableConfig.pagination.pageSize);
setLocalPageSize(tableConfig.pagination.pageSize);
setCurrentPage(1); // 페이지를 1로 리셋
}
// 현재 페이지 동기화 (상세설정에서 페이지를 직접 변경한 경우)
if (tableConfig.pagination?.currentPage && tableConfig.pagination.currentPage !== currentPage) {
console.log("🔄 상세설정에서 현재 페이지 변경 감지:", tableConfig.pagination.currentPage);
// console.log("🔄 상세설정에서 현재 페이지 변경 감지:", tableConfig.pagination.currentPage);
setCurrentPage(tableConfig.pagination.currentPage);
}
}, [tableConfig.pagination?.pageSize, tableConfig.pagination?.currentPage]);
@ -890,7 +891,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
})
.sort((a, b) => a.order - b.order);
} else {
console.log("🎯 사용할 컬럼이 없음");
// console.log("🎯 사용할 컬럼이 없음");
return [];
}
@ -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">
@ -1522,15 +1546,15 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
<select
value={localPageSize}
onChange={(e) => {
console.log("🚀 페이지 크기 드롭다운 변경 감지:", e.target.value);
// console.log("🚀 페이지 크기 드롭다운 변경 감지:", e.target.value);
const newPageSize = parseInt(e.target.value);
console.log("🎯 페이지 크기 변경 이벤트:", {
from: localPageSize,
to: newPageSize,
hasOnConfigChange: !!onConfigChange,
onConfigChangeType: typeof onConfigChange
});
// console.log("🎯 페이지 크기 변경 이벤트:", {
// from: localPageSize,
// to: newPageSize,
// hasOnConfigChange: !!onConfigChange,
// onConfigChangeType: typeof onConfigChange
// });
// 로컬 상태 업데이트
setLocalPageSize(newPageSize);
@ -1540,7 +1564,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 상세설정에 변경사항 알림 (pagination 설정 업데이트)
if (onConfigChange) {
console.log("📤 테이블에서 페이지 크기 변경을 상세설정에 알림:", newPageSize);
// console.log("📤 테이블에서 페이지 크기 변경을 상세설정에 알림:", newPageSize);
onConfigChange({
...tableConfig,
pagination: {
@ -1549,7 +1573,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
},
});
} else {
console.warn("⚠️ onConfigChange가 정의되지 않음 - 상세설정과 연동 불가");
// console.warn("⚠️ onConfigChange가 정의되지 않음 - 상세설정과 연동 불가");
}
// 데이터는 useEffect에서 자동으로 다시 로드됨

View File

@ -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";
@ -32,18 +33,18 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
screenTableName,
tableColumns,
}) => {
console.log("🔍 TableListConfigPanel props:", {
config,
configType: typeof config,
configSelectedTable: config?.selectedTable,
configPagination: config?.pagination,
paginationEnabled: config?.pagination?.enabled,
paginationPageSize: config?.pagination?.pageSize,
configKeys: typeof config === 'object' ? Object.keys(config || {}) : 'not object',
screenTableName,
tableColumns: tableColumns?.length,
tableColumnsSample: tableColumns?.[0],
});
// console.log("🔍 TableListConfigPanel props:", {
// config,
// configType: typeof config,
// configSelectedTable: config?.selectedTable,
// configPagination: config?.pagination,
// paginationEnabled: config?.pagination?.enabled,
// paginationPageSize: config?.pagination?.pageSize,
// configKeys: typeof config === 'object' ? Object.keys(config || {}) : 'not object',
// screenTableName,
// tableColumns: tableColumns?.length,
// tableColumnsSample: tableColumns?.[0],
// });
const [availableTables, setAvailableTables] = useState<Array<{ tableName: string; displayName: string }>>([]);
const [loadingTables, setLoadingTables] = useState(false);
@ -73,6 +74,16 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const [loadingEntityJoins, setLoadingEntityJoins] = useState(false);
// 🔄 외부에서 config가 변경될 때 내부 상태 동기화 (표의 페이지네이션 변경 감지)
useEffect(() => {
// console.log("🔄 TableListConfigPanel - 외부 config 변경 감지:", {
// configPagination: config?.pagination,
// configPageSize: config?.pagination?.pageSize,
// });
// 현재는 별도 내부 상태가 없어서 자동으로 UI가 업데이트됨
// 만약 내부 상태가 있다면 여기서 동기화 처리
}, [config]);
// 🎯 엔티티 컬럼 표시 설정을 위한 상태
const [entityDisplayConfigs, setEntityDisplayConfigs] = useState<
Record<
@ -216,14 +227,14 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
};
const handleNestedChange = (parentKey: keyof TableListConfig, childKey: string, value: any) => {
console.log("🔧 TableListConfigPanel handleNestedChange:", {
parentKey,
childKey,
value,
parentValue: config[parentKey],
hasOnChange: !!onChange,
onChangeType: typeof onChange,
});
// console.log("🔧 TableListConfigPanel handleNestedChange:", {
// parentKey,
// childKey,
// value,
// parentValue: config[parentKey],
// hasOnChange: !!onChange,
// onChangeType: typeof onChange,
// });
const parentValue = config[parentKey] as any;
const newConfig = {
@ -233,7 +244,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
},
};
console.log("📤 TableListConfigPanel onChange 호출:", newConfig);
// console.log("📤 TableListConfigPanel onChange 호출:", newConfig);
onChange(newConfig);
};
@ -745,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>
@ -870,8 +1063,16 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
<div className="space-y-2">
<Label htmlFor="pageSize"> </Label>
<Select
key={`pageSize-${config.pagination?.pageSize}`}
value={config.pagination?.pageSize?.toString() || "20"}
onValueChange={(value) => handleNestedChange("pagination", "pageSize", parseInt(value))}
onValueChange={(value) => {
// console.log("🎯 상세설정에서 페이지 크기 변경:", {
// from: config.pagination?.pageSize,
// to: parseInt(value),
// currentConfigPageSize: config.pagination?.pageSize
// });
handleNestedChange("pagination", "pageSize", parseInt(value));
}}
>
<SelectTrigger>
<SelectValue />

View File

@ -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);
}

View File

@ -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,
@ -83,7 +95,7 @@ export const TableListDefinition = createComponentDefinition({
// 데이터 로딩
autoLoad: true,
},
defaultSize: { width: 800, height: 960 },
defaultSize: { width: 120, height: 200 }, // 그리드 1컬럼 크기로 축소
configPanel: TableListConfigPanel,
icon: "Table",
tags: ["테이블", "데이터", "목록", "그리드"],

View File

@ -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;

View File

@ -58,21 +58,22 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
}
: autoGeneration;
console.log("🔧 텍스트 입력 컴포넌트 설정:", {
config,
componentConfig,
component: component,
autoGeneration,
testAutoGeneration,
isTestMode: component.label?.toLowerCase().includes("test"),
isHidden,
isInteractive,
formData,
columnName: component.columnName,
currentFormValue: formData?.[component.columnName],
componentValue: component.value,
autoGeneratedValue,
});
// 디버그 로그 (필요시 주석 해제)
// console.log("🔧 텍스트 입력 컴포넌트 설정:", {
// config,
// componentConfig,
// component: component,
// autoGeneration,
// testAutoGeneration,
// isTestMode: component.label?.toLowerCase().includes("test"),
// isHidden,
// isInteractive,
// formData,
// columnName: component.columnName,
// currentFormValue: formData?.[component.columnName],
// componentValue: component.value,
// autoGeneratedValue,
// });
// 자동생성 값 생성 (컴포넌트 마운트 시 또는 폼 데이터 변경 시)
useEffect(() => {
@ -248,18 +249,18 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
onDragEnd={onDragEnd}
onChange={(e) => {
const newValue = e.target.value;
console.log("🎯 TextInputComponent onChange 호출:", {
componentId: component.id,
columnName: component.columnName,
newValue,
isInteractive,
hasOnFormDataChange: !!onFormDataChange,
hasOnChange: !!props.onChange,
});
// console.log("🎯 TextInputComponent onChange 호출:", {
// componentId: component.id,
// columnName: component.columnName,
// newValue,
// isInteractive,
// hasOnFormDataChange: !!onFormDataChange,
// hasOnChange: !!props.onChange,
// });
// isInteractive 모드에서는 formData 업데이트
if (isInteractive && onFormDataChange && component.columnName) {
console.log(`📤 TextInputComponent -> onFormDataChange 호출: ${component.columnName} = "${newValue}"`);
// console.log(`📤 TextInputComponent -> onFormDataChange 호출: ${component.columnName} = "${newValue}"`);
console.log("🔍 onFormDataChange 함수 정보:", {
functionName: onFormDataChange.name,
functionString: onFormDataChange.toString().substring(0, 200),
@ -275,7 +276,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// 기존 onChange 핸들러도 호출
if (props.onChange) {
console.log(`📤 TextInputComponent -> props.onChange 호출: "${newValue}"`);
// console.log(`📤 TextInputComponent -> props.onChange 호출: "${newValue}"`);
props.onChange(newValue);
}
}}

View File

@ -1,11 +1,5 @@
import { Position, Size } from "@/types/screen";
export interface GridSettings {
columns: number;
gap: number;
padding: number;
snapToGrid: boolean;
}
import { GridSettings } from "@/types/screen-management";
export interface GridInfo {
columnWidth: number;

View File

@ -34,6 +34,12 @@ export interface BaseComponent {
readonly?: boolean;
style?: ComponentStyle;
className?: string;
// 새 컴포넌트 시스템에서 필요한 속성들
gridColumns?: number; // 그리드에서 차지할 컬럼 수 (1-12)
zoneId?: string; // 레이아웃 존 ID
componentConfig?: any; // 컴포넌트별 설정
componentType?: string; // 새 컴포넌트 시스템의 ID
webTypeConfig?: WebTypeConfig; // 웹타입별 설정
}
/**
@ -110,10 +116,20 @@ export interface FileComponent extends BaseComponent {
uploadedFiles?: UploadedFile[];
}
/**
*
*/
export interface ComponentComponent extends BaseComponent {
type: "component";
widgetType: WebType; // 웹타입 (기존 호환성)
componentType: string; // 새 컴포넌트 시스템의 ID
componentConfig: any; // 컴포넌트별 설정
}
/**
*
*/
export type ComponentData = WidgetComponent | ContainerComponent | GroupComponent | DataTableComponent | FileComponent;
export type ComponentData = WidgetComponent | ContainerComponent | GroupComponent | DataTableComponent | FileComponent | ComponentComponent;
// ===== 웹타입별 설정 인터페이스 =====
@ -370,6 +386,13 @@ export interface GridSettings {
color: string;
opacity: number;
snapToGrid: boolean;
// gridUtils에서 필요한 속성들 추가
columns: number;
gap: number;
padding: number;
showGrid?: boolean;
gridColor?: string;
gridOpacity?: number;
}
/**

View File

@ -136,6 +136,8 @@ export interface CommonStyle {
lineHeight?: string;
// 라벨 스타일
labelDisplay?: boolean; // 라벨 표시 여부
labelText?: string; // 라벨 텍스트
labelFontSize?: string;
labelColor?: string;
labelFontWeight?: string;