@visns-studio/visns-components 5.13.10 → 5.13.12
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 +4 -3
- package/package.json +1 -1
- package/src/components/DataGrid.jsx +23 -1
- package/src/components/proposal/BrandingProfileCompanyInfo.jsx +18 -1
- package/src/components/proposal/ProposalTemplatePreview.jsx +31 -3
- package/src/components/proposal/VariableInserter.css +77 -0
- package/src/components/proposal/VariableInserter.jsx +165 -45
- package/src/utils/urlBuilder.js +2 -0
package/README.md
CHANGED
|
@@ -2937,7 +2937,8 @@ const proposalEndpoints = {
|
|
|
2937
2937
|
brandingCSS: '/ajax/branding-profiles/{id}/css',
|
|
2938
2938
|
|
|
2939
2939
|
// PDF generation
|
|
2940
|
-
generatePDF: '/ajax/pdf/generate-proposal',
|
|
2940
|
+
generatePDF: '/ajax/pdf/generate-proposal', // Legacy DomPDF
|
|
2941
|
+
generatePDFSpatie: '/ajax/pdf/generate-proposal-spatie', // Recommended: Spatie PDF
|
|
2941
2942
|
previewHTML: '/ajax/pdf/preview-proposal',
|
|
2942
2943
|
|
|
2943
2944
|
// Variable helpers
|
|
@@ -3092,8 +3093,8 @@ const QuoteProposalManager = () => {
|
|
|
3092
3093
|
// Generate PDF or handle proposal data
|
|
3093
3094
|
console.log('Generating proposal PDF:', proposalData);
|
|
3094
3095
|
|
|
3095
|
-
// Example: Download PDF
|
|
3096
|
-
window.open(`/ajax/pdf/generate-proposal?quote_id=${quoteData.id}`, '_blank');
|
|
3096
|
+
// Example: Download PDF (using Spatie - recommended)
|
|
3097
|
+
window.open(`/ajax/pdf/generate-proposal-spatie?quote_id=${quoteData.id}`, '_blank');
|
|
3097
3098
|
}}
|
|
3098
3099
|
/>
|
|
3099
3100
|
</div>
|
package/package.json
CHANGED
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
89
89
|
},
|
|
90
90
|
"name": "@visns-studio/visns-components",
|
|
91
|
-
"version": "5.13.
|
|
91
|
+
"version": "5.13.12",
|
|
92
92
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
93
93
|
"main": "src/index.js",
|
|
94
94
|
"files": [
|
|
@@ -450,7 +450,7 @@ const DataGrid = forwardRef(
|
|
|
450
450
|
if (gridRef.current?.api) {
|
|
451
451
|
gridRef.current.api.forceUpdate();
|
|
452
452
|
}
|
|
453
|
-
},
|
|
453
|
+
}, 50); // Increased delay to ensure proper rendering consistency
|
|
454
454
|
});
|
|
455
455
|
|
|
456
456
|
return sqlResult.then((res) => ({
|
|
@@ -1208,6 +1208,14 @@ const DataGrid = forwardRef(
|
|
|
1208
1208
|
|
|
1209
1209
|
const handleReload = () => {
|
|
1210
1210
|
gridRef.current.paginationProps.reload();
|
|
1211
|
+
|
|
1212
|
+
// Force grid to recalculate row heights after reload
|
|
1213
|
+
// This fixes the row height rendering issue after filter changes
|
|
1214
|
+
setTimeout(() => {
|
|
1215
|
+
if (gridRef.current?.api) {
|
|
1216
|
+
gridRef.current.api.forceUpdate();
|
|
1217
|
+
}
|
|
1218
|
+
}, 50); // Increased delay to ensure proper rendering after reload
|
|
1211
1219
|
};
|
|
1212
1220
|
|
|
1213
1221
|
const handleGroupAction = async (groupValue, iconConfig) => {
|
|
@@ -1312,6 +1320,13 @@ const DataGrid = forwardRef(
|
|
|
1312
1320
|
// Reload grid data if needed
|
|
1313
1321
|
if (gridRef.current && gridRef.current.paginationProps) {
|
|
1314
1322
|
gridRef.current.paginationProps.reload();
|
|
1323
|
+
|
|
1324
|
+
// Force grid to recalculate row heights after reload
|
|
1325
|
+
setTimeout(() => {
|
|
1326
|
+
if (gridRef.current?.api) {
|
|
1327
|
+
gridRef.current.api.forceUpdate();
|
|
1328
|
+
}
|
|
1329
|
+
}, 50);
|
|
1315
1330
|
}
|
|
1316
1331
|
} catch (error) {
|
|
1317
1332
|
console.error('Group action fetch error:', error);
|
|
@@ -3130,6 +3145,13 @@ const DataGrid = forwardRef(
|
|
|
3130
3145
|
// Force reload of data
|
|
3131
3146
|
if (gridRef.current && gridRef.current.paginationProps) {
|
|
3132
3147
|
gridRef.current.paginationProps.reload();
|
|
3148
|
+
|
|
3149
|
+
// Force grid to recalculate row heights after reload
|
|
3150
|
+
setTimeout(() => {
|
|
3151
|
+
if (gridRef.current?.api) {
|
|
3152
|
+
gridRef.current.api.forceUpdate();
|
|
3153
|
+
}
|
|
3154
|
+
}, 50);
|
|
3133
3155
|
}
|
|
3134
3156
|
}, [ajaxSetting?.url]);
|
|
3135
3157
|
|
|
@@ -14,6 +14,7 @@ const BrandingProfileCompanyInfo = ({
|
|
|
14
14
|
address: '',
|
|
15
15
|
website: '',
|
|
16
16
|
phone: '',
|
|
17
|
+
email: '',
|
|
17
18
|
abn: '',
|
|
18
19
|
...brandingProfile?.company_info || {}
|
|
19
20
|
});
|
|
@@ -95,12 +96,17 @@ const BrandingProfileCompanyInfo = ({
|
|
|
95
96
|
<strong>Phone:</strong> {companyInfo.phone}
|
|
96
97
|
</div>
|
|
97
98
|
)}
|
|
99
|
+
{companyInfo.email && (
|
|
100
|
+
<div className="branding-company-info-item">
|
|
101
|
+
<strong>Email:</strong> {companyInfo.email}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
98
104
|
{companyInfo.abn && (
|
|
99
105
|
<div className="branding-company-info-item">
|
|
100
106
|
<strong>ABN:</strong> {companyInfo.abn}
|
|
101
107
|
</div>
|
|
102
108
|
)}
|
|
103
|
-
{!companyInfo.address && !companyInfo.website && !companyInfo.phone && !companyInfo.abn && (
|
|
109
|
+
{!companyInfo.address && !companyInfo.website && !companyInfo.phone && !companyInfo.email && !companyInfo.abn && (
|
|
104
110
|
<div className="branding-company-info-empty">
|
|
105
111
|
<Info size={16} />
|
|
106
112
|
<p>No company information added yet. Click Edit to add details.</p>
|
|
@@ -157,6 +163,17 @@ const BrandingProfileCompanyInfo = ({
|
|
|
157
163
|
/>
|
|
158
164
|
</div>
|
|
159
165
|
|
|
166
|
+
<div className="branding-company-info-field">
|
|
167
|
+
<label htmlFor="company-email">Email Address</label>
|
|
168
|
+
<input
|
|
169
|
+
type="email"
|
|
170
|
+
id="company-email"
|
|
171
|
+
value={companyInfo.email}
|
|
172
|
+
onChange={(e) => handleInputChange('email', e.target.value)}
|
|
173
|
+
placeholder="info@yourcompany.com"
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
160
177
|
<div className="branding-company-info-field">
|
|
161
178
|
<label htmlFor="company-abn">ABN</label>
|
|
162
179
|
<input
|
|
@@ -128,12 +128,33 @@ const ProposalTemplatePreview = ({
|
|
|
128
128
|
|
|
129
129
|
const handleDownloadPDF = async () => {
|
|
130
130
|
try {
|
|
131
|
-
|
|
131
|
+
// Use Spatie PDF endpoint with proper payload structure
|
|
132
|
+
const payload = {
|
|
133
|
+
proposal_data: previewData?.template || {},
|
|
134
|
+
template_id: parseInt(templateId),
|
|
135
|
+
branding_id: selectedBrandingProfile ? parseInt(selectedBrandingProfile) : null,
|
|
136
|
+
sections: previewData?.sections || [],
|
|
137
|
+
header_config: {
|
|
138
|
+
enabled: true,
|
|
139
|
+
show_phone: true,
|
|
140
|
+
show_email: true,
|
|
141
|
+
show_website: true,
|
|
142
|
+
show_address: true,
|
|
143
|
+
show_abn: false
|
|
144
|
+
},
|
|
145
|
+
filename: `proposal-template-${templateId}.pdf`,
|
|
146
|
+
download: true
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
console.log('ProposalTemplatePreview: Generating PDF with payload', payload);
|
|
150
|
+
|
|
151
|
+
const response = await fetch(apiEndpoints.templatePdfSpatie, {
|
|
132
152
|
method: 'POST',
|
|
133
153
|
headers: {
|
|
134
154
|
'Content-Type': 'application/json',
|
|
135
155
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
136
|
-
}
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(payload)
|
|
137
158
|
});
|
|
138
159
|
|
|
139
160
|
if (response.ok) {
|
|
@@ -146,8 +167,15 @@ const ProposalTemplatePreview = ({
|
|
|
146
167
|
a.click();
|
|
147
168
|
window.URL.revokeObjectURL(url);
|
|
148
169
|
document.body.removeChild(a);
|
|
170
|
+
console.log('ProposalTemplatePreview: PDF generated successfully');
|
|
149
171
|
} else {
|
|
150
|
-
|
|
172
|
+
const errorText = await response.text();
|
|
173
|
+
console.error('ProposalTemplatePreview: PDF generation failed', {
|
|
174
|
+
status: response.status,
|
|
175
|
+
statusText: response.statusText,
|
|
176
|
+
error: errorText
|
|
177
|
+
});
|
|
178
|
+
throw new Error(`Failed to generate PDF: ${response.status} - ${response.statusText}`);
|
|
151
179
|
}
|
|
152
180
|
} catch (error) {
|
|
153
181
|
console.error('Error downloading PDF:', error);
|
|
@@ -109,6 +109,19 @@
|
|
|
109
109
|
color: white;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
.variable-inserter-mode-buttons {
|
|
113
|
+
display: flex;
|
|
114
|
+
gap: 0.5rem;
|
|
115
|
+
align-items: center;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.variable-inserter-toggle-btn.active {
|
|
119
|
+
background: rgba(60, 191, 125, 0.4);
|
|
120
|
+
border-color: rgba(60, 191, 125, 0.6);
|
|
121
|
+
color: white;
|
|
122
|
+
box-shadow: 0 0 0 2px rgba(60, 191, 125, 0.2);
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
.variable-inserter-intelligent-indicator {
|
|
113
126
|
color: #3cbf7d;
|
|
114
127
|
font-weight: 600;
|
|
@@ -240,8 +253,32 @@
|
|
|
240
253
|
.variable-inserter-category-header {
|
|
241
254
|
display: flex;
|
|
242
255
|
align-items: center;
|
|
256
|
+
justify-content: space-between;
|
|
243
257
|
gap: 0.5rem;
|
|
244
258
|
margin-bottom: 1rem;
|
|
259
|
+
padding: 0.75rem;
|
|
260
|
+
border-radius: 8px;
|
|
261
|
+
background: #f8fafc;
|
|
262
|
+
border: 1px solid #e2e8f0;
|
|
263
|
+
transition: all 0.2s;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.variable-inserter-category-header.clickable {
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
user-select: none;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.variable-inserter-category-header.clickable:hover {
|
|
272
|
+
background: #f1f5f9;
|
|
273
|
+
border-color: #cbd5e1;
|
|
274
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.variable-inserter-category-header-content {
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: 0.75rem;
|
|
281
|
+
flex: 1;
|
|
245
282
|
}
|
|
246
283
|
|
|
247
284
|
.variable-inserter-category-icon {
|
|
@@ -254,6 +291,30 @@
|
|
|
254
291
|
font-weight: 600;
|
|
255
292
|
color: #1f2937;
|
|
256
293
|
margin: 0;
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
gap: 0.5rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.variable-inserter-field-count {
|
|
300
|
+
font-size: 0.75rem;
|
|
301
|
+
color: #64748b;
|
|
302
|
+
font-weight: 500;
|
|
303
|
+
background: #e2e8f0;
|
|
304
|
+
padding: 0.125rem 0.375rem;
|
|
305
|
+
border-radius: 4px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.variable-inserter-expand-icon {
|
|
309
|
+
color: #64748b;
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
justify-content: center;
|
|
313
|
+
transition: transform 0.2s;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.variable-inserter-category-header.clickable:hover .variable-inserter-expand-icon {
|
|
317
|
+
color: #475569;
|
|
257
318
|
}
|
|
258
319
|
|
|
259
320
|
.variable-inserter-variables-grid {
|
|
@@ -304,6 +365,22 @@
|
|
|
304
365
|
color: #1b3933;
|
|
305
366
|
margin: 0 0 0.25rem 0;
|
|
306
367
|
word-break: break-all;
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: 0.5rem;
|
|
371
|
+
flex-wrap: wrap;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.variable-inserter-calc-badge {
|
|
375
|
+
background: #f59e0b;
|
|
376
|
+
color: white;
|
|
377
|
+
font-size: 0.625rem;
|
|
378
|
+
font-weight: 700;
|
|
379
|
+
padding: 0.125rem 0.25rem;
|
|
380
|
+
border-radius: 3px;
|
|
381
|
+
letter-spacing: 0.025em;
|
|
382
|
+
font-family: 'Arial', sans-serif;
|
|
383
|
+
flex-shrink: 0;
|
|
307
384
|
}
|
|
308
385
|
|
|
309
386
|
.variable-inserter-variable-button:hover .variable-inserter-variable-name {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { X, Search, Tag, User, Building, Calendar, DollarSign, FileText } from 'lucide-react';
|
|
2
|
+
import { X, Search, Tag, User, Building, Calendar, DollarSign, FileText, ChevronDown, ChevronRight } from 'lucide-react';
|
|
3
3
|
import { defaultProposalApiEndpoints } from '../../utils/urlBuilder';
|
|
4
4
|
import './VariableInserter.css';
|
|
5
5
|
|
|
@@ -73,8 +73,12 @@ const VariableInserter = ({
|
|
|
73
73
|
const [searchTerm, setSearchTerm] = useState('');
|
|
74
74
|
const [selectedCategory, setSelectedCategory] = useState('all');
|
|
75
75
|
const [intelligentVariables, setIntelligentVariables] = useState([]);
|
|
76
|
+
const [allDatabaseFields, setAllDatabaseFields] = useState([]);
|
|
76
77
|
const [isLoadingIntelligent, setIsLoadingIntelligent] = useState(false);
|
|
77
|
-
const [
|
|
78
|
+
const [isLoadingDatabase, setIsLoadingDatabase] = useState(false);
|
|
79
|
+
const [useIntelligentVariables, setUseIntelligentVariables] = useState(false);
|
|
80
|
+
const [useAllDatabaseFields, setUseAllDatabaseFields] = useState(true);
|
|
81
|
+
const [expandedTables, setExpandedTables] = useState(new Set());
|
|
78
82
|
|
|
79
83
|
// Load intelligent variables from API
|
|
80
84
|
useEffect(() => {
|
|
@@ -103,29 +107,83 @@ const VariableInserter = ({
|
|
|
103
107
|
loadIntelligentVariables();
|
|
104
108
|
}, [useIntelligentVariables]);
|
|
105
109
|
|
|
106
|
-
//
|
|
110
|
+
// Load all database fields from API
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const loadAllDatabaseFields = async () => {
|
|
113
|
+
if (!useAllDatabaseFields) return;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
setIsLoadingDatabase(true);
|
|
117
|
+
const response = await fetch(apiEndpoints.allDatabaseFields);
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
|
|
120
|
+
if (data.success && data.tables) {
|
|
121
|
+
setAllDatabaseFields(data.tables);
|
|
122
|
+
} else {
|
|
123
|
+
console.warn('No database fields available, falling back to intelligent variables');
|
|
124
|
+
setUseAllDatabaseFields(false);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Failed to load database fields:', error);
|
|
128
|
+
setUseAllDatabaseFields(false);
|
|
129
|
+
} finally {
|
|
130
|
+
setIsLoadingDatabase(false);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
loadAllDatabaseFields();
|
|
135
|
+
}, [useAllDatabaseFields, apiEndpoints.allDatabaseFields]);
|
|
136
|
+
|
|
137
|
+
// Toggle table expansion
|
|
138
|
+
const toggleTable = (tableName) => {
|
|
139
|
+
const newExpanded = new Set(expandedTables);
|
|
140
|
+
if (newExpanded.has(tableName)) {
|
|
141
|
+
newExpanded.delete(tableName);
|
|
142
|
+
} else {
|
|
143
|
+
newExpanded.add(tableName);
|
|
144
|
+
}
|
|
145
|
+
setExpandedTables(newExpanded);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Merge available variables with intelligent variables, database fields, or defaults
|
|
107
149
|
const allVariables = useMemo(() => {
|
|
108
150
|
if (availableVariables.length > 0) {
|
|
109
151
|
return availableVariables;
|
|
110
152
|
}
|
|
111
153
|
|
|
154
|
+
if (useAllDatabaseFields && allDatabaseFields.length > 0) {
|
|
155
|
+
return allDatabaseFields;
|
|
156
|
+
}
|
|
157
|
+
|
|
112
158
|
if (useIntelligentVariables && intelligentVariables.length > 0) {
|
|
113
159
|
return intelligentVariables;
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
return defaultVariables;
|
|
117
|
-
}, [availableVariables, intelligentVariables, useIntelligentVariables]);
|
|
163
|
+
}, [availableVariables, allDatabaseFields, intelligentVariables, useIntelligentVariables, useAllDatabaseFields]);
|
|
118
164
|
|
|
119
|
-
const filteredVariables = allVariables.map(category =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
165
|
+
const filteredVariables = allVariables.map(category => {
|
|
166
|
+
// For database fields mode, we need to handle table name searching
|
|
167
|
+
const matchesSearch = searchTerm === '' ||
|
|
168
|
+
category.category.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
169
|
+
(category.table_name && category.table_name.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
170
|
+
|
|
171
|
+
// Filter variables within category if there's a search term
|
|
172
|
+
const filteredCategoryVariables = searchTerm === '' ? category.variables :
|
|
173
|
+
category.variables.filter(variable =>
|
|
174
|
+
variable.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
175
|
+
variable.description.toLowerCase().includes(searchTerm.toLowerCase())
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...category,
|
|
180
|
+
variables: filteredCategoryVariables,
|
|
181
|
+
matchesSearch: matchesSearch || filteredCategoryVariables.length > 0
|
|
182
|
+
};
|
|
183
|
+
}).filter(category =>
|
|
126
184
|
selectedCategory === 'all' ||
|
|
127
185
|
category.category.toLowerCase().includes(selectedCategory.toLowerCase())
|
|
128
|
-
).filter(category => category.
|
|
186
|
+
).filter(category => category.matchesSearch);
|
|
129
187
|
|
|
130
188
|
const categories = ['all', ...allVariables.map(cat => cat.category)];
|
|
131
189
|
|
|
@@ -137,7 +195,12 @@ const VariableInserter = ({
|
|
|
137
195
|
<h2 className="variable-inserter-title">Insert Variable</h2>
|
|
138
196
|
<p className="variable-inserter-subtitle">
|
|
139
197
|
Click on a variable to insert it at the cursor position
|
|
140
|
-
{
|
|
198
|
+
{useAllDatabaseFields && allDatabaseFields.length > 0 && (
|
|
199
|
+
<span className="variable-inserter-intelligent-indicator">
|
|
200
|
+
• Using all database fields ({allDatabaseFields.reduce((sum, table) => sum + table.field_count, 0)} variables from {allDatabaseFields.length} tables)
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
{useIntelligentVariables && intelligentVariables.length > 0 && !useAllDatabaseFields && (
|
|
141
204
|
<span className="variable-inserter-intelligent-indicator">
|
|
142
205
|
• Using intelligent model-based variables
|
|
143
206
|
</span>
|
|
@@ -148,16 +211,46 @@ const VariableInserter = ({
|
|
|
148
211
|
Loading intelligent variables...
|
|
149
212
|
</p>
|
|
150
213
|
)}
|
|
214
|
+
{isLoadingDatabase && (
|
|
215
|
+
<p className="variable-inserter-loading">
|
|
216
|
+
Loading database fields...
|
|
217
|
+
</p>
|
|
218
|
+
)}
|
|
151
219
|
</div>
|
|
152
220
|
<div className="variable-inserter-header-actions">
|
|
153
|
-
{intelligentVariables.length > 0 && (
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
221
|
+
{(intelligentVariables.length > 0 || allDatabaseFields.length > 0) && (
|
|
222
|
+
<div className="variable-inserter-mode-buttons">
|
|
223
|
+
<button
|
|
224
|
+
onClick={() => {
|
|
225
|
+
setUseAllDatabaseFields(true);
|
|
226
|
+
setUseIntelligentVariables(false);
|
|
227
|
+
}}
|
|
228
|
+
className={`variable-inserter-toggle-btn ${useAllDatabaseFields ? 'active' : ''}`}
|
|
229
|
+
title="Show all database fields from all tables"
|
|
230
|
+
>
|
|
231
|
+
All Fields
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
onClick={() => {
|
|
235
|
+
setUseIntelligentVariables(true);
|
|
236
|
+
setUseAllDatabaseFields(false);
|
|
237
|
+
}}
|
|
238
|
+
className={`variable-inserter-toggle-btn ${useIntelligentVariables && !useAllDatabaseFields ? 'active' : ''}`}
|
|
239
|
+
title="Show intelligent model-based variables"
|
|
240
|
+
>
|
|
241
|
+
Smart
|
|
242
|
+
</button>
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => {
|
|
245
|
+
setUseIntelligentVariables(false);
|
|
246
|
+
setUseAllDatabaseFields(false);
|
|
247
|
+
}}
|
|
248
|
+
className={`variable-inserter-toggle-btn ${!useIntelligentVariables && !useAllDatabaseFields ? 'active' : ''}`}
|
|
249
|
+
title="Show default variables"
|
|
250
|
+
>
|
|
251
|
+
Default
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
161
254
|
)}
|
|
162
255
|
<button
|
|
163
256
|
onClick={onCancel}
|
|
@@ -208,35 +301,62 @@ const VariableInserter = ({
|
|
|
208
301
|
<div className="variable-inserter-categories">
|
|
209
302
|
{filteredVariables.map((category) => {
|
|
210
303
|
const IconComponent = category.icon;
|
|
304
|
+
const isExpanded = expandedTables.has(category.table_name || category.category);
|
|
305
|
+
const isTableMode = useAllDatabaseFields && category.table_name;
|
|
306
|
+
|
|
211
307
|
return (
|
|
212
308
|
<div key={category.category} className="variable-inserter-category">
|
|
213
|
-
<div
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
309
|
+
<div
|
|
310
|
+
className={`variable-inserter-category-header ${isTableMode ? 'clickable' : ''}`}
|
|
311
|
+
onClick={isTableMode ? () => toggleTable(category.table_name) : undefined}
|
|
312
|
+
>
|
|
313
|
+
<div className="variable-inserter-category-header-content">
|
|
314
|
+
<IconComponent size={20} className="variable-inserter-category-icon" />
|
|
315
|
+
<h3 className="variable-inserter-category-title">
|
|
316
|
+
{category.category}
|
|
317
|
+
{isTableMode && (
|
|
318
|
+
<span className="variable-inserter-field-count">
|
|
319
|
+
{category.field_count || category.variables.length} fields
|
|
320
|
+
</span>
|
|
321
|
+
)}
|
|
322
|
+
</h3>
|
|
323
|
+
</div>
|
|
324
|
+
{isTableMode && (
|
|
325
|
+
<div className="variable-inserter-expand-icon">
|
|
326
|
+
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
218
329
|
</div>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
330
|
+
|
|
331
|
+
{/* Show variables only if expanded (for table mode) or always (for other modes) */}
|
|
332
|
+
{(!isTableMode || isExpanded) && (
|
|
333
|
+
<div className="variable-inserter-variables-grid">
|
|
334
|
+
{category.variables.map((variable) => (
|
|
335
|
+
<button
|
|
336
|
+
key={variable.name}
|
|
337
|
+
onClick={() => onInsert(variable.name)}
|
|
338
|
+
className="variable-inserter-variable-button"
|
|
339
|
+
>
|
|
340
|
+
<div className="variable-inserter-variable-content">
|
|
341
|
+
<div className="variable-inserter-variable-info">
|
|
342
|
+
<div className="variable-inserter-variable-name">
|
|
343
|
+
{`{{${variable.name}}}`}
|
|
344
|
+
{variable.is_calculation_version && (
|
|
345
|
+
<span className="variable-inserter-calc-badge">
|
|
346
|
+
CALC
|
|
347
|
+
</span>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
<div className="variable-inserter-variable-description">
|
|
351
|
+
{variable.description}
|
|
352
|
+
</div>
|
|
233
353
|
</div>
|
|
354
|
+
<Tag size={16} className="variable-inserter-variable-icon" />
|
|
234
355
|
</div>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
</div>
|
|
356
|
+
</button>
|
|
357
|
+
))}
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
240
360
|
</div>
|
|
241
361
|
);
|
|
242
362
|
})}
|
package/src/utils/urlBuilder.js
CHANGED
|
@@ -21,11 +21,13 @@ export const defaultProposalApiEndpoints = {
|
|
|
21
21
|
// Variable management
|
|
22
22
|
intelligentVariables: '/ajax/proposalTemplates/variables/intelligent',
|
|
23
23
|
availableVariables: '/ajax/proposalTemplates/variables/available',
|
|
24
|
+
allDatabaseFields: '/ajax/proposalTemplates/variables/all-database-fields',
|
|
24
25
|
|
|
25
26
|
// Template management
|
|
26
27
|
templateData: '/ajax/proposalTemplates/{templateId}',
|
|
27
28
|
templatePreview: '/ajax/proposalTemplates/{templateId}/preview',
|
|
28
29
|
templatePdf: '/ajax/proposalTemplates/{templateId}/pdf',
|
|
30
|
+
templatePdfSpatie: '/ajax/pdf/generate-proposal-spatie',
|
|
29
31
|
templateFullPreview: '/ajax/proposalTemplates/{templateId}/preview',
|
|
30
32
|
|
|
31
33
|
// Section management
|