@accounter/client 0.0.8-alpha-20251030162201-d2f279aafe537912ec3546af855cbd3a38ac0f5c → 0.0.8-alpha-20251030162303-87017b2c2addf0e61478260aac336b9343f15c98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +1 -16
  2. package/dist/assets/{Checkbox-qEdmT4HE.js → Checkbox-CLE7WylX.js} +2 -2
  3. package/dist/assets/{MultiSelect-Dm9mO7pJ.js → MultiSelect-SsgUtHuW.js} +1 -1
  4. package/dist/assets/{Progress-kr0gnfBa.js → Progress-D7j8SJRp.js} +1 -1
  5. package/dist/assets/{Table-DnHfGe-y.js → Table-DpZKU9e6.js} +1 -1
  6. package/dist/assets/{Typography-Cu3puAUK.js → Typography-DOCvIzjw.js} +1 -1
  7. package/dist/assets/{YearPickerInput-oa86GF4P.js → YearPickerInput-BbyQMsmW.js} +1 -1
  8. package/dist/assets/{accordion-DLtMSqIv.js → accordion-KRd9M_7N.js} +1 -1
  9. package/dist/assets/{accountant-approvals-DsL0uiBa.js → accountant-approvals-BWkXiUJx.js} +1 -1
  10. package/dist/assets/{accounter-table-BL_yrGIl.js → accounter-table-BvOYdMv2.js} +1 -1
  11. package/dist/assets/{addDays-DqZEDZy3.js → addDays-HTdY9JyJ.js} +1 -1
  12. package/dist/assets/{all-charges-D3Xf-pEY.js → all-charges-uOrXkSoe.js} +1 -1
  13. package/dist/assets/{arrow-up-down-DchtBEG4.js → arrow-up-down-D0e6luFS.js} +1 -1
  14. package/dist/assets/{building-2-ZTKteVvf.js → building-2-BScQAy2y.js} +1 -1
  15. package/dist/assets/{business-extended-info-1mzdXJbs.js → business-extended-info-BPdDug4k.js} +4 -4
  16. package/dist/assets/{business-header-CJqfKV4d.js → business-header-ScM944FH.js} +1 -1
  17. package/dist/assets/{business-ledger-filters-BRB1r_DP.js → business-ledger-filters-C6zdPVNQ.js} +1 -1
  18. package/dist/assets/{business-ledger-single-C8FIQJ8V.js → business-ledger-single-D_gxQY7c.js} +1 -1
  19. package/dist/assets/business-qHY5sNtm.js +37 -0
  20. package/dist/assets/business-trip-BD6qMULS.js +1 -0
  21. package/dist/assets/{charge-Df3lWZH-.js → charge-CXgCTI2e.js} +1 -1
  22. package/dist/assets/charges-filters-DhYogrgu.js +1 -0
  23. package/dist/assets/{charges-ledger-validation-CtCBR3C-.js → charges-ledger-validation-K96Gpr99.js} +1 -1
  24. package/dist/assets/{charges-table-CWbe3t13.js → charges-table-Cff0tibm.js} +7 -7
  25. package/dist/assets/{chart-Cmm-jp0c.js → chart-Rag7RVt5.js} +1 -1
  26. package/dist/assets/contracts-C26gq1yS.js +16 -0
  27. package/dist/assets/{data-table-pagination-fdOZTf64.js → data-table-pagination-BenxjRXY.js} +2 -2
  28. package/dist/assets/{download-csv-button-CwK31swX.js → download-csv-button-8ow10rSS.js} +1 -1
  29. package/dist/assets/edit-issue-document-modal-2d_PTbvO.js +1 -0
  30. package/dist/assets/{editable-business-trip-Bjxkm9xV.js → editable-business-trip-BFJ_Mqji.js} +2 -2
  31. package/dist/assets/eye-off-BNMLF-BO.js +6 -0
  32. package/dist/assets/{funnel-CMG4QvaK.js → funnel-DW3_rS_8.js} +1 -1
  33. package/dist/assets/{index-BZVrGMgV.js → index-1iKxq5ag.js} +2 -2
  34. package/dist/assets/{index-C_ZNQ0Zr.js → index-2unHvdGT.js} +1 -1
  35. package/dist/assets/{index-DInU4RP4.js → index-4ufIi3AX.js} +2 -2
  36. package/dist/assets/index-B0Je179L.js +1 -0
  37. package/dist/assets/index-B2VI47XT.js +1 -0
  38. package/dist/assets/index-B86FoQmY.js +1 -0
  39. package/dist/assets/{index-CgokSIpu.js → index-BCiHeRl-.js} +1 -1
  40. package/dist/assets/{index-6G62OvEh.js → index-BDUyPP2c.js} +1 -1
  41. package/dist/assets/index-BHA96r33.js +1 -0
  42. package/dist/assets/{index-CjC_SXIA.js → index-BPS5RB7_.js} +1 -1
  43. package/dist/assets/{index-B9CbQIGq.js → index-BQyZXdTq.js} +2 -2
  44. package/dist/assets/index-BZLSFBEt.js +1 -0
  45. package/dist/assets/{index-Cw_aKL-8.js → index-Bd-SKdyy.js} +178 -173
  46. package/dist/assets/{index-C7oYbGGm.js → index-Bo2SK7RM.js} +1 -1
  47. package/dist/assets/{index-jCPWIpdP.js → index-Bp6IHcih.js} +1 -1
  48. package/dist/assets/index-BrSZxw1u.css +1 -0
  49. package/dist/assets/{index-DFW4nqx8.js → index-CEiIgRJ2.js} +7 -7
  50. package/dist/assets/{index-CTatkdRy.js → index-CKNxDyu9.js} +2 -2
  51. package/dist/assets/index-CqIav7Xr.js +1 -0
  52. package/dist/assets/index-Dp9F68vS.js +1 -0
  53. package/dist/assets/{index-CriBDqbK.js → index-DvvRs9Ha.js} +1 -1
  54. package/dist/assets/{index-DLwMwjDQ.js → index-DyLWk3js.js} +1 -1
  55. package/dist/assets/index-NdA_oCxx.js +1 -0
  56. package/dist/assets/{index-BEahR51x.js → index-VD71ra9C.js} +2 -2
  57. package/dist/assets/{index-xxe8M91O.js → index-lMXNkFuw.js} +2 -2
  58. package/dist/assets/index-q3ev6JER.js +1 -0
  59. package/dist/assets/{index.es-B_Bf6nNm.js → index.es-C9I2aTP4.js} +1 -1
  60. package/dist/assets/{insert-business-trip-modal-Ds3RIlmF.js → insert-business-trip-modal-DVcWpJHr.js} +2 -2
  61. package/dist/assets/issue-document-BxTTlJ1T.js +1 -0
  62. package/dist/assets/{list-plus-B3EB-dKe.js → list-plus-B7wBrICK.js} +1 -1
  63. package/dist/assets/login-page-BpgscuKm.js +1 -0
  64. package/dist/assets/{match-document-modal-Bo9pBr8W.js → match-document-modal-CqP2SE2c.js} +4 -4
  65. package/dist/assets/{missing-info-charges-BQYXI7bj.js → missing-info-charges-CIs_kE3P.js} +1 -1
  66. package/dist/assets/{modal-Pj_6bAFk.js → modal-CwuHXtxz.js} +1 -1
  67. package/dist/assets/modify-contract-dialog-BoeamC68.js +1 -0
  68. package/dist/assets/{page-layout-CVTeUSZp.js → page-layout-XgNPsCmr.js} +1 -1
  69. package/dist/assets/{page-not-found-TvTAiAs4.js → page-not-found-CrFJMQNO.js} +1 -1
  70. package/dist/assets/{panel-top-open-DzBY_TuM.js → panel-top-open-DU7alj-S.js} +1 -1
  71. package/dist/assets/{pencil-BBmFhuZN.js → pencil-9DTTP5vx.js} +1 -1
  72. package/dist/assets/{report-commentary-row-Dqw62LLo.js → report-commentary-row-BdS3qqMS.js} +1 -1
  73. package/dist/assets/{save-BW18gNVs.js → save-i8sPy_OW.js} +1 -1
  74. package/dist/assets/similar-transactions-modal-CZAOYt-d.js +1 -0
  75. package/dist/assets/sub-BL9nfaPD.js +1 -0
  76. package/dist/assets/subMonths-DHPWOZP6.js +1 -0
  77. package/dist/assets/{summary-Bbsq64IN.js → summary-Dfiju7xJ.js} +1 -1
  78. package/dist/assets/{toggle-expansion-button-BrKkZtGj.js → toggle-expansion-button-BbVh8z6s.js} +1 -1
  79. package/dist/assets/tooltip-C-fd9L1l.js +1 -0
  80. package/dist/assets/{use-url-query-C0mVgO2i.js → use-url-query-5TFyEk1x.js} +1 -1
  81. package/dist/index.html +2 -2
  82. package/package.json +1 -1
  83. package/src/components/business/admin-business-section.tsx +103 -282
  84. package/src/components/clients/contracts/modify-contract-dialog.tsx +341 -253
  85. package/src/components/contracts/cells/client.tsx +24 -0
  86. package/src/components/contracts/cells/date.tsx +10 -0
  87. package/src/components/contracts/cells/index.ts +2 -0
  88. package/src/components/contracts/columns.tsx +182 -0
  89. package/src/components/contracts/contracts-filter.tsx +160 -0
  90. package/src/components/contracts/index.tsx +264 -0
  91. package/src/components/contracts/issue-documents-modal.tsx +234 -0
  92. package/src/components/layout/sidelinks.tsx +7 -0
  93. package/src/components/screens/businesses/clients/contracts/contracts.tsx +56 -0
  94. package/src/gql/gql.ts +27 -3
  95. package/src/gql/graphql.ts +73 -52
  96. package/src/router/config.tsx +13 -0
  97. package/src/router/routes.ts +1 -0
  98. package/dist/assets/business-CAcKSrnu.js +0 -37
  99. package/dist/assets/business-trip-DkTEY8b1.js +0 -1
  100. package/dist/assets/charges-filters-DUuY-N0H.js +0 -1
  101. package/dist/assets/index-BD4s2ucv.css +0 -1
  102. package/dist/assets/index-BFwWHKS1.js +0 -1
  103. package/dist/assets/index-BI0XToIk.js +0 -1
  104. package/dist/assets/index-BQb_C-JL.js +0 -1
  105. package/dist/assets/index-CJQlHnFs.js +0 -1
  106. package/dist/assets/index-Cm6Blkgf.js +0 -1
  107. package/dist/assets/index-DJLQkpmI.js +0 -6
  108. package/dist/assets/index-DcwGbXWr.js +0 -1
  109. package/dist/assets/index-Dlj3jpuW.js +0 -1
  110. package/dist/assets/index-iya2tfvn.js +0 -1
  111. package/dist/assets/issue-document-_iHCFO9h.js +0 -1
  112. package/dist/assets/login-page-BtT_TCEJ.js +0 -1
  113. package/dist/assets/similar-transactions-modal-B39q1NLa.js +0 -1
  114. package/dist/assets/sub-DOjETdwl.js +0 -1
  115. package/dist/assets/subMonths-DqeFZ4O6.js +0 -1
  116. package/dist/assets/tooltip-CbnJYNc4.js +0 -1
@@ -0,0 +1,24 @@
1
+ import { type ReactElement } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { ROUTES } from '@/router/routes.js';
4
+
5
+ type Props = {
6
+ id: string;
7
+ name: string;
8
+ };
9
+
10
+ export function Client({ id, name }: Props): ReactElement {
11
+ return (
12
+ <div className="flex flex-wrap flex-col justify-center">
13
+ <Link
14
+ to={ROUTES.BUSINESSES.DETAIL(id)}
15
+ target="_blank"
16
+ rel="noreferrer"
17
+ onClick={event => event.stopPropagation()}
18
+ className="inline-flex items-center font-semibold"
19
+ >
20
+ {name}
21
+ </Link>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,10 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { TimelessDateString } from '@/helpers/index.js';
3
+
4
+ type Props = {
5
+ timelessDate: TimelessDateString;
6
+ };
7
+
8
+ export const DateCell = ({ timelessDate }: Props): ReactElement => {
9
+ return <p className="text-sm font-medium">{new Date(timelessDate).toLocaleDateString()}</p>;
10
+ };
@@ -0,0 +1,2 @@
1
+ export { DateCell } from './date.js';
2
+ export { Client } from './client.js';
@@ -0,0 +1,182 @@
1
+ import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff, LinkIcon } from 'lucide-react';
2
+ import { Link } from 'react-router-dom';
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuSeparator,
8
+ DropdownMenuTrigger,
9
+ } from '@/components/ui/dropdown-menu.js';
10
+ import type { BillingCycle, SubscriptionPlan } from '@/gql/graphql.js';
11
+ import type { TimelessDateString } from '@/helpers/dates.js';
12
+ import { cn } from '@/lib/utils.js';
13
+ import type { Column, ColumnDef } from '@tanstack/react-table';
14
+ import { ModifyContractDialog } from '../clients/contracts/modify-contract-dialog.js';
15
+ import { Badge } from '../ui/badge.js';
16
+ import { Button } from '../ui/button.js';
17
+ import { Checkbox } from '../ui/checkbox.js';
18
+ import { Client, DateCell } from './cells/index.js';
19
+ import type { ContractRow } from './index.js';
20
+
21
+ interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
22
+ column: Column<TData, TValue>;
23
+ title: string;
24
+ }
25
+ export function DataTableColumnHeader<TData, TValue>({
26
+ column,
27
+ title,
28
+ className,
29
+ }: DataTableColumnHeaderProps<TData, TValue>) {
30
+ if (!column.getCanSort()) {
31
+ return <div className={cn(className)}>{title}</div>;
32
+ }
33
+ return (
34
+ <div className={cn('flex items-center gap-2', className)}>
35
+ <DropdownMenu>
36
+ <DropdownMenuTrigger asChild>
37
+ <Button variant="ghost" size="sm" className="data-[state=open]:bg-accent -ml-3 h-8">
38
+ <span>{title}</span>
39
+ {column.getIsSorted() === 'desc' ? (
40
+ <ArrowDown />
41
+ ) : column.getIsSorted() === 'asc' ? (
42
+ <ArrowUp />
43
+ ) : (
44
+ <ChevronsUpDown />
45
+ )}
46
+ </Button>
47
+ </DropdownMenuTrigger>
48
+ <DropdownMenuContent align="start">
49
+ <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
50
+ <ArrowUp />
51
+ Asc
52
+ </DropdownMenuItem>
53
+ <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
54
+ <ArrowDown />
55
+ Desc
56
+ </DropdownMenuItem>
57
+ <DropdownMenuSeparator />
58
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
59
+ <EyeOff />
60
+ Hide
61
+ </DropdownMenuItem>
62
+ </DropdownMenuContent>
63
+ </DropdownMenu>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ export const columns: ColumnDef<ContractRow>[] = [
69
+ {
70
+ id: 'select',
71
+ header: ({ table }) => (
72
+ <Checkbox
73
+ checked={
74
+ table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')
75
+ }
76
+ onCheckedChange={value => table.toggleAllPageRowsSelected(!!value)}
77
+ aria-label="Select all"
78
+ />
79
+ ),
80
+ cell: ({ row }) => (
81
+ <Checkbox
82
+ checked={row.getIsSelected()}
83
+ onCheckedChange={value => row.toggleSelected(!!value)}
84
+ aria-label="Select row"
85
+ />
86
+ ),
87
+ enableSorting: false,
88
+ enableHiding: false,
89
+ },
90
+ {
91
+ accessorKey: 'isActive',
92
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Is Active" />,
93
+ cell: ({ row }) => {
94
+ const isActive = row.getValue<boolean>('isActive');
95
+ return isActive ? (
96
+ <Badge variant="default">Active</Badge>
97
+ ) : (
98
+ <Badge variant="destructive">Inactive</Badge>
99
+ );
100
+ },
101
+ },
102
+ {
103
+ accessorKey: 'client.name',
104
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Client" />,
105
+ cell: ({ row }) => {
106
+ return <Client id={row.original.client.id} name={row.original.client.name} />;
107
+ },
108
+ },
109
+ {
110
+ accessorKey: 'startDate',
111
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Start" />,
112
+ cell: ({ row }) => <DateCell timelessDate={row.getValue<TimelessDateString>('startDate')} />,
113
+ },
114
+ {
115
+ accessorKey: 'endDate',
116
+ header: ({ column }) => <DataTableColumnHeader column={column} title="End" />,
117
+ cell: ({ row }) => <DateCell timelessDate={row.getValue<TimelessDateString>('endDate')} />,
118
+ },
119
+ {
120
+ accessorKey: 'purchaseOrder',
121
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Purchase Order" />,
122
+ cell: ({ row }) => (
123
+ <div className="flex flex-row gap-1 items-center">
124
+ {row.original.msCloud && (
125
+ <Link to={row.original.msCloud} target="_blank" rel="noreferrer" className="size-8">
126
+ <Button variant="link" size="sm" disabled>
127
+ <LinkIcon className="size-4" />
128
+ </Button>
129
+ </Link>
130
+ )}
131
+ <span className="text-sm font-medium">{row.getValue<string>('purchaseOrder')}</span>
132
+ </div>
133
+ ),
134
+ },
135
+ {
136
+ accessorKey: 'product',
137
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Product" />,
138
+ cell: ({ row }) => <p className="text-sm font-medium">{row.getValue<string>('product')}</p>,
139
+ },
140
+ {
141
+ accessorKey: 'plan',
142
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Subscription Plan" />,
143
+ cell: ({ row }) => (
144
+ <p className="text-sm font-medium">{row.getValue<SubscriptionPlan>('plan')}</p>
145
+ ),
146
+ },
147
+ {
148
+ accessorKey: 'billingCycle',
149
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Billing Cycle" />,
150
+ cell: ({ row }) => (
151
+ <p className="text-sm font-medium">{row.getValue<BillingCycle>('billingCycle')}</p>
152
+ ),
153
+ },
154
+ {
155
+ accessorKey: 'amount.raw',
156
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Amount" />,
157
+ cell: ({ row }) => <p className="text-sm font-medium">{row.original.amount.formatted}</p>,
158
+ },
159
+ {
160
+ accessorKey: 'operationsLimit',
161
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Operations Limit" />,
162
+ cell: ({ row }) => {
163
+ const operationsLimit = row.getValue<number>('operationsLimit');
164
+ if (!operationsLimit) {
165
+ return null;
166
+ }
167
+ return <p className="text-sm font-medium">{operationsLimit}</p>;
168
+ },
169
+ },
170
+ {
171
+ accessorKey: 'edit',
172
+ header: '',
173
+ cell: ({ row }) => (
174
+ <ModifyContractDialog
175
+ clientId={row.original.client.id}
176
+ contractId={row.original.id}
177
+ // onDone={refetch}
178
+ />
179
+ ),
180
+ enableSorting: false,
181
+ },
182
+ ];
@@ -0,0 +1,160 @@
1
+ import { useState } from 'react';
2
+ import { Filter } from 'lucide-react';
3
+ import { Button } from '@/components/ui/button.js';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from '@/components/ui/dialog.js';
12
+ import {
13
+ Select,
14
+ SelectContent,
15
+ SelectItem,
16
+ SelectTrigger,
17
+ SelectValue,
18
+ } from '@/components/ui/select.js';
19
+ import { BillingCycle, Product, SubscriptionPlan } from '@/gql/graphql.js';
20
+ import { standardBillingCycle, standardPlan } from '@/helpers/index.js';
21
+ import type { Table } from '@tanstack/react-table';
22
+ import { Label } from '../ui/label.js';
23
+ import type { ContractRow } from './index.js';
24
+
25
+ interface Props {
26
+ table: Table<ContractRow>;
27
+ }
28
+
29
+ export function ContractsFilter({ table }: Props) {
30
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
31
+
32
+ return (
33
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
34
+ <DialogTrigger asChild>
35
+ <Button size="sm" variant="outline">
36
+ <Filter className="size-4" />
37
+ </Button>
38
+ </DialogTrigger>
39
+ <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
40
+ <DialogHeader>
41
+ <DialogTitle>Filter Contracts</DialogTitle>
42
+ <DialogDescription>Filter contracts based on various criteria</DialogDescription>
43
+ </DialogHeader>
44
+ <div className="grid gap-4 py-4">
45
+ <div className="grid gap-4 md:grid-cols-2">
46
+ <div className="space-y-2">
47
+ <Label>Product Type</Label>
48
+ <Select
49
+ onValueChange={value =>
50
+ table.getColumn('product')?.setFilterValue(value === 'NULL' ? '' : value)
51
+ }
52
+ value={table.getColumn('product')?.getFilterValue() as Product | ''}
53
+ >
54
+ <SelectTrigger className="w-full">
55
+ <SelectValue />
56
+ </SelectTrigger>
57
+ <SelectContent>
58
+ <SelectItem value="NULL" className="font-light text-xs">
59
+ (None)
60
+ </SelectItem>
61
+ {Object.values(Product).map(product => (
62
+ <SelectItem key={product} value={product}>
63
+ {product}
64
+ </SelectItem>
65
+ ))}
66
+ </SelectContent>
67
+ </Select>
68
+ </div>
69
+ <div className="space-y-2">
70
+ <Label>Billing Cycle</Label>
71
+ <Select
72
+ onValueChange={value =>
73
+ table.getColumn('billingCycle')?.setFilterValue(value === 'NULL' ? '' : value)
74
+ }
75
+ value={table.getColumn('billingCycle')?.getFilterValue() as BillingCycle | ''}
76
+ >
77
+ <SelectTrigger className="w-full">
78
+ <SelectValue />
79
+ </SelectTrigger>
80
+ <SelectContent>
81
+ <SelectItem value="NULL" className="font-light text-xs">
82
+ (None)
83
+ </SelectItem>
84
+ {Object.values(BillingCycle).map(cycle => (
85
+ <SelectItem key={cycle} value={cycle}>
86
+ {standardBillingCycle(cycle)}
87
+ </SelectItem>
88
+ ))}
89
+ </SelectContent>
90
+ </Select>
91
+ </div>
92
+ </div>
93
+
94
+ <div className="grid gap-4 md:grid-cols-2">
95
+ <div className="space-y-2">
96
+ <Label>Subscription Plan</Label>
97
+ <Select
98
+ onValueChange={value =>
99
+ table.getColumn('plan')?.setFilterValue(value === 'NULL' ? '' : value)
100
+ }
101
+ value={table.getColumn('plan')?.getFilterValue() as SubscriptionPlan | ''}
102
+ >
103
+ <SelectTrigger className="w-full">
104
+ <SelectValue />
105
+ </SelectTrigger>
106
+ <SelectContent>
107
+ <SelectItem value="NULL" className="font-light text-xs">
108
+ (None)
109
+ </SelectItem>
110
+ {Object.values(SubscriptionPlan).map(plan => (
111
+ <SelectItem key={plan} value={plan}>
112
+ {standardPlan(plan)}
113
+ </SelectItem>
114
+ ))}
115
+ </SelectContent>
116
+ </Select>
117
+ </div>
118
+
119
+ <div className="space-y-2">
120
+ <Label>Is Active</Label>
121
+ <Select
122
+ onValueChange={value =>
123
+ table
124
+ .getColumn('isActive')
125
+ ?.setFilterValue(value === 'NULL' ? '' : value === 'active' ? true : false)
126
+ }
127
+ value={convertBooleanToString(
128
+ table.getColumn('isActive')?.getFilterValue() as boolean | undefined,
129
+ )}
130
+ >
131
+ <SelectTrigger className="w-full">
132
+ <SelectValue />
133
+ </SelectTrigger>
134
+ <SelectContent>
135
+ <SelectItem value="NULL" className="font-light text-xs">
136
+ (None)
137
+ </SelectItem>
138
+ <SelectItem value="active">Active</SelectItem>
139
+ <SelectItem value="inactive">Inactive</SelectItem>
140
+ </SelectContent>
141
+ </Select>
142
+ </div>
143
+
144
+ {/* <div>
145
+ <FormLabel className="text-base">Active Status</FormLabel>
146
+ <Switch checked={field.value} onCheckedChange={field.onChange} />
147
+ </div> */}
148
+ </div>
149
+ </div>
150
+ </DialogContent>
151
+ </Dialog>
152
+ );
153
+ }
154
+
155
+ function convertBooleanToString(value: boolean | undefined): string {
156
+ if (value === undefined) {
157
+ return 'NULL';
158
+ }
159
+ return value ? 'active' : 'inactive';
160
+ }
@@ -0,0 +1,264 @@
1
+ import { useEffect, useMemo, useState, type ReactElement } from 'react';
2
+ import { ChevronDown } from 'lucide-react';
3
+ import { Pagination } from '@/components/common/index.js';
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@/components/ui/table.js';
12
+ import { ContractForContractsTableFieldsFragmentDoc } from '@/gql/graphql.js';
13
+ import { getFragmentData, type FragmentType } from '@/gql/index.js';
14
+ import type { TimelessDateString } from '@/helpers/dates.js';
15
+ import {
16
+ flexRender,
17
+ getCoreRowModel,
18
+ getFilteredRowModel,
19
+ getPaginationRowModel,
20
+ getSortedRowModel,
21
+ useReactTable,
22
+ type ColumnFiltersState,
23
+ type SortingState,
24
+ } from '@tanstack/react-table';
25
+ import type { BillingCycle, Product, SubscriptionPlan } from '../../gql/graphql.js';
26
+ import { Button } from '../ui/button.js';
27
+ import {
28
+ DropdownMenu,
29
+ DropdownMenuCheckboxItem,
30
+ DropdownMenuContent,
31
+ DropdownMenuTrigger,
32
+ } from '../ui/dropdown-menu.js';
33
+ import { columns } from './columns.js';
34
+ import { ContractsFilter } from './contracts-filter.js';
35
+ import { IssueDocumentsModal } from './issue-documents-modal.js';
36
+
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
38
+ /* GraphQL */ `
39
+ fragment ContractForContractsTableFields on Contract {
40
+ id
41
+ isActive
42
+ client {
43
+ id
44
+ originalBusiness {
45
+ id
46
+ name
47
+ }
48
+ }
49
+ purchaseOrders
50
+ startDate
51
+ endDate
52
+ amount {
53
+ raw
54
+ formatted
55
+ }
56
+ billingCycle
57
+ product
58
+ plan
59
+ operationsLimit
60
+ msCloud
61
+ # documentType
62
+ # remarks
63
+ # operationsLimit
64
+ }
65
+ `;
66
+
67
+ export interface ContractRow {
68
+ id: string;
69
+ isActive: boolean;
70
+ client: {
71
+ id: string;
72
+ name: string;
73
+ };
74
+ purchaseOrder?: string;
75
+ startDate: TimelessDateString;
76
+ endDate: TimelessDateString;
77
+ amount: {
78
+ raw: number;
79
+ formatted: string;
80
+ };
81
+ billingCycle: BillingCycle;
82
+ product?: Product;
83
+ plan?: SubscriptionPlan;
84
+ operationsLimit?: number;
85
+ msCloud?: string;
86
+ // documentType
87
+ // remarks
88
+ // operationsLimit
89
+ }
90
+
91
+ function convertContractFragmentToTableRow(
92
+ data: FragmentType<typeof ContractForContractsTableFieldsFragmentDoc>,
93
+ ): ContractRow {
94
+ const fragmentData = getFragmentData(ContractForContractsTableFieldsFragmentDoc, data);
95
+ return {
96
+ id: fragmentData.id,
97
+ isActive: fragmentData.isActive,
98
+ client: {
99
+ id: fragmentData.client.id,
100
+ name: fragmentData.client.originalBusiness.name,
101
+ },
102
+ purchaseOrder: fragmentData.purchaseOrders[0],
103
+ startDate: fragmentData.startDate,
104
+ endDate: fragmentData.endDate,
105
+ amount: {
106
+ raw: fragmentData.amount.raw,
107
+ formatted: fragmentData.amount.formatted,
108
+ },
109
+ billingCycle: fragmentData.billingCycle,
110
+ product: fragmentData.product ?? undefined,
111
+ plan: fragmentData.plan ?? undefined,
112
+ operationsLimit: fragmentData.operationsLimit,
113
+ msCloud: fragmentData.msCloud?.toString() ?? undefined,
114
+ };
115
+ }
116
+
117
+ type Props = {
118
+ data: FragmentType<typeof ContractForContractsTableFieldsFragmentDoc>[];
119
+ onChange?: () => void;
120
+ };
121
+
122
+ export const ContractsTable = ({ data }: Props): ReactElement => {
123
+ const [sorting, setSorting] = useState<SortingState>([
124
+ {
125
+ id: 'endDate',
126
+ desc: true,
127
+ },
128
+ {
129
+ id: 'amount.raw',
130
+ desc: true,
131
+ },
132
+ ]);
133
+ const [rowSelection, setRowSelection] = useState({});
134
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
135
+ const [selectedContractIds, setSelectedContractIds] = useState<string[]>([]);
136
+
137
+ const contracts = useMemo(
138
+ () => data.map(rawContract => convertContractFragmentToTableRow(rawContract)),
139
+ [data],
140
+ );
141
+
142
+ const table = useReactTable({
143
+ data: contracts,
144
+ columns,
145
+ getCoreRowModel: getCoreRowModel(),
146
+ onSortingChange: setSorting,
147
+ getSortedRowModel: getSortedRowModel(),
148
+ getPaginationRowModel: getPaginationRowModel(),
149
+ onRowSelectionChange: setRowSelection,
150
+ onColumnFiltersChange: setColumnFilters,
151
+ getFilteredRowModel: getFilteredRowModel(),
152
+ state: {
153
+ sorting,
154
+ rowSelection,
155
+ columnFilters,
156
+ },
157
+ initialState: {
158
+ pagination: {
159
+ pageIndex: 0,
160
+ pageSize: 50,
161
+ },
162
+ },
163
+ });
164
+
165
+ useEffect(() => {
166
+ console.log('Row selection changed:', rowSelection);
167
+ const selectedIndexes = Object.entries(rowSelection)
168
+ .filter(([, value]) => !!value)
169
+ .map(([key]) => Number(key));
170
+ const newSelectedContractIds = Array.from(
171
+ new Set(contracts.filter((_, i) => selectedIndexes.includes(i)).map(c => c.id)),
172
+ );
173
+ if (
174
+ selectedContractIds.length !== newSelectedContractIds.length ||
175
+ !selectedContractIds.every(id => newSelectedContractIds.includes(id))
176
+ ) {
177
+ setSelectedContractIds(newSelectedContractIds);
178
+ }
179
+ }, [rowSelection, contracts, selectedContractIds, columnFilters]);
180
+
181
+ return (
182
+ <div className="w-full">
183
+ <div className="flex items-center py-4 gap-4">
184
+ <ContractsFilter table={table} />
185
+ <DropdownMenu>
186
+ <DropdownMenuTrigger asChild>
187
+ <Button variant="outline">
188
+ Columns <ChevronDown />
189
+ </Button>
190
+ </DropdownMenuTrigger>
191
+ <DropdownMenuContent align="end">
192
+ {table
193
+ .getAllColumns()
194
+ .filter(column => column.getCanHide())
195
+ .map(column => {
196
+ return (
197
+ <DropdownMenuCheckboxItem
198
+ key={column.id}
199
+ className="capitalize"
200
+ checked={column.getIsVisible()}
201
+ onCheckedChange={value => column.toggleVisibility(!!value)}
202
+ >
203
+ {column.id}
204
+ </DropdownMenuCheckboxItem>
205
+ );
206
+ })}
207
+ </DropdownMenuContent>
208
+ </DropdownMenu>
209
+
210
+ <div className="ml-auto">
211
+ <IssueDocumentsModal contractIds={selectedContractIds} className="ml-auto" />
212
+ </div>
213
+ </div>
214
+ <div className="overflow-hidden rounded-md border">
215
+ <Table>
216
+ <TableHeader>
217
+ {table.getHeaderGroups().map(headerGroup => (
218
+ <TableRow key={headerGroup.id}>
219
+ {headerGroup.headers.map(header => (
220
+ <TableHead key={header.id} colSpan={header.colSpan}>
221
+ {header.isPlaceholder
222
+ ? null
223
+ : flexRender(header.column.columnDef.header, header.getContext())}
224
+ </TableHead>
225
+ ))}
226
+ </TableRow>
227
+ ))}
228
+ </TableHeader>
229
+ <TableBody>
230
+ {table.getRowModel().rows?.length ? (
231
+ table.getRowModel().rows.map(row => (
232
+ <TableRow key={row.id}>
233
+ {row.getVisibleCells().map(cell => (
234
+ <TableCell key={cell.id}>
235
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
236
+ </TableCell>
237
+ ))}
238
+ </TableRow>
239
+ ))
240
+ ) : (
241
+ <TableRow>
242
+ <TableCell colSpan={columns.length} className="h-24 text-center">
243
+ No results.
244
+ </TableCell>
245
+ </TableRow>
246
+ )}
247
+ </TableBody>
248
+ </Table>
249
+ </div>
250
+ <div className="flex items-center justify-end space-x-2 py-4">
251
+ <div className="text-muted-foreground flex-1 text-sm">
252
+ {table.getFilteredSelectedRowModel().rows.length} of{' '}
253
+ {table.getFilteredRowModel().rows.length} row(s) selected.
254
+ </div>
255
+ <Pagination
256
+ className="w-fit mx-0"
257
+ value={table.getState().pagination.pageIndex}
258
+ total={table.getPageCount()}
259
+ onChange={page => table.setPageIndex(page)}
260
+ />
261
+ </div>
262
+ </div>
263
+ );
264
+ };