@happyvertical/smrt-marketing 0.39.16

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.
Files changed (67) hide show
  1. package/AGENTS.md +79 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +67 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/CampaignChannelCollection.d.ts +9 -0
  8. package/dist/collections/CampaignChannelCollection.d.ts.map +1 -0
  9. package/dist/collections/CampaignCollection.d.ts +10 -0
  10. package/dist/collections/CampaignCollection.d.ts.map +1 -0
  11. package/dist/collections/CampaignMetricSnapshotCollection.d.ts +15 -0
  12. package/dist/collections/CampaignMetricSnapshotCollection.d.ts.map +1 -0
  13. package/dist/collections/index.d.ts +4 -0
  14. package/dist/collections/index.d.ts.map +1 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +625 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/manifest.json +1173 -0
  20. package/dist/models/Campaign.d.ts +31 -0
  21. package/dist/models/Campaign.d.ts.map +1 -0
  22. package/dist/models/CampaignChannel.d.ts +22 -0
  23. package/dist/models/CampaignChannel.d.ts.map +1 -0
  24. package/dist/models/CampaignMetricSnapshot.d.ts +29 -0
  25. package/dist/models/CampaignMetricSnapshot.d.ts.map +1 -0
  26. package/dist/models/index.d.ts +4 -0
  27. package/dist/models/index.d.ts.map +1 -0
  28. package/dist/services/BudgetPacingService.d.ts +25 -0
  29. package/dist/services/BudgetPacingService.d.ts.map +1 -0
  30. package/dist/services/CampaignLifecycleService.d.ts +19 -0
  31. package/dist/services/CampaignLifecycleService.d.ts.map +1 -0
  32. package/dist/services/MetricIngestionService.d.ts +16 -0
  33. package/dist/services/MetricIngestionService.d.ts.map +1 -0
  34. package/dist/services/index.d.ts +4 -0
  35. package/dist/services/index.d.ts.map +1 -0
  36. package/dist/smrt-knowledge.json +875 -0
  37. package/dist/svelte/__tests__/components.test.js +115 -0
  38. package/dist/svelte/__tests__/helpers.test.js +94 -0
  39. package/dist/svelte/components/BudgetPacing.svelte +129 -0
  40. package/dist/svelte/components/BudgetPacing.svelte.d.ts +10 -0
  41. package/dist/svelte/components/BudgetPacing.svelte.d.ts.map +1 -0
  42. package/dist/svelte/components/CampaignDetail.svelte +208 -0
  43. package/dist/svelte/components/CampaignDetail.svelte.d.ts +14 -0
  44. package/dist/svelte/components/CampaignDetail.svelte.d.ts.map +1 -0
  45. package/dist/svelte/components/CampaignList.svelte +140 -0
  46. package/dist/svelte/components/CampaignList.svelte.d.ts +11 -0
  47. package/dist/svelte/components/CampaignList.svelte.d.ts.map +1 -0
  48. package/dist/svelte/components/ChannelMix.svelte +138 -0
  49. package/dist/svelte/components/ChannelMix.svelte.d.ts +10 -0
  50. package/dist/svelte/components/ChannelMix.svelte.d.ts.map +1 -0
  51. package/dist/svelte/components/MarketingDashboard.svelte +129 -0
  52. package/dist/svelte/components/MarketingDashboard.svelte.d.ts +13 -0
  53. package/dist/svelte/components/MarketingDashboard.svelte.d.ts.map +1 -0
  54. package/dist/svelte/format.d.ts +8 -0
  55. package/dist/svelte/format.d.ts.map +1 -0
  56. package/dist/svelte/format.js +42 -0
  57. package/dist/svelte/index.d.ts +8 -0
  58. package/dist/svelte/index.d.ts.map +1 -0
  59. package/dist/svelte/index.js +7 -0
  60. package/dist/svelte/types.d.ts +73 -0
  61. package/dist/svelte/types.d.ts.map +1 -0
  62. package/dist/svelte/types.js +80 -0
  63. package/dist/types.d.ts +61 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +29 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +79 -0
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ import { Badge, Button } from '@happyvertical/smrt-ui/ui';
3
+ import { formatCents, formatDateRange, humanizeKey } from '../format.js';
4
+ import type { CampaignSummaryView } from '../types.js';
5
+ import { campaignStatusBadgeVariant } from '../types.js';
6
+
7
+ export interface Props {
8
+ campaigns?: CampaignSummaryView[];
9
+ selectedCampaignId?: string;
10
+ locale?: string;
11
+ onSelect?: (campaignId: string) => void;
12
+ }
13
+
14
+ let { campaigns = [], selectedCampaignId, locale, onSelect }: Props = $props();
15
+ </script>
16
+
17
+ <div class="campaign-list">
18
+ {#if campaigns.length === 0}
19
+ <p class="empty">No campaigns yet.</p>
20
+ {:else}
21
+ <table>
22
+ <thead>
23
+ <tr>
24
+ <th scope="col">Campaign</th>
25
+ <th scope="col">Status</th>
26
+ <th scope="col">Schedule</th>
27
+ <th scope="col" class="number">Spend / budget</th>
28
+ <th scope="col" class="number">Channels</th>
29
+ {#if onSelect}<th scope="col"><span class="visually-hidden">Actions</span></th>{/if}
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ {#each campaigns as campaign (campaign.id)}
34
+ <tr class:selected={campaign.id === selectedCampaignId}>
35
+ <td>
36
+ <span class="name">{campaign.name}</span>
37
+ <span class="secondary">{campaign.campaignKey}</span>
38
+ {#if campaign.objective}
39
+ <span class="secondary">{humanizeKey(campaign.objective)}</span>
40
+ {/if}
41
+ </td>
42
+ <td>
43
+ <Badge variant={campaignStatusBadgeVariant(campaign.status)} size="sm">
44
+ {humanizeKey(campaign.status)}
45
+ </Badge>
46
+ </td>
47
+ <td>{formatDateRange(campaign.startAt, campaign.endAt, locale)}</td>
48
+ <td class="number">
49
+ <span class="name">
50
+ {formatCents(campaign.spendCents ?? 0, campaign.currency, locale)}
51
+ </span>
52
+ <span class="secondary">
53
+ of {formatCents(campaign.budgetCents, campaign.currency, locale)}
54
+ </span>
55
+ </td>
56
+ <td class="number">{campaign.channelCount ?? 0}</td>
57
+ {#if onSelect}
58
+ <td class="action">
59
+ <Button
60
+ variant={campaign.id === selectedCampaignId ? 'primary' : 'secondary'}
61
+ size="sm"
62
+ onclick={() => onSelect?.(campaign.id)}
63
+ >
64
+ View
65
+ </Button>
66
+ </td>
67
+ {/if}
68
+ </tr>
69
+ {/each}
70
+ </tbody>
71
+ </table>
72
+ {/if}
73
+ </div>
74
+
75
+ <style>
76
+ .campaign-list {
77
+ width: 100%;
78
+ overflow-x: auto;
79
+ }
80
+
81
+ .empty {
82
+ margin: 0;
83
+ color: var(--smrt-color-on-surface-variant);
84
+ font: var(--smrt-typography-body-medium-font);
85
+ }
86
+
87
+ table {
88
+ width: 100%;
89
+ border-collapse: collapse;
90
+ color: var(--smrt-color-on-surface);
91
+ font: var(--smrt-typography-body-medium-font);
92
+ }
93
+
94
+ th,
95
+ td {
96
+ padding: var(--smrt-spacing-3);
97
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
98
+ text-align: left;
99
+ vertical-align: top;
100
+ }
101
+
102
+ th {
103
+ background: var(--smrt-color-surface-container);
104
+ font-weight: var(--smrt-typography-weight-semibold);
105
+ white-space: nowrap;
106
+ }
107
+
108
+ tr.selected td {
109
+ background: var(--smrt-color-primary-container);
110
+ }
111
+
112
+ .name,
113
+ .secondary {
114
+ display: block;
115
+ }
116
+
117
+ .name {
118
+ font-weight: var(--smrt-typography-weight-medium);
119
+ }
120
+
121
+ .secondary {
122
+ color: var(--smrt-color-on-surface-variant);
123
+ font: var(--smrt-typography-body-small-font);
124
+ }
125
+
126
+ .number,
127
+ .action {
128
+ text-align: right;
129
+ white-space: nowrap;
130
+ }
131
+
132
+ .visually-hidden {
133
+ position: absolute;
134
+ width: 1px;
135
+ height: 1px;
136
+ overflow: hidden;
137
+ clip: rect(0 0 0 0);
138
+ white-space: nowrap;
139
+ }
140
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { CampaignSummaryView } from '../types.js';
2
+ export interface Props {
3
+ campaigns?: CampaignSummaryView[];
4
+ selectedCampaignId?: string;
5
+ locale?: string;
6
+ onSelect?: (campaignId: string) => void;
7
+ }
8
+ declare const CampaignList: import("svelte").Component<Props, {}, "">;
9
+ type CampaignList = ReturnType<typeof CampaignList>;
10
+ export default CampaignList;
11
+ //# sourceMappingURL=CampaignList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CampaignList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/CampaignList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAqED,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,138 @@
1
+ <script lang="ts">
2
+ import { Progress } from '@happyvertical/smrt-ui/feedback';
3
+ import { Badge, Card } from '@happyvertical/smrt-ui/ui';
4
+ import {
5
+ formatCents,
6
+ formatNumber,
7
+ formatPercent,
8
+ humanizeKey,
9
+ } from '../format.js';
10
+ import type { CampaignChannelView } from '../types.js';
11
+ import { calculateChannelMix, campaignStatusBadgeVariant } from '../types.js';
12
+
13
+ export interface Props {
14
+ channels?: CampaignChannelView[];
15
+ currency?: string;
16
+ locale?: string;
17
+ }
18
+
19
+ let { channels = [], currency = 'USD', locale }: Props = $props();
20
+ const mix = $derived(calculateChannelMix(channels));
21
+ </script>
22
+
23
+ <section class="channel-mix">
24
+ <h3>Channel mix</h3>
25
+ {#if mix.length === 0}
26
+ <p class="empty">No channel performance yet.</p>
27
+ {:else}
28
+ <div class="rows">
29
+ {#each mix as channel (channel.id)}
30
+ <Card padding="sm">
31
+ <div class="head">
32
+ <div>
33
+ <span class="name">{channel.label ?? humanizeKey(channel.channelKind)}</span>
34
+ <span class="secondary">{channel.channelRef}</span>
35
+ </div>
36
+ <Badge variant={campaignStatusBadgeVariant(channel.status)} size="sm">
37
+ {humanizeKey(channel.status)}
38
+ </Badge>
39
+ </div>
40
+ <div class="progress-label">
41
+ <span>{formatCents(channel.spendCents, currency, locale)} spent</span>
42
+ <span>{formatPercent(channel.spendFraction, locale)} of mix</span>
43
+ </div>
44
+ <Progress
45
+ value={channel.spendFraction}
46
+ max={1}
47
+ label={`${channel.label ?? channel.channelKind} spend share`}
48
+ />
49
+ <dl class="metrics">
50
+ <div><dt>Allocation</dt><dd>{formatCents(channel.allocatedBudgetCents, currency, locale)}</dd></div>
51
+ <div><dt>Impressions</dt><dd>{formatNumber(channel.impressions ?? 0, locale)}</dd></div>
52
+ <div><dt>Clicks</dt><dd>{formatNumber(channel.clicks ?? 0, locale)}</dd></div>
53
+ <div><dt>Leads</dt><dd>{formatNumber(channel.leads ?? 0, locale)}</dd></div>
54
+ </dl>
55
+ </Card>
56
+ {/each}
57
+ </div>
58
+ {/if}
59
+ </section>
60
+
61
+ <style>
62
+ .channel-mix,
63
+ .rows {
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+
68
+ .channel-mix {
69
+ gap: var(--smrt-spacing-3);
70
+ }
71
+
72
+ .rows {
73
+ gap: var(--smrt-spacing-2);
74
+ }
75
+
76
+ h3,
77
+ .empty {
78
+ margin: 0;
79
+ }
80
+
81
+ h3 {
82
+ color: var(--smrt-color-on-surface);
83
+ font: var(--smrt-typography-title-medium-font);
84
+ }
85
+
86
+ .empty,
87
+ .secondary,
88
+ dt,
89
+ .progress-label {
90
+ color: var(--smrt-color-on-surface-variant);
91
+ }
92
+
93
+ .empty,
94
+ .name {
95
+ font: var(--smrt-typography-body-medium-font);
96
+ }
97
+
98
+ .head,
99
+ .progress-label {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ gap: var(--smrt-spacing-3);
104
+ }
105
+
106
+ .name,
107
+ .secondary {
108
+ display: block;
109
+ }
110
+
111
+ .name {
112
+ color: var(--smrt-color-on-surface);
113
+ font-weight: var(--smrt-typography-weight-medium);
114
+ }
115
+
116
+ .secondary,
117
+ .progress-label,
118
+ dt {
119
+ font: var(--smrt-typography-body-small-font);
120
+ }
121
+
122
+ .progress-label {
123
+ margin: var(--smrt-spacing-3) 0 var(--smrt-spacing-1);
124
+ }
125
+
126
+ .metrics {
127
+ display: grid;
128
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
129
+ gap: var(--smrt-spacing-3);
130
+ margin: var(--smrt-spacing-3) 0 0;
131
+ }
132
+
133
+ .metrics dd {
134
+ margin: var(--smrt-spacing-1) 0 0;
135
+ color: var(--smrt-color-on-surface);
136
+ font: var(--smrt-typography-body-medium-font);
137
+ }
138
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { CampaignChannelView } from '../types.js';
2
+ export interface Props {
3
+ channels?: CampaignChannelView[];
4
+ currency?: string;
5
+ locale?: string;
6
+ }
7
+ declare const ChannelMix: import("svelte").Component<Props, {}, "">;
8
+ type ChannelMix = ReturnType<typeof ChannelMix>;
9
+ export default ChannelMix;
10
+ //# sourceMappingURL=ChannelMix.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelMix.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ChannelMix.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAmDD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ import { Card } from '@happyvertical/smrt-ui/ui';
3
+ import type { Snippet } from 'svelte';
4
+ import { formatCents, formatNumber } from '../format.js';
5
+ import type { CampaignSummaryView } from '../types.js';
6
+ import { summarizeMarketingDashboard } from '../types.js';
7
+
8
+ export interface Props {
9
+ campaigns?: CampaignSummaryView[];
10
+ locale?: string;
11
+ campaignList?: Snippet;
12
+ detail?: Snippet;
13
+ children?: Snippet;
14
+ }
15
+
16
+ let {
17
+ campaigns = [],
18
+ locale,
19
+ campaignList,
20
+ detail,
21
+ children,
22
+ }: Props = $props();
23
+
24
+ const summary = $derived(summarizeMarketingDashboard(campaigns));
25
+ </script>
26
+
27
+ <section class="marketing-dashboard">
28
+ <div class="tiles">
29
+ <Card padding="sm">
30
+ <dl class="tile">
31
+ <dt>Campaigns</dt>
32
+ <dd>{summary.activeCampaignCount} active</dd>
33
+ <dd class="secondary">{summary.campaignCount} total</dd>
34
+ </dl>
35
+ </Card>
36
+ <Card padding="sm">
37
+ <dl class="tile">
38
+ <dt>Channels</dt>
39
+ <dd>{summary.channelCount}</dd>
40
+ <dd class="secondary">linked executions</dd>
41
+ </dl>
42
+ </Card>
43
+ <Card padding="sm">
44
+ <dl class="tile">
45
+ <dt>Leads</dt>
46
+ <dd>{formatNumber(summary.leads, locale)}</dd>
47
+ <dd class="secondary">{formatNumber(summary.conversions, locale)} conversions</dd>
48
+ </dl>
49
+ </Card>
50
+ <Card padding="sm">
51
+ <dl class="tile">
52
+ <dt>Reach</dt>
53
+ <dd>{formatNumber(summary.impressions, locale)}</dd>
54
+ <dd class="secondary">{formatNumber(summary.clicks, locale)} clicks</dd>
55
+ </dl>
56
+ </Card>
57
+ </div>
58
+
59
+ {#if summary.currencyTotals.length > 0}
60
+ <Card padding="sm">
61
+ <div class="currency-totals">
62
+ {#each summary.currencyTotals as total (total.currency)}
63
+ <dl class="currency-total">
64
+ <dt>{total.currency}</dt>
65
+ <dd>{formatCents(total.spendCents, total.currency, locale)} spent</dd>
66
+ <dd class="secondary">
67
+ of {formatCents(total.budgetCents, total.currency, locale)} budget
68
+ </dd>
69
+ </dl>
70
+ {/each}
71
+ </div>
72
+ </Card>
73
+ {/if}
74
+
75
+ {#if campaignList}
76
+ <div class="surface">{@render campaignList()}</div>
77
+ {/if}
78
+ {#if detail}
79
+ <div class="surface">{@render detail()}</div>
80
+ {/if}
81
+ {@render children?.()}
82
+ </section>
83
+
84
+ <style>
85
+ .marketing-dashboard {
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: var(--smrt-spacing-4);
89
+ }
90
+
91
+ .tiles {
92
+ display: grid;
93
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
94
+ gap: var(--smrt-spacing-3);
95
+ }
96
+
97
+ .tile,
98
+ .currency-total {
99
+ margin: 0;
100
+ }
101
+
102
+ dt {
103
+ color: var(--smrt-color-on-surface-variant);
104
+ font: var(--smrt-typography-label-medium-font);
105
+ text-transform: uppercase;
106
+ letter-spacing: var(--smrt-typography-label-medium-tracking);
107
+ }
108
+
109
+ dd {
110
+ margin: var(--smrt-spacing-1) 0 0;
111
+ color: var(--smrt-color-on-surface);
112
+ font: var(--smrt-typography-title-medium-font);
113
+ }
114
+
115
+ .secondary {
116
+ color: var(--smrt-color-on-surface-variant);
117
+ font: var(--smrt-typography-body-small-font);
118
+ }
119
+
120
+ .currency-totals {
121
+ display: grid;
122
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
123
+ gap: var(--smrt-spacing-4);
124
+ }
125
+
126
+ .surface {
127
+ min-width: 0;
128
+ }
129
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { CampaignSummaryView } from '../types.js';
3
+ export interface Props {
4
+ campaigns?: CampaignSummaryView[];
5
+ locale?: string;
6
+ campaignList?: Snippet;
7
+ detail?: Snippet;
8
+ children?: Snippet;
9
+ }
10
+ declare const MarketingDashboard: import("svelte").Component<Props, {}, "">;
11
+ type MarketingDashboard = ReturnType<typeof MarketingDashboard>;
12
+ export default MarketingDashboard;
13
+ //# sourceMappingURL=MarketingDashboard.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarketingDashboard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MarketingDashboard.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiFD,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { DateLike } from './types.js';
2
+ export declare function formatCents(cents: number, currency: string, locale?: string): string;
3
+ export declare function formatNumber(value: number, locale?: string): string;
4
+ export declare function formatPercent(value: number, locale?: string): string;
5
+ export declare function formatDate(value: DateLike, locale?: string): string;
6
+ export declare function formatDateRange(startAt: DateLike, endAt: DateLike, locale?: string): string;
7
+ export declare function humanizeKey(value: string): string;
8
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/svelte/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAYR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAKpE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,QAAQ,EACf,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAGR;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
@@ -0,0 +1,42 @@
1
+ export function formatCents(cents, currency, locale) {
2
+ if (!Number.isFinite(cents))
3
+ return '—';
4
+ const sign = cents < 0 ? -1 : 1;
5
+ const major = (sign * Math.round(Math.abs(cents))) / 100;
6
+ try {
7
+ return new Intl.NumberFormat(locale, {
8
+ style: 'currency',
9
+ currency,
10
+ }).format(major);
11
+ }
12
+ catch {
13
+ return `${major.toFixed(2)} ${currency}`.trim();
14
+ }
15
+ }
16
+ export function formatNumber(value, locale) {
17
+ return new Intl.NumberFormat(locale).format(value);
18
+ }
19
+ export function formatPercent(value, locale) {
20
+ return new Intl.NumberFormat(locale, {
21
+ style: 'percent',
22
+ maximumFractionDigits: 1,
23
+ }).format(value);
24
+ }
25
+ export function formatDate(value, locale) {
26
+ if (value == null)
27
+ return '—';
28
+ const date = value instanceof Date ? value : new Date(value);
29
+ if (Number.isNaN(date.getTime()))
30
+ return '—';
31
+ return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date);
32
+ }
33
+ export function formatDateRange(startAt, endAt, locale) {
34
+ if (startAt == null && endAt == null)
35
+ return 'No fixed schedule';
36
+ return `${formatDate(startAt, locale)} – ${formatDate(endAt, locale)}`;
37
+ }
38
+ export function humanizeKey(value) {
39
+ return value
40
+ .replace(/[_-]+/g, ' ')
41
+ .replace(/^\w/, (letter) => letter.toUpperCase());
42
+ }
@@ -0,0 +1,8 @@
1
+ export { default as BudgetPacing } from './components/BudgetPacing.svelte';
2
+ export { default as CampaignDetail } from './components/CampaignDetail.svelte';
3
+ export { default as CampaignList } from './components/CampaignList.svelte';
4
+ export { default as ChannelMix } from './components/ChannelMix.svelte';
5
+ export { default as MarketingDashboard } from './components/MarketingDashboard.svelte';
6
+ export * from './format.js';
7
+ export * from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AACvF,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { default as BudgetPacing } from './components/BudgetPacing.svelte';
2
+ export { default as CampaignDetail } from './components/CampaignDetail.svelte';
3
+ export { default as CampaignList } from './components/CampaignList.svelte';
4
+ export { default as ChannelMix } from './components/ChannelMix.svelte';
5
+ export { default as MarketingDashboard } from './components/MarketingDashboard.svelte';
6
+ export * from './format.js';
7
+ export * from './types.js';
@@ -0,0 +1,73 @@
1
+ export type MarketingBadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
2
+ export type DateLike = Date | string | number | null | undefined;
3
+ export interface CampaignSummaryView {
4
+ id: string;
5
+ campaignKey: string;
6
+ name: string;
7
+ objective?: string;
8
+ status: string;
9
+ startAt?: DateLike;
10
+ endAt?: DateLike;
11
+ budgetCents: number;
12
+ currency: string;
13
+ spendCents?: number;
14
+ impressions?: number;
15
+ clicks?: number;
16
+ conversions?: number;
17
+ leads?: number;
18
+ channelCount?: number;
19
+ }
20
+ export interface CampaignDetailView extends CampaignSummaryView {
21
+ description?: string;
22
+ }
23
+ export interface CampaignChannelView {
24
+ id: string;
25
+ channelKind: string;
26
+ channelRef: string;
27
+ label?: string;
28
+ status: string;
29
+ allocatedBudgetCents: number;
30
+ spendCents: number;
31
+ impressions?: number;
32
+ clicks?: number;
33
+ conversions?: number;
34
+ leads?: number;
35
+ }
36
+ export interface BudgetPacingView {
37
+ campaignId: string;
38
+ campaignChannelId?: string;
39
+ currency: string;
40
+ budgetCents: number;
41
+ spendCents: number;
42
+ remainingCents: number;
43
+ expectedSpendCents: number | null;
44
+ varianceCents: number | null;
45
+ budgetFraction: number | null;
46
+ elapsedFraction: number | null;
47
+ status: string;
48
+ }
49
+ export interface CurrencyMarketingTotal {
50
+ currency: string;
51
+ budgetCents: number;
52
+ spendCents: number;
53
+ }
54
+ export interface MarketingDashboardSummary {
55
+ campaignCount: number;
56
+ activeCampaignCount: number;
57
+ channelCount: number;
58
+ impressions: number;
59
+ clicks: number;
60
+ conversions: number;
61
+ leads: number;
62
+ currencyTotals: CurrencyMarketingTotal[];
63
+ }
64
+ export interface ChannelMixEntry extends CampaignChannelView {
65
+ spendFraction: number;
66
+ allocationFraction: number;
67
+ }
68
+ export declare function campaignStatusBadgeVariant(status: string): MarketingBadgeVariant;
69
+ export declare function pacingStatusBadgeVariant(status: string): MarketingBadgeVariant;
70
+ export declare function summarizeMarketingDashboard(campaigns: CampaignSummaryView[]): MarketingDashboardSummary;
71
+ export declare function calculateChannelMix(channels: CampaignChannelView[]): ChannelMixEntry[];
72
+ export declare function cappedBudgetProgress(pacing: BudgetPacingView): number;
73
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/svelte/types.ts"],"names":[],"mappings":"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"}