feat(channels): implement PayloadPreviewModal side-by-side JSON inspector and Preview Transformed Payload button
This commit is contained in:
@@ -54,6 +54,11 @@ export const channelsApi = {
|
||||
const res = await apiClient.get<ApiResponse<any[]>>(`${BASE_URL}/${channelId}/jobs`);
|
||||
return res.data || [];
|
||||
},
|
||||
|
||||
previewPayload: async (channelId: string): Promise<any> => {
|
||||
const res = await apiClient.post<ApiResponse<any>>(`${BASE_URL}/${channelId}/preview`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
export default channelsApi;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Save, Plus, Trash2, ArrowRight } from "lucide-react";
|
||||
import { Save, Plus, Trash2, ArrowRight, Eye } from "lucide-react";
|
||||
import { Button } from "../../../components/customs/Button";
|
||||
import { channelsApi } from "../api/channels.api";
|
||||
import { notify } from "../../../services/toast";
|
||||
import { PayloadPreviewModal } from "./PayloadPreviewModal";
|
||||
|
||||
const COMMON_PIM_ATTRIBUTES = [
|
||||
{ code: "name", label: "Product Title / Name (name)" },
|
||||
@@ -34,6 +35,8 @@ export function ChannelMappingTab({ channelId }: { channelId: string }) {
|
||||
const [mappings, setMappings] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [previewing, setPreviewing] = useState(false);
|
||||
const [previewData, setPreviewData] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadMappings();
|
||||
@@ -93,6 +96,18 @@ export function ChannelMappingTab({ channelId }: { channelId: string }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreview = async () => {
|
||||
setPreviewing(true);
|
||||
try {
|
||||
const data = await channelsApi.previewPayload(channelId);
|
||||
setPreviewData(data);
|
||||
} catch {
|
||||
notify.error("Failed to generate transformation preview");
|
||||
} finally {
|
||||
setPreviewing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-48">
|
||||
@@ -109,6 +124,9 @@ export function ChannelMappingTab({ channelId }: { channelId: string }) {
|
||||
<p className="text-xs text-muted-foreground mt-0.5">Map central PIM attributes to target storefront fields and apply transformation pipelines.</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" loading={previewing} onClick={handlePreview}>
|
||||
<Eye className="w-4 h-4 mr-2" /> Preview Transformed Payload
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleAddRule}>
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Rule
|
||||
</Button>
|
||||
@@ -118,6 +136,12 @@ export function ChannelMappingTab({ channelId }: { channelId: string }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PayloadPreviewModal
|
||||
isOpen={Boolean(previewData)}
|
||||
onClose={() => setPreviewData(null)}
|
||||
previewData={previewData}
|
||||
/>
|
||||
|
||||
<div className="border border-border rounded-lg overflow-hidden bg-surface shadow-sm">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="bg-surface-muted border-b border-border text-xs text-muted-foreground uppercase">
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Button } from "../../../components/customs/Button";
|
||||
import { Code, Check, Copy } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface PayloadPreviewModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
previewData: any;
|
||||
}
|
||||
|
||||
export function PayloadPreviewModal({ isOpen, onClose, previewData }: PayloadPreviewModalProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
if (!isOpen || !previewData) return null;
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(JSON.stringify(previewData.adapterOutput, null, 2));
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
|
||||
<div className="bg-surface border border-border rounded-xl shadow-2xl max-w-4xl w-full flex flex-col max-h-[85vh] overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="px-6 py-4 border-b border-border bg-gradient-to-r from-primary/10 to-surface flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
|
||||
<Code className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground text-sm">Transformed Payload Preview</h3>
|
||||
<p className="text-xs text-muted-foreground">Real-time adapter transformation preview for channel: <span className="font-semibold text-primary">{previewData.channel?.name}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-muted-foreground hover:text-foreground text-sm font-bold p-1 rounded"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 grid grid-cols-2 gap-4 flex-1 overflow-y-auto">
|
||||
{/* Left Column: PIM Raw Data */}
|
||||
<div className="flex flex-col border border-border rounded-lg bg-surface-muted overflow-hidden">
|
||||
<div className="px-3 py-2 bg-border/40 text-xs font-semibold text-muted-foreground uppercase tracking-wider border-b border-border">
|
||||
PIM Central Product (Raw JSON)
|
||||
</div>
|
||||
<pre className="p-4 text-xs font-mono text-foreground overflow-auto flex-1 max-h-96">
|
||||
{JSON.stringify(previewData.pimProductRaw, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Adapter Storefront Output */}
|
||||
<div className="flex flex-col border border-primary/20 rounded-lg bg-primary/5/30 overflow-hidden">
|
||||
<div className="px-3 py-2 bg-primary/10 text-xs font-semibold text-primary uppercase tracking-wider border-b border-primary/20 flex justify-between items-center">
|
||||
<span>Adapter Storefront Payload ({previewData.channel?.code})</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="inline-flex items-center gap-1 text-[11px] text-primary hover:underline"
|
||||
>
|
||||
{copied ? <Check className="w-3 h-3 text-success" /> : <Copy className="w-3 h-3" />}
|
||||
{copied ? "Copied!" : "Copy Payload"}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="p-4 text-xs font-mono text-primary-dark overflow-auto flex-1 max-h-96">
|
||||
{JSON.stringify(previewData.adapterOutput, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-3 border-t border-border bg-surface-muted flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Close Inspector
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user