Compare commits

..

No commits in common. "12a8290873bca77773e1d5d1b7040778a0f5c956" and "85bf4882a8ea7c4da5eb40009a1a83a1123e4cd7" have entirely different histories.

6 changed files with 54 additions and 164 deletions

View File

@ -26,8 +26,5 @@ export { useConnectionResolver } from "./useConnectionResolver";
export { useCartSync } from "./useCartSync"; export { useCartSync } from "./useCartSync";
export type { UseCartSyncReturn } from "./useCartSync"; export type { UseCartSyncReturn } from "./useCartSync";
// 설정 패널 접기/펼치기 상태 관리
export { useCollapsibleSections } from "./useCollapsibleSections";
// SQL 빌더 유틸 (고급 사용 시) // SQL 빌더 유틸 (고급 사용 시)
export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder"; export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder";

View File

@ -1,58 +0,0 @@
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 };
}

View File

@ -730,13 +730,14 @@ export function PopCardListComponent({
gap: `${scaled.gap}px`, gap: `${scaled.gap}px`,
...(isHorizontalMode ...(isHorizontalMode
? { ? {
gridTemplateRows: `repeat(${gridRows}, minmax(${scaled.cardHeight}px, auto))`, gridTemplateRows: `repeat(${gridRows}, ${scaled.cardHeight}px)`,
gridAutoFlow: "column", gridAutoFlow: "column",
gridAutoColumns: `${scaled.cardWidth}px`, gridAutoColumns: `${scaled.cardWidth}px`,
} }
: { : {
// 세로 모드: 1fr 비율 기반으로 컨테이너 너비 초과 방지
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`, gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
gridAutoRows: `minmax(${scaled.cardHeight}px, auto)`, gridAutoRows: `${scaled.cardHeight}px`,
}), }),
}; };
@ -1007,10 +1008,9 @@ function Card({
} }
}, [effectiveMax, inputField?.enabled, limitCol, isCartListMode]); }, [effectiveMax, inputField?.enabled, limitCol, isCartListMode]);
const hasPackageEntries = packageEntries.length > 0;
const cardStyle: React.CSSProperties = { const cardStyle: React.CSSProperties = {
minHeight: `${scaled.cardHeight}px`, height: `${scaled.cardHeight}px`,
overflow: "hidden",
}; };
const headerStyle: React.CSSProperties = { const headerStyle: React.CSSProperties = {
@ -1116,7 +1116,7 @@ function Card({
return ( return (
<div <div
className={`relative flex cursor-pointer flex-col rounded-lg border bg-card shadow-sm transition-all duration-150 hover:shadow-md ${borderClass}`} className={`relative cursor-pointer rounded-lg border bg-card shadow-sm transition-all duration-150 hover:shadow-md ${borderClass}`}
style={cardStyle} style={cardStyle}
onClick={handleCardClick} onClick={handleCardClick}
role="button" role="button"
@ -1157,7 +1157,7 @@ function Card({
)} )}
{/* 본문 영역 */} {/* 본문 영역 */}
<div className="flex flex-1 overflow-hidden" style={bodyStyle}> <div className="flex" style={bodyStyle}>
{/* 이미지 (왼쪽) */} {/* 이미지 (왼쪽) */}
{image?.enabled && ( {image?.enabled && (
<div className="shrink-0"> <div className="shrink-0">
@ -1199,7 +1199,7 @@ function Card({
{/* 오른쪽: 수량 버튼 + 담기/취소/삭제 버튼 */} {/* 오른쪽: 수량 버튼 + 담기/취소/삭제 버튼 */}
{(inputField?.enabled || cartAction || isCartListMode) && ( {(inputField?.enabled || cartAction || isCartListMode) && (
<div <div
className="ml-2 flex shrink-0 flex-col items-stretch justify-start gap-2" className="ml-2 flex shrink-0 flex-col items-stretch justify-center gap-2"
style={{ minWidth: "100px" }} style={{ minWidth: "100px" }}
> >
{/* 수량 버튼 (입력 필드 ON일 때만) */} {/* 수량 버튼 (입력 필드 ON일 때만) */}
@ -1268,37 +1268,6 @@ function Card({
)} )}
</div> </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 && ( {inputField?.enabled && (
<NumberInputModal <NumberInputModal
open={isModalOpen} open={isModalOpen}

View File

@ -10,7 +10,6 @@
import React, { useState, useEffect, useMemo } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { ChevronDown, ChevronRight, Plus, Trash2, Database, Check } from "lucide-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 type { GridMode } from "@/components/pop/designer/types/pop-layout";
import { GRID_BREAKPOINTS } from "@/components/pop/designer/types/pop-layout"; import { GRID_BREAKPOINTS } from "@/components/pop/designer/types/pop-layout";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -136,7 +135,6 @@ const COLOR_OPTIONS = [
export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentColSpan }: ConfigPanelProps) { export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentColSpan }: ConfigPanelProps) {
const [activeTab, setActiveTab] = useState<"basic" | "template">("basic"); const [activeTab, setActiveTab] = useState<"basic" | "template">("basic");
const sections = useCollapsibleSections("pop-card-list");
const cfg: PopCardListConfig = config || DEFAULT_CONFIG; const cfg: PopCardListConfig = config || DEFAULT_CONFIG;
@ -186,7 +184,6 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
onUpdate={updateConfig} onUpdate={updateConfig}
currentMode={currentMode} currentMode={currentMode}
currentColSpan={currentColSpan} currentColSpan={currentColSpan}
sections={sections}
/> />
)} )}
{activeTab === "template" && ( {activeTab === "template" && (
@ -198,7 +195,7 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
</div> </div>
</div> </div>
) : ( ) : (
<CardTemplateTab config={cfg} onUpdate={updateConfig} sections={sections} /> <CardTemplateTab config={cfg} onUpdate={updateConfig} />
) )
)} )}
</div> </div>
@ -208,20 +205,16 @@ export function PopCardListConfigPanel({ config, onUpdate, currentMode, currentC
// ===== 기본 설정 탭 (테이블 + 레이아웃 통합) ===== // ===== 기본 설정 탭 (테이블 + 레이아웃 통합) =====
type SectionsApi = { isOpen: (key: string) => boolean; toggle: (key: string) => void };
function BasicSettingsTab({ function BasicSettingsTab({
config, config,
onUpdate, onUpdate,
currentMode, currentMode,
currentColSpan, currentColSpan,
sections,
}: { }: {
config: PopCardListConfig; config: PopCardListConfig;
onUpdate: (partial: Partial<PopCardListConfig>) => void; onUpdate: (partial: Partial<PopCardListConfig>) => void;
currentMode?: GridMode; currentMode?: GridMode;
currentColSpan?: number; currentColSpan?: number;
sections: SectionsApi;
}) { }) {
const dataSource = config.dataSource || DEFAULT_DATA_SOURCE; const dataSource = config.dataSource || DEFAULT_DATA_SOURCE;
const [tables, setTables] = useState<TableInfo[]>([]); const [tables, setTables] = useState<TableInfo[]>([]);
@ -328,7 +321,7 @@ function BasicSettingsTab({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* 장바구니 목록 모드 */} {/* 장바구니 목록 모드 */}
<CollapsibleSection sectionKey="basic-cart-mode" title="장바구니 목록 모드" sections={sections}> <CollapsibleSection title="장바구니 목록 모드" defaultOpen={isCartListMode}>
<CartListModeSection <CartListModeSection
cartListMode={config.cartListMode} cartListMode={config.cartListMode}
onUpdate={(cartListMode) => onUpdate({ cartListMode })} onUpdate={(cartListMode) => onUpdate({ cartListMode })}
@ -337,7 +330,7 @@ function BasicSettingsTab({
{/* 테이블 선택 (장바구니 모드 시 숨김) */} {/* 테이블 선택 (장바구니 모드 시 숨김) */}
{!isCartListMode && ( {!isCartListMode && (
<CollapsibleSection sectionKey="basic-table" title="테이블 선택" sections={sections}> <CollapsibleSection title="테이블 선택" defaultOpen>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
@ -372,9 +365,7 @@ function BasicSettingsTab({
{/* 조인 설정 (장바구니 모드 시 숨김) */} {/* 조인 설정 (장바구니 모드 시 숨김) */}
{!isCartListMode && dataSource.tableName && ( {!isCartListMode && dataSource.tableName && (
<CollapsibleSection <CollapsibleSection
sectionKey="basic-join"
title="조인 설정" title="조인 설정"
sections={sections}
badge={ badge={
dataSource.joins && dataSource.joins.length > 0 dataSource.joins && dataSource.joins.length > 0
? `${dataSource.joins.length}` ? `${dataSource.joins.length}`
@ -392,9 +383,7 @@ function BasicSettingsTab({
{/* 정렬 기준 (장바구니 모드 시 숨김) */} {/* 정렬 기준 (장바구니 모드 시 숨김) */}
{!isCartListMode && dataSource.tableName && ( {!isCartListMode && dataSource.tableName && (
<CollapsibleSection <CollapsibleSection
sectionKey="basic-sort"
title="정렬 기준" title="정렬 기준"
sections={sections}
badge={ badge={
dataSource.sort dataSource.sort
? Array.isArray(dataSource.sort) ? Array.isArray(dataSource.sort)
@ -414,9 +403,7 @@ function BasicSettingsTab({
{/* 필터 기준 (장바구니 모드 시 숨김) */} {/* 필터 기준 (장바구니 모드 시 숨김) */}
{!isCartListMode && dataSource.tableName && ( {!isCartListMode && dataSource.tableName && (
<CollapsibleSection <CollapsibleSection
sectionKey="basic-filter"
title="필터 기준" title="필터 기준"
sections={sections}
badge={ badge={
dataSource.filters && dataSource.filters.length > 0 dataSource.filters && dataSource.filters.length > 0
? `${dataSource.filters.length}` ? `${dataSource.filters.length}`
@ -434,9 +421,7 @@ function BasicSettingsTab({
{/* 저장 매핑 (장바구니 모드일 때만) */} {/* 저장 매핑 (장바구니 모드일 때만) */}
{isCartListMode && ( {isCartListMode && (
<CollapsibleSection <CollapsibleSection
sectionKey="basic-save-mapping"
title="저장 매핑" title="저장 매핑"
sections={sections}
badge={ badge={
config.saveMapping?.mappings && config.saveMapping.mappings.length > 0 config.saveMapping?.mappings && config.saveMapping.mappings.length > 0
? `${config.saveMapping.mappings.length}` ? `${config.saveMapping.mappings.length}`
@ -452,7 +437,7 @@ function BasicSettingsTab({
)} )}
{/* 레이아웃 설정 */} {/* 레이아웃 설정 */}
<CollapsibleSection sectionKey="basic-layout" title="레이아웃 설정" sections={sections}> <CollapsibleSection title="레이아웃 설정" defaultOpen>
<div className="space-y-3"> <div className="space-y-3">
{modeLabel && ( {modeLabel && (
<div className="flex items-center gap-1.5 rounded-md bg-muted/50 px-2.5 py-1.5"> <div className="flex items-center gap-1.5 rounded-md bg-muted/50 px-2.5 py-1.5">
@ -541,11 +526,9 @@ function BasicSettingsTab({
function CardTemplateTab({ function CardTemplateTab({
config, config,
onUpdate, onUpdate,
sections,
}: { }: {
config: PopCardListConfig; config: PopCardListConfig;
onUpdate: (partial: Partial<PopCardListConfig>) => void; onUpdate: (partial: Partial<PopCardListConfig>) => void;
sections: SectionsApi;
}) { }) {
const dataSource = config.dataSource || DEFAULT_DATA_SOURCE; const dataSource = config.dataSource || DEFAULT_DATA_SOURCE;
const template = config.cardTemplate || DEFAULT_TEMPLATE; const template = config.cardTemplate || DEFAULT_TEMPLATE;
@ -651,7 +634,7 @@ function CardTemplateTab({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* 헤더 설정 */} {/* 헤더 설정 */}
<CollapsibleSection sectionKey="tpl-header" title="헤더 설정" sections={sections}> <CollapsibleSection title="헤더 설정" defaultOpen>
<HeaderSettingsSection <HeaderSettingsSection
header={template.header || DEFAULT_HEADER} header={template.header || DEFAULT_HEADER}
columnGroups={columnGroups} columnGroups={columnGroups}
@ -660,7 +643,7 @@ function CardTemplateTab({
</CollapsibleSection> </CollapsibleSection>
{/* 이미지 설정 */} {/* 이미지 설정 */}
<CollapsibleSection sectionKey="tpl-image" title="이미지 설정" sections={sections}> <CollapsibleSection title="이미지 설정" defaultOpen>
<ImageSettingsSection <ImageSettingsSection
image={template.image || DEFAULT_IMAGE} image={template.image || DEFAULT_IMAGE}
columnGroups={columnGroups} columnGroups={columnGroups}
@ -670,10 +653,9 @@ function CardTemplateTab({
{/* 본문 필드 */} {/* 본문 필드 */}
<CollapsibleSection <CollapsibleSection
sectionKey="tpl-body"
title="본문 필드" title="본문 필드"
sections={sections}
badge={`${template.body?.fields?.length || 0}`} badge={`${template.body?.fields?.length || 0}`}
defaultOpen
> >
<BodyFieldsSection <BodyFieldsSection
body={template.body || DEFAULT_BODY} body={template.body || DEFAULT_BODY}
@ -683,7 +665,7 @@ function CardTemplateTab({
</CollapsibleSection> </CollapsibleSection>
{/* 입력 필드 설정 */} {/* 입력 필드 설정 */}
<CollapsibleSection sectionKey="tpl-input" title="입력 필드" sections={sections}> <CollapsibleSection title="입력 필드" defaultOpen={false}>
<InputFieldSettingsSection <InputFieldSettingsSection
inputField={config.inputField} inputField={config.inputField}
columns={columns} columns={columns}
@ -693,7 +675,7 @@ function CardTemplateTab({
</CollapsibleSection> </CollapsibleSection>
{/* 포장등록 설정 */} {/* 포장등록 설정 */}
<CollapsibleSection sectionKey="tpl-package" title="포장등록 (계산기)" sections={sections}> <CollapsibleSection title="포장등록 (계산기)" defaultOpen={false}>
<PackageSettingsSection <PackageSettingsSection
packageConfig={config.packageConfig} packageConfig={config.packageConfig}
onUpdate={(packageConfig) => onUpdate({ packageConfig })} onUpdate={(packageConfig) => onUpdate({ packageConfig })}
@ -701,7 +683,7 @@ function CardTemplateTab({
</CollapsibleSection> </CollapsibleSection>
{/* 담기 버튼 설정 */} {/* 담기 버튼 설정 */}
<CollapsibleSection sectionKey="tpl-cart" title="담기 버튼" sections={sections}> <CollapsibleSection title="담기 버튼" defaultOpen={false}>
<CartActionSettingsSection <CartActionSettingsSection
cartAction={config.cartAction} cartAction={config.cartAction}
onUpdate={(cartAction) => onUpdate({ cartAction })} onUpdate={(cartAction) => onUpdate({ cartAction })}
@ -711,7 +693,7 @@ function CardTemplateTab({
</CollapsibleSection> </CollapsibleSection>
{/* 반응형 표시 설정 */} {/* 반응형 표시 설정 */}
<CollapsibleSection sectionKey="tpl-responsive" title="반응형 표시" sections={sections}> <CollapsibleSection title="반응형 표시" defaultOpen={false}>
<ResponsiveDisplaySection <ResponsiveDisplaySection
config={config} config={config}
onUpdate={onUpdate} onUpdate={onUpdate}
@ -787,26 +769,24 @@ function GroupedColumnSelect({
// ===== 접기/펴기 섹션 컴포넌트 ===== // ===== 접기/펴기 섹션 컴포넌트 =====
function CollapsibleSection({ function CollapsibleSection({
sectionKey,
title, title,
badge, badge,
sections, defaultOpen = false,
children, children,
}: { }: {
sectionKey: string;
title: string; title: string;
badge?: string; badge?: string;
sections: SectionsApi; defaultOpen?: boolean;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const open = sections.isOpen(sectionKey); const [open, setOpen] = useState(defaultOpen);
return ( return (
<div className="rounded-md border"> <div className="rounded-md border">
<button <button
type="button" type="button"
className="flex w-full items-center justify-between px-3 py-2 text-left transition-colors hover:bg-muted/50" className="flex w-full items-center justify-between px-3 py-2 text-left transition-colors hover:bg-muted/50"
onClick={() => sections.toggle(sectionKey)} onClick={() => setOpen(!open)}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{open ? ( {open ? (

View File

@ -1937,7 +1937,7 @@ function PageEditor({
isPreviewing?: boolean; isPreviewing?: boolean;
onUpdateItem?: (updatedItem: DashboardItem) => void; onUpdateItem?: (updatedItem: DashboardItem) => void;
}) { }) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(true);
return ( return (
<div className="rounded-md border p-2"> <div className="rounded-md border p-2">

View File

@ -9,7 +9,6 @@
*/ */
import { useState, useEffect, useCallback, useMemo } from "react"; import { useState, useEffect, useCallback, useMemo } from "react";
import { useCollapsibleSections } from "@/hooks/pop/useCollapsibleSections";
import { import {
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
@ -649,7 +648,10 @@ function SaveTabContent({
const noFields = allFields.length === 0; const noFields = allFields.length === 0;
const sections = useCollapsibleSections("pop-field"); const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
const toggleSection = useCallback((key: string) => {
setCollapsed((prev) => ({ ...prev, [key]: !prev[key] }));
}, []);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@ -664,16 +666,16 @@ function SaveTabContent({
<div className="rounded-md border bg-card"> <div className="rounded-md border bg-card">
<div <div
className="flex cursor-pointer items-center gap-1.5 px-3 py-2" className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
onClick={() => sections.toggle("table")} onClick={() => toggleSection("table")}
> >
{sections.isOpen("table") ? ( {collapsed["table"] ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" /> <ChevronRight className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
)} )}
<span className="text-xs font-medium"> </span> <span className="text-xs font-medium"> </span>
</div> </div>
{sections.isOpen("table") && <div className="space-y-3 border-t p-3"> {!collapsed["table"] && <div className="space-y-3 border-t p-3">
{/* 읽기 테이블 (display 섹션이 있을 때만) */} {/* 읽기 테이블 (display 섹션이 있을 때만) */}
{hasDisplayFields && ( {hasDisplayFields && (
<> <>
@ -837,19 +839,19 @@ function SaveTabContent({
<div className="rounded-md border bg-card"> <div className="rounded-md border bg-card">
<div <div
className="flex cursor-pointer items-center gap-1.5 px-3 py-2" className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
onClick={() => sections.toggle("read")} onClick={() => toggleSection("read")}
> >
{sections.isOpen("read") ? ( {collapsed["read"] ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" /> <ChevronRight className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
)} )}
<span className="text-xs font-medium"> </span> <span className="text-xs font-medium"> </span>
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
( ) ( )
</span> </span>
</div> </div>
{sections.isOpen("read") && <div className="space-y-2 border-t p-3"> {!collapsed["read"] && <div className="space-y-2 border-t p-3">
{readColumns.length === 0 ? ( {readColumns.length === 0 ? (
<p className="py-2 text-xs text-muted-foreground"> <p className="py-2 text-xs text-muted-foreground">
... ...
@ -986,19 +988,19 @@ function SaveTabContent({
<div className="rounded-md border bg-card"> <div className="rounded-md border bg-card">
<div <div
className="flex cursor-pointer items-center gap-1.5 px-3 py-2" className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
onClick={() => sections.toggle("input")} onClick={() => toggleSection("input")}
> >
{sections.isOpen("input") ? ( {collapsed["input"] ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" /> <ChevronRight className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
)} )}
<span className="text-xs font-medium"> </span> <span className="text-xs font-medium"> </span>
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
( ) ( )
</span> </span>
</div> </div>
{sections.isOpen("input") && <div className="space-y-2 border-t p-3"> {!collapsed["input"] && <div className="space-y-2 border-t p-3">
{saveColumns.length === 0 ? ( {saveColumns.length === 0 ? (
<p className="py-2 text-xs text-muted-foreground"> <p className="py-2 text-xs text-muted-foreground">
... ...
@ -1048,19 +1050,19 @@ function SaveTabContent({
<div className="rounded-md border bg-card"> <div className="rounded-md border bg-card">
<div <div
className="flex cursor-pointer items-center gap-1.5 px-3 py-2" className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
onClick={() => sections.toggle("hidden")} onClick={() => toggleSection("hidden")}
> >
{sections.isOpen("hidden") ? ( {collapsed["hidden"] ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" /> <ChevronRight className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
)} )}
<span className="text-xs font-medium"> </span> <span className="text-xs font-medium"> </span>
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
(UI , ) (UI , )
</span> </span>
</div> </div>
{sections.isOpen("hidden") && <div className="space-y-3 border-t p-3"> {!collapsed["hidden"] && <div className="space-y-3 border-t p-3">
{hiddenMappings.map((m) => { {hiddenMappings.map((m) => {
const isJson = m.valueSource === "json_extract"; const isJson = m.valueSource === "json_extract";
const isStatic = m.valueSource === "static"; const isStatic = m.valueSource === "static";
@ -1213,19 +1215,19 @@ function SaveTabContent({
<div className="rounded-md border bg-card"> <div className="rounded-md border bg-card">
<div <div
className="flex cursor-pointer items-center gap-1.5 px-3 py-2" className="flex cursor-pointer items-center gap-1.5 px-3 py-2"
onClick={() => sections.toggle("autogen")} onClick={() => toggleSection("autogen")}
> >
{sections.isOpen("autogen") ? ( {collapsed["autogen"] ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" /> <ChevronRight className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
)} )}
<span className="text-xs font-medium"> </span> <span className="text-xs font-medium"> </span>
<span className="text-[10px] text-muted-foreground"> <span className="text-[10px] text-muted-foreground">
( ) ( )
</span> </span>
</div> </div>
{sections.isOpen("autogen") && <div className="space-y-3 border-t p-3"> {!collapsed["autogen"] && <div className="space-y-3 border-t p-3">
{autoGenMappings.map((m) => { {autoGenMappings.map((m) => {
const isLinked = !!m.linkedFieldId; const isLinked = !!m.linkedFieldId;
return ( return (
@ -1394,7 +1396,7 @@ function SectionEditor({
onMoveUp, onMoveUp,
allSections, allSections,
}: SectionEditorProps) { }: SectionEditorProps) {
const [collapsed, setCollapsed] = useState(true); const [collapsed, setCollapsed] = useState(false);
const resolvedStyle = migrateStyle(section.style); const resolvedStyle = migrateStyle(section.style);
const sectionFields = section.fields || []; const sectionFields = section.fields || [];