@fayz-ai/plugin-marketing 0.13.32 → 0.13.34

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.
@@ -0,0 +1,80 @@
1
+ import { useMarketingConfig } from './chunk-JHCVIZ52.js';
2
+ import React from 'react';
3
+ import { useTranslation } from '@fayz-ai/core';
4
+ import { SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/admin';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var SOURCE_OPTIONS = [
8
+ { value: "crm", label: "CRM" },
9
+ { value: "agenda", label: "Agenda" },
10
+ { value: "orders", label: "Orders" },
11
+ { value: "custom", label: "Custom" }
12
+ ];
13
+ function SettingsView() {
14
+ const t = useTranslation();
15
+ const { conversion, channels } = useMarketingConfig();
16
+ const [source, setSource] = React.useState(conversion.source);
17
+ const [trackValue, setTrackValue] = React.useState(true);
18
+ const [autoSync, setAutoSync] = React.useState(true);
19
+ const [assisted, setAssisted] = React.useState(false);
20
+ const [tracked, setTracked] = React.useState(
21
+ () => Object.fromEntries(channels.map((c) => [c.id, true]))
22
+ );
23
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
24
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t("marketing.settings.conversionModel"), description: t("marketing.settings.conversionModelDesc"), children: [
25
+ /* @__PURE__ */ jsx(
26
+ SelectRow,
27
+ {
28
+ label: t("marketing.settings.attributedFrom"),
29
+ description: conversion.label,
30
+ value: source,
31
+ options: SOURCE_OPTIONS,
32
+ onChange: (v) => setSource(v)
33
+ }
34
+ ),
35
+ /* @__PURE__ */ jsx(
36
+ ToggleRow,
37
+ {
38
+ label: t("marketing.settings.trackValue"),
39
+ description: t("marketing.settings.trackValueDesc"),
40
+ checked: trackValue,
41
+ onChange: setTrackValue
42
+ }
43
+ )
44
+ ] }),
45
+ /* @__PURE__ */ jsx(SettingsGroup, { title: t("marketing.settings.channels"), description: t("marketing.settings.channelsDesc"), children: channels.map((c) => /* @__PURE__ */ jsx(
46
+ ToggleRow,
47
+ {
48
+ label: c.label,
49
+ description: t("marketing.settings.channelTrackDesc"),
50
+ checked: tracked[c.id] ?? true,
51
+ onChange: (v) => setTracked((prev) => ({ ...prev, [c.id]: v }))
52
+ },
53
+ c.id
54
+ )) }),
55
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t("marketing.settings.attribution"), description: t("marketing.settings.attributionDesc"), children: [
56
+ /* @__PURE__ */ jsx(
57
+ ToggleRow,
58
+ {
59
+ label: t("marketing.settings.autoSync"),
60
+ description: t("marketing.settings.autoSyncDesc"),
61
+ checked: autoSync,
62
+ onChange: setAutoSync
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsx(
66
+ ToggleRow,
67
+ {
68
+ label: t("marketing.settings.assisted"),
69
+ description: t("marketing.settings.assistedDesc"),
70
+ checked: assisted,
71
+ onChange: setAssisted
72
+ }
73
+ )
74
+ ] })
75
+ ] });
76
+ }
77
+
78
+ export { SettingsView };
79
+ //# sourceMappingURL=SettingsView-JJXZ3R7V.js.map
80
+ //# sourceMappingURL=SettingsView-JJXZ3R7V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/SettingsView.tsx"],"names":[],"mappings":"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","file":"SettingsView-JJXZ3R7V.js","sourcesContent":["import React from 'react'\nimport { useTranslation } from '@fayz-ai/core'\nimport { SettingsGroup, ToggleRow, SelectRow } from '@fayz-ai/admin'\nimport { useMarketingConfig } from '../context'\n\nconst SOURCE_OPTIONS = [\n { value: 'crm', label: 'CRM' },\n { value: 'agenda', label: 'Agenda' },\n { value: 'orders', label: 'Orders' },\n { value: 'custom', label: 'Custom' },\n]\n\nexport function SettingsView() {\n const t = useTranslation()\n const { conversion, channels } = useMarketingConfig()\n\n // Local UI state — mock toggles (no backend yet), mirrors other plugins' settings.\n const [source, setSource] = React.useState(conversion.source)\n const [trackValue, setTrackValue] = React.useState(true)\n const [autoSync, setAutoSync] = React.useState(true)\n const [assisted, setAssisted] = React.useState(false)\n const [tracked, setTracked] = React.useState<Record<string, boolean>>(\n () => Object.fromEntries(channels.map((c) => [c.id, true])),\n )\n\n return (\n <div className=\"space-y-4\">\n <SettingsGroup title={t('marketing.settings.conversionModel')} description={t('marketing.settings.conversionModelDesc')}>\n <SelectRow\n label={t('marketing.settings.attributedFrom')}\n description={conversion.label}\n value={source}\n options={SOURCE_OPTIONS}\n onChange={(v) => setSource(v as typeof source)}\n />\n <ToggleRow\n label={t('marketing.settings.trackValue')}\n description={t('marketing.settings.trackValueDesc')}\n checked={trackValue}\n onChange={setTrackValue}\n />\n </SettingsGroup>\n\n <SettingsGroup title={t('marketing.settings.channels')} description={t('marketing.settings.channelsDesc')}>\n {channels.map((c) => (\n <ToggleRow\n key={c.id}\n label={c.label}\n description={t('marketing.settings.channelTrackDesc')}\n checked={tracked[c.id] ?? true}\n onChange={(v) => setTracked((prev) => ({ ...prev, [c.id]: v }))}\n />\n ))}\n </SettingsGroup>\n\n <SettingsGroup title={t('marketing.settings.attribution')} description={t('marketing.settings.attributionDesc')}>\n <ToggleRow\n label={t('marketing.settings.autoSync')}\n description={t('marketing.settings.autoSyncDesc')}\n checked={autoSync}\n onChange={setAutoSync}\n />\n <ToggleRow\n label={t('marketing.settings.assisted')}\n description={t('marketing.settings.assistedDesc')}\n checked={assisted}\n onChange={setAssisted}\n />\n </SettingsGroup>\n </div>\n )\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { createPluginContext } from '@fayz-ai/admin';
3
+
4
+ // src/context.tsx
5
+ var ctx = createPluginContext("MarketingPage");
6
+ var MarketingContextProvider = ctx.ContextProvider;
7
+ var useMarketingConfig = ctx.useConfig;
8
+ var useMarketingProvider = ctx.useProvider;
9
+ var useMarketingStore = ctx.useStore;
10
+ function useChannelLookup() {
11
+ const { channels } = useMarketingConfig();
12
+ return React.useMemo(() => new Map(channels.map((c) => [c.id, c])), [channels]);
13
+ }
14
+
15
+ export { MarketingContextProvider, useChannelLookup, useMarketingConfig, useMarketingProvider, useMarketingStore };
16
+ //# sourceMappingURL=chunk-JHCVIZ52.js.map
17
+ //# sourceMappingURL=chunk-JHCVIZ52.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.tsx"],"names":[],"mappings":";;;;AAuCA,IAAM,GAAA,GAAM,oBAAsF,eAAe,CAAA;AAE1G,IAAM,2BAA2B,GAAA,CAAI;AACrC,IAAM,qBAAqB,GAAA,CAAI;AAC/B,IAAM,uBAAuB,GAAA,CAAI;AACjC,IAAM,oBAAoB,GAAA,CAAI;AAG9B,SAAS,gBAAA,GAAoD;AAClE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,kBAAA,EAAmB;AACxC,EAAA,OAAO,MAAM,OAAA,CAAQ,MAAM,IAAI,GAAA,CAAI,QAAA,CAAS,IAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,IAAI,CAAC,CAAC,CAAC,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAChF","file":"chunk-JHCVIZ52.js","sourcesContent":["import React from 'react'\nimport { createPluginContext } from '@fayz-ai/admin'\nimport type { AcquisitionChannel, ConversionModel } from './types'\nimport type { MarketingCurrency } from './lib/format'\nimport type { MarketingDomainModules } from './lib/presets'\nimport type { MarketingDataProvider } from './data/types'\nimport type { MarketingUIState } from './store'\n\n/**\n * Só `pageTitle`/`pageSubtitle` têm padrão: eles são como ESTE produto chama o\n * módulo. Os rótulos de navegação são opcionais e, sem override, saem do i18n\n * do plugin — um padrão em inglês fazia a faixa de abas aparecer escrita\n * \"Overview · Channels · Campaigns\" num app inteiro em português.\n */\nexport interface MarketingLabels {\n pageTitle: string\n pageSubtitle: string\n overview?: string\n channels?: string\n campaigns?: string\n funnel?: string\n cadences?: string\n landingPages?: string\n content?: string\n blog?: string\n blogCategories?: string\n settings?: string\n}\n\nexport interface ResolvedMarketingConfig {\n conversion: ConversionModel\n channels: AcquisitionChannel[]\n modules: MarketingDomainModules\n currency: MarketingCurrency\n labels: MarketingLabels\n /** Ver MarketingPluginOptions.moduleNav. */\n moduleNav?: 'rail' | 'tabs' | 'sidebar'\n}\n\nconst ctx = createPluginContext<ResolvedMarketingConfig, MarketingDataProvider, MarketingUIState>('MarketingPage')\n\nexport const MarketingContextProvider = ctx.ContextProvider\nexport const useMarketingConfig = ctx.useConfig\nexport const useMarketingProvider = ctx.useProvider\nexport const useMarketingStore = ctx.useStore\n\n/** Convenience: look up a channel definition by id. */\nexport function useChannelLookup(): Map<string, AcquisitionChannel> {\n const { channels } = useMarketingConfig()\n return React.useMemo(() => new Map(channels.map((c) => [c.id, c])), [channels])\n}\n"]}
@@ -0,0 +1,207 @@
1
+ import { useMarketingStore, useChannelLookup } from './chunk-JHCVIZ52.js';
2
+ import { AtSign, Pin, Twitter, Linkedin, Facebook, Youtube, Music2, Instagram, Link2, ArrowUpRight, ArrowDownRight, Bike, Utensils, DoorOpen, MessageCircle, MousePointerClick, Users, Mail, Globe, Megaphone, Search } from 'lucide-react';
3
+ import { cn, RangeControl } from '@fayz-ai/ui';
4
+ import { useTranslation } from '@fayz-ai/core';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ // src/data/blogEntities.tsx
8
+ var BLOG_POSTS_TABLE = "plg_blog_posts";
9
+ var BLOG_CATEGORIES_TABLE = "plg_blog_categories";
10
+ function buildBlogPostEntity() {
11
+ const fields = [
12
+ { key: "title", label: "Title", type: "text", required: true, searchable: true, showInTable: true, sortable: true },
13
+ { key: "slug", label: "Slug", type: "text", required: true, searchable: true, showInTable: false, hint: 'URL identifier, e.g. "cannabis-and-oral-health"' },
14
+ { key: "status", label: "Status", type: "select", options: ["draft", "published"], showInTable: true, defaultValue: "draft" },
15
+ { key: "categoryId", label: "Category", type: "relation", relation: { table: BLOG_CATEGORIES_TABLE, valueField: "id", labelField: "name", tenantScoped: true }, showInTable: false },
16
+ { key: "excerpt", label: "Excerpt", type: "textarea", span: 2, showInTable: false, placeholder: "Short summary shown on cards" },
17
+ { key: "body", label: "Body", type: "markdown", span: 2, showInTable: false },
18
+ { key: "coverImage", label: "Cover image", type: "image", showInTable: false, placeholder: "https://\u2026" },
19
+ { key: "readTime", label: "Read time", type: "text", showInTable: false, placeholder: "5 min" },
20
+ { key: "publishedAt", label: "Published at", type: "date", showInTable: true, sortable: true },
21
+ { key: "authorName", label: "Author name", type: "text", showInTable: false },
22
+ { key: "authorRole", label: "Author role", type: "text", showInTable: false, placeholder: "e.g. Cirurgi\xE3o-Dentista" },
23
+ { key: "authorAvatarUrl", label: "Author avatar", type: "image", showInTable: false, placeholder: "https://\u2026" },
24
+ { key: "authorBio", label: "Author bio", type: "textarea", span: 2, showInTable: false }
25
+ ];
26
+ return {
27
+ name: "Post",
28
+ namePlural: "Posts",
29
+ icon: "Newspaper",
30
+ fields,
31
+ displayField: "title",
32
+ subtitleField: "slug",
33
+ facets: [{ field: "status", allLabel: "All" }],
34
+ data: {
35
+ table: BLOG_POSTS_TABLE,
36
+ tenantScoped: true,
37
+ searchColumns: ["title", "slug", "excerpt"]
38
+ }
39
+ };
40
+ }
41
+ function buildBlogCategoryEntity() {
42
+ const fields = [
43
+ { key: "name", label: "Name", type: "text", required: true, searchable: true, showInTable: true, sortable: true },
44
+ { key: "slug", label: "Slug", type: "text", required: true, searchable: true, showInTable: true, hint: "URL identifier" },
45
+ { key: "description", label: "Description", type: "textarea", span: 2, showInTable: false }
46
+ ];
47
+ return {
48
+ name: "Category",
49
+ namePlural: "Categories",
50
+ icon: "Tags",
51
+ fields,
52
+ displayField: "name",
53
+ data: {
54
+ table: BLOG_CATEGORIES_TABLE,
55
+ tenantScoped: true,
56
+ searchColumns: ["name", "slug"]
57
+ }
58
+ };
59
+ }
60
+
61
+ // src/lib/format.ts
62
+ var DEFAULT_CURRENCY = { code: "USD", locale: "en-US", symbol: "$" };
63
+ function formatCurrency(value, currency) {
64
+ try {
65
+ return new Intl.NumberFormat(currency.locale, {
66
+ style: "currency",
67
+ currency: currency.code,
68
+ maximumFractionDigits: value >= 1e3 ? 0 : 2
69
+ }).format(value);
70
+ } catch {
71
+ return `${currency.symbol}${Math.round(value).toLocaleString()}`;
72
+ }
73
+ }
74
+ function formatNumber(value) {
75
+ return Math.round(value).toLocaleString();
76
+ }
77
+ function formatPercent(value, digits = 1) {
78
+ return `${value.toFixed(digits)}%`;
79
+ }
80
+ function formatCompact(value) {
81
+ if (value >= 1e3) return `${(value / 1e3).toFixed(value >= 1e4 ? 0 : 1)}k`;
82
+ return String(Math.round(value));
83
+ }
84
+ function trendDelta(current, previous) {
85
+ if (previous === 0) return { pct: current > 0 ? 100 : 0, dir: current > 0 ? "up" : "neutral" };
86
+ const pct = (current - previous) / previous * 100;
87
+ return { pct: Math.abs(pct), dir: pct > 0.5 ? "up" : pct < -0.5 ? "down" : "neutral" };
88
+ }
89
+ var ICONS = {
90
+ Search,
91
+ Megaphone,
92
+ Globe,
93
+ Mail,
94
+ Users,
95
+ MousePointerClick,
96
+ Instagram,
97
+ MessageCircle,
98
+ DoorOpen,
99
+ Utensils,
100
+ Bike
101
+ };
102
+ function ChannelIcon({ name, className }) {
103
+ const Icon = ICONS[name] ?? Link2;
104
+ return /* @__PURE__ */ jsx(Icon, { className });
105
+ }
106
+ var PLATFORM_ICONS = {
107
+ instagram: Instagram,
108
+ tiktok: Music2,
109
+ youtube: Youtube,
110
+ facebook: Facebook,
111
+ linkedin: Linkedin,
112
+ x: Twitter,
113
+ pinterest: Pin,
114
+ threads: AtSign
115
+ };
116
+ function PlatformIcon({ platform, className }) {
117
+ const Icon = PLATFORM_ICONS[platform] ?? Link2;
118
+ return /* @__PURE__ */ jsx(Icon, { className });
119
+ }
120
+ function KpiCard({
121
+ icon: Icon,
122
+ label,
123
+ value,
124
+ sub,
125
+ current,
126
+ previous,
127
+ invertTrend
128
+ }) {
129
+ const trend = current != null && previous != null ? trendDelta(current, previous) : null;
130
+ const good = trend ? invertTrend ? trend.dir === "down" : trend.dir === "up" : false;
131
+ const bad = trend ? invertTrend ? trend.dir === "up" : trend.dir === "down" : false;
132
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-border bg-card p-4", children: [
133
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
134
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
135
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: label })
136
+ ] }),
137
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-end justify-between gap-2", children: [
138
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-semibold text-foreground", children: value }),
139
+ trend && trend.dir !== "neutral" && /* @__PURE__ */ jsxs("span", { className: cn(
140
+ "mb-0.5 inline-flex items-center gap-0.5 text-xs font-medium",
141
+ good && "text-emerald-600",
142
+ bad && "text-rose-600",
143
+ !good && !bad && "text-muted-foreground"
144
+ ), children: [
145
+ trend.dir === "up" ? /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ArrowDownRight, { className: "h-3 w-3" }),
146
+ trend.pct.toFixed(0),
147
+ "%"
148
+ ] })
149
+ ] }),
150
+ sub && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: sub })
151
+ ] });
152
+ }
153
+ var RANGES = ["7d", "30d", "90d"];
154
+ var RANGE_LABEL = {
155
+ "7d": "last_7_days",
156
+ "30d": "last_30_days",
157
+ "90d": "last_90_days"
158
+ };
159
+ function RangeTabs() {
160
+ const t = useTranslation();
161
+ const range = useMarketingStore((s) => s.range);
162
+ const setRange = useMarketingStore((s) => s.setRange);
163
+ return /* @__PURE__ */ jsx(
164
+ RangeControl,
165
+ {
166
+ options: RANGES.map((r) => ({ value: r, label: t(`analytics.range.${RANGE_LABEL[r]}`) })),
167
+ value: range,
168
+ onChange: (r) => void setRange(r)
169
+ }
170
+ );
171
+ }
172
+ var STATUS_STYLE = {
173
+ active: "bg-emerald-100 text-emerald-700",
174
+ paused: "bg-amber-100 text-amber-700",
175
+ ended: "bg-muted text-muted-foreground",
176
+ draft: "bg-slate-100 text-slate-600"
177
+ };
178
+ function StatusBadge({ status }) {
179
+ const t = useTranslation();
180
+ return /* @__PURE__ */ jsx("span", { className: cn("rounded-full px-2 py-0.5 text-xs font-medium capitalize", STATUS_STYLE[status]), children: t(`marketing.status.${status}`) });
181
+ }
182
+ function ChannelCell({ channelId }) {
183
+ const lookup = useChannelLookup();
184
+ const ch = lookup.get(channelId);
185
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-foreground", children: [
186
+ /* @__PURE__ */ jsx(ChannelIcon, { name: ch?.icon ?? "Link2", className: "h-3.5 w-3.5 text-muted-foreground" }),
187
+ ch?.label ?? channelId
188
+ ] });
189
+ }
190
+ function ProportionBar({ value, max, color, label, right }) {
191
+ const pct = max > 0 ? Math.max(value / max * 100, 2) : 0;
192
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
193
+ /* @__PURE__ */ jsx("div", { className: "w-32 shrink-0 text-sm text-muted-foreground", children: label }),
194
+ /* @__PURE__ */ jsx("div", { className: "h-7 flex-1 overflow-hidden rounded-md bg-muted/40", children: /* @__PURE__ */ jsx(
195
+ "div",
196
+ {
197
+ className: "flex h-full items-center rounded-md px-2 text-xs font-semibold",
198
+ style: { width: `${pct}%`, backgroundColor: color + "26", color },
199
+ children: right
200
+ }
201
+ ) })
202
+ ] });
203
+ }
204
+
205
+ export { BLOG_POSTS_TABLE, ChannelCell, DEFAULT_CURRENCY, KpiCard, PlatformIcon, ProportionBar, RangeTabs, StatusBadge, buildBlogCategoryEntity, buildBlogPostEntity, formatCompact, formatCurrency, formatNumber, formatPercent };
206
+ //# sourceMappingURL=chunk-P3OYOY5W.js.map
207
+ //# sourceMappingURL=chunk-P3OYOY5W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data/blogEntities.tsx","../src/lib/format.ts","../src/components/icons.tsx","../src/components/MarketingBits.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-P3OYOY5W.js","sourcesContent":["import type { EntityDef, FieldDef } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Blog backoffice entities — the single source of truth that drives BOTH the\n// generic list (CrudListView columns + facets) and the create/edit form\n// (CrudFormPage fields, incl. the markdown body + category relation).\n//\n// Persistence is the generic Supabase provider resolved from `data.table`\n// (plg_blog_posts / plg_blog_categories, owned by @fayz-ai/plugin-blog's\n// migrations). camelCase field keys map to snake_case columns automatically.\n// The website reads the same rows through the anon v_public_blog_posts view.\n// ---------------------------------------------------------------------------\n\nexport const BLOG_POSTS_TABLE = 'plg_blog_posts'\nexport const BLOG_CATEGORIES_TABLE = 'plg_blog_categories'\n\nexport interface BlogPostRow {\n id: string\n title: string\n slug: string\n excerpt: string\n body: string\n categoryId?: string\n coverImage?: string\n authorName?: string\n authorRole?: string\n authorAvatarUrl?: string\n authorBio?: string\n status: 'draft' | 'published'\n readTime?: string\n publishedAt?: string\n}\n\nexport interface BlogCategoryRow {\n id: string\n name: string\n slug: string\n description?: string\n}\n\nexport function buildBlogPostEntity(): EntityDef<BlogPostRow> {\n const fields: FieldDef[] = [\n { key: 'title', label: 'Title', type: 'text', required: true, searchable: true, showInTable: true, sortable: true },\n { key: 'slug', label: 'Slug', type: 'text', required: true, searchable: true, showInTable: false, hint: 'URL identifier, e.g. \"cannabis-and-oral-health\"' },\n { key: 'status', label: 'Status', type: 'select', options: ['draft', 'published'], showInTable: true, defaultValue: 'draft' },\n { key: 'categoryId', label: 'Category', type: 'relation', relation: { table: BLOG_CATEGORIES_TABLE, valueField: 'id', labelField: 'name', tenantScoped: true }, showInTable: false },\n { key: 'excerpt', label: 'Excerpt', type: 'textarea', span: 2, showInTable: false, placeholder: 'Short summary shown on cards' },\n { key: 'body', label: 'Body', type: 'markdown', span: 2, showInTable: false },\n { key: 'coverImage', label: 'Cover image', type: 'image', showInTable: false, placeholder: 'https://…' },\n { key: 'readTime', label: 'Read time', type: 'text', showInTable: false, placeholder: '5 min' },\n { key: 'publishedAt', label: 'Published at', type: 'date', showInTable: true, sortable: true },\n { key: 'authorName', label: 'Author name', type: 'text', showInTable: false },\n { key: 'authorRole', label: 'Author role', type: 'text', showInTable: false, placeholder: 'e.g. Cirurgião-Dentista' },\n { key: 'authorAvatarUrl', label: 'Author avatar', type: 'image', showInTable: false, placeholder: 'https://…' },\n { key: 'authorBio', label: 'Author bio', type: 'textarea', span: 2, showInTable: false },\n ]\n return {\n name: 'Post',\n namePlural: 'Posts',\n icon: 'Newspaper',\n fields,\n displayField: 'title',\n subtitleField: 'slug',\n facets: [{ field: 'status', allLabel: 'All' }],\n data: {\n table: BLOG_POSTS_TABLE,\n tenantScoped: true,\n searchColumns: ['title', 'slug', 'excerpt'],\n },\n }\n}\n\nexport function buildBlogCategoryEntity(): EntityDef<BlogCategoryRow> {\n const fields: FieldDef[] = [\n { key: 'name', label: 'Name', type: 'text', required: true, searchable: true, showInTable: true, sortable: true },\n { key: 'slug', label: 'Slug', type: 'text', required: true, searchable: true, showInTable: true, hint: 'URL identifier' },\n { key: 'description', label: 'Description', type: 'textarea', span: 2, showInTable: false },\n ]\n return {\n name: 'Category',\n namePlural: 'Categories',\n icon: 'Tags',\n fields,\n displayField: 'name',\n data: {\n table: BLOG_CATEGORIES_TABLE,\n tenantScoped: true,\n searchColumns: ['name', 'slug'],\n },\n }\n}\n","export interface MarketingCurrency {\n code: string\n locale: string\n symbol: string\n}\n\nexport const DEFAULT_CURRENCY: MarketingCurrency = { code: 'USD', locale: 'en-US', symbol: '$' }\n\nexport function formatCurrency(value: number, currency: MarketingCurrency): string {\n try {\n return new Intl.NumberFormat(currency.locale, {\n style: 'currency',\n currency: currency.code,\n maximumFractionDigits: value >= 1000 ? 0 : 2,\n }).format(value)\n } catch {\n return `${currency.symbol}${Math.round(value).toLocaleString()}`\n }\n}\n\nexport function formatNumber(value: number): string {\n return Math.round(value).toLocaleString()\n}\n\nexport function formatPercent(value: number, digits = 1): string {\n return `${value.toFixed(digits)}%`\n}\n\n/** Compact form for big reach numbers (12.4k). */\nexport function formatCompact(value: number): string {\n if (value >= 1000) return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k`\n return String(Math.round(value))\n}\n\n/** Percentage-point delta vs a previous value, signed. */\nexport function trendDelta(current: number, previous: number): { pct: number; dir: 'up' | 'down' | 'neutral' } {\n if (previous === 0) return { pct: current > 0 ? 100 : 0, dir: current > 0 ? 'up' : 'neutral' }\n const pct = ((current - previous) / previous) * 100\n return { pct: Math.abs(pct), dir: pct > 0.5 ? 'up' : pct < -0.5 ? 'down' : 'neutral' }\n}\n","import React from 'react'\nimport {\n Search, Megaphone, Globe, Mail, Users, MousePointerClick,\n Instagram, MessageCircle, DoorOpen, Utensils, Bike, Link2,\n Youtube, Facebook, Linkedin, Twitter, Music2, Pin, AtSign,\n} from 'lucide-react'\n\n// Channel icon names referenced by presets → lucide components. Falls back to\n// a generic link icon for anything unmapped.\nconst ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n Search, Megaphone, Globe, Mail, Users, MousePointerClick,\n Instagram, MessageCircle, DoorOpen, Utensils, Bike,\n}\n\nexport function ChannelIcon({ name, className }: { name: string; className?: string }) {\n const Icon = ICONS[name] ?? Link2\n return <Icon className={className} />\n}\n\n// Platform icons for the content planner. Lucide has no TikTok / Pinterest /\n// Threads glyphs — Music2 / Pin / AtSign are the conventional stand-ins.\nconst PLATFORM_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n instagram: Instagram,\n tiktok: Music2,\n youtube: Youtube,\n facebook: Facebook,\n linkedin: Linkedin,\n x: Twitter,\n pinterest: Pin,\n threads: AtSign,\n}\n\nexport function PlatformIcon({ platform, className }: { platform: string; className?: string }) {\n const Icon = PLATFORM_ICONS[platform] ?? Link2\n return <Icon className={className} />\n}\n","import React from 'react'\nimport { ArrowDownRight, ArrowUpRight } from 'lucide-react'\nimport { cn, RangeControl } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport type { CampaignStatus, DateRangeKey } from '../types'\nimport { trendDelta } from '../lib/format'\nimport { useMarketingStore } from '../context'\nimport { ChannelIcon } from './icons'\nimport { useChannelLookup } from '../context'\n\n// ---------------------------------------------------------------------------\n// KPI card with optional trend chip.\n// ---------------------------------------------------------------------------\n\nexport function KpiCard({\n icon: Icon, label, value, sub, current, previous, invertTrend,\n}: {\n icon: React.ComponentType<{ className?: string }>\n label: string\n value: string\n sub?: string\n current?: number\n previous?: number\n /** for cost-style metrics where down is good (e.g. CPA) */\n invertTrend?: boolean\n}) {\n const trend = current != null && previous != null ? trendDelta(current, previous) : null\n const good = trend ? (invertTrend ? trend.dir === 'down' : trend.dir === 'up') : false\n const bad = trend ? (invertTrend ? trend.dir === 'up' : trend.dir === 'down') : false\n return (\n <div className=\"rounded-card border border-border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Icon className=\"h-4 w-4\" />\n <span className=\"text-xs font-medium\">{label}</span>\n </div>\n <div className=\"mt-2 flex items-end justify-between gap-2\">\n <p className=\"text-2xl font-semibold text-foreground\">{value}</p>\n {trend && trend.dir !== 'neutral' && (\n <span className={cn(\n 'mb-0.5 inline-flex items-center gap-0.5 text-xs font-medium',\n good && 'text-emerald-600', bad && 'text-rose-600',\n !good && !bad && 'text-muted-foreground',\n )}>\n {trend.dir === 'up' ? <ArrowUpRight className=\"h-3 w-3\" /> : <ArrowDownRight className=\"h-3 w-3\" />}\n {trend.pct.toFixed(0)}%\n </span>\n )}\n </div>\n {sub && <p className=\"mt-0.5 text-xs text-muted-foreground\">{sub}</p>}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Seletor de período. Lê/escreve o range compartilhado do módulo.\n//\n// É o MESMO controle das Análises e do painel (RangeControl): era um segmentado\n// de 7d/30d/90d, que é a gramática de uma alternância dentro de um cartão — não\n// da pergunta que a página inteira responde. Trocar de tela trocava o desenho\n// sem trocar o que se faz.\n// ---------------------------------------------------------------------------\n\nconst RANGES: DateRangeKey[] = ['7d', '30d', '90d']\nconst RANGE_LABEL: Record<DateRangeKey, string> = {\n '7d': 'last_7_days', '30d': 'last_30_days', '90d': 'last_90_days',\n}\n\nexport function RangeTabs() {\n const t = useTranslation()\n const range = useMarketingStore((s) => s.range)\n const setRange = useMarketingStore((s) => s.setRange)\n return (\n <RangeControl\n // O rótulo do dicionário, não a chave: \"30d\" no botão era a única tela\n // do produto que escrevia um período em código.\n options={RANGES.map((r) => ({ value: r, label: t(`analytics.range.${RANGE_LABEL[r]}`) }))}\n value={range}\n onChange={(r) => void setRange(r as DateRangeKey)}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// Status badge for campaigns.\n// ---------------------------------------------------------------------------\n\nconst STATUS_STYLE: Record<CampaignStatus, string> = {\n active: 'bg-emerald-100 text-emerald-700',\n paused: 'bg-amber-100 text-amber-700',\n ended: 'bg-muted text-muted-foreground',\n draft: 'bg-slate-100 text-slate-600',\n}\n\nexport function StatusBadge({ status }: { status: CampaignStatus }) {\n const t = useTranslation()\n return (\n <span className={cn('rounded-full px-2 py-0.5 text-xs font-medium capitalize', STATUS_STYLE[status])}>\n {t(`marketing.status.${status}`)}\n </span>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Channel cell (icon + label) used in tables.\n// ---------------------------------------------------------------------------\n\nexport function ChannelCell({ channelId }: { channelId: string }) {\n const lookup = useChannelLookup()\n const ch = lookup.get(channelId)\n return (\n <span className=\"inline-flex items-center gap-1.5 text-foreground\">\n <ChannelIcon name={ch?.icon ?? 'Link2'} className=\"h-3.5 w-3.5 text-muted-foreground\" />\n {ch?.label ?? channelId}\n </span>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Horizontal proportion bar (funnel / channel-mix), no charting dep.\n// ---------------------------------------------------------------------------\n\nexport function ProportionBar({ value, max, color, label, right }: {\n value: number\n max: number\n color: string\n label: React.ReactNode\n right?: React.ReactNode\n}) {\n const pct = max > 0 ? Math.max((value / max) * 100, 2) : 0\n return (\n <div className=\"flex items-center gap-3\">\n <div className=\"w-32 shrink-0 text-sm text-muted-foreground\">{label}</div>\n <div className=\"h-7 flex-1 overflow-hidden rounded-md bg-muted/40\">\n <div className=\"flex h-full items-center rounded-md px-2 text-xs font-semibold\"\n style={{ width: `${pct}%`, backgroundColor: color + '26', color }}>\n {right}\n </div>\n </div>\n </div>\n )\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}