@visns-studio/visns-components 5.11.5 → 5.11.6
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/README.md +353 -0
- package/package.json +5 -5
- package/src/components/crm/Field.jsx +88 -60
- package/src/components/crm/columns/ColumnRenderers.jsx +19 -0
- package/src/components/crm/generic/GenericDetail.jsx +40 -0
- package/src/components/crm/generic/GenericEditableTable.jsx +3 -3
- package/src/components/crm/generic/GenericQuote.jsx +353 -1
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +102 -22
- package/src/components/crm/generic/styles/GenericQuote.module.scss +203 -0
- package/src/components/proposal/ProposalTemplatePreview.css +364 -0
- package/src/components/proposal/ProposalTemplatePreview.jsx +331 -0
- package/src/components/proposal/ProposalTemplateSectionManager.css +321 -0
- package/src/components/proposal/ProposalTemplateSectionManager.jsx +473 -0
- package/src/components/proposal/SectionEditor.css +508 -0
- package/src/components/proposal/SectionEditor.jsx +412 -0
- package/src/components/proposal/SectionTypeSelector.css +277 -0
- package/src/components/proposal/SectionTypeSelector.jsx +158 -0
- package/src/components/proposal/VariableInserter.css +392 -0
- package/src/components/proposal/VariableInserter.jsx +260 -0
- package/src/components/proposal/index.js +5 -0
- package/src/components/styles/global.css +179 -1
- package/src/index.js +13 -0
- package/src/utils/urlBuilder.js +40 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { X, Search, Tag, User, Building, Calendar, DollarSign, FileText } from 'lucide-react';
|
|
3
|
+
import { defaultProposalApiEndpoints } from '../../utils/urlBuilder';
|
|
4
|
+
import './VariableInserter.css';
|
|
5
|
+
|
|
6
|
+
const defaultVariables = [
|
|
7
|
+
{
|
|
8
|
+
category: 'Customer Information',
|
|
9
|
+
icon: User,
|
|
10
|
+
variables: [
|
|
11
|
+
{ name: 'customer_name', description: 'Customer company name' },
|
|
12
|
+
{ name: 'customer_contact_name', description: 'Primary contact name' },
|
|
13
|
+
{ name: 'customer_contact_email', description: 'Primary contact email' },
|
|
14
|
+
{ name: 'customer_contact_phone', description: 'Primary contact phone' },
|
|
15
|
+
{ name: 'customer_address', description: 'Customer address' },
|
|
16
|
+
{ name: 'customer_abn', description: 'Customer ABN' }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
category: 'Company Information',
|
|
21
|
+
icon: Building,
|
|
22
|
+
variables: [
|
|
23
|
+
{ name: 'company_name', description: 'Your company name' },
|
|
24
|
+
{ name: 'company_address', description: 'Your company address' },
|
|
25
|
+
{ name: 'company_phone', description: 'Your company phone' },
|
|
26
|
+
{ name: 'company_email', description: 'Your company email' },
|
|
27
|
+
{ name: 'company_abn', description: 'Your company ABN' },
|
|
28
|
+
{ name: 'company_website', description: 'Your company website' }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
category: 'Quote Details',
|
|
33
|
+
icon: FileText,
|
|
34
|
+
variables: [
|
|
35
|
+
{ name: 'quote_number', description: 'Quote reference number' },
|
|
36
|
+
{ name: 'quote_date', description: 'Quote creation date' },
|
|
37
|
+
{ name: 'quote_valid_until', description: 'Quote expiry date' },
|
|
38
|
+
{ name: 'quote_total', description: 'Total quote amount' },
|
|
39
|
+
{ name: 'quote_subtotal', description: 'Quote subtotal' },
|
|
40
|
+
{ name: 'quote_tax', description: 'Quote tax amount' },
|
|
41
|
+
{ name: 'quote_items_table', description: 'Complete quote items table' }
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
category: 'Project Information',
|
|
46
|
+
icon: Calendar,
|
|
47
|
+
variables: [
|
|
48
|
+
{ name: 'project_name', description: 'Project title' },
|
|
49
|
+
{ name: 'project_description', description: 'Project description' },
|
|
50
|
+
{ name: 'project_start_date', description: 'Proposed start date' },
|
|
51
|
+
{ name: 'project_end_date', description: 'Proposed end date' },
|
|
52
|
+
{ name: 'project_duration', description: 'Project duration' }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
category: 'Sales Information',
|
|
57
|
+
icon: DollarSign,
|
|
58
|
+
variables: [
|
|
59
|
+
{ name: 'salesperson_name', description: 'Sales representative name' },
|
|
60
|
+
{ name: 'salesperson_email', description: 'Sales representative email' },
|
|
61
|
+
{ name: 'salesperson_phone', description: 'Sales representative phone' },
|
|
62
|
+
{ name: 'proposal_date', description: 'Proposal generation date' }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const VariableInserter = ({
|
|
68
|
+
availableVariables = [],
|
|
69
|
+
onInsert,
|
|
70
|
+
onCancel,
|
|
71
|
+
apiEndpoints = defaultProposalApiEndpoints
|
|
72
|
+
}) => {
|
|
73
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
74
|
+
const [selectedCategory, setSelectedCategory] = useState('all');
|
|
75
|
+
const [intelligentVariables, setIntelligentVariables] = useState([]);
|
|
76
|
+
const [isLoadingIntelligent, setIsLoadingIntelligent] = useState(false);
|
|
77
|
+
const [useIntelligentVariables, setUseIntelligentVariables] = useState(true);
|
|
78
|
+
|
|
79
|
+
// Load intelligent variables from API
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const loadIntelligentVariables = async () => {
|
|
82
|
+
if (!useIntelligentVariables) return;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
setIsLoadingIntelligent(true);
|
|
86
|
+
const response = await fetch(apiEndpoints.intelligentVariables);
|
|
87
|
+
const data = await response.json();
|
|
88
|
+
|
|
89
|
+
if (data.success && data.variables) {
|
|
90
|
+
setIntelligentVariables(data.variables);
|
|
91
|
+
} else {
|
|
92
|
+
console.warn('No intelligent variables available, falling back to defaults');
|
|
93
|
+
setUseIntelligentVariables(false);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Failed to load intelligent variables:', error);
|
|
97
|
+
setUseIntelligentVariables(false);
|
|
98
|
+
} finally {
|
|
99
|
+
setIsLoadingIntelligent(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
loadIntelligentVariables();
|
|
104
|
+
}, [useIntelligentVariables]);
|
|
105
|
+
|
|
106
|
+
// Merge available variables with intelligent variables or defaults
|
|
107
|
+
const allVariables = useMemo(() => {
|
|
108
|
+
if (availableVariables.length > 0) {
|
|
109
|
+
return availableVariables;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (useIntelligentVariables && intelligentVariables.length > 0) {
|
|
113
|
+
return intelligentVariables;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return defaultVariables;
|
|
117
|
+
}, [availableVariables, intelligentVariables, useIntelligentVariables]);
|
|
118
|
+
|
|
119
|
+
const filteredVariables = allVariables.map(category => ({
|
|
120
|
+
...category,
|
|
121
|
+
variables: category.variables.filter(variable =>
|
|
122
|
+
variable.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
123
|
+
variable.description.toLowerCase().includes(searchTerm.toLowerCase())
|
|
124
|
+
)
|
|
125
|
+
})).filter(category =>
|
|
126
|
+
selectedCategory === 'all' ||
|
|
127
|
+
category.category.toLowerCase().includes(selectedCategory.toLowerCase())
|
|
128
|
+
).filter(category => category.variables.length > 0);
|
|
129
|
+
|
|
130
|
+
const categories = ['all', ...allVariables.map(cat => cat.category)];
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="variable-inserter-overlay">
|
|
134
|
+
<div className="variable-inserter-modal">
|
|
135
|
+
<div className="variable-inserter-header">
|
|
136
|
+
<div>
|
|
137
|
+
<h2 className="variable-inserter-title">Insert Variable</h2>
|
|
138
|
+
<p className="variable-inserter-subtitle">
|
|
139
|
+
Click on a variable to insert it at the cursor position
|
|
140
|
+
{useIntelligentVariables && intelligentVariables.length > 0 && (
|
|
141
|
+
<span className="variable-inserter-intelligent-indicator">
|
|
142
|
+
• Using intelligent model-based variables
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
</p>
|
|
146
|
+
{isLoadingIntelligent && (
|
|
147
|
+
<p className="variable-inserter-loading">
|
|
148
|
+
Loading intelligent variables...
|
|
149
|
+
</p>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
<div className="variable-inserter-header-actions">
|
|
153
|
+
{intelligentVariables.length > 0 && (
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => setUseIntelligentVariables(!useIntelligentVariables)}
|
|
156
|
+
className="variable-inserter-toggle-btn"
|
|
157
|
+
title={useIntelligentVariables ? "Switch to default variables" : "Switch to intelligent variables"}
|
|
158
|
+
>
|
|
159
|
+
{useIntelligentVariables ? "Default" : "Smart"}
|
|
160
|
+
</button>
|
|
161
|
+
)}
|
|
162
|
+
<button
|
|
163
|
+
onClick={onCancel}
|
|
164
|
+
className="variable-inserter-close-btn"
|
|
165
|
+
>
|
|
166
|
+
<X size={20} />
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div className="variable-inserter-search-section">
|
|
172
|
+
<div className="variable-inserter-search-container">
|
|
173
|
+
{/* Search */}
|
|
174
|
+
<div className="variable-inserter-search-input-wrapper">
|
|
175
|
+
<Search size={20} className="variable-inserter-search-icon" />
|
|
176
|
+
<input
|
|
177
|
+
type="text"
|
|
178
|
+
placeholder="Search variables..."
|
|
179
|
+
value={searchTerm}
|
|
180
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
181
|
+
className="variable-inserter-search-input"
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Category Filter */}
|
|
186
|
+
<select
|
|
187
|
+
value={selectedCategory}
|
|
188
|
+
onChange={(e) => setSelectedCategory(e.target.value)}
|
|
189
|
+
className="variable-inserter-category-select"
|
|
190
|
+
>
|
|
191
|
+
{categories.map(category => (
|
|
192
|
+
<option key={category} value={category}>
|
|
193
|
+
{category === 'all' ? 'All Categories' : category}
|
|
194
|
+
</option>
|
|
195
|
+
))}
|
|
196
|
+
</select>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="variable-inserter-content">
|
|
201
|
+
{filteredVariables.length === 0 ? (
|
|
202
|
+
<div className="variable-inserter-empty">
|
|
203
|
+
<Tag size={48} className="variable-inserter-empty-icon" />
|
|
204
|
+
<h3 className="variable-inserter-empty-title">No variables found</h3>
|
|
205
|
+
<p className="variable-inserter-empty-text">Try adjusting your search or category filter</p>
|
|
206
|
+
</div>
|
|
207
|
+
) : (
|
|
208
|
+
<div className="variable-inserter-categories">
|
|
209
|
+
{filteredVariables.map((category) => {
|
|
210
|
+
const IconComponent = category.icon;
|
|
211
|
+
return (
|
|
212
|
+
<div key={category.category} className="variable-inserter-category">
|
|
213
|
+
<div className="variable-inserter-category-header">
|
|
214
|
+
<IconComponent size={20} className="variable-inserter-category-icon" />
|
|
215
|
+
<h3 className="variable-inserter-category-title">
|
|
216
|
+
{category.category}
|
|
217
|
+
</h3>
|
|
218
|
+
</div>
|
|
219
|
+
<div className="variable-inserter-variables-grid">
|
|
220
|
+
{category.variables.map((variable) => (
|
|
221
|
+
<button
|
|
222
|
+
key={variable.name}
|
|
223
|
+
onClick={() => onInsert(variable.name)}
|
|
224
|
+
className="variable-inserter-variable-button"
|
|
225
|
+
>
|
|
226
|
+
<div className="variable-inserter-variable-content">
|
|
227
|
+
<div className="variable-inserter-variable-info">
|
|
228
|
+
<div className="variable-inserter-variable-name">
|
|
229
|
+
{`{{${variable.name}}}`}
|
|
230
|
+
</div>
|
|
231
|
+
<div className="variable-inserter-variable-description">
|
|
232
|
+
{variable.description}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
<Tag size={16} className="variable-inserter-variable-icon" />
|
|
236
|
+
</div>
|
|
237
|
+
</button>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
})}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div className="variable-inserter-footer">
|
|
248
|
+
<button
|
|
249
|
+
onClick={onCancel}
|
|
250
|
+
className="variable-inserter-cancel-btn"
|
|
251
|
+
>
|
|
252
|
+
Cancel
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export default VariableInserter;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as ProposalTemplateSectionManager } from './ProposalTemplateSectionManager';
|
|
2
|
+
export { default as SectionEditor } from './SectionEditor';
|
|
3
|
+
export { default as SectionTypeSelector } from './SectionTypeSelector';
|
|
4
|
+
export { default as VariableInserter } from './VariableInserter';
|
|
5
|
+
export { default as ProposalTemplatePreview } from './ProposalTemplatePreview';
|
|
@@ -194,7 +194,9 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
194
194
|
.inovua-react-toolkit-combo-box__input
|
|
195
195
|
):not(.inovua-react-toolkit-date-input__input):not(
|
|
196
196
|
.inovua-react-toolkit-text-input__input
|
|
197
|
-
):not(.inovua-react-toolkit-numeric-input__input)
|
|
197
|
+
):not(.inovua-react-toolkit-numeric-input__input):not(
|
|
198
|
+
.variable-inserter-search-input
|
|
199
|
+
) {
|
|
198
200
|
background: white;
|
|
199
201
|
border-radius: var(--br);
|
|
200
202
|
border: 1px solid rgba(var(--paragraph-color-rgb), 0.15);
|
|
@@ -438,6 +440,182 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
438
440
|
height: 0 !important;
|
|
439
441
|
}
|
|
440
442
|
|
|
443
|
+
/* Relation Color Preview Styles */
|
|
444
|
+
.relation-color-container {
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
gap: 8px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.relation-color-container.inline {
|
|
451
|
+
display: inline-flex;
|
|
452
|
+
margin-right: 4px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.relation-color-preview {
|
|
456
|
+
width: 20px;
|
|
457
|
+
height: 20px;
|
|
458
|
+
border: 2px solid #dee2e6;
|
|
459
|
+
border-radius: 4px;
|
|
460
|
+
background-color: #ffffff;
|
|
461
|
+
position: relative;
|
|
462
|
+
overflow: hidden;
|
|
463
|
+
flex-shrink: 0;
|
|
464
|
+
cursor: pointer;
|
|
465
|
+
transition: all 0.2s ease;
|
|
466
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
467
|
+
|
|
468
|
+
/* Checkerboard pattern for transparency */
|
|
469
|
+
background-image:
|
|
470
|
+
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
471
|
+
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
472
|
+
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
473
|
+
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
474
|
+
background-size: 4px 4px;
|
|
475
|
+
background-position: 0 0, 0 2px, 2px -2px, -2px 0px;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.relation-color-preview:hover {
|
|
479
|
+
border-color: #adb5bd;
|
|
480
|
+
transform: scale(1.1);
|
|
481
|
+
box-shadow:
|
|
482
|
+
inset 0 1px 2px rgba(0, 0, 0, 0.1),
|
|
483
|
+
0 2px 8px rgba(0, 0, 0, 0.15);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.relation-color-text {
|
|
487
|
+
font-size: 0.875rem;
|
|
488
|
+
font-weight: 500;
|
|
489
|
+
color: #495057;
|
|
490
|
+
font-family: 'Courier New', monospace;
|
|
491
|
+
user-select: all;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.relation-array-container {
|
|
495
|
+
display: flex;
|
|
496
|
+
flex-wrap: wrap;
|
|
497
|
+
align-items: center;
|
|
498
|
+
gap: 4px;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.separator {
|
|
502
|
+
color: #6c757d;
|
|
503
|
+
margin: 0 2px;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Compact Inline Color Picker Styles for Forms */
|
|
507
|
+
.cpicker-inline {
|
|
508
|
+
width: 100%;
|
|
509
|
+
display: flex;
|
|
510
|
+
flex-direction: column;
|
|
511
|
+
gap: 8px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.cpicker-preview-container {
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
gap: 8px;
|
|
518
|
+
padding: 6px 10px;
|
|
519
|
+
background: #f8f9fa;
|
|
520
|
+
border: 1px solid #dee2e6;
|
|
521
|
+
border-radius: 6px;
|
|
522
|
+
min-height: 40px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.cpicker__box {
|
|
526
|
+
width: 28px;
|
|
527
|
+
height: 28px;
|
|
528
|
+
border: 2px solid #dee2e6;
|
|
529
|
+
border-radius: 6px;
|
|
530
|
+
background-color: #ffffff;
|
|
531
|
+
position: relative;
|
|
532
|
+
overflow: hidden;
|
|
533
|
+
transition: all 0.2s ease;
|
|
534
|
+
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
535
|
+
flex-shrink: 0;
|
|
536
|
+
|
|
537
|
+
/* Checkerboard pattern for transparency */
|
|
538
|
+
background-image:
|
|
539
|
+
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
540
|
+
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
541
|
+
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
542
|
+
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
543
|
+
background-size: 6px 6px;
|
|
544
|
+
background-position: 0 0, 0 3px, 3px -3px, -3px 0px;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* When no color is selected */
|
|
548
|
+
.cpicker__box[style=""] {
|
|
549
|
+
background:
|
|
550
|
+
linear-gradient(45deg,
|
|
551
|
+
transparent 40%,
|
|
552
|
+
#dc3545 42%,
|
|
553
|
+
#dc3545 58%,
|
|
554
|
+
transparent 60%
|
|
555
|
+
),
|
|
556
|
+
linear-gradient(-45deg,
|
|
557
|
+
transparent 40%,
|
|
558
|
+
#dc3545 42%,
|
|
559
|
+
#dc3545 58%,
|
|
560
|
+
transparent 60%
|
|
561
|
+
),
|
|
562
|
+
#ffffff;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.cpicker-value {
|
|
566
|
+
font-size: 0.8rem;
|
|
567
|
+
font-weight: 500;
|
|
568
|
+
color: #495057;
|
|
569
|
+
font-family: 'Courier New', monospace;
|
|
570
|
+
background: rgba(255, 255, 255, 0.9);
|
|
571
|
+
padding: 3px 6px;
|
|
572
|
+
border-radius: 3px;
|
|
573
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
574
|
+
flex-grow: 1;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.cpicker-container {
|
|
578
|
+
width: 100%;
|
|
579
|
+
display: flex;
|
|
580
|
+
justify-content: center;
|
|
581
|
+
padding: 8px;
|
|
582
|
+
background: #ffffff;
|
|
583
|
+
border: 1px solid #dee2e6;
|
|
584
|
+
border-radius: 6px;
|
|
585
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* Ensure Sketch picker fits well and is more compact */
|
|
589
|
+
.cpicker-container .w-color-sketch {
|
|
590
|
+
box-shadow: none !important;
|
|
591
|
+
border: none !important;
|
|
592
|
+
border-radius: 6px !important;
|
|
593
|
+
max-width: 100% !important;
|
|
594
|
+
transform: scale(0.85) !important;
|
|
595
|
+
transform-origin: center top !important;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* Further responsive scaling for color picker */
|
|
599
|
+
@media (max-width: 768px) {
|
|
600
|
+
.cpicker-container {
|
|
601
|
+
padding: 6px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.cpicker-container .w-color-sketch {
|
|
605
|
+
transform: scale(0.75) !important;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
@media (max-width: 480px) {
|
|
610
|
+
.cpicker-container {
|
|
611
|
+
padding: 4px;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.cpicker-container .w-color-sketch {
|
|
615
|
+
transform: scale(0.65) !important;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
441
619
|
/* Safari-specific fixes for form elements */
|
|
442
620
|
@supports (-webkit-touch-callout: none) {
|
|
443
621
|
/* Safari-specific styles */
|
package/src/index.js
CHANGED
|
@@ -65,6 +65,14 @@ import GenericReport from './components/crm/generic/GenericReport';
|
|
|
65
65
|
import GenericSort from './components/crm/generic/GenericSort';
|
|
66
66
|
import NotificationList from './components/crm/generic/NotificationList';
|
|
67
67
|
|
|
68
|
+
/** Proposal Components */
|
|
69
|
+
import ProposalTemplateSectionManager from './components/proposal/ProposalTemplateSectionManager';
|
|
70
|
+
import SectionEditor from './components/proposal/SectionEditor';
|
|
71
|
+
import SectionTypeSelector from './components/proposal/SectionTypeSelector';
|
|
72
|
+
import VariableInserter from './components/proposal/VariableInserter';
|
|
73
|
+
import ProposalTemplatePreview from './components/proposal/ProposalTemplatePreview';
|
|
74
|
+
|
|
75
|
+
|
|
68
76
|
export {
|
|
69
77
|
AsyncSelect,
|
|
70
78
|
AssociationManager,
|
|
@@ -115,11 +123,16 @@ export {
|
|
|
115
123
|
Notification,
|
|
116
124
|
NotificationList,
|
|
117
125
|
Profile,
|
|
126
|
+
ProposalTemplateSectionManager,
|
|
127
|
+
ProposalTemplatePreview,
|
|
118
128
|
Reset,
|
|
129
|
+
SectionEditor,
|
|
130
|
+
SectionTypeSelector,
|
|
119
131
|
SelectList,
|
|
120
132
|
Select,
|
|
121
133
|
SortableList,
|
|
122
134
|
Table,
|
|
123
135
|
TableFilter,
|
|
136
|
+
VariableInserter,
|
|
124
137
|
Verify,
|
|
125
138
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build URL by replacing template variables with actual values
|
|
3
|
+
* @param {string} urlTemplate - URL template with placeholders like /ajax/{templateId}/sections/{sectionId}
|
|
4
|
+
* @param {object} params - Object containing parameter values
|
|
5
|
+
* @returns {string} URL with placeholders replaced
|
|
6
|
+
*/
|
|
7
|
+
export const buildUrl = (urlTemplate, params = {}) => {
|
|
8
|
+
let url = urlTemplate;
|
|
9
|
+
|
|
10
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
11
|
+
url = url.replace(`{${key}}`, value);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return url;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default API endpoints configuration for proposal system
|
|
19
|
+
*/
|
|
20
|
+
export const defaultProposalApiEndpoints = {
|
|
21
|
+
// Variable management
|
|
22
|
+
intelligentVariables: '/ajax/proposalTemplates/variables/intelligent',
|
|
23
|
+
availableVariables: '/ajax/proposalTemplates/variables/available',
|
|
24
|
+
|
|
25
|
+
// Template management
|
|
26
|
+
templateData: '/ajax/proposalTemplates/{templateId}',
|
|
27
|
+
templatePreview: '/ajax/proposalTemplates/{templateId}/preview',
|
|
28
|
+
templatePdf: '/ajax/proposalTemplates/{templateId}/pdf',
|
|
29
|
+
templateFullPreview: '/ajax/proposalTemplates/{templateId}/preview',
|
|
30
|
+
|
|
31
|
+
// Section management
|
|
32
|
+
sectionsCreate: '/ajax/proposalTemplates/{templateId}/sections',
|
|
33
|
+
sectionsUpdate: '/ajax/proposalTemplates/{templateId}/sections/{sectionId}',
|
|
34
|
+
sectionsDelete: '/ajax/proposalTemplates/{templateId}/sections/{sectionId}',
|
|
35
|
+
sectionsReorder: '/ajax/proposalTemplates/{templateId}/sections/reorder',
|
|
36
|
+
sectionsToggle: '/ajax/proposalTemplates/{templateId}/sections/{sectionId}',
|
|
37
|
+
|
|
38
|
+
// Branding profiles
|
|
39
|
+
brandingProfiles: '/ajax/brandingProfiles/dropdown'
|
|
40
|
+
};
|