@happyvertical/smrt-commerce 0.30.0

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 (101) hide show
  1. package/AGENTS.md +44 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +146 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/ContractCollection.d.ts +87 -0
  8. package/dist/collections/ContractCollection.d.ts.map +1 -0
  9. package/dist/collections/CustomerCollection.d.ts +58 -0
  10. package/dist/collections/CustomerCollection.d.ts.map +1 -0
  11. package/dist/collections/FulfillmentCollection.d.ts +75 -0
  12. package/dist/collections/FulfillmentCollection.d.ts.map +1 -0
  13. package/dist/collections/InvoiceCollection.d.ts +162 -0
  14. package/dist/collections/InvoiceCollection.d.ts.map +1 -0
  15. package/dist/collections/InvoiceLineItemCollection.d.ts +90 -0
  16. package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -0
  17. package/dist/collections/PaymentAllocationCollection.d.ts +86 -0
  18. package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -0
  19. package/dist/collections/PaymentCollection.d.ts +96 -0
  20. package/dist/collections/PaymentCollection.d.ts.map +1 -0
  21. package/dist/collections/PaymentIntentCollection.d.ts +66 -0
  22. package/dist/collections/PaymentIntentCollection.d.ts.map +1 -0
  23. package/dist/collections/PayoutCollection.d.ts +47 -0
  24. package/dist/collections/PayoutCollection.d.ts.map +1 -0
  25. package/dist/collections/VendorCollection.d.ts +59 -0
  26. package/dist/collections/VendorCollection.d.ts.map +1 -0
  27. package/dist/collections/index.d.ts +15 -0
  28. package/dist/collections/index.d.ts.map +1 -0
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +5308 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/manifest.json +13852 -0
  34. package/dist/models/Contract.d.ts +425 -0
  35. package/dist/models/Contract.d.ts.map +1 -0
  36. package/dist/models/ContractLineItem.d.ts +92 -0
  37. package/dist/models/ContractLineItem.d.ts.map +1 -0
  38. package/dist/models/Customer.d.ts +98 -0
  39. package/dist/models/Customer.d.ts.map +1 -0
  40. package/dist/models/Fulfillment.d.ts +99 -0
  41. package/dist/models/Fulfillment.d.ts.map +1 -0
  42. package/dist/models/FulfillmentLineItem.d.ts +42 -0
  43. package/dist/models/FulfillmentLineItem.d.ts.map +1 -0
  44. package/dist/models/Invoice.d.ts +326 -0
  45. package/dist/models/Invoice.d.ts.map +1 -0
  46. package/dist/models/InvoiceLineItem.d.ts +120 -0
  47. package/dist/models/InvoiceLineItem.d.ts.map +1 -0
  48. package/dist/models/Payment.d.ts +269 -0
  49. package/dist/models/Payment.d.ts.map +1 -0
  50. package/dist/models/PaymentAllocation.d.ts +93 -0
  51. package/dist/models/PaymentAllocation.d.ts.map +1 -0
  52. package/dist/models/PaymentIntent.d.ts +341 -0
  53. package/dist/models/PaymentIntent.d.ts.map +1 -0
  54. package/dist/models/Payout.d.ts +200 -0
  55. package/dist/models/Payout.d.ts.map +1 -0
  56. package/dist/models/Vendor.d.ts +153 -0
  57. package/dist/models/Vendor.d.ts.map +1 -0
  58. package/dist/models/index.d.ts +17 -0
  59. package/dist/models/index.d.ts.map +1 -0
  60. package/dist/playground.d.ts +2 -0
  61. package/dist/playground.d.ts.map +1 -0
  62. package/dist/playground.js +108 -0
  63. package/dist/playground.js.map +1 -0
  64. package/dist/smrt-knowledge.json +5494 -0
  65. package/dist/svelte/components/InvoiceActions.svelte +191 -0
  66. package/dist/svelte/components/InvoiceActions.svelte.d.ts +26 -0
  67. package/dist/svelte/components/InvoiceActions.svelte.d.ts.map +1 -0
  68. package/dist/svelte/components/InvoiceCard.svelte +233 -0
  69. package/dist/svelte/components/InvoiceCard.svelte.d.ts +16 -0
  70. package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -0
  71. package/dist/svelte/components/InvoiceHeader.svelte +258 -0
  72. package/dist/svelte/components/InvoiceHeader.svelte.d.ts +26 -0
  73. package/dist/svelte/components/InvoiceHeader.svelte.d.ts.map +1 -0
  74. package/dist/svelte/components/InvoiceLineItems.svelte +322 -0
  75. package/dist/svelte/components/InvoiceLineItems.svelte.d.ts +24 -0
  76. package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -0
  77. package/dist/svelte/components/InvoiceTotals.svelte +193 -0
  78. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +27 -0
  79. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -0
  80. package/dist/svelte/components/UnbilledItems.svelte +355 -0
  81. package/dist/svelte/components/UnbilledItems.svelte.d.ts +18 -0
  82. package/dist/svelte/components/UnbilledItems.svelte.d.ts.map +1 -0
  83. package/dist/svelte/i18n.d.ts +19 -0
  84. package/dist/svelte/i18n.d.ts.map +1 -0
  85. package/dist/svelte/i18n.js +19 -0
  86. package/dist/svelte/index.d.ts +40 -0
  87. package/dist/svelte/index.d.ts.map +1 -0
  88. package/dist/svelte/index.js +43 -0
  89. package/dist/svelte/playground.d.ts +103 -0
  90. package/dist/svelte/playground.d.ts.map +1 -0
  91. package/dist/svelte/playground.js +103 -0
  92. package/dist/svelte/types.d.ts +47 -0
  93. package/dist/svelte/types.d.ts.map +1 -0
  94. package/dist/svelte/types.js +4 -0
  95. package/dist/types/index.d.ts +234 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/ui.d.ts +10 -0
  98. package/dist/ui.d.ts.map +1 -0
  99. package/dist/ui.js +85 -0
  100. package/dist/ui.js.map +1 -0
  101. package/package.json +87 -0
@@ -0,0 +1,258 @@
1
+ <script lang="ts">
2
+ /**
3
+ * InvoiceHeader - Invoice metadata display
4
+ *
5
+ * Shows invoice number, status, dates, and customer info.
6
+ */
7
+
8
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
9
+ import { M } from '../i18n.js';
10
+ import type { InvoiceStatus } from '../types.js';
11
+
12
+ const { t } = useI18n();
13
+
14
+ /** Props for InvoiceHeader component */
15
+ export interface Props {
16
+ /** Invoice number/reference */
17
+ invoiceNumber: string;
18
+ /** Current status */
19
+ status: InvoiceStatus;
20
+ /** Issue date */
21
+ issueDate: Date | string;
22
+ /** Due date */
23
+ dueDate?: Date | string | null;
24
+ /** Paid date */
25
+ paidDate?: Date | string | null;
26
+ /** Customer/client name */
27
+ customerName?: string;
28
+ /** Project name */
29
+ projectName?: string;
30
+ /** Allow editing */
31
+ editable?: boolean;
32
+ /** Called when status changes */
33
+ onstatuschange?: (status: InvoiceStatus) => void;
34
+ }
35
+
36
+ const {
37
+ invoiceNumber,
38
+ status,
39
+ issueDate,
40
+ dueDate,
41
+ paidDate,
42
+ customerName,
43
+ projectName,
44
+ editable = false,
45
+ onstatuschange,
46
+ }: Props = $props();
47
+
48
+ // Format date
49
+ function formatDate(date: Date | string | null | undefined): string {
50
+ if (!date) return 'N/A';
51
+ const d = typeof date === 'string' ? new Date(date) : date;
52
+ return new Intl.DateTimeFormat('en-CA', {
53
+ year: 'numeric',
54
+ month: 'short',
55
+ day: 'numeric',
56
+ }).format(d);
57
+ }
58
+
59
+ // Status display config - uses Material 3 color roles
60
+ const statusConfig: Record<
61
+ InvoiceStatus,
62
+ { label: string; bg: string; text: string }
63
+ > = {
64
+ draft: {
65
+ label: 'Draft',
66
+ bg: 'var(--smrt-color-surface-variant, #e7e0ec)',
67
+ text: 'var(--smrt-color-on-surface-variant, #49454f)',
68
+ },
69
+ sent: {
70
+ label: 'Sent',
71
+ bg: 'var(--smrt-color-primary-container, #d3e3fd)',
72
+ text: 'var(--smrt-color-on-primary-container, #041e49)',
73
+ },
74
+ viewed: {
75
+ label: 'Viewed',
76
+ bg: 'var(--smrt-color-secondary-container, #e3e0f9)',
77
+ text: 'var(--smrt-color-on-secondary-container, #1d192b)',
78
+ },
79
+ paid: {
80
+ label: 'Paid',
81
+ bg: 'var(--smrt-color-tertiary-container, #ddf5e5)',
82
+ text: 'var(--smrt-color-on-tertiary-container, #0c1f15)',
83
+ },
84
+ overdue: {
85
+ label: 'Overdue',
86
+ bg: 'var(--smrt-color-error-container, #ffdad6)',
87
+ text: 'var(--smrt-color-on-error-container, #410002)',
88
+ },
89
+ cancelled: {
90
+ label: 'Cancelled',
91
+ bg: 'var(--smrt-color-error-container, #ffdad6)',
92
+ text: 'var(--smrt-color-error, #ba1a1a)',
93
+ },
94
+ };
95
+
96
+ const statusInfo = $derived(statusConfig[status] ?? statusConfig.draft);
97
+
98
+ // Check if overdue
99
+ const isOverdue = $derived.by(() => {
100
+ if (status === 'paid' || status === 'cancelled') return false;
101
+ if (!dueDate) return false;
102
+ const due = typeof dueDate === 'string' ? new Date(dueDate) : dueDate;
103
+ return due < new Date();
104
+ });
105
+ </script>
106
+
107
+ <div class="invoice-header">
108
+ <div class="header-main">
109
+ <div class="invoice-title">
110
+ <h2 class="invoice-number">{invoiceNumber}</h2>
111
+ <span
112
+ class="status-badge"
113
+ style:background-color={isOverdue && status !== 'overdue' ? statusConfig.overdue.bg : statusInfo.bg}
114
+ style:color={isOverdue && status !== 'overdue' ? statusConfig.overdue.text : statusInfo.text}
115
+ >
116
+ {isOverdue && status !== 'overdue' ? 'Overdue' : statusInfo.label}
117
+ </span>
118
+ </div>
119
+
120
+ {#if customerName || projectName}
121
+ <div class="invoice-context">
122
+ {#if customerName}
123
+ <span class="context-item">
124
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
125
+ <circle cx="8" cy="5" r="3" />
126
+ <path d="M2 14v-1a4 4 0 014-4h4a4 4 0 014 4v1" />
127
+ </svg>
128
+ {customerName}
129
+ </span>
130
+ {/if}
131
+ {#if projectName}
132
+ <span class="context-item">
133
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
134
+ <path d="M2 4h12v10H2z" />
135
+ <path d="M5 4V2h6v2" />
136
+ </svg>
137
+ {projectName}
138
+ </span>
139
+ {/if}
140
+ </div>
141
+ {/if}
142
+ </div>
143
+
144
+ <div class="header-meta">
145
+ <div class="meta-item">
146
+ <span class="meta-label">{t(M['commerce.invoice_header.issue_date'])}</span>
147
+ <span class="meta-value">{formatDate(issueDate)}</span>
148
+ </div>
149
+
150
+ {#if dueDate}
151
+ <div class="meta-item" class:overdue={isOverdue}>
152
+ <span class="meta-label">{t(M['commerce.invoice_header.due_date'])}</span>
153
+ <span class="meta-value">{formatDate(dueDate)}</span>
154
+ </div>
155
+ {/if}
156
+
157
+ {#if paidDate && status === 'paid'}
158
+ <div class="meta-item paid">
159
+ <span class="meta-label">{t(M['commerce.invoice_header.paid_date'])}</span>
160
+ <span class="meta-value">{formatDate(paidDate)}</span>
161
+ </div>
162
+ {/if}
163
+ </div>
164
+ </div>
165
+
166
+ <style>
167
+ .invoice-header {
168
+ display: flex;
169
+ justify-content: space-between;
170
+ align-items: flex-start;
171
+ gap: var(--smrt-spacing-6, 1.5rem);
172
+ padding: var(--smrt-spacing-6, 1.5rem);
173
+ background: var(--smrt-color-surface, #ffffff);
174
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
175
+ border-radius: var(--smrt-radius-medium, 0.5rem);
176
+ flex-wrap: wrap;
177
+ }
178
+
179
+ .header-main {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: var(--smrt-spacing-2, 0.5rem);
183
+ }
184
+
185
+ .invoice-title {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: var(--smrt-spacing-3, 0.75rem);
189
+ }
190
+
191
+ .invoice-number {
192
+ font: var(--smrt-typography-title-large-font);
193
+ font-weight: var(--smrt-typography-weight-semibold, 600);
194
+ color: var(--smrt-color-on-surface, #1c1b1f);
195
+ margin: 0;
196
+ }
197
+
198
+ .status-badge {
199
+ display: inline-flex;
200
+ align-items: center;
201
+ padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-3, 0.75rem);
202
+ font: var(--smrt-typography-label-small-font);
203
+ font-weight: var(--smrt-typography-weight-medium, 500);
204
+ border-radius: var(--smrt-radius-full, 9999px);
205
+ text-transform: capitalize;
206
+ }
207
+
208
+ .invoice-context {
209
+ display: flex;
210
+ gap: var(--smrt-spacing-4, 1rem);
211
+ flex-wrap: wrap;
212
+ }
213
+
214
+ .context-item {
215
+ display: inline-flex;
216
+ align-items: center;
217
+ gap: var(--smrt-spacing-1_5, 0.375rem);
218
+ font: var(--smrt-typography-body-medium-font);
219
+ color: var(--smrt-color-on-surface-variant, #49454f);
220
+ }
221
+
222
+ .context-item svg {
223
+ flex-shrink: 0;
224
+ }
225
+
226
+ .header-meta {
227
+ display: flex;
228
+ gap: var(--smrt-spacing-6, 1.5rem);
229
+ flex-wrap: wrap;
230
+ }
231
+
232
+ .meta-item {
233
+ display: flex;
234
+ flex-direction: column;
235
+ gap: var(--smrt-spacing-0_5, 0.125rem);
236
+ }
237
+
238
+ .meta-label {
239
+ font: var(--smrt-typography-label-small-font);
240
+ color: var(--smrt-color-on-surface-variant, #49454f);
241
+ text-transform: uppercase;
242
+ letter-spacing: 0.025em;
243
+ }
244
+
245
+ .meta-value {
246
+ font: var(--smrt-typography-body-medium-font);
247
+ font-weight: var(--smrt-typography-weight-medium, 500);
248
+ color: var(--smrt-color-on-surface, #1c1b1f);
249
+ }
250
+
251
+ .meta-item.overdue .meta-value {
252
+ color: var(--smrt-color-error, #ba1a1a);
253
+ }
254
+
255
+ .meta-item.paid .meta-value {
256
+ color: var(--smrt-color-tertiary, #006c4c);
257
+ }
258
+ </style>
@@ -0,0 +1,26 @@
1
+ import type { InvoiceStatus } from '../types.js';
2
+ /** Props for InvoiceHeader component */
3
+ export interface Props {
4
+ /** Invoice number/reference */
5
+ invoiceNumber: string;
6
+ /** Current status */
7
+ status: InvoiceStatus;
8
+ /** Issue date */
9
+ issueDate: Date | string;
10
+ /** Due date */
11
+ dueDate?: Date | string | null;
12
+ /** Paid date */
13
+ paidDate?: Date | string | null;
14
+ /** Customer/client name */
15
+ customerName?: string;
16
+ /** Project name */
17
+ projectName?: string;
18
+ /** Allow editing */
19
+ editable?: boolean;
20
+ /** Called when status changes */
21
+ onstatuschange?: (status: InvoiceStatus) => void;
22
+ }
23
+ declare const InvoiceHeader: import("svelte").Component<Props, {}, "">;
24
+ type InvoiceHeader = ReturnType<typeof InvoiceHeader>;
25
+ export default InvoiceHeader;
26
+ //# sourceMappingURL=InvoiceHeader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvoiceHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceHeader.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,iBAAiB;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,eAAe;IACf,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;CAClD;AA6ID,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,322 @@
1
+ <script lang="ts">
2
+ /**
3
+ * InvoiceLineItems - Line items table for invoices/estimates
4
+ *
5
+ * Displays line items with optional inline editing capabilities.
6
+ * Supports both read-only and editable modes.
7
+ */
8
+
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import type { Snippet } from 'svelte';
11
+ import { M } from '../i18n.js';
12
+ import type { LineItem } from '../types.js';
13
+
14
+ const { t } = useI18n();
15
+
16
+ /** Props for InvoiceLineItems component */
17
+ export interface Props {
18
+ /** Line items to display */
19
+ items: LineItem[];
20
+ /** Enable editing mode */
21
+ editable?: boolean;
22
+ /** Currency code */
23
+ currency?: 'CAD' | 'USD';
24
+ /** Called when item is updated */
25
+ onupdate?: (id: string, item: Partial<LineItem>) => void;
26
+ /** Called when item is deleted */
27
+ ondelete?: (id: string) => void;
28
+ /** Called when add button is clicked */
29
+ onadd?: () => void;
30
+ /** Show source type column */
31
+ showSource?: boolean;
32
+ /** Empty state message */
33
+ emptyMessage?: string;
34
+ }
35
+
36
+ const {
37
+ items,
38
+ editable = false,
39
+ currency = 'CAD',
40
+ onupdate,
41
+ ondelete,
42
+ onadd,
43
+ showSource = false,
44
+ emptyMessage = 'No line items',
45
+ }: Props = $props();
46
+
47
+ // Format cents to dollars
48
+ function formatMoney(cents: number): string {
49
+ return new Intl.NumberFormat('en-CA', {
50
+ style: 'currency',
51
+ currency,
52
+ minimumFractionDigits: 2,
53
+ }).format(cents / 100);
54
+ }
55
+
56
+ // Source type labels
57
+ const sourceLabels: Record<string, string> = {
58
+ expense: 'Expense',
59
+ time: 'Time',
60
+ manual: 'Manual',
61
+ };
62
+
63
+ // Calculate total
64
+ const total = $derived(items.reduce((sum, item) => sum + item.amount, 0));
65
+ </script>
66
+
67
+ <div class="line-items-container">
68
+ {#if items.length === 0}
69
+ <div class="empty-state">
70
+ <p>{emptyMessage}</p>
71
+ {#if editable && onadd}
72
+ <button type="button" class="add-btn" onclick={onadd}>
73
+ {t(M['commerce.invoice_line_items.add_item'])}
74
+ </button>
75
+ {/if}
76
+ </div>
77
+ {:else}
78
+ <table class="line-items-table">
79
+ <thead>
80
+ <tr>
81
+ <th class="col-description">Description</th>
82
+ {#if showSource}
83
+ <th class="col-source">Source</th>
84
+ {/if}
85
+ <th class="col-qty">Qty</th>
86
+ <th class="col-price">{t(M['commerce.invoice_line_items.unit_price'])}</th>
87
+ <th class="col-amount">Amount</th>
88
+ {#if editable}
89
+ <th class="col-actions"></th>
90
+ {/if}
91
+ </tr>
92
+ </thead>
93
+ <tbody>
94
+ {#each items as item (item.id)}
95
+ <tr>
96
+ <td class="col-description">
97
+ {#if item.category}
98
+ <span class="item-category">{item.category}</span>
99
+ {/if}
100
+ <span class="item-description">{item.description}</span>
101
+ </td>
102
+ {#if showSource}
103
+ <td class="col-source">
104
+ <span class="source-badge source-{item.sourceType ?? 'manual'}">
105
+ {sourceLabels[item.sourceType ?? 'manual']}
106
+ </span>
107
+ </td>
108
+ {/if}
109
+ <td class="col-qty">{item.quantity}</td>
110
+ <td class="col-price">{formatMoney(item.unitPrice)}</td>
111
+ <td class="col-amount">{formatMoney(item.amount)}</td>
112
+ {#if editable}
113
+ <td class="col-actions">
114
+ <button
115
+ type="button"
116
+ class="action-btn delete"
117
+ title={t(M['commerce.invoice_line_items.remove_item'])}
118
+ onclick={() => ondelete?.(item.id)}
119
+ >
120
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
121
+ <path d="M4 4l8 8M4 12l8-8" />
122
+ </svg>
123
+ </button>
124
+ </td>
125
+ {/if}
126
+ </tr>
127
+ {/each}
128
+ </tbody>
129
+ <tfoot>
130
+ <tr class="total-row">
131
+ <td colspan={showSource ? 4 : 3} class="total-label">Subtotal</td>
132
+ <td class="col-amount">{formatMoney(total)}</td>
133
+ {#if editable}
134
+ <td></td>
135
+ {/if}
136
+ </tr>
137
+ </tfoot>
138
+ </table>
139
+
140
+ {#if editable && onadd}
141
+ <button type="button" class="add-btn add-btn-below" onclick={onadd}>
142
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
143
+ <path d="M8 3v10M3 8h10" />
144
+ </svg>
145
+ {t(M['commerce.invoice_line_items.add_item'])}
146
+ </button>
147
+ {/if}
148
+ {/if}
149
+ </div>
150
+
151
+ <style>
152
+ .line-items-container {
153
+ width: 100%;
154
+ }
155
+
156
+ .empty-state {
157
+ display: flex;
158
+ flex-direction: column;
159
+ align-items: center;
160
+ padding: var(--smrt-spacing-8, 2rem) var(--smrt-spacing-4, 1rem);
161
+ background: var(--smrt-color-surface-container-low, #f7f2fa);
162
+ border: 1px dashed var(--smrt-color-outline, #79747e);
163
+ border-radius: var(--smrt-radius-medium, 0.5rem);
164
+ text-align: center;
165
+ }
166
+
167
+ .empty-state p {
168
+ color: var(--smrt-color-on-surface-variant, #49454f);
169
+ margin: 0 0 var(--smrt-spacing-4, 1rem);
170
+ }
171
+
172
+ .line-items-table {
173
+ width: 100%;
174
+ border-collapse: collapse;
175
+ font: var(--smrt-typography-body-medium-font);
176
+ }
177
+
178
+ .line-items-table th {
179
+ text-align: left;
180
+ font-weight: var(--smrt-typography-weight-medium, 500);
181
+ color: var(--smrt-color-on-surface-variant, #49454f);
182
+ padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-2, 0.5rem);
183
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
184
+ }
185
+
186
+ .line-items-table td {
187
+ padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-2, 0.5rem);
188
+ border-bottom: 1px solid var(--smrt-color-surface-variant, #e7e0ec);
189
+ vertical-align: top;
190
+ }
191
+
192
+ .col-description {
193
+ width: 50%;
194
+ }
195
+
196
+ .col-source {
197
+ width: 80px;
198
+ }
199
+
200
+ .col-qty {
201
+ width: 60px;
202
+ text-align: right;
203
+ }
204
+
205
+ .col-price,
206
+ .col-amount {
207
+ width: 100px;
208
+ text-align: right;
209
+ font-variant-numeric: tabular-nums;
210
+ }
211
+
212
+ .col-actions {
213
+ width: 40px;
214
+ text-align: center;
215
+ }
216
+
217
+ .item-category {
218
+ display: block;
219
+ font: var(--smrt-typography-label-small-font);
220
+ color: var(--smrt-color-on-surface-variant, #49454f);
221
+ text-transform: uppercase;
222
+ letter-spacing: 0.025em;
223
+ }
224
+
225
+ .item-description {
226
+ color: var(--smrt-color-on-surface, #1c1b1f);
227
+ }
228
+
229
+ .source-badge {
230
+ display: inline-block;
231
+ padding: var(--smrt-spacing-0_5, 0.125rem) var(--smrt-spacing-2, 0.5rem);
232
+ font: var(--smrt-typography-label-small-font);
233
+ font-weight: var(--smrt-typography-weight-medium, 500);
234
+ border-radius: var(--smrt-radius-full, 9999px);
235
+ }
236
+
237
+ .source-expense {
238
+ background: var(--smrt-color-tertiary-container, #ddf5e5);
239
+ color: var(--smrt-color-on-tertiary-container, #0c1f15);
240
+ }
241
+
242
+ .source-time {
243
+ background: var(--smrt-color-primary-container, #d3e3fd);
244
+ color: var(--smrt-color-on-primary-container, #041e49);
245
+ }
246
+
247
+ .source-manual {
248
+ background: var(--smrt-color-surface-variant, #e7e0ec);
249
+ color: var(--smrt-color-on-surface-variant, #49454f);
250
+ }
251
+
252
+ .total-row td {
253
+ border-top: 2px solid var(--smrt-color-outline-variant, #c4c6d0);
254
+ border-bottom: none;
255
+ font-weight: var(--smrt-typography-weight-semibold, 600);
256
+ padding-top: var(--smrt-spacing-4, 1rem);
257
+ }
258
+
259
+ .total-label {
260
+ text-align: right;
261
+ color: var(--smrt-color-on-surface, #1c1b1f);
262
+ }
263
+
264
+ .action-btn {
265
+ display: inline-flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ width: 28px;
269
+ height: 28px;
270
+ background: transparent;
271
+ border: none;
272
+ border-radius: var(--smrt-radius-extra-small, 0.25rem);
273
+ cursor: pointer;
274
+ color: var(--smrt-color-on-surface-variant, #49454f);
275
+ transition: all var(--smrt-duration-fast, 150ms) var(--smrt-easing-standard, cubic-bezier(0.2, 0, 0, 1));
276
+ }
277
+
278
+ @media (prefers-reduced-motion: reduce) {
279
+ .action-btn {
280
+ transition: none;
281
+ }
282
+ }
283
+
284
+ .action-btn:hover {
285
+ background: var(--smrt-color-surface-variant, #e7e0ec);
286
+ color: var(--smrt-color-on-surface, #1c1b1f);
287
+ }
288
+
289
+ .action-btn.delete:hover {
290
+ background: var(--smrt-color-error-container, #ffdad6);
291
+ color: var(--smrt-color-error, #ba1a1a);
292
+ }
293
+
294
+ .add-btn {
295
+ display: inline-flex;
296
+ align-items: center;
297
+ gap: var(--smrt-spacing-2, 0.5rem);
298
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-4, 1rem);
299
+ font: var(--smrt-typography-label-large-font);
300
+ font-weight: var(--smrt-typography-weight-medium, 500);
301
+ color: var(--smrt-color-primary, #005ac1);
302
+ background: transparent;
303
+ border: 1px dashed var(--smrt-color-primary, #005ac1);
304
+ border-radius: var(--smrt-radius-small, 0.375rem);
305
+ cursor: pointer;
306
+ transition: all var(--smrt-duration-fast, 150ms) var(--smrt-easing-standard, cubic-bezier(0.2, 0, 0, 1));
307
+ }
308
+
309
+ @media (prefers-reduced-motion: reduce) {
310
+ .add-btn {
311
+ transition: none;
312
+ }
313
+ }
314
+
315
+ .add-btn:hover {
316
+ background: var(--smrt-color-primary-container, #d3e3fd);
317
+ }
318
+
319
+ .add-btn-below {
320
+ margin-top: var(--smrt-spacing-3, 0.75rem);
321
+ }
322
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { LineItem } from '../types.js';
2
+ /** Props for InvoiceLineItems component */
3
+ export interface Props {
4
+ /** Line items to display */
5
+ items: LineItem[];
6
+ /** Enable editing mode */
7
+ editable?: boolean;
8
+ /** Currency code */
9
+ currency?: 'CAD' | 'USD';
10
+ /** Called when item is updated */
11
+ onupdate?: (id: string, item: Partial<LineItem>) => void;
12
+ /** Called when item is deleted */
13
+ ondelete?: (id: string) => void;
14
+ /** Called when add button is clicked */
15
+ onadd?: () => void;
16
+ /** Show source type column */
17
+ showSource?: boolean;
18
+ /** Empty state message */
19
+ emptyMessage?: string;
20
+ }
21
+ declare const InvoiceLineItems: import("svelte").Component<Props, {}, "">;
22
+ type InvoiceLineItems = ReturnType<typeof InvoiceLineItems>;
23
+ export default InvoiceLineItems;
24
+ //# sourceMappingURL=InvoiceLineItems.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvoiceLineItems.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineItems.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,2CAA2C;AAC3C,MAAM,WAAW,KAAK;IACpB,4BAA4B;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8HD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}