@contractspec/example.crm-pipeline 3.7.6 → 3.7.10

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 (130) hide show
  1. package/.turbo/turbo-build.log +45 -42
  2. package/AGENTS.md +51 -33
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +67 -148
  5. package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
  6. package/dist/browser/docs/index.js +1 -1
  7. package/dist/browser/events/contact.event.js +1 -1
  8. package/dist/browser/events/deal.event.js +1 -1
  9. package/dist/browser/events/index.js +3 -3
  10. package/dist/browser/events/task.event.js +1 -1
  11. package/dist/browser/handlers/crm.handlers.js +13 -2
  12. package/dist/browser/handlers/index.js +13 -2
  13. package/dist/browser/index.js +680 -447
  14. package/dist/browser/ui/CrmDashboard.js +574 -352
  15. package/dist/browser/ui/CrmDealCard.js +5 -5
  16. package/dist/browser/ui/CrmPipelineBoard.js +13 -13
  17. package/dist/browser/ui/hooks/index.js +21 -10
  18. package/dist/browser/ui/hooks/useDealList.js +20 -9
  19. package/dist/browser/ui/hooks/useDealMutations.js +1 -1
  20. package/dist/browser/ui/index.js +683 -450
  21. package/dist/browser/ui/modals/CreateDealModal.js +12 -12
  22. package/dist/browser/ui/modals/DealActionsModal.js +21 -21
  23. package/dist/browser/ui/modals/index.js +33 -33
  24. package/dist/browser/ui/renderers/index.js +140 -118
  25. package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
  26. package/dist/browser/ui/renderers/pipeline.renderer.js +108 -97
  27. package/dist/browser/ui/tables/DealListTab.js +390 -0
  28. package/dist/deal/index.d.ts +2 -2
  29. package/dist/docs/crm-pipeline.docblock.js +1 -1
  30. package/dist/docs/index.js +1 -1
  31. package/dist/events/contact.event.js +1 -1
  32. package/dist/events/deal.event.js +1 -1
  33. package/dist/events/index.js +3 -3
  34. package/dist/events/task.event.js +1 -1
  35. package/dist/handlers/crm.handlers.d.ts +2 -0
  36. package/dist/handlers/crm.handlers.js +13 -2
  37. package/dist/handlers/index.d.ts +2 -2
  38. package/dist/handlers/index.js +13 -2
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +680 -447
  41. package/dist/node/docs/crm-pipeline.docblock.js +1 -1
  42. package/dist/node/docs/index.js +1 -1
  43. package/dist/node/events/contact.event.js +1 -1
  44. package/dist/node/events/deal.event.js +1 -1
  45. package/dist/node/events/index.js +3 -3
  46. package/dist/node/events/task.event.js +1 -1
  47. package/dist/node/handlers/crm.handlers.js +13 -2
  48. package/dist/node/handlers/index.js +13 -2
  49. package/dist/node/index.js +680 -447
  50. package/dist/node/ui/CrmDashboard.js +574 -352
  51. package/dist/node/ui/CrmDealCard.js +5 -5
  52. package/dist/node/ui/CrmPipelineBoard.js +13 -13
  53. package/dist/node/ui/hooks/index.js +21 -10
  54. package/dist/node/ui/hooks/useDealList.js +20 -9
  55. package/dist/node/ui/hooks/useDealMutations.js +1 -1
  56. package/dist/node/ui/index.js +683 -450
  57. package/dist/node/ui/modals/CreateDealModal.js +12 -12
  58. package/dist/node/ui/modals/DealActionsModal.js +21 -21
  59. package/dist/node/ui/modals/index.js +33 -33
  60. package/dist/node/ui/renderers/index.js +140 -118
  61. package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
  62. package/dist/node/ui/renderers/pipeline.renderer.js +108 -97
  63. package/dist/node/ui/tables/DealListTab.js +390 -0
  64. package/dist/operations/index.d.ts +1 -1
  65. package/dist/ui/CrmDashboard.js +574 -352
  66. package/dist/ui/CrmDealCard.js +5 -5
  67. package/dist/ui/CrmPipelineBoard.js +13 -13
  68. package/dist/ui/hooks/index.d.ts +2 -2
  69. package/dist/ui/hooks/index.js +21 -10
  70. package/dist/ui/hooks/useDealList.d.ts +8 -2
  71. package/dist/ui/hooks/useDealList.js +20 -9
  72. package/dist/ui/hooks/useDealMutations.d.ts +9 -0
  73. package/dist/ui/hooks/useDealMutations.js +1 -1
  74. package/dist/ui/index.d.ts +3 -3
  75. package/dist/ui/index.js +683 -450
  76. package/dist/ui/modals/CreateDealModal.js +12 -12
  77. package/dist/ui/modals/DealActionsModal.js +21 -21
  78. package/dist/ui/modals/index.js +33 -33
  79. package/dist/ui/renderers/index.d.ts +1 -1
  80. package/dist/ui/renderers/index.js +140 -118
  81. package/dist/ui/renderers/pipeline.markdown.js +13 -2
  82. package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
  83. package/dist/ui/renderers/pipeline.renderer.js +108 -97
  84. package/dist/ui/tables/DealListTab.d.ts +20 -0
  85. package/dist/ui/tables/DealListTab.js +391 -0
  86. package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
  87. package/package.json +29 -14
  88. package/src/crm-pipeline.feature.ts +86 -86
  89. package/src/deal/deal.enum.ts +8 -8
  90. package/src/deal/deal.operation.ts +255 -255
  91. package/src/deal/deal.schema.ts +92 -92
  92. package/src/deal/deal.test-spec.ts +48 -48
  93. package/src/deal/index.ts +17 -19
  94. package/src/docs/crm-pipeline.docblock.ts +44 -44
  95. package/src/entities/company.entity.ts +52 -52
  96. package/src/entities/contact.entity.ts +67 -67
  97. package/src/entities/deal.entity.ts +134 -134
  98. package/src/entities/index.ts +27 -27
  99. package/src/entities/task.entity.ts +105 -105
  100. package/src/events/contact.event.ts +22 -22
  101. package/src/events/deal.event.ts +77 -77
  102. package/src/events/task.event.ts +19 -19
  103. package/src/example.ts +32 -32
  104. package/src/handlers/crm.handlers.ts +375 -357
  105. package/src/handlers/deal.handlers.ts +179 -179
  106. package/src/handlers/index.ts +18 -19
  107. package/src/handlers/mock-data.ts +167 -167
  108. package/src/index.ts +11 -11
  109. package/src/operations/index.ts +16 -16
  110. package/src/presentations/dashboard.presentation.ts +45 -45
  111. package/src/presentations/pipeline.presentation.ts +90 -90
  112. package/src/seeders/index.ts +26 -26
  113. package/src/shared/overlay-types.ts +23 -23
  114. package/src/ui/CrmDashboard.tsx +210 -279
  115. package/src/ui/CrmDealCard.tsx +64 -64
  116. package/src/ui/CrmPipelineBoard.tsx +105 -105
  117. package/src/ui/hooks/index.ts +3 -3
  118. package/src/ui/hooks/useDealList.ts +113 -85
  119. package/src/ui/hooks/useDealMutations.ts +151 -150
  120. package/src/ui/index.ts +5 -10
  121. package/src/ui/modals/CreateDealModal.tsx +217 -217
  122. package/src/ui/modals/DealActionsModal.tsx +390 -390
  123. package/src/ui/overlays/demo-overlays.ts +43 -43
  124. package/src/ui/renderers/index.ts +4 -3
  125. package/src/ui/renderers/pipeline.markdown.ts +165 -165
  126. package/src/ui/renderers/pipeline.renderer.tsx +17 -16
  127. package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
  128. package/src/ui/tables/DealListTab.tsx +276 -0
  129. package/tsconfig.json +7 -8
  130. package/tsdown.config.js +7 -3
@@ -10,127 +10,127 @@
10
10
  * - Drag-and-drop ready (UI only, no lib dependency)
11
11
  */
12
12
  import { useState } from 'react';
13
+ import { CrmDealCard } from './CrmDealCard';
13
14
  // import { Button } from '@contractspec/lib.design-system';
14
15
  import type { Deal } from './hooks/useDealList';
15
- import { CrmDealCard } from './CrmDealCard';
16
16
 
17
17
  interface CrmPipelineBoardProps {
18
- dealsByStage: Record<string, Deal[]>;
19
- stages: { id: string; name: string; position: number }[];
20
- onDealClick?: (dealId: string) => void;
21
- onDealMove?: (dealId: string, toStageId: string) => void;
18
+ dealsByStage: Record<string, Deal[]>;
19
+ stages: { id: string; name: string; position: number }[];
20
+ onDealClick?: (dealId: string) => void;
21
+ onDealMove?: (dealId: string, toStageId: string) => void;
22
22
  }
23
23
 
24
24
  function formatCurrency(value: number): string {
25
- if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`;
26
- if (value >= 1000) return `$${(value / 1000).toFixed(0)}K`;
27
- return `$${value}`;
25
+ if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`;
26
+ if (value >= 1000) return `$${(value / 1000).toFixed(0)}K`;
27
+ return `$${value}`;
28
28
  }
29
29
 
30
30
  export function CrmPipelineBoard({
31
- dealsByStage,
32
- stages,
33
- onDealClick,
34
- onDealMove,
31
+ dealsByStage,
32
+ stages,
33
+ onDealClick,
34
+ onDealMove,
35
35
  }: CrmPipelineBoardProps) {
36
- // Track which deal has the quick-move dropdown open
37
- const [quickMoveOpen, setQuickMoveOpen] = useState<string | null>(null);
36
+ // Track which deal has the quick-move dropdown open
37
+ const [quickMoveOpen, setQuickMoveOpen] = useState<string | null>(null);
38
38
 
39
- // Sort stages by position
40
- const sortedStages = [...stages].sort((a, b) => a.position - b.position);
39
+ // Sort stages by position
40
+ const sortedStages = [...stages].sort((a, b) => a.position - b.position);
41
41
 
42
- const handleQuickMove = (dealId: string, toStageId: string) => {
43
- onDealMove?.(dealId, toStageId);
44
- setQuickMoveOpen(null);
45
- };
42
+ const handleQuickMove = (dealId: string, toStageId: string) => {
43
+ onDealMove?.(dealId, toStageId);
44
+ setQuickMoveOpen(null);
45
+ };
46
46
 
47
- return (
48
- <div className="flex gap-4 overflow-x-auto pb-4">
49
- {sortedStages.map((stage) => {
50
- const deals = dealsByStage[stage.id] ?? [];
51
- const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
47
+ return (
48
+ <div className="flex gap-4 overflow-x-auto pb-4">
49
+ {sortedStages.map((stage) => {
50
+ const deals = dealsByStage[stage.id] ?? [];
51
+ const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
52
52
 
53
- return (
54
- <div
55
- key={stage.id}
56
- className="bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg"
57
- >
58
- {/* Stage Header */}
59
- <div className="border-border flex items-center justify-between border-b px-3 py-2">
60
- <div>
61
- <h3 className="font-medium">{stage.name}</h3>
62
- <p className="text-muted-foreground text-xs">
63
- {deals.length} deals · {formatCurrency(stageValue)}
64
- </p>
65
- </div>
66
- <span className="bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium">
67
- {deals.length}
68
- </span>
69
- </div>
53
+ return (
54
+ <div
55
+ key={stage.id}
56
+ className="flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30"
57
+ >
58
+ {/* Stage Header */}
59
+ <div className="flex items-center justify-between border-border border-b px-3 py-2">
60
+ <div>
61
+ <h3 className="font-medium">{stage.name}</h3>
62
+ <p className="text-muted-foreground text-xs">
63
+ {deals.length} deals · {formatCurrency(stageValue)}
64
+ </p>
65
+ </div>
66
+ <span className="flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs">
67
+ {deals.length}
68
+ </span>
69
+ </div>
70
70
 
71
- {/* Deals Column */}
72
- <div className="flex flex-1 flex-col gap-2 p-2">
73
- {deals.length === 0 ? (
74
- <div className="border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs">
75
- No deals
76
- </div>
77
- ) : (
78
- deals.map((deal) => (
79
- <div key={deal.id} className="group relative">
80
- <CrmDealCard
81
- deal={deal}
82
- onClick={() => onDealClick?.(deal.id)}
83
- />
71
+ {/* Deals Column */}
72
+ <div className="flex flex-1 flex-col gap-2 p-2">
73
+ {deals.length === 0 ? (
74
+ <div className="flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs">
75
+ No deals
76
+ </div>
77
+ ) : (
78
+ deals.map((deal) => (
79
+ <div key={deal.id} className="group relative">
80
+ <CrmDealCard
81
+ deal={deal}
82
+ onClick={() => onDealClick?.(deal.id)}
83
+ />
84
84
 
85
- {/* Quick Move Button */}
86
- {deal.status === 'OPEN' && onDealMove && (
87
- <div className="absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100">
88
- <button
89
- type="button"
90
- onClick={(e) => {
91
- e.stopPropagation();
92
- setQuickMoveOpen(
93
- quickMoveOpen === deal.id ? null : deal.id
94
- );
95
- }}
96
- className="bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm"
97
- title="Quick move"
98
- >
99
- ➡️
100
- </button>
85
+ {/* Quick Move Button */}
86
+ {deal.status === 'OPEN' && onDealMove && (
87
+ <div className="absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100">
88
+ <button
89
+ type="button"
90
+ onClick={(e) => {
91
+ e.stopPropagation();
92
+ setQuickMoveOpen(
93
+ quickMoveOpen === deal.id ? null : deal.id
94
+ );
95
+ }}
96
+ className="flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted"
97
+ title="Quick move"
98
+ >
99
+ ➡️
100
+ </button>
101
101
 
102
- {/* Quick Move Dropdown */}
103
- {quickMoveOpen === deal.id && (
104
- <div className="bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg">
105
- <p className="text-muted-foreground px-3 py-1 text-xs font-medium">
106
- Move to:
107
- </p>
108
- {sortedStages
109
- .filter((s) => s.id !== deal.stageId)
110
- .map((s) => (
111
- <button
112
- key={s.id}
113
- type="button"
114
- onClick={(e) => {
115
- e.stopPropagation();
116
- handleQuickMove(deal.id, s.id);
117
- }}
118
- className="hover:bg-muted w-full px-3 py-1.5 text-left text-sm"
119
- >
120
- {s.name}
121
- </button>
122
- ))}
123
- </div>
124
- )}
125
- </div>
126
- )}
127
- </div>
128
- ))
129
- )}
130
- </div>
131
- </div>
132
- );
133
- })}
134
- </div>
135
- );
102
+ {/* Quick Move Dropdown */}
103
+ {quickMoveOpen === deal.id && (
104
+ <div className="absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg">
105
+ <p className="px-3 py-1 font-medium text-muted-foreground text-xs">
106
+ Move to:
107
+ </p>
108
+ {sortedStages
109
+ .filter((s) => s.id !== deal.stageId)
110
+ .map((s) => (
111
+ <button
112
+ key={s.id}
113
+ type="button"
114
+ onClick={(e) => {
115
+ e.stopPropagation();
116
+ handleQuickMove(deal.id, s.id);
117
+ }}
118
+ className="w-full px-3 py-1.5 text-left text-sm hover:bg-muted"
119
+ >
120
+ {s.name}
121
+ </button>
122
+ ))}
123
+ </div>
124
+ )}
125
+ </div>
126
+ )}
127
+ </div>
128
+ ))
129
+ )}
130
+ </div>
131
+ </div>
132
+ );
133
+ })}
134
+ </div>
135
+ );
136
136
  }
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
- export { useDealList, type UseDealListOptions } from './useDealList';
3
+ export { type UseDealListOptions, useDealList } from './useDealList';
4
4
  export {
5
- useDealMutations,
6
- type UseDealMutationsOptions,
5
+ type UseDealMutationsOptions,
6
+ useDealMutations,
7
7
  } from './useDealMutations';
8
8
 
9
9
  // Note: For deal types (CreateDealInput, MoveDealInput, etc.), import directly from:
@@ -1,17 +1,18 @@
1
1
  'use client';
2
2
 
3
+ import { useTemplateRuntime } from '@contractspec/lib.example-shared-ui';
4
+ import type { ContractTableSort } from '@contractspec/lib.presentation-runtime-core';
3
5
  /**
4
6
  * Hook for fetching and managing deal list data
5
7
  *
6
8
  * Uses runtime-local database-backed handlers.
7
9
  */
8
10
  import { useCallback, useEffect, useMemo, useState } from 'react';
9
- import { useTemplateRuntime } from '@contractspec/lib.example-shared-ui';
10
11
  import {
11
- type CrmHandlers,
12
- type Deal as RuntimeDeal,
13
- type ListDealsOutput as RuntimeListDealsOutput,
14
- type Stage,
12
+ type CrmHandlers,
13
+ type Deal as RuntimeDeal,
14
+ type ListDealsOutput as RuntimeListDealsOutput,
15
+ type Stage,
15
16
  } from '../../handlers/crm.handlers';
16
17
 
17
18
  // Re-export types for convenience
@@ -19,95 +20,122 @@ export type Deal = RuntimeDeal;
19
20
  export type ListDealsOutput = RuntimeListDealsOutput;
20
21
 
21
22
  export interface UseDealListOptions {
22
- pipelineId?: string;
23
- stageId?: string;
24
- status?: 'OPEN' | 'WON' | 'LOST' | 'all';
25
- search?: string;
26
- limit?: number;
23
+ pipelineId?: string;
24
+ stageId?: string;
25
+ status?: 'OPEN' | 'WON' | 'LOST' | 'all';
26
+ search?: string;
27
+ limit?: number;
28
+ pageIndex?: number;
29
+ pageSize?: number;
30
+ sorting?: ContractTableSort[];
27
31
  }
28
32
 
29
33
  export function useDealList(options: UseDealListOptions = {}) {
30
- const { handlers, projectId } = useTemplateRuntime<{ crm: CrmHandlers }>();
31
- const { crm } = handlers;
34
+ const { handlers, projectId } = useTemplateRuntime<{ crm: CrmHandlers }>();
35
+ const { crm } = handlers;
32
36
 
33
- const [data, setData] = useState<ListDealsOutput | null>(null);
34
- const [dealsByStage, setDealsByStage] = useState<Record<string, Deal[]>>({});
35
- const [stages, setStages] = useState<Stage[]>([]);
36
- const [loading, setLoading] = useState(true);
37
- const [error, setError] = useState<Error | null>(null);
38
- const [page, setPage] = useState(1);
37
+ const [data, setData] = useState<ListDealsOutput | null>(null);
38
+ const [dealsByStage, setDealsByStage] = useState<Record<string, Deal[]>>({});
39
+ const [stages, setStages] = useState<Stage[]>([]);
40
+ const [loading, setLoading] = useState(true);
41
+ const [error, setError] = useState<Error | null>(null);
42
+ const [internalPage, setInternalPage] = useState(0);
39
43
 
40
- const pipelineId = options.pipelineId ?? 'pipeline-1';
44
+ const pipelineId = options.pipelineId ?? 'pipeline-1';
45
+ const pageIndex = options.pageIndex ?? internalPage;
46
+ const pageSize = options.pageSize ?? options.limit ?? 50;
47
+ const [sort] = options.sorting ?? [];
48
+ const sortBy = sort?.id;
49
+ const sortDirection = sort ? (sort.desc ? 'desc' : 'asc') : undefined;
41
50
 
42
- const fetchData = useCallback(async () => {
43
- setLoading(true);
44
- setError(null);
51
+ const fetchData = useCallback(async () => {
52
+ setLoading(true);
53
+ setError(null);
45
54
 
46
- try {
47
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
48
- crm.listDeals({
49
- projectId,
50
- pipelineId,
51
- stageId: options.stageId,
52
- status: options.status === 'all' ? undefined : options.status,
53
- search: options.search,
54
- limit: options.limit ?? 50,
55
- offset: (page - 1) * (options.limit ?? 50),
56
- }),
57
- crm.getDealsByStage({ projectId, pipelineId }),
58
- crm.getPipelineStages({ pipelineId }),
59
- ]);
60
- setData(dealsResult);
61
- setDealsByStage(stageDealsResult);
62
- setStages(stagesResult);
63
- } catch (err) {
64
- setError(err instanceof Error ? err : new Error('Unknown error'));
65
- } finally {
66
- setLoading(false);
67
- }
68
- }, [
69
- crm,
70
- projectId,
71
- pipelineId,
72
- options.stageId,
73
- options.status,
74
- options.search,
75
- options.limit,
76
- page,
77
- ]);
55
+ try {
56
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
57
+ crm.listDeals({
58
+ projectId,
59
+ pipelineId,
60
+ stageId: options.stageId,
61
+ status: options.status === 'all' ? undefined : options.status,
62
+ search: options.search,
63
+ limit: pageSize,
64
+ offset: pageIndex * pageSize,
65
+ sortBy:
66
+ sortBy === 'name' ||
67
+ sortBy === 'value' ||
68
+ sortBy === 'status' ||
69
+ sortBy === 'expectedCloseDate' ||
70
+ sortBy === 'updatedAt'
71
+ ? sortBy
72
+ : undefined,
73
+ sortDirection,
74
+ }),
75
+ crm.getDealsByStage({ projectId, pipelineId }),
76
+ crm.getPipelineStages({ pipelineId }),
77
+ ]);
78
+ setData(dealsResult);
79
+ setDealsByStage(stageDealsResult);
80
+ setStages(stagesResult);
81
+ } catch (err) {
82
+ setError(err instanceof Error ? err : new Error('Unknown error'));
83
+ } finally {
84
+ setLoading(false);
85
+ }
86
+ }, [
87
+ crm,
88
+ projectId,
89
+ pipelineId,
90
+ options.stageId,
91
+ options.status,
92
+ options.search,
93
+ pageIndex,
94
+ pageSize,
95
+ sortBy,
96
+ sortDirection,
97
+ ]);
78
98
 
79
- useEffect(() => {
80
- fetchData();
81
- }, [fetchData]);
99
+ useEffect(() => {
100
+ fetchData();
101
+ }, [fetchData]);
82
102
 
83
- // Calculate stats
84
- const stats = useMemo(() => {
85
- if (!data) return null;
86
- const open = data.deals.filter((d: Deal) => d.status === 'OPEN');
87
- const won = data.deals.filter((d: Deal) => d.status === 'WON');
88
- const lost = data.deals.filter((d: Deal) => d.status === 'LOST');
103
+ // Calculate stats
104
+ const stats = useMemo(() => {
105
+ if (!data) return null;
106
+ const open = data.deals.filter((d: Deal) => d.status === 'OPEN');
107
+ const won = data.deals.filter((d: Deal) => d.status === 'WON');
108
+ const lost = data.deals.filter((d: Deal) => d.status === 'LOST');
89
109
 
90
- return {
91
- total: data.total,
92
- totalValue: data.totalValue,
93
- openCount: open.length,
94
- openValue: open.reduce((sum: number, d: Deal) => sum + d.value, 0),
95
- wonCount: won.length,
96
- wonValue: won.reduce((sum: number, d: Deal) => sum + d.value, 0),
97
- lostCount: lost.length,
98
- };
99
- }, [data]);
110
+ return {
111
+ total: data.total,
112
+ totalValue: data.totalValue,
113
+ openCount: open.length,
114
+ openValue: open.reduce((sum: number, d: Deal) => sum + d.value, 0),
115
+ wonCount: won.length,
116
+ wonValue: won.reduce((sum: number, d: Deal) => sum + d.value, 0),
117
+ lostCount: lost.length,
118
+ };
119
+ }, [data]);
100
120
 
101
- return {
102
- data,
103
- dealsByStage,
104
- stages,
105
- loading,
106
- error,
107
- stats,
108
- page,
109
- refetch: fetchData,
110
- nextPage: () => setPage((p) => p + 1),
111
- prevPage: () => page > 1 && setPage((p) => p - 1),
112
- };
121
+ return {
122
+ data,
123
+ dealsByStage,
124
+ stages,
125
+ loading,
126
+ error,
127
+ stats,
128
+ page: pageIndex + 1,
129
+ pageIndex,
130
+ pageSize,
131
+ refetch: fetchData,
132
+ nextPage:
133
+ options.pageIndex === undefined
134
+ ? () => setInternalPage((page) => page + 1)
135
+ : undefined,
136
+ prevPage:
137
+ options.pageIndex === undefined
138
+ ? () => pageIndex > 0 && setInternalPage((page) => page - 1)
139
+ : undefined,
140
+ };
113
141
  }