fix(pop-card-list): 장바구니 모드 설정 UI 개선 + 버그 5건 수정

- screenApi 필드명 수정 (screenId/screenName)
- 레이아웃 components Record -> Object.values() 변환
- config 키 수정 (props -> config)
- SelectItem 빈 값 방어 (cartType || __comp_id)
- cartType 수동입력 -> 원본 컴포넌트 Select 자동 로드
- 상태 필터 UI 제거 (in_cart 고정)
- 설정 미완료 가드 완화 (sourceScreenId만 필수)
This commit is contained in:
SeongHyun Kim 2026-02-27 15:13:49 +09:00
parent aa319a6bda
commit c1cf31f57b
2 changed files with 117 additions and 53 deletions

View File

@ -498,8 +498,8 @@ export function PopCardListComponent({
if (isCartListMode) {
const cartListMode = config!.cartListMode!;
// 설정 미완료 시 데이터 조회하지 않음
if (!cartListMode.sourceScreenId || !cartListMode.cartType) {
// 원본 화면 미선택 시 데이터 조회하지 않음
if (!cartListMode.sourceScreenId) {
setLoading(false);
setRows([]);
return;
@ -513,27 +513,33 @@ export function PopCardListComponent({
if (cartListMode.sourceScreenId) {
try {
const layoutJson = await screenApi.getLayoutPop(cartListMode.sourceScreenId);
const components = layoutJson?.components || [];
const matched = components.find(
(c: any) =>
c.type === "pop-card-list" &&
c.props?.cartAction?.cartType === cartListMode.cartType
);
if (matched?.props?.cardTemplate) {
setInheritedTemplate(matched.props.cardTemplate);
const componentsMap = layoutJson?.components || {};
const componentList = Object.values(componentsMap) as any[];
const matched = cartListMode.cartType
? componentList.find(
(c: any) =>
c.type === "pop-card-list" &&
c.config?.cartAction?.cartType === cartListMode.cartType
)
: componentList.find((c: any) => c.type === "pop-card-list");
if (matched?.config?.cardTemplate) {
setInheritedTemplate(matched.config.cardTemplate);
}
} catch {
// 레이아웃 로드 실패 시 config.cardTemplate 폴백
}
}
// cart_items 조회
// cart_items 조회 (cartType이 있으면 필터, 없으면 전체)
const cartFilters: Record<string, unknown> = {
status: cartListMode.statusFilter || "in_cart",
};
if (cartListMode.cartType) {
cartFilters.cart_type = cartListMode.cartType;
}
const result = await dataApi.getTableData("cart_items", {
size: 500,
filters: {
cart_type: cartListMode.cartType,
status: cartListMode.statusFilter || "in_cart",
},
filters: cartFilters,
});
const parsed = (result.data || []).map(parseCartRow);
@ -680,10 +686,10 @@ export function PopCardListComponent({
ref={containerRef}
className={`flex h-full w-full flex-col ${className || ""}`}
>
{isCartListMode && (!config?.cartListMode?.sourceScreenId || !config?.cartListMode?.cartType) ? (
{isCartListMode && !config?.cartListMode?.sourceScreenId ? (
<div className="flex flex-1 items-center justify-center rounded-md border border-dashed bg-muted/30 p-4">
<p className="text-sm text-muted-foreground">
.
.
</p>
</div>
) : !isCartListMode && !dataSource?.tableName ? (

View File

@ -864,6 +864,12 @@ function CollapsibleSection({
// ===== 장바구니 목록 모드 설정 =====
interface SourceCardListInfo {
componentId: string;
label: string;
cartType: string;
}
function CartListModeSection({
cartListMode,
onUpdate,
@ -873,7 +879,10 @@ function CartListModeSection({
}) {
const mode: CartListModeConfig = cartListMode || { enabled: false };
const [screens, setScreens] = useState<{ id: number; name: string }[]>([]);
const [sourceCardLists, setSourceCardLists] = useState<SourceCardListInfo[]>([]);
const [loadingComponents, setLoadingComponents] = useState(false);
// 화면 목록 로드
useEffect(() => {
screenApi
.getScreens({ size: 500 })
@ -890,6 +899,52 @@ function CartListModeSection({
.catch(() => {});
}, []);
// 원본 화면 선택 시 -> 해당 화면의 pop-card-list 컴포넌트 목록 로드
useEffect(() => {
if (!mode.sourceScreenId) {
setSourceCardLists([]);
return;
}
setLoadingComponents(true);
screenApi
.getLayoutPop(mode.sourceScreenId)
.then((layoutJson: any) => {
const componentsMap = layoutJson?.components || {};
const componentList = Object.values(componentsMap) as any[];
const cardLists: SourceCardListInfo[] = componentList
.filter((c: any) => c.type === "pop-card-list")
.map((c: any) => ({
componentId: c.id || "",
label: c.label || c.config?.cartAction?.cartType || "카드 목록",
cartType: c.config?.cartAction?.cartType || "",
}));
setSourceCardLists(cardLists);
})
.catch(() => {
setSourceCardLists([]);
})
.finally(() => setLoadingComponents(false));
}, [mode.sourceScreenId]);
const handleScreenChange = (val: string) => {
const screenId = val === "__none__" ? undefined : Number(val);
onUpdate({ ...mode, sourceScreenId: screenId, cartType: undefined });
};
const handleComponentSelect = (val: string) => {
if (val === "__none__") {
onUpdate({ ...mode, cartType: undefined });
return;
}
// cartType 직접 매칭 또는 componentId 매칭 (__comp_ 접두사)
const found = val.startsWith("__comp_")
? sourceCardLists.find((c) => c.componentId === val.replace("__comp_", ""))
: sourceCardLists.find((c) => c.cartType === val);
if (found) {
onUpdate({ ...mode, cartType: found.cartType || undefined });
}
};
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
@ -912,9 +967,7 @@ function CartListModeSection({
<Label className="text-[10px] text-muted-foreground"> </Label>
<Select
value={mode.sourceScreenId ? String(mode.sourceScreenId) : "__none__"}
onValueChange={(val) =>
onUpdate({ ...mode, sourceScreenId: val === "__none__" ? undefined : Number(val) })
}
onValueChange={handleScreenChange}
>
<SelectTrigger className="mt-1 h-7 text-xs">
<SelectValue placeholder="화면 선택" />
@ -928,41 +981,46 @@ function CartListModeSection({
))}
</SelectContent>
</Select>
<p className="mt-1 text-[9px] text-muted-foreground">
.
</p>
</div>
{/* 장바구니 구분값 */}
<div>
<Label className="text-[10px] text-muted-foreground"> </Label>
<Input
value={mode.cartType || ""}
onChange={(e) => onUpdate({ ...mode, cartType: e.target.value })}
placeholder="예: purchase_inbound"
className="mt-1 h-7 text-xs"
/>
<p className="mt-1 text-[9px] text-muted-foreground">
.
</p>
</div>
{/* 상태 필터 */}
<div>
<Label className="text-[10px] text-muted-foreground"> </Label>
<Select
value={mode.statusFilter || "in_cart"}
onValueChange={(val) => onUpdate({ ...mode, statusFilter: val })}
>
<SelectTrigger className="mt-1 h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="in_cart"> (in_cart)</SelectItem>
<SelectItem value="confirmed"> (confirmed)</SelectItem>
</SelectContent>
</Select>
</div>
{/* 원본 컴포넌트 선택 (원본 화면에서 자동 로드) */}
{mode.sourceScreenId && (
<div>
<Label className="text-[10px] text-muted-foreground"> </Label>
{loadingComponents ? (
<div className="mt-1 flex h-7 items-center px-2 text-xs text-muted-foreground">
...
</div>
) : sourceCardLists.length === 0 ? (
<div className="mt-1 rounded-md border border-dashed bg-muted/30 px-2 py-1.5 text-[10px] text-muted-foreground">
.
</div>
) : (
<Select
value={mode.cartType || "__none__"}
onValueChange={handleComponentSelect}
>
<SelectTrigger className="mt-1 h-7 text-xs">
<SelectValue placeholder="카드 목록 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__"> </SelectItem>
{sourceCardLists.map((c) => {
const selectValue = c.cartType || `__comp_${c.componentId}`;
return (
<SelectItem key={c.componentId || selectValue} value={selectValue}>
{c.label}{c.cartType ? ` (${c.cartType})` : ""}
</SelectItem>
);
})}
</SelectContent>
</Select>
)}
<p className="mt-1 text-[9px] text-muted-foreground">
.
</p>
</div>
)}
</>
)}
</div>