@layerfi/components 0.1.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.
@@ -0,0 +1,1615 @@
1
+ // src/components/BalanceSheet/BalanceSheet.tsx
2
+ import React7, { useState as useState2 } from "react";
3
+
4
+ // src/api/util.ts
5
+ var formStringFromObject = (object) => Object.entries(object).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
6
+
7
+ // src/api/layer/authenticate.ts
8
+ var authenticate = ({
9
+ appId,
10
+ appSecret,
11
+ authenticationUrl = "https://auth.layerfi.com/oauth2/token",
12
+ clientId,
13
+ scope
14
+ }) => () => fetch(authenticationUrl, {
15
+ method: "POST",
16
+ headers: {
17
+ Authorization: "Basic " + btoa(appId + ":" + appSecret),
18
+ "Content-Type": "application/x-www-form-urlencoded"
19
+ },
20
+ body: formStringFromObject({
21
+ grant_type: "client_credentials",
22
+ scope,
23
+ client_id: clientId
24
+ })
25
+ }).then((res) => res.json());
26
+
27
+ // src/api/layer/balance_sheet.json
28
+ var balance_sheet_default = {
29
+ business_id: "05eb0771-ae7d-4cd1-ae7f-cc8765351afc",
30
+ type: "Balance_Sheet",
31
+ start_date: "2023-01-01T00:00:00Z",
32
+ end_date: "2023-01-31T00:00:00Z",
33
+ assets: [
34
+ {
35
+ name: "CURRENT_ASSETS",
36
+ display_name: "Current Assets",
37
+ value: "100000",
38
+ line_items: [
39
+ {
40
+ name: "BANK_ACCOUNTS",
41
+ display_name: "Bank Accounts",
42
+ value: "100000",
43
+ line_items: [
44
+ {
45
+ display_name: "Checking Account 123",
46
+ value: "50000",
47
+ line_items: []
48
+ },
49
+ {
50
+ display_name: "Savings Account 456",
51
+ value: "50000",
52
+ line_items: []
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ name: "ACCOUNTS_RECEIVABLE",
58
+ display_name: "Bank Accounts",
59
+ value: "100000",
60
+ line_items: [
61
+ {
62
+ account_id: "cc7861f7-d8b3-4ab9-b853-6b85838199ef",
63
+ display_name: "Accounts Receivable - Customer A",
64
+ value: "50000",
65
+ line_items: []
66
+ },
67
+ {
68
+ account_id: "23606d6a-4e78-406b-9283-76bfb750639f",
69
+ display_name: "Accounts Receivable - Customer B",
70
+ value: "50000",
71
+ line_items: []
72
+ }
73
+ ]
74
+ },
75
+ {
76
+ name: "OTHER_ASSETS",
77
+ display_name: "Other Assets",
78
+ value: "150",
79
+ line_items: [
80
+ {
81
+ account_id: "40816f76-1409-4727-b733-c4c126f0a725",
82
+ display_name: "Inventory Item A",
83
+ value: "50000",
84
+ line_items: []
85
+ }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ liabilities_and_equity: [
92
+ {
93
+ name: "LIABILITIES",
94
+ display_name: "Liabilities",
95
+ value: "100000",
96
+ line_items: [
97
+ {
98
+ name: "CURRENT_LIABILITIES",
99
+ display_name: "Current Liabilities",
100
+ value: "100000",
101
+ line_items: [
102
+ {
103
+ account_id: "40816f76-1409-4727-b733-c4c126f0a725",
104
+ display_name: "ACCOUNTS_PAYABLE",
105
+ value: "50000",
106
+ line_items: [
107
+ {
108
+ account_id: "261d0ef4-6d35-4081-8c82-685114fd497d",
109
+ display_name: "Supplier A",
110
+ value: "50000",
111
+ line_items: []
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ account_id: "5123c5eb-28ce-4cce-90bd-389e4befb645",
117
+ display_name: "Credit Cards",
118
+ value: "50000",
119
+ line_items: [
120
+ {
121
+ account_id: "beb41e99-0539-4d97-8faf-c8464eac60dc",
122
+ display_name: "Visa 5678",
123
+ value: "50000",
124
+ line_items: []
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ account_id: "7261b324-aa1a-4c03-9ac8-0a40980d5886",
130
+ display_name: "Other current Liabilities",
131
+ value: "2309",
132
+ line_items: []
133
+ }
134
+ ]
135
+ }
136
+ ]
137
+ },
138
+ {
139
+ name: "EQUITY",
140
+ display_name: "Equity",
141
+ value: "100000",
142
+ line_items: [
143
+ {
144
+ name: "OPENING_BALANCE_EQUITY",
145
+ display_name: "Opening Balance Equity",
146
+ value: "50000",
147
+ line_items: []
148
+ },
149
+ {
150
+ name: "RETAINED_EARNINGS",
151
+ display_name: "Retained Earnings",
152
+ value: "50000",
153
+ line_items: []
154
+ },
155
+ {
156
+ name: "NET_INCOME",
157
+ display_name: "Net Income",
158
+ value: "50000",
159
+ line_items: []
160
+ }
161
+ ]
162
+ }
163
+ ],
164
+ fully_categorized: true
165
+ };
166
+
167
+ // src/api/layer/balance_sheet.ts
168
+ var getBalanceSheet = (_token, _params) => () => balance_sheet_default;
169
+
170
+ // src/api/layer/authenticated_http.ts
171
+ var get = (url) => (accessToken, options) => () => fetch(url(options?.params || {}), {
172
+ headers: {
173
+ Authorization: "Bearer " + (accessToken || ""),
174
+ "Content-Type": "application/json"
175
+ },
176
+ method: "GET"
177
+ }).then((res) => res.json());
178
+ var put = (url) => (accessToken, options) => fetch(url(options?.params || {}), {
179
+ headers: {
180
+ Authorization: "Bearer " + (accessToken || ""),
181
+ "Content-Type": "application/json",
182
+ "Cache-Control": "no-cache"
183
+ },
184
+ method: "PUT",
185
+ body: JSON.stringify(options?.body)
186
+ }).then((res) => res.json());
187
+
188
+ // src/api/layer/bankTransactions.ts
189
+ var getBankTransactions = get(
190
+ ({
191
+ businessId,
192
+ sortBy = "date",
193
+ sortOrder = "DESC"
194
+ }) => `https://sandbox.layerfi.com/v1/businesses/${businessId}/bank-transactions?sort_by=${sortBy}&sort_order=${sortOrder}`
195
+ );
196
+ var categorizeBankTransaction = put(
197
+ ({ businessId, bankTransactionId }) => `https://sandbox.layerfi.com/v1/businesses/${businessId}/bank-transactions/${bankTransactionId}/categorize`
198
+ );
199
+
200
+ // src/api/layer/categories.ts
201
+ var getCategories = get(
202
+ ({ businessId }) => `https://sandbox.layerfi.com/v1/businesses/${businessId}/categories`
203
+ );
204
+
205
+ // src/api/layer/profit_and_loss.ts
206
+ var getProfitAndLoss = get(
207
+ ({ businessId, startDate, endDate }) => `https://sandbox.layerfi.com/v1/businesses/${businessId}/reports/profit-and-loss?start_date=${startDate}&end_date=${endDate}`
208
+ );
209
+
210
+ // src/api/layer.ts
211
+ var Layer = {
212
+ authenticate,
213
+ categorizeBankTransaction,
214
+ getBalanceSheet,
215
+ getBankTransactions,
216
+ getCategories,
217
+ getProfitAndLoss
218
+ };
219
+
220
+ // src/hooks/useLayerContext/useLayerContext.tsx
221
+ import { useContext } from "react";
222
+
223
+ // src/contexts/LayerContext/LayerContext.tsx
224
+ import { createContext } from "react";
225
+ var LayerContext = createContext({
226
+ auth: { access_token: "", expires_in: -1, token_type: "" },
227
+ businessId: "",
228
+ categories: []
229
+ });
230
+
231
+ // src/hooks/useLayerContext/useLayerContext.tsx
232
+ var useLayerContext = () => useContext(LayerContext);
233
+
234
+ // src/hooks/useBalanceSheet/useBalanceSheet.tsx
235
+ import { format, startOfDay } from "date-fns";
236
+ import useSWR from "swr";
237
+ var useBalanceSheet = (date) => {
238
+ const { auth, businessId } = useLayerContext();
239
+ const dateString = format(startOfDay(date), "yyyy-mm-dd");
240
+ const { data, isLoading, error } = useSWR(
241
+ businessId && dateString && auth?.access_token && `balance-sheet-${businessId}-${dateString}`,
242
+ Layer.getBalanceSheet(auth?.access_token, {
243
+ params: { businessId, date: dateString }
244
+ })
245
+ );
246
+ return { data, isLoading, error };
247
+ };
248
+
249
+ // src/icons/DownloadCloud.tsx
250
+ import * as React from "react";
251
+ var DownloadCloud = (props) => /* @__PURE__ */ React.createElement(
252
+ "svg",
253
+ {
254
+ xmlns: "http://www.w3.org/2000/svg",
255
+ width: 24,
256
+ height: 24,
257
+ fill: "none",
258
+ ...props
259
+ },
260
+ /* @__PURE__ */ React.createElement(
261
+ "path",
262
+ {
263
+ stroke: "#000",
264
+ strokeLinecap: "round",
265
+ strokeLinejoin: "round",
266
+ strokeWidth: 2,
267
+ d: "M4 16.242A4.5 4.5 0 0 1 6.08 8.02a6.002 6.002 0 0 1 11.84 0A4.5 4.5 0 0 1 20 16.242M8 17l4 4m0 0 4-4m-4 4v-9"
268
+ }
269
+ )
270
+ );
271
+ var DownloadCloud_default = DownloadCloud;
272
+
273
+ // src/components/BalanceSheetDatePicker/BalanceSheetDatePicker.tsx
274
+ import React3, { useRef } from "react";
275
+
276
+ // src/icons/Calendar.tsx
277
+ import * as React2 from "react";
278
+ var Calendar = (props) => /* @__PURE__ */ React2.createElement(
279
+ "svg",
280
+ {
281
+ xmlns: "http://www.w3.org/2000/svg",
282
+ width: 20,
283
+ height: 22,
284
+ fill: "none",
285
+ ...props
286
+ },
287
+ /* @__PURE__ */ React2.createElement(
288
+ "path",
289
+ {
290
+ stroke: "#000",
291
+ strokeLinecap: "round",
292
+ strokeLinejoin: "round",
293
+ strokeWidth: 2,
294
+ d: "M19 9H1m13-8v4M6 1v4m-.2 16h8.4c1.68 0 2.52 0 3.162-.327a3 3 0 0 0 1.311-1.311C19 18.72 19 17.88 19 16.2V7.8c0-1.68 0-2.52-.327-3.162a3 3 0 0 0-1.311-1.311C16.72 3 15.88 3 14.2 3H5.8c-1.68 0-2.52 0-3.162.327a3 3 0 0 0-1.311 1.311C1 5.28 1 6.12 1 7.8v8.4c0 1.68 0 2.52.327 3.162a3 3 0 0 0 1.311 1.311C3.28 21 4.12 21 5.8 21Z"
295
+ }
296
+ )
297
+ );
298
+ var Calendar_default = Calendar;
299
+
300
+ // src/components/BalanceSheetDatePicker/BalanceSheetDatePicker.tsx
301
+ import { format as format2 } from "date-fns";
302
+ var BalanceSheetDatePicker = ({ value, onChange }) => {
303
+ const inputRef = useRef(null);
304
+ const showPicker = () => inputRef.current && inputRef.current.showPicker();
305
+ return /* @__PURE__ */ React3.createElement("span", { className: "Layer__balance-sheet-date-picker" }, /* @__PURE__ */ React3.createElement("button", { onClick: showPicker }, /* @__PURE__ */ React3.createElement(Calendar_default, null), format2(value, "LLLL dd, yyyy"), /* @__PURE__ */ React3.createElement(
306
+ "input",
307
+ {
308
+ type: "date",
309
+ ref: inputRef,
310
+ value: format2(value, "yyyy-MM-dd"),
311
+ onChange
312
+ }
313
+ )));
314
+ };
315
+
316
+ // src/components/BalanceSheetRow/BalanceSheetRow.tsx
317
+ import React6, { useState } from "react";
318
+
319
+ // src/icons/ChevronDown.tsx
320
+ import * as React4 from "react";
321
+ var ChevronDown = ({ size = 24, ...props }) => /* @__PURE__ */ React4.createElement(
322
+ "svg",
323
+ {
324
+ xmlns: "http://www.w3.org/2000/svg",
325
+ width: size,
326
+ height: size,
327
+ fill: "none",
328
+ viewBox: "0 0 24 24",
329
+ ...props
330
+ },
331
+ /* @__PURE__ */ React4.createElement(
332
+ "path",
333
+ {
334
+ strokeLinecap: "round",
335
+ strokeLinejoin: "round",
336
+ strokeWidth: 2,
337
+ d: "m6 9 6 6 6-6"
338
+ }
339
+ )
340
+ );
341
+ var ChevronDown_default = ChevronDown;
342
+
343
+ // src/icons/ChevronRight.tsx
344
+ import * as React5 from "react";
345
+ var ChavronRight = ({ strokeColor, size, ...props }) => /* @__PURE__ */ React5.createElement(
346
+ "svg",
347
+ {
348
+ xmlns: "http://www.w3.org/2000/svg",
349
+ width: size || 24,
350
+ height: size || 24,
351
+ fill: "none",
352
+ viewBox: "0 0 24 24",
353
+ ...props
354
+ },
355
+ /* @__PURE__ */ React5.createElement(
356
+ "path",
357
+ {
358
+ stroke: strokeColor ?? "#000",
359
+ strokeLinecap: "round",
360
+ strokeLinejoin: "round",
361
+ strokeWidth: 2,
362
+ d: "m9 18 6-6-6-6"
363
+ }
364
+ )
365
+ );
366
+ var ChevronRight_default = ChavronRight;
367
+
368
+ // src/models/Money.ts
369
+ var formatter = new Intl.NumberFormat("en-US", {
370
+ minimumIntegerDigits: 1,
371
+ minimumFractionDigits: 2,
372
+ maximumFractionDigits: 2
373
+ });
374
+ var centsToDollars = (cents) => formatter.format(cents / 100);
375
+ var dollarsToCents = (dollars) => Math.round(parseFloat(dollars) * 100);
376
+
377
+ // src/components/BalanceSheetRow/BalanceSheetRow.tsx
378
+ var BalanceSheetRow = ({
379
+ lineItem,
380
+ depth = 0,
381
+ maxDepth = 2
382
+ }) => {
383
+ if (!lineItem) {
384
+ return null;
385
+ }
386
+ const { value, display_name, line_items } = lineItem;
387
+ const [expanded, setExpanded] = useState(true);
388
+ const amount = value || 0;
389
+ const isPositive = amount >= 0;
390
+ const amountString = centsToDollars(Math.abs(amount));
391
+ const labelClasses = [
392
+ "Layer__balance-sheet-row",
393
+ "Layer__balance-sheet-row__label"
394
+ ];
395
+ const valueClasses = [
396
+ "Layer__balance-sheet-row",
397
+ "Layer__balance-sheet-row__value"
398
+ ];
399
+ !!value && valueClasses.push(
400
+ isPositive ? "Layer__balance-sheet-row__value--amount-positive" : "Layer__balance-sheet-row__value--amount-negative"
401
+ );
402
+ labelClasses.push(`Layer__balance-sheet-row__label--depth-${depth}`);
403
+ valueClasses.push(`Layer__balance-sheet-row__value--depth-${depth}`);
404
+ const toggleExpanded = () => setExpanded(!expanded);
405
+ const canGoDeeper = depth < maxDepth;
406
+ const hasChildren = line_items?.length > 0;
407
+ const displayChildren = hasChildren && canGoDeeper;
408
+ labelClasses.push(
409
+ `Layer__balance-sheet-row__label--display-children-${displayChildren}`
410
+ );
411
+ valueClasses.push(
412
+ `Layer__balance-sheet-row__value--display-children-${displayChildren}`
413
+ );
414
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement("div", { className: labelClasses.join(" "), onClick: toggleExpanded }, expanded ? /* @__PURE__ */ React6.createElement(ChevronDown_default, { size: 16 }) : /* @__PURE__ */ React6.createElement(ChevronRight_default, { size: 16 }), display_name), /* @__PURE__ */ React6.createElement("div", { className: valueClasses.join(" ") }, !!value && amountString), canGoDeeper && hasChildren && expanded && (line_items || []).map((line_item) => /* @__PURE__ */ React6.createElement(
415
+ BalanceSheetRow,
416
+ {
417
+ key: line_item.display_name,
418
+ lineItem: line_item,
419
+ depth: depth + 1,
420
+ maxDepth
421
+ }
422
+ )));
423
+ };
424
+
425
+ // src/components/BalanceSheet/BalanceSheet.tsx
426
+ import { format as format3, parseISO } from "date-fns";
427
+ var BalanceSheet = () => {
428
+ const [effectiveDate, setEffectiveDate] = useState2(/* @__PURE__ */ new Date());
429
+ const { data, isLoading } = useBalanceSheet(effectiveDate);
430
+ const assets = {
431
+ name: "Assets",
432
+ display_name: "Assets",
433
+ line_items: data?.assets || [],
434
+ value: void 0
435
+ };
436
+ const lne = {
437
+ name: "LiabilitiesAndEquity",
438
+ display_name: "Liabilities & Equity",
439
+ line_items: data?.liabilities_and_equity || [],
440
+ value: void 0
441
+ };
442
+ const dateString = format3(effectiveDate, "LLLL d, yyyy");
443
+ return /* @__PURE__ */ React7.createElement("div", { className: "Layer__balance-sheet" }, /* @__PURE__ */ React7.createElement("div", { className: "Layer__balance-sheet__header" }, /* @__PURE__ */ React7.createElement("h2", { className: "Layer__balance-sheet__title" }, "Balance Sheet", /* @__PURE__ */ React7.createElement("span", { className: "Layer__balance-sheet__date" }, dateString)), /* @__PURE__ */ React7.createElement(
444
+ BalanceSheetDatePicker,
445
+ {
446
+ value: effectiveDate,
447
+ onChange: (event) => setEffectiveDate(parseISO(event.target.value))
448
+ }
449
+ ), /* @__PURE__ */ React7.createElement("button", { className: "Layer__balance-sheet__download-button" }, /* @__PURE__ */ React7.createElement(DownloadCloud_default, null), "Download")), !data || isLoading ? /* @__PURE__ */ React7.createElement("div", null, "Loading") : /* @__PURE__ */ React7.createElement("div", { className: "Layer__balance-sheet__table" }, /* @__PURE__ */ React7.createElement(BalanceSheetRow, { key: assets.name, lineItem: assets }), /* @__PURE__ */ React7.createElement(BalanceSheetRow, { key: lne.name, lineItem: lne })));
450
+ };
451
+
452
+ // src/components/BankTransactions/BankTransactions.tsx
453
+ import React18, { useState as useState6 } from "react";
454
+
455
+ // src/hooks/useBankTransactions/useBankTransactions.tsx
456
+ import useSWR2 from "swr";
457
+ var useBankTransactions = () => {
458
+ const { auth, businessId } = useLayerContext();
459
+ const {
460
+ data: responseData,
461
+ isLoading,
462
+ error: responseError,
463
+ mutate
464
+ } = useSWR2(
465
+ businessId && auth?.access_token && `bank-transactions-${businessId}`,
466
+ Layer.getBankTransactions(auth?.access_token, { params: { businessId } })
467
+ );
468
+ const {
469
+ data = [],
470
+ meta: metadata = {},
471
+ error = void 0
472
+ } = responseData || {};
473
+ const categorize = (id, newCategory) => Layer.categorizeBankTransaction(auth.access_token, {
474
+ params: { businessId, bankTransactionId: id },
475
+ body: newCategory
476
+ }).then(({ data: transaction, error: error2 }) => {
477
+ if (transaction) {
478
+ mutate();
479
+ }
480
+ if (error2) {
481
+ console.error(error2);
482
+ throw error2;
483
+ }
484
+ });
485
+ return { data, metadata, isLoading, error, categorize };
486
+ };
487
+
488
+ // src/components/BankTransactionRow/BankTransactionRow.tsx
489
+ import React17, { useState as useState5 } from "react";
490
+
491
+ // src/icons/CheckedCircle.tsx
492
+ import * as React8 from "react";
493
+ var CheckedCircle = ({
494
+ fillColor = "none",
495
+ strokeColor = "#000",
496
+ size = 24,
497
+ ...props
498
+ }) => /* @__PURE__ */ React8.createElement(
499
+ "svg",
500
+ {
501
+ xmlns: "http://www.w3.org/2000/svg",
502
+ width: size,
503
+ height: size,
504
+ viewBox: "0 0 24 24",
505
+ fill: fillColor,
506
+ ...props
507
+ },
508
+ /* @__PURE__ */ React8.createElement(
509
+ "path",
510
+ {
511
+ stroke: strokeColor,
512
+ strokeLinecap: "round",
513
+ strokeLinejoin: "round",
514
+ strokeWidth: 2,
515
+ d: "m7.5 12 3 3 6-6m5.5 3c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Z"
516
+ }
517
+ )
518
+ );
519
+ var CheckedCircle_default = CheckedCircle;
520
+
521
+ // src/icons/ChevronUp.tsx
522
+ import * as React9 from "react";
523
+ var ChevronUp = (props) => /* @__PURE__ */ React9.createElement(
524
+ "svg",
525
+ {
526
+ xmlns: "http://www.w3.org/2000/svg",
527
+ width: 24,
528
+ height: 24,
529
+ fill: "none",
530
+ viewBox: "0 0 24 24",
531
+ ...props
532
+ },
533
+ /* @__PURE__ */ React9.createElement(
534
+ "path",
535
+ {
536
+ stroke: "#000",
537
+ strokeLinecap: "round",
538
+ strokeLinejoin: "round",
539
+ strokeWidth: 2,
540
+ d: "m18 15-6-6-6 6"
541
+ }
542
+ )
543
+ );
544
+ var ChevronUp_default = ChevronUp;
545
+
546
+ // src/components/CategoryMenu/CategoryMenu.tsx
547
+ import React10 from "react";
548
+ import Select from "react-select";
549
+ var CategoryMenu = ({
550
+ bankTransaction,
551
+ name,
552
+ value,
553
+ onChange
554
+ }) => {
555
+ const { categories } = useLayerContext();
556
+ const suggestedOptions = bankTransaction?.categorization_flow?.type === "ASK_FROM_SUGGESTIONS" /* ASK_FROM_SUGGESTIONS */ ? [
557
+ {
558
+ label: "Suggested",
559
+ options: bankTransaction.categorization_flow.suggestions
560
+ }
561
+ ] : [];
562
+ const categoryOptions = (categories || []).map((category) => {
563
+ if (category?.subCategories && category?.subCategories?.length > 0) {
564
+ return {
565
+ label: category.display_name,
566
+ options: category.subCategories
567
+ };
568
+ }
569
+ return {
570
+ label: category.display_name,
571
+ options: [category]
572
+ };
573
+ }).filter((x) => x);
574
+ const options = [...suggestedOptions, ...categoryOptions];
575
+ return /* @__PURE__ */ React10.createElement(
576
+ Select,
577
+ {
578
+ name,
579
+ className: "Layer__category-menu",
580
+ options,
581
+ isSearchable: true,
582
+ value,
583
+ onChange: (newValue) => newValue && onChange(newValue),
584
+ getOptionLabel: (category) => category.display_name,
585
+ getOptionValue: (category) => category.stable_name || category.category,
586
+ menuPortalTarget: document.body,
587
+ styles: { menuPortal: (base) => ({ ...base, zIndex: 9999 }) }
588
+ }
589
+ );
590
+ };
591
+
592
+ // src/components/ExpandedBankTransactionRow/ExpandedBankTransactionRow.tsx
593
+ import React15, { useState as useState4 } from "react";
594
+
595
+ // src/icons/Link.tsx
596
+ import * as React11 from "react";
597
+ var Link = ({ size = 24, ...props }) => /* @__PURE__ */ React11.createElement(
598
+ "svg",
599
+ {
600
+ xmlns: "http://www.w3.org/2000/svg",
601
+ width: size,
602
+ height: size,
603
+ fill: "none",
604
+ viewBox: "0 0 24 24",
605
+ ...props
606
+ },
607
+ /* @__PURE__ */ React11.createElement(
608
+ "path",
609
+ {
610
+ stroke: "#000",
611
+ strokeLinecap: "round",
612
+ strokeLinejoin: "round",
613
+ strokeWidth: 2,
614
+ d: "m12.708 18.364-1.415 1.414a5 5 0 1 1-7.07-7.07l1.413-1.415m12.728 1.414 1.415-1.414a5 5 0 0 0-7.071-7.071l-1.415 1.414M8.5 15.5l7-7"
615
+ }
616
+ )
617
+ );
618
+ var Link_default = Link;
619
+
620
+ // src/icons/LinkBroken.tsx
621
+ import * as React12 from "react";
622
+ var LinkBroken = ({ size = 24, ...props }) => /* @__PURE__ */ React12.createElement(
623
+ "svg",
624
+ {
625
+ xmlns: "http://www.w3.org/2000/svg",
626
+ width: size,
627
+ height: size,
628
+ fill: "none",
629
+ viewBox: "0 0 24 24",
630
+ ...props
631
+ },
632
+ /* @__PURE__ */ React12.createElement(
633
+ "path",
634
+ {
635
+ stroke: "#000",
636
+ strokeLinecap: "round",
637
+ strokeLinejoin: "round",
638
+ strokeWidth: 2,
639
+ d: "m8.5 15.5 7-7M9 4V2m6 18v2M4 9H2m18 6h2M4.914 4.914 3.5 3.5m15.586 15.586L20.5 20.5M12 17.657l-2.121 2.121a4 4 0 1 1-5.657-5.657L6.343 12m11.314 0 2.121-2.121a4 4 0 0 0-5.657-5.657L12 6.343"
640
+ }
641
+ )
642
+ );
643
+ var LinkBroken_default = LinkBroken;
644
+
645
+ // src/components/RadioButtonGroup/RadioButtonGroup.tsx
646
+ import React14 from "react";
647
+
648
+ // src/components/RadioButtonGroup/RadioButton.tsx
649
+ import React13 from "react";
650
+ var RadioButton = ({
651
+ checked,
652
+ label,
653
+ name,
654
+ onChange,
655
+ value,
656
+ disabled,
657
+ size
658
+ }) => {
659
+ return /* @__PURE__ */ React13.createElement(
660
+ "label",
661
+ {
662
+ className: `Layer__radio-button-group__radio-button Layer__radio-button-group__radio-button--size-${size}`
663
+ },
664
+ /* @__PURE__ */ React13.createElement(
665
+ "input",
666
+ {
667
+ type: "radio",
668
+ checked,
669
+ name,
670
+ onChange,
671
+ value,
672
+ disabled: disabled ?? false
673
+ }
674
+ ),
675
+ /* @__PURE__ */ React13.createElement("div", null, label)
676
+ );
677
+ };
678
+
679
+ // src/components/RadioButtonGroup/RadioButtonGroup.tsx
680
+ var RadioButtonGroup = ({
681
+ name,
682
+ size = "large",
683
+ buttons,
684
+ onChange,
685
+ selected
686
+ }) => {
687
+ const selectedValue = selected || buttons[0].value;
688
+ return /* @__PURE__ */ React14.createElement(
689
+ "div",
690
+ {
691
+ className: `Layer__radio-button-group Layer__radio-button-group--size-${size}`
692
+ },
693
+ buttons.map((button) => /* @__PURE__ */ React14.createElement(
694
+ RadioButton,
695
+ {
696
+ ...button,
697
+ key: button.value,
698
+ name,
699
+ size,
700
+ checked: selectedValue === button.value,
701
+ onChange,
702
+ disabled: button.disabled ?? false
703
+ }
704
+ ))
705
+ );
706
+ };
707
+
708
+ // src/components/ExpandedBankTransactionRow/ExpandedBankTransactionRow.tsx
709
+ var ExpandedBankTransactionRow = ({
710
+ bankTransaction,
711
+ close
712
+ }) => {
713
+ const { categorize: categorizeBankTransaction2 } = useBankTransactions();
714
+ const [purpose, setPurpose] = useState4("categorize" /* categorize */);
715
+ const defaultCategory = bankTransaction.category || bankTransaction.categorization_flow?.type === "ASK_FROM_SUGGESTIONS" /* ASK_FROM_SUGGESTIONS */ && bankTransaction.categorization_flow?.suggestions?.[0];
716
+ const [rowState, updateRowState] = useState4({
717
+ splits: [
718
+ {
719
+ amount: bankTransaction.amount,
720
+ inputValue: centsToDollars(bankTransaction.amount),
721
+ category: defaultCategory
722
+ }
723
+ ],
724
+ description: "",
725
+ file: void 0
726
+ });
727
+ const addSplit = () => updateRowState({
728
+ ...rowState,
729
+ splits: [
730
+ ...rowState.splits,
731
+ { amount: 0, inputValue: "0.00", category: defaultCategory }
732
+ ]
733
+ });
734
+ const removeSplit = () => updateRowState({
735
+ ...rowState,
736
+ splits: rowState.splits.slice(0, -1)
737
+ });
738
+ const updateAmounts = (rowNumber) => (event) => {
739
+ const newAmount = dollarsToCents(event.target.value) || 0;
740
+ const newDisplaying = event.target.value;
741
+ const splitTotal = rowState.splits.slice(0, -1).reduce((sum, split, index) => {
742
+ const amount = index === rowNumber ? newAmount : split.amount;
743
+ return sum + amount;
744
+ }, 0);
745
+ const remaining = bankTransaction.amount - splitTotal;
746
+ rowState.splits[rowNumber].amount = newAmount;
747
+ rowState.splits[rowNumber].inputValue = newDisplaying;
748
+ rowState.splits[rowState.splits.length - 1].amount = remaining;
749
+ rowState.splits[rowState.splits.length - 1].inputValue = centsToDollars(remaining);
750
+ updateRowState({ ...rowState });
751
+ };
752
+ const onBlur = (event) => {
753
+ if (event.target.value === "") {
754
+ const [_, index] = event.target.name.split("-");
755
+ rowState.splits[parseInt(index)].inputValue = "0.00";
756
+ updateRowState({ ...rowState });
757
+ }
758
+ };
759
+ const onChangePurpose = (event) => setPurpose(
760
+ event.target.value === "match" /* match */ ? "match" /* match */ : "categorize" /* categorize */
761
+ );
762
+ const changeCategory = (index, newValue) => {
763
+ rowState.splits[index].category = newValue;
764
+ updateRowState({ ...rowState });
765
+ };
766
+ const save = () => categorizeBankTransaction2(
767
+ bankTransaction.id,
768
+ rowState.splits.length === 1 ? {
769
+ type: "Category",
770
+ category: {
771
+ type: "StableName",
772
+ stable_name: rowState?.splits[0].category?.stable_name || rowState?.splits[0].category?.category
773
+ }
774
+ } : {
775
+ type: "Split",
776
+ entries: rowState.splits.map((split) => ({
777
+ category: split.category?.stable_name || split.category?.category,
778
+ amount: split.amount
779
+ }))
780
+ }
781
+ ).then(close);
782
+ const className = "Layer__expanded-bank-transaction-row";
783
+ return /* @__PURE__ */ React15.createElement("div", { className }, /* @__PURE__ */ React15.createElement("div", { className: `${className}__purpose-button` }, /* @__PURE__ */ React15.createElement(
784
+ RadioButtonGroup,
785
+ {
786
+ name: `purpose-${bankTransaction.id}`,
787
+ size: "small",
788
+ buttons: [
789
+ { value: "categorize", label: "Categorize" },
790
+ { value: "match", label: "Match", disabled: true }
791
+ ],
792
+ selected: purpose,
793
+ onChange: onChangePurpose
794
+ }
795
+ )), /* @__PURE__ */ React15.createElement(
796
+ "div",
797
+ {
798
+ className: `${className}__content`,
799
+ id: `expanded-${bankTransaction.id}`
800
+ },
801
+ /* @__PURE__ */ React15.createElement(
802
+ "div",
803
+ {
804
+ className: `${className}__table-cell ${className}__table-cell--header`
805
+ }
806
+ ),
807
+ /* @__PURE__ */ React15.createElement(
808
+ "div",
809
+ {
810
+ className: `${className}__table-cell ${className}__table-cell--header`
811
+ },
812
+ "Category"
813
+ ),
814
+ /* @__PURE__ */ React15.createElement(
815
+ "div",
816
+ {
817
+ className: `${className}__table-cell ${className}__table-cell--header`
818
+ },
819
+ "Description"
820
+ ),
821
+ /* @__PURE__ */ React15.createElement(
822
+ "div",
823
+ {
824
+ className: `${className}__table-cell ${className}__table-cell--header`
825
+ },
826
+ "Receipt"
827
+ ),
828
+ /* @__PURE__ */ React15.createElement(
829
+ "div",
830
+ {
831
+ className: `${className}__table-cell ${className}__table-cell--header`
832
+ }
833
+ ),
834
+ /* @__PURE__ */ React15.createElement(
835
+ "div",
836
+ {
837
+ className: `${className}__table-cell ${className}__table-cell--header`
838
+ }
839
+ ),
840
+ /* @__PURE__ */ React15.createElement("div", { className: `${className}__table-cell` }, rowState.splits.length === 1 ? /* @__PURE__ */ React15.createElement("div", { className: `${className}__button--split`, onClick: addSplit }, /* @__PURE__ */ React15.createElement(LinkBroken_default, { className: `${className}__svg`, size: 18 }), "Split") : /* @__PURE__ */ React15.createElement(
841
+ "div",
842
+ {
843
+ className: `${className}__button--merge`,
844
+ onClick: removeSplit
845
+ },
846
+ /* @__PURE__ */ React15.createElement(Link_default, { className: `${className}__svg`, size: 18 }),
847
+ "Merge"
848
+ )),
849
+ /* @__PURE__ */ React15.createElement("div", { className: `${className}__table-cell` }, rowState.splits.map((split, index) => /* @__PURE__ */ React15.createElement(
850
+ "div",
851
+ {
852
+ className: `${className}__table-cell--split-entry`,
853
+ key: `split-${index}`
854
+ },
855
+ /* @__PURE__ */ React15.createElement(
856
+ CategoryMenu,
857
+ {
858
+ bankTransaction,
859
+ name: `category-${index}`,
860
+ value: split.category,
861
+ onChange: (value) => changeCategory(index, value)
862
+ }
863
+ ),
864
+ rowState.splits.length > 1 && /* @__PURE__ */ React15.createElement(
865
+ "input",
866
+ {
867
+ type: "text",
868
+ name: `split-${index}`,
869
+ disabled: index + 1 === rowState.splits.length,
870
+ onChange: updateAmounts(index),
871
+ value: split.inputValue,
872
+ onBlur,
873
+ className: `${className}__split-amount${split.amount < 0 ? "--negative" : ""}`
874
+ }
875
+ )
876
+ ))),
877
+ /* @__PURE__ */ React15.createElement(
878
+ "div",
879
+ {
880
+ className: `${className}__table-cell ${className}__table-cell--description`
881
+ },
882
+ /* @__PURE__ */ React15.createElement("textarea", null)
883
+ ),
884
+ /* @__PURE__ */ React15.createElement("div", { className: `${className}__table-cell` }, /* @__PURE__ */ React15.createElement("input", { type: "file" })),
885
+ /* @__PURE__ */ React15.createElement("div", { className: `${className}__table-cell` }),
886
+ /* @__PURE__ */ React15.createElement("div", { className: `${className}__table-cell` }, /* @__PURE__ */ React15.createElement("button", { onClick: save, className: `${className}__button--save` }, "Save"))
887
+ ));
888
+ };
889
+
890
+ // src/components/Pill/Pill.tsx
891
+ import React16 from "react";
892
+ var Pill = ({ children }) => /* @__PURE__ */ React16.createElement("span", { className: "Layer__pill" }, children);
893
+
894
+ // src/components/BankTransactionRow/BankTransactionRow.tsx
895
+ import { parseISO as parseISO2, format as formatTime } from "date-fns";
896
+ var isCredit = ({ direction }) => direction === "CREDIT" /* CREDIT */;
897
+ var BankTransactionRow = ({
898
+ dateFormat: dateFormat2,
899
+ bankTransaction,
900
+ isOpen,
901
+ toggleOpen,
902
+ editable
903
+ }) => {
904
+ const { categorize: categorizeBankTransaction2 } = useBankTransactions();
905
+ const [selectedCategory, setSelectedCategory] = useState5(
906
+ bankTransaction.categorization_flow.type === "ASK_FROM_SUGGESTIONS" /* ASK_FROM_SUGGESTIONS */ ? bankTransaction.categorization_flow.suggestions[0] : void 0
907
+ );
908
+ const className = "Layer__bank-transaction-row__table-cell";
909
+ const openClassName = isOpen ? `${className}--expanded` : "";
910
+ const save = () => categorizeBankTransaction2(bankTransaction.id, {
911
+ type: "Category",
912
+ category: {
913
+ type: "StableName",
914
+ stable_name: selectedCategory?.stable_name || selectedCategory?.category || ""
915
+ }
916
+ });
917
+ return /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName} ${className}--date` }, formatTime(parseISO2(bankTransaction.date), dateFormat2)), /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName}` }, bankTransaction.counterparty_name), /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName}` }, "Business Checking"), /* @__PURE__ */ React17.createElement(
918
+ "div",
919
+ {
920
+ className: `${className} ${openClassName} ${className}--amount-${isCredit(bankTransaction) ? "credit" : "debit"}`
921
+ },
922
+ centsToDollars(bankTransaction.amount)
923
+ ), isOpen ? /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName}` }) : /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName}` }, editable ? /* @__PURE__ */ React17.createElement(
924
+ CategoryMenu,
925
+ {
926
+ bankTransaction,
927
+ name: `category-${bankTransaction.id}`,
928
+ value: selectedCategory,
929
+ onChange: setSelectedCategory
930
+ }
931
+ ) : /* @__PURE__ */ React17.createElement(Pill, null, bankTransaction?.category?.display_name)), /* @__PURE__ */ React17.createElement("div", { className: `${className} ${openClassName} ${className}--actions` }, /* @__PURE__ */ React17.createElement(
932
+ "div",
933
+ {
934
+ className: "Layer__bank-transaction-row__save-button",
935
+ onClick: () => save()
936
+ },
937
+ editable && !isOpen && /* @__PURE__ */ React17.createElement(
938
+ CheckedCircle_default,
939
+ {
940
+ size: 28,
941
+ strokeColor: "#0C48E5",
942
+ fillColor: "#e0e9ff"
943
+ }
944
+ )
945
+ ), /* @__PURE__ */ React17.createElement(
946
+ "div",
947
+ {
948
+ onClick: () => toggleOpen(bankTransaction.id),
949
+ className: "Layer__bank-transaction-row__expand-button"
950
+ },
951
+ isOpen ? /* @__PURE__ */ React17.createElement(ChevronUp_default, null) : /* @__PURE__ */ React17.createElement(ChevronDown_default, null)
952
+ )), isOpen && /* @__PURE__ */ React17.createElement(
953
+ ExpandedBankTransactionRow,
954
+ {
955
+ bankTransaction,
956
+ close: () => toggleOpen(bankTransaction.id)
957
+ }
958
+ ));
959
+ };
960
+
961
+ // src/components/BankTransactions/BankTransactions.tsx
962
+ var dateFormat = "MM/dd/yyyy";
963
+ var CategorizedCategories = [
964
+ "CATEGORIZED" /* CATEGORIZED */,
965
+ "JOURNALING" /* JOURNALING */,
966
+ "SPLIT" /* SPLIT */
967
+ ];
968
+ var ReviewCategories = [
969
+ "READY_FOR_INPUT" /* READY_FOR_INPUT */,
970
+ "LAYER_REVIEW" /* LAYER_REVIEW */
971
+ ];
972
+ var filterVisibility = (display) => (bankTransaction) => {
973
+ const categorized = CategorizedCategories.includes(
974
+ bankTransaction.categorization_status
975
+ );
976
+ const inReview = ReviewCategories.includes(
977
+ bankTransaction.categorization_status
978
+ );
979
+ return display === "review" /* review */ && inReview || display === "categorized" /* categorized */ && categorized;
980
+ };
981
+ var BankTransactions = () => {
982
+ const [display, setDisplay] = useState6("review" /* review */);
983
+ const { data } = useBankTransactions();
984
+ const bankTransactions = data.filter(filterVisibility(display));
985
+ const onCategorizationDisplayChange = (event) => setDisplay(
986
+ event.target.value === "categorized" /* categorized */ ? "categorized" /* categorized */ : "review" /* review */
987
+ );
988
+ const [openRows, setOpenRows] = useState6({});
989
+ const toggleOpen = (id) => setOpenRows({ ...openRows, [id]: !openRows[id] });
990
+ return /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions", "data-display": display }, /* @__PURE__ */ React18.createElement("header", { className: "Layer__bank-transactions__header" }, /* @__PURE__ */ React18.createElement("h2", { className: "Layer__bank-transactions__title" }, "Transactions"), /* @__PURE__ */ React18.createElement(
991
+ RadioButtonGroup,
992
+ {
993
+ name: "bank-transaction-display",
994
+ buttons: [
995
+ { label: "To Review", value: "review" /* review */ },
996
+ { label: "Categorized", value: "categorized" /* categorized */ }
997
+ ],
998
+ selected: display,
999
+ onChange: onCategorizationDisplayChange
1000
+ }
1001
+ )), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table" }, /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header" }, "Date"), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header" }, "Transaction"), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header" }, "Account"), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header Layer__bank-transactions__table-cell--header-amount" }, "Amount"), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header" }, "Category"), /* @__PURE__ */ React18.createElement("div", { className: "Layer__bank-transactions__table-cell Layer__bank-transactions__table-cell--header" }, "Actions"), bankTransactions.map((bankTransaction) => /* @__PURE__ */ React18.createElement(
1002
+ BankTransactionRow,
1003
+ {
1004
+ key: bankTransaction.id,
1005
+ dateFormat,
1006
+ bankTransaction,
1007
+ isOpen: openRows[bankTransaction.id],
1008
+ toggleOpen,
1009
+ editable: display === "review" /* review */
1010
+ }
1011
+ ))));
1012
+ };
1013
+
1014
+ // src/components/Hello/Hello.tsx
1015
+ import React19 from "react";
1016
+ import useSWR3 from "swr";
1017
+ var fetcher = (url) => fetch(url).then((res) => res.json());
1018
+ var Hello = ({ user }) => {
1019
+ const { data, isLoading } = useSWR3(
1020
+ `https://api.github.com/users/${user || "jyurek"}`,
1021
+ fetcher
1022
+ );
1023
+ const name = (isLoading ? "..." : data?.name) || "User";
1024
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("div", { className: "hello" }, "Hello, ", name, "!"));
1025
+ };
1026
+
1027
+ // src/components/ProfitAndLoss/ProfitAndLoss.tsx
1028
+ import React27, { createContext as createContext2 } from "react";
1029
+
1030
+ // src/hooks/useProfitAndLoss/useProfitAndLoss.tsx
1031
+ import { useState as useState7 } from "react";
1032
+ import { startOfMonth, endOfMonth, formatISO } from "date-fns";
1033
+ import useSWR4 from "swr";
1034
+ var useProfitAndLoss = ({ startDate: initialStartDate, endDate: initialEndDate } = {
1035
+ startDate: startOfMonth(/* @__PURE__ */ new Date()),
1036
+ endDate: endOfMonth(/* @__PURE__ */ new Date())
1037
+ }) => {
1038
+ const { auth, businessId } = useLayerContext();
1039
+ const [startDate, setStartDate] = useState7(
1040
+ initialStartDate || startOfMonth(Date.now())
1041
+ );
1042
+ const [endDate, setEndDate] = useState7(
1043
+ initialEndDate || endOfMonth(Date.now())
1044
+ );
1045
+ const {
1046
+ data: rawData,
1047
+ isLoading,
1048
+ error: rawError
1049
+ } = useSWR4(
1050
+ businessId && startDate && endDate && auth?.access_token && `profit-and-loss-${businessId}-${startDate.valueOf()}-${endDate.valueOf()}`,
1051
+ Layer.getProfitAndLoss(auth?.access_token, {
1052
+ params: {
1053
+ businessId,
1054
+ startDate: formatISO(startDate),
1055
+ endDate: formatISO(endDate)
1056
+ }
1057
+ })
1058
+ );
1059
+ const { data, error } = rawData || {};
1060
+ const changeDateRange = ({
1061
+ startDate: newStartDate,
1062
+ endDate: newEndDate
1063
+ }) => {
1064
+ newStartDate && setStartDate(newStartDate);
1065
+ newEndDate && setEndDate(newEndDate);
1066
+ };
1067
+ return {
1068
+ data,
1069
+ isLoading,
1070
+ error: error || rawError,
1071
+ dateRange: { startDate, endDate },
1072
+ changeDateRange
1073
+ };
1074
+ };
1075
+
1076
+ // src/components/ProfitAndLossChart/ProfitAndLossChart.tsx
1077
+ import React21, { useContext as useContext2, useMemo, useState as useState8 } from "react";
1078
+
1079
+ // src/components/ProfitAndLossChart/Indicator.tsx
1080
+ import React20, { useEffect } from "react";
1081
+ var emptyViewBox = { x: 0, y: 0, width: 0, height: 0 };
1082
+ var Indicator = ({
1083
+ viewBox = {},
1084
+ className,
1085
+ animateFrom,
1086
+ setAnimateFrom
1087
+ }) => {
1088
+ if (!className?.match(/selected/)) {
1089
+ return null;
1090
+ }
1091
+ const {
1092
+ x: animateTo = 0,
1093
+ y = 0,
1094
+ width = 0,
1095
+ height = 0
1096
+ } = "x" in viewBox ? viewBox : emptyViewBox;
1097
+ const boxWidth = width * 2 + 4;
1098
+ const multiplier = 1.5;
1099
+ const xOffset = (boxWidth * multiplier - boxWidth) / 2;
1100
+ useEffect(() => {
1101
+ setAnimateFrom(animateTo);
1102
+ }, [animateTo]);
1103
+ const actualX = animateFrom === -1 ? animateTo : animateFrom;
1104
+ return /* @__PURE__ */ React20.createElement(
1105
+ "rect",
1106
+ {
1107
+ className: "Layer__profit-and-loss-chart__selection-indicator",
1108
+ style: {
1109
+ width: `${boxWidth * multiplier}px`,
1110
+ x: actualX - xOffset,
1111
+ y: y + height
1112
+ }
1113
+ }
1114
+ );
1115
+ };
1116
+
1117
+ // src/components/ProfitAndLossChart/ProfitAndLossChart.tsx
1118
+ import { endOfMonth as endOfMonth2, format as format4, parseISO as parseISO3, startOfMonth as startOfMonth2, sub } from "date-fns";
1119
+ import {
1120
+ BarChart,
1121
+ XAxis,
1122
+ Cell,
1123
+ Bar,
1124
+ LabelList,
1125
+ CartesianGrid,
1126
+ Legend,
1127
+ ResponsiveContainer
1128
+ } from "recharts";
1129
+ var barGap = 4;
1130
+ var barSize = 20;
1131
+ var ProfitAndLossChart = () => {
1132
+ const { changeDateRange, dateRange } = useContext2(ProfitAndLoss.Context);
1133
+ const thisMonth = startOfMonth2(Date.now());
1134
+ const startSelectionMonth = dateRange.startDate.getMonth();
1135
+ const endSelectionMonth = dateRange.endDate.getMonth();
1136
+ const monthData = [];
1137
+ monthData.push(
1138
+ useProfitAndLoss({
1139
+ startDate: startOfMonth2(sub(thisMonth, { months: 11 })),
1140
+ endDate: endOfMonth2(sub(thisMonth, { months: 11 }))
1141
+ })?.data
1142
+ );
1143
+ monthData.push(
1144
+ useProfitAndLoss({
1145
+ startDate: startOfMonth2(sub(thisMonth, { months: 10 })),
1146
+ endDate: endOfMonth2(sub(thisMonth, { months: 10 }))
1147
+ })?.data
1148
+ );
1149
+ monthData.push(
1150
+ useProfitAndLoss({
1151
+ startDate: startOfMonth2(sub(thisMonth, { months: 9 })),
1152
+ endDate: endOfMonth2(sub(thisMonth, { months: 9 }))
1153
+ })?.data
1154
+ );
1155
+ monthData.push(
1156
+ useProfitAndLoss({
1157
+ startDate: startOfMonth2(sub(thisMonth, { months: 8 })),
1158
+ endDate: endOfMonth2(sub(thisMonth, { months: 8 }))
1159
+ })?.data
1160
+ );
1161
+ monthData.push(
1162
+ useProfitAndLoss({
1163
+ startDate: startOfMonth2(sub(thisMonth, { months: 7 })),
1164
+ endDate: endOfMonth2(sub(thisMonth, { months: 7 }))
1165
+ })?.data
1166
+ );
1167
+ monthData.push(
1168
+ useProfitAndLoss({
1169
+ startDate: startOfMonth2(sub(thisMonth, { months: 6 })),
1170
+ endDate: endOfMonth2(sub(thisMonth, { months: 6 }))
1171
+ })?.data
1172
+ );
1173
+ monthData.push(
1174
+ useProfitAndLoss({
1175
+ startDate: startOfMonth2(sub(thisMonth, { months: 5 })),
1176
+ endDate: endOfMonth2(sub(thisMonth, { months: 5 }))
1177
+ })?.data
1178
+ );
1179
+ monthData.push(
1180
+ useProfitAndLoss({
1181
+ startDate: startOfMonth2(sub(thisMonth, { months: 4 })),
1182
+ endDate: endOfMonth2(sub(thisMonth, { months: 4 }))
1183
+ })?.data
1184
+ );
1185
+ monthData.push(
1186
+ useProfitAndLoss({
1187
+ startDate: startOfMonth2(sub(thisMonth, { months: 3 })),
1188
+ endDate: endOfMonth2(sub(thisMonth, { months: 3 }))
1189
+ })?.data
1190
+ );
1191
+ monthData.push(
1192
+ useProfitAndLoss({
1193
+ startDate: startOfMonth2(sub(thisMonth, { months: 2 })),
1194
+ endDate: endOfMonth2(sub(thisMonth, { months: 2 }))
1195
+ })?.data
1196
+ );
1197
+ monthData.push(
1198
+ useProfitAndLoss({
1199
+ startDate: startOfMonth2(sub(thisMonth, { months: 1 })),
1200
+ endDate: endOfMonth2(sub(thisMonth, { months: 1 }))
1201
+ })?.data
1202
+ );
1203
+ monthData.push(
1204
+ useProfitAndLoss({
1205
+ startDate: thisMonth,
1206
+ endDate: endOfMonth2(thisMonth)
1207
+ })?.data
1208
+ );
1209
+ const getMonthName = (pnl) => !!pnl ? format4(parseISO3(pnl.start_date), "LLL") : "";
1210
+ const summarizePnL = (pnl) => ({
1211
+ name: getMonthName(pnl),
1212
+ revenue: pnl?.income.value || 0,
1213
+ expenses: (pnl?.income.value || 0) - (pnl?.net_profit || 0),
1214
+ selected: !!pnl && parseISO3(pnl.start_date).getMonth() >= startSelectionMonth && parseISO3(pnl.end_date).getMonth() <= endSelectionMonth
1215
+ });
1216
+ const onClick = ({ activeTooltipIndex }) => {
1217
+ const selection = monthData[activeTooltipIndex || -1];
1218
+ if (selection) {
1219
+ const { start_date: startDate, end_date: endDate } = selection;
1220
+ changeDateRange({
1221
+ startDate: parseISO3(startDate),
1222
+ endDate: parseISO3(endDate)
1223
+ });
1224
+ }
1225
+ };
1226
+ const data = useMemo(
1227
+ () => monthData.map(summarizePnL),
1228
+ [
1229
+ startSelectionMonth,
1230
+ endSelectionMonth,
1231
+ ...monthData.map((m) => m?.net_profit)
1232
+ ]
1233
+ );
1234
+ const [animateFrom, setAnimateFrom] = useState8(-1);
1235
+ return /* @__PURE__ */ React21.createElement(ResponsiveContainer, { width: "100%", height: 250 }, /* @__PURE__ */ React21.createElement(
1236
+ BarChart,
1237
+ {
1238
+ margin: { left: 24, right: 24, bottom: 24 },
1239
+ data,
1240
+ onClick,
1241
+ barGap,
1242
+ className: "Layer__profit-and-loss-chart"
1243
+ },
1244
+ /* @__PURE__ */ React21.createElement(CartesianGrid, { vertical: false }),
1245
+ /* @__PURE__ */ React21.createElement(
1246
+ Legend,
1247
+ {
1248
+ verticalAlign: "top",
1249
+ align: "left",
1250
+ payload: [
1251
+ { value: "Income", type: "circle", id: "IncomeLegend" },
1252
+ { value: "Expenses", type: "circle", id: "ExpensesLegend" }
1253
+ ]
1254
+ }
1255
+ ),
1256
+ /* @__PURE__ */ React21.createElement(XAxis, { dataKey: "name", tickLine: false }),
1257
+ /* @__PURE__ */ React21.createElement(
1258
+ Bar,
1259
+ {
1260
+ dataKey: "revenue",
1261
+ barSize,
1262
+ isAnimationActive: false,
1263
+ radius: [barSize / 4, barSize / 4, 0, 0],
1264
+ className: "Layer__profit-and-loss-chart__bar--income"
1265
+ },
1266
+ /* @__PURE__ */ React21.createElement(
1267
+ LabelList,
1268
+ {
1269
+ content: /* @__PURE__ */ React21.createElement(
1270
+ Indicator,
1271
+ {
1272
+ animateFrom,
1273
+ setAnimateFrom
1274
+ }
1275
+ )
1276
+ }
1277
+ ),
1278
+ data.map((entry) => /* @__PURE__ */ React21.createElement(
1279
+ Cell,
1280
+ {
1281
+ key: entry.name,
1282
+ className: entry.selected ? "Layer__profit-and-loss-chart__cell--selected" : ""
1283
+ }
1284
+ ))
1285
+ ),
1286
+ /* @__PURE__ */ React21.createElement(
1287
+ Bar,
1288
+ {
1289
+ dataKey: "expenses",
1290
+ barSize,
1291
+ isAnimationActive: false,
1292
+ radius: [barSize / 4, barSize / 4, 0, 0],
1293
+ className: "Layer__profit-and-loss-chart__bar--expenses"
1294
+ },
1295
+ data.map((entry) => /* @__PURE__ */ React21.createElement(
1296
+ Cell,
1297
+ {
1298
+ key: entry.name,
1299
+ className: entry.selected ? "Layer__profit-and-loss-chart__cell--selected" : ""
1300
+ }
1301
+ ))
1302
+ )
1303
+ ));
1304
+ };
1305
+
1306
+ // src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx
1307
+ import React23, { useContext as useContext3 } from "react";
1308
+
1309
+ // src/icons/ChevronLeft.tsx
1310
+ import * as React22 from "react";
1311
+ var ChevronLeft = ({
1312
+ strokeColor,
1313
+ size,
1314
+ ...props
1315
+ }) => /* @__PURE__ */ React22.createElement(
1316
+ "svg",
1317
+ {
1318
+ xmlns: "http://www.w3.org/2000/svg",
1319
+ width: size || 24,
1320
+ height: size || 24,
1321
+ fill: "none",
1322
+ viewBox: "0 0 24 24",
1323
+ ...props
1324
+ },
1325
+ /* @__PURE__ */ React22.createElement(
1326
+ "path",
1327
+ {
1328
+ stroke: strokeColor ?? "#000",
1329
+ strokeLinecap: "round",
1330
+ strokeLinejoin: "round",
1331
+ strokeWidth: 2,
1332
+ d: "m15 18-6-6 6-6"
1333
+ }
1334
+ )
1335
+ );
1336
+ var ChevronLeft_default = ChevronLeft;
1337
+
1338
+ // src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx
1339
+ import { add, endOfMonth as endOfMonth3, format as format5, startOfMonth as startOfMonth3 } from "date-fns";
1340
+ var ProfitAndLossDatePicker = () => {
1341
+ const { changeDateRange, dateRange } = useContext3(ProfitAndLoss.Context);
1342
+ const date = dateRange.startDate;
1343
+ const label = format5(date, "LLLL y");
1344
+ const change = (duration) => {
1345
+ const newDate = add(date, duration);
1346
+ changeDateRange({
1347
+ startDate: startOfMonth3(newDate),
1348
+ endDate: endOfMonth3(newDate)
1349
+ });
1350
+ };
1351
+ const previousMonth = () => change({ months: -1 });
1352
+ const nextMonth = () => change({ months: 1 });
1353
+ return /* @__PURE__ */ React23.createElement("div", { className: "Layer__profit-and-loss-date-picker" }, /* @__PURE__ */ React23.createElement(
1354
+ "button",
1355
+ {
1356
+ "aria-label": "View Previous Month",
1357
+ className: "Layer__profit-and-loss-date-picker__button",
1358
+ onClick: previousMonth
1359
+ },
1360
+ /* @__PURE__ */ React23.createElement(
1361
+ ChevronLeft_default,
1362
+ {
1363
+ className: "Layer__profit-and-loss-date-picker__button-icon",
1364
+ size: 18
1365
+ }
1366
+ )
1367
+ ), /* @__PURE__ */ React23.createElement("span", { className: "Layer__profit-and-loss-date-picker__label" }, label), /* @__PURE__ */ React23.createElement(
1368
+ "button",
1369
+ {
1370
+ "aria-label": "View Next Month",
1371
+ className: "Layer__profit-and-loss-date-picker__button",
1372
+ onClick: nextMonth
1373
+ },
1374
+ /* @__PURE__ */ React23.createElement(
1375
+ ChevronRight_default,
1376
+ {
1377
+ className: "Layer__profit-and-loss-date-picker__button-icon",
1378
+ size: 18
1379
+ }
1380
+ )
1381
+ ));
1382
+ };
1383
+
1384
+ // src/components/ProfitAndLossSummaries/ProfitAndLossSummaries.tsx
1385
+ import React24, { useContext as useContext4 } from "react";
1386
+ import { format as formatDate, parseISO as parseISO4 } from "date-fns";
1387
+ var ProfitAndLossSummaries = () => {
1388
+ const { data } = useContext4(ProfitAndLoss.Context);
1389
+ if (!data) {
1390
+ return null;
1391
+ }
1392
+ const monthName = formatDate(parseISO4(data?.start_date), "LLLL");
1393
+ return /* @__PURE__ */ React24.createElement("div", { className: "Layer__profit-and-loss-summaries" }, /* @__PURE__ */ React24.createElement("div", { className: "Layer__profit-and-loss-summaries__summary Layer__profit-and-loss-summaries__summary--income" }, /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__title" }, "Revenue"), /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__amount" }, centsToDollars(data.income.value))), /* @__PURE__ */ React24.createElement("div", { className: "Layer__profit-and-loss-summaries__summary Layer__profit-and-loss-summaries__summary--expenses" }, /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__title" }, "Expenses"), /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__amount" }, centsToDollars(Math.abs(data.income.value - data.net_profit)))), /* @__PURE__ */ React24.createElement("div", { className: "Layer__profit-and-loss-summaries__summary Layer__profit-and-loss-summaries__summary--net-profit" }, /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__title" }, "Net Profit"), /* @__PURE__ */ React24.createElement("span", { className: "Layer__profit-and-loss-summaries__amount" }, centsToDollars(data.net_profit))));
1394
+ };
1395
+
1396
+ // src/components/ProfitAndLossTable/ProfitAndLossTable.tsx
1397
+ import React26, { useContext as useContext5 } from "react";
1398
+
1399
+ // src/components/ProfitAndLossRow/ProfitAndLossRow.tsx
1400
+ import React25 from "react";
1401
+ var ProfitAndLossRow = ({
1402
+ variant,
1403
+ lineItem,
1404
+ depth = 0,
1405
+ maxDepth = 1,
1406
+ direction = "DEBIT" /* DEBIT */
1407
+ }) => {
1408
+ if (!lineItem) {
1409
+ return null;
1410
+ }
1411
+ const { value, display_name, line_items, name } = lineItem;
1412
+ const variantName = variant || name;
1413
+ const amount = value || 0;
1414
+ const amountString = centsToDollars(Math.abs(amount));
1415
+ const labelClasses = [
1416
+ "Layer__profit-and-loss-row",
1417
+ "Layer__profit-and-loss-row__label"
1418
+ ];
1419
+ const valueClasses = [
1420
+ "Layer__profit-and-loss-row",
1421
+ "Layer__profit-and-loss-row__value"
1422
+ ];
1423
+ valueClasses.push(
1424
+ direction === "CREDIT" /* CREDIT */ ? "Layer__profit-and-loss-row__value--amount-positive" : "Layer__profit-and-loss-row__value--amount-negative"
1425
+ );
1426
+ labelClasses.push(`Layer__profit-and-loss-row__label--depth-${depth}`);
1427
+ valueClasses.push(`Layer__profit-and-loss-row__value--depth-${depth}`);
1428
+ variantName && labelClasses.push(
1429
+ `Layer__profit-and-loss-row__label--variant-${variantName}`
1430
+ );
1431
+ variantName && valueClasses.push(
1432
+ `Layer__profit-and-loss-row__value--variant-${variantName}`
1433
+ );
1434
+ return /* @__PURE__ */ React25.createElement(React25.Fragment, null, /* @__PURE__ */ React25.createElement("div", { className: labelClasses.join(" ") }, display_name), /* @__PURE__ */ React25.createElement("div", { className: valueClasses.join(" ") }, amountString), depth < maxDepth && (line_items || []).map((line_item) => /* @__PURE__ */ React25.createElement(
1435
+ ProfitAndLossRow,
1436
+ {
1437
+ key: line_item.display_name,
1438
+ lineItem: line_item,
1439
+ depth: depth + 1,
1440
+ maxDepth,
1441
+ direction
1442
+ }
1443
+ )));
1444
+ };
1445
+
1446
+ // src/components/ProfitAndLossTable/ProfitAndLossTable.tsx
1447
+ var ProfitAndLossTable = () => {
1448
+ const { data, isLoading } = useContext5(ProfitAndLoss.Context);
1449
+ return /* @__PURE__ */ React26.createElement("div", { className: "Layer__profit-and-loss-table" }, !data || isLoading ? /* @__PURE__ */ React26.createElement("div", null, "Loading") : /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(
1450
+ ProfitAndLossRow,
1451
+ {
1452
+ lineItem: data.income,
1453
+ direction: "CREDIT" /* CREDIT */
1454
+ }
1455
+ ), /* @__PURE__ */ React26.createElement(
1456
+ ProfitAndLossRow,
1457
+ {
1458
+ lineItem: data.cost_of_goods_sold,
1459
+ direction: "DEBIT" /* DEBIT */
1460
+ }
1461
+ ), /* @__PURE__ */ React26.createElement(
1462
+ ProfitAndLossRow,
1463
+ {
1464
+ lineItem: {
1465
+ value: data.gross_profit,
1466
+ display_name: "Gross Profit"
1467
+ },
1468
+ variant: "GROSS",
1469
+ direction: "CREDIT" /* CREDIT */
1470
+ }
1471
+ ), /* @__PURE__ */ React26.createElement(
1472
+ ProfitAndLossRow,
1473
+ {
1474
+ lineItem: data.expenses,
1475
+ direction: "DEBIT" /* DEBIT */
1476
+ }
1477
+ ), /* @__PURE__ */ React26.createElement(
1478
+ ProfitAndLossRow,
1479
+ {
1480
+ lineItem: {
1481
+ value: data.profit_before_taxes,
1482
+ display_name: "Profit Before Taxes"
1483
+ },
1484
+ variant: "BEFORETAX",
1485
+ direction: "CREDIT" /* CREDIT */
1486
+ }
1487
+ ), /* @__PURE__ */ React26.createElement(ProfitAndLossRow, { lineItem: data.taxes, direction: "DEBIT" /* DEBIT */ }), /* @__PURE__ */ React26.createElement(
1488
+ ProfitAndLossRow,
1489
+ {
1490
+ lineItem: {
1491
+ value: data.net_profit,
1492
+ display_name: "Net Profit"
1493
+ },
1494
+ variant: "NETPROFIT",
1495
+ direction: "CREDIT" /* CREDIT */
1496
+ }
1497
+ ), /* @__PURE__ */ React26.createElement(
1498
+ ProfitAndLossRow,
1499
+ {
1500
+ lineItem: data.other_outflows,
1501
+ direction: "DEBIT" /* DEBIT */
1502
+ }
1503
+ ), /* @__PURE__ */ React26.createElement(
1504
+ ProfitAndLossRow,
1505
+ {
1506
+ lineItem: data.personal_expenses,
1507
+ direction: "DEBIT" /* DEBIT */
1508
+ }
1509
+ )));
1510
+ };
1511
+
1512
+ // src/components/ProfitAndLoss/ProfitAndLoss.tsx
1513
+ import { endOfMonth as endOfMonth4, startOfMonth as startOfMonth4 } from "date-fns";
1514
+ var PNLContext = createContext2({
1515
+ data: void 0,
1516
+ isLoading: true,
1517
+ error: void 0,
1518
+ dateRange: {
1519
+ startDate: startOfMonth4(/* @__PURE__ */ new Date()),
1520
+ endDate: endOfMonth4(/* @__PURE__ */ new Date())
1521
+ },
1522
+ changeDateRange: () => {
1523
+ }
1524
+ });
1525
+ var ProfitAndLoss = ({ children }) => {
1526
+ const contextData = useProfitAndLoss();
1527
+ return /* @__PURE__ */ React27.createElement(PNLContext.Provider, { value: contextData }, /* @__PURE__ */ React27.createElement("div", { className: "Layer__profit-and-loss" }, /* @__PURE__ */ React27.createElement("h2", { className: "Layer__profit-and-loss__title" }, "Profit & Loss"), children));
1528
+ };
1529
+ ProfitAndLoss.Chart = ProfitAndLossChart;
1530
+ ProfitAndLoss.Context = PNLContext;
1531
+ ProfitAndLoss.DatePicker = ProfitAndLossDatePicker;
1532
+ ProfitAndLoss.Summaries = ProfitAndLossSummaries;
1533
+ ProfitAndLoss.Table = ProfitAndLossTable;
1534
+
1535
+ // src/providers/LayerProvider/LayerProvider.tsx
1536
+ import React28, { useReducer, useEffect as useEffect2 } from "react";
1537
+ import useSWR5, { SWRConfig } from "swr";
1538
+ var reducer = (state, action) => {
1539
+ switch (action.type) {
1540
+ case "LayerContext.setAuth" /* setAuth */:
1541
+ case "LayerContext.setCategories" /* setCategories */:
1542
+ return { ...state, ...action.payload };
1543
+ default:
1544
+ return state;
1545
+ }
1546
+ };
1547
+ var LayerEnvironment = {
1548
+ production: {
1549
+ url: "not defined yet",
1550
+ scope: "not defined yet"
1551
+ },
1552
+ staging: {
1553
+ url: "https://auth.layerfi.com/oauth2/token",
1554
+ scope: "https://sandbox.layerfi.com/sandbox"
1555
+ }
1556
+ };
1557
+ var LayerProvider = ({
1558
+ appId,
1559
+ appSecret,
1560
+ businessId,
1561
+ children,
1562
+ clientId,
1563
+ environment = "production"
1564
+ }) => {
1565
+ const defaultSWRConfig = {
1566
+ revalidateInterval: 0,
1567
+ revalidateOnFocus: false,
1568
+ revalidateOnReconnect: false,
1569
+ revalidateIfStale: false
1570
+ };
1571
+ const { url, scope } = LayerEnvironment[environment];
1572
+ const [state, dispatch] = useReducer(reducer, {
1573
+ auth: { access_token: "", token_type: "", expires_in: 0 },
1574
+ businessId,
1575
+ categories: []
1576
+ });
1577
+ const { data: auth } = useSWR5(
1578
+ "authenticate",
1579
+ Layer.authenticate({
1580
+ appId,
1581
+ appSecret,
1582
+ authenticationUrl: url,
1583
+ scope,
1584
+ clientId
1585
+ }),
1586
+ defaultSWRConfig
1587
+ );
1588
+ useEffect2(() => {
1589
+ if (!!auth?.access_token) {
1590
+ dispatch({ type: "LayerContext.setAuth" /* setAuth */, payload: { auth } });
1591
+ }
1592
+ }, [auth?.access_token]);
1593
+ const { data: categories } = useSWR5(
1594
+ businessId && auth?.access_token && `categories-${businessId}`,
1595
+ Layer.getCategories(auth?.access_token, { params: { businessId } }),
1596
+ defaultSWRConfig
1597
+ );
1598
+ useEffect2(() => {
1599
+ if (!!categories?.data?.categories?.length) {
1600
+ dispatch({
1601
+ type: "LayerContext.setCategories" /* setCategories */,
1602
+ payload: { categories: categories.data.categories || [] }
1603
+ });
1604
+ }
1605
+ }, [categories?.data?.categories?.length]);
1606
+ return /* @__PURE__ */ React28.createElement(SWRConfig, { value: defaultSWRConfig }, /* @__PURE__ */ React28.createElement(LayerContext.Provider, { value: state }, children));
1607
+ };
1608
+ export {
1609
+ BalanceSheet,
1610
+ BankTransactions,
1611
+ Hello,
1612
+ LayerProvider,
1613
+ ProfitAndLoss
1614
+ };
1615
+ //# sourceMappingURL=index.js.map