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) {
|
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 ? (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue