@happyvertical/smrt-sales 0.39.2

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 (201) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/__smrt-register__-kWdT7LOt.js +6 -0
  8. package/dist/chunks/__smrt-register__-kWdT7LOt.js.map +1 -0
  9. package/dist/chunks/commissions-BCxqOKVM.js +2181 -0
  10. package/dist/chunks/commissions-BCxqOKVM.js.map +1 -0
  11. package/dist/chunks/crm-DwEz7E2r.js +1428 -0
  12. package/dist/chunks/crm-DwEz7E2r.js.map +1 -0
  13. package/dist/chunks/referrals-BY47p70T.js +2880 -0
  14. package/dist/chunks/referrals-BY47p70T.js.map +1 -0
  15. package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts +26 -0
  16. package/dist/commissions/collections/CommissionAdjustmentCollection.d.ts.map +1 -0
  17. package/dist/commissions/collections/CommissionCollection.d.ts +43 -0
  18. package/dist/commissions/collections/CommissionCollection.d.ts.map +1 -0
  19. package/dist/commissions/collections/CommissionPayoutCollection.d.ts +19 -0
  20. package/dist/commissions/collections/CommissionPayoutCollection.d.ts.map +1 -0
  21. package/dist/commissions/collections/CommissionPlanCollection.d.ts +46 -0
  22. package/dist/commissions/collections/CommissionPlanCollection.d.ts.map +1 -0
  23. package/dist/commissions/collections/EarnerCollection.d.ts +14 -0
  24. package/dist/commissions/collections/EarnerCollection.d.ts.map +1 -0
  25. package/dist/commissions/collections/EarningEventCollection.d.ts +27 -0
  26. package/dist/commissions/collections/EarningEventCollection.d.ts.map +1 -0
  27. package/dist/commissions/collections/index.d.ts +11 -0
  28. package/dist/commissions/collections/index.d.ts.map +1 -0
  29. package/dist/commissions/index.d.ts +20 -0
  30. package/dist/commissions/index.d.ts.map +1 -0
  31. package/dist/commissions/models/Commission.d.ts +150 -0
  32. package/dist/commissions/models/Commission.d.ts.map +1 -0
  33. package/dist/commissions/models/CommissionAdjustment.d.ts +62 -0
  34. package/dist/commissions/models/CommissionAdjustment.d.ts.map +1 -0
  35. package/dist/commissions/models/CommissionPayout.d.ts +119 -0
  36. package/dist/commissions/models/CommissionPayout.d.ts.map +1 -0
  37. package/dist/commissions/models/CommissionPlan.d.ts +117 -0
  38. package/dist/commissions/models/CommissionPlan.d.ts.map +1 -0
  39. package/dist/commissions/models/Earner.d.ts +49 -0
  40. package/dist/commissions/models/Earner.d.ts.map +1 -0
  41. package/dist/commissions/models/EarningEvent.d.ts +89 -0
  42. package/dist/commissions/models/EarningEvent.d.ts.map +1 -0
  43. package/dist/commissions/models/index.d.ts +11 -0
  44. package/dist/commissions/models/index.d.ts.map +1 -0
  45. package/dist/commissions/money.d.ts +42 -0
  46. package/dist/commissions/money.d.ts.map +1 -0
  47. package/dist/commissions/services/CommissionBalanceService.d.ts +14 -0
  48. package/dist/commissions/services/CommissionBalanceService.d.ts.map +1 -0
  49. package/dist/commissions/services/CommissionCalculationService.d.ts +142 -0
  50. package/dist/commissions/services/CommissionCalculationService.d.ts.map +1 -0
  51. package/dist/commissions/services/CommissionPayoutService.d.ts +134 -0
  52. package/dist/commissions/services/CommissionPayoutService.d.ts.map +1 -0
  53. package/dist/commissions/services/CommissionSettlementService.d.ts +42 -0
  54. package/dist/commissions/services/CommissionSettlementService.d.ts.map +1 -0
  55. package/dist/commissions/services/index.d.ts +9 -0
  56. package/dist/commissions/services/index.d.ts.map +1 -0
  57. package/dist/commissions/types.d.ts +243 -0
  58. package/dist/commissions/types.d.ts.map +1 -0
  59. package/dist/commissions.d.ts +2 -0
  60. package/dist/commissions.d.ts.map +1 -0
  61. package/dist/commissions.js +3 -0
  62. package/dist/crm/collections/LeadCollection.d.ts +79 -0
  63. package/dist/crm/collections/LeadCollection.d.ts.map +1 -0
  64. package/dist/crm/collections/OpportunityCollection.d.ts +42 -0
  65. package/dist/crm/collections/OpportunityCollection.d.ts.map +1 -0
  66. package/dist/crm/collections/OpportunityConversionCollection.d.ts +26 -0
  67. package/dist/crm/collections/OpportunityConversionCollection.d.ts.map +1 -0
  68. package/dist/crm/collections/PipelineDefinitionCollection.d.ts +46 -0
  69. package/dist/crm/collections/PipelineDefinitionCollection.d.ts.map +1 -0
  70. package/dist/crm/collections/PipelineStageCollection.d.ts +22 -0
  71. package/dist/crm/collections/PipelineStageCollection.d.ts.map +1 -0
  72. package/dist/crm/collections/SalesActivityCollection.d.ts +24 -0
  73. package/dist/crm/collections/SalesActivityCollection.d.ts.map +1 -0
  74. package/dist/crm/collections/SalesRepresentativeCollection.d.ts +19 -0
  75. package/dist/crm/collections/SalesRepresentativeCollection.d.ts.map +1 -0
  76. package/dist/crm/index.d.ts +27 -0
  77. package/dist/crm/index.d.ts.map +1 -0
  78. package/dist/crm/models/Lead.d.ts +135 -0
  79. package/dist/crm/models/Lead.d.ts.map +1 -0
  80. package/dist/crm/models/Opportunity.d.ts +111 -0
  81. package/dist/crm/models/Opportunity.d.ts.map +1 -0
  82. package/dist/crm/models/OpportunityConversion.d.ts +66 -0
  83. package/dist/crm/models/OpportunityConversion.d.ts.map +1 -0
  84. package/dist/crm/models/PipelineDefinition.d.ts +58 -0
  85. package/dist/crm/models/PipelineDefinition.d.ts.map +1 -0
  86. package/dist/crm/models/PipelineStage.d.ts +66 -0
  87. package/dist/crm/models/PipelineStage.d.ts.map +1 -0
  88. package/dist/crm/models/SalesActivity.d.ts +76 -0
  89. package/dist/crm/models/SalesActivity.d.ts.map +1 -0
  90. package/dist/crm/models/SalesRepresentative.d.ts +60 -0
  91. package/dist/crm/models/SalesRepresentative.d.ts.map +1 -0
  92. package/dist/crm/types.d.ts +228 -0
  93. package/dist/crm/types.d.ts.map +1 -0
  94. package/dist/crm.d.ts +2 -0
  95. package/dist/crm.d.ts.map +1 -0
  96. package/dist/crm.js +3 -0
  97. package/dist/index.d.ts +4 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +5 -0
  100. package/dist/manifest.json +9340 -0
  101. package/dist/referrals/collections/AttributionExceptionCollection.d.ts +17 -0
  102. package/dist/referrals/collections/AttributionExceptionCollection.d.ts.map +1 -0
  103. package/dist/referrals/collections/AttributionPolicyCollection.d.ts +50 -0
  104. package/dist/referrals/collections/AttributionPolicyCollection.d.ts.map +1 -0
  105. package/dist/referrals/collections/ReferralAgreementCollection.d.ts +48 -0
  106. package/dist/referrals/collections/ReferralAgreementCollection.d.ts.map +1 -0
  107. package/dist/referrals/collections/ReferralCollection.d.ts +25 -0
  108. package/dist/referrals/collections/ReferralCollection.d.ts.map +1 -0
  109. package/dist/referrals/collections/ReferralLinkCollection.d.ts +96 -0
  110. package/dist/referrals/collections/ReferralLinkCollection.d.ts.map +1 -0
  111. package/dist/referrals/collections/ReferralProgramCollection.d.ts +18 -0
  112. package/dist/referrals/collections/ReferralProgramCollection.d.ts.map +1 -0
  113. package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts +13 -0
  114. package/dist/referrals/collections/ReferralTermSnapshotCollection.d.ts.map +1 -0
  115. package/dist/referrals/collections/ReferralTouchCollection.d.ts +30 -0
  116. package/dist/referrals/collections/ReferralTouchCollection.d.ts.map +1 -0
  117. package/dist/referrals/collections/ReferrerCollection.d.ts +17 -0
  118. package/dist/referrals/collections/ReferrerCollection.d.ts.map +1 -0
  119. package/dist/referrals/index.d.ts +38 -0
  120. package/dist/referrals/index.d.ts.map +1 -0
  121. package/dist/referrals/models/AttributionException.d.ts +85 -0
  122. package/dist/referrals/models/AttributionException.d.ts.map +1 -0
  123. package/dist/referrals/models/AttributionPolicy.d.ts +128 -0
  124. package/dist/referrals/models/AttributionPolicy.d.ts.map +1 -0
  125. package/dist/referrals/models/Referral.d.ts +112 -0
  126. package/dist/referrals/models/Referral.d.ts.map +1 -0
  127. package/dist/referrals/models/ReferralAgreement.d.ts +125 -0
  128. package/dist/referrals/models/ReferralAgreement.d.ts.map +1 -0
  129. package/dist/referrals/models/ReferralLink.d.ts +71 -0
  130. package/dist/referrals/models/ReferralLink.d.ts.map +1 -0
  131. package/dist/referrals/models/ReferralProgram.d.ts +57 -0
  132. package/dist/referrals/models/ReferralProgram.d.ts.map +1 -0
  133. package/dist/referrals/models/ReferralTermSnapshot.d.ts +61 -0
  134. package/dist/referrals/models/ReferralTermSnapshot.d.ts.map +1 -0
  135. package/dist/referrals/models/ReferralTouch.d.ts +55 -0
  136. package/dist/referrals/models/ReferralTouch.d.ts.map +1 -0
  137. package/dist/referrals/models/Referrer.d.ts +69 -0
  138. package/dist/referrals/models/Referrer.d.ts.map +1 -0
  139. package/dist/referrals/services/AttributionService.d.ts +248 -0
  140. package/dist/referrals/services/AttributionService.d.ts.map +1 -0
  141. package/dist/referrals/services/ReferralCommissionService.d.ts +91 -0
  142. package/dist/referrals/services/ReferralCommissionService.d.ts.map +1 -0
  143. package/dist/referrals/services/ReferralQualificationService.d.ts +135 -0
  144. package/dist/referrals/services/ReferralQualificationService.d.ts.map +1 -0
  145. package/dist/referrals/types.d.ts +226 -0
  146. package/dist/referrals/types.d.ts.map +1 -0
  147. package/dist/referrals.d.ts +2 -0
  148. package/dist/referrals.d.ts.map +1 -0
  149. package/dist/referrals.js +3 -0
  150. package/dist/smrt-knowledge.json +5501 -0
  151. package/dist/svelte/__tests__/format.test.js +68 -0
  152. package/dist/svelte/__tests__/types.test.js +349 -0
  153. package/dist/svelte/components/AttributionConflictQueue.svelte +361 -0
  154. package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts +15 -0
  155. package/dist/svelte/components/AttributionConflictQueue.svelte.d.ts.map +1 -0
  156. package/dist/svelte/components/CommissionBreakdown.svelte +251 -0
  157. package/dist/svelte/components/CommissionBreakdown.svelte.d.ts +11 -0
  158. package/dist/svelte/components/CommissionBreakdown.svelte.d.ts.map +1 -0
  159. package/dist/svelte/components/CommissionExpenseSummary.svelte +186 -0
  160. package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts +13 -0
  161. package/dist/svelte/components/CommissionExpenseSummary.svelte.d.ts.map +1 -0
  162. package/dist/svelte/components/ExecutedAgreementsList.svelte +136 -0
  163. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts +11 -0
  164. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts.map +1 -0
  165. package/dist/svelte/components/LeadList.svelte +224 -0
  166. package/dist/svelte/components/LeadList.svelte.d.ts +19 -0
  167. package/dist/svelte/components/LeadList.svelte.d.ts.map +1 -0
  168. package/dist/svelte/components/OpportunityBoard.svelte +230 -0
  169. package/dist/svelte/components/OpportunityBoard.svelte.d.ts +17 -0
  170. package/dist/svelte/components/OpportunityBoard.svelte.d.ts.map +1 -0
  171. package/dist/svelte/components/OpportunityDetail.svelte +472 -0
  172. package/dist/svelte/components/OpportunityDetail.svelte.d.ts +27 -0
  173. package/dist/svelte/components/OpportunityDetail.svelte.d.ts.map +1 -0
  174. package/dist/svelte/components/PayoutBatchReview.svelte +285 -0
  175. package/dist/svelte/components/PayoutBatchReview.svelte.d.ts +21 -0
  176. package/dist/svelte/components/PayoutBatchReview.svelte.d.ts.map +1 -0
  177. package/dist/svelte/components/PayoutHistoryList.svelte +245 -0
  178. package/dist/svelte/components/PayoutHistoryList.svelte.d.ts +11 -0
  179. package/dist/svelte/components/PayoutHistoryList.svelte.d.ts.map +1 -0
  180. package/dist/svelte/components/ReferralLinkManager.svelte +216 -0
  181. package/dist/svelte/components/ReferralLinkManager.svelte.d.ts +17 -0
  182. package/dist/svelte/components/ReferralLinkManager.svelte.d.ts.map +1 -0
  183. package/dist/svelte/components/ReferralStatusList.svelte +118 -0
  184. package/dist/svelte/components/ReferralStatusList.svelte.d.ts +11 -0
  185. package/dist/svelte/components/ReferralStatusList.svelte.d.ts.map +1 -0
  186. package/dist/svelte/components/ReferrerEarningsSummary.svelte +106 -0
  187. package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts +11 -0
  188. package/dist/svelte/components/ReferrerEarningsSummary.svelte.d.ts.map +1 -0
  189. package/dist/svelte/components/SalesDashboard.svelte +192 -0
  190. package/dist/svelte/components/SalesDashboard.svelte.d.ts +29 -0
  191. package/dist/svelte/components/SalesDashboard.svelte.d.ts.map +1 -0
  192. package/dist/svelte/format.d.ts +46 -0
  193. package/dist/svelte/format.d.ts.map +1 -0
  194. package/dist/svelte/format.js +75 -0
  195. package/dist/svelte/index.d.ts +50 -0
  196. package/dist/svelte/index.d.ts.map +1 -0
  197. package/dist/svelte/index.js +37 -0
  198. package/dist/svelte/types.d.ts +422 -0
  199. package/dist/svelte/types.d.ts.map +1 -0
  200. package/dist/svelte/types.js +376 -0
  201. package/package.json +89 -0
@@ -0,0 +1,472 @@
1
+ <script lang="ts">
2
+ /**
3
+ * OpportunityDetail — single-opportunity work surface (#1924).
4
+ *
5
+ * Shows value / probability / expected close / outcome, the owner, the
6
+ * activity timeline (open next actions highlighted), and recorded conversion
7
+ * links. Stage movement, activity recording, and closing are delegated to
8
+ * callbacks; everything renders from plain view-model props.
9
+ */
10
+ import { Form, FormGroup, Input, Select } from '@happyvertical/smrt-ui/forms';
11
+ import { Badge, Button } from '@happyvertical/smrt-ui/ui';
12
+ import { formatCents, formatDate, formatPercent } from '../format.js';
13
+ import type {
14
+ ClosedOpportunityOutcome,
15
+ ConversionLinkView,
16
+ OpportunityDetailView,
17
+ PipelineStageView,
18
+ RecordActivityDraft,
19
+ SalesActivityView,
20
+ } from '../types.js';
21
+ import { isOverdue, opportunityStatusBadgeVariant } from '../types.js';
22
+
23
+ export interface Props {
24
+ /** The opportunity being worked. */
25
+ opportunity: OpportunityDetailView;
26
+ /** Stages of the opportunity's pipeline (for the move picker). */
27
+ stages?: PipelineStageView[];
28
+ /** Activity/next-action timeline rows, newest first by convention. */
29
+ activities?: SalesActivityView[];
30
+ /** Recorded downstream conversion links. */
31
+ conversions?: ConversionLinkView[];
32
+ /** Selectable activity kinds for the record-activity form. */
33
+ activityKinds?: string[];
34
+ /** Disable actions while a mutation is in flight. */
35
+ busy?: boolean;
36
+ /** BCP 47 locale for money/date formatting. */
37
+ locale?: string;
38
+ /** Move the opportunity to another stage. */
39
+ onMoveStage?: (stageId: string) => void;
40
+ /** Record a new activity / next action. */
41
+ onRecordActivity?: (draft: RecordActivityDraft) => void;
42
+ /** Close the opportunity with a terminal outcome. */
43
+ onClose?: (outcome: ClosedOpportunityOutcome, reason?: string) => void;
44
+ }
45
+
46
+ let {
47
+ opportunity,
48
+ stages = [],
49
+ activities = [],
50
+ conversions = [],
51
+ activityKinds = ['note', 'call', 'email', 'meeting', 'task'],
52
+ busy = false,
53
+ locale,
54
+ onMoveStage,
55
+ onRecordActivity,
56
+ onClose,
57
+ }: Props = $props();
58
+
59
+ const isOpen = $derived(opportunity.status === 'open');
60
+
61
+ // Stage-move draft.
62
+ let moveTargetStageId = $state('');
63
+
64
+ // Close draft.
65
+ let closeReason = $state('');
66
+
67
+ // Record-activity draft.
68
+ let draftKind = $state('note');
69
+ let draftSummary = $state('');
70
+ let draftDueAt = $state('');
71
+
72
+ function submitMove() {
73
+ if (moveTargetStageId) onMoveStage?.(moveTargetStageId);
74
+ }
75
+
76
+ function close(outcome: ClosedOpportunityOutcome) {
77
+ const reason = closeReason.trim();
78
+ onClose?.(outcome, reason === '' ? undefined : reason);
79
+ }
80
+
81
+ function submitActivity() {
82
+ const summary = draftSummary.trim();
83
+ if (summary === '') return;
84
+ onRecordActivity?.({
85
+ activityKind: draftKind,
86
+ summary,
87
+ dueAt: draftDueAt === '' ? null : draftDueAt,
88
+ });
89
+ draftSummary = '';
90
+ draftDueAt = '';
91
+ }
92
+
93
+ function isOpenNextAction(activity: SalesActivityView): boolean {
94
+ return (
95
+ activity.dueAt != null && activity.dueAt !== '' && !activity.completedAt
96
+ );
97
+ }
98
+ </script>
99
+
100
+ <article class="sales-opportunity-detail">
101
+ <header class="head">
102
+ <h2 class="head__name">{opportunity.name}</h2>
103
+ <Badge variant={opportunityStatusBadgeVariant(opportunity.status)} size="sm">
104
+ {opportunity.status}
105
+ </Badge>
106
+ </header>
107
+
108
+ <dl class="facts">
109
+ <div class="facts__item">
110
+ <dt>Expected value</dt>
111
+ <dd>
112
+ {formatCents(opportunity.expectedValueCents, opportunity.currency, locale)}
113
+ </dd>
114
+ </div>
115
+ <div class="facts__item">
116
+ <dt>Probability</dt>
117
+ <dd>{formatPercent(opportunity.probability, locale)}</dd>
118
+ </div>
119
+ <div class="facts__item">
120
+ <dt>Expected close</dt>
121
+ <dd>{formatDate(opportunity.expectedCloseAt, locale)}</dd>
122
+ </div>
123
+ <div class="facts__item">
124
+ <dt>Stage</dt>
125
+ <dd>{opportunity.stageName ?? '—'}</dd>
126
+ </div>
127
+ <div class="facts__item">
128
+ <dt>Owner</dt>
129
+ <dd>{opportunity.ownerName ?? '—'}</dd>
130
+ </div>
131
+ </dl>
132
+
133
+ {#if !isOpen}
134
+ <p class="outcome" class:outcome--won={opportunity.status === 'won'} class:outcome--lost={opportunity.status === 'lost'}>
135
+ Closed {opportunity.status}
136
+ {#if opportunity.status === 'won'}
137
+ on {formatDate(opportunity.wonAt, locale)}
138
+ {:else}
139
+ on {formatDate(opportunity.lostAt, locale)}
140
+ {/if}
141
+ {#if opportunity.outcomeReason}
142
+ — {opportunity.outcomeReason}
143
+ {/if}
144
+ </p>
145
+ {/if}
146
+
147
+ {#if isOpen && (onMoveStage || onClose)}
148
+ <div class="controls">
149
+ {#if onMoveStage && stages.length > 0}
150
+ <div class="controls__group">
151
+ <FormGroup label="Move to stage">
152
+ <Select
153
+ value={moveTargetStageId}
154
+ disabled={busy}
155
+ onchange={(event) => {
156
+ moveTargetStageId = (event.currentTarget as HTMLSelectElement).value;
157
+ }}
158
+ >
159
+ <option value="" disabled>Choose a stage…</option>
160
+ {#each stages as stage (stage.id)}
161
+ {#if stage.id !== opportunity.stageId}
162
+ <option value={stage.id}>{stage.name}</option>
163
+ {/if}
164
+ {/each}
165
+ </Select>
166
+ </FormGroup>
167
+ <Button
168
+ variant="secondary"
169
+ size="sm"
170
+ disabled={busy || moveTargetStageId === ''}
171
+ onclick={submitMove}
172
+ >
173
+ Move
174
+ </Button>
175
+ </div>
176
+ {/if}
177
+
178
+ {#if onClose}
179
+ <div class="controls__group">
180
+ <FormGroup label="Outcome reason" hint="Recorded with the close (recommended when losing).">
181
+ <Input
182
+ value={closeReason}
183
+ disabled={busy}
184
+ oninput={(event) => {
185
+ closeReason = (event.currentTarget as HTMLInputElement).value;
186
+ }}
187
+ />
188
+ </FormGroup>
189
+ <span class="controls__buttons">
190
+ <Button variant="primary" size="sm" disabled={busy} onclick={() => close('won')}>
191
+ Close won
192
+ </Button>
193
+ <Button variant="danger" size="sm" disabled={busy} onclick={() => close('lost')}>
194
+ Close lost
195
+ </Button>
196
+ </span>
197
+ </div>
198
+ {/if}
199
+ </div>
200
+ {/if}
201
+
202
+ <section class="timeline" aria-label="Activity timeline">
203
+ <h3>Activity</h3>
204
+ {#if activities.length === 0}
205
+ <p class="empty">No activity recorded yet.</p>
206
+ {:else}
207
+ <ol class="timeline__list">
208
+ {#each activities as activity (activity.id)}
209
+ {@const open = isOpenNextAction(activity)}
210
+ <li class="timeline__item" class:next-action={open}>
211
+ <span class="timeline__kind">{activity.activityKind}</span>
212
+ <span class="timeline__summary">{activity.summary}</span>
213
+ <span class="timeline__dates">
214
+ {#if activity.dueAt}
215
+ <span class:overdue={open && isOverdue(activity.dueAt)}>
216
+ due {formatDate(activity.dueAt, locale)}
217
+ </span>
218
+ {/if}
219
+ {#if activity.completedAt}
220
+ <span>done {formatDate(activity.completedAt, locale)}</span>
221
+ {/if}
222
+ {#if activity.actorName}
223
+ <span>by {activity.actorName}</span>
224
+ {/if}
225
+ </span>
226
+ </li>
227
+ {/each}
228
+ </ol>
229
+ {/if}
230
+
231
+ {#if onRecordActivity && isOpen}
232
+ <Form class="record" onsubmit={submitActivity}>
233
+ <FormGroup label="Kind">
234
+ <Select
235
+ value={draftKind}
236
+ disabled={busy}
237
+ onchange={(event) => {
238
+ draftKind = (event.currentTarget as HTMLSelectElement).value;
239
+ }}
240
+ >
241
+ {#each activityKinds as kind (kind)}
242
+ <option value={kind}>{kind}</option>
243
+ {/each}
244
+ </Select>
245
+ </FormGroup>
246
+ <FormGroup label="Summary" required>
247
+ <Input
248
+ value={draftSummary}
249
+ disabled={busy}
250
+ required
251
+ oninput={(event) => {
252
+ draftSummary = (event.currentTarget as HTMLInputElement).value;
253
+ }}
254
+ />
255
+ </FormGroup>
256
+ <FormGroup label="Due date" hint="Leave empty for a completed note.">
257
+ <Input
258
+ type="date"
259
+ value={draftDueAt}
260
+ disabled={busy}
261
+ oninput={(event) => {
262
+ draftDueAt = (event.currentTarget as HTMLInputElement).value;
263
+ }}
264
+ />
265
+ </FormGroup>
266
+ <Button
267
+ type="submit"
268
+ variant="secondary"
269
+ size="sm"
270
+ disabled={busy || draftSummary.trim() === ''}
271
+ >
272
+ Record activity
273
+ </Button>
274
+ </Form>
275
+ {/if}
276
+ </section>
277
+
278
+ {#if conversions.length > 0}
279
+ <section class="conversions" aria-label="Conversions">
280
+ <h3>Conversions</h3>
281
+ <ul class="conversions__list">
282
+ {#each conversions as conversion (conversion.id)}
283
+ <li>
284
+ <span class="conversions__kind">{conversion.targetKind}</span>
285
+ {#if conversion.href}
286
+ <a href={conversion.href}><code>{conversion.targetId}</code></a>
287
+ {:else}
288
+ <code>{conversion.targetId}</code>
289
+ {/if}
290
+ {#if conversion.note}
291
+ <span class="conversions__note">{conversion.note}</span>
292
+ {/if}
293
+ </li>
294
+ {/each}
295
+ </ul>
296
+ </section>
297
+ {/if}
298
+ </article>
299
+
300
+ <style>
301
+ .sales-opportunity-detail {
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: var(--smrt-spacing-4, 1rem);
305
+ }
306
+
307
+ .head {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: var(--smrt-spacing-2, 0.5rem);
311
+ }
312
+
313
+ .head__name {
314
+ margin: 0;
315
+ font-size: var(--smrt-typography-title-large-size, 1.25rem);
316
+ }
317
+
318
+ .facts {
319
+ display: flex;
320
+ flex-wrap: wrap;
321
+ gap: var(--smrt-spacing-4, 1rem);
322
+ margin: 0;
323
+ }
324
+
325
+ .facts__item dt {
326
+ color: var(--smrt-color-on-surface-variant, #64748b);
327
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
328
+ }
329
+
330
+ .facts__item dd {
331
+ margin: 0;
332
+ font-weight: var(--smrt-typography-weight-semibold, 600);
333
+ }
334
+
335
+ .outcome {
336
+ margin: 0;
337
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
338
+ border-radius: var(--smrt-radius-sm, 4px);
339
+ background: var(--smrt-color-surface-container, #f3f4f6);
340
+ }
341
+
342
+ .outcome--won {
343
+ background: var(--smrt-color-success-container, #dcfce7);
344
+ color: var(--smrt-color-on-success-container, #14532d);
345
+ }
346
+
347
+ .outcome--lost {
348
+ background: var(--smrt-color-error-container, #fee2e2);
349
+ color: var(--smrt-color-on-error-container, #7f1d1d);
350
+ }
351
+
352
+ .controls {
353
+ display: flex;
354
+ flex-wrap: wrap;
355
+ gap: var(--smrt-spacing-4, 1rem);
356
+ align-items: flex-end;
357
+ }
358
+
359
+ .controls__group {
360
+ display: flex;
361
+ align-items: flex-end;
362
+ gap: var(--smrt-spacing-2, 0.5rem);
363
+ }
364
+
365
+ .controls__buttons {
366
+ display: inline-flex;
367
+ gap: var(--smrt-spacing-2, 0.5rem);
368
+ }
369
+
370
+ /* The base FormGroup carries a bottom margin for stacked forms; inside these
371
+ inline action rows it would misalign the adjacent buttons. */
372
+ .controls__group :global(.form-group),
373
+ .sales-opportunity-detail :global(.record .form-group) {
374
+ margin-bottom: 0;
375
+ }
376
+
377
+ h3 {
378
+ margin: 0 0 var(--smrt-spacing-2, 0.5rem);
379
+ font-size: var(--smrt-typography-title-small-size, 0.875rem);
380
+ text-transform: uppercase;
381
+ letter-spacing: 0.04em;
382
+ color: var(--smrt-color-on-surface-variant, #64748b);
383
+ }
384
+
385
+ .empty {
386
+ margin: 0;
387
+ color: var(--smrt-color-on-surface-variant, #64748b);
388
+ font-style: italic;
389
+ }
390
+
391
+ .timeline__list {
392
+ margin: 0;
393
+ padding: 0;
394
+ list-style: none;
395
+ display: flex;
396
+ flex-direction: column;
397
+ gap: var(--smrt-spacing-2, 0.5rem);
398
+ }
399
+
400
+ .timeline__item {
401
+ display: flex;
402
+ flex-wrap: wrap;
403
+ align-items: baseline;
404
+ gap: var(--smrt-spacing-2, 0.5rem);
405
+ padding: var(--smrt-spacing-2, 0.5rem);
406
+ border: 1px solid var(--smrt-color-outline-variant, #d8dde6);
407
+ border-radius: var(--smrt-radius-sm, 4px);
408
+ }
409
+
410
+ .timeline__item.next-action {
411
+ border-color: var(--smrt-color-primary, #2563eb);
412
+ background: var(--smrt-color-primary-container, #eff6ff);
413
+ }
414
+
415
+ .timeline__kind {
416
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
417
+ text-transform: uppercase;
418
+ letter-spacing: 0.04em;
419
+ color: var(--smrt-color-on-surface-variant, #64748b);
420
+ }
421
+
422
+ .timeline__summary {
423
+ flex: 1 1 12rem;
424
+ }
425
+
426
+ .timeline__dates {
427
+ display: inline-flex;
428
+ gap: var(--smrt-spacing-2, 0.5rem);
429
+ color: var(--smrt-color-on-surface-variant, #64748b);
430
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
431
+ }
432
+
433
+ .timeline__dates .overdue {
434
+ color: var(--smrt-color-error, #dc2626);
435
+ font-weight: var(--smrt-typography-weight-medium, 500);
436
+ }
437
+
438
+ .sales-opportunity-detail :global(.record) {
439
+ display: flex;
440
+ flex-wrap: wrap;
441
+ align-items: flex-end;
442
+ gap: var(--smrt-spacing-2, 0.5rem);
443
+ margin-top: var(--smrt-spacing-3, 0.75rem);
444
+ }
445
+
446
+ .conversions__list {
447
+ margin: 0;
448
+ padding: 0;
449
+ list-style: none;
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: var(--smrt-spacing-1, 0.25rem);
453
+ }
454
+
455
+ .conversions__kind {
456
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
457
+ text-transform: uppercase;
458
+ letter-spacing: 0.04em;
459
+ color: var(--smrt-color-on-surface-variant, #64748b);
460
+ margin-right: var(--smrt-spacing-1, 0.25rem);
461
+ }
462
+
463
+ .conversions__note {
464
+ color: var(--smrt-color-on-surface-variant, #64748b);
465
+ margin-left: var(--smrt-spacing-1, 0.25rem);
466
+ }
467
+
468
+ code {
469
+ font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
470
+ font-size: var(--smrt-typography-body-small-size, 0.8125rem);
471
+ }
472
+ </style>
@@ -0,0 +1,27 @@
1
+ import type { ClosedOpportunityOutcome, ConversionLinkView, OpportunityDetailView, PipelineStageView, RecordActivityDraft, SalesActivityView } from '../types.js';
2
+ export interface Props {
3
+ /** The opportunity being worked. */
4
+ opportunity: OpportunityDetailView;
5
+ /** Stages of the opportunity's pipeline (for the move picker). */
6
+ stages?: PipelineStageView[];
7
+ /** Activity/next-action timeline rows, newest first by convention. */
8
+ activities?: SalesActivityView[];
9
+ /** Recorded downstream conversion links. */
10
+ conversions?: ConversionLinkView[];
11
+ /** Selectable activity kinds for the record-activity form. */
12
+ activityKinds?: string[];
13
+ /** Disable actions while a mutation is in flight. */
14
+ busy?: boolean;
15
+ /** BCP 47 locale for money/date formatting. */
16
+ locale?: string;
17
+ /** Move the opportunity to another stage. */
18
+ onMoveStage?: (stageId: string) => void;
19
+ /** Record a new activity / next action. */
20
+ onRecordActivity?: (draft: RecordActivityDraft) => void;
21
+ /** Close the opportunity with a terminal outcome. */
22
+ onClose?: (outcome: ClosedOpportunityOutcome, reason?: string) => void;
23
+ }
24
+ declare const OpportunityDetail: import("svelte").Component<Props, {}, "">;
25
+ type OpportunityDetail = ReturnType<typeof OpportunityDetail>;
26
+ export default OpportunityDetail;
27
+ //# sourceMappingURL=OpportunityDetail.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpportunityDetail.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/OpportunityDetail.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,KAAK;IACpB,oCAAoC;IACpC,WAAW,EAAE,qBAAqB,CAAC;IACnC,kEAAkE;IAClE,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B,sEAAsE;IACtE,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACjC,4CAA4C;IAC5C,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAyOD,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}