@manta-eu/react 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -8
- package/dist/components/audience-phase-badge/index.d.ts +1 -1
- package/dist/components/audience-phase-badge/index.d.ts.map +1 -1
- package/dist/components/audience-phase-badge/index.js +3 -0
- package/dist/components/authenticated-chat/index.d.ts.map +1 -1
- package/dist/components/authenticated-chat/index.js +0 -5
- package/dist/components/chat-view/Chat.d.ts +6 -0
- package/dist/components/chat-view/Chat.d.ts.map +1 -1
- package/dist/components/chat-view/Chat.js +6 -6
- package/dist/components/chat-view/ChatHeaders.d.ts.map +1 -1
- package/dist/components/chat-view/ChatHeaders.js +3 -2
- package/dist/components/embedded-chat/EmbeddedDatastorePicker.d.ts +1 -0
- package/dist/components/embedded-chat/EmbeddedDatastorePicker.d.ts.map +1 -1
- package/dist/components/embedded-chat/EmbeddedDatastorePicker.js +9 -3
- package/dist/components/embedded-chat/index.d.ts.map +1 -1
- package/dist/components/embedded-chat/index.js +1 -8
- package/dist/components/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/layouts/Sidebar.js +11 -4
- package/dist/components/planning/ActioningView.d.ts.map +1 -1
- package/dist/components/planning/ActioningView.js +11 -6
- package/dist/components/planning/ExecutionSectionItem.d.ts +2 -3
- package/dist/components/planning/ExecutionSectionItem.d.ts.map +1 -1
- package/dist/components/planning/ExecutionSectionItem.js +3 -6
- package/dist/components/planning/ExecutionSections.d.ts +2 -3
- package/dist/components/planning/ExecutionSections.d.ts.map +1 -1
- package/dist/components/planning/ExecutionSections.js +2 -2
- package/dist/components/planning/ManualActionCard.d.ts +3 -4
- package/dist/components/planning/ManualActionCard.d.ts.map +1 -1
- package/dist/components/planning/ManualActionCard.js +24 -6
- package/dist/components/rdf-insights/CampaignChannelMix.js +1 -1
- package/dist/components/rdf-insights/CampaignGrid.d.ts.map +1 -1
- package/dist/components/rdf-insights/CampaignGrid.js +2 -2
- package/dist/components/rdf-insights/DailyPerformance.js +2 -2
- package/dist/components/rdf-insights/KpiDashboard.d.ts +2 -2
- package/dist/components/rdf-insights/KpiDashboard.d.ts.map +1 -1
- package/dist/components/rdf-insights/KpiDashboard.js +33 -53
- package/dist/components/rdf-insights/MetaDailyInsights.js +2 -2
- package/dist/components/rdf-insights/ProductCard.d.ts.map +1 -1
- package/dist/components/rdf-insights/ProductCard.js +1 -1
- package/dist/components/rdf-insights/ProductVariantsTable.js +1 -1
- package/dist/components/rdf-insights/useExtractedInsights.d.ts +1 -1
- package/dist/components/rdf-insights/useExtractedInsights.js +3 -3
- package/dist/datastore-picker/datastore-picker-rows.d.ts +1 -0
- package/dist/datastore-picker/datastore-picker-rows.d.ts.map +1 -1
- package/dist/datastore-picker/datastore-picker-rows.js +6 -2
- package/dist/graph/nodes/GoogleCampaignView.js +2 -2
- package/dist/hooks/use-action-plans-list.js +1 -1
- package/dist/hooks/use-manual-actions.d.ts +11 -1
- package/dist/hooks/use-manual-actions.d.ts.map +1 -1
- package/dist/hooks/use-manual-actions.js +47 -100
- package/dist/hooks/useAuth.d.ts +5 -1
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +4 -0
- package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
- package/dist/ldo-ui.d.ts +1 -1
- package/dist/ldo-ui.d.ts.map +1 -1
- package/dist/lib/manual-actions.js +1 -1
- package/dist/lib/rdf/index.d.ts +4 -2
- package/dist/lib/rdf/index.d.ts.map +1 -1
- package/dist/lib/rdf/index.js +48 -18
- package/dist/lib/rdf/insights.d.ts +6 -1
- package/dist/lib/rdf/insights.d.ts.map +1 -1
- package/dist/lib/rdf/insights.js +15 -3
- package/dist/lib/rdf/types.d.ts +21 -8
- package/dist/lib/rdf/types.d.ts.map +1 -1
- package/dist/providers/AuthContext.d.ts +1 -1
- package/dist/providers/AuthContext.d.ts.map +1 -1
- package/dist/providers/index.d.ts +4 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -1
- package/dist/routes/datastores/components/DatastoreFormDialog.d.ts +4 -2
- package/dist/routes/datastores/components/DatastoreFormDialog.d.ts.map +1 -1
- package/dist/routes/datastores/components/DatastoreFormDialog.js +7 -4
- package/dist/routes/datastores/components/DatastoresToolbar.d.ts +3 -1
- package/dist/routes/datastores/components/DatastoresToolbar.d.ts.map +1 -1
- package/dist/routes/datastores/components/DatastoresToolbar.js +18 -5
- package/dist/routes/datastores/components/LinkUnlinkedDatastoreDialog.d.ts.map +1 -1
- package/dist/routes/datastores/components/LinkUnlinkedDatastoreDialog.js +6 -2
- package/dist/routes/datastores/components/ProvisionDatastoreDialog.d.ts +7 -0
- package/dist/routes/datastores/components/ProvisionDatastoreDialog.d.ts.map +1 -0
- package/dist/routes/datastores/components/ProvisionDatastoreDialog.js +10 -0
- package/dist/routes/datastores/flatten.d.ts +4 -0
- package/dist/routes/datastores/flatten.d.ts.map +1 -1
- package/dist/routes/datastores/flatten.js +11 -0
- package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts +4 -1
- package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts.map +1 -1
- package/dist/routes/datastores/hooks/useDatastoreColumns.js +6 -2
- package/dist/routes/datastores/hooks/useTenantOrganizationCheck.d.ts +3 -0
- package/dist/routes/datastores/hooks/useTenantOrganizationCheck.d.ts.map +1 -0
- package/dist/routes/datastores/hooks/useTenantOrganizationCheck.js +21 -0
- package/dist/routes/datastores/index.d.ts.map +1 -1
- package/dist/routes/datastores/index.js +44 -6
- package/dist/routes/datastores/link-provider.d.ts +11 -0
- package/dist/routes/datastores/link-provider.d.ts.map +1 -0
- package/dist/routes/datastores/link-provider.js +34 -0
- package/dist/routes/integrations/components/connect-forms/types.d.ts +1 -0
- package/dist/routes/integrations/components/connect-forms/types.d.ts.map +1 -1
- package/dist/routes/integrations/connection-view.d.ts.map +1 -1
- package/dist/routes/integrations/connection-view.js +19 -6
- package/dist/routes/integrations/types.d.ts +2 -0
- package/dist/routes/integrations/types.d.ts.map +1 -1
- package/dist/routes/memories/components/ChangeScopeDialog.d.ts +1 -1
- package/dist/routes/memories/components/ChangeScopeDialog.d.ts.map +1 -1
- package/dist/routes/memories/components/ChangeScopeDialog.js +3 -3
- package/dist/routes/memories/components/EmptyStates.d.ts +1 -2
- package/dist/routes/memories/components/EmptyStates.d.ts.map +1 -1
- package/dist/routes/memories/components/EmptyStates.js +2 -2
- package/dist/routes/memories/components/MemoriesPanel.d.ts +1 -1
- package/dist/routes/memories/components/MemoriesPanel.d.ts.map +1 -1
- package/dist/routes/memories/components/MemoriesPanel.js +9 -9
- package/dist/routes/memories/components/MemoryActions.d.ts +1 -1
- package/dist/routes/memories/components/MemoryActions.d.ts.map +1 -1
- package/dist/routes/memories/components/MemoryActions.js +4 -4
- package/dist/routes/memories/components/MemoryFields.d.ts +1 -1
- package/dist/routes/memories/components/MemoryFields.d.ts.map +1 -1
- package/dist/routes/memories/components/MemoryFields.js +2 -2
- package/dist/routes/memories/components/MemoryFormDialog.d.ts +1 -1
- package/dist/routes/memories/components/MemoryFormDialog.d.ts.map +1 -1
- package/dist/routes/memories/components/MemoryFormDialog.js +2 -2
- package/dist/routes/memories/components/TeamPicker.d.ts +1 -1
- package/dist/routes/memories/components/TeamPicker.d.ts.map +1 -1
- package/dist/routes/memories/components/TeamPicker.js +2 -2
- package/dist/routes/memories/hooks/useMemoryColumns.d.ts +1 -1
- package/dist/routes/memories/hooks/useMemoryColumns.d.ts.map +1 -1
- package/dist/routes/memories/hooks/useMemoryColumns.js +3 -3
- package/dist/routes/memories/index.d.ts.map +1 -1
- package/dist/routes/memories/index.js +21 -21
- package/package.json +7 -6
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
3
|
import { addAll } from "@manta-eu/lib/set";
|
|
4
|
-
import { CircleCheck, Hand, MessageSquareWarning } from "lucide-react";
|
|
4
|
+
import { CircleCheck, Hand, MessageSquareWarning, RotateCcw } from "lucide-react";
|
|
5
5
|
import { cn } from "#lib/utils";
|
|
6
6
|
export function ManualActionCard(props) {
|
|
7
|
-
const { manualAction, entities, description, isResolving,
|
|
8
|
-
const title = description ?? buildManualActionTitle(manualAction);
|
|
7
|
+
const { manualAction, entities, description, isResolving, onResolve } = props;
|
|
8
|
+
const title = description ?? manualAction.description ?? buildManualActionTitle(manualAction);
|
|
9
9
|
const displayEntities = isPresent(entities) ? entities : [buildFallbackEntity(manualAction)];
|
|
10
|
-
return (_jsx("div", { className: "mx-2 my-2", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-dashed border-active-border/50 bg-card shadow-sm", children: [_jsx(CardHeader, { title: title }), _jsx(EntityTable, { entities: displayEntities }), _jsx(CardFooter, { isResolving: isResolving,
|
|
10
|
+
return (_jsx("div", { className: "mx-2 my-2", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-dashed border-active-border/50 bg-card shadow-sm", children: [_jsx(CardHeader, { title: title }), _jsx(EntityTable, { entities: displayEntities }), _jsx(CardFooter, { isResolving: isResolving, retryable: manualAction.retryable ?? false, defaultResolution: manualAction.defaultResolution ?? "COMPLETED", onResolve: onResolve })] }) }));
|
|
11
11
|
}
|
|
12
12
|
function CardHeader(props) {
|
|
13
13
|
const { title } = props;
|
|
@@ -39,9 +39,27 @@ function PropertyChangePills(props) {
|
|
|
39
39
|
}
|
|
40
40
|
return (_jsxs("div", { className: "flex flex-1 items-center justify-end gap-1.5", children: [_jsx("span", { className: "rounded-[5px] bg-accent px-1.5 py-0.5 font-mono text-[11px] leading-4 text-muted-foreground", children: change.oldValue }), _jsx("span", { className: "font-mono text-[11px] text-muted-foreground", children: "\u2192" }), _jsx("span", { className: "rounded-[5px] bg-success px-1.5 py-0.5 font-mono text-[11px] leading-4 text-success-foreground", children: change.newValue })] }));
|
|
41
41
|
}
|
|
42
|
+
const primaryButton = cn("flex h-8 items-center gap-1.5 rounded-lg bg-success px-3", "typography-tiny text-success-foreground", "hover:bg-success/80 transition-colors", "disabled:opacity-50 disabled:cursor-not-allowed");
|
|
43
|
+
const secondaryButton = cn("flex h-8 items-center gap-1.5 rounded-lg border border-default-border bg-card px-3", "typography-tiny text-default-foreground", "hover:bg-muted-border transition-colors", "disabled:opacity-50 disabled:cursor-not-allowed");
|
|
44
|
+
export function resolutionChoices(retryable, defaultResolution) {
|
|
45
|
+
const positive = retryable ? ["COMPLETED", "RETRY"] : ["COMPLETED"];
|
|
46
|
+
const ordered = retryable && defaultResolution === "RETRY" ? [...positive].reverse() : positive;
|
|
47
|
+
return [...ordered, "REJECTED"];
|
|
48
|
+
}
|
|
49
|
+
const choiceLabels = {
|
|
50
|
+
COMPLETED: "Done, I fixed it",
|
|
51
|
+
RETRY: "Retry",
|
|
52
|
+
REJECTED: "Reject",
|
|
53
|
+
};
|
|
54
|
+
const choiceIcons = {
|
|
55
|
+
COMPLETED: _jsx(CircleCheck, { className: "size-4" }),
|
|
56
|
+
RETRY: _jsx(RotateCcw, { className: "size-4" }),
|
|
57
|
+
REJECTED: _jsx(MessageSquareWarning, { className: "size-4" }),
|
|
58
|
+
};
|
|
42
59
|
function CardFooter(props) {
|
|
43
|
-
const { isResolving,
|
|
44
|
-
|
|
60
|
+
const { isResolving, retryable, defaultResolution, onResolve } = props;
|
|
61
|
+
const choices = resolutionChoices(retryable, defaultResolution);
|
|
62
|
+
return (_jsx("div", { className: "flex items-center justify-between bg-muted px-3 py-3", children: _jsx("div", { className: "flex items-center gap-2", children: choices.map((choice, index) => (_jsxs("button", { type: "button", disabled: isResolving, onClick: () => onResolve(choice), className: index === 0 ? primaryButton : secondaryButton, children: [choiceIcons[choice], choiceLabels[choice]] }, choice))) }) }));
|
|
45
63
|
}
|
|
46
64
|
function buildManualActionTitle(manualAction) {
|
|
47
65
|
const subject = manualAction.subjectLabel || manualAction.typeName;
|
|
@@ -38,5 +38,5 @@ export function CampaignChannelMix(props) {
|
|
|
38
38
|
return (_jsxs("text", { x: viewBox.cx, y: viewBox.cy, textAnchor: "middle", dominantBaseline: "middle", children: [_jsx("tspan", { x: viewBox.cx, y: viewBox.cy, className: "fill-default-foreground typography-extra-large-header ", children: totalCampaigns }), _jsx("tspan", { x: viewBox.cx, y: (viewBox.cy || 0) + 24, className: "fill-muted-foreground typography-tiny", children: "Campaigns" })] }));
|
|
39
39
|
}
|
|
40
40
|
return null;
|
|
41
|
-
} }) })] }) }), _jsx("div", { className: "space-y-2", children: data.channels.map((channel, index) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border border-muted-border/50 p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "size-3 rounded-full", style: { backgroundColor: chartData[index]?.fill } }), _jsx("span", { className: "typography-small-medium", children: channel.channel })] }), _jsxs("div", { className: "flex items-center gap-4 typography-tiny text-muted-foreground", children: [_jsxs("span", { children: [channel.campaignCount, " campaign", channel.campaignCount !== 1 ? "s" : ""] }), _jsxs("span", { children: [channel.percentage.toFixed(1), "%"] }), _jsx("span", { className: "font-mono", children: formatMetric(channel.totalSpend, "currency") })] })] }, channel.channel))) }), _jsx(InsightCardFooter, { left: `Total: ${totalCampaigns} campaigns across ${data.channels.length} channels`, right: `${formatMetric(totalSpend, "currency")} spend` })] })] }));
|
|
41
|
+
} }) })] }) }), _jsx("div", { className: "space-y-2", children: data.channels.map((channel, index) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border border-muted-border/50 p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "size-3 rounded-full", style: { backgroundColor: chartData[index]?.fill } }), _jsx("span", { className: "typography-small-medium", children: channel.channel })] }), _jsxs("div", { className: "flex items-center gap-4 typography-tiny text-muted-foreground", children: [_jsxs("span", { children: [channel.campaignCount, " campaign", channel.campaignCount !== 1 ? "s" : ""] }), _jsxs("span", { children: [channel.percentage.toFixed(1), "%"] }), _jsx("span", { className: "font-mono", children: formatMetric(channel.totalSpend, "currency", channel.currency) })] })] }, channel.channel))) }), _jsx(InsightCardFooter, { left: `Total: ${totalCampaigns} campaigns across ${data.channels.length} channels`, right: `${formatMetric(totalSpend, "currency", data.currency)} spend` })] })] }));
|
|
42
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CampaignGrid.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/CampaignGrid.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"CampaignGrid.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/CampaignGrid.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAwHvD,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kDAyBpD"}
|
|
@@ -44,12 +44,12 @@ function Campaign(props) {
|
|
|
44
44
|
index,
|
|
45
45
|
value,
|
|
46
46
|
}));
|
|
47
|
-
return (_jsxs("div", { className: "rounded-lg border border-muted-border/50 p-4 space-y-3 hover:border-muted-border transition-colors", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "typography-small-semibold line-clamp-1", children: campaign.name || "Unnamed Campaign" }), _jsxs("div", { className: "flex items-center gap-2 typography-tiny", children: [_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Circle, { className: cn("size-2", getStatusColor(campaign.status)) }), _jsx("span", { children: campaign.status || "Active" })] }), campaign.channel && _jsx("span", { className: "text-muted-foreground", children: campaign.channel })] })] }), _jsx("div", { className: "h-12 -mx-2", children: _jsx(ChartContainer, { config: sparklineConfig, className: "h-full w-full", children: _jsx(LineChart, { data: sparklineData, children: _jsx(Line, { type: "monotone", dataKey: "value", stroke: "var(--color-value)", strokeWidth: 2, dot: false, isAnimationActive: false }) }) }) }), _jsxs("div", { className: "space-y-2 pt-2 border-t border-muted-border/50", children: [_jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(DollarSign, { className: "size-3" }), "Spend"] }), _jsx("span", { className: "font-mono typography-small-semibold", children: formatMetric(campaign.spend, "currency") })] }), _jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(TrendingUp, { className: "size-3" }), "Revenue"] }), _jsx("span", { className: "font-mono typography-small-semibold", children: formatMetric(campaign.revenue, "currency") })] }), _jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(Target, { className: "size-3" }), "ROAS"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: cn("font-mono typography-small-semibold", roasTier.color), children: [campaign.roas.toFixed(2), "x"] }), _jsx(Badge, { variant: "secondary", className: cn("typography-tiny", roasTier.color), children: roasTier.label })] })] })] })] }, campaign.uri));
|
|
47
|
+
return (_jsxs("div", { className: "rounded-lg border border-muted-border/50 p-4 space-y-3 hover:border-muted-border transition-colors", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "typography-small-semibold line-clamp-1", children: campaign.name || "Unnamed Campaign" }), _jsxs("div", { className: "flex items-center gap-2 typography-tiny", children: [_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Circle, { className: cn("size-2", getStatusColor(campaign.status)) }), _jsx("span", { children: campaign.status || "Active" })] }), campaign.channel && _jsx("span", { className: "text-muted-foreground", children: campaign.channel })] })] }), _jsx("div", { className: "h-12 -mx-2", children: _jsx(ChartContainer, { config: sparklineConfig, className: "h-full w-full", children: _jsx(LineChart, { data: sparklineData, children: _jsx(Line, { type: "monotone", dataKey: "value", stroke: "var(--color-value)", strokeWidth: 2, dot: false, isAnimationActive: false }) }) }) }), _jsxs("div", { className: "space-y-2 pt-2 border-t border-muted-border/50", children: [_jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(DollarSign, { className: "size-3" }), "Spend"] }), _jsx("span", { className: "font-mono typography-small-semibold", children: formatMetric(campaign.spend, "currency", campaign.currency) })] }), _jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(TrendingUp, { className: "size-3" }), "Revenue"] }), _jsx("span", { className: "font-mono typography-small-semibold", children: formatMetric(campaign.revenue, "currency", campaign.currency) })] }), _jsxs("div", { className: "flex items-center justify-between typography-tiny", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(Target, { className: "size-3" }), "ROAS"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: cn("font-mono typography-small-semibold", roasTier.color), children: [campaign.roas.toFixed(2), "x"] }), _jsx(Badge, { variant: "secondary", className: cn("typography-tiny", roasTier.color), children: roasTier.label })] })] })] })] }, campaign.uri));
|
|
48
48
|
}
|
|
49
49
|
export function CampaignGrid(props) {
|
|
50
50
|
const { data } = props;
|
|
51
51
|
if (!isPresent(data.campaigns))
|
|
52
52
|
return null;
|
|
53
53
|
const totalSpend = data.campaigns.reduce((sum, c) => sum + c.spend, 0);
|
|
54
|
-
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: LayoutGrid, title: `Campaigns (${data.campaigns.length})` }), _jsxs(CardContent, { className: "pt-4", children: [_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: data.campaigns.map((campaign) => (_jsx(Campaign, { campaign: campaign }, campaign.uri))) }), _jsx(InsightCardFooter, { left: `Showing ${data.campaigns.length} campaigns`, right: `Total Spend: ${formatMetric(totalSpend, "currency")}` })] })] }));
|
|
54
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: LayoutGrid, title: `Campaigns (${data.campaigns.length})` }), _jsxs(CardContent, { className: "pt-4", children: [_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: data.campaigns.map((campaign) => (_jsx(Campaign, { campaign: campaign }, campaign.uri))) }), _jsx(InsightCardFooter, { left: `Showing ${data.campaigns.length} campaigns`, right: `Total Spend: ${formatMetric(totalSpend, "currency", data.currency)}` })] })] }));
|
|
55
55
|
}
|
|
@@ -6,7 +6,7 @@ import { InsightCardHeader } from "./InsightCardHeader.js";
|
|
|
6
6
|
import { MetricDisplay } from "./MetricDisplay.js";
|
|
7
7
|
import { Card, CardContent } from "#components/card/index";
|
|
8
8
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "#components/chart/index";
|
|
9
|
-
import { formatMetric } from "#lib/rdf/insights";
|
|
9
|
+
import { formatCompactMoney, formatMetric } from "#lib/rdf/insights";
|
|
10
10
|
export function DailyPerformance(props) {
|
|
11
11
|
const { data } = props;
|
|
12
12
|
if (!isPresent(data.days))
|
|
@@ -46,5 +46,5 @@ export function DailyPerformance(props) {
|
|
|
46
46
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
47
47
|
}
|
|
48
48
|
const dateRange = startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : "";
|
|
49
|
-
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Calendar, title: "Daily Performance", dateRange: dateRange }), _jsxs(CardContent, { className: "pt-4 space-y-4", children: [_jsx(ChartContainer, { config: chartConfig, className: "h-[300px]", children: _jsxs(LineChart, { data: chartData, margin: { left: 0, right: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => value }), _jsx(YAxis, { tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) =>
|
|
49
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Calendar, title: "Daily Performance", dateRange: dateRange }), _jsxs(CardContent, { className: "pt-4 space-y-4", children: [_jsx(ChartContainer, { config: chartConfig, className: "h-[300px]", children: _jsxs(LineChart, { data: chartData, margin: { left: 0, right: 0 }, children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => value }), _jsx(YAxis, { tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => formatCompactMoney(value, data.currency) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Legend, {}), _jsx(Line, { dataKey: "revenue", type: "monotone", stroke: "var(--color-revenue)", strokeWidth: 2, dot: false }), _jsx(Line, { dataKey: "spend", type: "monotone", stroke: "var(--color-spend)", strokeWidth: 2, dot: false }), _jsx(Line, { dataKey: "orders", type: "monotone", stroke: "var(--color-orders)", strokeWidth: 2, strokeDasharray: "5 5", dot: false })] }) }), _jsxs("div", { className: "grid grid-cols-3 gap-4 pt-4", children: [_jsx(MetricDisplay, { icon: DollarSign, label: "Revenue", value: formatMetric(totalRevenue, "currency", data.currency), subtitle: `$${avgDailyRevenue.toFixed(0)}/day avg` }), _jsx(MetricDisplay, { icon: DollarSign, label: "Spend", value: formatMetric(totalSpend, "currency", data.currency), subtitle: `$${avgDailySpend.toFixed(0)}/day avg` }), _jsx(MetricDisplay, { icon: ShoppingCart, label: "Orders", value: totalOrders.toString(), subtitle: `${avgDailyOrders.toFixed(1)}/day avg` })] }), _jsxs("div", { className: "pt-2 border-t border-muted-border/50 flex items-center justify-between", children: [_jsx("span", { className: "typography-tiny text-muted-foreground", children: "Overall ROAS" }), _jsxs("span", { className: "typography-small-semibold font-mono", children: [overallRoas.toFixed(2), "x"] })] })] })] }));
|
|
50
50
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KpiSnapshot } from "#lib/rdf/types";
|
|
2
2
|
type KpiDashboardProps = {
|
|
3
|
-
kpis:
|
|
3
|
+
kpis: KpiSnapshot[];
|
|
4
4
|
};
|
|
5
5
|
export declare function KpiDashboard(props: KpiDashboardProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
6
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KpiDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/KpiDashboard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"KpiDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/KpiDashboard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,gBAAgB,CAAC;AAE7D,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,WAAW,EAAE,CAAC;CACrB,CAAC;AA4DF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kDAqBpD"}
|
|
@@ -3,61 +3,41 @@ import { isPresent } from "@manta-eu/lib/present";
|
|
|
3
3
|
import { Building2 } from "lucide-react";
|
|
4
4
|
import { InsightCardHeader } from "./InsightCardHeader.js";
|
|
5
5
|
import { Card, CardContent } from "#components/card/index";
|
|
6
|
-
import { formatMetric } from "#lib/rdf/insights";
|
|
7
|
-
const kpiMetadata =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
format: "currency",
|
|
36
|
-
description: "Average Order Value - average revenue per order",
|
|
37
|
-
},
|
|
38
|
-
grossMargin30d: {
|
|
39
|
-
label: "Gross Margin (30d)",
|
|
40
|
-
format: "percentage",
|
|
41
|
-
description: "Gross profit margin percentage",
|
|
42
|
-
},
|
|
43
|
-
};
|
|
6
|
+
import { formatMetric, percentChange } from "#lib/rdf/insights";
|
|
7
|
+
const kpiMetadata = [
|
|
8
|
+
{ key: "spend", label: "Spend", format: "currency" },
|
|
9
|
+
{ key: "revenue", label: "Revenue", format: "currency" },
|
|
10
|
+
{ key: "roas", label: "ROAS", format: "multiplier" },
|
|
11
|
+
{ key: "purchases", label: "Purchases", format: "count" },
|
|
12
|
+
{ key: "cpa", label: "CPA", format: "currency" },
|
|
13
|
+
{ key: "aov", label: "AOV", format: "currency" },
|
|
14
|
+
];
|
|
15
|
+
function formatValue(value, format, currency) {
|
|
16
|
+
if (format === "count")
|
|
17
|
+
return Math.round(value).toLocaleString("en-US");
|
|
18
|
+
return format === "currency" ? formatMetric(value, format, currency) : formatMetric(value, format);
|
|
19
|
+
}
|
|
20
|
+
function formatChange(change) {
|
|
21
|
+
return `${change > 0 ? "+" : ""}${change.toFixed(1)}%`;
|
|
22
|
+
}
|
|
23
|
+
function SnapshotGrid(props) {
|
|
24
|
+
const { snapshot, showCurrency } = props;
|
|
25
|
+
const metrics = kpiMetadata.flatMap((metadata) => {
|
|
26
|
+
const value = snapshot.current[metadata.key];
|
|
27
|
+
if (value === null)
|
|
28
|
+
return [];
|
|
29
|
+
return [{ ...metadata, value, change: percentChange(value, snapshot.previous?.[metadata.key]) }];
|
|
30
|
+
});
|
|
31
|
+
if (!isPresent(metrics))
|
|
32
|
+
return null;
|
|
33
|
+
return (_jsxs("div", { className: "space-y-2", children: [showCurrency && (_jsx("div", { className: "typography-tiny-medium tracking-wide text-muted-foreground uppercase", children: snapshot.currency })), _jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3", children: metrics.map((metric) => (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "typography-tiny-medium tracking-wide text-muted-foreground/70 uppercase", children: metric.label }), _jsx("div", { className: "typography-large-body-semibold font-mono leading-tight", children: formatValue(metric.value, metric.format, snapshot.currency) }), metric.change !== null && (_jsxs("div", { className: "typography-tiny text-muted-foreground", children: [formatChange(metric.change), " vs prev 30d"] }))] }, metric.key))) })] }));
|
|
34
|
+
}
|
|
44
35
|
export function KpiDashboard(props) {
|
|
45
36
|
const { kpis } = props;
|
|
46
|
-
|
|
47
|
-
Object.entries(kpis).forEach(([key, value]) => {
|
|
48
|
-
if (value !== null) {
|
|
49
|
-
const metadata = kpiMetadata[key];
|
|
50
|
-
if (metadata) {
|
|
51
|
-
metrics.push({
|
|
52
|
-
label: metadata.label,
|
|
53
|
-
value,
|
|
54
|
-
format: metadata.format,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
if (!isPresent(metrics)) {
|
|
37
|
+
if (!isPresent(kpis)) {
|
|
60
38
|
return null;
|
|
61
39
|
}
|
|
62
|
-
|
|
40
|
+
const [first] = kpis;
|
|
41
|
+
const dateRange = first?.startDate && first.endDate ? `${first.startDate} – ${first.endDate}` : undefined;
|
|
42
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Building2, title: "Your Performance (30 days)", dateRange: dateRange }), _jsx(CardContent, { className: "pt-4 space-y-4", children: kpis.map((snapshot) => (_jsx(SnapshotGrid, { snapshot: snapshot, showCurrency: kpis.length > 1 }, snapshot.currency))) })] }));
|
|
63
43
|
}
|
|
@@ -6,7 +6,7 @@ import { InsightCardHeader } from "./InsightCardHeader.js";
|
|
|
6
6
|
import { MetricDisplay } from "./MetricDisplay.js";
|
|
7
7
|
import { Card, CardContent, CardFooter } from "#components/card/index";
|
|
8
8
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "#components/chart/index";
|
|
9
|
-
import { formatMetric } from "#lib/rdf/insights";
|
|
9
|
+
import { formatCompactMoney, formatMetric } from "#lib/rdf/insights";
|
|
10
10
|
export function MetaDailyInsights(props) {
|
|
11
11
|
const { data } = props;
|
|
12
12
|
if (!isPresent(data.days))
|
|
@@ -45,5 +45,5 @@ export function MetaDailyInsights(props) {
|
|
|
45
45
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
46
46
|
}
|
|
47
47
|
const dateRange = startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : "";
|
|
48
|
-
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Calendar, title: "Meta Campaign Daily Insights", subtitle: data.campaignName, dateRange: dateRange }), _jsxs(CardContent, { className: "pt-4 space-y-4", children: [_jsx("div", { className: "bg-muted/10 rounded-radius p-4", children: _jsx(ChartContainer, { config: chartConfig, className: "h-[300px]", children: _jsxs(LineChart, { data: chartData, margin: { left: 0, right: 0 }, children: [_jsx(CartesianGrid, { vertical: false, strokeOpacity: 0.3 }), _jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => value }), _jsx(YAxis, { tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) =>
|
|
48
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Calendar, title: "Meta Campaign Daily Insights", subtitle: data.campaignName, dateRange: dateRange }), _jsxs(CardContent, { className: "pt-4 space-y-4", children: [_jsx("div", { className: "bg-muted/10 rounded-radius p-4", children: _jsx(ChartContainer, { config: chartConfig, className: "h-[300px]", children: _jsxs(LineChart, { data: chartData, margin: { left: 0, right: 0 }, children: [_jsx(CartesianGrid, { vertical: false, strokeOpacity: 0.3 }), _jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => value }), _jsx(YAxis, { tickLine: false, axisLine: false, tickMargin: 8, tickFormatter: (value) => formatCompactMoney(value, data.currency) }), _jsx(ChartTooltip, { content: _jsx(ChartTooltipContent, {}) }), _jsx(Legend, {}), _jsx(Line, { dataKey: "revenue", type: "monotone", stroke: "var(--color-revenue)", strokeWidth: 2, dot: false }), _jsx(Line, { dataKey: "spend", type: "monotone", stroke: "var(--color-spend)", strokeWidth: 2, dot: false }), _jsx(Line, { dataKey: "roas", type: "monotone", stroke: "var(--color-roas)", strokeWidth: 2, strokeDasharray: "5 5", dot: false })] }) }) }), _jsxs("div", { className: "grid grid-cols-3 gap-4 pt-4", children: [_jsx(MetricDisplay, { icon: DollarSign, label: "Spend", value: formatMetric(totalSpend, "currency", data.currency), subtitle: `$${avgDailySpend.toFixed(0)}/day avg` }), _jsx(MetricDisplay, { icon: TrendingUp, label: "Revenue", value: formatMetric(totalRevenue, "currency", data.currency), subtitle: `$${avgDailyRevenue.toFixed(0)}/day avg` }), _jsx(MetricDisplay, { icon: ShoppingCart, label: "Purchases", value: totalPurchases.toString(), subtitle: `${(totalPurchases / data.days.length).toFixed(1)}/day avg` })] })] }), _jsx(CardFooter, { className: "pt-0 border-t border-muted-border pt-4", children: _jsxs("div", { className: "flex items-center justify-between w-full typography-tiny", children: [_jsx("span", { className: "text-muted-foreground", children: "Overall ROAS" }), _jsxs("span", { className: "font-mono typography-small-semibold", children: [overallRoas.toFixed(2), "x"] })] }) })] }));
|
|
49
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductCard.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/ProductCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG9D,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"ProductCard.d.ts","sourceRoot":"","sources":["../../../src/components/rdf-insights/ProductCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG9D,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAgFlD"}
|
|
@@ -15,5 +15,5 @@ export function ProductCard(props) {
|
|
|
15
15
|
"out-of-stock": { color: "bg-error-foreground", text: "Out of Stock" },
|
|
16
16
|
};
|
|
17
17
|
const status = statusConfig[availabilityStatus];
|
|
18
|
-
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(ShoppingBag, { className: "size-5 text-muted-foreground mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsx(CardTitle, { className: "typography-body", children: product.name }), product.vendor && (_jsxs("div", { className: "flex items-center gap-1 mt-1 typography-small text-muted-foreground", children: [_jsx(User, { className: "size-3.5" }), _jsx("span", { children: product.vendor })] }))] })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [variant?.price !== null && variant?.price !== undefined && (_jsx("div", { className: "typography-large-body-semibold font-mono", children: formatMetric(variant.price, "currency") })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `size-2 rounded-full ${status.color}` }), _jsx("span", { className: "typography-small text-muted-foreground", children: status.text }), product.quantity != null && product.quantity < 0 && (_jsx("span", { className: "typography-tiny text-muted-foreground", children: "(Negative inventory tracked)" }))] }), isPresent(product.tags) && (_jsx("div", { className: "flex flex-wrap gap-1", children: product.tags.map((tag) => (_jsx(Badge, { variant: "secondary", className: "typography-tiny", children: tag }, tag))) })), variant?.cost === 0 && (_jsxs("div", { className: cn("flex items-start gap-2 p-3 rounded-md", "bg-warning", "border border-muted-border"), children: [_jsx(AlertCircle, { className: "size-4 text-warning-foreground mt-0.5" }), _jsxs("div", { className: "flex-1 typography-small", children: [_jsx("p", { className: "typography-small-medium text-warning-foreground", children: "Missing cost data" }), _jsx("p", { className: "text-warning-foreground/80 typography-tiny mt-0.5", children: "Add cost to track profit margins" })] })] })), product.url && (_jsxs("a", { href: product.url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 typography-small text-primary hover:underline", children: ["View in Shopify", _jsx(ExternalLink, { className: "size-3.5" })] }))] })] }));
|
|
18
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(ShoppingBag, { className: "size-5 text-muted-foreground mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsx(CardTitle, { className: "typography-body", children: product.name }), product.vendor && (_jsxs("div", { className: "flex items-center gap-1 mt-1 typography-small text-muted-foreground", children: [_jsx(User, { className: "size-3.5" }), _jsx("span", { children: product.vendor })] }))] })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [variant?.price !== null && variant?.price !== undefined && (_jsx("div", { className: "typography-large-body-semibold font-mono", children: formatMetric(variant.price, "currency", variant.currency) })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `size-2 rounded-full ${status.color}` }), _jsx("span", { className: "typography-small text-muted-foreground", children: status.text }), product.quantity != null && product.quantity < 0 && (_jsx("span", { className: "typography-tiny text-muted-foreground", children: "(Negative inventory tracked)" }))] }), isPresent(product.tags) && (_jsx("div", { className: "flex flex-wrap gap-1", children: product.tags.map((tag) => (_jsx(Badge, { variant: "secondary", className: "typography-tiny", children: tag }, tag))) })), variant?.cost === 0 && (_jsxs("div", { className: cn("flex items-start gap-2 p-3 rounded-md", "bg-warning", "border border-muted-border"), children: [_jsx(AlertCircle, { className: "size-4 text-warning-foreground mt-0.5" }), _jsxs("div", { className: "flex-1 typography-small", children: [_jsx("p", { className: "typography-small-medium text-warning-foreground", children: "Missing cost data" }), _jsx("p", { className: "text-warning-foreground/80 typography-tiny mt-0.5", children: "Add cost to track profit margins" })] })] })), product.url && (_jsxs("a", { href: product.url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 typography-small text-primary hover:underline", children: ["View in Shopify", _jsx(ExternalLink, { className: "size-3.5" })] }))] })] }));
|
|
19
19
|
}
|
|
@@ -53,5 +53,5 @@ export function ProductVariantsTable(props) {
|
|
|
53
53
|
return null;
|
|
54
54
|
const totalInventory = data.variants.reduce((sum, v) => sum + (v.inventory || 0), 0);
|
|
55
55
|
const avgMargin = data.variants.reduce((sum, v) => sum + (v.marginPct || 0), 0) / data.variants.length;
|
|
56
|
-
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Package, title: `Product Variants (${data.variants.length})` }), _jsxs(CardContent, { className: "pt-4", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[200px]", children: "Variant Name" }), _jsx(TableHead, { children: "SKU" }), _jsx(TableHead, { children: "Price" }), _jsx(TableHead, { children: "Cost" }), _jsx(TableHead, { className: "w-[200px]", children: "Margin %" }), _jsx(TableHead, { children: "Inventory" })] }) }), _jsx(TableBody, { children: data.variants.map((variant) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "typography-small-medium", children: variant.variantName || "Default" }), variant.productName && (_jsx("div", { className: "typography-tiny text-muted-foreground", children: variant.productName }))] }), _jsx(TableCell, { className: "font-mono typography-tiny text-muted-foreground", children: variant.sku || "—" }), _jsx(TableCell, { className: "font-mono", children: isNotNil(variant.price) ? (_jsxs(_Fragment, { children: [variant.compareAtPrice && variant.compareAtPrice > variant.price && (_jsx("div", { className: "typography-tiny text-muted-foreground line-through", children: formatMetric(variant.compareAtPrice, "currency") })), _jsx("div", { children: formatMetric(variant.price, "currency") })] })) : ("—") }), _jsx(TableCell, { className: "font-mono", children: isNotNil(variant.cost) ? formatMetric(variant.cost, "currency") : "—" }), _jsx(TableCell, { children: isNotNil(variant.marginPct) ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Circle, { className: cn("size-2", getMarginIconColor(variant.marginPct)) }), _jsxs("span", { className: cn("font-mono typography-small-semibold", getMarginColor(variant.marginPct)), children: [variant.marginPct.toFixed(0), "%"] }), _jsx("div", { className: "flex-1 h-2 bg-muted rounded-full overflow-hidden", children: _jsx("div", { className: cn("h-full transition-all", getMarginBgColor(variant.marginPct)), style: { width: `${Math.min(variant.marginPct, 100)}%` } }) })] })) : ("—") }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Circle, { className: cn("size-2", getStockIconColor(variant.inventory)) }), _jsxs("div", { children: [_jsx("div", { className: "font-mono typography-small-semibold", children: variant.inventory !== null ? variant.inventory : "—" }), variant.inventory !== null && (_jsx("div", { className: "typography-tiny text-muted-foreground", children: getStockText(variant.inventory) }))] })] }) })] }, variant.uri))) })] }), _jsx(InsightCardFooter, { left: `${data.variants.length} variants · Avg margin: ${avgMargin.toFixed(1)}%`, right: `Total inventory: ${totalInventory} units` })] })] }));
|
|
56
|
+
return (_jsxs(Card, { className: "shadow-none gap-4 py-4", children: [_jsx(InsightCardHeader, { icon: Package, title: `Product Variants (${data.variants.length})` }), _jsxs(CardContent, { className: "pt-4", children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[200px]", children: "Variant Name" }), _jsx(TableHead, { children: "SKU" }), _jsx(TableHead, { children: "Price" }), _jsx(TableHead, { children: "Cost" }), _jsx(TableHead, { className: "w-[200px]", children: "Margin %" }), _jsx(TableHead, { children: "Inventory" })] }) }), _jsx(TableBody, { children: data.variants.map((variant) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "typography-small-medium", children: variant.variantName || "Default" }), variant.productName && (_jsx("div", { className: "typography-tiny text-muted-foreground", children: variant.productName }))] }), _jsx(TableCell, { className: "font-mono typography-tiny text-muted-foreground", children: variant.sku || "—" }), _jsx(TableCell, { className: "font-mono", children: isNotNil(variant.price) ? (_jsxs(_Fragment, { children: [variant.compareAtPrice && variant.compareAtPrice > variant.price && (_jsx("div", { className: "typography-tiny text-muted-foreground line-through", children: formatMetric(variant.compareAtPrice, "currency", variant.currency) })), _jsx("div", { children: formatMetric(variant.price, "currency", variant.currency) })] })) : ("—") }), _jsx(TableCell, { className: "font-mono", children: isNotNil(variant.cost) ? formatMetric(variant.cost, "currency", variant.currency) : "—" }), _jsx(TableCell, { children: isNotNil(variant.marginPct) ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Circle, { className: cn("size-2", getMarginIconColor(variant.marginPct)) }), _jsxs("span", { className: cn("font-mono typography-small-semibold", getMarginColor(variant.marginPct)), children: [variant.marginPct.toFixed(0), "%"] }), _jsx("div", { className: "flex-1 h-2 bg-muted rounded-full overflow-hidden", children: _jsx("div", { className: cn("h-full transition-all", getMarginBgColor(variant.marginPct)), style: { width: `${Math.min(variant.marginPct, 100)}%` } }) })] })) : ("—") }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Circle, { className: cn("size-2", getStockIconColor(variant.inventory)) }), _jsxs("div", { children: [_jsx("div", { className: "font-mono typography-small-semibold", children: variant.inventory !== null ? variant.inventory : "—" }), variant.inventory !== null && (_jsx("div", { className: "typography-tiny text-muted-foreground", children: getStockText(variant.inventory) }))] })] }) })] }, variant.uri))) })] }), _jsx(InsightCardFooter, { left: `${data.variants.length} variants · Avg margin: ${avgMargin.toFixed(1)}%`, right: `Total inventory: ${totalInventory} units` })] })] }));
|
|
57
57
|
}
|
|
@@ -10,7 +10,7 @@ export declare function useExtractedInsights(jsonLd: JsonLdGraphDocument): {
|
|
|
10
10
|
productVariants: import("../../lib/rdf/types.js").ProductVariantsData | null;
|
|
11
11
|
campaignGrid: import("../../lib/rdf/types.js").CampaignGridData | null;
|
|
12
12
|
product: import("../../lib/rdf/types.js").Product | null;
|
|
13
|
-
kpis: import("../../lib/rdf/types.js").
|
|
13
|
+
kpis: import("../../lib/rdf/types.js").KpiSnapshot[] | null;
|
|
14
14
|
entityRelationTree: import("./EntityRelationTreeInsight.js").EntityRelationTreeRoot[];
|
|
15
15
|
hasExtractableInsights: boolean;
|
|
16
16
|
};
|
|
@@ -4,7 +4,7 @@ import { m, meta } from "@manta-eu/rdf-lib/namespaces";
|
|
|
4
4
|
import { flatten, uniq } from "ramda";
|
|
5
5
|
import { useMemo } from "react";
|
|
6
6
|
import { extractEntityRelationTreeRoots } from "./extractEntityRelationTreeRoots.js";
|
|
7
|
-
import { extractCampaignChannelMix, extractCampaignGrid, extractCampaignPerformanceSummary,
|
|
7
|
+
import { extractCampaignChannelMix, extractCampaignGrid, extractCampaignPerformanceSummary, extractDailyPerformance, extractKpiSnapshots, extractMetaDailyInsights, extractProduct, extractProductProfitability, extractProductSalesRanking, extractProductVariants, extractRoasBenchmark, } from "#lib/rdf/index";
|
|
8
8
|
const typeToExtractors = {
|
|
9
9
|
[m.ProductProfitability]: ["productProfitability"],
|
|
10
10
|
[m.ProductSalesRanking]: ["productSalesRanking"],
|
|
@@ -15,7 +15,7 @@ const typeToExtractors = {
|
|
|
15
15
|
[meta.Campaign]: ["campaignChannelMix", "campaignGrid"],
|
|
16
16
|
[m.ProductVariant]: ["productVariants"],
|
|
17
17
|
[m.Product]: ["product"],
|
|
18
|
-
[m.
|
|
18
|
+
[m.KPISnapshot]: ["kpis"],
|
|
19
19
|
};
|
|
20
20
|
function collectTypes(jsonLd) {
|
|
21
21
|
return new Set(flatten(jsonLd["@graph"].map((node) => getNodeTypes(node).map(expandPrefix))));
|
|
@@ -45,7 +45,7 @@ export function useExtractedInsights(jsonLd) {
|
|
|
45
45
|
const productVariants = requiredExtractors.has("productVariants") ? extractProductVariants(jsonLd) : null;
|
|
46
46
|
const campaignGrid = requiredExtractors.has("campaignGrid") ? extractCampaignGrid(jsonLd) : null;
|
|
47
47
|
const product = requiredExtractors.has("product") ? extractProduct(jsonLd) : null;
|
|
48
|
-
const kpis = requiredExtractors.has("kpis") ?
|
|
48
|
+
const kpis = requiredExtractors.has("kpis") ? extractKpiSnapshots(jsonLd) : null;
|
|
49
49
|
// Candidates only — EntityRelationTreeInsight filters against live connected-account IRIs.
|
|
50
50
|
const entityRelationTree = extractEntityRelationTreeRoots(jsonLd);
|
|
51
51
|
const hasExtractableInsights = productProfitability ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datastore-picker-rows.d.ts","sourceRoot":"","sources":["../../src/datastore-picker/datastore-picker-rows.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,QAAgB,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAuHF,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAO7E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"datastore-picker-rows.d.ts","sourceRoot":"","sources":["../../src/datastore-picker/datastore-picker-rows.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,QAAgB,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAuHF,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAO7E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAOV;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC9C,CAAC;AAGF,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,kBAAkB,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAU9G;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,kBAAkB,EAAE,CAsBvF"}
|
|
@@ -107,8 +107,12 @@ export function hasDatastoreChoice(datastores) {
|
|
|
107
107
|
return only !== undefined && hasAncestorAbove(list, only);
|
|
108
108
|
}
|
|
109
109
|
export function canPickDatastore(args) {
|
|
110
|
-
const { canSwitchDatastore, canSelect, datastores } = args;
|
|
111
|
-
|
|
110
|
+
const { canSwitchDatastore, canSelect, datastores, isOperator } = args;
|
|
111
|
+
if (!canSwitchDatastore || !canSelect)
|
|
112
|
+
return false;
|
|
113
|
+
// An operator picks from the datastore tree, so there is no list here to count, and their right
|
|
114
|
+
// to switch is already settled by canSwitchDatastore.
|
|
115
|
+
return isOperator === true || hasDatastoreChoice(datastores);
|
|
112
116
|
}
|
|
113
117
|
// Below this the search box is more clutter than help.
|
|
114
118
|
export const searchThreshold = 7;
|
|
@@ -6,7 +6,7 @@ import { memo } from "react";
|
|
|
6
6
|
import { OpenInPlatformLink } from "../OpenInPlatformLink.js";
|
|
7
7
|
import { EntityCardShell } from "./EntityCardShell.js";
|
|
8
8
|
import { formatBidStrategy, formatDate, formatNetworksSummary, getChannelDescriptor, googleRed, statusColors, } from "./google-campaign-utils.js";
|
|
9
|
-
import { readEntityIri,
|
|
9
|
+
import { readEntityIri, readSchemaOrgString } from "./typed-node-helpers.js";
|
|
10
10
|
import { useEntity } from "#hooks/use-entity";
|
|
11
11
|
import { formatRelativeUpdated } from "#lib/format-date";
|
|
12
12
|
function row(Icon, text) {
|
|
@@ -19,7 +19,7 @@ export const GoogleCampaignView = memo(function GoogleCampaignView(props) {
|
|
|
19
19
|
const { data, selected = false, ringColor, onContextMenu } = props;
|
|
20
20
|
const iri = readEntityIri(data.jsonLd);
|
|
21
21
|
const { data: typed } = useEntity({ schema: GoogleCampaignCardSchema, iri });
|
|
22
|
-
const currency =
|
|
22
|
+
const currency = readSchemaOrgString(data.jsonLd, "currency");
|
|
23
23
|
const statusKey = typed?.campaignStatus?.toString().toLowerCase() ?? "";
|
|
24
24
|
const statusColor = statusColors[statusKey] ?? "#6b7280";
|
|
25
25
|
const servingStatus = typed?.campaignServingStatus?.toString();
|
|
@@ -109,7 +109,7 @@ function integrationsFilterClause(integrations) {
|
|
|
109
109
|
function pendingApprovalGraphsSubquery() {
|
|
110
110
|
return `{ SELECT DISTINCT ?graph WHERE {
|
|
111
111
|
GRAPH ?graph {
|
|
112
|
-
?manualAction <${m.
|
|
112
|
+
?manualAction a <${m.ManualAction}> .
|
|
113
113
|
OPTIONAL { ?manualAction <${m.status}> ?manualActionStatus . }
|
|
114
114
|
}
|
|
115
115
|
FILTER (!BOUND(?manualActionStatus) || UCASE(REPLACE(STR(?manualActionStatus), "^.*[#/]", "")) = "PENDING")
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { MantaManualActionSchema } from "@manta-eu/ldo";
|
|
2
|
+
import type { LensEntity } from "./lens-entity.js";
|
|
3
|
+
export declare const manualActionStatuses: readonly ["PENDING", "COMPLETED", "REJECTED", "RETRY"];
|
|
2
4
|
export type ManualActionStatus = (typeof manualActionStatuses)[number];
|
|
5
|
+
export type ManualActionResolution = Exclude<ManualActionStatus, "PENDING">;
|
|
3
6
|
export type ManualAction = {
|
|
4
7
|
subjectIri: string;
|
|
5
8
|
subjectLabel: string | null;
|
|
@@ -7,12 +10,19 @@ export type ManualAction = {
|
|
|
7
10
|
typeName: string;
|
|
8
11
|
status: ManualActionStatus | null;
|
|
9
12
|
assignees: string[];
|
|
13
|
+
description?: string | null;
|
|
14
|
+
aboutIri?: string | null;
|
|
15
|
+
retryable?: boolean;
|
|
16
|
+
defaultResolution?: "COMPLETED" | "RETRY" | null;
|
|
10
17
|
};
|
|
11
18
|
export type UseManualActionsOptions = {
|
|
12
19
|
planGraphIri: string;
|
|
13
20
|
enabled?: boolean;
|
|
14
21
|
refetchIntervalMs?: number | false;
|
|
15
22
|
};
|
|
23
|
+
export type ManualActionEntity = LensEntity<typeof MantaManualActionSchema>;
|
|
24
|
+
export declare function toManualAction(entity: ManualActionEntity): ManualAction | null;
|
|
25
|
+
export declare function toManualActions(entities: ManualActionEntity[]): ManualAction[];
|
|
16
26
|
export declare function useManualActions(config: UseManualActionsOptions): import("@tanstack/react-query").UseQueryResult<ManualAction[], Error>;
|
|
17
27
|
export declare function useInvalidateManualActions(): (planGraphIri: string) => Promise<void>;
|
|
18
28
|
export type UseManualActionsForPlansOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-manual-actions.d.ts","sourceRoot":"","sources":["../../src/hooks/use-manual-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-manual-actions.d.ts","sourceRoot":"","sources":["../../src/hooks/use-manual-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAMxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAE3F,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AAa5E,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE5E,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,GAAG,IAAI,CAiB9E;AAMD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,YAAY,EAAE,CAK9E;AAYD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,uBAAuB,yEAa/D;AAED,wBAAgB,0BAA0B,KAEhC,cAAc,MAAM,mBAC7B;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACpC,CAAC;AASF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,+BAA+B,sFAc/E"}
|