Compare commits
2 Commits
85bf4882a8
...
12a8290873
| Author | SHA1 | Date |
|---|---|---|
|
|
12a8290873 | |
|
|
7a9a705f19 |
|
|
@ -26,5 +26,8 @@ export { useConnectionResolver } from "./useConnectionResolver";
|
|||
export { useCartSync } from "./useCartSync";
|
||||
export type { UseCartSyncReturn } from "./useCartSync";
|
||||
|
||||
// 설정 패널 접기/펼치기 상태 관리
|
||||
export { useCollapsibleSections } from "./useCollapsibleSections";
|
||||
|
||||
// SQL 빌더 유틸 (고급 사용 시)
|
||||
export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { useState, useCallback, useRef } from "react";
|
||||
|
||||
/**
|
||||
* 설정 패널 접기/펼치기 상태를 sessionStorage로 기억하는 훅
|
||||
*
|
||||
* - 초기 상태: 모든 섹션 접힘
|
||||
* - 사용자가 펼친 섹션은 같은 탭 세션 내에서 기억
|
||||
* - 탭 닫으면 초기화
|
||||
*
|
||||
* @param storageKey sessionStorage 키 (예: "pop-card-list")
|
||||
*/
|
||||
export function useCollapsibleSections(storageKey: string) {
|
||||
const fullKey = `pop-config-sections-${storageKey}`;
|
||||
|
||||
const [openSections, setOpenSections] = useState<Set<string>>(() => {
|
||||
if (typeof window === "undefined") return new Set<string>();
|
||||
try {
|
||||
const saved = sessionStorage.getItem(fullKey);
|
||||
if (saved) return new Set<string>(JSON.parse(saved));
|
||||
} catch {}
|
||||
return new Set<string>();
|
||||
});
|
||||
|
||||
const openSectionsRef = useRef(openSections);
|
||||
openSectionsRef.current = openSections;
|
||||
|
||||
const persist = useCallback(
|
||||
(next: Set<string>) => {
|
||||
try {
|
||||
sessionStorage.setItem(fullKey, JSON.stringify([...next]));
|
||||
} catch {}
|
||||
},
|
||||
[fullKey],
|
||||
);
|
||||
|
||||
const isOpen = useCallback(
|
||||
(key: string) => openSectionsRef.current.has(key),
|
||||
[],
|
||||
);
|
||||
|
||||
const toggle = useCallback(
|
||||
(key: string) => {
|
||||
setOpenSections((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(key)) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.add(key);
|
||||
}
|
||||
persist(next);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[persist],
|
||||
);
|
||||
|
||||
return { isOpen, toggle };
|
||||
}
|
||||
|
|
@ -730,14 +730,13 @@ export function PopCardListComponent({
|
|||
gap: `${scaled.gap}px`,
|
||||
...(isHorizontalMode
|
||||
? {
|
||||
gridTemplateRows: `repeat(${gridRows}, ${scaled.cardHeight}px)`,
|
||||
gridTemplateRows: `repeat(${gridRows}, minmax(${scaled.cardHeight}px, auto))`,
|
||||
gridAutoFlow: "column",
|
||||
gridAutoColumns: `${scaled.cardWidth}px`,
|
||||
}
|
||||
: {
|
||||
// 세로 모드: 1fr 비율 기반으로 컨테이너 너비 초과 방지
|
||||
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
||||
gridAutoRows: `${scaled.cardHeight}px`,
|
||||
gridAutoRows: `minmax(${scaled.cardHeight}px, auto)`,
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
@ -1008,9 +1007,10 @@ function Card({
|
|||
}
|
||||
}, [effectiveMax, inputField?.enabled, limitCol, isCartListMode]);
|
||||
|
||||
const hasPackageEntries = packageEntries.length > 0;
|
||||
|
||||
const cardStyle: React.CSSProperties = {
|
||||
height: `${scaled.cardHeight}px`,
|
||||
overflow: "hidden",
|
||||
minHeight: `${scaled.cardHeight}px`,
|
||||
};
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
|
|
@ -1116,7 +1116,7 @@ function Card({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`relative cursor-pointer rounded-lg border bg-card shadow-sm transition-all duration-150 hover:shadow-md ${borderClass}`}
|
||||
className={`relative flex cursor-pointer flex-col rounded-lg border bg-card shadow-sm transition-all duration-150 hover:shadow-md ${borderClass}`}
|
||||
style={cardStyle}
|
||||
onClick={handleCardClick}
|
||||
role="button"
|
||||
|
|
@ -1157,7 +1157,7 @@ function Card({
|
|||
)}
|
||||
|
||||
{/* 본문 영역 */}
|
||||
<div className="flex" style={bodyStyle}>
|
||||
<div className="flex flex-1 overflow-hidden" style={bodyStyle}>
|
||||
{/* 이미지 (왼쪽) */}
|
||||
{image?.enabled && (
|
||||
<div className="shrink-0">
|
||||
|
|
@ -1199,7 +1199,7 @@ function Card({
|
|||
{/* 오른쪽: 수량 버튼 + 담기/취소/삭제 버튼 */}
|
||||
{(inputField?.enabled || cartAction || isCartListMode) && (
|
||||
<div
|
||||
className="ml-2 flex shrink-0 flex-col items-stretch justify-center gap-2"
|
||||
className="ml-2 flex shrink-0 flex-col items-stretch justify-start gap-2"
|
||||
style={{ minWidth: "100px" }}
|
||||
>
|
||||
{/* 수량 버튼 (입력 필드 ON일 때만) */}
|
||||
|
|
@ -1268,6 +1268,37 @@ function Card({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* 포장 요약 바: 본문 아래에 표시 */}
|
||||
{hasPackageEntries && (
|
||||
<div className="border-t bg-emerald-50">
|
||||
{packageEntries.map((entry, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between px-3 py-1.5"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-emerald-500 px-2 py-0.5 text-[10px] font-bold text-white">
|
||||
포장완료
|
||||
</span>
|
||||
<Package className="h-4 w-4 text-emerald-600" />
|
||||
<span
|
||||
className="font-medium text-emerald-700"
|
||||
style={{ fontSize: `${scaled.bodyTextSize}px` }}
|
||||
>
|
||||
{entry.packageCount}{entry.unitLabel} x {entry.quantityPerUnit}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className="font-bold text-emerald-700"
|
||||
style={{ fontSize: `${scaled.bodyTextSize}px` }}
|
||||
>
|
||||
= {entry.totalQuantity.toLocaleString()}{inputField?.unit || "EA"}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inputField?.enabled && (
|
||||
<NumberInputModal
|
||||
open={isModalOpen}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { ChevronDown, ChevronRight, Plus, Trash2, Database, Check } from "lucide-react";
|
||||
import { useCollapsibleSections } from "@/hooks/pop/useCollapsibleSections";
|
||||
import type { GridMode } from "@/components/pop/designer/types/pop-layout";
|
||||
import { GRID_BREAKPOINTS } from "@/components/pop/designer/types/pop-layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -135,6 +136,7 @@ const COLOR_OPTIONS = [
|
|||
|
||||
export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentColSpan }: ConfigPanelProps) {
|
||||
const [activeTab, setActiveTab] = useState<"basic" | "template">("basic");
|
||||
const sections = useCollapsibleSections("pop-card-list");
|
||||
|
||||
const cfg: PopCardListConfig = config || DEFAULT_CONFIG;
|
||||
|
||||
|
|
@ -184,6 +186,7 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
|
|||
onUpdate={updateConfig}
|
||||
currentMode={currentMode}
|
||||
currentColSpan={currentColSpan}
|
||||
sections={sections}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "template" && (
|
||||
|
|
@ -195,7 +198,7 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<CardTemplateTab config={cfg} onUpdate={updateConfig} />
|
||||
<CardTemplateTab config={cfg} onUpdate={updateConfig} sections={sections} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -205,16 +208,20 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
|
|||
|
||||
// ===== 기본 설정 탭 (테이블 + 레이아웃 통합) =====
|
||||
|
||||
type SectionsApi = { isOpen: (key: string) => boolean; toggle: (key: string) => void };
|
||||
|
||||
function BasicSettingsTab({
|
||||
config,
|
||||
onUpdate,
|
||||
currentMode,
|
||||
currentColSpan,
|
||||
sections,
|
||||
}: {
|
||||
config: PopCardListConfig;
|
||||
onUpdate: (partial: Partial<PopCardListConfig>) => void;
|
||||
currentMode?: GridMode;
|
||||
currentColSpan?: number;
|
||||
sections: SectionsApi;
|
||||
}) {
|
||||
const dataSource = config.dataSource || DEFAULT_DATA_SOURCE;
|
||||
const [tables, setTables] = useState<TableInfo[]>([]);
|
||||
|
|
@ -321,7 +328,7 @@ function BasicSettingsTab({
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 장바구니 목록 모드 */}
|
||||
<CollapsibleSection title="장바구니 목록 모드" defaultOpen={isCartListMode}>
|
||||
<CollapsibleSection sectionKey="basic-cart-mode" title="장바구니 목록 모드" sections={sections}>
|
||||
<CartListModeSection
|
||||
cartListMode={config.cartListMode}
|
||||
onUpdate={(cartListMode) => onUpdate({ cartListMode })}
|
||||
|
|
@ -330,7 +337,7 @@ function BasicSettingsTab({
|
|||
|
||||
{/* 테이블 선택 (장바구니 모드 시 숨김) */}
|
||||
{!isCartListMode && (
|
||||
<CollapsibleSection title="테이블 선택" defaultOpen>
|
||||
<CollapsibleSection sectionKey="basic-table" title="테이블 선택" sections={sections}>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-[10px] text-muted-foreground">데이터 테이블</Label>
|
||||
|
|
@ -365,7 +372,9 @@ function BasicSettingsTab({
|
|||
{/* 조인 설정 (장바구니 모드 시 숨김) */}
|
||||
{!isCartListMode && dataSource.tableName && (
|
||||
<CollapsibleSection
|
||||
sectionKey="basic-join"
|
||||
title="조인 설정"
|
||||
sections={sections}
|
||||
badge={
|
||||
dataSource.joins && dataSource.joins.length > 0
|
||||
? `${dataSource.joins.length}개`
|
||||
|
|
@ -383,7 +392,9 @@ function BasicSettingsTab({
|
|||
{/* 정렬 기준 (장바구니 모드 시 숨김) */}
|
||||
{!isCartListMode && dataSource.tableName && (
|
||||
<CollapsibleSection
|
||||
sectionKey="basic-sort"
|
||||
title="정렬 기준"
|
||||
sections={sections}
|
||||
badge={
|
||||
dataSource.sort
|
||||
? Array.isArray(dataSource.sort)
|
||||
|
|
@ -403,7 +414,9 @@ function BasicSettingsTab({
|
|||
{/* 필터 기준 (장바구니 모드 시 숨김) */}
|
||||
{!isCartListMode && dataSource.tableName && (
|
||||
<CollapsibleSection
|
||||
sectionKey="basic-filter"
|
||||
title="필터 기준"
|
||||
sections={sections}
|
||||
badge={
|
||||
dataSource.filters && dataSource.filters.length > 0
|
||||
? `${dataSource.filters.length}개`
|
||||
|
|
@ -421,7 +434,9 @@ function BasicSettingsTab({
|
|||
{/* 저장 매핑 (장바구니 모드일 때만) */}
|
||||
{isCartListMode && (
|
||||
<CollapsibleSection
|
||||
sectionKey="basic-save-mapping"
|
||||
title="저장 매핑"
|
||||
sections={sections}
|
||||
badge={
|
||||
config.saveMapping?.mappings && config.saveMapping.mappings.length > 0
|
||||
? `${config.saveMapping.mappings.length}개`
|
||||
|
|
@ -437,7 +452,7 @@ function BasicSettingsTab({
|
|||
)}
|
||||
|
||||
{/* 레이아웃 설정 */}
|
||||
<CollapsibleSection title="레이아웃 설정" defaultOpen>
|
||||
<CollapsibleSection sectionKey="basic-layout" title="레이아웃 설정" sections={sections}>
|
||||
<div className="space-y-3">
|
||||
{modeLabel && (
|
||||
<div className="flex items-center gap-1.5 rounded-md bg-muted/50 px-2.5 py-1.5">
|
||||
|
|
@ -526,9 +541,11 @@ function BasicSettingsTab({
|
|||
function CardTemplateTab({
|
||||
config,
|
||||
onUpdate,
|
||||
sections,
|
||||
}: {
|
||||
config: PopCardListConfig;
|
||||
onUpdate: (partial: Partial<PopCardListConfig>) => void;
|
||||
sections: SectionsApi;
|
||||
}) {
|
||||
const dataSource = config.dataSource || DEFAULT_DATA_SOURCE;
|
||||
const template = config.cardTemplate || DEFAULT_TEMPLATE;
|
||||
|
|
@ -634,7 +651,7 @@ function CardTemplateTab({
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 헤더 설정 */}
|
||||
<CollapsibleSection title="헤더 설정" defaultOpen>
|
||||
<CollapsibleSection sectionKey="tpl-header" title="헤더 설정" sections={sections}>
|
||||
<HeaderSettingsSection
|
||||
header={template.header || DEFAULT_HEADER}
|
||||
columnGroups={columnGroups}
|
||||
|
|
@ -643,7 +660,7 @@ function CardTemplateTab({
|
|||
</CollapsibleSection>
|
||||
|
||||
{/* 이미지 설정 */}
|
||||
<CollapsibleSection title="이미지 설정" defaultOpen>
|
||||
<CollapsibleSection sectionKey="tpl-image" title="이미지 설정" sections={sections}>
|
||||
<ImageSettingsSection
|
||||
image={template.image || DEFAULT_IMAGE}
|
||||
columnGroups={columnGroups}
|
||||
|
|
@ -653,9 +670,10 @@ function CardTemplateTab({
|
|||
|
||||
{/* 본문 필드 */}
|
||||
<CollapsibleSection
|
||||
sectionKey="tpl-body"
|
||||
title="본문 필드"
|
||||
sections={sections}
|
||||
badge={`${template.body?.fields?.length || 0}개`}
|
||||
defaultOpen
|
||||
>
|
||||
<BodyFieldsSection
|
||||
body={template.body || DEFAULT_BODY}
|
||||
|
|
@ -665,7 +683,7 @@ function CardTemplateTab({
|
|||
</CollapsibleSection>
|
||||
|
||||
{/* 입력 필드 설정 */}
|
||||
<CollapsibleSection title="입력 필드" defaultOpen={false}>
|
||||
<CollapsibleSection sectionKey="tpl-input" title="입력 필드" sections={sections}>
|
||||
<InputFieldSettingsSection
|
||||
inputField={config.inputField}
|
||||
columns={columns}
|
||||
|
|
@ -675,7 +693,7 @@ function CardTemplateTab({
|
|||
</CollapsibleSection>
|
||||
|
||||
{/* 포장등록 설정 */}
|
||||
<CollapsibleSection title="포장등록 (계산기)" defaultOpen={false}>
|
||||
<CollapsibleSection sectionKey="tpl-package" title="포장등록 (계산기)" sections={sections}>
|
||||
<PackageSettingsSection
|
||||
packageConfig={config.packageConfig}
|
||||
onUpdate={(packageConfig) => onUpdate({ packageConfig })}
|
||||
|
|
@ -683,7 +701,7 @@ function CardTemplateTab({
|
|||
</CollapsibleSection>
|
||||
|
||||
{/* 담기 버튼 설정 */}
|
||||
<CollapsibleSection title="담기 버튼" defaultOpen={false}>
|
||||
<CollapsibleSection sectionKey="tpl-cart" title="담기 버튼" sections={sections}>
|
||||
<CartActionSettingsSection
|
||||
cartAction={config.cartAction}
|
||||
onUpdate={(cartAction) => onUpdate({ cartAction })}
|
||||
|
|
@ -693,7 +711,7 @@ function CardTemplateTab({
|
|||
</CollapsibleSection>
|
||||
|
||||
{/* 반응형 표시 설정 */}
|
||||
<CollapsibleSection title="반응형 표시" defaultOpen={false}>
|
||||
<CollapsibleSection sectionKey="tpl-responsive" title="반응형 표시" sections={sections}>
|
||||
<ResponsiveDisplaySection
|
||||
config={config}
|
||||
onUpdate={onUpdate}
|
||||
|
|
@ -769,24 +787,26 @@ function GroupedColumnSelect({
|
|||
// ===== 접기/펴기 섹션 컴포넌트 =====
|
||||
|
||||
function CollapsibleSection({
|
||||
sectionKey,
|
||||
title,
|
||||
badge,
|
||||
defaultOpen = false,
|
||||
sections,
|
||||
children,
|
||||
}: {
|
||||
sectionKey: string;
|
||||
title: string;
|
||||
badge?: string;
|
||||
defaultOpen?: boolean;
|
||||
sections: SectionsApi;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
const open = sections.isOpen(sectionKey);
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between px-3 py-2 text-left transition-colors hover:bg-muted/50"
|
||||
onClick={() => setOpen(!open)}
|
||||
onClick={() => sections.toggle(sectionKey)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{open ? (
|
||||
|
|
|
|||
|
|
@ -1937,7 +1937,7 @@ function PageEditor({
|
|||
isPreviewing?: boolean;
|
||||
onUpdateItem?: (updatedItem: DashboardItem) => void;
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="rounded-md border p-2">
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { useCollapsibleSections } from "@/hooks/pop/useCollapsibleSections";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
|
|
@ -648,10 +649,7 @@ function SaveTabContent({
|
|||
|
||||
const noFields = allFields.length === 0;
|
||||
|
||||
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
|
||||
const toggleSection = useCallback((key: string) => {
|
||||
setCollapsed((prev) => ({ ...prev, [key]: !prev[key] }));
|
||||
}, []);
|
||||
const sections = useCollapsibleSections("pop-field");
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -666,16 +664,16 @@ function SaveTabContent({
|
|||
<div className="rounded-md border bg-card">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
|
||||
onClick={() => toggleSection("table")}
|
||||
onClick={() => sections.toggle("table")}
|
||||
>
|
||||
{collapsed["table"] ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
{sections.isOpen("table") ? (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs font-medium">테이블 설정</span>
|
||||
</div>
|
||||
{!collapsed["table"] && <div className="space-y-3 border-t p-3">
|
||||
{sections.isOpen("table") && <div className="space-y-3 border-t p-3">
|
||||
{/* 읽기 테이블 (display 섹션이 있을 때만) */}
|
||||
{hasDisplayFields && (
|
||||
<>
|
||||
|
|
@ -839,19 +837,19 @@ function SaveTabContent({
|
|||
<div className="rounded-md border bg-card">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
|
||||
onClick={() => toggleSection("read")}
|
||||
onClick={() => sections.toggle("read")}
|
||||
>
|
||||
{collapsed["read"] ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
{sections.isOpen("read") ? (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs font-medium">읽기 필드</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
(읽기 폼)
|
||||
</span>
|
||||
</div>
|
||||
{!collapsed["read"] && <div className="space-y-2 border-t p-3">
|
||||
{sections.isOpen("read") && <div className="space-y-2 border-t p-3">
|
||||
{readColumns.length === 0 ? (
|
||||
<p className="py-2 text-xs text-muted-foreground">
|
||||
읽기 테이블의 컬럼을 불러오는 중...
|
||||
|
|
@ -988,19 +986,19 @@ function SaveTabContent({
|
|||
<div className="rounded-md border bg-card">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
|
||||
onClick={() => toggleSection("input")}
|
||||
onClick={() => sections.toggle("input")}
|
||||
>
|
||||
{collapsed["input"] ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
{sections.isOpen("input") ? (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs font-medium">입력 필드</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
(입력 폼 → 저장)
|
||||
</span>
|
||||
</div>
|
||||
{!collapsed["input"] && <div className="space-y-2 border-t p-3">
|
||||
{sections.isOpen("input") && <div className="space-y-2 border-t p-3">
|
||||
{saveColumns.length === 0 ? (
|
||||
<p className="py-2 text-xs text-muted-foreground">
|
||||
저장 테이블의 컬럼을 불러오는 중...
|
||||
|
|
@ -1050,19 +1048,19 @@ function SaveTabContent({
|
|||
<div className="rounded-md border bg-card">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
|
||||
onClick={() => toggleSection("hidden")}
|
||||
onClick={() => sections.toggle("hidden")}
|
||||
>
|
||||
{collapsed["hidden"] ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
{sections.isOpen("hidden") ? (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs font-medium">숨은 필드</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
(UI 미표시, 전달 데이터에서 추출하여 저장)
|
||||
</span>
|
||||
</div>
|
||||
{!collapsed["hidden"] && <div className="space-y-3 border-t p-3">
|
||||
{sections.isOpen("hidden") && <div className="space-y-3 border-t p-3">
|
||||
{hiddenMappings.map((m) => {
|
||||
const isJson = m.valueSource === "json_extract";
|
||||
const isStatic = m.valueSource === "static";
|
||||
|
|
@ -1215,19 +1213,19 @@ function SaveTabContent({
|
|||
<div className="rounded-md border bg-card">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
|
||||
onClick={() => toggleSection("autogen")}
|
||||
onClick={() => sections.toggle("autogen")}
|
||||
>
|
||||
{collapsed["autogen"] ? (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
{sections.isOpen("autogen") ? (
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-xs font-medium">자동생성 필드</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
(저장 시 서버에서 채번)
|
||||
</span>
|
||||
</div>
|
||||
{!collapsed["autogen"] && <div className="space-y-3 border-t p-3">
|
||||
{sections.isOpen("autogen") && <div className="space-y-3 border-t p-3">
|
||||
{autoGenMappings.map((m) => {
|
||||
const isLinked = !!m.linkedFieldId;
|
||||
return (
|
||||
|
|
@ -1396,7 +1394,7 @@ function SectionEditor({
|
|||
onMoveUp,
|
||||
allSections,
|
||||
}: SectionEditorProps) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const resolvedStyle = migrateStyle(section.style);
|
||||
|
||||
const sectionFields = section.fields || [];
|
||||
|
|
|
|||
Loading…
Reference in New Issue