feat: 항목 표시 설정 기능 추가 (기본값, 빈 값 처리 포함)
- DisplayItem 타입 추가 (icon, field, text, badge) - 필드별 표시 형식 지원 (text, currency, number, date, badge) - 빈 값 처리 옵션 추가 (hide, default, blank) - 기본값 설정 기능 추가 - 스타일 옵션 추가 (굵게, 밑줄, 기울임, 색상) - renderDisplayItems 헬퍼 함수로 유연한 표시 렌더링 - SelectedItemsDetailInputConfigPanel에 항목 표시 설정 UI 추가 - displayItems가 없으면 기존 방식(모든 필드 나열)으로 폴백
This commit is contained in:
@@ -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<SelectedItemsDetailInpu
|
||||
}
|
||||
};
|
||||
|
||||
// 🆕 displayItems를 렌더링하는 헬퍼 함수
|
||||
const renderDisplayItems = useCallback((entry: GroupEntry, item: ItemData) => {
|
||||
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 (
|
||||
<IconComponent
|
||||
key={displayItem.id}
|
||||
className="h-3 w-3 inline-block mr-1"
|
||||
style={inlineStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case "text":
|
||||
return (
|
||||
<span
|
||||
key={displayItem.id}
|
||||
className={styleClasses}
|
||||
style={inlineStyle}
|
||||
>
|
||||
{displayItem.value}
|
||||
</span>
|
||||
);
|
||||
|
||||
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 (
|
||||
<span
|
||||
key={displayItem.id}
|
||||
className={cn(styleClasses, "text-muted-foreground")}
|
||||
style={inlineStyle}
|
||||
>
|
||||
{displayItem.label}{defaultValue}
|
||||
</span>
|
||||
);
|
||||
case "blank":
|
||||
default:
|
||||
// 빈 칸으로 표시
|
||||
return (
|
||||
<span key={displayItem.id} className={styleClasses} style={inlineStyle}>
|
||||
{displayItem.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 값이 있는 경우, 형식에 맞게 표시
|
||||
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 (
|
||||
<Badge
|
||||
key={displayItem.id}
|
||||
variant={displayItem.badgeVariant || "default"}
|
||||
className={styleClasses}
|
||||
style={inlineStyle}
|
||||
>
|
||||
{displayItem.label}{formattedValue}
|
||||
</Badge>
|
||||
);
|
||||
case "text":
|
||||
default:
|
||||
// 일반 텍스트
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={displayItem.id} className={styleClasses} style={inlineStyle}>
|
||||
{displayItem.label}{formattedValue}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
case "badge": {
|
||||
const fieldValue = displayItem.fieldName ? entry[displayItem.fieldName] : displayItem.value;
|
||||
return (
|
||||
<Badge
|
||||
key={displayItem.id}
|
||||
variant={displayItem.badgeVariant || "default"}
|
||||
className={styleClasses}
|
||||
style={inlineStyle}
|
||||
>
|
||||
{displayItem.label}{fieldValue}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}, [componentConfig.displayItems, componentConfig.additionalFields]);
|
||||
|
||||
// 빈 상태 렌더링
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
@@ -650,8 +804,8 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
||||
className="flex items-center justify-between border rounded p-2 text-xs bg-muted/30 cursor-pointer hover:bg-muted/50"
|
||||
onClick={() => handleEditGroupEntry(item.id, group.id, entry.id)}
|
||||
>
|
||||
<span>
|
||||
{idx + 1}. {groupFields.map((f) => entry[f.name] || "-").join(" / ")}
|
||||
<span className="flex items-center gap-1">
|
||||
{idx + 1}. {renderDisplayItems(entry, item)}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user