패널 정리중

This commit is contained in:
kjs 2025-10-28 18:41:45 +09:00
parent 743ae6dbf1
commit eeae338cd4
5 changed files with 457 additions and 892 deletions

View File

@ -61,6 +61,24 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
const [displayColumnOpen, setDisplayColumnOpen] = useState(false);
const [displayColumnSearch, setDisplayColumnSearch] = useState("");
// 🎯 플로우 위젯이 화면에 있는지 확인
const hasFlowWidget = useMemo(() => {
const found = allComponents.some((comp: any) => {
// ScreenDesigner에서 저장하는 componentType 속성 확인!
const compType = comp.componentType || comp.widgetType || "";
// "flow-widget" 체크
const isFlow = compType === "flow-widget" || compType?.toLowerCase().includes("flow");
if (isFlow) {
console.log("✅ 플로우 위젯 발견!", { id: comp.id, componentType: comp.componentType });
}
return isFlow;
});
console.log("🎯 플로우 위젯 존재 여부:", found);
return found;
}, [allComponents]);
// 컴포넌트 prop 변경 시 로컬 상태 동기화 (Input만)
useEffect(() => {
const latestConfig = component.componentConfig || {};
@ -298,7 +316,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
variant="outline"
role="combobox"
aria-expanded={modalScreenOpen}
className="h-6 w-full px-2 py-0 justify-between" style={{ fontSize: "12px" }}
className="h-6 w-full justify-between px-2 py-0"
style={{ fontSize: "12px" }}
disabled={screensLoading}
>
{config.action?.targetScreenId
@ -372,7 +391,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
variant="outline"
role="combobox"
aria-expanded={modalScreenOpen}
className="h-6 w-full px-2 py-0 justify-between" style={{ fontSize: "12px" }}
className="h-6 w-full justify-between px-2 py-0"
style={{ fontSize: "12px" }}
disabled={screensLoading}
>
{config.action?.targetScreenId
@ -526,7 +546,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
variant="outline"
role="combobox"
aria-expanded={displayColumnOpen}
className="mt-2 h-8 w-full justify-between text-xs" style={{ fontSize: "12px" }}
className="mt-2 h-8 w-full justify-between text-xs"
style={{ fontSize: "12px" }}
disabled={columnsLoading || tableColumns.length === 0}
>
{columnsLoading
@ -543,7 +564,9 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" style={{ fontSize: "12px" }} />
<CommandList>
<CommandEmpty className="text-xs" style={{ fontSize: "12px" }}> .</CommandEmpty>
<CommandEmpty className="text-xs" style={{ fontSize: "12px" }}>
.
</CommandEmpty>
<CommandGroup>
{tableColumns.map((column) => (
<CommandItem
@ -553,7 +576,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
onUpdateProperty("componentConfig.action.historyDisplayColumn", currentValue);
setDisplayColumnOpen(false);
}}
className="text-xs" style={{ fontSize: "12px" }}
className="text-xs"
style={{ fontSize: "12px" }}
>
<Check
className={cn(
@ -586,7 +610,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
variant="outline"
role="combobox"
aria-expanded={navScreenOpen}
className="h-6 w-full px-2 py-0 justify-between" style={{ fontSize: "12px" }}
className="h-6 w-full justify-between px-2 py-0"
style={{ fontSize: "12px" }}
disabled={screensLoading}
>
{config.action?.targetScreenId
@ -659,7 +684,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
setLocalInputs((prev) => ({ ...prev, targetUrl: newValue }));
onUpdateProperty("componentConfig.action.targetUrl", newValue);
}}
className="h-6 w-full px-2 py-0 text-xs" style={{ fontSize: "12px" }}
className="h-6 w-full px-2 py-0 text-xs"
style={{ fontSize: "12px" }}
/>
<p className="mt-1 text-xs text-gray-500">URL을 </p>
</div>
@ -671,7 +697,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
</div>
{/* 🆕 플로우 단계별 표시 제어 섹션 */}
{/* 🆕 플로우 단계별 표시 제어 섹션 (플로우 위젯이 있을 때만 표시) */}
{hasFlowWidget && (
<div className="mt-8 border-t border-gray-200 pt-6">
<FlowVisibilityConfigPanel
component={component}
@ -679,6 +706,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
onUpdateProperty={onUpdateProperty}
/>
</div>
)}
</div>
);
};

View File

@ -9,6 +9,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
import { Workflow, Info, CheckCircle, XCircle, Loader2, ArrowRight, ArrowDown } from "lucide-react";
import { ComponentData } from "@/types/screen";
import { FlowVisibilityConfig } from "@/types/control-management";
@ -344,7 +345,11 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
checked={isChecked}
onCheckedChange={() => toggleStep(step.id)}
/>
<Label htmlFor={`step-${step.id}`} className="flex flex-1 items-center gap-2 text-xs" style={{ fontSize: "12px" }}>
<Label
htmlFor={`step-${step.id}`}
className="flex flex-1 items-center gap-2 text-xs"
style={{ fontSize: "12px" }}
>
<Badge variant="outline" className="text-xs">
Step {step.stepOrder}
</Badge>
@ -403,7 +408,8 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
value={groupId}
onChange={(e) => setGroupId(e.target.value)}
placeholder="group-1"
className="h-6 text-xs sm:h-9 sm:text-xs" style={{ fontSize: "12px" }}
className="h-6 text-xs sm:h-9 sm:text-xs"
style={{ fontSize: "12px" }}
/>
<p className="text-muted-foreground text-[10px]">
ID를
@ -453,7 +459,8 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
setGroupGap(Number(e.target.value));
setTimeout(() => applyConfig(), 0);
}}
className="h-6 text-xs sm:h-9 sm:text-xs" style={{ fontSize: "12px" }}
className="h-6 text-xs sm:h-9 sm:text-xs"
style={{ fontSize: "12px" }}
/>
<Badge variant="outline" className="text-xs">
{groupGap}px
@ -473,7 +480,11 @@ export const FlowVisibilityConfigPanel: React.FC<FlowVisibilityConfigPanelProps>
setTimeout(() => applyConfig(), 0);
}}
>
<SelectTrigger id="group-align" className="h-6 text-xs sm:h-9 sm:text-xs" style={{ fontSize: "12px" }}>
<SelectTrigger
id="group-align"
className="h-6 text-xs sm:h-9 sm:text-xs"
style={{ fontSize: "12px" }}
>
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@ -20,7 +20,7 @@ interface SingleTableWithStickyProps {
handleSelectAll: (checked: boolean) => void;
handleRowClick: (row: any) => void;
renderCheckboxCell: (row: any, index: number) => React.ReactNode;
formatCellValue: (value: any, format?: string, columnName?: string) => string;
formatCellValue: (value: any, format?: string, columnName?: string, rowData?: Record<string, any>) => string;
getColumnWidth: (column: ColumnConfig) => number;
containerWidth?: string; // 컨테이너 너비 설정
}
@ -63,7 +63,13 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
boxSizing: "border-box",
}}
>
<TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-20 bg-gradient-to-r from-slate-50/90 to-gray-50/70 backdrop-blur-sm border-b border-gray-200/40" : "bg-gradient-to-r from-slate-50/90 to-gray-50/70 backdrop-blur-sm border-b border-gray-200/40"}>
<TableHeader
className={
tableConfig.stickyHeader
? "sticky top-0 z-20 border-b border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 backdrop-blur-sm"
: "border-b border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 backdrop-blur-sm"
}
>
<TableRow className="border-b border-gray-200/40">
{visibleColumns.map((column, colIndex) => {
// 왼쪽 고정 컬럼들의 누적 너비 계산
@ -86,12 +92,14 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
className={cn(
column.columnName === "__checkbox__"
? "h-12 border-0 px-6 py-4 text-center align-middle"
: "h-12 cursor-pointer border-0 px-6 py-4 text-left align-middle font-semibold whitespace-nowrap text-gray-700 select-none transition-all duration-200 hover:text-gray-900",
: "h-12 cursor-pointer border-0 px-6 py-4 text-left align-middle font-semibold whitespace-nowrap text-gray-700 transition-all duration-200 select-none hover:text-gray-900",
`text-${column.align}`,
column.sortable && "hover:bg-orange-200/70",
// 고정 컬럼 스타일
column.fixed === "left" && "sticky z-10 border-r border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 shadow-sm",
column.fixed === "right" && "sticky z-10 border-l border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 shadow-sm",
column.fixed === "left" &&
"sticky z-10 border-r border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 shadow-sm",
column.fixed === "right" &&
"sticky z-10 border-l border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70 shadow-sm",
// 숨김 컬럼 스타일 (디자인 모드에서만)
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
)}
@ -112,7 +120,12 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
<div className="flex items-center gap-2">
{column.columnName === "__checkbox__" ? (
checkboxConfig.selectAll && (
<Checkbox checked={isAllSelected} onCheckedChange={handleSelectAll} aria-label="전체 선택" style={{ zIndex: 1 }} />
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
aria-label="전체 선택"
style={{ zIndex: 1 }}
/>
)
) : (
<>
@ -144,11 +157,18 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
<div className="flex flex-col items-center justify-center space-y-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-br from-gray-100 to-gray-200">
<svg className="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<span className="text-sm font-medium text-gray-500"> </span>
<span className="text-xs text-gray-400 bg-gray-100 px-3 py-1 rounded-full"> </span>
<span className="rounded-full bg-gray-100 px-3 py-1 text-xs text-gray-400">
</span>
</div>
</TableCell>
</TableRow>
@ -158,7 +178,8 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
key={`row-${index}`}
className={cn(
"h-12 cursor-pointer border-b border-gray-100/40 leading-none transition-all duration-200",
tableConfig.tableStyle?.hoverEffect && "hover:bg-gradient-to-r hover:from-orange-50/80 hover:to-orange-100/60 hover:shadow-sm",
tableConfig.tableStyle?.hoverEffect &&
"hover:bg-gradient-to-r hover:from-orange-50/80 hover:to-orange-100/60 hover:shadow-sm",
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/30",
)}
style={{ minHeight: "48px", height: "48px", lineHeight: "1" }}
@ -186,8 +207,10 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
"h-12 px-6 py-4 align-middle text-sm whitespace-nowrap text-gray-600 transition-all duration-200",
`text-${column.align}`,
// 고정 컬럼 스타일
column.fixed === "left" && "sticky z-10 border-r border-gray-200/40 bg-white/90 backdrop-blur-sm",
column.fixed === "right" && "sticky z-10 border-l border-gray-200/40 bg-white/90 backdrop-blur-sm",
column.fixed === "left" &&
"sticky z-10 border-r border-gray-200/40 bg-white/90 backdrop-blur-sm",
column.fixed === "right" &&
"sticky z-10 border-l border-gray-200/40 bg-white/90 backdrop-blur-sm",
)}
style={{
minHeight: "48px",
@ -207,7 +230,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
>
{column.columnName === "__checkbox__"
? renderCheckboxCell(row, index)
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
: formatCellValue(row[column.columnName], column.format, column.columnName, row) || "\u00A0"}
</TableCell>
);
})}

View File

@ -620,9 +620,29 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
};
const formatCellValue = useCallback(
(value: any, column: ColumnConfig) => {
(value: any, column: ColumnConfig, rowData?: Record<string, any>) => {
if (value === null || value === undefined) return "-";
// 🎯 엔티티 컬럼 표시 설정이 있는 경우
if (column.entityDisplayConfig && rowData) {
// displayColumns 또는 selectedColumns 둘 다 체크
const displayColumns = column.entityDisplayConfig.displayColumns || column.entityDisplayConfig.selectedColumns;
const separator = column.entityDisplayConfig.separator;
if (displayColumns && displayColumns.length > 0) {
// 선택된 컬럼들의 값을 구분자로 조합
const values = displayColumns
.map((colName) => {
const cellValue = rowData[colName];
if (cellValue === null || cellValue === undefined) return "";
return String(cellValue);
})
.filter((v) => v !== ""); // 빈 값 제외
return values.join(separator || " - ");
}
}
const meta = columnMeta[column.columnName];
if (meta?.webType && meta?.codeCategory) {
const convertedValue = optimizedConvertCode(value, meta.codeCategory);
@ -908,9 +928,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
columnLabels={columnLabels}
renderCheckboxHeader={renderCheckboxHeader}
renderCheckboxCell={renderCheckboxCell}
formatCellValue={(value: any, format?: string, columnName?: string) => {
formatCellValue={(value: any, format?: string, columnName?: string, rowData?: Record<string, any>) => {
const column = visibleColumns.find((c) => c.columnName === columnName);
return column ? formatCellValue(value, column) : String(value);
return column ? formatCellValue(value, column, rowData) : String(value);
}}
getColumnWidth={getColumnWidth}
containerWidth={calculatedWidth}
@ -1091,7 +1111,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
>
{column.columnName === "__checkbox__"
? renderCheckboxCell(row, index)
: formatCellValue(cellValue, column)}
: formatCellValue(cellValue, column, row)}
</td>
);
})}