@visns-studio/visns-components 5.11.4 → 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 +1213 -11
- package/package.json +5 -5
- package/src/components/crm/AssociationManager.jsx +151 -33
- package/src/components/crm/Field.jsx +115 -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,412 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { X, Save, Tag, Settings, Eye } from 'lucide-react';
|
|
3
|
+
import Field from '../crm/Field';
|
|
4
|
+
import VariableInserter from './VariableInserter';
|
|
5
|
+
import './SectionEditor.css';
|
|
6
|
+
|
|
7
|
+
const SectionEditor = ({ section, availableVariables = [], onSave, onCancel }) => {
|
|
8
|
+
const [formData, setFormData] = useState({
|
|
9
|
+
id: section?.id || null,
|
|
10
|
+
title: section?.title || '',
|
|
11
|
+
section_type: section?.section_type || '',
|
|
12
|
+
content: section?.content || '',
|
|
13
|
+
variables: section?.variables || [],
|
|
14
|
+
styling: section?.styling || {},
|
|
15
|
+
is_enabled: section?.is_enabled !== false,
|
|
16
|
+
is_dynamic: section?.is_dynamic !== false,
|
|
17
|
+
sort_order: section?.sort_order || 1
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const [showVariableInserter, setShowVariableInserter] = useState(false);
|
|
21
|
+
const [showStylingOptions, setShowStylingOptions] = useState(false);
|
|
22
|
+
const [isPreview, setIsPreview] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (section) {
|
|
26
|
+
setFormData({
|
|
27
|
+
id: section.id || null,
|
|
28
|
+
title: section.title || '',
|
|
29
|
+
section_type: section.section_type || '',
|
|
30
|
+
content: section.content || '',
|
|
31
|
+
variables: section.variables || [],
|
|
32
|
+
styling: section.styling || {},
|
|
33
|
+
is_enabled: section.is_enabled !== false,
|
|
34
|
+
is_dynamic: section.is_dynamic !== false,
|
|
35
|
+
sort_order: section.sort_order || 1
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}, [section]);
|
|
39
|
+
|
|
40
|
+
const handleInputChange = (field, value) => {
|
|
41
|
+
console.log(`Field change: ${field} =`, value);
|
|
42
|
+
setFormData(prev => ({
|
|
43
|
+
...prev,
|
|
44
|
+
[field]: value
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleStylingChange = (property, value) => {
|
|
49
|
+
setFormData(prev => ({
|
|
50
|
+
...prev,
|
|
51
|
+
styling: {
|
|
52
|
+
...prev.styling,
|
|
53
|
+
[property]: value
|
|
54
|
+
}
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleVariableInsert = (variable) => {
|
|
59
|
+
const variableTag = `{{${variable}}}`;
|
|
60
|
+
|
|
61
|
+
// Try to insert at cursor position in TinyMCE editor
|
|
62
|
+
if (window.tinymce && window.tinymce.activeEditor) {
|
|
63
|
+
window.tinymce.activeEditor.insertContent(variableTag);
|
|
64
|
+
} else {
|
|
65
|
+
// Fallback: append to content
|
|
66
|
+
handleInputChange('content', formData.content + variableTag);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add to variables array if not already present
|
|
70
|
+
if (!formData.variables.includes(variable)) {
|
|
71
|
+
setFormData(prev => ({
|
|
72
|
+
...prev,
|
|
73
|
+
variables: [...prev.variables, variable]
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setShowVariableInserter(false);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleSave = () => {
|
|
81
|
+
if (!formData.title.trim()) {
|
|
82
|
+
alert('Please enter a section title');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Try to get the latest content from TinyMCE editor before saving
|
|
87
|
+
let currentContent = formData.content;
|
|
88
|
+
|
|
89
|
+
// Multiple approaches to get TinyMCE content
|
|
90
|
+
if (window.tinymce) {
|
|
91
|
+
// Try active editor first
|
|
92
|
+
if (window.tinymce.activeEditor) {
|
|
93
|
+
currentContent = window.tinymce.activeEditor.getContent();
|
|
94
|
+
console.log('TinyMCE content from activeEditor:', currentContent);
|
|
95
|
+
}
|
|
96
|
+
// Try to find editor by content field
|
|
97
|
+
else if (window.tinymce.get('content')) {
|
|
98
|
+
currentContent = window.tinymce.get('content').getContent();
|
|
99
|
+
console.log('TinyMCE content from content field:', currentContent);
|
|
100
|
+
}
|
|
101
|
+
// Try to find any editor instances
|
|
102
|
+
else if (window.tinymce.editors && window.tinymce.editors.length > 0) {
|
|
103
|
+
currentContent = window.tinymce.editors[0].getContent();
|
|
104
|
+
console.log('TinyMCE content from first editor:', currentContent);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const finalFormData = {
|
|
109
|
+
...formData,
|
|
110
|
+
content: currentContent
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
console.log('Final form data being saved:', finalFormData);
|
|
114
|
+
onSave(finalFormData);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const renderPreview = () => {
|
|
118
|
+
// Ensure content is a string
|
|
119
|
+
let previewContent = formData.content || '';
|
|
120
|
+
|
|
121
|
+
// Convert to string if it's not already
|
|
122
|
+
if (typeof previewContent !== 'string') {
|
|
123
|
+
previewContent = String(previewContent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If content is empty, show placeholder
|
|
127
|
+
if (!previewContent.trim()) {
|
|
128
|
+
return (
|
|
129
|
+
<div className="prose prose-sm max-w-none border border-gray-200 rounded-lg p-4 bg-gray-50">
|
|
130
|
+
<div className="text-gray-500 italic">No content to preview. Add some content to see the preview.</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Replace variables with sample data for preview
|
|
136
|
+
const sampleData = {
|
|
137
|
+
customer_name: 'ACME Corporation',
|
|
138
|
+
company_name: 'OMNIA Global Group',
|
|
139
|
+
project_name: 'Digital Transformation Project',
|
|
140
|
+
quote_total: '$25,000.00',
|
|
141
|
+
quote_date: new Date().toLocaleDateString(),
|
|
142
|
+
salesperson_name: 'John Smith'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
Object.entries(sampleData).forEach(([key, value]) => {
|
|
147
|
+
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
148
|
+
previewContent = previewContent.replace(regex, `<span class="bg-yellow-100 px-1 font-medium">${value}</span>`);
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Error processing preview content:', error);
|
|
152
|
+
previewContent = formData.content || '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="prose prose-sm max-w-none border border-gray-200 rounded-lg p-4 bg-gray-50">
|
|
157
|
+
<div dangerouslySetInnerHTML={{ __html: previewContent }} />
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="section-editor-modal">
|
|
164
|
+
<div className="section-editor-content">
|
|
165
|
+
<div className="section-editor-header">
|
|
166
|
+
<div>
|
|
167
|
+
<h2 className="section-editor-title">
|
|
168
|
+
{section?.id ? 'Edit Section' : 'New Section'}
|
|
169
|
+
</h2>
|
|
170
|
+
<p className="section-editor-subtitle">
|
|
171
|
+
Section Type: {formData.section_type?.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
|
172
|
+
</p>
|
|
173
|
+
</div>
|
|
174
|
+
<div className="section-editor-header-actions">
|
|
175
|
+
<button
|
|
176
|
+
onClick={() => setIsPreview(!isPreview)}
|
|
177
|
+
className={`section-editor-preview-toggle ${
|
|
178
|
+
isPreview ? 'active' : 'inactive'
|
|
179
|
+
}`}
|
|
180
|
+
>
|
|
181
|
+
<Eye size={16} />
|
|
182
|
+
{isPreview ? 'Edit' : 'Preview'}
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={onCancel}
|
|
186
|
+
className="section-editor-close-btn"
|
|
187
|
+
>
|
|
188
|
+
<X size={20} />
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div className="section-editor-body">
|
|
194
|
+
{/* Basic Section Info */}
|
|
195
|
+
<div className="section-editor-form-grid">
|
|
196
|
+
<div className="section-editor-form-group">
|
|
197
|
+
<Field
|
|
198
|
+
settings={{
|
|
199
|
+
id: "title",
|
|
200
|
+
label: "Section Title",
|
|
201
|
+
type: "text",
|
|
202
|
+
value: formData.title,
|
|
203
|
+
required: true,
|
|
204
|
+
placeholder: "Enter section title"
|
|
205
|
+
}}
|
|
206
|
+
inputClass={{ title: "form-control" }}
|
|
207
|
+
inputValue={formData.title}
|
|
208
|
+
onChange={(event) => {
|
|
209
|
+
const value = event?.target?.value || event;
|
|
210
|
+
handleInputChange('title', value);
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<div className="section-editor-checkbox-group">
|
|
215
|
+
<div className="section-editor-checkbox">
|
|
216
|
+
<input
|
|
217
|
+
type="checkbox"
|
|
218
|
+
id="is_enabled"
|
|
219
|
+
checked={formData.is_enabled}
|
|
220
|
+
onChange={(e) => handleInputChange('is_enabled', e.target.checked)}
|
|
221
|
+
/>
|
|
222
|
+
<label htmlFor="is_enabled">Enabled</label>
|
|
223
|
+
</div>
|
|
224
|
+
<div className="section-editor-checkbox">
|
|
225
|
+
<input
|
|
226
|
+
type="checkbox"
|
|
227
|
+
id="is_dynamic"
|
|
228
|
+
checked={formData.is_dynamic}
|
|
229
|
+
onChange={(e) => handleInputChange('is_dynamic', e.target.checked)}
|
|
230
|
+
/>
|
|
231
|
+
<label htmlFor="is_dynamic">Dynamic Content</label>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{/* Content Editor or Preview - Only show if not dynamic */}
|
|
237
|
+
{!formData.is_dynamic ? (
|
|
238
|
+
<div className="section-editor-content-section">
|
|
239
|
+
<div className="section-editor-content-header">
|
|
240
|
+
<label className="section-editor-content-label">
|
|
241
|
+
Section Content
|
|
242
|
+
</label>
|
|
243
|
+
<div className="section-editor-content-actions">
|
|
244
|
+
<button
|
|
245
|
+
onClick={() => setShowVariableInserter(true)}
|
|
246
|
+
className="section-editor-action-btn primary"
|
|
247
|
+
>
|
|
248
|
+
<Tag size={14} />
|
|
249
|
+
Insert Variable
|
|
250
|
+
</button>
|
|
251
|
+
<button
|
|
252
|
+
onClick={() => setShowStylingOptions(!showStylingOptions)}
|
|
253
|
+
className="section-editor-action-btn"
|
|
254
|
+
>
|
|
255
|
+
<Settings size={14} />
|
|
256
|
+
Styling
|
|
257
|
+
</button>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{isPreview ? (
|
|
262
|
+
<div className="section-editor-preview">
|
|
263
|
+
{renderPreview()}
|
|
264
|
+
</div>
|
|
265
|
+
) : (
|
|
266
|
+
<Field
|
|
267
|
+
settings={{
|
|
268
|
+
id: "content",
|
|
269
|
+
type: "richeditor",
|
|
270
|
+
value: formData.content,
|
|
271
|
+
rows: 15,
|
|
272
|
+
placeholder: "Enter section content. Use {{variable_name}} for dynamic content.",
|
|
273
|
+
editorConfig: {
|
|
274
|
+
height: 400,
|
|
275
|
+
plugins: [
|
|
276
|
+
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
|
|
277
|
+
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
|
|
278
|
+
'insertdatetime', 'media', 'table', 'help', 'wordcount'
|
|
279
|
+
],
|
|
280
|
+
toolbar: 'undo redo | blocks | bold italic forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help',
|
|
281
|
+
content_style: 'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px }'
|
|
282
|
+
}
|
|
283
|
+
}}
|
|
284
|
+
inputClass={{ content: "form-control" }}
|
|
285
|
+
inputValue={formData.content}
|
|
286
|
+
onChangeRicheditor={(value) => handleInputChange('content', value)}
|
|
287
|
+
onChange={(event) => {
|
|
288
|
+
const value = event?.target?.value || event;
|
|
289
|
+
handleInputChange('content', value);
|
|
290
|
+
}}
|
|
291
|
+
onBlur={() => {
|
|
292
|
+
// Force content sync when editor loses focus
|
|
293
|
+
if (window.tinymce && window.tinymce.activeEditor) {
|
|
294
|
+
const content = window.tinymce.activeEditor.getContent();
|
|
295
|
+
if (content !== formData.content) {
|
|
296
|
+
handleInputChange('content', content);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}}
|
|
300
|
+
/>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
) : (
|
|
304
|
+
/* Dynamic Content Notice */
|
|
305
|
+
<div className="section-editor-dynamic-notice">
|
|
306
|
+
<div className="section-editor-dynamic-card">
|
|
307
|
+
<div className="section-editor-dynamic-icon">
|
|
308
|
+
<Tag size={20} />
|
|
309
|
+
</div>
|
|
310
|
+
<div className="section-editor-dynamic-content">
|
|
311
|
+
<h4 className="section-editor-dynamic-title">Dynamic Content Enabled</h4>
|
|
312
|
+
<p className="section-editor-dynamic-description">
|
|
313
|
+
This section will be automatically generated using dynamic variables and templates.
|
|
314
|
+
Content will be populated when the proposal is generated based on the selected data sources.
|
|
315
|
+
</p>
|
|
316
|
+
<div className="section-editor-dynamic-actions">
|
|
317
|
+
<button
|
|
318
|
+
onClick={() => setShowStylingOptions(!showStylingOptions)}
|
|
319
|
+
className="section-editor-action-btn"
|
|
320
|
+
>
|
|
321
|
+
<Settings size={14} />
|
|
322
|
+
Styling Options
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
|
|
330
|
+
{/* Styling Options */}
|
|
331
|
+
{showStylingOptions && (
|
|
332
|
+
<div className="section-editor-styling-panel">
|
|
333
|
+
<h4 className="section-editor-styling-title">Section Styling</h4>
|
|
334
|
+
<div className="section-editor-styling-grid">
|
|
335
|
+
<div className="section-editor-styling-option">
|
|
336
|
+
<input
|
|
337
|
+
type="checkbox"
|
|
338
|
+
id="page_break_before"
|
|
339
|
+
checked={formData.styling.page_break_before || false}
|
|
340
|
+
onChange={(e) => handleStylingChange('page_break_before', e.target.checked)}
|
|
341
|
+
/>
|
|
342
|
+
<label htmlFor="page_break_before">Page break before</label>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="section-editor-styling-option">
|
|
345
|
+
<input
|
|
346
|
+
type="checkbox"
|
|
347
|
+
id="page_break_after"
|
|
348
|
+
checked={formData.styling.page_break_after || false}
|
|
349
|
+
onChange={(e) => handleStylingChange('page_break_after', e.target.checked)}
|
|
350
|
+
/>
|
|
351
|
+
<label htmlFor="page_break_after">Page break after</label>
|
|
352
|
+
</div>
|
|
353
|
+
<div>
|
|
354
|
+
<label className="section-editor-form-label">Margin Top (px)</label>
|
|
355
|
+
<input
|
|
356
|
+
type="number"
|
|
357
|
+
value={formData.styling.margin_top || ''}
|
|
358
|
+
onChange={(e) => handleStylingChange('margin_top', e.target.value)}
|
|
359
|
+
placeholder="0"
|
|
360
|
+
className="section-editor-styling-input"
|
|
361
|
+
/>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
)}
|
|
366
|
+
|
|
367
|
+
{/* Variables Summary */}
|
|
368
|
+
{formData.variables.length > 0 && (
|
|
369
|
+
<div className="section-editor-variables-summary">
|
|
370
|
+
<h4 className="section-editor-variables-title">Variables Used in This Section</h4>
|
|
371
|
+
<div className="section-editor-variables-list">
|
|
372
|
+
{formData.variables.map((variable, index) => (
|
|
373
|
+
<span key={index} className="section-editor-variable-tag">
|
|
374
|
+
{`{{${variable}}}`}
|
|
375
|
+
</span>
|
|
376
|
+
))}
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
)}
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
{/* Footer */}
|
|
383
|
+
<div className="section-editor-footer">
|
|
384
|
+
<button
|
|
385
|
+
onClick={onCancel}
|
|
386
|
+
className="section-editor-btn secondary"
|
|
387
|
+
>
|
|
388
|
+
Cancel
|
|
389
|
+
</button>
|
|
390
|
+
<button
|
|
391
|
+
onClick={handleSave}
|
|
392
|
+
className="section-editor-btn primary"
|
|
393
|
+
>
|
|
394
|
+
<Save size={16} />
|
|
395
|
+
Save Section
|
|
396
|
+
</button>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
{/* Variable Inserter Modal */}
|
|
400
|
+
{showVariableInserter && (
|
|
401
|
+
<VariableInserter
|
|
402
|
+
availableVariables={availableVariables}
|
|
403
|
+
onInsert={handleVariableInsert}
|
|
404
|
+
onCancel={() => setShowVariableInserter(false)}
|
|
405
|
+
/>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
export default SectionEditor;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/* Section Type Selector Modal Styles */
|
|
2
|
+
.section-type-modal {
|
|
3
|
+
position: fixed;
|
|
4
|
+
inset: 0;
|
|
5
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
6
|
+
backdrop-filter: blur(4px);
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
z-index: 50;
|
|
11
|
+
animation: fadeIn 0.2s ease-out;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@keyframes fadeIn {
|
|
15
|
+
from { opacity: 0; }
|
|
16
|
+
to { opacity: 1; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.section-type-modal-content {
|
|
20
|
+
background: white;
|
|
21
|
+
border-radius: 8px;
|
|
22
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
23
|
+
max-width: 900px;
|
|
24
|
+
width: 100%;
|
|
25
|
+
margin: 1rem;
|
|
26
|
+
max-height: 90vh;
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
animation: slideUp 0.3s ease-out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes slideUp {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: translateY(20px) scale(0.95);
|
|
35
|
+
}
|
|
36
|
+
to {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
transform: translateY(0) scale(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.section-type-modal-header {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
padding: 1.5rem;
|
|
47
|
+
border-bottom: 1px solid #e2e8f0;
|
|
48
|
+
background: #1b3933;
|
|
49
|
+
color: white;
|
|
50
|
+
border-radius: 8px 8px 0 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.section-type-modal-header .section-type-modal-title {
|
|
54
|
+
font-size: 1.25rem;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
margin: 0;
|
|
57
|
+
color: white !important;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.section-type-modal-subtitle {
|
|
61
|
+
font-size: 0.9rem;
|
|
62
|
+
margin: 0.25rem 0 0 0;
|
|
63
|
+
opacity: 1;
|
|
64
|
+
color: rgba(255, 255, 255, 0.9);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.section-type-close-btn {
|
|
68
|
+
padding: 0.5rem;
|
|
69
|
+
color: white;
|
|
70
|
+
background: rgba(255, 255, 255, 0.1);
|
|
71
|
+
border: none;
|
|
72
|
+
border-radius: 5px;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
transition: background-color 0.2s;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.section-type-close-btn:hover {
|
|
78
|
+
background: #3cbf7d;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.section-type-modal-body {
|
|
82
|
+
padding: 1.5rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.section-category {
|
|
86
|
+
margin-bottom: 2rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.section-category:last-child {
|
|
90
|
+
margin-bottom: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.section-category-title {
|
|
94
|
+
font-size: 1.1rem;
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
color: #1e293b;
|
|
97
|
+
margin-bottom: 1rem;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.5rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.section-types-grid {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
106
|
+
gap: 1rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.section-type-card {
|
|
110
|
+
text-align: left;
|
|
111
|
+
padding: 1rem;
|
|
112
|
+
border: 2px solid #e2e8f0;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
transition: all 0.2s ease-in-out;
|
|
116
|
+
background: white;
|
|
117
|
+
position: relative;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.section-type-card::before {
|
|
122
|
+
content: '';
|
|
123
|
+
position: absolute;
|
|
124
|
+
top: 0;
|
|
125
|
+
left: 0;
|
|
126
|
+
right: 0;
|
|
127
|
+
height: 3px;
|
|
128
|
+
background: transparent;
|
|
129
|
+
transition: background 0.2s ease-in-out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.section-type-card:hover {
|
|
133
|
+
border-color: #3b82f6;
|
|
134
|
+
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
|
|
135
|
+
transform: translateY(-2px);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.section-type-card:hover::before {
|
|
139
|
+
background: #3cbf7d;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.section-type-card.header-category {
|
|
143
|
+
border-color: #e0f2fe;
|
|
144
|
+
background: #f8fafc;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.section-type-card.header-category:hover {
|
|
148
|
+
border-color: #1b3933;
|
|
149
|
+
box-shadow: 0 8px 25px rgba(27, 57, 51, 0.15);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.section-type-card.content-category {
|
|
153
|
+
border-color: #ecfdf5;
|
|
154
|
+
background: #f8fafc;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.section-type-card.content-category:hover {
|
|
158
|
+
border-color: #3cbf7d;
|
|
159
|
+
box-shadow: 0 8px 25px rgba(60, 191, 125, 0.15);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.section-type-card.pricing-category {
|
|
163
|
+
border-color: #fef3c7;
|
|
164
|
+
background: #f8fafc;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.section-type-card.pricing-category:hover {
|
|
168
|
+
border-color: #1b3933;
|
|
169
|
+
box-shadow: 0 8px 25px rgba(27, 57, 51, 0.15);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.section-type-card.footer-category {
|
|
173
|
+
border-color: #f3e8ff;
|
|
174
|
+
background: #f8fafc;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.section-type-card.footer-category:hover {
|
|
178
|
+
border-color: #1b3933;
|
|
179
|
+
box-shadow: 0 8px 25px rgba(27, 57, 51, 0.15);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.section-type-card-header {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: flex-start;
|
|
185
|
+
gap: 0.75rem;
|
|
186
|
+
margin-bottom: 0.75rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.section-type-icon {
|
|
190
|
+
flex-shrink: 0;
|
|
191
|
+
padding: 0.5rem;
|
|
192
|
+
border-radius: 5px;
|
|
193
|
+
background: rgba(60, 191, 125, 0.1);
|
|
194
|
+
color: #1b3933;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.section-type-card-content {
|
|
198
|
+
flex: 1;
|
|
199
|
+
min-width: 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.section-type-name {
|
|
203
|
+
font-size: 1rem;
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
color: #1e293b;
|
|
206
|
+
margin: 0 0 0.25rem 0;
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 0.5rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.section-type-description {
|
|
213
|
+
font-size: 0.875rem;
|
|
214
|
+
color: #64748b;
|
|
215
|
+
margin: 0;
|
|
216
|
+
line-height: 1.4;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.dynamic-badge {
|
|
220
|
+
padding: 0.15rem 0.4rem;
|
|
221
|
+
font-size: 0.65rem;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
background-color: #dcfce7;
|
|
224
|
+
color: #1b3933;
|
|
225
|
+
border-radius: 4px;
|
|
226
|
+
text-transform: uppercase;
|
|
227
|
+
letter-spacing: 0.025em;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.section-type-modal-footer {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
justify-content: flex-end;
|
|
234
|
+
padding: 1rem 1.5rem;
|
|
235
|
+
border-top: 1px solid #e2e8f0;
|
|
236
|
+
background: #f8fafc;
|
|
237
|
+
border-radius: 0 0 8px 8px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.section-type-cancel-btn {
|
|
241
|
+
padding: 0.5rem 1rem;
|
|
242
|
+
font-size: 0.875rem;
|
|
243
|
+
font-weight: 500;
|
|
244
|
+
color: #64748b;
|
|
245
|
+
background: white;
|
|
246
|
+
border: 1px solid #e1e1e1;
|
|
247
|
+
border-radius: 5px;
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
transition: all 0.2s;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.section-type-cancel-btn:hover {
|
|
253
|
+
background: #f8fafc;
|
|
254
|
+
border-color: #9ca3af;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Responsive adjustments */
|
|
258
|
+
@media (max-width: 768px) {
|
|
259
|
+
.section-type-modal-content {
|
|
260
|
+
max-width: none;
|
|
261
|
+
margin: 0.5rem;
|
|
262
|
+
border-radius: 5px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.section-types-grid {
|
|
266
|
+
grid-template-columns: 1fr;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.section-type-modal-header {
|
|
270
|
+
padding: 1rem;
|
|
271
|
+
border-radius: 5px 5px 0 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.section-type-modal-body {
|
|
275
|
+
padding: 1rem;
|
|
276
|
+
}
|
|
277
|
+
}
|