@contractspec/example.crm-pipeline 3.7.19 → 3.7.22

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 (42) hide show
  1. package/.turbo/turbo-build.log +32 -32
  2. package/CHANGELOG.md +62 -0
  3. package/dist/browser/crm-pipeline.feature.js +1 -1
  4. package/dist/browser/example.js +1 -1
  5. package/dist/browser/index.js +5 -5
  6. package/dist/browser/ui/CrmDashboard.js +1 -1
  7. package/dist/browser/ui/hooks/index.js +1 -1
  8. package/dist/browser/ui/hooks/useDealList.js +1 -1
  9. package/dist/browser/ui/index.js +4 -4
  10. package/dist/browser/ui/renderers/index.js +3 -3
  11. package/dist/browser/ui/renderers/pipeline.renderer.js +1 -1
  12. package/dist/browser/ui/tables/DealListTab.js +1 -1
  13. package/dist/crm-pipeline.feature.js +1 -1
  14. package/dist/example.d.ts +3 -2
  15. package/dist/example.js +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +5 -5
  18. package/dist/node/crm-pipeline.feature.js +1 -1
  19. package/dist/node/example.js +1 -1
  20. package/dist/node/index.js +5 -5
  21. package/dist/node/ui/CrmDashboard.js +1 -1
  22. package/dist/node/ui/hooks/index.js +1 -1
  23. package/dist/node/ui/hooks/useDealList.js +1 -1
  24. package/dist/node/ui/index.js +4 -4
  25. package/dist/node/ui/renderers/index.js +3 -3
  26. package/dist/node/ui/renderers/pipeline.renderer.js +1 -1
  27. package/dist/node/ui/tables/DealListTab.js +1 -1
  28. package/dist/ui/CrmDashboard.js +1 -1
  29. package/dist/ui/hooks/index.js +1 -1
  30. package/dist/ui/hooks/useDealList.js +1 -1
  31. package/dist/ui/index.js +4 -4
  32. package/dist/ui/renderers/index.js +3 -3
  33. package/dist/ui/renderers/pipeline.renderer.js +1 -1
  34. package/dist/ui/tables/DealListTab.d.ts +5 -1
  35. package/dist/ui/tables/DealListTab.js +1 -1
  36. package/package.json +11 -11
  37. package/src/crm-pipeline.feature.ts +1 -1
  38. package/src/example.ts +15 -26
  39. package/src/index.ts +1 -0
  40. package/src/ui/hooks/useDealList.ts +1 -1
  41. package/src/ui/tables/DealListTab.smoke.test.tsx +70 -5
  42. package/src/ui/tables/DealListTab.tsx +96 -8
@@ -20,16 +20,26 @@ beforeAll(() => {
20
20
  value: SyntaxError,
21
21
  configurable: true,
22
22
  });
23
+ const encodeURIComponentFromGlobal =
24
+ globalThis.encodeURIComponent.bind(globalThis);
25
+ const decodeURIComponentFromGlobal =
26
+ globalThis.decodeURIComponent.bind(globalThis);
27
+ Object.assign(windowInstance, {
28
+ encodeURIComponent: encodeURIComponentFromGlobal,
29
+ decodeURIComponent: decodeURIComponentFromGlobal,
30
+ });
23
31
  Object.assign(globalThis, {
24
32
  window: windowInstance,
25
33
  document: windowInstance.document,
26
34
  navigator: windowInstance.navigator,
35
+ location: windowInstance.location,
27
36
  HTMLElement: windowInstance.HTMLElement,
28
37
  HTMLButtonElement: windowInstance.HTMLButtonElement,
29
38
  Node: windowInstance.Node,
30
39
  Event: windowInstance.Event,
31
40
  MouseEvent: windowInstance.MouseEvent,
32
41
  MutationObserver: windowInstance.MutationObserver,
42
+ DocumentFragment: windowInstance.DocumentFragment,
33
43
  getComputedStyle: windowInstance.getComputedStyle.bind(windowInstance),
34
44
  requestAnimationFrame: (callback: FrameRequestCallback) =>
35
45
  setTimeout(() => callback(Date.now()), 0),
@@ -45,10 +55,23 @@ afterEach(() => {
45
55
  function sortDeals(
46
56
  pageIndex: number,
47
57
  pageSize: number,
48
- sorting: { id: string; desc: boolean }[]
58
+ sorting: { id: string; desc: boolean }[],
59
+ search: string,
60
+ status: 'OPEN' | 'WON' | 'LOST' | 'all'
49
61
  ) {
50
62
  const [sort] = sorting;
51
- const sorted = [...TEST_DEALS].sort((left, right) => {
63
+ const filtered = TEST_DEALS.filter((deal) => {
64
+ const matchesStatus = status === 'all' ? true : deal.status === status;
65
+ const matchesSearch =
66
+ !search ||
67
+ [deal.name, deal.companyId, deal.contactId, deal.notes, deal.ownerId]
68
+ .filter(Boolean)
69
+ .join(' ')
70
+ .toLowerCase()
71
+ .includes(search.toLowerCase());
72
+ return matchesStatus && matchesSearch;
73
+ });
74
+ const sorted = [...filtered].sort((left, right) => {
52
75
  const leftValue =
53
76
  sort?.id === 'deal'
54
77
  ? left.name
@@ -82,15 +105,50 @@ function Harness() {
82
105
  pageIndex: 0,
83
106
  pageSize: 3,
84
107
  });
108
+ const [search, setSearch] = React.useState('');
109
+ const [status, setStatus] = React.useState<'OPEN' | 'WON' | 'LOST' | 'all'>(
110
+ 'all'
111
+ );
112
+ const filteredDeals = sortDeals(
113
+ pagination.pageIndex,
114
+ pagination.pageSize,
115
+ sorting,
116
+ search,
117
+ status
118
+ );
119
+ const totalItems = TEST_DEALS.filter((deal) => {
120
+ const matchesStatus = status === 'all' ? true : deal.status === status;
121
+ const matchesSearch =
122
+ !search ||
123
+ [deal.name, deal.companyId, deal.contactId, deal.notes, deal.ownerId]
124
+ .filter(Boolean)
125
+ .join(' ')
126
+ .toLowerCase()
127
+ .includes(search.toLowerCase());
128
+ return matchesStatus && matchesSearch;
129
+ }).length;
85
130
  return (
86
131
  <DealListDataTable
87
- deals={sortDeals(pagination.pageIndex, pagination.pageSize, sorting)}
88
- totalItems={TEST_DEALS.length}
132
+ deals={filteredDeals}
133
+ totalItems={totalItems}
89
134
  pageIndex={pagination.pageIndex}
90
135
  pageSize={pagination.pageSize}
91
136
  sorting={sorting}
92
- onSortingChange={setSorting}
137
+ search={search}
138
+ status={status}
139
+ onSortingChange={(nextSorting) => {
140
+ setSorting(nextSorting);
141
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
142
+ }}
93
143
  onPaginationChange={setPagination}
144
+ onSearchChange={(value) => {
145
+ setSearch(value);
146
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
147
+ }}
148
+ onStatusChange={(value) => {
149
+ setStatus(value);
150
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
151
+ }}
94
152
  />
95
153
  );
96
154
  }
@@ -142,6 +200,13 @@ describe('DealListDataTable', () => {
142
200
  'Affichage de 4 à 6 sur 6 résultats'
143
201
  );
144
202
 
203
+ await click(
204
+ [...container.getElementsByTagName('button')].find(
205
+ (button) => button.textContent?.trim() === 'Won Only'
206
+ )
207
+ );
208
+ expect(container.textContent).toContain('Status: WON');
209
+
145
210
  await act(async () => {
146
211
  root.unmount();
147
212
  });
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  Button,
5
5
  DataTable,
6
+ DataTableToolbar,
6
7
  LoaderBlock,
7
8
  } from '@contractspec/lib.design-system';
8
9
  import type { ContractTableSort } from '@contractspec/lib.presentation-runtime-core';
@@ -41,24 +42,73 @@ export interface DealListDataTableProps {
41
42
  pageIndex: number;
42
43
  pageSize: number;
43
44
  sorting: ContractTableSort[];
45
+ search: string;
46
+ status: 'OPEN' | 'WON' | 'LOST' | 'all';
44
47
  loading?: boolean;
45
48
  onSortingChange: (sorting: ContractTableSort[]) => void;
46
49
  onPaginationChange: (pagination: {
47
50
  pageIndex: number;
48
51
  pageSize: number;
49
52
  }) => void;
53
+ onSearchChange: (value: string) => void;
54
+ onStatusChange: (value: 'OPEN' | 'WON' | 'LOST' | 'all') => void;
50
55
  onDealClick?: (dealId: string) => void;
51
56
  }
52
57
 
58
+ function buildStatusActions({
59
+ value,
60
+ onChange,
61
+ }: {
62
+ value: 'OPEN' | 'WON' | 'LOST' | 'all';
63
+ onChange: (value: 'OPEN' | 'WON' | 'LOST' | 'all') => void;
64
+ }) {
65
+ return (
66
+ <HStack gap="sm" className="flex-wrap">
67
+ <Button
68
+ variant={value === 'all' ? 'secondary' : 'outline'}
69
+ size="sm"
70
+ onPress={() => onChange('all')}
71
+ >
72
+ All Deals
73
+ </Button>
74
+ <Button
75
+ variant={value === 'OPEN' ? 'secondary' : 'outline'}
76
+ size="sm"
77
+ onPress={() => onChange('OPEN')}
78
+ >
79
+ Open Only
80
+ </Button>
81
+ <Button
82
+ variant={value === 'WON' ? 'secondary' : 'outline'}
83
+ size="sm"
84
+ onPress={() => onChange('WON')}
85
+ >
86
+ Won Only
87
+ </Button>
88
+ <Button
89
+ variant={value === 'LOST' ? 'secondary' : 'outline'}
90
+ size="sm"
91
+ onPress={() => onChange('LOST')}
92
+ >
93
+ Lost Only
94
+ </Button>
95
+ </HStack>
96
+ );
97
+ }
98
+
53
99
  export function DealListDataTable({
54
100
  deals,
55
101
  totalItems,
56
102
  pageIndex,
57
103
  pageSize,
58
104
  sorting,
105
+ search,
106
+ status,
59
107
  loading,
60
108
  onSortingChange,
61
109
  onPaginationChange,
110
+ onSearchChange,
111
+ onStatusChange,
62
112
  onDealClick,
63
113
  }: DealListDataTableProps) {
64
114
  const controller = useContractTable<Deal>({
@@ -216,14 +266,36 @@ export function DealListDataTable({
216
266
  description="Server-mode table using the shared ContractSpec controller."
217
267
  loading={loading}
218
268
  toolbar={
219
- <HStack gap="sm" className="flex-wrap">
220
- <Text className="text-muted-foreground text-sm">
221
- Selected {controller.selectedRowIds.length}
222
- </Text>
223
- <Text className="text-muted-foreground text-sm">
224
- {totalItems} total deals
225
- </Text>
226
- </HStack>
269
+ <DataTableToolbar
270
+ controller={controller}
271
+ searchPlaceholder="Search deals, companies, contacts, or notes"
272
+ searchValue={search}
273
+ onSearchChange={onSearchChange}
274
+ activeChips={
275
+ status === 'all'
276
+ ? []
277
+ : [
278
+ {
279
+ key: 'status',
280
+ label: `Status: ${status}`,
281
+ onRemove: () => onStatusChange('all'),
282
+ },
283
+ ]
284
+ }
285
+ onClearAll={() => {
286
+ onSearchChange('');
287
+ onStatusChange('all');
288
+ }}
289
+ actionsStart={buildStatusActions({
290
+ value: status,
291
+ onChange: onStatusChange,
292
+ })}
293
+ actionsEnd={
294
+ <Text className="text-muted-foreground text-sm">
295
+ {totalItems} total deals
296
+ </Text>
297
+ }
298
+ />
227
299
  }
228
300
  footer={`Page ${controller.pageIndex + 1} of ${controller.pageCount}`}
229
301
  emptyState={
@@ -247,9 +319,15 @@ export function DealListTab({
247
319
  pageIndex: 0,
248
320
  pageSize: 3,
249
321
  });
322
+ const [search, setSearch] = React.useState('');
323
+ const [status, setStatus] = React.useState<'OPEN' | 'WON' | 'LOST' | 'all'>(
324
+ 'all'
325
+ );
250
326
  const { data, loading } = useDealList({
251
327
  pageIndex: pagination.pageIndex,
252
328
  pageSize: pagination.pageSize,
329
+ search,
330
+ status,
253
331
  sorting,
254
332
  });
255
333
 
@@ -264,12 +342,22 @@ export function DealListTab({
264
342
  pageIndex={pagination.pageIndex}
265
343
  pageSize={pagination.pageSize}
266
344
  sorting={sorting}
345
+ search={search}
346
+ status={status}
267
347
  loading={loading}
268
348
  onSortingChange={(nextSorting) => {
269
349
  setSorting(nextSorting);
270
350
  setPagination((current) => ({ ...current, pageIndex: 0 }));
271
351
  }}
272
352
  onPaginationChange={setPagination}
353
+ onSearchChange={(value) => {
354
+ setSearch(value);
355
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
356
+ }}
357
+ onStatusChange={(value) => {
358
+ setStatus(value);
359
+ setPagination((current) => ({ ...current, pageIndex: 0 }));
360
+ }}
273
361
  onDealClick={onDealClick}
274
362
  />
275
363
  );