@contractspec/example.crm-pipeline 1.46.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$bundle.log +111 -36
- package/.turbo/turbo-build.log +109 -34
- package/CHANGELOG.md +56 -0
- package/dist/crm-pipeline.feature.d.ts +2 -2
- package/dist/crm-pipeline.feature.d.ts.map +1 -1
- package/dist/crm-pipeline.feature.js +9 -2
- package/dist/crm-pipeline.feature.js.map +1 -1
- package/dist/deal/deal.enum.d.ts +3 -3
- package/dist/deal/deal.enum.d.ts.map +1 -1
- package/dist/deal/deal.operation.d.ts +128 -128
- package/dist/deal/deal.operation.d.ts.map +1 -1
- package/dist/deal/deal.schema.d.ts +71 -71
- package/dist/deal/deal.test-spec.d.ts +8 -0
- package/dist/deal/deal.test-spec.d.ts.map +1 -0
- package/dist/deal/deal.test-spec.js +65 -0
- package/dist/deal/deal.test-spec.js.map +1 -0
- package/dist/entities/company.entity.d.ts +28 -28
- package/dist/entities/company.entity.d.ts.map +1 -1
- package/dist/entities/contact.entity.d.ts +32 -32
- package/dist/entities/contact.entity.d.ts.map +1 -1
- package/dist/entities/deal.entity.d.ts +53 -53
- package/dist/entities/index.js.map +1 -1
- package/dist/entities/task.entity.d.ts +43 -43
- package/dist/entities/task.entity.d.ts.map +1 -1
- package/dist/events/contact.event.d.ts +8 -8
- package/dist/events/contact.event.d.ts.map +1 -1
- package/dist/events/contact.event.js +1 -1
- package/dist/events/deal.event.d.ts +30 -30
- package/dist/events/deal.event.js +1 -1
- package/dist/events/task.event.d.ts +8 -8
- package/dist/events/task.event.d.ts.map +1 -1
- package/dist/events/task.event.js +1 -1
- package/dist/example.d.ts +2 -2
- package/dist/example.d.ts.map +1 -1
- package/dist/example.js +4 -2
- package/dist/example.js.map +1 -1
- package/dist/handlers/crm.handlers.d.ts +89 -0
- package/dist/handlers/crm.handlers.d.ts.map +1 -0
- package/dist/handlers/crm.handlers.js +172 -0
- package/dist/handlers/crm.handlers.js.map +1 -0
- package/dist/handlers/deal.handlers.js.map +1 -1
- package/dist/handlers/index.d.ts +2 -1
- package/dist/handlers/index.js +2 -1
- package/dist/handlers/mock-data.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/presentations/dashboard.presentation.d.ts +3 -4
- package/dist/presentations/dashboard.presentation.d.ts.map +1 -1
- package/dist/presentations/dashboard.presentation.js +8 -5
- package/dist/presentations/dashboard.presentation.js.map +1 -1
- package/dist/presentations/pipeline.presentation.d.ts +5 -6
- package/dist/presentations/pipeline.presentation.d.ts.map +1 -1
- package/dist/presentations/pipeline.presentation.js +12 -9
- package/dist/presentations/pipeline.presentation.js.map +1 -1
- package/dist/seeders/index.d.ts +10 -0
- package/dist/seeders/index.d.ts.map +1 -0
- package/dist/seeders/index.js +47 -0
- package/dist/seeders/index.js.map +1 -0
- package/dist/shared/overlay-types.d.ts +34 -0
- package/dist/shared/overlay-types.d.ts.map +1 -0
- package/dist/shared/overlay-types.js +0 -0
- package/dist/ui/CrmDashboard.d.ts +7 -0
- package/dist/ui/CrmDashboard.d.ts.map +1 -0
- package/dist/ui/CrmDashboard.js +304 -0
- package/dist/ui/CrmDashboard.js.map +1 -0
- package/dist/ui/CrmDealCard.d.ts +15 -0
- package/dist/ui/CrmDealCard.d.ts.map +1 -0
- package/dist/ui/CrmDealCard.js +49 -0
- package/dist/ui/CrmDealCard.js.map +1 -0
- package/dist/ui/CrmPipelineBoard.d.ts +23 -0
- package/dist/ui/CrmPipelineBoard.d.ts.map +1 -0
- package/dist/ui/CrmPipelineBoard.js +98 -0
- package/dist/ui/CrmPipelineBoard.js.map +1 -0
- package/dist/ui/hooks/index.d.ts +3 -0
- package/dist/ui/hooks/index.js +6 -0
- package/dist/ui/hooks/useDealList.d.ts +35 -0
- package/dist/ui/hooks/useDealList.d.ts.map +1 -0
- package/dist/ui/hooks/useDealList.js +94 -0
- package/dist/ui/hooks/useDealList.js.map +1 -0
- package/dist/ui/hooks/useDealMutations.d.ts +26 -0
- package/dist/ui/hooks/useDealMutations.d.ts.map +1 -0
- package/dist/ui/hooks/useDealMutations.js +159 -0
- package/dist/ui/hooks/useDealMutations.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/modals/CreateDealModal.d.ts +33 -0
- package/dist/ui/modals/CreateDealModal.d.ts.map +1 -0
- package/dist/ui/modals/CreateDealModal.js +183 -0
- package/dist/ui/modals/CreateDealModal.js.map +1 -0
- package/dist/ui/modals/DealActionsModal.d.ts +51 -0
- package/dist/ui/modals/DealActionsModal.d.ts.map +1 -0
- package/dist/ui/modals/DealActionsModal.js +372 -0
- package/dist/ui/modals/DealActionsModal.js.map +1 -0
- package/dist/ui/modals/index.d.ts +3 -0
- package/dist/ui/modals/index.js +4 -0
- package/dist/ui/overlays/demo-overlays.d.ts +19 -0
- package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
- package/dist/ui/overlays/demo-overlays.js +68 -0
- package/dist/ui/overlays/demo-overlays.js.map +1 -0
- package/dist/ui/overlays/index.d.ts +2 -0
- package/dist/ui/overlays/index.js +3 -0
- package/dist/ui/renderers/index.d.ts +3 -0
- package/dist/ui/renderers/index.js +4 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts +23 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.markdown.js +118 -0
- package/dist/ui/renderers/pipeline.markdown.js.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts +9 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.js +28 -0
- package/dist/ui/renderers/pipeline.renderer.js.map +1 -0
- package/package.json +38 -13
- package/src/crm-pipeline.feature.ts +3 -3
- package/src/deal/deal.test-spec.ts +55 -0
- package/src/example.ts +3 -3
- package/src/handlers/crm.handlers.ts +415 -0
- package/src/handlers/index.ts +3 -0
- package/src/index.ts +1 -0
- package/src/presentations/dashboard.presentation.ts +5 -6
- package/src/presentations/pipeline.presentation.ts +9 -10
- package/src/seeders/index.ts +35 -0
- package/src/shared/overlay-types.ts +39 -0
- package/src/ui/CrmDashboard.tsx +311 -0
- package/src/ui/CrmDealCard.tsx +83 -0
- package/src/ui/CrmPipelineBoard.tsx +136 -0
- package/src/ui/hooks/index.ts +10 -0
- package/src/ui/hooks/useDealList.ts +113 -0
- package/src/ui/hooks/useDealMutations.ts +174 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/modals/CreateDealModal.tsx +239 -0
- package/src/ui/modals/DealActionsModal.tsx +424 -0
- package/src/ui/modals/index.ts +2 -0
- package/src/ui/overlays/demo-overlays.ts +68 -0
- package/src/ui/overlays/index.ts +1 -0
- package/src/ui/renderers/index.ts +6 -0
- package/src/ui/renderers/pipeline.markdown.ts +198 -0
- package/src/ui/renderers/pipeline.renderer.tsx +35 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for CRM deal mutations (commands)
|
|
3
|
+
*
|
|
4
|
+
* Uses runtime-local database-backed handlers for:
|
|
5
|
+
* - CreateDealContract
|
|
6
|
+
* - MoveDealContract
|
|
7
|
+
* - WinDealContract
|
|
8
|
+
* - LoseDealContract
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useState } from 'react';
|
|
11
|
+
import { useTemplateRuntime } from '@contractspec/lib.example-shared-ui';
|
|
12
|
+
import type {
|
|
13
|
+
CreateDealInput,
|
|
14
|
+
CrmHandlers,
|
|
15
|
+
Deal,
|
|
16
|
+
LoseDealInput,
|
|
17
|
+
MoveDealInput,
|
|
18
|
+
WinDealInput,
|
|
19
|
+
} from '../../handlers/crm.handlers';
|
|
20
|
+
|
|
21
|
+
export interface MutationState<T> {
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
data: T | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseDealMutationsOptions {
|
|
28
|
+
onSuccess?: () => void;
|
|
29
|
+
onError?: (error: Error) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useDealMutations(options: UseDealMutationsOptions = {}) {
|
|
33
|
+
const { handlers, projectId } = useTemplateRuntime<{ crm: CrmHandlers }>();
|
|
34
|
+
const { crm } = handlers;
|
|
35
|
+
|
|
36
|
+
const [createState, setCreateState] = useState<MutationState<Deal>>({
|
|
37
|
+
loading: false,
|
|
38
|
+
error: null,
|
|
39
|
+
data: null,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const [moveState, setMoveState] = useState<MutationState<Deal>>({
|
|
43
|
+
loading: false,
|
|
44
|
+
error: null,
|
|
45
|
+
data: null,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const [winState, setWinState] = useState<MutationState<Deal>>({
|
|
49
|
+
loading: false,
|
|
50
|
+
error: null,
|
|
51
|
+
data: null,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const [loseState, setLoseState] = useState<MutationState<Deal>>({
|
|
55
|
+
loading: false,
|
|
56
|
+
error: null,
|
|
57
|
+
data: null,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a new deal
|
|
62
|
+
*/
|
|
63
|
+
const createDeal = useCallback(
|
|
64
|
+
async (input: CreateDealInput): Promise<Deal | null> => {
|
|
65
|
+
setCreateState({ loading: true, error: null, data: null });
|
|
66
|
+
try {
|
|
67
|
+
const result = await crm.createDeal(input, {
|
|
68
|
+
projectId,
|
|
69
|
+
ownerId: 'user-1', // Demo user
|
|
70
|
+
});
|
|
71
|
+
setCreateState({ loading: false, error: null, data: result });
|
|
72
|
+
options.onSuccess?.();
|
|
73
|
+
return result;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const error =
|
|
76
|
+
err instanceof Error ? err : new Error('Failed to create deal');
|
|
77
|
+
setCreateState({ loading: false, error, data: null });
|
|
78
|
+
options.onError?.(error);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[crm, projectId, options]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Move a deal to a different stage
|
|
87
|
+
*/
|
|
88
|
+
const moveDeal = useCallback(
|
|
89
|
+
async (input: MoveDealInput): Promise<Deal | null> => {
|
|
90
|
+
setMoveState({ loading: true, error: null, data: null });
|
|
91
|
+
try {
|
|
92
|
+
const result = await crm.moveDeal(input);
|
|
93
|
+
setMoveState({ loading: false, error: null, data: result });
|
|
94
|
+
options.onSuccess?.();
|
|
95
|
+
return result;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const error =
|
|
98
|
+
err instanceof Error ? err : new Error('Failed to move deal');
|
|
99
|
+
setMoveState({ loading: false, error, data: null });
|
|
100
|
+
options.onError?.(error);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[crm, options]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mark a deal as won
|
|
109
|
+
*/
|
|
110
|
+
const winDeal = useCallback(
|
|
111
|
+
async (input: WinDealInput): Promise<Deal | null> => {
|
|
112
|
+
setWinState({ loading: true, error: null, data: null });
|
|
113
|
+
try {
|
|
114
|
+
const result = await crm.winDeal(input);
|
|
115
|
+
setWinState({ loading: false, error: null, data: result });
|
|
116
|
+
options.onSuccess?.();
|
|
117
|
+
return result;
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const error =
|
|
120
|
+
err instanceof Error ? err : new Error('Failed to mark deal as won');
|
|
121
|
+
setWinState({ loading: false, error, data: null });
|
|
122
|
+
options.onError?.(error);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[crm, options]
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Mark a deal as lost
|
|
131
|
+
*/
|
|
132
|
+
const loseDeal = useCallback(
|
|
133
|
+
async (input: LoseDealInput): Promise<Deal | null> => {
|
|
134
|
+
setLoseState({ loading: true, error: null, data: null });
|
|
135
|
+
try {
|
|
136
|
+
const result = await crm.loseDeal(input);
|
|
137
|
+
setLoseState({ loading: false, error: null, data: result });
|
|
138
|
+
options.onSuccess?.();
|
|
139
|
+
return result;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const error =
|
|
142
|
+
err instanceof Error ? err : new Error('Failed to mark deal as lost');
|
|
143
|
+
setLoseState({ loading: false, error, data: null });
|
|
144
|
+
options.onError?.(error);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
[crm, options]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
// Mutations
|
|
153
|
+
createDeal,
|
|
154
|
+
moveDeal,
|
|
155
|
+
winDeal,
|
|
156
|
+
loseDeal,
|
|
157
|
+
|
|
158
|
+
// State
|
|
159
|
+
createState,
|
|
160
|
+
moveState,
|
|
161
|
+
winState,
|
|
162
|
+
loseState,
|
|
163
|
+
|
|
164
|
+
// Convenience
|
|
165
|
+
isLoading:
|
|
166
|
+
createState.loading ||
|
|
167
|
+
moveState.loading ||
|
|
168
|
+
winState.loading ||
|
|
169
|
+
loseState.loading,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Note: Types are re-exported from the handlers package
|
|
174
|
+
// Consumers should import types directly from '@contractspec/example.crm-pipeline/handlers'
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Main dashboard
|
|
2
|
+
export * from './CrmDashboard';
|
|
3
|
+
|
|
4
|
+
// Components
|
|
5
|
+
export * from './CrmPipelineBoard';
|
|
6
|
+
export * from './CrmDealCard';
|
|
7
|
+
|
|
8
|
+
// Modals
|
|
9
|
+
export * from './modals';
|
|
10
|
+
|
|
11
|
+
// Hooks
|
|
12
|
+
export * from './hooks';
|
|
13
|
+
|
|
14
|
+
// Renderers
|
|
15
|
+
export * from './renderers';
|
|
16
|
+
|
|
17
|
+
// Overlays
|
|
18
|
+
export * from './overlays';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CreateDealModal - Form for creating a new deal
|
|
5
|
+
*
|
|
6
|
+
* Wires to CreateDealContract via useDealMutations hook.
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { Button, Input } from '@contractspec/lib.design-system';
|
|
10
|
+
|
|
11
|
+
// Local type definition for modal props
|
|
12
|
+
export interface CreateDealInput {
|
|
13
|
+
name: string;
|
|
14
|
+
value: number;
|
|
15
|
+
currency: string;
|
|
16
|
+
pipelineId: string;
|
|
17
|
+
stageId: string;
|
|
18
|
+
expectedCloseDate?: Date;
|
|
19
|
+
contactId?: string;
|
|
20
|
+
companyId?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CreateDealModalProps {
|
|
24
|
+
isOpen: boolean;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
onSubmit: (input: CreateDealInput) => Promise<void>;
|
|
27
|
+
stages: { id: string; name: string }[];
|
|
28
|
+
isLoading?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CURRENCIES = ['USD', 'EUR', 'GBP', 'CAD'];
|
|
32
|
+
const DEFAULT_PIPELINE_ID = 'pipeline-1';
|
|
33
|
+
|
|
34
|
+
export function CreateDealModal({
|
|
35
|
+
isOpen,
|
|
36
|
+
onClose,
|
|
37
|
+
onSubmit,
|
|
38
|
+
stages,
|
|
39
|
+
isLoading = false,
|
|
40
|
+
}: CreateDealModalProps) {
|
|
41
|
+
const [name, setName] = useState('');
|
|
42
|
+
const [value, setValue] = useState('');
|
|
43
|
+
const [currency, setCurrency] = useState('USD');
|
|
44
|
+
const [stageId, setStageId] = useState(stages[0]?.id ?? '');
|
|
45
|
+
const [expectedCloseDate, setExpectedCloseDate] = useState('');
|
|
46
|
+
const [error, setError] = useState<string | null>(null);
|
|
47
|
+
|
|
48
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
setError(null);
|
|
51
|
+
|
|
52
|
+
// Validation
|
|
53
|
+
if (!name.trim()) {
|
|
54
|
+
setError('Deal name is required');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const numericValue = parseFloat(value);
|
|
59
|
+
if (isNaN(numericValue) || numericValue <= 0) {
|
|
60
|
+
setError('Value must be a positive number');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!stageId) {
|
|
65
|
+
setError('Please select a pipeline stage');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await onSubmit({
|
|
71
|
+
name: name.trim(),
|
|
72
|
+
value: numericValue,
|
|
73
|
+
currency,
|
|
74
|
+
pipelineId: DEFAULT_PIPELINE_ID,
|
|
75
|
+
stageId,
|
|
76
|
+
expectedCloseDate: expectedCloseDate
|
|
77
|
+
? new Date(expectedCloseDate)
|
|
78
|
+
: undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Reset form
|
|
82
|
+
setName('');
|
|
83
|
+
setValue('');
|
|
84
|
+
setCurrency('USD');
|
|
85
|
+
setStageId(stages[0]?.id ?? '');
|
|
86
|
+
setExpectedCloseDate('');
|
|
87
|
+
onClose();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
setError(err instanceof Error ? err.message : 'Failed to create deal');
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (!isOpen) return null;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
97
|
+
{/* Backdrop */}
|
|
98
|
+
<div
|
|
99
|
+
className="bg-background/80 absolute inset-0 backdrop-blur-sm"
|
|
100
|
+
onClick={onClose}
|
|
101
|
+
role="button"
|
|
102
|
+
tabIndex={0}
|
|
103
|
+
onKeyDown={(e) => {
|
|
104
|
+
if (e.key === 'Enter' || e.key === ' ') onClose();
|
|
105
|
+
}}
|
|
106
|
+
aria-label="Close modal"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
{/* Modal */}
|
|
110
|
+
<div className="bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl">
|
|
111
|
+
<h2 className="mb-4 text-xl font-semibold">Create New Deal</h2>
|
|
112
|
+
|
|
113
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
114
|
+
{/* Deal Name */}
|
|
115
|
+
<div>
|
|
116
|
+
<label
|
|
117
|
+
htmlFor="deal-name"
|
|
118
|
+
className="text-muted-foreground mb-1 block text-sm font-medium"
|
|
119
|
+
>
|
|
120
|
+
Deal Name *
|
|
121
|
+
</label>
|
|
122
|
+
<Input
|
|
123
|
+
id="deal-name"
|
|
124
|
+
value={name}
|
|
125
|
+
onChange={(e) => setName(e.target.value)}
|
|
126
|
+
placeholder="e.g., Enterprise License - Acme Corp"
|
|
127
|
+
disabled={isLoading}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Value & Currency */}
|
|
132
|
+
<div className="flex gap-3">
|
|
133
|
+
<div className="flex-1">
|
|
134
|
+
<label
|
|
135
|
+
htmlFor="deal-value"
|
|
136
|
+
className="text-muted-foreground mb-1 block text-sm font-medium"
|
|
137
|
+
>
|
|
138
|
+
Value *
|
|
139
|
+
</label>
|
|
140
|
+
<Input
|
|
141
|
+
id="deal-value"
|
|
142
|
+
type="number"
|
|
143
|
+
min="0"
|
|
144
|
+
step="0.01"
|
|
145
|
+
value={value}
|
|
146
|
+
onChange={(e) => setValue(e.target.value)}
|
|
147
|
+
placeholder="50000"
|
|
148
|
+
disabled={isLoading}
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="w-24">
|
|
152
|
+
<label
|
|
153
|
+
htmlFor="deal-currency"
|
|
154
|
+
className="text-muted-foreground mb-1 block text-sm font-medium"
|
|
155
|
+
>
|
|
156
|
+
Currency
|
|
157
|
+
</label>
|
|
158
|
+
<select
|
|
159
|
+
id="deal-currency"
|
|
160
|
+
value={currency}
|
|
161
|
+
onChange={(e) => setCurrency(e.target.value)}
|
|
162
|
+
disabled={isLoading}
|
|
163
|
+
className="border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
|
|
164
|
+
>
|
|
165
|
+
{CURRENCIES.map((c) => (
|
|
166
|
+
<option key={c} value={c}>
|
|
167
|
+
{c}
|
|
168
|
+
</option>
|
|
169
|
+
))}
|
|
170
|
+
</select>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Stage */}
|
|
175
|
+
<div>
|
|
176
|
+
<label
|
|
177
|
+
htmlFor="deal-stage"
|
|
178
|
+
className="text-muted-foreground mb-1 block text-sm font-medium"
|
|
179
|
+
>
|
|
180
|
+
Pipeline Stage *
|
|
181
|
+
</label>
|
|
182
|
+
<select
|
|
183
|
+
id="deal-stage"
|
|
184
|
+
value={stageId}
|
|
185
|
+
onChange={(e) => setStageId(e.target.value)}
|
|
186
|
+
disabled={isLoading}
|
|
187
|
+
className="border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
|
|
188
|
+
>
|
|
189
|
+
{stages.map((stage) => (
|
|
190
|
+
<option key={stage.id} value={stage.id}>
|
|
191
|
+
{stage.name}
|
|
192
|
+
</option>
|
|
193
|
+
))}
|
|
194
|
+
</select>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* Expected Close Date */}
|
|
198
|
+
<div>
|
|
199
|
+
<label
|
|
200
|
+
htmlFor="deal-close-date"
|
|
201
|
+
className="text-muted-foreground mb-1 block text-sm font-medium"
|
|
202
|
+
>
|
|
203
|
+
Expected Close Date
|
|
204
|
+
</label>
|
|
205
|
+
<Input
|
|
206
|
+
id="deal-close-date"
|
|
207
|
+
type="date"
|
|
208
|
+
value={expectedCloseDate}
|
|
209
|
+
onChange={(e) => setExpectedCloseDate(e.target.value)}
|
|
210
|
+
disabled={isLoading}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Error Message */}
|
|
215
|
+
{error && (
|
|
216
|
+
<div className="bg-destructive/10 text-destructive rounded-md p-3 text-sm">
|
|
217
|
+
{error}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* Actions */}
|
|
222
|
+
<div className="flex justify-end gap-3 pt-2">
|
|
223
|
+
<Button
|
|
224
|
+
type="button"
|
|
225
|
+
variant="ghost"
|
|
226
|
+
onPress={onClose}
|
|
227
|
+
disabled={isLoading}
|
|
228
|
+
>
|
|
229
|
+
Cancel
|
|
230
|
+
</Button>
|
|
231
|
+
<Button type="submit" disabled={isLoading}>
|
|
232
|
+
{isLoading ? 'Creating...' : 'Create Deal'}
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
</form>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|