From e234f885772e056de7be91e9cfddbb6f2b2562f0 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 18 Nov 2025 10:14:31 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=95=AD=EB=AA=A9=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(=EA=B8=B0=EB=B3=B8=EA=B0=92,=20=EB=B9=88=20=EA=B0=92=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=8F=AC=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DisplayItem 타입 추가 (icon, field, text, badge) - 필드별 표시 형식 지원 (text, currency, number, date, badge) - 빈 값 처리 옵션 추가 (hide, default, blank) - 기본값 설정 기능 추가 - 스타일 옵션 추가 (굵게, 밑줄, 기울임, 색상) - renderDisplayItems 헬퍼 함수로 유연한 표시 렌더링 - SelectedItemsDetailInputConfigPanel에 항목 표시 설정 UI 추가 - displayItems가 없으면 기존 방식(모든 필드 나열)으로 폴백 --- .../SelectedItemsDetailInputComponent.tsx | 160 +++++++++- .../SelectedItemsDetailInputConfigPanel.tsx | 286 +++++++++++++++++- .../selected-items-detail-input/types.ts | 68 +++++ 3 files changed, 510 insertions(+), 4 deletions(-) diff --git a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx index a70d8e74..da16a349 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx +++ b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx @@ -3,16 +3,18 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; import { useSearchParams } from "next/navigation"; import { ComponentRendererProps } from "@/types/component"; -import { SelectedItemsDetailInputConfig, AdditionalFieldDefinition, ItemData, GroupEntry } from "./types"; +import { SelectedItemsDetailInputConfig, AdditionalFieldDefinition, ItemData, GroupEntry, DisplayItem } from "./types"; import { useModalDataStore, ModalDataItem } from "@/stores/modalDataStore"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { X } from "lucide-react"; +import * as LucideIcons from "lucide-react"; import { commonCodeApi } from "@/lib/api/commonCode"; import { cn } from "@/lib/utils"; @@ -542,6 +544,158 @@ export const SelectedItemsDetailInputComponent: React.FC { + const displayItems = componentConfig.displayItems || []; + + if (displayItems.length === 0) { + // displayItems가 없으면 기본 방식 (모든 필드 나열) + const fields = componentConfig.additionalFields || []; + return fields.map((f) => entry[f.name] || "-").join(" / "); + } + + // displayItems 설정대로 렌더링 + return ( + <> + {displayItems.map((displayItem) => { + const styleClasses = cn( + displayItem.bold && "font-bold", + displayItem.underline && "underline", + displayItem.italic && "italic" + ); + + const inlineStyle: React.CSSProperties = { + color: displayItem.color, + backgroundColor: displayItem.backgroundColor, + }; + + switch (displayItem.type) { + case "icon": { + if (!displayItem.icon) return null; + const IconComponent = (LucideIcons as any)[displayItem.icon]; + if (!IconComponent) return null; + return ( + + ); + } + + case "text": + return ( + + {displayItem.value} + + ); + + case "field": { + const fieldValue = entry[displayItem.fieldName || ""]; + const isEmpty = fieldValue === null || fieldValue === undefined || fieldValue === ""; + + // 🆕 빈 값 처리 + if (isEmpty) { + switch (displayItem.emptyBehavior) { + case "hide": + return null; // 항목 숨김 + case "default": + // 기본값 표시 + const defaultValue = displayItem.defaultValue || "-"; + return ( + + {displayItem.label}{defaultValue} + + ); + case "blank": + default: + // 빈 칸으로 표시 + return ( + + {displayItem.label} + + ); + } + } + + // 값이 있는 경우, 형식에 맞게 표시 + let formattedValue = fieldValue; + switch (displayItem.format) { + case "currency": + // 천 단위 구분 + formattedValue = new Intl.NumberFormat("ko-KR").format(Number(fieldValue) || 0); + break; + case "number": + formattedValue = new Intl.NumberFormat("ko-KR").format(Number(fieldValue) || 0); + break; + case "date": + // YYYY.MM.DD 형식 + if (fieldValue) { + const date = new Date(fieldValue); + if (!isNaN(date.getTime())) { + formattedValue = date.toLocaleDateString("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }).replace(/\. /g, ".").replace(/\.$/, ""); + } + } + break; + case "badge": + // 배지로 표시 + return ( + + {displayItem.label}{formattedValue} + + ); + case "text": + default: + // 일반 텍스트 + break; + } + + return ( + + {displayItem.label}{formattedValue} + + ); + } + + case "badge": { + const fieldValue = displayItem.fieldName ? entry[displayItem.fieldName] : displayItem.value; + return ( + + {displayItem.label}{fieldValue} + + ); + } + + default: + return null; + } + })} + + ); + }, [componentConfig.displayItems, componentConfig.additionalFields]); + // 빈 상태 렌더링 if (items.length === 0) { return ( @@ -650,8 +804,8 @@ export const SelectedItemsDetailInputComponent: React.FC handleEditGroupEntry(item.id, group.id, entry.id)} > - - {idx + 1}. {groupFields.map((f) => entry[f.name] || "-").join(" / ")} + + {idx + 1}. {renderDisplayItems(entry, item)} + + + + + +

+ 각 입력 항목이 추가되면 어떻게 표시될지 설정합니다 +

+ + {displayItems.length === 0 ? ( +
+ 표시 항목이 없습니다. 위의 버튼으로 추가하세요. +
+ (미설정 시 모든 필드를 " / "로 구분하여 표시) +
+ ) : ( +
+ {displayItems.map((item, index) => ( + + + {/* 헤더 */} +
+ + {item.type === "icon" && "🎨 아이콘"} + {item.type === "field" && "📝 필드"} + {item.type === "text" && "💬 텍스트"} + {item.type === "badge" && "🏷️ 배지"} + + +
+ + {/* 아이콘 설정 */} + {item.type === "icon" && ( +
+
+ + updateDisplayItem(index, { icon: e.target.value })} + placeholder="예: Building, User, Package" + className="h-7 text-xs" + /> +

+ lucide-react 아이콘 이름을 입력하세요 +

+
+
+ )} + + {/* 텍스트 설정 */} + {item.type === "text" && ( +
+ + updateDisplayItem(index, { value: e.target.value })} + placeholder="예: | , / , - " + className="h-7 text-xs" + /> +
+ )} + + {/* 필드 설정 */} + {item.type === "field" && ( +
+ {/* 필드 선택 */} +
+ + +
+ + {/* 라벨 */} +
+ + updateDisplayItem(index, { label: e.target.value })} + placeholder="예: 거래처:, 단가:" + className="h-7 text-xs" + /> +
+ + {/* 표시 형식 */} +
+ + +
+ + {/* 빈 값 처리 */} +
+ + +
+ + {/* 기본값 (emptyBehavior가 "default"일 때만) */} + {item.emptyBehavior === "default" && ( +
+ + updateDisplayItem(index, { defaultValue: e.target.value })} + placeholder="예: 미입력, 0, -" + className="h-7 text-xs" + /> +
+ )} +
+ )} + + {/* 스타일 설정 */} +
+ +
+ + + +
+ + {/* 색상 */} +
+
+ + updateDisplayItem(index, { color: e.target.value })} + className="h-7 w-full" + /> +
+
+ + updateDisplayItem(index, { backgroundColor: e.target.value })} + className="h-7 w-full" + /> +
+
+
+
+
+ ))} +
+ )} + + {/* 사용 예시 */}

💡 사용 예시

diff --git a/frontend/lib/registry/components/selected-items-detail-input/types.ts b/frontend/lib/registry/components/selected-items-detail-input/types.ts index c8a67a62..6685fc50 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/types.ts +++ b/frontend/lib/registry/components/selected-items-detail-input/types.ts @@ -115,6 +115,12 @@ export interface SelectedItemsDetailInputConfig extends ComponentConfig { */ inputMode?: "inline" | "modal"; + /** + * 🆕 항목 표시 설정 (각 그룹의 입력 항목을 어떻게 표시할지) + * 예: [{ type: "icon", icon: "Building" }, { type: "field", fieldName: "customer_name", label: "거래처:" }] + */ + displayItems?: DisplayItem[]; + /** * 빈 상태 메시지 */ @@ -135,6 +141,68 @@ export interface GroupEntry { [key: string]: any; } +/** + * 🆕 표시 항목 타입 + */ +export type DisplayItemType = "icon" | "field" | "text" | "badge"; + +/** + * 🆕 빈 값 처리 방식 + */ +export type EmptyBehavior = "hide" | "default" | "blank"; + +/** + * 🆕 필드 표시 형식 + */ +export type DisplayFieldFormat = "text" | "date" | "currency" | "number" | "badge"; + +/** + * 🆕 표시 항목 정의 (아이콘, 필드, 텍스트, 배지) + */ +export interface DisplayItem { + /** 항목 타입 */ + type: DisplayItemType; + + /** 고유 ID */ + id: string; + + // === type: "field" 인 경우 === + /** 필드명 (컬럼명) */ + fieldName?: string; + /** 라벨 (예: "거래처:", "단가:") */ + label?: string; + /** 표시 형식 */ + format?: DisplayFieldFormat; + /** 빈 값일 때 동작 */ + emptyBehavior?: EmptyBehavior; + /** 기본값 (빈 값일 때 표시) */ + defaultValue?: string; + + // === type: "icon" 인 경우 === + /** 아이콘 이름 (lucide-react 아이콘명) */ + icon?: string; + + // === type: "text" 인 경우 === + /** 텍스트 내용 */ + value?: string; + + // === type: "badge" 인 경우 === + /** 배지 스타일 */ + badgeVariant?: "default" | "secondary" | "destructive" | "outline"; + + // === 공통 스타일 === + /** 굵게 표시 */ + bold?: boolean; + /** 밑줄 표시 */ + underline?: boolean; + /** 기울임 표시 */ + italic?: boolean; + /** 텍스트 색상 */ + color?: string; + /** 배경 색상 */ + backgroundColor?: string; +} + /** * 🆕 품목 + 그룹별 여러 입력 항목 * 각 필드 그룹마다 독립적으로 여러 개의 입력을 추가할 수 있음