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

View File

@ -864,6 +864,12 @@ function CollapsibleSection({
// ===== 장바구니 목록 모드 설정 ===== // ===== 장바구니 목록 모드 설정 =====
interface SourceCardListInfo {
componentId: string;
label: string;
cartType: string;
}
function CartListModeSection({ function CartListModeSection({
cartListMode, cartListMode,
onUpdate, onUpdate,
@ -873,7 +879,10 @@ function CartListModeSection({
}) { }) {
const mode: CartListModeConfig = cartListMode || { enabled: false }; const mode: CartListModeConfig = cartListMode || { enabled: false };
const [screens, setScreens] = useState<{ id: number; name: string }[]>([]); const [screens, setScreens] = useState<{ id: number; name: string }[]>([]);
const [sourceCardLists, setSourceCardLists] = useState<SourceCardListInfo[]>([]);
const [loadingComponents, setLoadingComponents] = useState(false);
// 화면 목록 로드
useEffect(() => { useEffect(() => {
screenApi screenApi
.getScreens({ size: 500 }) .getScreens({ size: 500 })
@ -890,6 +899,52 @@ function CartListModeSection({
.catch(() => {}); .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 ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -912,9 +967,7 @@ function CartListModeSection({
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Select <Select
value={mode.sourceScreenId ? String(mode.sourceScreenId) : "__none__"} value={mode.sourceScreenId ? String(mode.sourceScreenId) : "__none__"}
onValueChange={(val) => onValueChange={handleScreenChange}
onUpdate({ ...mode, sourceScreenId: val === "__none__" ? undefined : Number(val) })
}
> >
<SelectTrigger className="mt-1 h-7 text-xs"> <SelectTrigger className="mt-1 h-7 text-xs">
<SelectValue placeholder="화면 선택" /> <SelectValue placeholder="화면 선택" />
@ -928,41 +981,46 @@ function CartListModeSection({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<p className="mt-1 text-[9px] text-muted-foreground">
.
</p>
</div> </div>
{/* 장바구니 구분값 */} {/* 원본 컴포넌트 선택 (원본 화면에서 자동 로드) */}
{mode.sourceScreenId && (
<div> <div>
<Label className="text-[10px] text-muted-foreground"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
<Input {loadingComponents ? (
value={mode.cartType || ""} <div className="mt-1 flex h-7 items-center px-2 text-xs text-muted-foreground">
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>
) : 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> .
<Label className="text-[10px] text-muted-foreground"> </Label> </div>
) : (
<Select <Select
value={mode.statusFilter || "in_cart"} value={mode.cartType || "__none__"}
onValueChange={(val) => onUpdate({ ...mode, statusFilter: val })} onValueChange={handleComponentSelect}
> >
<SelectTrigger className="mt-1 h-7 text-xs"> <SelectTrigger className="mt-1 h-7 text-xs">
<SelectValue /> <SelectValue placeholder="카드 목록 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="in_cart"> (in_cart)</SelectItem> <SelectItem value="__none__"> </SelectItem>
<SelectItem value="confirmed"> (confirmed)</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> </SelectContent>
</Select> </Select>
)}
<p className="mt-1 text-[9px] text-muted-foreground">
.
</p>
</div> </div>
)}
</> </>
)} )}
</div> </div>