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:
parent
aa319a6bda
commit
c1cf31f57b
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue