@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.
- package/AGENTS.md +44 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +146 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/collections/ContractCollection.d.ts +87 -0
- package/dist/collections/ContractCollection.d.ts.map +1 -0
- package/dist/collections/CustomerCollection.d.ts +58 -0
- package/dist/collections/CustomerCollection.d.ts.map +1 -0
- package/dist/collections/FulfillmentCollection.d.ts +75 -0
- package/dist/collections/FulfillmentCollection.d.ts.map +1 -0
- package/dist/collections/InvoiceCollection.d.ts +162 -0
- package/dist/collections/InvoiceCollection.d.ts.map +1 -0
- package/dist/collections/InvoiceLineItemCollection.d.ts +90 -0
- package/dist/collections/InvoiceLineItemCollection.d.ts.map +1 -0
- package/dist/collections/PaymentAllocationCollection.d.ts +86 -0
- package/dist/collections/PaymentAllocationCollection.d.ts.map +1 -0
- package/dist/collections/PaymentCollection.d.ts +96 -0
- package/dist/collections/PaymentCollection.d.ts.map +1 -0
- package/dist/collections/PaymentIntentCollection.d.ts +66 -0
- package/dist/collections/PaymentIntentCollection.d.ts.map +1 -0
- package/dist/collections/PayoutCollection.d.ts +47 -0
- package/dist/collections/PayoutCollection.d.ts.map +1 -0
- package/dist/collections/VendorCollection.d.ts +59 -0
- package/dist/collections/VendorCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +15 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5308 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +13852 -0
- package/dist/models/Contract.d.ts +425 -0
- package/dist/models/Contract.d.ts.map +1 -0
- package/dist/models/ContractLineItem.d.ts +92 -0
- package/dist/models/ContractLineItem.d.ts.map +1 -0
- package/dist/models/Customer.d.ts +98 -0
- package/dist/models/Customer.d.ts.map +1 -0
- package/dist/models/Fulfillment.d.ts +99 -0
- package/dist/models/Fulfillment.d.ts.map +1 -0
- package/dist/models/FulfillmentLineItem.d.ts +42 -0
- package/dist/models/FulfillmentLineItem.d.ts.map +1 -0
- package/dist/models/Invoice.d.ts +326 -0
- package/dist/models/Invoice.d.ts.map +1 -0
- package/dist/models/InvoiceLineItem.d.ts +120 -0
- package/dist/models/InvoiceLineItem.d.ts.map +1 -0
- package/dist/models/Payment.d.ts +269 -0
- package/dist/models/Payment.d.ts.map +1 -0
- package/dist/models/PaymentAllocation.d.ts +93 -0
- package/dist/models/PaymentAllocation.d.ts.map +1 -0
- package/dist/models/PaymentIntent.d.ts +341 -0
- package/dist/models/PaymentIntent.d.ts.map +1 -0
- package/dist/models/Payout.d.ts +200 -0
- package/dist/models/Payout.d.ts.map +1 -0
- package/dist/models/Vendor.d.ts +153 -0
- package/dist/models/Vendor.d.ts.map +1 -0
- package/dist/models/index.d.ts +17 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +108 -0
- package/dist/playground.js.map +1 -0
- package/dist/smrt-knowledge.json +5494 -0
- package/dist/svelte/components/InvoiceActions.svelte +191 -0
- package/dist/svelte/components/InvoiceActions.svelte.d.ts +26 -0
- package/dist/svelte/components/InvoiceActions.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceCard.svelte +233 -0
- package/dist/svelte/components/InvoiceCard.svelte.d.ts +16 -0
- package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceHeader.svelte +258 -0
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts +26 -0
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceLineItems.svelte +322 -0
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts +24 -0
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceTotals.svelte +193 -0
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts +27 -0
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UnbilledItems.svelte +355 -0
- package/dist/svelte/components/UnbilledItems.svelte.d.ts +18 -0
- package/dist/svelte/components/UnbilledItems.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +19 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +19 -0
- package/dist/svelte/index.d.ts +40 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +43 -0
- package/dist/svelte/playground.d.ts +103 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +103 -0
- package/dist/svelte/types.d.ts +47 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +4 -0
- package/dist/types/index.d.ts +234 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +85 -0
- package/dist/ui.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* InvoiceTotals - Totals section for invoices/estimates
|
|
4
|
+
*
|
|
5
|
+
* Displays subtotal, tax, total, and optional amount paid/balance due.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
|
+
import { M } from '../i18n.js';
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n();
|
|
12
|
+
|
|
13
|
+
/** Props for InvoiceTotals component */
|
|
14
|
+
export interface Props {
|
|
15
|
+
/** Subtotal in cents */
|
|
16
|
+
subtotal: number;
|
|
17
|
+
/** Tax rate as percentage (e.g., 5 for 5%) */
|
|
18
|
+
taxRate?: number;
|
|
19
|
+
/** Tax amount in cents (calculated from rate if not provided) */
|
|
20
|
+
taxAmount?: number;
|
|
21
|
+
/** Total in cents */
|
|
22
|
+
total: number;
|
|
23
|
+
/** Amount already paid in cents */
|
|
24
|
+
amountPaid?: number;
|
|
25
|
+
/** Currency code */
|
|
26
|
+
currency?: 'CAD' | 'USD';
|
|
27
|
+
/** Show tax breakdown */
|
|
28
|
+
showTax?: boolean;
|
|
29
|
+
/** Show payment status */
|
|
30
|
+
showPaid?: boolean;
|
|
31
|
+
/** Tax label */
|
|
32
|
+
taxLabel?: string;
|
|
33
|
+
/** Size variant */
|
|
34
|
+
size?: 'sm' | 'md' | 'lg';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
subtotal,
|
|
39
|
+
taxRate = 0,
|
|
40
|
+
taxAmount,
|
|
41
|
+
total,
|
|
42
|
+
amountPaid = 0,
|
|
43
|
+
currency = 'CAD',
|
|
44
|
+
showTax = true,
|
|
45
|
+
showPaid = false,
|
|
46
|
+
taxLabel = 'GST',
|
|
47
|
+
size = 'md',
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
// Calculate tax if not provided
|
|
51
|
+
const calculatedTax = $derived(
|
|
52
|
+
taxAmount ?? Math.round(subtotal * (taxRate / 100)),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Calculate balance due
|
|
56
|
+
const balanceDue = $derived(total - amountPaid);
|
|
57
|
+
|
|
58
|
+
// Format cents to dollars
|
|
59
|
+
function formatMoney(cents: number): string {
|
|
60
|
+
return new Intl.NumberFormat('en-CA', {
|
|
61
|
+
style: 'currency',
|
|
62
|
+
currency,
|
|
63
|
+
minimumFractionDigits: 2,
|
|
64
|
+
}).format(cents / 100);
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div class="invoice-totals" class:sm={size === 'sm'} class:lg={size === 'lg'}>
|
|
69
|
+
<div class="totals-row">
|
|
70
|
+
<span class="totals-label">Subtotal</span>
|
|
71
|
+
<span class="totals-value">{formatMoney(subtotal)}</span>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{#if showTax && (taxRate > 0 || calculatedTax > 0)}
|
|
75
|
+
<div class="totals-row">
|
|
76
|
+
<span class="totals-label">{taxLabel} ({taxRate}%)</span>
|
|
77
|
+
<span class="totals-value">{formatMoney(calculatedTax)}</span>
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
<div class="totals-row total">
|
|
82
|
+
<span class="totals-label">Total</span>
|
|
83
|
+
<span class="totals-value">{formatMoney(total)}</span>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{#if showPaid && amountPaid > 0}
|
|
87
|
+
<div class="totals-row paid">
|
|
88
|
+
<span class="totals-label">{t(M['commerce.invoice_totals.amount_paid'])}</span>
|
|
89
|
+
<span class="totals-value">-{formatMoney(amountPaid)}</span>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="totals-row balance" class:due={balanceDue > 0} class:credit={balanceDue < 0}>
|
|
93
|
+
<span class="totals-label">
|
|
94
|
+
{#if balanceDue > 0}
|
|
95
|
+
{t(M['commerce.invoice_totals.balance_due'])}
|
|
96
|
+
{:else if balanceDue < 0}
|
|
97
|
+
{t(M['commerce.invoice_totals.credit'])}
|
|
98
|
+
{:else}
|
|
99
|
+
{t(M['commerce.invoice_totals.paid_in_full'])}
|
|
100
|
+
{/if}
|
|
101
|
+
</span>
|
|
102
|
+
<span class="totals-value">{formatMoney(Math.abs(balanceDue))}</span>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<style>
|
|
108
|
+
.invoice-totals {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: var(--smrt-spacing-2, 0.5rem);
|
|
112
|
+
width: 100%;
|
|
113
|
+
max-width: 280px;
|
|
114
|
+
margin-left: auto;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.invoice-totals.sm {
|
|
118
|
+
max-width: 220px;
|
|
119
|
+
gap: var(--smrt-spacing-1_5, 0.375rem);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.invoice-totals.lg {
|
|
123
|
+
max-width: 320px;
|
|
124
|
+
gap: var(--smrt-spacing-3, 0.75rem);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.totals-row {
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: space-between;
|
|
130
|
+
align-items: center;
|
|
131
|
+
font: var(--smrt-typography-body-medium-font);
|
|
132
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.sm .totals-row {
|
|
136
|
+
font: var(--smrt-typography-body-small-font);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.lg .totals-row {
|
|
140
|
+
font: var(--smrt-typography-body-large-font);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.totals-label {
|
|
144
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.totals-value {
|
|
148
|
+
font-variant-numeric: tabular-nums;
|
|
149
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
150
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.totals-row.total {
|
|
154
|
+
padding-top: var(--smrt-spacing-2, 0.5rem);
|
|
155
|
+
border-top: 2px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
156
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.totals-row.total .totals-label,
|
|
160
|
+
.totals-row.total .totals-value {
|
|
161
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
162
|
+
font: var(--smrt-typography-title-medium-font);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.lg .totals-row.total .totals-label,
|
|
166
|
+
.lg .totals-row.total .totals-value {
|
|
167
|
+
font: var(--smrt-typography-title-large-font);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.totals-row.paid {
|
|
171
|
+
color: var(--smrt-color-tertiary, #006c4c);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.totals-row.paid .totals-label {
|
|
175
|
+
color: var(--smrt-color-tertiary, #006c4c);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.totals-row.balance {
|
|
179
|
+
padding-top: var(--smrt-spacing-2, 0.5rem);
|
|
180
|
+
border-top: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
181
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.totals-row.balance.due .totals-label,
|
|
185
|
+
.totals-row.balance.due .totals-value {
|
|
186
|
+
color: var(--smrt-color-error, #ba1a1a);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.totals-row.balance.credit .totals-label,
|
|
190
|
+
.totals-row.balance.credit .totals-value {
|
|
191
|
+
color: var(--smrt-color-tertiary, #006c4c);
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Props for InvoiceTotals component */
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** Subtotal in cents */
|
|
4
|
+
subtotal: number;
|
|
5
|
+
/** Tax rate as percentage (e.g., 5 for 5%) */
|
|
6
|
+
taxRate?: number;
|
|
7
|
+
/** Tax amount in cents (calculated from rate if not provided) */
|
|
8
|
+
taxAmount?: number;
|
|
9
|
+
/** Total in cents */
|
|
10
|
+
total: number;
|
|
11
|
+
/** Amount already paid in cents */
|
|
12
|
+
amountPaid?: number;
|
|
13
|
+
/** Currency code */
|
|
14
|
+
currency?: 'CAD' | 'USD';
|
|
15
|
+
/** Show tax breakdown */
|
|
16
|
+
showTax?: boolean;
|
|
17
|
+
/** Show payment status */
|
|
18
|
+
showPaid?: boolean;
|
|
19
|
+
/** Tax label */
|
|
20
|
+
taxLabel?: string;
|
|
21
|
+
/** Size variant */
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
}
|
|
24
|
+
declare const InvoiceTotals: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type InvoiceTotals = ReturnType<typeof InvoiceTotals>;
|
|
26
|
+
export default InvoiceTotals;
|
|
27
|
+
//# sourceMappingURL=InvoiceTotals.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvoiceTotals.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceTotals.svelte.ts"],"names":[],"mappings":"AAYA,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAoFD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* UnbilledItems - Selectable list of unbilled expenses/time
|
|
4
|
+
*
|
|
5
|
+
* Shows unbilled items with checkbox selection for invoice creation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
|
+
import { M } from '../i18n.js';
|
|
10
|
+
import type { UnbilledItem } from '../types.js';
|
|
11
|
+
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
|
|
14
|
+
/** Props for UnbilledItems component */
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** Unbilled items */
|
|
17
|
+
items: UnbilledItem[];
|
|
18
|
+
/** Currency code */
|
|
19
|
+
currency?: 'CAD' | 'USD';
|
|
20
|
+
/** Called when selection changes */
|
|
21
|
+
onselectionchange?: (selectedIds: string[]) => void;
|
|
22
|
+
/** Called when create invoice is clicked */
|
|
23
|
+
oncreate?: (selectedIds: string[]) => void;
|
|
24
|
+
/** Empty state message */
|
|
25
|
+
emptyMessage?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
items = $bindable([]),
|
|
30
|
+
currency = 'CAD',
|
|
31
|
+
onselectionchange,
|
|
32
|
+
oncreate,
|
|
33
|
+
emptyMessage = 'No unbilled items',
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
// Track selection state
|
|
37
|
+
let selectedIds = $state<Set<string>>(
|
|
38
|
+
new Set(items.filter((i) => i.selected).map((i) => i.id)),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Update parent when selection changes
|
|
42
|
+
$effect(() => {
|
|
43
|
+
onselectionchange?.([...selectedIds]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Format money
|
|
47
|
+
function formatMoney(cents: number): string {
|
|
48
|
+
return new Intl.NumberFormat('en-CA', {
|
|
49
|
+
style: 'currency',
|
|
50
|
+
currency,
|
|
51
|
+
minimumFractionDigits: 2,
|
|
52
|
+
}).format(cents / 100);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Format date
|
|
56
|
+
function formatDate(date: Date | string): string {
|
|
57
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
58
|
+
return new Intl.DateTimeFormat('en-CA', {
|
|
59
|
+
month: 'short',
|
|
60
|
+
day: 'numeric',
|
|
61
|
+
}).format(d);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Toggle item selection
|
|
65
|
+
function toggleItem(id: string) {
|
|
66
|
+
if (selectedIds.has(id)) {
|
|
67
|
+
selectedIds.delete(id);
|
|
68
|
+
} else {
|
|
69
|
+
selectedIds.add(id);
|
|
70
|
+
}
|
|
71
|
+
selectedIds = new Set(selectedIds); // Trigger reactivity
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Toggle all items
|
|
75
|
+
function toggleAll() {
|
|
76
|
+
if (selectedIds.size === items.length) {
|
|
77
|
+
selectedIds = new Set();
|
|
78
|
+
} else {
|
|
79
|
+
selectedIds = new Set(items.map((i) => i.id));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Calculate selected total
|
|
84
|
+
const selectedTotal = $derived(
|
|
85
|
+
items
|
|
86
|
+
.filter((i) => selectedIds.has(i.id))
|
|
87
|
+
.reduce((sum, i) => sum + i.amount, 0),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// All selected
|
|
91
|
+
const allSelected = $derived(
|
|
92
|
+
selectedIds.size === items.length && items.length > 0,
|
|
93
|
+
);
|
|
94
|
+
const someSelected = $derived(
|
|
95
|
+
selectedIds.size > 0 && selectedIds.size < items.length,
|
|
96
|
+
);
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<div class="unbilled-items">
|
|
100
|
+
{#if items.length === 0}
|
|
101
|
+
<div class="empty-state">
|
|
102
|
+
<p>{emptyMessage}</p>
|
|
103
|
+
</div>
|
|
104
|
+
{:else}
|
|
105
|
+
<div class="items-header">
|
|
106
|
+
<label class="select-all">
|
|
107
|
+
<input
|
|
108
|
+
type="checkbox"
|
|
109
|
+
checked={allSelected}
|
|
110
|
+
indeterminate={someSelected}
|
|
111
|
+
onchange={toggleAll}
|
|
112
|
+
/>
|
|
113
|
+
<span>{t(M['commerce.unbilled_items.select_all'])}</span>
|
|
114
|
+
</label>
|
|
115
|
+
<span class="selected-count">
|
|
116
|
+
{t(M['commerce.unbilled_items.selected_count'], { selected: selectedIds.size, total: items.length })}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="items-list">
|
|
121
|
+
{#each items as item (item.id)}
|
|
122
|
+
<label class="item-row" class:selected={selectedIds.has(item.id)}>
|
|
123
|
+
<input
|
|
124
|
+
type="checkbox"
|
|
125
|
+
checked={selectedIds.has(item.id)}
|
|
126
|
+
onchange={() => toggleItem(item.id)}
|
|
127
|
+
/>
|
|
128
|
+
<div class="item-content">
|
|
129
|
+
<div class="item-main">
|
|
130
|
+
<span class="item-type type-{item.type}">{item.type}</span>
|
|
131
|
+
<span class="item-description">{item.description}</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="item-meta">
|
|
134
|
+
{#if item.category}
|
|
135
|
+
<span class="item-category">{item.category}</span>
|
|
136
|
+
{/if}
|
|
137
|
+
<span class="item-date">{formatDate(item.date)}</span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<span class="item-amount">{formatMoney(item.amount)}</span>
|
|
141
|
+
</label>
|
|
142
|
+
{/each}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{#if selectedIds.size > 0}
|
|
146
|
+
<div class="items-footer">
|
|
147
|
+
<div class="selected-summary">
|
|
148
|
+
<span class="summary-label">{t(M['commerce.unbilled_items.selected_total'])}</span>
|
|
149
|
+
<span class="summary-value">{formatMoney(selectedTotal)}</span>
|
|
150
|
+
</div>
|
|
151
|
+
{#if oncreate}
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
class="create-btn"
|
|
155
|
+
onclick={() => oncreate?.([...selectedIds])}
|
|
156
|
+
>
|
|
157
|
+
{t(M['commerce.unbilled_items.create_invoice'])}
|
|
158
|
+
</button>
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|
|
161
|
+
{/if}
|
|
162
|
+
{/if}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<style>
|
|
166
|
+
.unbilled-items {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
border: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
170
|
+
border-radius: var(--smrt-radius-medium, 0.5rem);
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.empty-state {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: center;
|
|
178
|
+
padding: var(--smrt-spacing-8, 2rem) var(--smrt-spacing-4, 1rem);
|
|
179
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.empty-state p {
|
|
183
|
+
margin: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.items-header {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: space-between;
|
|
189
|
+
align-items: center;
|
|
190
|
+
padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
|
|
191
|
+
background: var(--smrt-color-surface-container-low, #f7f2fa);
|
|
192
|
+
border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.select-all {
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: var(--smrt-spacing-2, 0.5rem);
|
|
199
|
+
font: var(--smrt-typography-body-medium-font);
|
|
200
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
201
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.selected-count {
|
|
206
|
+
font: var(--smrt-typography-label-small-font);
|
|
207
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.items-list {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
max-height: 400px;
|
|
214
|
+
overflow-y: auto;
|
|
215
|
+
background: var(--smrt-color-surface, #ffffff);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.item-row {
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
gap: var(--smrt-spacing-3, 0.75rem);
|
|
222
|
+
padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
|
|
223
|
+
border-bottom: 1px solid var(--smrt-color-surface-variant, #e7e0ec);
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
transition: background var(--smrt-duration-fast, 150ms) var(--smrt-easing-standard, cubic-bezier(0.2, 0, 0, 1));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@media (prefers-reduced-motion: reduce) {
|
|
229
|
+
.item-row {
|
|
230
|
+
transition: none;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.item-row:hover {
|
|
235
|
+
background: var(--smrt-color-surface-container-low, #f7f2fa);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.item-row.selected {
|
|
239
|
+
background: var(--smrt-color-primary-container, #d3e3fd);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.item-row:last-child {
|
|
243
|
+
border-bottom: none;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.item-content {
|
|
247
|
+
flex: 1;
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
gap: var(--smrt-spacing-1, 0.25rem);
|
|
251
|
+
min-width: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.item-main {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: var(--smrt-spacing-2, 0.5rem);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.item-type {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
padding: var(--smrt-spacing-0_5, 0.125rem) var(--smrt-spacing-2, 0.5rem);
|
|
263
|
+
font: var(--smrt-typography-label-small-font);
|
|
264
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
265
|
+
text-transform: uppercase;
|
|
266
|
+
letter-spacing: 0.025em;
|
|
267
|
+
border-radius: var(--smrt-radius-full, 9999px);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.type-expense {
|
|
271
|
+
background: var(--smrt-color-tertiary-container, #ddf5e5);
|
|
272
|
+
color: var(--smrt-color-on-tertiary-container, #0c1f15);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.type-time {
|
|
276
|
+
background: var(--smrt-color-primary-container, #d3e3fd);
|
|
277
|
+
color: var(--smrt-color-on-primary-container, #041e49);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.item-description {
|
|
281
|
+
font: var(--smrt-typography-body-medium-font);
|
|
282
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
text-overflow: ellipsis;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.item-meta {
|
|
289
|
+
display: flex;
|
|
290
|
+
gap: var(--smrt-spacing-3, 0.75rem);
|
|
291
|
+
font: var(--smrt-typography-body-small-font);
|
|
292
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.item-category {
|
|
296
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.item-amount {
|
|
300
|
+
font: var(--smrt-typography-body-medium-font);
|
|
301
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
302
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
303
|
+
font-variant-numeric: tabular-nums;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.items-footer {
|
|
307
|
+
display: flex;
|
|
308
|
+
justify-content: space-between;
|
|
309
|
+
align-items: center;
|
|
310
|
+
padding: var(--smrt-spacing-4, 1rem);
|
|
311
|
+
background: var(--smrt-color-surface-container-low, #f7f2fa);
|
|
312
|
+
border-top: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.selected-summary {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: baseline;
|
|
318
|
+
gap: var(--smrt-spacing-2, 0.5rem);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.summary-label {
|
|
322
|
+
font: var(--smrt-typography-body-medium-font);
|
|
323
|
+
color: var(--smrt-color-on-surface-variant, #49454f);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.summary-value {
|
|
327
|
+
font: var(--smrt-typography-title-large-font);
|
|
328
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
329
|
+
color: var(--smrt-color-on-surface, #1c1b1f);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.create-btn {
|
|
333
|
+
display: inline-flex;
|
|
334
|
+
align-items: center;
|
|
335
|
+
padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-4, 1rem);
|
|
336
|
+
font: var(--smrt-typography-label-large-font);
|
|
337
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
338
|
+
color: var(--smrt-color-on-primary, #ffffff);
|
|
339
|
+
background: var(--smrt-color-primary, #005ac1);
|
|
340
|
+
border: none;
|
|
341
|
+
border-radius: var(--smrt-radius-small, 0.375rem);
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
transition: background var(--smrt-duration-fast, 150ms) var(--smrt-easing-standard, cubic-bezier(0.2, 0, 0, 1));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@media (prefers-reduced-motion: reduce) {
|
|
347
|
+
.create-btn {
|
|
348
|
+
transition: none;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.create-btn:hover {
|
|
353
|
+
background: color-mix(in srgb, var(--smrt-color-primary, #005ac1) 85%, var(--smrt-color-shadow, #000));
|
|
354
|
+
}
|
|
355
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UnbilledItem } from '../types.js';
|
|
2
|
+
/** Props for UnbilledItems component */
|
|
3
|
+
export interface Props {
|
|
4
|
+
/** Unbilled items */
|
|
5
|
+
items: UnbilledItem[];
|
|
6
|
+
/** Currency code */
|
|
7
|
+
currency?: 'CAD' | 'USD';
|
|
8
|
+
/** Called when selection changes */
|
|
9
|
+
onselectionchange?: (selectedIds: string[]) => void;
|
|
10
|
+
/** Called when create invoice is clicked */
|
|
11
|
+
oncreate?: (selectedIds: string[]) => void;
|
|
12
|
+
/** Empty state message */
|
|
13
|
+
emptyMessage?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const UnbilledItems: import("svelte").Component<Props, {}, "items">;
|
|
16
|
+
type UnbilledItems = ReturnType<typeof UnbilledItems>;
|
|
17
|
+
export default UnbilledItems;
|
|
18
|
+
//# sourceMappingURL=UnbilledItems.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnbilledItems.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/UnbilledItems.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,qBAAqB;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2ID,QAAA,MAAM,aAAa,gDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const M: {
|
|
2
|
+
readonly 'commerce.invoice_actions.send_invoice': "commerce.invoice_actions.send_invoice";
|
|
3
|
+
readonly 'commerce.invoice_actions.mark_as_paid': "commerce.invoice_actions.mark_as_paid";
|
|
4
|
+
readonly 'commerce.invoice_header.issue_date': "commerce.invoice_header.issue_date";
|
|
5
|
+
readonly 'commerce.invoice_header.due_date': "commerce.invoice_header.due_date";
|
|
6
|
+
readonly 'commerce.invoice_header.paid_date': "commerce.invoice_header.paid_date";
|
|
7
|
+
readonly 'commerce.invoice_line_items.add_item': "commerce.invoice_line_items.add_item";
|
|
8
|
+
readonly 'commerce.invoice_line_items.unit_price': "commerce.invoice_line_items.unit_price";
|
|
9
|
+
readonly 'commerce.invoice_line_items.remove_item': "commerce.invoice_line_items.remove_item";
|
|
10
|
+
readonly 'commerce.invoice_totals.amount_paid': "commerce.invoice_totals.amount_paid";
|
|
11
|
+
readonly 'commerce.invoice_totals.balance_due': "commerce.invoice_totals.balance_due";
|
|
12
|
+
readonly 'commerce.invoice_totals.credit': "commerce.invoice_totals.credit";
|
|
13
|
+
readonly 'commerce.invoice_totals.paid_in_full': "commerce.invoice_totals.paid_in_full";
|
|
14
|
+
readonly 'commerce.unbilled_items.select_all': "commerce.unbilled_items.select_all";
|
|
15
|
+
readonly 'commerce.unbilled_items.selected_count': "commerce.unbilled_items.selected_count";
|
|
16
|
+
readonly 'commerce.unbilled_items.selected_total': "commerce.unbilled_items.selected_total";
|
|
17
|
+
readonly 'commerce.unbilled_items.create_invoice': "commerce.unbilled_items.create_invoice";
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;CAiBZ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineMessages } from '@happyvertical/smrt-ui/i18n';
|
|
2
|
+
export const M = defineMessages({
|
|
3
|
+
'commerce.invoice_actions.send_invoice': 'Send Invoice',
|
|
4
|
+
'commerce.invoice_actions.mark_as_paid': 'Mark as Paid',
|
|
5
|
+
'commerce.invoice_header.issue_date': 'Issue Date',
|
|
6
|
+
'commerce.invoice_header.due_date': 'Due Date',
|
|
7
|
+
'commerce.invoice_header.paid_date': 'Paid Date',
|
|
8
|
+
'commerce.invoice_line_items.add_item': 'Add Item',
|
|
9
|
+
'commerce.invoice_line_items.unit_price': 'Unit Price',
|
|
10
|
+
'commerce.invoice_line_items.remove_item': 'Remove item',
|
|
11
|
+
'commerce.invoice_totals.amount_paid': 'Amount Paid',
|
|
12
|
+
'commerce.invoice_totals.balance_due': 'Balance Due',
|
|
13
|
+
'commerce.invoice_totals.credit': 'Credit',
|
|
14
|
+
'commerce.invoice_totals.paid_in_full': 'Paid in Full',
|
|
15
|
+
'commerce.unbilled_items.select_all': 'Select All',
|
|
16
|
+
'commerce.unbilled_items.selected_count': '{selected} of {total} selected',
|
|
17
|
+
'commerce.unbilled_items.selected_total': 'Selected Total:',
|
|
18
|
+
'commerce.unbilled_items.create_invoice': 'Create Invoice',
|
|
19
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commerce Svelte Components
|
|
3
|
+
*
|
|
4
|
+
* This module auto-registers all commerce UI components with the ModuleUIRegistry
|
|
5
|
+
* when imported. Import this module to enable registry-based component discovery.
|
|
6
|
+
*
|
|
7
|
+
* @example Direct imports
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { InvoiceCard, InvoiceHeader } from '@happyvertical/smrt-commerce/svelte';
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @example Registry-based discovery
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';
|
|
15
|
+
* import '@happyvertical/smrt-commerce/svelte'; // Auto-registers components
|
|
16
|
+
*
|
|
17
|
+
* const Component = ModuleUIRegistry.get('@happyvertical/smrt-commerce', 'invoice-card');
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import type { ComponentProps } from 'svelte';
|
|
21
|
+
import InvoiceActions from './components/InvoiceActions.svelte';
|
|
22
|
+
import InvoiceCard from './components/InvoiceCard.svelte';
|
|
23
|
+
import InvoiceHeader from './components/InvoiceHeader.svelte';
|
|
24
|
+
import InvoiceLineItems from './components/InvoiceLineItems.svelte';
|
|
25
|
+
import InvoiceTotals from './components/InvoiceTotals.svelte';
|
|
26
|
+
import UnbilledItems from './components/UnbilledItems.svelte';
|
|
27
|
+
export type InvoiceActionsProps = ComponentProps<typeof InvoiceActions>;
|
|
28
|
+
export { default as InvoiceActions } from './components/InvoiceActions.svelte';
|
|
29
|
+
export type InvoiceCardProps = ComponentProps<typeof InvoiceCard>;
|
|
30
|
+
export { default as InvoiceCard } from './components/InvoiceCard.svelte';
|
|
31
|
+
export type InvoiceHeaderProps = ComponentProps<typeof InvoiceHeader>;
|
|
32
|
+
export { default as InvoiceHeader } from './components/InvoiceHeader.svelte';
|
|
33
|
+
export type InvoiceLineItemsProps = ComponentProps<typeof InvoiceLineItems>;
|
|
34
|
+
export { default as InvoiceLineItems } from './components/InvoiceLineItems.svelte';
|
|
35
|
+
export type InvoiceTotalsProps = ComponentProps<typeof InvoiceTotals>;
|
|
36
|
+
export { default as InvoiceTotals } from './components/InvoiceTotals.svelte';
|
|
37
|
+
export type UnbilledItemsProps = ComponentProps<typeof UnbilledItems>;
|
|
38
|
+
export { default as UnbilledItems } from './components/UnbilledItems.svelte';
|
|
39
|
+
export type { InvoiceData, InvoiceStatus, LineItem, UnbilledItem, } from './types.js';
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAI7C,OAAO,cAAc,MAAM,oCAAoC,CAAC;AAChE,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,gBAAgB,MAAM,sCAAsC,CAAC;AACpE,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAG9D,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AAExE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAC7E,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAE7E,YAAY,EACV,WAAW,EACX,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,YAAY,CAAC"}
|