Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node
This commit is contained in:
commit
f59fcb56b0
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 사용 */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
{/* 프로필 수정 모달 */}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}}
|
||||
>
|
||||
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */}
|
||||
|
|
|
|||
|
|
@ -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 카테고리 제외)
|
||||
|
|
|
|||
|
|
@ -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]: {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
// 기존 토스트가 있다면 먼저 제거
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 { 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에서 자동으로 다시 로드됨
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -67,3 +67,16 @@ export class TableListRenderer extends AutoRegisteringComponentRenderer {
|
|||
|
||||
// 자동 등록 실행
|
||||
TableListRenderer.registerSelf();
|
||||
|
||||
// 강제 등록 (디버깅용)
|
||||
if (typeof window !== "undefined") {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
console.log("🔄 TableList 강제 등록 시도...");
|
||||
TableListRenderer.registerSelf();
|
||||
console.log("✅ TableList 강제 등록 완료");
|
||||
} catch (error) {
|
||||
console.error("❌ TableList 강제 등록 실패:", error);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,21 @@ export const TableListDefinition = createComponentDefinition({
|
|||
nameEng: "TableList Component",
|
||||
description: "데이터베이스 테이블의 데이터를 목록으로 표시하는 컴포넌트",
|
||||
category: ComponentCategory.DISPLAY,
|
||||
webType: "table",
|
||||
webType: "text",
|
||||
component: TableListWrapper,
|
||||
defaultConfig: {
|
||||
// 표시 모드 설정
|
||||
displayMode: "table" as const,
|
||||
|
||||
// 카드 모드 기본 설정
|
||||
cardConfig: {
|
||||
idColumn: "id",
|
||||
titleColumn: "name",
|
||||
cardsPerRow: 3,
|
||||
cardSpacing: 16,
|
||||
showActions: true,
|
||||
},
|
||||
|
||||
// 테이블 기본 설정
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
|
|
@ -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: ["테이블", "데이터", "목록", "그리드"],
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ export interface CommonStyle {
|
|||
lineHeight?: string;
|
||||
|
||||
// 라벨 스타일
|
||||
labelDisplay?: boolean; // 라벨 표시 여부
|
||||
labelText?: string; // 라벨 텍스트
|
||||
labelFontSize?: string;
|
||||
labelColor?: string;
|
||||
labelFontWeight?: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue