@zentto/studio 0.6.0 → 0.6.2
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/dist/designer/zs-app-wizard.js +562 -562
- package/dist/designer/zs-page-designer.d.ts +6 -0
- package/dist/designer/zs-page-designer.d.ts.map +1 -1
- package/dist/designer/zs-page-designer.js +1081 -991
- package/dist/designer/zs-page-designer.js.map +1 -1
- package/dist/fields/zs-field-datagrid.d.ts +10 -0
- package/dist/fields/zs-field-datagrid.d.ts.map +1 -1
- package/dist/fields/zs-field-datagrid.js +162 -60
- package/dist/fields/zs-field-datagrid.js.map +1 -1
- package/dist/fields/zs-field-report.js +52 -52
- package/dist/zentto-studio-renderer.d.ts.map +1 -1
- package/dist/zentto-studio-renderer.js +310 -307
- package/dist/zentto-studio-renderer.js.map +1 -1
- package/package.json +74 -70
|
@@ -101,493 +101,493 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
101
101
|
this.apiCompany = '';
|
|
102
102
|
this.apiBranch = '';
|
|
103
103
|
}
|
|
104
|
-
static { this.styles = css `
|
|
105
|
-
:host {
|
|
106
|
-
display: block; height: 100%;
|
|
107
|
-
--zrd-bg: ${unsafeCSS(BG)};
|
|
108
|
-
--zrd-panel-bg: ${unsafeCSS(PANEL_BG)};
|
|
109
|
-
--zrd-border: ${unsafeCSS(BORDER)};
|
|
110
|
-
--zrd-accent: ${unsafeCSS(ACCENT)};
|
|
111
|
-
--zrd-accent-light: ${unsafeCSS(ACCENT_LIGHT)};
|
|
112
|
-
--zrd-text: ${unsafeCSS(TEXT)};
|
|
113
|
-
--zrd-text-muted: ${unsafeCSS(TEXT_MUTED)};
|
|
114
|
-
--zrd-danger: ${unsafeCSS(DANGER)};
|
|
115
|
-
font-family: 'Segoe UI', Roboto, Arial, sans-serif;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* ─── Layout ──────────────────────────────── */
|
|
119
|
-
.designer {
|
|
120
|
-
display: grid;
|
|
121
|
-
grid-template-rows: auto 1fr;
|
|
122
|
-
grid-template-columns: 200px 4px 1fr 4px 240px;
|
|
123
|
-
height: 100%; background: var(--zrd-bg);
|
|
124
|
-
overflow: hidden;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* ─── Toolbar ─────────────────────────────── */
|
|
128
|
-
.toolbar {
|
|
129
|
-
grid-column: 1 / -1;
|
|
130
|
-
display: flex; align-items: center; gap: 8px;
|
|
131
|
-
padding: 6px 12px; min-height: 36px;
|
|
132
|
-
background: var(--zrd-panel-bg);
|
|
133
|
-
border-bottom: 1px solid var(--zrd-border);
|
|
134
|
-
flex-wrap: wrap;
|
|
135
|
-
}
|
|
136
|
-
.toolbar-sep { width: 1px; height: 20px; background: var(--zrd-border); margin: 0 2px; }
|
|
137
|
-
.tb-btn {
|
|
138
|
-
background: none; border: 1px solid var(--zrd-border);
|
|
139
|
-
border-radius: 4px; padding: 4px 10px;
|
|
140
|
-
cursor: pointer; font-size: 12px; color: var(--zrd-text);
|
|
141
|
-
font-family: inherit; transition: background 0.15s;
|
|
142
|
-
white-space: nowrap; display: flex; align-items: center; gap: 4px;
|
|
143
|
-
}
|
|
144
|
-
.tb-btn:hover { background: var(--zrd-accent-light); }
|
|
145
|
-
.tb-btn:disabled { opacity: 0.4; cursor: default; }
|
|
146
|
-
.tb-btn--active { background: var(--zrd-accent); color: white; border-color: var(--zrd-accent); }
|
|
147
|
-
.tb-btn--danger:hover { background: #ffebee; color: var(--zrd-danger); border-color: var(--zrd-danger); }
|
|
148
|
-
.report-name {
|
|
149
|
-
font-weight: 600; font-size: 14px; cursor: pointer;
|
|
150
|
-
padding: 2px 6px; border-radius: 3px; border: 1px solid transparent;
|
|
151
|
-
color: var(--zrd-text);
|
|
152
|
-
}
|
|
153
|
-
.report-name:hover { border-color: var(--zrd-border); background: var(--zrd-accent-light); }
|
|
154
|
-
.report-name-input {
|
|
155
|
-
font-weight: 600; font-size: 14px; border: 1px solid var(--zrd-accent);
|
|
156
|
-
border-radius: 3px; padding: 2px 6px; outline: none;
|
|
157
|
-
background: white; color: var(--zrd-text); font-family: inherit;
|
|
158
|
-
}
|
|
159
|
-
.tb-spacer { flex: 1; }
|
|
160
|
-
.zoom-controls {
|
|
161
|
-
display: flex; align-items: center; gap: 4px;
|
|
162
|
-
padding-left: 8px; border-left: 1px solid var(--zrd-border);
|
|
163
|
-
}
|
|
164
|
-
.zoom-btn { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 3px; font-size: 14px; font-weight: bold; }
|
|
165
|
-
.zoom-label { font-size: 11px; min-width: 36px; text-align: center; color: var(--zrd-text-muted); cursor: pointer; }
|
|
166
|
-
|
|
167
|
-
/* ─── Resize Handle ───────────────────────── */
|
|
168
|
-
.panel-resize {
|
|
169
|
-
width: 4px; cursor: col-resize; background: transparent;
|
|
170
|
-
transition: background 0.15s; flex-shrink: 0;
|
|
171
|
-
}
|
|
172
|
-
.panel-resize:hover { background: var(--zrd-accent); }
|
|
173
|
-
|
|
174
|
-
/* ─── Left Panel (Toolbox) ────────────────── */
|
|
175
|
-
.left-panel {
|
|
176
|
-
background: var(--zrd-panel-bg);
|
|
177
|
-
border-right: 1px solid var(--zrd-border);
|
|
178
|
-
display: flex; flex-direction: column; overflow: hidden;
|
|
179
|
-
}
|
|
180
|
-
.panel-tabs {
|
|
181
|
-
display: flex; border-bottom: 1px solid var(--zrd-border);
|
|
182
|
-
}
|
|
183
|
-
.panel-tab {
|
|
184
|
-
flex: 1; padding: 8px 4px; text-align: center; cursor: pointer;
|
|
185
|
-
font-size: 11px; border-bottom: 2px solid transparent;
|
|
186
|
-
color: var(--zrd-text-muted); transition: all 0.15s;
|
|
187
|
-
background: none; border-top: none; border-left: none; border-right: none;
|
|
188
|
-
font-family: inherit;
|
|
189
|
-
}
|
|
190
|
-
.panel-tab:hover { color: var(--zrd-text); }
|
|
191
|
-
.panel-tab--active { border-bottom-color: var(--zrd-accent); color: var(--zrd-accent); font-weight: 600; }
|
|
192
|
-
.panel-content { padding: 4px; overflow-y: auto; flex: 1; }
|
|
193
|
-
.panel-content::-webkit-scrollbar { width: 6px; }
|
|
194
|
-
.panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
195
|
-
|
|
196
|
-
/* Toolbox grid (3 columns like report-designer) */
|
|
197
|
-
.toolbox-section {
|
|
198
|
-
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
199
|
-
letter-spacing: 0.8px; color: #aaa;
|
|
200
|
-
padding: 10px 8px 5px; margin-top: 2px;
|
|
201
|
-
}
|
|
202
|
-
.toolbox-section:first-child { margin-top: 0; padding-top: 6px; }
|
|
203
|
-
.toolbox-grid {
|
|
204
|
-
display: grid; grid-template-columns: repeat(3, 1fr);
|
|
205
|
-
gap: 3px; padding: 0 4px;
|
|
206
|
-
}
|
|
207
|
-
.toolbox-item {
|
|
208
|
-
display: flex; flex-direction: column; align-items: center;
|
|
209
|
-
gap: 3px; padding: 7px 2px 5px;
|
|
210
|
-
border: 1px solid transparent; border-radius: 5px;
|
|
211
|
-
cursor: grab; user-select: none; text-align: center;
|
|
212
|
-
transition: all 0.15s; background: transparent;
|
|
213
|
-
}
|
|
214
|
-
.toolbox-item:hover {
|
|
215
|
-
background: var(--zrd-accent-light); border-color: #c5dcf0;
|
|
216
|
-
box-shadow: 0 1px 4px rgba(25,118,210,0.1);
|
|
217
|
-
}
|
|
218
|
-
.toolbox-item:active { cursor: grabbing; opacity: 0.6; transform: scale(0.95); }
|
|
219
|
-
.toolbox-icon {
|
|
220
|
-
width: 28px; height: 28px; display: flex; align-items: center;
|
|
221
|
-
justify-content: center; border-radius: 6px;
|
|
222
|
-
font-size: 15px; flex-shrink: 0;
|
|
223
|
-
background: #f0f4f8; color: #1976d2;
|
|
224
|
-
border: 1px solid #e3e8ee;
|
|
225
|
-
transition: all 0.15s;
|
|
226
|
-
}
|
|
227
|
-
.toolbox-item:hover .toolbox-icon {
|
|
228
|
-
background: #1976d2; color: white; border-color: #1976d2;
|
|
229
|
-
box-shadow: 0 2px 6px rgba(25,118,210,0.3);
|
|
230
|
-
}
|
|
231
|
-
.toolbox-label {
|
|
232
|
-
font-size: 9px; font-weight: 500; line-height: 1.1;
|
|
233
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
234
|
-
max-width: 100%; color: #777;
|
|
235
|
-
}
|
|
236
|
-
.toolbox-item:hover .toolbox-label { color: #1976d2; }
|
|
237
|
-
|
|
238
|
-
/* ─── Canvas ──────────────────────────────── */
|
|
239
|
-
.canvas-area {
|
|
240
|
-
overflow: auto; padding: 30px; position: relative;
|
|
241
|
-
background: #d0d0d0; display: flex; justify-content: center;
|
|
242
|
-
}
|
|
243
|
-
.canvas {
|
|
244
|
-
background: white; position: relative; margin: 0 auto; border-radius: 2px;
|
|
245
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.08);
|
|
246
|
-
min-width: 600px; min-height: 400px; padding: 24px;
|
|
247
|
-
transform-origin: top center;
|
|
248
|
-
}
|
|
249
|
-
.canvas-section { margin-bottom: 20px; }
|
|
250
|
-
.canvas-section-header {
|
|
251
|
-
font-size: 13px; font-weight: 600; color: var(--zrd-text);
|
|
252
|
-
padding: 6px 8px; margin-bottom: 8px;
|
|
253
|
-
background: #f0f0f0; border-radius: 4px; border-left: 3px solid var(--zrd-accent);
|
|
254
|
-
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
255
|
-
}
|
|
256
|
-
.canvas-section-header:hover { background: var(--zrd-accent-light); }
|
|
257
|
-
.canvas-grid {
|
|
258
|
-
display: grid; gap: 8px; padding: 4px;
|
|
259
|
-
background: repeating-linear-gradient(0deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px),
|
|
260
|
-
repeating-linear-gradient(90deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/* Field on canvas */
|
|
264
|
-
.canvas-field {
|
|
265
|
-
position: relative; border: 1px solid transparent;
|
|
266
|
-
border-radius: 4px; cursor: pointer; user-select: none;
|
|
267
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
268
|
-
padding: 4px;
|
|
269
|
-
}
|
|
270
|
-
.canvas-field:hover { border-color: rgba(25,118,210,0.5); }
|
|
271
|
-
.canvas-field--selected {
|
|
272
|
-
border-color: var(--zrd-accent);
|
|
273
|
-
box-shadow: 0 0 0 1px var(--zrd-accent);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/* Type badge (above field) */
|
|
277
|
-
.field-type-badge {
|
|
278
|
-
position: absolute; top: -14px; left: 0;
|
|
279
|
-
font-size: 9px; border-radius: 3px 3px 0 0;
|
|
280
|
-
padding: 1px 6px; line-height: 13px;
|
|
281
|
-
pointer-events: none; z-index: 6; display: none;
|
|
282
|
-
white-space: nowrap;
|
|
283
|
-
}
|
|
284
|
-
.canvas-field:hover .field-type-badge,
|
|
285
|
-
.canvas-field--selected .field-type-badge { display: block; }
|
|
286
|
-
|
|
287
|
-
/* Resize handles (8-point like report-designer) */
|
|
288
|
-
.rh { position: absolute; width: 6px; height: 6px; background: var(--zrd-accent); border: 1px solid white; z-index: 5; display: none; border-radius: 1px; }
|
|
289
|
-
.rh:hover { background: #0d47a1; }
|
|
290
|
-
.canvas-field--selected .rh { display: block; }
|
|
291
|
-
.rh-nw { top: -3px; left: -3px; cursor: nw-resize; }
|
|
292
|
-
.rh-n { top: -3px; left: calc(50% - 3px); cursor: n-resize; }
|
|
293
|
-
.rh-ne { top: -3px; right: -3px; cursor: ne-resize; }
|
|
294
|
-
.rh-e { top: calc(50% - 3px); right: -3px; cursor: e-resize; }
|
|
295
|
-
.rh-se { bottom: -3px; right: -3px; cursor: se-resize; }
|
|
296
|
-
.rh-s { bottom: -3px; left: calc(50% - 3px); cursor: s-resize; }
|
|
297
|
-
.rh-sw { bottom: -3px; left: -3px; cursor: sw-resize; }
|
|
298
|
-
.rh-w { top: calc(50% - 3px); left: -3px; cursor: w-resize; }
|
|
299
|
-
|
|
300
|
-
/* Action buttons on field */
|
|
301
|
-
.field-actions {
|
|
302
|
-
position: absolute; top: -14px; right: 4px;
|
|
303
|
-
display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; z-index: 7;
|
|
304
|
-
}
|
|
305
|
-
.canvas-field:hover .field-actions { opacity: 1; }
|
|
306
|
-
.fa-btn {
|
|
307
|
-
width: 18px; height: 14px; border-radius: 3px 3px 0 0;
|
|
308
|
-
border: none; cursor: pointer; font-size: 9px;
|
|
309
|
-
display: flex; align-items: center; justify-content: center;
|
|
310
|
-
}
|
|
311
|
-
.fa-btn--move { background: var(--zrd-accent); color: white; }
|
|
312
|
-
.fa-btn--delete { background: var(--zrd-danger); color: white; }
|
|
313
|
-
.fa-btn--copy { background: #7c3aed; color: white; }
|
|
314
|
-
|
|
315
|
-
/* Field preview content */
|
|
316
|
-
.field-preview {
|
|
317
|
-
pointer-events: none;
|
|
318
|
-
}
|
|
319
|
-
.field-preview-label {
|
|
320
|
-
font-size: 11px; font-weight: 500; color: var(--zrd-text-muted);
|
|
321
|
-
margin-bottom: 3px;
|
|
322
|
-
}
|
|
323
|
-
.field-preview-input {
|
|
324
|
-
height: 30px; border: 1px solid #e0e0e0; border-radius: 4px;
|
|
325
|
-
background: #fafafa; display: flex; align-items: center;
|
|
326
|
-
padding: 0 8px; font-size: 12px; color: #999;
|
|
327
|
-
}
|
|
328
|
-
.field-preview-input--textarea { height: 60px; align-items: flex-start; padding-top: 6px; }
|
|
329
|
-
.field-preview-input--switch { height: auto; border: none; background: none; padding: 0; }
|
|
330
|
-
.field-preview-input--separator { height: 1px; border: none; background: #ddd; padding: 0; }
|
|
331
|
-
.field-preview-input--heading { height: auto; border: none; background: none; padding: 0; font-size: 16px; font-weight: 600; color: var(--zrd-text); }
|
|
332
|
-
.field-preview-input--datagrid { height: 120px; border: 2px dashed var(--zrd-accent); background: var(--zrd-accent-light); justify-content: center; font-weight: 500; color: var(--zrd-accent); }
|
|
333
|
-
.field-preview-input--report { height: 120px; border: 2px dashed #e65100; background: #fff3e0; justify-content: center; font-weight: 500; color: #e65100; }
|
|
334
|
-
.field-preview-input--chart { height: 120px; border: 2px dashed #6a1b9a; background: #f3e5f5; justify-content: center; font-weight: 500; color: #6a1b9a; }
|
|
335
|
-
|
|
336
|
-
/* Drop zone */
|
|
337
|
-
.drop-zone {
|
|
338
|
-
border: 2px dashed var(--zrd-border); border-radius: 6px;
|
|
339
|
-
padding: 16px; text-align: center; margin: 4px 0;
|
|
340
|
-
color: var(--zrd-text-muted); font-size: 12px; transition: all 0.15s;
|
|
341
|
-
}
|
|
342
|
-
.drop-zone--active { border-color: var(--zrd-accent); background: var(--zrd-accent-light); color: var(--zrd-accent); }
|
|
343
|
-
|
|
344
|
-
/* ─── Right Panel (Properties — Figma-quality) ──── */
|
|
345
|
-
.right-panel {
|
|
346
|
-
background: var(--zrd-panel-bg);
|
|
347
|
-
border-left: 1px solid var(--zrd-border);
|
|
348
|
-
overflow-y: auto; font-size: 11px;
|
|
349
|
-
}
|
|
350
|
-
.right-panel::-webkit-scrollbar { width: 5px; }
|
|
351
|
-
.right-panel::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
|
|
352
|
-
.right-panel::-webkit-scrollbar-thumb:hover { background: #bbb; }
|
|
353
|
-
|
|
354
|
-
/* ─ Prop Section ─ */
|
|
355
|
-
.prop-section { border-bottom: 1px solid #eee; padding: 8px 10px 10px; }
|
|
356
|
-
.prop-section:last-child { border-bottom: none; }
|
|
357
|
-
.prop-section-header {
|
|
358
|
-
display: flex; align-items: center; gap: 6px;
|
|
359
|
-
cursor: pointer; user-select: none; margin-bottom: 6px; padding: 2px 0;
|
|
360
|
-
}
|
|
361
|
-
.prop-section-header h4 {
|
|
362
|
-
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
363
|
-
letter-spacing: 0.6px; margin: 0; flex: 1;
|
|
364
|
-
}
|
|
365
|
-
.prop-section-header[data-section="general"] h4 { color: #1976d2; }
|
|
366
|
-
.prop-section-header[data-section="layout"] h4 { color: #7c3aed; }
|
|
367
|
-
.prop-section-header[data-section="behavior"] h4 { color: #0d9488; }
|
|
368
|
-
.prop-section-header[data-section="rules"] h4 { color: #ea580c; }
|
|
369
|
-
.prop-section-header[data-section="style"] h4 { color: #c2185b; }
|
|
370
|
-
.prop-section-header[data-section="form"] h4 { color: #1976d2; }
|
|
371
|
-
.collapse-icon {
|
|
372
|
-
font-size: 8px; color: #bbb; transition: transform 0.15s;
|
|
373
|
-
width: 14px; height: 14px; display: flex; align-items: center;
|
|
374
|
-
justify-content: center; border-radius: 3px;
|
|
375
|
-
}
|
|
376
|
-
.collapse-icon:hover { background: #f0f0f0; color: #666; }
|
|
377
|
-
.collapse-icon--collapsed { transform: rotate(-90deg); }
|
|
378
|
-
|
|
379
|
-
/* ─ Prop Rows ─ */
|
|
380
|
-
.prop-row {
|
|
381
|
-
display: grid; grid-template-columns: 62px 1fr;
|
|
382
|
-
align-items: center; gap: 4px; min-height: 26px; margin-bottom: 1px;
|
|
383
|
-
}
|
|
384
|
-
.prop-row-full { grid-template-columns: 1fr; margin-bottom: 3px; }
|
|
385
|
-
.prop-label {
|
|
386
|
-
font-size: 11px; color: #999; font-weight: 400;
|
|
387
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
388
|
-
padding-left: 1px; line-height: 1;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/* ─ Figma-style input ─ */
|
|
392
|
-
.prop-input {
|
|
393
|
-
width: 100%; border: 1px solid transparent; border-radius: 4px;
|
|
394
|
-
padding: 5px 7px; font-size: 11px; background: #f5f5f5;
|
|
395
|
-
color: var(--zrd-text); outline: none; min-width: 0;
|
|
396
|
-
font-family: inherit; box-sizing: border-box;
|
|
397
|
-
transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
|
|
398
|
-
}
|
|
399
|
-
.prop-input:hover { background: #efefef; border-color: #ddd; }
|
|
400
|
-
.prop-input:focus { border-color: #1976d2; background: white; box-shadow: 0 0 0 2px rgba(25,118,210,0.08); }
|
|
401
|
-
select.prop-input {
|
|
402
|
-
padding: 4px 20px 4px 7px; cursor: pointer; appearance: none;
|
|
403
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
|
|
404
|
-
background-repeat: no-repeat; background-position: right 6px center;
|
|
405
|
-
}
|
|
406
|
-
textarea.prop-input {
|
|
407
|
-
min-height: 44px; resize: vertical; font-family: 'SF Mono','Consolas','Monaco',monospace;
|
|
408
|
-
font-size: 10px; line-height: 1.5; padding: 6px 7px;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/* ─ Numeric stepper (Figma-style) ─ */
|
|
412
|
-
.prop-stepper {
|
|
413
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
414
|
-
border: 1px solid transparent; border-radius: 4px; overflow: hidden;
|
|
415
|
-
transition: border-color 0.12s;
|
|
416
|
-
}
|
|
417
|
-
.prop-stepper:hover { border-color: #ddd; }
|
|
418
|
-
.prop-stepper:focus-within { border-color: #1976d2; background: white; }
|
|
419
|
-
.prop-stepper input {
|
|
420
|
-
border: none; background: transparent; width: 100%;
|
|
421
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
422
|
-
padding: 4px 2px 4px 7px; min-width: 0; font-family: inherit;
|
|
423
|
-
-moz-appearance: textfield;
|
|
424
|
-
}
|
|
425
|
-
.prop-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
426
|
-
.prop-stepper-btns {
|
|
427
|
-
display: flex; flex-direction: column; border-left: 1px solid #eee;
|
|
428
|
-
}
|
|
429
|
-
.prop-stepper-btn {
|
|
430
|
-
border: none; background: none; cursor: pointer; padding: 0;
|
|
431
|
-
width: 18px; height: 12px; display: flex; align-items: center;
|
|
432
|
-
justify-content: center; font-size: 7px; color: #999;
|
|
433
|
-
transition: all 0.1s;
|
|
434
|
-
}
|
|
435
|
-
.prop-stepper-btn:hover { background: #e8e8e8; color: #333; }
|
|
436
|
-
|
|
437
|
-
/* ─ Position grid (4-cell Figma layout) ─ */
|
|
438
|
-
.prop-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
|
439
|
-
.prop-pos-cell {
|
|
440
|
-
display: flex; align-items: center; background: #f5f5f5;
|
|
441
|
-
border: 1px solid transparent; border-radius: 4px; padding: 0 6px;
|
|
442
|
-
height: 26px; gap: 3px; transition: border-color 0.12s;
|
|
443
|
-
}
|
|
444
|
-
.prop-pos-cell:hover { border-color: #ddd; }
|
|
445
|
-
.prop-pos-cell:focus-within { border-color: #1976d2; background: white; }
|
|
446
|
-
.prop-pos-label {
|
|
447
|
-
font-size: 9px; font-weight: 700; width: 10px; text-align: center;
|
|
448
|
-
flex-shrink: 0; user-select: none;
|
|
449
|
-
}
|
|
450
|
-
.prop-pos-label--x { color: #ef4444; }
|
|
451
|
-
.prop-pos-label--y { color: #3b82f6; }
|
|
452
|
-
.prop-pos-label--w { color: #8b5cf6; }
|
|
453
|
-
.prop-pos-label--h { color: #10b981; }
|
|
454
|
-
.prop-pos-cell input {
|
|
455
|
-
border: none; background: transparent; width: 100%;
|
|
456
|
-
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
457
|
-
padding: 0; min-width: 0; font-family: inherit;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/* ─ Toggle switches (Figma compact) ─ */
|
|
461
|
-
.prop-toggle {
|
|
462
|
-
display: flex; align-items: center; gap: 8px;
|
|
463
|
-
min-height: 24px; padding: 1px 0;
|
|
464
|
-
}
|
|
465
|
-
.prop-switch {
|
|
466
|
-
position: relative; width: 28px; height: 16px; border-radius: 8px;
|
|
467
|
-
background: #d4d4d4; cursor: pointer; transition: background 0.2s;
|
|
468
|
-
flex-shrink: 0; border: none; padding: 0;
|
|
469
|
-
}
|
|
470
|
-
.prop-switch--active { background: #1976d2; }
|
|
471
|
-
.prop-switch::after {
|
|
472
|
-
content: ''; position: absolute; top: 2px; left: 2px;
|
|
473
|
-
width: 12px; height: 12px; border-radius: 50%;
|
|
474
|
-
background: white; box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
|
475
|
-
transition: transform 0.2s;
|
|
476
|
-
}
|
|
477
|
-
.prop-switch--active::after { transform: translateX(12px); }
|
|
478
|
-
.prop-toggle-label { font-size: 11px; color: #888; }
|
|
479
|
-
.prop-toggle:hover .prop-toggle-label { color: #555; }
|
|
480
|
-
|
|
481
|
-
/* ─ Segmented control (alignment, format) ─ */
|
|
482
|
-
.prop-segmented {
|
|
483
|
-
display: flex; border: 1px solid #e0e0e0; border-radius: 5px;
|
|
484
|
-
overflow: hidden; background: #f5f5f5;
|
|
485
|
-
}
|
|
486
|
-
.prop-seg-btn {
|
|
487
|
-
flex: 1; padding: 4px 0; background: transparent; border: none;
|
|
488
|
-
border-right: 1px solid #e0e0e0; cursor: pointer;
|
|
489
|
-
font-size: 11px; color: #999; transition: all 0.12s; text-align: center;
|
|
490
|
-
font-family: inherit;
|
|
491
|
-
}
|
|
492
|
-
.prop-seg-btn:last-child { border-right: none; }
|
|
493
|
-
.prop-seg-btn:hover { background: #eee; color: #555; }
|
|
494
|
-
.prop-seg-btn--active { background: #1976d2; color: white; }
|
|
495
|
-
|
|
496
|
-
/* ─ Color picker (swatch + hex inline) ─ */
|
|
497
|
-
.prop-color-row { display: flex; align-items: center; gap: 6px; }
|
|
498
|
-
.prop-color-swatch {
|
|
499
|
-
width: 22px; height: 22px; border-radius: 5px;
|
|
500
|
-
border: 1px solid #ddd; cursor: pointer; flex-shrink: 0;
|
|
501
|
-
position: relative; overflow: hidden;
|
|
502
|
-
}
|
|
503
|
-
.prop-color-swatch input[type="color"] {
|
|
504
|
-
position: absolute; inset: -4px; width: calc(100% + 8px);
|
|
505
|
-
height: calc(100% + 8px); cursor: pointer; border: none; padding: 0;
|
|
506
|
-
}
|
|
507
|
-
.prop-color-hex {
|
|
508
|
-
border: 1px solid transparent; border-radius: 4px;
|
|
509
|
-
padding: 4px 6px; font-size: 10px;
|
|
510
|
-
font-family: 'SF Mono','Consolas',monospace;
|
|
511
|
-
background: #f5f5f5; color: var(--zrd-text); width: 70px; outline: none;
|
|
512
|
-
box-sizing: border-box;
|
|
513
|
-
}
|
|
514
|
-
.prop-color-hex:hover { border-color: #ddd; }
|
|
515
|
-
.prop-color-hex:focus { border-color: #1976d2; background: white; }
|
|
516
|
-
|
|
517
|
-
/* ─ Info badge ─ */
|
|
518
|
-
.prop-info {
|
|
519
|
-
display: flex; align-items: center; gap: 4px;
|
|
520
|
-
padding: 5px 8px; border-radius: 4px; background: #f0f7ff;
|
|
521
|
-
font-size: 10px; color: #1976d2; margin-bottom: 4px;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/* ─ Divider line ─ */
|
|
525
|
-
.prop-divider { height: 1px; background: #f0f0f0; margin: 4px 0; }
|
|
526
|
-
|
|
527
|
-
/* ─ Empty state ─ */
|
|
528
|
-
.props-empty {
|
|
529
|
-
text-align: center; padding: 32px 16px; color: #ccc;
|
|
530
|
-
}
|
|
531
|
-
.props-empty-icon { font-size: 28px; margin-bottom: 6px; opacity: 0.4; }
|
|
532
|
-
.props-empty-text { font-size: 11px; line-height: 1.5; }
|
|
533
|
-
|
|
534
|
-
/* ─ Type badge ─ */
|
|
535
|
-
.props-type-badge {
|
|
536
|
-
display: inline-flex; align-items: center; gap: 4px;
|
|
537
|
-
padding: 3px 8px; border-radius: 4px;
|
|
538
|
-
font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
|
|
539
|
-
text-transform: uppercase;
|
|
540
|
-
}
|
|
541
|
-
.props-field-id {
|
|
542
|
-
font-size: 10px; color: #bbb; font-family: 'SF Mono','Consolas',monospace;
|
|
543
|
-
margin-top: 2px;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/* ─ JSON view ─ */
|
|
547
|
-
.json-panel {
|
|
548
|
-
font-family: 'SF Mono','Consolas','Monaco',monospace; font-size: 11px;
|
|
549
|
-
line-height: 1.5; background: #1e1e2d; color: #a2a3b7;
|
|
550
|
-
padding: 16px; border-radius: 4px; overflow: auto;
|
|
551
|
-
white-space: pre; tab-size: 2;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/* ─── InteractJS Drag & Drop ─────────────────── */
|
|
555
|
-
.dragging { opacity: 0.4; cursor: grabbing !important; }
|
|
556
|
-
.drag-clone {
|
|
557
|
-
position: fixed; pointer-events: none; z-index: 9999;
|
|
558
|
-
opacity: 0.85; border: 2px solid var(--zrd-accent);
|
|
559
|
-
border-radius: 6px; background: white;
|
|
560
|
-
box-shadow: 0 8px 24px rgba(25,118,210,0.25);
|
|
561
|
-
padding: 8px 12px; font-size: 12px; font-weight: 500;
|
|
562
|
-
color: var(--zrd-accent); max-width: 200px;
|
|
563
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
564
|
-
}
|
|
565
|
-
.drop-active { border: 2px dashed var(--zrd-accent) !important; background: rgba(25,118,210,0.04) !important; }
|
|
566
|
-
.drop-target { background: rgba(25,118,210,0.1) !important; border-color: var(--zrd-accent) !important; }
|
|
567
|
-
.resize-active { outline: 2px dashed var(--zrd-accent); outline-offset: 2px; }
|
|
568
|
-
.drag-insert-line {
|
|
569
|
-
position: absolute; left: 4px; right: 4px; height: 2px;
|
|
570
|
-
background: var(--zrd-accent); border-radius: 1px; z-index: 10;
|
|
571
|
-
pointer-events: none;
|
|
572
|
-
}
|
|
573
|
-
.drag-insert-line::before, .drag-insert-line::after {
|
|
574
|
-
content: ''; position: absolute; top: -3px;
|
|
575
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
576
|
-
background: var(--zrd-accent);
|
|
577
|
-
}
|
|
578
|
-
.drag-insert-line::before { left: -4px; }
|
|
579
|
-
.drag-insert-line::after { right: -4px; }
|
|
580
|
-
.canvas-field.can-drop { box-shadow: 0 0 0 2px rgba(25,118,210,0.3); }
|
|
581
|
-
.section-drop-zone {
|
|
582
|
-
min-height: 8px; transition: all 0.15s; border-radius: 4px;
|
|
583
|
-
margin: 2px 4px;
|
|
584
|
-
}
|
|
585
|
-
.section-drop-zone.drop-active {
|
|
586
|
-
min-height: 32px; border: 2px dashed var(--zrd-accent);
|
|
587
|
-
background: rgba(25,118,210,0.06);
|
|
588
|
-
display: flex; align-items: center; justify-content: center;
|
|
589
|
-
font-size: 11px; color: var(--zrd-accent);
|
|
590
|
-
}
|
|
104
|
+
static { this.styles = css `
|
|
105
|
+
:host {
|
|
106
|
+
display: block; height: 100%;
|
|
107
|
+
--zrd-bg: ${unsafeCSS(BG)};
|
|
108
|
+
--zrd-panel-bg: ${unsafeCSS(PANEL_BG)};
|
|
109
|
+
--zrd-border: ${unsafeCSS(BORDER)};
|
|
110
|
+
--zrd-accent: ${unsafeCSS(ACCENT)};
|
|
111
|
+
--zrd-accent-light: ${unsafeCSS(ACCENT_LIGHT)};
|
|
112
|
+
--zrd-text: ${unsafeCSS(TEXT)};
|
|
113
|
+
--zrd-text-muted: ${unsafeCSS(TEXT_MUTED)};
|
|
114
|
+
--zrd-danger: ${unsafeCSS(DANGER)};
|
|
115
|
+
font-family: 'Segoe UI', Roboto, Arial, sans-serif;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ─── Layout ──────────────────────────────── */
|
|
119
|
+
.designer {
|
|
120
|
+
display: grid;
|
|
121
|
+
grid-template-rows: auto 1fr;
|
|
122
|
+
grid-template-columns: 200px 4px 1fr 4px 240px;
|
|
123
|
+
height: 100%; background: var(--zrd-bg);
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ─── Toolbar ─────────────────────────────── */
|
|
128
|
+
.toolbar {
|
|
129
|
+
grid-column: 1 / -1;
|
|
130
|
+
display: flex; align-items: center; gap: 8px;
|
|
131
|
+
padding: 6px 12px; min-height: 36px;
|
|
132
|
+
background: var(--zrd-panel-bg);
|
|
133
|
+
border-bottom: 1px solid var(--zrd-border);
|
|
134
|
+
flex-wrap: wrap;
|
|
135
|
+
}
|
|
136
|
+
.toolbar-sep { width: 1px; height: 20px; background: var(--zrd-border); margin: 0 2px; }
|
|
137
|
+
.tb-btn {
|
|
138
|
+
background: none; border: 1px solid var(--zrd-border);
|
|
139
|
+
border-radius: 4px; padding: 4px 10px;
|
|
140
|
+
cursor: pointer; font-size: 12px; color: var(--zrd-text);
|
|
141
|
+
font-family: inherit; transition: background 0.15s;
|
|
142
|
+
white-space: nowrap; display: flex; align-items: center; gap: 4px;
|
|
143
|
+
}
|
|
144
|
+
.tb-btn:hover { background: var(--zrd-accent-light); }
|
|
145
|
+
.tb-btn:disabled { opacity: 0.4; cursor: default; }
|
|
146
|
+
.tb-btn--active { background: var(--zrd-accent); color: white; border-color: var(--zrd-accent); }
|
|
147
|
+
.tb-btn--danger:hover { background: #ffebee; color: var(--zrd-danger); border-color: var(--zrd-danger); }
|
|
148
|
+
.report-name {
|
|
149
|
+
font-weight: 600; font-size: 14px; cursor: pointer;
|
|
150
|
+
padding: 2px 6px; border-radius: 3px; border: 1px solid transparent;
|
|
151
|
+
color: var(--zrd-text);
|
|
152
|
+
}
|
|
153
|
+
.report-name:hover { border-color: var(--zrd-border); background: var(--zrd-accent-light); }
|
|
154
|
+
.report-name-input {
|
|
155
|
+
font-weight: 600; font-size: 14px; border: 1px solid var(--zrd-accent);
|
|
156
|
+
border-radius: 3px; padding: 2px 6px; outline: none;
|
|
157
|
+
background: white; color: var(--zrd-text); font-family: inherit;
|
|
158
|
+
}
|
|
159
|
+
.tb-spacer { flex: 1; }
|
|
160
|
+
.zoom-controls {
|
|
161
|
+
display: flex; align-items: center; gap: 4px;
|
|
162
|
+
padding-left: 8px; border-left: 1px solid var(--zrd-border);
|
|
163
|
+
}
|
|
164
|
+
.zoom-btn { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 3px; font-size: 14px; font-weight: bold; }
|
|
165
|
+
.zoom-label { font-size: 11px; min-width: 36px; text-align: center; color: var(--zrd-text-muted); cursor: pointer; }
|
|
166
|
+
|
|
167
|
+
/* ─── Resize Handle ───────────────────────── */
|
|
168
|
+
.panel-resize {
|
|
169
|
+
width: 4px; cursor: col-resize; background: transparent;
|
|
170
|
+
transition: background 0.15s; flex-shrink: 0;
|
|
171
|
+
}
|
|
172
|
+
.panel-resize:hover { background: var(--zrd-accent); }
|
|
173
|
+
|
|
174
|
+
/* ─── Left Panel (Toolbox) ────────────────── */
|
|
175
|
+
.left-panel {
|
|
176
|
+
background: var(--zrd-panel-bg);
|
|
177
|
+
border-right: 1px solid var(--zrd-border);
|
|
178
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
179
|
+
}
|
|
180
|
+
.panel-tabs {
|
|
181
|
+
display: flex; border-bottom: 1px solid var(--zrd-border);
|
|
182
|
+
}
|
|
183
|
+
.panel-tab {
|
|
184
|
+
flex: 1; padding: 8px 4px; text-align: center; cursor: pointer;
|
|
185
|
+
font-size: 11px; border-bottom: 2px solid transparent;
|
|
186
|
+
color: var(--zrd-text-muted); transition: all 0.15s;
|
|
187
|
+
background: none; border-top: none; border-left: none; border-right: none;
|
|
188
|
+
font-family: inherit;
|
|
189
|
+
}
|
|
190
|
+
.panel-tab:hover { color: var(--zrd-text); }
|
|
191
|
+
.panel-tab--active { border-bottom-color: var(--zrd-accent); color: var(--zrd-accent); font-weight: 600; }
|
|
192
|
+
.panel-content { padding: 4px; overflow-y: auto; flex: 1; }
|
|
193
|
+
.panel-content::-webkit-scrollbar { width: 6px; }
|
|
194
|
+
.panel-content::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
|
|
195
|
+
|
|
196
|
+
/* Toolbox grid (3 columns like report-designer) */
|
|
197
|
+
.toolbox-section {
|
|
198
|
+
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
199
|
+
letter-spacing: 0.8px; color: #aaa;
|
|
200
|
+
padding: 10px 8px 5px; margin-top: 2px;
|
|
201
|
+
}
|
|
202
|
+
.toolbox-section:first-child { margin-top: 0; padding-top: 6px; }
|
|
203
|
+
.toolbox-grid {
|
|
204
|
+
display: grid; grid-template-columns: repeat(3, 1fr);
|
|
205
|
+
gap: 3px; padding: 0 4px;
|
|
206
|
+
}
|
|
207
|
+
.toolbox-item {
|
|
208
|
+
display: flex; flex-direction: column; align-items: center;
|
|
209
|
+
gap: 3px; padding: 7px 2px 5px;
|
|
210
|
+
border: 1px solid transparent; border-radius: 5px;
|
|
211
|
+
cursor: grab; user-select: none; text-align: center;
|
|
212
|
+
transition: all 0.15s; background: transparent;
|
|
213
|
+
}
|
|
214
|
+
.toolbox-item:hover {
|
|
215
|
+
background: var(--zrd-accent-light); border-color: #c5dcf0;
|
|
216
|
+
box-shadow: 0 1px 4px rgba(25,118,210,0.1);
|
|
217
|
+
}
|
|
218
|
+
.toolbox-item:active { cursor: grabbing; opacity: 0.6; transform: scale(0.95); }
|
|
219
|
+
.toolbox-icon {
|
|
220
|
+
width: 28px; height: 28px; display: flex; align-items: center;
|
|
221
|
+
justify-content: center; border-radius: 6px;
|
|
222
|
+
font-size: 15px; flex-shrink: 0;
|
|
223
|
+
background: #f0f4f8; color: #1976d2;
|
|
224
|
+
border: 1px solid #e3e8ee;
|
|
225
|
+
transition: all 0.15s;
|
|
226
|
+
}
|
|
227
|
+
.toolbox-item:hover .toolbox-icon {
|
|
228
|
+
background: #1976d2; color: white; border-color: #1976d2;
|
|
229
|
+
box-shadow: 0 2px 6px rgba(25,118,210,0.3);
|
|
230
|
+
}
|
|
231
|
+
.toolbox-label {
|
|
232
|
+
font-size: 9px; font-weight: 500; line-height: 1.1;
|
|
233
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
234
|
+
max-width: 100%; color: #777;
|
|
235
|
+
}
|
|
236
|
+
.toolbox-item:hover .toolbox-label { color: #1976d2; }
|
|
237
|
+
|
|
238
|
+
/* ─── Canvas ──────────────────────────────── */
|
|
239
|
+
.canvas-area {
|
|
240
|
+
overflow: auto; padding: 30px; position: relative;
|
|
241
|
+
background: #d0d0d0; display: flex; justify-content: center;
|
|
242
|
+
}
|
|
243
|
+
.canvas {
|
|
244
|
+
background: white; position: relative; margin: 0 auto; border-radius: 2px;
|
|
245
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.08);
|
|
246
|
+
min-width: 600px; min-height: 400px; padding: 24px;
|
|
247
|
+
transform-origin: top center;
|
|
248
|
+
}
|
|
249
|
+
.canvas-section { margin-bottom: 20px; }
|
|
250
|
+
.canvas-section-header {
|
|
251
|
+
font-size: 13px; font-weight: 600; color: var(--zrd-text);
|
|
252
|
+
padding: 6px 8px; margin-bottom: 8px;
|
|
253
|
+
background: #f0f0f0; border-radius: 4px; border-left: 3px solid var(--zrd-accent);
|
|
254
|
+
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
.canvas-section-header:hover { background: var(--zrd-accent-light); }
|
|
257
|
+
.canvas-grid {
|
|
258
|
+
display: grid; gap: 8px; padding: 4px;
|
|
259
|
+
background: repeating-linear-gradient(0deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px),
|
|
260
|
+
repeating-linear-gradient(90deg, transparent, transparent 19px, rgba(0,0,0,0.03) 19px, rgba(0,0,0,0.03) 20px);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Field on canvas */
|
|
264
|
+
.canvas-field {
|
|
265
|
+
position: relative; border: 1px solid transparent;
|
|
266
|
+
border-radius: 4px; cursor: pointer; user-select: none;
|
|
267
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
268
|
+
padding: 4px;
|
|
269
|
+
}
|
|
270
|
+
.canvas-field:hover { border-color: rgba(25,118,210,0.5); }
|
|
271
|
+
.canvas-field--selected {
|
|
272
|
+
border-color: var(--zrd-accent);
|
|
273
|
+
box-shadow: 0 0 0 1px var(--zrd-accent);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Type badge (above field) */
|
|
277
|
+
.field-type-badge {
|
|
278
|
+
position: absolute; top: -14px; left: 0;
|
|
279
|
+
font-size: 9px; border-radius: 3px 3px 0 0;
|
|
280
|
+
padding: 1px 6px; line-height: 13px;
|
|
281
|
+
pointer-events: none; z-index: 6; display: none;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
}
|
|
284
|
+
.canvas-field:hover .field-type-badge,
|
|
285
|
+
.canvas-field--selected .field-type-badge { display: block; }
|
|
286
|
+
|
|
287
|
+
/* Resize handles (8-point like report-designer) */
|
|
288
|
+
.rh { position: absolute; width: 6px; height: 6px; background: var(--zrd-accent); border: 1px solid white; z-index: 5; display: none; border-radius: 1px; }
|
|
289
|
+
.rh:hover { background: #0d47a1; }
|
|
290
|
+
.canvas-field--selected .rh { display: block; }
|
|
291
|
+
.rh-nw { top: -3px; left: -3px; cursor: nw-resize; }
|
|
292
|
+
.rh-n { top: -3px; left: calc(50% - 3px); cursor: n-resize; }
|
|
293
|
+
.rh-ne { top: -3px; right: -3px; cursor: ne-resize; }
|
|
294
|
+
.rh-e { top: calc(50% - 3px); right: -3px; cursor: e-resize; }
|
|
295
|
+
.rh-se { bottom: -3px; right: -3px; cursor: se-resize; }
|
|
296
|
+
.rh-s { bottom: -3px; left: calc(50% - 3px); cursor: s-resize; }
|
|
297
|
+
.rh-sw { bottom: -3px; left: -3px; cursor: sw-resize; }
|
|
298
|
+
.rh-w { top: calc(50% - 3px); left: -3px; cursor: w-resize; }
|
|
299
|
+
|
|
300
|
+
/* Action buttons on field */
|
|
301
|
+
.field-actions {
|
|
302
|
+
position: absolute; top: -14px; right: 4px;
|
|
303
|
+
display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; z-index: 7;
|
|
304
|
+
}
|
|
305
|
+
.canvas-field:hover .field-actions { opacity: 1; }
|
|
306
|
+
.fa-btn {
|
|
307
|
+
width: 18px; height: 14px; border-radius: 3px 3px 0 0;
|
|
308
|
+
border: none; cursor: pointer; font-size: 9px;
|
|
309
|
+
display: flex; align-items: center; justify-content: center;
|
|
310
|
+
}
|
|
311
|
+
.fa-btn--move { background: var(--zrd-accent); color: white; }
|
|
312
|
+
.fa-btn--delete { background: var(--zrd-danger); color: white; }
|
|
313
|
+
.fa-btn--copy { background: #7c3aed; color: white; }
|
|
314
|
+
|
|
315
|
+
/* Field preview content */
|
|
316
|
+
.field-preview {
|
|
317
|
+
pointer-events: none;
|
|
318
|
+
}
|
|
319
|
+
.field-preview-label {
|
|
320
|
+
font-size: 11px; font-weight: 500; color: var(--zrd-text-muted);
|
|
321
|
+
margin-bottom: 3px;
|
|
322
|
+
}
|
|
323
|
+
.field-preview-input {
|
|
324
|
+
height: 30px; border: 1px solid #e0e0e0; border-radius: 4px;
|
|
325
|
+
background: #fafafa; display: flex; align-items: center;
|
|
326
|
+
padding: 0 8px; font-size: 12px; color: #999;
|
|
327
|
+
}
|
|
328
|
+
.field-preview-input--textarea { height: 60px; align-items: flex-start; padding-top: 6px; }
|
|
329
|
+
.field-preview-input--switch { height: auto; border: none; background: none; padding: 0; }
|
|
330
|
+
.field-preview-input--separator { height: 1px; border: none; background: #ddd; padding: 0; }
|
|
331
|
+
.field-preview-input--heading { height: auto; border: none; background: none; padding: 0; font-size: 16px; font-weight: 600; color: var(--zrd-text); }
|
|
332
|
+
.field-preview-input--datagrid { height: 120px; border: 2px dashed var(--zrd-accent); background: var(--zrd-accent-light); justify-content: center; font-weight: 500; color: var(--zrd-accent); }
|
|
333
|
+
.field-preview-input--report { height: 120px; border: 2px dashed #e65100; background: #fff3e0; justify-content: center; font-weight: 500; color: #e65100; }
|
|
334
|
+
.field-preview-input--chart { height: 120px; border: 2px dashed #6a1b9a; background: #f3e5f5; justify-content: center; font-weight: 500; color: #6a1b9a; }
|
|
335
|
+
|
|
336
|
+
/* Drop zone */
|
|
337
|
+
.drop-zone {
|
|
338
|
+
border: 2px dashed var(--zrd-border); border-radius: 6px;
|
|
339
|
+
padding: 16px; text-align: center; margin: 4px 0;
|
|
340
|
+
color: var(--zrd-text-muted); font-size: 12px; transition: all 0.15s;
|
|
341
|
+
}
|
|
342
|
+
.drop-zone--active { border-color: var(--zrd-accent); background: var(--zrd-accent-light); color: var(--zrd-accent); }
|
|
343
|
+
|
|
344
|
+
/* ─── Right Panel (Properties — Figma-quality) ──── */
|
|
345
|
+
.right-panel {
|
|
346
|
+
background: var(--zrd-panel-bg);
|
|
347
|
+
border-left: 1px solid var(--zrd-border);
|
|
348
|
+
overflow-y: auto; font-size: 11px;
|
|
349
|
+
}
|
|
350
|
+
.right-panel::-webkit-scrollbar { width: 5px; }
|
|
351
|
+
.right-panel::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; }
|
|
352
|
+
.right-panel::-webkit-scrollbar-thumb:hover { background: #bbb; }
|
|
353
|
+
|
|
354
|
+
/* ─ Prop Section ─ */
|
|
355
|
+
.prop-section { border-bottom: 1px solid #eee; padding: 8px 10px 10px; }
|
|
356
|
+
.prop-section:last-child { border-bottom: none; }
|
|
357
|
+
.prop-section-header {
|
|
358
|
+
display: flex; align-items: center; gap: 6px;
|
|
359
|
+
cursor: pointer; user-select: none; margin-bottom: 6px; padding: 2px 0;
|
|
360
|
+
}
|
|
361
|
+
.prop-section-header h4 {
|
|
362
|
+
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
363
|
+
letter-spacing: 0.6px; margin: 0; flex: 1;
|
|
364
|
+
}
|
|
365
|
+
.prop-section-header[data-section="general"] h4 { color: #1976d2; }
|
|
366
|
+
.prop-section-header[data-section="layout"] h4 { color: #7c3aed; }
|
|
367
|
+
.prop-section-header[data-section="behavior"] h4 { color: #0d9488; }
|
|
368
|
+
.prop-section-header[data-section="rules"] h4 { color: #ea580c; }
|
|
369
|
+
.prop-section-header[data-section="style"] h4 { color: #c2185b; }
|
|
370
|
+
.prop-section-header[data-section="form"] h4 { color: #1976d2; }
|
|
371
|
+
.collapse-icon {
|
|
372
|
+
font-size: 8px; color: #bbb; transition: transform 0.15s;
|
|
373
|
+
width: 14px; height: 14px; display: flex; align-items: center;
|
|
374
|
+
justify-content: center; border-radius: 3px;
|
|
375
|
+
}
|
|
376
|
+
.collapse-icon:hover { background: #f0f0f0; color: #666; }
|
|
377
|
+
.collapse-icon--collapsed { transform: rotate(-90deg); }
|
|
378
|
+
|
|
379
|
+
/* ─ Prop Rows ─ */
|
|
380
|
+
.prop-row {
|
|
381
|
+
display: grid; grid-template-columns: 62px 1fr;
|
|
382
|
+
align-items: center; gap: 4px; min-height: 26px; margin-bottom: 1px;
|
|
383
|
+
}
|
|
384
|
+
.prop-row-full { grid-template-columns: 1fr; margin-bottom: 3px; }
|
|
385
|
+
.prop-label {
|
|
386
|
+
font-size: 11px; color: #999; font-weight: 400;
|
|
387
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
388
|
+
padding-left: 1px; line-height: 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* ─ Figma-style input ─ */
|
|
392
|
+
.prop-input {
|
|
393
|
+
width: 100%; border: 1px solid transparent; border-radius: 4px;
|
|
394
|
+
padding: 5px 7px; font-size: 11px; background: #f5f5f5;
|
|
395
|
+
color: var(--zrd-text); outline: none; min-width: 0;
|
|
396
|
+
font-family: inherit; box-sizing: border-box;
|
|
397
|
+
transition: border-color 0.12s, background 0.12s, box-shadow 0.12s;
|
|
398
|
+
}
|
|
399
|
+
.prop-input:hover { background: #efefef; border-color: #ddd; }
|
|
400
|
+
.prop-input:focus { border-color: #1976d2; background: white; box-shadow: 0 0 0 2px rgba(25,118,210,0.08); }
|
|
401
|
+
select.prop-input {
|
|
402
|
+
padding: 4px 20px 4px 7px; cursor: pointer; appearance: none;
|
|
403
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
|
|
404
|
+
background-repeat: no-repeat; background-position: right 6px center;
|
|
405
|
+
}
|
|
406
|
+
textarea.prop-input {
|
|
407
|
+
min-height: 44px; resize: vertical; font-family: 'SF Mono','Consolas','Monaco',monospace;
|
|
408
|
+
font-size: 10px; line-height: 1.5; padding: 6px 7px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* ─ Numeric stepper (Figma-style) ─ */
|
|
412
|
+
.prop-stepper {
|
|
413
|
+
display: flex; align-items: center; background: #f5f5f5;
|
|
414
|
+
border: 1px solid transparent; border-radius: 4px; overflow: hidden;
|
|
415
|
+
transition: border-color 0.12s;
|
|
416
|
+
}
|
|
417
|
+
.prop-stepper:hover { border-color: #ddd; }
|
|
418
|
+
.prop-stepper:focus-within { border-color: #1976d2; background: white; }
|
|
419
|
+
.prop-stepper input {
|
|
420
|
+
border: none; background: transparent; width: 100%;
|
|
421
|
+
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
422
|
+
padding: 4px 2px 4px 7px; min-width: 0; font-family: inherit;
|
|
423
|
+
-moz-appearance: textfield;
|
|
424
|
+
}
|
|
425
|
+
.prop-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
426
|
+
.prop-stepper-btns {
|
|
427
|
+
display: flex; flex-direction: column; border-left: 1px solid #eee;
|
|
428
|
+
}
|
|
429
|
+
.prop-stepper-btn {
|
|
430
|
+
border: none; background: none; cursor: pointer; padding: 0;
|
|
431
|
+
width: 18px; height: 12px; display: flex; align-items: center;
|
|
432
|
+
justify-content: center; font-size: 7px; color: #999;
|
|
433
|
+
transition: all 0.1s;
|
|
434
|
+
}
|
|
435
|
+
.prop-stepper-btn:hover { background: #e8e8e8; color: #333; }
|
|
436
|
+
|
|
437
|
+
/* ─ Position grid (4-cell Figma layout) ─ */
|
|
438
|
+
.prop-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
|
439
|
+
.prop-pos-cell {
|
|
440
|
+
display: flex; align-items: center; background: #f5f5f5;
|
|
441
|
+
border: 1px solid transparent; border-radius: 4px; padding: 0 6px;
|
|
442
|
+
height: 26px; gap: 3px; transition: border-color 0.12s;
|
|
443
|
+
}
|
|
444
|
+
.prop-pos-cell:hover { border-color: #ddd; }
|
|
445
|
+
.prop-pos-cell:focus-within { border-color: #1976d2; background: white; }
|
|
446
|
+
.prop-pos-label {
|
|
447
|
+
font-size: 9px; font-weight: 700; width: 10px; text-align: center;
|
|
448
|
+
flex-shrink: 0; user-select: none;
|
|
449
|
+
}
|
|
450
|
+
.prop-pos-label--x { color: #ef4444; }
|
|
451
|
+
.prop-pos-label--y { color: #3b82f6; }
|
|
452
|
+
.prop-pos-label--w { color: #8b5cf6; }
|
|
453
|
+
.prop-pos-label--h { color: #10b981; }
|
|
454
|
+
.prop-pos-cell input {
|
|
455
|
+
border: none; background: transparent; width: 100%;
|
|
456
|
+
font-size: 11px; color: var(--zrd-text); outline: none;
|
|
457
|
+
padding: 0; min-width: 0; font-family: inherit;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* ─ Toggle switches (Figma compact) ─ */
|
|
461
|
+
.prop-toggle {
|
|
462
|
+
display: flex; align-items: center; gap: 8px;
|
|
463
|
+
min-height: 24px; padding: 1px 0;
|
|
464
|
+
}
|
|
465
|
+
.prop-switch {
|
|
466
|
+
position: relative; width: 28px; height: 16px; border-radius: 8px;
|
|
467
|
+
background: #d4d4d4; cursor: pointer; transition: background 0.2s;
|
|
468
|
+
flex-shrink: 0; border: none; padding: 0;
|
|
469
|
+
}
|
|
470
|
+
.prop-switch--active { background: #1976d2; }
|
|
471
|
+
.prop-switch::after {
|
|
472
|
+
content: ''; position: absolute; top: 2px; left: 2px;
|
|
473
|
+
width: 12px; height: 12px; border-radius: 50%;
|
|
474
|
+
background: white; box-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
|
475
|
+
transition: transform 0.2s;
|
|
476
|
+
}
|
|
477
|
+
.prop-switch--active::after { transform: translateX(12px); }
|
|
478
|
+
.prop-toggle-label { font-size: 11px; color: #888; }
|
|
479
|
+
.prop-toggle:hover .prop-toggle-label { color: #555; }
|
|
480
|
+
|
|
481
|
+
/* ─ Segmented control (alignment, format) ─ */
|
|
482
|
+
.prop-segmented {
|
|
483
|
+
display: flex; border: 1px solid #e0e0e0; border-radius: 5px;
|
|
484
|
+
overflow: hidden; background: #f5f5f5;
|
|
485
|
+
}
|
|
486
|
+
.prop-seg-btn {
|
|
487
|
+
flex: 1; padding: 4px 0; background: transparent; border: none;
|
|
488
|
+
border-right: 1px solid #e0e0e0; cursor: pointer;
|
|
489
|
+
font-size: 11px; color: #999; transition: all 0.12s; text-align: center;
|
|
490
|
+
font-family: inherit;
|
|
491
|
+
}
|
|
492
|
+
.prop-seg-btn:last-child { border-right: none; }
|
|
493
|
+
.prop-seg-btn:hover { background: #eee; color: #555; }
|
|
494
|
+
.prop-seg-btn--active { background: #1976d2; color: white; }
|
|
495
|
+
|
|
496
|
+
/* ─ Color picker (swatch + hex inline) ─ */
|
|
497
|
+
.prop-color-row { display: flex; align-items: center; gap: 6px; }
|
|
498
|
+
.prop-color-swatch {
|
|
499
|
+
width: 22px; height: 22px; border-radius: 5px;
|
|
500
|
+
border: 1px solid #ddd; cursor: pointer; flex-shrink: 0;
|
|
501
|
+
position: relative; overflow: hidden;
|
|
502
|
+
}
|
|
503
|
+
.prop-color-swatch input[type="color"] {
|
|
504
|
+
position: absolute; inset: -4px; width: calc(100% + 8px);
|
|
505
|
+
height: calc(100% + 8px); cursor: pointer; border: none; padding: 0;
|
|
506
|
+
}
|
|
507
|
+
.prop-color-hex {
|
|
508
|
+
border: 1px solid transparent; border-radius: 4px;
|
|
509
|
+
padding: 4px 6px; font-size: 10px;
|
|
510
|
+
font-family: 'SF Mono','Consolas',monospace;
|
|
511
|
+
background: #f5f5f5; color: var(--zrd-text); width: 70px; outline: none;
|
|
512
|
+
box-sizing: border-box;
|
|
513
|
+
}
|
|
514
|
+
.prop-color-hex:hover { border-color: #ddd; }
|
|
515
|
+
.prop-color-hex:focus { border-color: #1976d2; background: white; }
|
|
516
|
+
|
|
517
|
+
/* ─ Info badge ─ */
|
|
518
|
+
.prop-info {
|
|
519
|
+
display: flex; align-items: center; gap: 4px;
|
|
520
|
+
padding: 5px 8px; border-radius: 4px; background: #f0f7ff;
|
|
521
|
+
font-size: 10px; color: #1976d2; margin-bottom: 4px;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ─ Divider line ─ */
|
|
525
|
+
.prop-divider { height: 1px; background: #f0f0f0; margin: 4px 0; }
|
|
526
|
+
|
|
527
|
+
/* ─ Empty state ─ */
|
|
528
|
+
.props-empty {
|
|
529
|
+
text-align: center; padding: 32px 16px; color: #ccc;
|
|
530
|
+
}
|
|
531
|
+
.props-empty-icon { font-size: 28px; margin-bottom: 6px; opacity: 0.4; }
|
|
532
|
+
.props-empty-text { font-size: 11px; line-height: 1.5; }
|
|
533
|
+
|
|
534
|
+
/* ─ Type badge ─ */
|
|
535
|
+
.props-type-badge {
|
|
536
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
537
|
+
padding: 3px 8px; border-radius: 4px;
|
|
538
|
+
font-size: 10px; font-weight: 700; letter-spacing: 0.3px;
|
|
539
|
+
text-transform: uppercase;
|
|
540
|
+
}
|
|
541
|
+
.props-field-id {
|
|
542
|
+
font-size: 10px; color: #bbb; font-family: 'SF Mono','Consolas',monospace;
|
|
543
|
+
margin-top: 2px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* ─ JSON view ─ */
|
|
547
|
+
.json-panel {
|
|
548
|
+
font-family: 'SF Mono','Consolas','Monaco',monospace; font-size: 11px;
|
|
549
|
+
line-height: 1.5; background: #1e1e2d; color: #a2a3b7;
|
|
550
|
+
padding: 16px; border-radius: 4px; overflow: auto;
|
|
551
|
+
white-space: pre; tab-size: 2;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* ─── InteractJS Drag & Drop ─────────────────── */
|
|
555
|
+
.dragging { opacity: 0.4; cursor: grabbing !important; }
|
|
556
|
+
.drag-clone {
|
|
557
|
+
position: fixed; pointer-events: none; z-index: 9999;
|
|
558
|
+
opacity: 0.85; border: 2px solid var(--zrd-accent);
|
|
559
|
+
border-radius: 6px; background: white;
|
|
560
|
+
box-shadow: 0 8px 24px rgba(25,118,210,0.25);
|
|
561
|
+
padding: 8px 12px; font-size: 12px; font-weight: 500;
|
|
562
|
+
color: var(--zrd-accent); max-width: 200px;
|
|
563
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
564
|
+
}
|
|
565
|
+
.drop-active { border: 2px dashed var(--zrd-accent) !important; background: rgba(25,118,210,0.04) !important; }
|
|
566
|
+
.drop-target { background: rgba(25,118,210,0.1) !important; border-color: var(--zrd-accent) !important; }
|
|
567
|
+
.resize-active { outline: 2px dashed var(--zrd-accent); outline-offset: 2px; }
|
|
568
|
+
.drag-insert-line {
|
|
569
|
+
position: absolute; left: 4px; right: 4px; height: 2px;
|
|
570
|
+
background: var(--zrd-accent); border-radius: 1px; z-index: 10;
|
|
571
|
+
pointer-events: none;
|
|
572
|
+
}
|
|
573
|
+
.drag-insert-line::before, .drag-insert-line::after {
|
|
574
|
+
content: ''; position: absolute; top: -3px;
|
|
575
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
576
|
+
background: var(--zrd-accent);
|
|
577
|
+
}
|
|
578
|
+
.drag-insert-line::before { left: -4px; }
|
|
579
|
+
.drag-insert-line::after { right: -4px; }
|
|
580
|
+
.canvas-field.can-drop { box-shadow: 0 0 0 2px rgba(25,118,210,0.3); }
|
|
581
|
+
.section-drop-zone {
|
|
582
|
+
min-height: 8px; transition: all 0.15s; border-radius: 4px;
|
|
583
|
+
margin: 2px 4px;
|
|
584
|
+
}
|
|
585
|
+
.section-drop-zone.drop-active {
|
|
586
|
+
min-height: 32px; border: 2px dashed var(--zrd-accent);
|
|
587
|
+
background: rgba(25,118,210,0.06);
|
|
588
|
+
display: flex; align-items: center; justify-content: center;
|
|
589
|
+
font-size: 11px; color: var(--zrd-accent);
|
|
590
|
+
}
|
|
591
591
|
`; }
|
|
592
592
|
// ─── Lifecycle ────────────────────────────────────
|
|
593
593
|
updated(changed) {
|
|
@@ -1015,6 +1015,112 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1015
1015
|
this.selectedFieldId = id;
|
|
1016
1016
|
this.commitChange();
|
|
1017
1017
|
}
|
|
1018
|
+
/** Handle drop from toolbox OR API fields — unified */
|
|
1019
|
+
handleCanvasDrop(e, sectionIndex) {
|
|
1020
|
+
e.preventDefault();
|
|
1021
|
+
const fieldName = e.dataTransfer?.getData('field-name');
|
|
1022
|
+
const dsId = e.dataTransfer?.getData('ds-id');
|
|
1023
|
+
if (fieldName && dsId) {
|
|
1024
|
+
// Drop from API tab — create field with smart type + binding
|
|
1025
|
+
this.addApiField(fieldName, dsId, sectionIndex);
|
|
1026
|
+
}
|
|
1027
|
+
else if (this.dragType) {
|
|
1028
|
+
// Drop from toolbox
|
|
1029
|
+
this.addField(this.dragType, sectionIndex);
|
|
1030
|
+
}
|
|
1031
|
+
this.dragType = null;
|
|
1032
|
+
}
|
|
1033
|
+
/** Add a field from an API data source with smart type detection */
|
|
1034
|
+
addApiField(fieldName, dsId, sectionIndex = 0) {
|
|
1035
|
+
if (!this.schema) {
|
|
1036
|
+
this.schema = {
|
|
1037
|
+
id: 'new-form', version: '1.0', title: 'Nuevo Formulario',
|
|
1038
|
+
layout: { type: 'grid', columns: 2 },
|
|
1039
|
+
sections: [{ id: 'main', title: 'Datos', fields: [] }],
|
|
1040
|
+
};
|
|
1041
|
+
this.undoStack = [JSON.stringify(this.schema)];
|
|
1042
|
+
}
|
|
1043
|
+
if (sectionIndex >= this.schema.sections.length)
|
|
1044
|
+
sectionIndex = 0;
|
|
1045
|
+
const type = this.inferFieldType(fieldName);
|
|
1046
|
+
const id = `${fieldName.toLowerCase().replace(/[^a-z0-9]/g, '_')}_${Date.now()}`;
|
|
1047
|
+
const label = this.humanizeFieldName(fieldName);
|
|
1048
|
+
const meta = getAllFields().find(f => f.type === type);
|
|
1049
|
+
// Find the source to get the endpoint for datagrid
|
|
1050
|
+
const source = this.apiSources.find(s => s.id === dsId);
|
|
1051
|
+
const endpoint = source?.endpoint ?? '';
|
|
1052
|
+
this.schema.sections[sectionIndex].fields.push({
|
|
1053
|
+
id,
|
|
1054
|
+
type,
|
|
1055
|
+
field: fieldName,
|
|
1056
|
+
label,
|
|
1057
|
+
props: {
|
|
1058
|
+
...meta?.defaultProps,
|
|
1059
|
+
dataSourceId: dsId,
|
|
1060
|
+
endpoint: endpoint || undefined,
|
|
1061
|
+
authToken: this.apiToken || undefined,
|
|
1062
|
+
authHeaders: this.apiToken ? {
|
|
1063
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
1064
|
+
...(this.apiCompany ? { 'x-empresa': this.apiCompany } : {}),
|
|
1065
|
+
...(this.apiBranch ? { 'x-sucursal': this.apiBranch } : {}),
|
|
1066
|
+
} : undefined,
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
this.selectedFieldId = id;
|
|
1070
|
+
this.commitChange();
|
|
1071
|
+
}
|
|
1072
|
+
/** Infer field type from column/field name */
|
|
1073
|
+
inferFieldType(name) {
|
|
1074
|
+
const lower = name.toLowerCase();
|
|
1075
|
+
if (lower.includes('email') || lower.includes('correo'))
|
|
1076
|
+
return 'email';
|
|
1077
|
+
if (lower.includes('phone') || lower.includes('telefono') || lower.includes('celular'))
|
|
1078
|
+
return 'phone';
|
|
1079
|
+
if (lower.includes('fecha') || lower.includes('date') || lower.includes('nacimiento') || lower.includes('ingreso') || lower.includes('vencimiento'))
|
|
1080
|
+
return 'date';
|
|
1081
|
+
if (lower.includes('precio') || lower.includes('price') || lower.includes('costo') || lower.includes('monto') || lower.includes('total') || lower.includes('saldo') || lower.includes('salario') || lower.includes('limite'))
|
|
1082
|
+
return 'currency';
|
|
1083
|
+
if (lower.includes('cantidad') || lower.includes('qty') || lower.includes('stock') || lower.includes('edad'))
|
|
1084
|
+
return 'number';
|
|
1085
|
+
if (lower.includes('activ') || lower.includes('active') || lower.includes('enabled') || lower.includes('visible'))
|
|
1086
|
+
return 'switch';
|
|
1087
|
+
if (lower.includes('descripcion') || lower.includes('description') || lower.includes('notas') || lower.includes('notes') || lower.includes('observ'))
|
|
1088
|
+
return 'textarea';
|
|
1089
|
+
if (lower.includes('password') || lower.includes('clave'))
|
|
1090
|
+
return 'password';
|
|
1091
|
+
if (lower.includes('url') || lower.includes('website') || lower.includes('link'))
|
|
1092
|
+
return 'url';
|
|
1093
|
+
if (lower.includes('imagen') || lower.includes('image') || lower.includes('foto') || lower.includes('avatar'))
|
|
1094
|
+
return 'image';
|
|
1095
|
+
if (lower.includes('pais') || lower.includes('country') || lower.includes('estado') || lower.includes('tipo') || lower.includes('status') || lower.includes('genero') || lower.includes('categoria'))
|
|
1096
|
+
return 'select';
|
|
1097
|
+
if (lower.includes('direccion') || lower.includes('address'))
|
|
1098
|
+
return 'address';
|
|
1099
|
+
return 'text';
|
|
1100
|
+
}
|
|
1101
|
+
/** Convert FIELD_NAME to "Field Name" */
|
|
1102
|
+
humanizeFieldName(name) {
|
|
1103
|
+
return name
|
|
1104
|
+
.replace(/([A-Z])/g, ' $1')
|
|
1105
|
+
.replace(/[_-]/g, ' ')
|
|
1106
|
+
.replace(/\b\w/g, c => c.toUpperCase())
|
|
1107
|
+
.trim();
|
|
1108
|
+
}
|
|
1109
|
+
/** Unified drop handler for toolbox AND API fields */
|
|
1110
|
+
handleCanvasDrop(e, sectionIndex) {
|
|
1111
|
+
e.preventDefault();
|
|
1112
|
+
const fieldName = e.dataTransfer?.getData('field-name');
|
|
1113
|
+
const dsId = e.dataTransfer?.getData('ds-id');
|
|
1114
|
+
if (fieldName && dsId) {
|
|
1115
|
+
// Drop from API tab
|
|
1116
|
+
this.addApiField(fieldName, dsId, sectionIndex);
|
|
1117
|
+
}
|
|
1118
|
+
else if (this.dragType) {
|
|
1119
|
+
// Drop from toolbox
|
|
1120
|
+
this.addField(this.dragType, sectionIndex);
|
|
1121
|
+
}
|
|
1122
|
+
this.dragType = null;
|
|
1123
|
+
}
|
|
1018
1124
|
removeField(si, fi) {
|
|
1019
1125
|
if (!this.schema)
|
|
1020
1126
|
return;
|
|
@@ -1047,77 +1153,77 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1047
1153
|
}
|
|
1048
1154
|
// ─── Render ───────────────────────────────────────
|
|
1049
1155
|
render() {
|
|
1050
|
-
return html `
|
|
1051
|
-
<div class="designer">
|
|
1052
|
-
${this.renderToolbar()}
|
|
1053
|
-
${this.renderLeftPanel()}
|
|
1054
|
-
<div class="panel-resize"></div>
|
|
1055
|
-
${this.renderCanvas()}
|
|
1056
|
-
<div class="panel-resize"></div>
|
|
1057
|
-
${this.renderRightPanel()}
|
|
1058
|
-
</div>
|
|
1156
|
+
return html `
|
|
1157
|
+
<div class="designer">
|
|
1158
|
+
${this.renderToolbar()}
|
|
1159
|
+
${this.renderLeftPanel()}
|
|
1160
|
+
<div class="panel-resize"></div>
|
|
1161
|
+
${this.renderCanvas()}
|
|
1162
|
+
<div class="panel-resize"></div>
|
|
1163
|
+
${this.renderRightPanel()}
|
|
1164
|
+
</div>
|
|
1059
1165
|
`;
|
|
1060
1166
|
}
|
|
1061
1167
|
// ─── Toolbar ──────────────────────────────────────
|
|
1062
1168
|
renderToolbar() {
|
|
1063
|
-
return html `
|
|
1064
|
-
<div class="toolbar">
|
|
1169
|
+
return html `
|
|
1170
|
+
<div class="toolbar">
|
|
1065
1171
|
${this.editingTitle
|
|
1066
|
-
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
1172
|
+
? html `<input class="report-name-input" .value="${this.schema?.title ?? ''}"
|
|
1067
1173
|
@blur="${(e) => { if (this.schema)
|
|
1068
|
-
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
1174
|
+
this.schema.title = e.target.value; this.editingTitle = false; this.commitChange(); }}"
|
|
1069
1175
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
1070
|
-
e.target.blur(); }}"
|
|
1176
|
+
e.target.blur(); }}"
|
|
1071
1177
|
/>`
|
|
1072
|
-
: html `<span class="report-name" @click="${() => { this.editingTitle = true; }}">${this.schema?.title || 'Sin titulo'}</span>`}
|
|
1073
|
-
|
|
1074
|
-
<div class="toolbar-sep"></div>
|
|
1075
|
-
|
|
1076
|
-
<button class="tb-btn" ?disabled="${this.undoStack.length <= 1}" @click="${this.undo}" title="Deshacer (Ctrl+Z)">↩ Deshacer</button>
|
|
1077
|
-
<button class="tb-btn" ?disabled="${this.redoStack.length === 0}" @click="${this.redo}" title="Rehacer (Ctrl+Y)">↪ Rehacer</button>
|
|
1078
|
-
|
|
1079
|
-
<div class="toolbar-sep"></div>
|
|
1080
|
-
|
|
1178
|
+
: html `<span class="report-name" @click="${() => { this.editingTitle = true; }}">${this.schema?.title || 'Sin titulo'}</span>`}
|
|
1179
|
+
|
|
1180
|
+
<div class="toolbar-sep"></div>
|
|
1181
|
+
|
|
1182
|
+
<button class="tb-btn" ?disabled="${this.undoStack.length <= 1}" @click="${this.undo}" title="Deshacer (Ctrl+Z)">↩ Deshacer</button>
|
|
1183
|
+
<button class="tb-btn" ?disabled="${this.redoStack.length === 0}" @click="${this.redo}" title="Rehacer (Ctrl+Y)">↪ Rehacer</button>
|
|
1184
|
+
|
|
1185
|
+
<div class="toolbar-sep"></div>
|
|
1186
|
+
|
|
1081
1187
|
<button class="tb-btn" @click="${() => {
|
|
1082
1188
|
if (!this.schema)
|
|
1083
1189
|
return;
|
|
1084
1190
|
this.schema.sections.push({ id: `section_${Date.now()}`, title: 'Nueva Seccion', fields: [] });
|
|
1085
1191
|
this.commitChange();
|
|
1086
|
-
}}">+ Seccion</button>
|
|
1087
|
-
|
|
1088
|
-
<div style="position:relative;display:inline-block;">
|
|
1089
|
-
<button class="tb-btn" @click="${() => { this.showTemplateMenu = !this.showTemplateMenu; }}">📋 Plantillas ▾</button>
|
|
1090
|
-
${this.showTemplateMenu ? this.renderTemplateMenu() : nothing}
|
|
1091
|
-
</div>
|
|
1092
|
-
|
|
1192
|
+
}}">+ Seccion</button>
|
|
1193
|
+
|
|
1194
|
+
<div style="position:relative;display:inline-block;">
|
|
1195
|
+
<button class="tb-btn" @click="${() => { this.showTemplateMenu = !this.showTemplateMenu; }}">📋 Plantillas ▾</button>
|
|
1196
|
+
${this.showTemplateMenu ? this.renderTemplateMenu() : nothing}
|
|
1197
|
+
</div>
|
|
1198
|
+
|
|
1093
1199
|
<button class="tb-btn tb-btn--danger" @click="${() => {
|
|
1094
1200
|
this.schema = { id: 'new-form', version: '1.0', title: 'Nuevo Formulario', layout: { type: 'grid', columns: 2 }, sections: [{ id: 'main', title: 'Datos', fields: [] }] };
|
|
1095
1201
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
1096
1202
|
this.redoStack = [];
|
|
1097
1203
|
this.selectedFieldId = null;
|
|
1098
1204
|
this.commitChange();
|
|
1099
|
-
}}">🗑 Nuevo</button>
|
|
1100
|
-
|
|
1101
|
-
<div class="toolbar-sep"></div>
|
|
1102
|
-
|
|
1103
|
-
<button class="tb-btn ${this.viewMode === 'design' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'design'; }}">✏️ Diseño</button>
|
|
1104
|
-
<button class="tb-btn ${this.viewMode === 'preview' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'preview'; }}">👁 Preview</button>
|
|
1105
|
-
<button class="tb-btn ${this.viewMode === 'json' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'json'; }}"></> JSON</button>
|
|
1106
|
-
|
|
1107
|
-
<span class="tb-spacer"></span>
|
|
1108
|
-
|
|
1205
|
+
}}">🗑 Nuevo</button>
|
|
1206
|
+
|
|
1207
|
+
<div class="toolbar-sep"></div>
|
|
1208
|
+
|
|
1209
|
+
<button class="tb-btn ${this.viewMode === 'design' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'design'; }}">✏️ Diseño</button>
|
|
1210
|
+
<button class="tb-btn ${this.viewMode === 'preview' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'preview'; }}">👁 Preview</button>
|
|
1211
|
+
<button class="tb-btn ${this.viewMode === 'json' ? 'tb-btn--active' : ''}" @click="${() => { this.viewMode = 'json'; }}"></> JSON</button>
|
|
1212
|
+
|
|
1213
|
+
<span class="tb-spacer"></span>
|
|
1214
|
+
|
|
1109
1215
|
<button class="tb-btn" @click="${() => {
|
|
1110
1216
|
if (this.schema) {
|
|
1111
1217
|
navigator.clipboard.writeText(JSON.stringify(this.schema, null, 2));
|
|
1112
1218
|
}
|
|
1113
|
-
}}">📋 Copiar</button>
|
|
1114
|
-
|
|
1115
|
-
<div class="zoom-controls">
|
|
1116
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.max(0.5, this.zoom - 0.1); }}">−</button>
|
|
1117
|
-
<span class="zoom-label" @click="${() => { this.zoom = 1; }}">${Math.round(this.zoom * 100)}%</span>
|
|
1118
|
-
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.min(2, this.zoom + 0.1); }}">+</button>
|
|
1119
|
-
</div>
|
|
1120
|
-
</div>
|
|
1219
|
+
}}">📋 Copiar</button>
|
|
1220
|
+
|
|
1221
|
+
<div class="zoom-controls">
|
|
1222
|
+
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.max(0.5, this.zoom - 0.1); }}">−</button>
|
|
1223
|
+
<span class="zoom-label" @click="${() => { this.zoom = 1; }}">${Math.round(this.zoom * 100)}%</span>
|
|
1224
|
+
<button class="tb-btn zoom-btn" @click="${() => { this.zoom = Math.min(2, this.zoom + 0.1); }}">+</button>
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1121
1227
|
`;
|
|
1122
1228
|
}
|
|
1123
1229
|
// ─── Left Panel ───────────────────────────────────
|
|
@@ -1129,107 +1235,103 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1129
1235
|
{ key: 'media', label: 'Media' },
|
|
1130
1236
|
{ key: 'layout', label: 'Layout' },
|
|
1131
1237
|
];
|
|
1132
|
-
return html `
|
|
1133
|
-
<div class="left-panel">
|
|
1134
|
-
<div class="panel-tabs">
|
|
1135
|
-
<button class="panel-tab ${this.leftTab === 'fields' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'fields'; }}">Campos</button>
|
|
1136
|
-
<button class="panel-tab ${this.leftTab === 'sections' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'sections'; }}">Secciones</button>
|
|
1137
|
-
<button class="panel-tab ${this.leftTab === 'api' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'api'; }}" style="${this.leftTab === 'api' ? 'color:#ea580c;border-bottom-color:#ea580c;' : ''}">API</button>
|
|
1138
|
-
</div>
|
|
1139
|
-
<div class="panel-content">
|
|
1140
|
-
${this.leftTab === 'fields' ? html `
|
|
1238
|
+
return html `
|
|
1239
|
+
<div class="left-panel">
|
|
1240
|
+
<div class="panel-tabs">
|
|
1241
|
+
<button class="panel-tab ${this.leftTab === 'fields' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'fields'; }}">Campos</button>
|
|
1242
|
+
<button class="panel-tab ${this.leftTab === 'sections' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'sections'; }}">Secciones</button>
|
|
1243
|
+
<button class="panel-tab ${this.leftTab === 'api' ? 'panel-tab--active' : ''}" @click="${() => { this.leftTab = 'api'; }}" style="${this.leftTab === 'api' ? 'color:#ea580c;border-bottom-color:#ea580c;' : ''}">API</button>
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="panel-content">
|
|
1246
|
+
${this.leftTab === 'fields' ? html `
|
|
1141
1247
|
${categories.map(cat => {
|
|
1142
1248
|
const items = getFieldsByCategory(cat.key);
|
|
1143
1249
|
if (items.length === 0)
|
|
1144
1250
|
return nothing;
|
|
1145
|
-
return html `
|
|
1146
|
-
<div class="toolbox-section">${cat.label}</div>
|
|
1147
|
-
<div class="toolbox-grid">
|
|
1148
|
-
${items.map(f => html `
|
|
1149
|
-
<div class="toolbox-item"
|
|
1150
|
-
draggable="true"
|
|
1151
|
-
data-field-type="${f.type}"
|
|
1152
|
-
@dragstart="${(e) => { this.dragType = f.type; e.dataTransfer?.setData('text/plain', f.type); }}"
|
|
1153
|
-
@dragend="${() => { this.dragType = null; }}"
|
|
1154
|
-
@dblclick="${() => this.addField(f.type)}"
|
|
1155
|
-
title="${f.label}"
|
|
1156
|
-
>
|
|
1157
|
-
<span class="toolbox-icon">${unsafeHTML(resolveIcon(f.icon, this.provider))}</span>
|
|
1158
|
-
<span class="toolbox-label">${f.label}</span>
|
|
1159
|
-
</div>
|
|
1160
|
-
`)}
|
|
1161
|
-
</div>
|
|
1251
|
+
return html `
|
|
1252
|
+
<div class="toolbox-section">${cat.label}</div>
|
|
1253
|
+
<div class="toolbox-grid">
|
|
1254
|
+
${items.map(f => html `
|
|
1255
|
+
<div class="toolbox-item"
|
|
1256
|
+
draggable="true"
|
|
1257
|
+
data-field-type="${f.type}"
|
|
1258
|
+
@dragstart="${(e) => { this.dragType = f.type; e.dataTransfer?.setData('text/plain', f.type); }}"
|
|
1259
|
+
@dragend="${() => { this.dragType = null; }}"
|
|
1260
|
+
@dblclick="${() => this.addField(f.type)}"
|
|
1261
|
+
title="${f.label}"
|
|
1262
|
+
>
|
|
1263
|
+
<span class="toolbox-icon">${unsafeHTML(resolveIcon(f.icon, this.provider))}</span>
|
|
1264
|
+
<span class="toolbox-label">${f.label}</span>
|
|
1265
|
+
</div>
|
|
1266
|
+
`)}
|
|
1267
|
+
</div>
|
|
1162
1268
|
`;
|
|
1163
|
-
})}
|
|
1164
|
-
` : this.leftTab === 'sections' ? html `
|
|
1165
|
-
${this.schema?.sections.map((s, i) => html `
|
|
1166
|
-
<div style="padding:8px 10px;margin:2px 4px;background:${i % 2 === 0 ? '#f8f9fa' : 'white'};border-radius:6px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:8px;border:1px solid #eee;transition:all 0.15s;"
|
|
1167
|
-
@click="${() => { }}"
|
|
1168
|
-
>
|
|
1169
|
-
<span style="background:#e3f2fd;color:#1976d2;font-weight:700;font-size:10px;padding:2px 6px;border-radius:4px;">§${i + 1}</span>
|
|
1170
|
-
<span style="flex:1;font-weight:500;">${s.title ?? 'Sin titulo'}</span>
|
|
1171
|
-
<span style="color:#aaa;font-size:10px;">${s.fields.length}</span>
|
|
1172
|
-
</div>
|
|
1173
|
-
`) ?? nothing}
|
|
1174
|
-
<button style="margin:8px 4px;padding:8px;width:calc(100% - 8px);border:1px dashed #ccc;border-radius:6px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;transition:all 0.15s;"
|
|
1269
|
+
})}
|
|
1270
|
+
` : this.leftTab === 'sections' ? html `
|
|
1271
|
+
${this.schema?.sections.map((s, i) => html `
|
|
1272
|
+
<div style="padding:8px 10px;margin:2px 4px;background:${i % 2 === 0 ? '#f8f9fa' : 'white'};border-radius:6px;font-size:12px;cursor:pointer;display:flex;align-items:center;gap:8px;border:1px solid #eee;transition:all 0.15s;"
|
|
1273
|
+
@click="${() => { }}"
|
|
1274
|
+
>
|
|
1275
|
+
<span style="background:#e3f2fd;color:#1976d2;font-weight:700;font-size:10px;padding:2px 6px;border-radius:4px;">§${i + 1}</span>
|
|
1276
|
+
<span style="flex:1;font-weight:500;">${s.title ?? 'Sin titulo'}</span>
|
|
1277
|
+
<span style="color:#aaa;font-size:10px;">${s.fields.length}</span>
|
|
1278
|
+
</div>
|
|
1279
|
+
`) ?? nothing}
|
|
1280
|
+
<button style="margin:8px 4px;padding:8px;width:calc(100% - 8px);border:1px dashed #ccc;border-radius:6px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;transition:all 0.15s;"
|
|
1175
1281
|
@click="${() => { if (this.schema) {
|
|
1176
1282
|
this.schema.sections.push({ id: 'section_' + Date.now(), title: 'Nueva Seccion', fields: [] });
|
|
1177
1283
|
this.commitChange();
|
|
1178
|
-
} }}"
|
|
1179
|
-
>+ Agregar Seccion</button>
|
|
1180
|
-
` : this.renderApiPanel()}
|
|
1181
|
-
</div>
|
|
1182
|
-
</div>
|
|
1284
|
+
} }}"
|
|
1285
|
+
>+ Agregar Seccion</button>
|
|
1286
|
+
` : this.renderApiPanel()}
|
|
1287
|
+
</div>
|
|
1288
|
+
</div>
|
|
1183
1289
|
`;
|
|
1184
1290
|
}
|
|
1185
1291
|
// ─── Canvas ───────────────────────────────────────
|
|
1186
1292
|
renderCanvas() {
|
|
1187
|
-
return html `
|
|
1188
|
-
<div class="canvas-area">
|
|
1293
|
+
return html `
|
|
1294
|
+
<div class="canvas-area">
|
|
1189
1295
|
${this.viewMode === 'json'
|
|
1190
1296
|
? html `<div class="json-panel" style="width:100%;max-width:800px;">${JSON.stringify(this.schema, null, 2)}</div>`
|
|
1191
1297
|
: this.viewMode === 'preview'
|
|
1192
1298
|
? html `<div class="canvas" style="transform:scale(${this.zoom});"><zentto-studio-renderer .schema="${this.schema}" .data="${this.data}"></zentto-studio-renderer></div>`
|
|
1193
|
-
: this.renderDesignCanvas()}
|
|
1194
|
-
</div>
|
|
1299
|
+
: this.renderDesignCanvas()}
|
|
1300
|
+
</div>
|
|
1195
1301
|
`;
|
|
1196
1302
|
}
|
|
1197
1303
|
renderDesignCanvas() {
|
|
1198
1304
|
if (!this.schema) {
|
|
1199
|
-
return html `<div class="drop-zone
|
|
1200
|
-
@dragover="${(e) => e.preventDefault()}"
|
|
1201
|
-
@drop="${(e) =>
|
|
1202
|
-
|
|
1203
|
-
this.dragType = null;
|
|
1204
|
-
} }}"
|
|
1205
|
-
>Arrastra un campo aqui para empezar</div>`;
|
|
1305
|
+
return html `<div class="drop-zone drop-zone--active" data-section-index="0" style="width:500px;height:200px;display:flex;align-items:center;justify-content:center;"
|
|
1306
|
+
@dragover="${(e) => e.preventDefault()}"
|
|
1307
|
+
@drop="${(e) => this.handleCanvasDrop(e, 0)}"
|
|
1308
|
+
>Arrastra campos del toolbox o de la API aqui</div>`;
|
|
1206
1309
|
}
|
|
1207
1310
|
const cols = this.schema.layout.columns ?? 1;
|
|
1208
|
-
return html `
|
|
1209
|
-
<div class="canvas" style="transform:scale(${this.zoom});" @click="${() => { this.selectedFieldId = null; }}">
|
|
1210
|
-
${this.schema.sections.map((section, si) => html `
|
|
1211
|
-
<div class="canvas-section">
|
|
1212
|
-
<div class="canvas-section-header" @click="${(e) => e.stopPropagation()}">
|
|
1213
|
-
<span style="font-size:10px;color:var(--zrd-accent);">§${si + 1}</span>
|
|
1214
|
-
${section.title ?? 'Seccion'}
|
|
1215
|
-
<span style="flex:1;"></span>
|
|
1216
|
-
<span style="font-size:10px;color:var(--zrd-text-muted);">${section.fields.length} campos</span>
|
|
1217
|
-
</div>
|
|
1218
|
-
<div class="section-drop-zone" data-section-index="${si}" data-position="top"></div>
|
|
1219
|
-
<div class="canvas-grid" data-section-index="${si}" style="grid-template-columns:repeat(${section.columns ?? cols}, 1fr);">
|
|
1220
|
-
${section.fields.map((field, fi) => this.renderCanvasField(field, si, fi, section.columns ?? cols))}
|
|
1221
|
-
</div>
|
|
1222
|
-
<div class="drop-zone ${this.dragType ? 'drop-zone--active' : ''}"
|
|
1223
|
-
data-section-index="${si}"
|
|
1224
|
-
@dragover="${(e) => e.preventDefault()}"
|
|
1225
|
-
@
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
</div>
|
|
1311
|
+
return html `
|
|
1312
|
+
<div class="canvas" style="transform:scale(${this.zoom});" @click="${() => { this.selectedFieldId = null; }}">
|
|
1313
|
+
${this.schema.sections.map((section, si) => html `
|
|
1314
|
+
<div class="canvas-section">
|
|
1315
|
+
<div class="canvas-section-header" @click="${(e) => e.stopPropagation()}">
|
|
1316
|
+
<span style="font-size:10px;color:var(--zrd-accent);">§${si + 1}</span>
|
|
1317
|
+
${section.title ?? 'Seccion'}
|
|
1318
|
+
<span style="flex:1;"></span>
|
|
1319
|
+
<span style="font-size:10px;color:var(--zrd-text-muted);">${section.fields.length} campos</span>
|
|
1320
|
+
</div>
|
|
1321
|
+
<div class="section-drop-zone" data-section-index="${si}" data-position="top"></div>
|
|
1322
|
+
<div class="canvas-grid" data-section-index="${si}" style="grid-template-columns:repeat(${section.columns ?? cols}, 1fr);">
|
|
1323
|
+
${section.fields.map((field, fi) => this.renderCanvasField(field, si, fi, section.columns ?? cols))}
|
|
1324
|
+
</div>
|
|
1325
|
+
<div class="drop-zone ${this.dragType ? 'drop-zone--active' : ''}"
|
|
1326
|
+
data-section-index="${si}"
|
|
1327
|
+
@dragover="${(e) => { e.preventDefault(); e.currentTarget.classList.add('drop-zone--active'); }}"
|
|
1328
|
+
@dragleave="${(e) => { if (!this.dragType)
|
|
1329
|
+
e.currentTarget.classList.remove('drop-zone--active'); }}"
|
|
1330
|
+
@drop="${(e) => this.handleCanvasDrop(e, si)}"
|
|
1331
|
+
>${this.dragType ? '↓ Soltar aqui' : '+ Arrastra campos o campos de API'}</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
`)}
|
|
1334
|
+
</div>
|
|
1233
1335
|
`;
|
|
1234
1336
|
}
|
|
1235
1337
|
renderCanvasField(field, si, fi, maxCols) {
|
|
@@ -1238,39 +1340,39 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1238
1340
|
const fullWidth = ['separator', 'heading', 'html', 'datagrid', 'report', 'chart'].includes(field.type);
|
|
1239
1341
|
const gridCol = fullWidth ? '1 / -1' : span > 1 ? `span ${span}` : '';
|
|
1240
1342
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
1241
|
-
return html `
|
|
1242
|
-
<div class="canvas-field ${isSelected ? 'canvas-field--selected' : ''}"
|
|
1243
|
-
style="${gridCol ? `grid-column:${gridCol};` : ''}"
|
|
1244
|
-
data-field-id="${field.id}"
|
|
1245
|
-
data-section-index="${si}"
|
|
1246
|
-
@click="${(e) => { e.stopPropagation(); this.selectedFieldId = field.id; }}"
|
|
1247
|
-
>
|
|
1248
|
-
<!-- Type badge -->
|
|
1249
|
-
<span class="field-type-badge" style="${typeColor}">${field.type}</span>
|
|
1250
|
-
|
|
1251
|
-
<!-- Action buttons -->
|
|
1252
|
-
<div class="field-actions">
|
|
1253
|
-
${fi > 0 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, -1); }}" title="Subir">↑</button>` : ''}
|
|
1254
|
-
${fi < (this.schema?.sections[si].fields.length ?? 0) - 1 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, 1); }}" title="Bajar">↓</button>` : ''}
|
|
1255
|
-
<button class="fa-btn fa-btn--copy" @click="${(e) => { e.stopPropagation(); this.duplicateField(si, fi); }}" title="Duplicar">⎘</button>
|
|
1256
|
-
<button class="fa-btn fa-btn--delete" @click="${(e) => { e.stopPropagation(); this.removeField(si, fi); }}" title="Eliminar">✕</button>
|
|
1257
|
-
</div>
|
|
1258
|
-
|
|
1259
|
-
<!-- Resize handles -->
|
|
1260
|
-
${isSelected ? html `
|
|
1261
|
-
<div class="rh rh-nw"></div><div class="rh rh-n"></div><div class="rh rh-ne"></div>
|
|
1262
|
-
<div class="rh rh-e"></div><div class="rh rh-se"></div><div class="rh rh-s"></div>
|
|
1263
|
-
<div class="rh rh-sw"></div><div class="rh rh-w"></div>
|
|
1264
|
-
` : ''}
|
|
1265
|
-
|
|
1266
|
-
<!-- Field preview -->
|
|
1267
|
-
<div class="field-preview">
|
|
1268
|
-
<div class="field-preview-label">${field.label ?? field.id}${field.required ? ' *' : ''}</div>
|
|
1269
|
-
<div class="field-preview-input ${this.getPreviewClass(field.type)}">
|
|
1270
|
-
${this.getPreviewContent(field)}
|
|
1271
|
-
</div>
|
|
1272
|
-
</div>
|
|
1273
|
-
</div>
|
|
1343
|
+
return html `
|
|
1344
|
+
<div class="canvas-field ${isSelected ? 'canvas-field--selected' : ''}"
|
|
1345
|
+
style="${gridCol ? `grid-column:${gridCol};` : ''}"
|
|
1346
|
+
data-field-id="${field.id}"
|
|
1347
|
+
data-section-index="${si}"
|
|
1348
|
+
@click="${(e) => { e.stopPropagation(); this.selectedFieldId = field.id; }}"
|
|
1349
|
+
>
|
|
1350
|
+
<!-- Type badge -->
|
|
1351
|
+
<span class="field-type-badge" style="${typeColor}">${field.type}</span>
|
|
1352
|
+
|
|
1353
|
+
<!-- Action buttons -->
|
|
1354
|
+
<div class="field-actions">
|
|
1355
|
+
${fi > 0 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, -1); }}" title="Subir">↑</button>` : ''}
|
|
1356
|
+
${fi < (this.schema?.sections[si].fields.length ?? 0) - 1 ? html `<button class="fa-btn fa-btn--move" @click="${(e) => { e.stopPropagation(); this.moveField(si, fi, 1); }}" title="Bajar">↓</button>` : ''}
|
|
1357
|
+
<button class="fa-btn fa-btn--copy" @click="${(e) => { e.stopPropagation(); this.duplicateField(si, fi); }}" title="Duplicar">⎘</button>
|
|
1358
|
+
<button class="fa-btn fa-btn--delete" @click="${(e) => { e.stopPropagation(); this.removeField(si, fi); }}" title="Eliminar">✕</button>
|
|
1359
|
+
</div>
|
|
1360
|
+
|
|
1361
|
+
<!-- Resize handles -->
|
|
1362
|
+
${isSelected ? html `
|
|
1363
|
+
<div class="rh rh-nw"></div><div class="rh rh-n"></div><div class="rh rh-ne"></div>
|
|
1364
|
+
<div class="rh rh-e"></div><div class="rh rh-se"></div><div class="rh rh-s"></div>
|
|
1365
|
+
<div class="rh rh-sw"></div><div class="rh rh-w"></div>
|
|
1366
|
+
` : ''}
|
|
1367
|
+
|
|
1368
|
+
<!-- Field preview -->
|
|
1369
|
+
<div class="field-preview">
|
|
1370
|
+
<div class="field-preview-label">${field.label ?? field.id}${field.required ? ' *' : ''}</div>
|
|
1371
|
+
<div class="field-preview-input ${this.getPreviewClass(field.type)}">
|
|
1372
|
+
${this.getPreviewContent(field)}
|
|
1373
|
+
</div>
|
|
1374
|
+
</div>
|
|
1375
|
+
</div>
|
|
1274
1376
|
`;
|
|
1275
1377
|
}
|
|
1276
1378
|
getPreviewClass(type) {
|
|
@@ -1323,227 +1425,215 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1323
1425
|
renderRightPanel() {
|
|
1324
1426
|
const field = this.selectedField;
|
|
1325
1427
|
if (!field) {
|
|
1326
|
-
return html `
|
|
1327
|
-
<div class="right-panel">
|
|
1328
|
-
<div class="props-empty">
|
|
1329
|
-
<div class="props-empty-icon">⬚</div>
|
|
1330
|
-
<div class="props-empty-text">Selecciona un campo<br/>para editar propiedades</div>
|
|
1331
|
-
</div>
|
|
1332
|
-
${this.schema ? this.renderFormProperties() : ''}
|
|
1333
|
-
</div>
|
|
1428
|
+
return html `
|
|
1429
|
+
<div class="right-panel">
|
|
1430
|
+
<div class="props-empty">
|
|
1431
|
+
<div class="props-empty-icon">⬚</div>
|
|
1432
|
+
<div class="props-empty-text">Selecciona un campo<br/>para editar propiedades</div>
|
|
1433
|
+
</div>
|
|
1434
|
+
${this.schema ? this.renderFormProperties() : ''}
|
|
1435
|
+
</div>
|
|
1334
1436
|
`;
|
|
1335
1437
|
}
|
|
1336
1438
|
const typeColor = FIELD_TYPE_COLORS[field.type] ?? 'background:#eceff1;color:#546e7a;';
|
|
1337
|
-
return html `
|
|
1338
|
-
<div class="right-panel">
|
|
1339
|
-
<!-- Header with type badge -->
|
|
1340
|
-
<div style="padding:8px 10px;border-bottom:1px solid #eee;">
|
|
1341
|
-
<span class="props-type-badge" style="${typeColor}">${field.type.toUpperCase()}</span>
|
|
1342
|
-
<div class="props-field-id">#${field.id}</div>
|
|
1343
|
-
</div>
|
|
1344
|
-
|
|
1345
|
-
<!-- General -->
|
|
1346
|
-
<div class="prop-section">
|
|
1347
|
-
<div class="prop-section-header" data-section="general" @click="${() => this.toggleSection('general')}">
|
|
1348
|
-
<span class="collapse-icon ${this.collapsedSections.has('general') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1349
|
-
<h4>General</h4>
|
|
1350
|
-
</div>
|
|
1351
|
-
${!this.collapsedSections.has('general') ? html `
|
|
1352
|
-
<div class="prop-row"><span class="prop-label">Label</span><input class="prop-input" .value="${field.label ?? ''}" @input="${(e) => { field.label = e.target.value; this.emitChange(); }}" /></div>
|
|
1353
|
-
<div class="prop-row"><span class="prop-label">Field</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.field}" @change="${(e) => { field.field = e.target.value; this.commitChange(); }}" /></div>
|
|
1354
|
-
<div class="prop-row"><span class="prop-label">Placeholder</span><input class="prop-input" .value="${field.placeholder ?? ''}" @input="${(e) => { field.placeholder = e.target.value; this.emitChange(); }}" /></div>
|
|
1355
|
-
<div class="prop-row"><span class="prop-label">Ayuda</span><input class="prop-input" .value="${field.helpText ?? ''}" @input="${(e) => { field.helpText = e.target.value; this.emitChange(); }}" /></div>
|
|
1356
|
-
` : ''}
|
|
1357
|
-
</div>
|
|
1358
|
-
|
|
1359
|
-
<!-- Layout (Figma position grid) -->
|
|
1360
|
-
<div class="prop-section">
|
|
1361
|
-
<div class="prop-section-header" data-section="layout" @click="${() => this.toggleSection('layout')}">
|
|
1362
|
-
<span class="collapse-icon ${this.collapsedSections.has('layout') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1363
|
-
<h4>Layout</h4>
|
|
1364
|
-
</div>
|
|
1365
|
-
${!this.collapsedSections.has('layout') ? html `
|
|
1366
|
-
<div class="prop-pos-grid">
|
|
1367
|
-
<div class="prop-pos-cell">
|
|
1368
|
-
<span class="prop-pos-label prop-pos-label--w">W</span>
|
|
1369
|
-
<input type="number" min="1" max="6" .value="${String(field.colSpan ?? 1)}" @change="${(e) => { field.colSpan = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1370
|
-
</div>
|
|
1371
|
-
<div class="prop-pos-cell">
|
|
1372
|
-
<span class="prop-pos-label prop-pos-label--h">T</span>
|
|
1373
|
-
<select .value="${field.type}" @change="${(e) => { field.type = e.target.value; this.commitChange(); }}" style="border:none;background:transparent;font-size:11px;width:100%;outline:none;color:var(--zrd-text);font-family:inherit;">
|
|
1374
|
-
${getAllFields().map(f => html `<option value="${f.type}" ?selected="${f.type === field.type}">${f.label}</option>`)}
|
|
1375
|
-
</select>
|
|
1376
|
-
</div>
|
|
1377
|
-
</div>
|
|
1378
|
-
<div class="prop-divider"></div>
|
|
1379
|
-
<div class="prop-row"><span class="prop-label">CSS Class</span><input class="prop-input" .value="${field.cssClass ?? ''}" placeholder="mi-clase" @input="${(e) => { field.cssClass = e.target.value; this.emitChange(); }}" /></div>
|
|
1380
|
-
` : ''}
|
|
1381
|
-
</div>
|
|
1382
|
-
|
|
1383
|
-
<!-- Style -->
|
|
1384
|
-
<div class="prop-section">
|
|
1385
|
-
<div class="prop-section-header" data-section="style" @click="${() => this.toggleSection('style')}">
|
|
1386
|
-
<span class="collapse-icon ${this.collapsedSections.has('style') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1387
|
-
<h4>Estilo</h4>
|
|
1388
|
-
</div>
|
|
1389
|
-
${!this.collapsedSections.has('style') ? html `
|
|
1390
|
-
<div class="prop-row">
|
|
1391
|
-
<span class="prop-label">Ancho</span>
|
|
1392
|
-
<div class="prop-segmented">
|
|
1393
|
-
${['auto', '100%', '50%'].map(w => html `
|
|
1394
|
-
<button class="prop-seg-btn ${(field.width ?? 'auto') === w ? 'prop-seg-btn--active' : ''}"
|
|
1395
|
-
@click="${() => { field.width = w === 'auto' ? undefined : w; this.commitChange(); }}"
|
|
1396
|
-
>${w}</button>
|
|
1397
|
-
`)}
|
|
1398
|
-
</div>
|
|
1399
|
-
</div>
|
|
1400
|
-
` : ''}
|
|
1401
|
-
</div>
|
|
1402
|
-
|
|
1403
|
-
<!-- Behavior (toggles) -->
|
|
1404
|
-
<div class="prop-section">
|
|
1405
|
-
<div class="prop-section-header" data-section="behavior" @click="${() => this.toggleSection('behavior')}">
|
|
1406
|
-
<span class="collapse-icon ${this.collapsedSections.has('behavior') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1407
|
-
<h4>Comportamiento</h4>
|
|
1408
|
-
</div>
|
|
1409
|
-
${!this.collapsedSections.has('behavior') ? html `
|
|
1410
|
-
${this.renderToggle('Requerido', field.required ?? false, (v) => { field.required = v; this.commitChange(); })}
|
|
1411
|
-
${this.renderToggle('Solo lectura', field.readOnly ?? false, (v) => { field.readOnly = v; this.commitChange(); })}
|
|
1412
|
-
${this.renderToggle('Deshabilitado', field.disabled ?? false, (v) => { field.disabled = v; this.commitChange(); })}
|
|
1413
|
-
${this.renderToggle('Oculto', field.hidden ?? false, (v) => { field.hidden = v; this.commitChange(); })}
|
|
1414
|
-
` : ''}
|
|
1415
|
-
</div>
|
|
1416
|
-
|
|
1417
|
-
<!-- Rules (expressions) -->
|
|
1418
|
-
<div class="prop-section">
|
|
1419
|
-
<div class="prop-section-header" data-section="rules" @click="${() => this.toggleSection('rules')}">
|
|
1420
|
-
<span class="collapse-icon ${this.collapsedSections.has('rules') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1421
|
-
<h4>Reglas</h4>
|
|
1422
|
-
</div>
|
|
1423
|
-
${!this.collapsedSections.has('rules') ? html `
|
|
1424
|
-
<div class="prop-info">💡 Usa {campo} para referencias y expresiones</div>
|
|
1425
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Condicion de visibilidad</span></div>
|
|
1426
|
-
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.visibilityRule ?? ''}" placeholder='{tipo} == "empresa" AND {activo} == true' @change="${(e) => { field.visibilityRule = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1427
|
-
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Valor computado</span></div>
|
|
1428
|
-
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.computedValue ?? ''}" placeholder='{precio} * {cantidad} * (1 + {iva}/100)' @change="${(e) => { field.computedValue = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1429
|
-
<div class="prop-divider"></div>
|
|
1430
|
-
<div class="prop-row"><span class="prop-label">Default</span><input class="prop-input" .value="${String(field.defaultValue ?? '')}" @change="${(e) => { field.defaultValue = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1431
|
-
` : ''}
|
|
1432
|
-
</div>
|
|
1433
|
-
|
|
1434
|
-
<!-- Data Source (API connection like report-designer) -->
|
|
1435
|
-
<div class="prop-section">
|
|
1436
|
-
<div class="prop-section-header" data-section="datasource" @click="${() => this.toggleSection('datasource')}">
|
|
1437
|
-
<span class="collapse-icon ${this.collapsedSections.has('datasource') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1438
|
-
<h4>Origen de Datos</h4>
|
|
1439
|
-
</div>
|
|
1440
|
-
${!this.collapsedSections.has('datasource') ? html `
|
|
1441
|
-
<div class="prop-info">🔌 Conecta campos a APIs y endpoints</div>
|
|
1439
|
+
return html `
|
|
1440
|
+
<div class="right-panel">
|
|
1441
|
+
<!-- Header with type badge -->
|
|
1442
|
+
<div style="padding:8px 10px;border-bottom:1px solid #eee;">
|
|
1443
|
+
<span class="props-type-badge" style="${typeColor}">${field.type.toUpperCase()}</span>
|
|
1444
|
+
<div class="props-field-id">#${field.id}</div>
|
|
1445
|
+
</div>
|
|
1446
|
+
|
|
1447
|
+
<!-- General -->
|
|
1448
|
+
<div class="prop-section">
|
|
1449
|
+
<div class="prop-section-header" data-section="general" @click="${() => this.toggleSection('general')}">
|
|
1450
|
+
<span class="collapse-icon ${this.collapsedSections.has('general') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1451
|
+
<h4>General</h4>
|
|
1452
|
+
</div>
|
|
1453
|
+
${!this.collapsedSections.has('general') ? html `
|
|
1454
|
+
<div class="prop-row"><span class="prop-label">Label</span><input class="prop-input" .value="${field.label ?? ''}" @input="${(e) => { field.label = e.target.value; this.emitChange(); }}" /></div>
|
|
1455
|
+
<div class="prop-row"><span class="prop-label">Field</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.field}" @change="${(e) => { field.field = e.target.value; this.commitChange(); }}" /></div>
|
|
1456
|
+
<div class="prop-row"><span class="prop-label">Placeholder</span><input class="prop-input" .value="${field.placeholder ?? ''}" @input="${(e) => { field.placeholder = e.target.value; this.emitChange(); }}" /></div>
|
|
1457
|
+
<div class="prop-row"><span class="prop-label">Ayuda</span><input class="prop-input" .value="${field.helpText ?? ''}" @input="${(e) => { field.helpText = e.target.value; this.emitChange(); }}" /></div>
|
|
1458
|
+
` : ''}
|
|
1459
|
+
</div>
|
|
1460
|
+
|
|
1461
|
+
<!-- Layout (Figma position grid) -->
|
|
1462
|
+
<div class="prop-section">
|
|
1463
|
+
<div class="prop-section-header" data-section="layout" @click="${() => this.toggleSection('layout')}">
|
|
1464
|
+
<span class="collapse-icon ${this.collapsedSections.has('layout') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1465
|
+
<h4>Layout</h4>
|
|
1466
|
+
</div>
|
|
1467
|
+
${!this.collapsedSections.has('layout') ? html `
|
|
1468
|
+
<div class="prop-pos-grid">
|
|
1469
|
+
<div class="prop-pos-cell">
|
|
1470
|
+
<span class="prop-pos-label prop-pos-label--w">W</span>
|
|
1471
|
+
<input type="number" min="1" max="6" .value="${String(field.colSpan ?? 1)}" @change="${(e) => { field.colSpan = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1472
|
+
</div>
|
|
1473
|
+
<div class="prop-pos-cell">
|
|
1474
|
+
<span class="prop-pos-label prop-pos-label--h">T</span>
|
|
1475
|
+
<select .value="${field.type}" @change="${(e) => { field.type = e.target.value; this.commitChange(); }}" style="border:none;background:transparent;font-size:11px;width:100%;outline:none;color:var(--zrd-text);font-family:inherit;">
|
|
1476
|
+
${getAllFields().map(f => html `<option value="${f.type}" ?selected="${f.type === field.type}">${f.label}</option>`)}
|
|
1477
|
+
</select>
|
|
1478
|
+
</div>
|
|
1479
|
+
</div>
|
|
1480
|
+
<div class="prop-divider"></div>
|
|
1481
|
+
<div class="prop-row"><span class="prop-label">CSS Class</span><input class="prop-input" .value="${field.cssClass ?? ''}" placeholder="mi-clase" @input="${(e) => { field.cssClass = e.target.value; this.emitChange(); }}" /></div>
|
|
1482
|
+
` : ''}
|
|
1483
|
+
</div>
|
|
1484
|
+
|
|
1485
|
+
<!-- Style -->
|
|
1486
|
+
<div class="prop-section">
|
|
1487
|
+
<div class="prop-section-header" data-section="style" @click="${() => this.toggleSection('style')}">
|
|
1488
|
+
<span class="collapse-icon ${this.collapsedSections.has('style') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1489
|
+
<h4>Estilo</h4>
|
|
1490
|
+
</div>
|
|
1491
|
+
${!this.collapsedSections.has('style') ? html `
|
|
1492
|
+
<div class="prop-row">
|
|
1493
|
+
<span class="prop-label">Ancho</span>
|
|
1494
|
+
<div class="prop-segmented">
|
|
1495
|
+
${['auto', '100%', '50%'].map(w => html `
|
|
1496
|
+
<button class="prop-seg-btn ${(field.width ?? 'auto') === w ? 'prop-seg-btn--active' : ''}"
|
|
1497
|
+
@click="${() => { field.width = w === 'auto' ? undefined : w; this.commitChange(); }}"
|
|
1498
|
+
>${w}</button>
|
|
1499
|
+
`)}
|
|
1500
|
+
</div>
|
|
1501
|
+
</div>
|
|
1502
|
+
` : ''}
|
|
1503
|
+
</div>
|
|
1504
|
+
|
|
1505
|
+
<!-- Behavior (toggles) -->
|
|
1506
|
+
<div class="prop-section">
|
|
1507
|
+
<div class="prop-section-header" data-section="behavior" @click="${() => this.toggleSection('behavior')}">
|
|
1508
|
+
<span class="collapse-icon ${this.collapsedSections.has('behavior') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1509
|
+
<h4>Comportamiento</h4>
|
|
1510
|
+
</div>
|
|
1511
|
+
${!this.collapsedSections.has('behavior') ? html `
|
|
1512
|
+
${this.renderToggle('Requerido', field.required ?? false, (v) => { field.required = v; this.commitChange(); })}
|
|
1513
|
+
${this.renderToggle('Solo lectura', field.readOnly ?? false, (v) => { field.readOnly = v; this.commitChange(); })}
|
|
1514
|
+
${this.renderToggle('Deshabilitado', field.disabled ?? false, (v) => { field.disabled = v; this.commitChange(); })}
|
|
1515
|
+
${this.renderToggle('Oculto', field.hidden ?? false, (v) => { field.hidden = v; this.commitChange(); })}
|
|
1516
|
+
` : ''}
|
|
1517
|
+
</div>
|
|
1518
|
+
|
|
1519
|
+
<!-- Rules (expressions) -->
|
|
1520
|
+
<div class="prop-section">
|
|
1521
|
+
<div class="prop-section-header" data-section="rules" @click="${() => this.toggleSection('rules')}">
|
|
1522
|
+
<span class="collapse-icon ${this.collapsedSections.has('rules') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1523
|
+
<h4>Reglas</h4>
|
|
1524
|
+
</div>
|
|
1525
|
+
${!this.collapsedSections.has('rules') ? html `
|
|
1526
|
+
<div class="prop-info">💡 Usa {campo} para referencias y expresiones</div>
|
|
1527
|
+
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Condicion de visibilidad</span></div>
|
|
1528
|
+
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.visibilityRule ?? ''}" placeholder='{tipo} == "empresa" AND {activo} == true' @change="${(e) => { field.visibilityRule = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1529
|
+
<div class="prop-row prop-row-full"><span class="prop-label" style="margin-bottom:2px;">Valor computado</span></div>
|
|
1530
|
+
<div class="prop-row prop-row-full"><textarea class="prop-input" rows="2" .value="${field.computedValue ?? ''}" placeholder='{precio} * {cantidad} * (1 + {iva}/100)' @change="${(e) => { field.computedValue = e.target.value || undefined; this.commitChange(); }}"></textarea></div>
|
|
1531
|
+
<div class="prop-divider"></div>
|
|
1532
|
+
<div class="prop-row"><span class="prop-label">Default</span><input class="prop-input" .value="${String(field.defaultValue ?? '')}" @change="${(e) => { field.defaultValue = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1533
|
+
` : ''}
|
|
1534
|
+
</div>
|
|
1535
|
+
|
|
1536
|
+
<!-- Data Source (API connection like report-designer) -->
|
|
1537
|
+
<div class="prop-section">
|
|
1538
|
+
<div class="prop-section-header" data-section="datasource" @click="${() => this.toggleSection('datasource')}">
|
|
1539
|
+
<span class="collapse-icon ${this.collapsedSections.has('datasource') ? 'collapse-icon--collapsed' : ''}">▾</span>
|
|
1540
|
+
<h4>Origen de Datos</h4>
|
|
1541
|
+
</div>
|
|
1542
|
+
${!this.collapsedSections.has('datasource') ? html `
|
|
1543
|
+
<div class="prop-info">🔌 Conecta campos a APIs y endpoints</div>
|
|
1442
1544
|
<div class="prop-row"><span class="prop-label">Endpoint</span><input class="prop-input" style="font-family:'SF Mono','Consolas',monospace;font-size:10px;" .value="${field.props?.endpoint ?? ''}" placeholder="/v1/clientes" @change="${(e) => { if (!field.props)
|
|
1443
|
-
field.props = {}; field.props.endpoint = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1545
|
+
field.props = {}; field.props.endpoint = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1444
1546
|
<div class="prop-row"><span class="prop-label">Campo valor</span><input class="prop-input" .value="${field.props?.valueField ?? ''}" placeholder="id" @change="${(e) => { if (!field.props)
|
|
1445
|
-
field.props = {}; field.props.valueField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1547
|
+
field.props = {}; field.props.valueField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1446
1548
|
<div class="prop-row"><span class="prop-label">Campo label</span><input class="prop-input" .value="${field.props?.displayField ?? ''}" placeholder="nombre" @change="${(e) => { if (!field.props)
|
|
1447
|
-
field.props = {}; field.props.displayField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1448
|
-
${field.type === 'datagrid' || field.type === 'select' || field.type === 'lookup' ? html `
|
|
1449
|
-
<div class="prop-divider"></div>
|
|
1549
|
+
field.props = {}; field.props.displayField = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1550
|
+
${field.type === 'datagrid' || field.type === 'select' || field.type === 'lookup' ? html `
|
|
1551
|
+
<div class="prop-divider"></div>
|
|
1450
1552
|
<div class="prop-row"><span class="prop-label">Data Source</span><input class="prop-input" .value="${field.props?.dataSourceId ?? ''}" placeholder="clientesList" @change="${(e) => { if (!field.props)
|
|
1451
|
-
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1452
|
-
` : ''}
|
|
1453
|
-
` : ''}
|
|
1454
|
-
</div>
|
|
1455
|
-
</div>
|
|
1553
|
+
field.props = {}; field.props.dataSourceId = e.target.value || undefined; this.commitChange(); }}" /></div>
|
|
1554
|
+
` : ''}
|
|
1555
|
+
` : ''}
|
|
1556
|
+
</div>
|
|
1557
|
+
</div>
|
|
1456
1558
|
`;
|
|
1457
1559
|
}
|
|
1458
1560
|
renderFormProperties() {
|
|
1459
1561
|
if (!this.schema)
|
|
1460
1562
|
return nothing;
|
|
1461
|
-
return html `
|
|
1462
|
-
<div class="prop-section">
|
|
1463
|
-
<div class="prop-section-header" data-section="form">
|
|
1464
|
-
<span class="collapse-icon">▾</span>
|
|
1465
|
-
<h4>Formulario</h4>
|
|
1466
|
-
</div>
|
|
1467
|
-
<div class="prop-row"><span class="prop-label">Titulo</span><input class="prop-input" .value="${this.schema.title}" @input="${(e) => { this.schema.title = e.target.value; this.emitChange(); }}" /></div>
|
|
1468
|
-
<div class="prop-divider"></div>
|
|
1469
|
-
<div class="prop-pos-grid">
|
|
1470
|
-
<div class="prop-pos-cell">
|
|
1471
|
-
<span class="prop-pos-label prop-pos-label--w">C</span>
|
|
1472
|
-
<input type="number" min="1" max="6" .value="${String(this.schema.layout.columns ?? 1)}" @change="${(e) => { this.schema.layout.columns = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1473
|
-
</div>
|
|
1474
|
-
<div class="prop-pos-cell">
|
|
1475
|
-
<span class="prop-pos-label prop-pos-label--y">G</span>
|
|
1476
|
-
<input type="number" min="0" .value="${String(this.schema.layout.gap ?? 16)}" @change="${(e) => { this.schema.layout.gap = parseInt(e.target.value) || 16; this.commitChange(); }}" />
|
|
1477
|
-
</div>
|
|
1478
|
-
</div>
|
|
1479
|
-
<div style="font-size:9px;color:#bbb;margin-top:4px;display:flex;gap:12px;">
|
|
1480
|
-
<span>C = Columnas</span><span>G = Gap (px)</span>
|
|
1481
|
-
</div>
|
|
1482
|
-
</div>
|
|
1563
|
+
return html `
|
|
1564
|
+
<div class="prop-section">
|
|
1565
|
+
<div class="prop-section-header" data-section="form">
|
|
1566
|
+
<span class="collapse-icon">▾</span>
|
|
1567
|
+
<h4>Formulario</h4>
|
|
1568
|
+
</div>
|
|
1569
|
+
<div class="prop-row"><span class="prop-label">Titulo</span><input class="prop-input" .value="${this.schema.title}" @input="${(e) => { this.schema.title = e.target.value; this.emitChange(); }}" /></div>
|
|
1570
|
+
<div class="prop-divider"></div>
|
|
1571
|
+
<div class="prop-pos-grid">
|
|
1572
|
+
<div class="prop-pos-cell">
|
|
1573
|
+
<span class="prop-pos-label prop-pos-label--w">C</span>
|
|
1574
|
+
<input type="number" min="1" max="6" .value="${String(this.schema.layout.columns ?? 1)}" @change="${(e) => { this.schema.layout.columns = parseInt(e.target.value) || 1; this.commitChange(); }}" />
|
|
1575
|
+
</div>
|
|
1576
|
+
<div class="prop-pos-cell">
|
|
1577
|
+
<span class="prop-pos-label prop-pos-label--y">G</span>
|
|
1578
|
+
<input type="number" min="0" .value="${String(this.schema.layout.gap ?? 16)}" @change="${(e) => { this.schema.layout.gap = parseInt(e.target.value) || 16; this.commitChange(); }}" />
|
|
1579
|
+
</div>
|
|
1580
|
+
</div>
|
|
1581
|
+
<div style="font-size:9px;color:#bbb;margin-top:4px;display:flex;gap:12px;">
|
|
1582
|
+
<span>C = Columnas</span><span>G = Gap (px)</span>
|
|
1583
|
+
</div>
|
|
1584
|
+
</div>
|
|
1483
1585
|
`;
|
|
1484
1586
|
}
|
|
1485
1587
|
renderApiPanel() {
|
|
1486
|
-
return html `
|
|
1487
|
-
<div style="padding:4px;">
|
|
1488
|
-
<!-- Auth Section -->
|
|
1489
|
-
${this.renderApiAuth()}
|
|
1490
|
-
|
|
1491
|
-
<!-- Quick connect (only when logged in or no auth needed) -->
|
|
1492
|
-
<div style="padding:4px 8px;margin-top:4px;">
|
|
1493
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Conectar Endpoint</div>
|
|
1494
|
-
<div style="display:flex;gap:4px;margin-bottom:6px;">
|
|
1495
|
-
<select id="api-method" style="width:70px;padding:4px 6px;border:1px solid #ddd;border-radius:4px;font-size:10px;background:white;font-family:inherit;">
|
|
1496
|
-
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
|
|
1497
|
-
</select>
|
|
1498
|
-
<input id="api-url" style="flex:1;padding:4px 8px;border:1px solid #ddd;border-radius:4px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:#fafafa;" placeholder="/v1/clientes" />
|
|
1499
|
-
</div>
|
|
1500
|
-
<button style="width:100%;padding:6px;border:1px solid #ea580c;border-radius:5px;background:#fff7ed;color:#ea580c;cursor:pointer;font-size:11px;font-weight:600;font-family:inherit;transition:all 0.15s;"
|
|
1501
|
-
@click="${this.fetchApiFields}"
|
|
1502
|
-
>${this.apiLoading ? 'Cargando...' : '🔌 Probar Conexion'}</button>
|
|
1503
|
-
</div>
|
|
1504
|
-
|
|
1505
|
-
<!-- Fetched sources -->
|
|
1506
|
-
${this.apiSources.length > 0 ? html `
|
|
1507
|
-
<div style="margin-top:8px;">
|
|
1508
|
-
${this.apiSources.map(src => html `
|
|
1509
|
-
<div style="margin:4px;border:1px solid #eee;border-radius:6px;overflow:hidden;">
|
|
1510
|
-
<div style="padding:6px 8px;background:#f8f9fa;display:flex;align-items:center;gap:6px;border-bottom:1px solid #eee;">
|
|
1511
|
-
<span style="background:#ea580c;color:white;font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;">${src.method}</span>
|
|
1512
|
-
<span style="font-size:10px;font-weight:600;color:#333;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${src.name}</span>
|
|
1513
|
-
<span style="font-size:9px;color:#aaa;">${src.fields.length} campos</span>
|
|
1514
|
-
</div>
|
|
1515
|
-
<div style="padding:2px 4px;max-height:150px;overflow-y:auto;">
|
|
1516
|
-
${src.fields.map(field => html `
|
|
1517
|
-
<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;font-size:11px;cursor:grab;border-radius:3px;transition:background 0.1s;"
|
|
1518
|
-
draggable="true"
|
|
1519
|
-
@dragstart="${(e) => { this.dragType = 'text'; e.dataTransfer?.setData('text/plain', 'text'); e.dataTransfer?.setData('field-name', field); e.dataTransfer?.setData('ds-id', src.id); }}"
|
|
1520
|
-
@dblclick="${() =>
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
<span style="color:#555;">${field}</span>
|
|
1536
|
-
</div>
|
|
1537
|
-
`)}
|
|
1538
|
-
</div>
|
|
1539
|
-
</div>
|
|
1540
|
-
`)}
|
|
1541
|
-
</div>
|
|
1542
|
-
` : ''}
|
|
1543
|
-
|
|
1544
|
-
<!-- Zentto API shortcuts -->
|
|
1545
|
-
<div style="margin-top:12px;padding:4px 8px;">
|
|
1546
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:6px;">Accesos Rapidos Zentto</div>
|
|
1588
|
+
return html `
|
|
1589
|
+
<div style="padding:4px;">
|
|
1590
|
+
<!-- Auth Section -->
|
|
1591
|
+
${this.renderApiAuth()}
|
|
1592
|
+
|
|
1593
|
+
<!-- Quick connect (only when logged in or no auth needed) -->
|
|
1594
|
+
<div style="padding:4px 8px;margin-top:4px;">
|
|
1595
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Conectar Endpoint</div>
|
|
1596
|
+
<div style="display:flex;gap:4px;margin-bottom:6px;">
|
|
1597
|
+
<select id="api-method" style="width:70px;padding:4px 6px;border:1px solid #ddd;border-radius:4px;font-size:10px;background:white;font-family:inherit;">
|
|
1598
|
+
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
|
|
1599
|
+
</select>
|
|
1600
|
+
<input id="api-url" style="flex:1;padding:4px 8px;border:1px solid #ddd;border-radius:4px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:#fafafa;" placeholder="/v1/clientes" />
|
|
1601
|
+
</div>
|
|
1602
|
+
<button style="width:100%;padding:6px;border:1px solid #ea580c;border-radius:5px;background:#fff7ed;color:#ea580c;cursor:pointer;font-size:11px;font-weight:600;font-family:inherit;transition:all 0.15s;"
|
|
1603
|
+
@click="${this.fetchApiFields}"
|
|
1604
|
+
>${this.apiLoading ? 'Cargando...' : '🔌 Probar Conexion'}</button>
|
|
1605
|
+
</div>
|
|
1606
|
+
|
|
1607
|
+
<!-- Fetched sources -->
|
|
1608
|
+
${this.apiSources.length > 0 ? html `
|
|
1609
|
+
<div style="margin-top:8px;">
|
|
1610
|
+
${this.apiSources.map(src => html `
|
|
1611
|
+
<div style="margin:4px;border:1px solid #eee;border-radius:6px;overflow:hidden;">
|
|
1612
|
+
<div style="padding:6px 8px;background:#f8f9fa;display:flex;align-items:center;gap:6px;border-bottom:1px solid #eee;">
|
|
1613
|
+
<span style="background:#ea580c;color:white;font-size:8px;font-weight:700;padding:1px 5px;border-radius:3px;">${src.method}</span>
|
|
1614
|
+
<span style="font-size:10px;font-weight:600;color:#333;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${src.name}</span>
|
|
1615
|
+
<span style="font-size:9px;color:#aaa;">${src.fields.length} campos</span>
|
|
1616
|
+
</div>
|
|
1617
|
+
<div style="padding:2px 4px;max-height:150px;overflow-y:auto;">
|
|
1618
|
+
${src.fields.map(field => html `
|
|
1619
|
+
<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;font-size:11px;cursor:grab;border-radius:3px;transition:background 0.1s;"
|
|
1620
|
+
draggable="true"
|
|
1621
|
+
@dragstart="${(e) => { this.dragType = 'text'; e.dataTransfer?.setData('text/plain', 'text'); e.dataTransfer?.setData('field-name', field); e.dataTransfer?.setData('ds-id', src.id); }}"
|
|
1622
|
+
@dblclick="${() => this.addApiField(field, src.id)}"
|
|
1623
|
+
>
|
|
1624
|
+
<span style="color:#1976d2;font-size:10px;">⬡</span>
|
|
1625
|
+
<span style="color:#555;">${field}</span>
|
|
1626
|
+
</div>
|
|
1627
|
+
`)}
|
|
1628
|
+
</div>
|
|
1629
|
+
</div>
|
|
1630
|
+
`)}
|
|
1631
|
+
</div>
|
|
1632
|
+
` : ''}
|
|
1633
|
+
|
|
1634
|
+
<!-- Zentto API shortcuts -->
|
|
1635
|
+
<div style="margin-top:12px;padding:4px 8px;">
|
|
1636
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:6px;">Accesos Rapidos Zentto</div>
|
|
1547
1637
|
${[
|
|
1548
1638
|
{ label: 'Clientes', endpoint: '/v1/clientes', icon: '👥' },
|
|
1549
1639
|
{ label: 'Articulos', endpoint: '/v1/articulos', icon: '📦' },
|
|
@@ -1553,34 +1643,34 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1553
1643
|
{ label: 'Proveedores', endpoint: '/v1/proveedores', icon: '🏭' },
|
|
1554
1644
|
{ label: 'Bancos', endpoint: '/v1/bancos', icon: '🏦' },
|
|
1555
1645
|
{ label: 'Paises', endpoint: '/v1/config/countries', icon: '🌍' },
|
|
1556
|
-
].map(api => html `
|
|
1557
|
-
<div style="display:flex;align-items:center;gap:8px;padding:6px 8px;margin:2px 0;border-radius:5px;cursor:pointer;font-size:11px;transition:all 0.15s;border:1px solid transparent;"
|
|
1646
|
+
].map(api => html `
|
|
1647
|
+
<div style="display:flex;align-items:center;gap:8px;padding:6px 8px;margin:2px 0;border-radius:5px;cursor:pointer;font-size:11px;transition:all 0.15s;border:1px solid transparent;"
|
|
1558
1648
|
@click="${() => {
|
|
1559
1649
|
const urlInput = this.shadowRoot?.querySelector('#api-url');
|
|
1560
1650
|
if (urlInput)
|
|
1561
1651
|
urlInput.value = api.endpoint;
|
|
1562
|
-
}}"
|
|
1563
|
-
@mouseenter="${(e) => { e.target.style.background = '#fff7ed'; e.target.style.borderColor = '#fed7aa'; }}"
|
|
1564
|
-
@mouseleave="${(e) => { e.target.style.background = ''; e.target.style.borderColor = 'transparent'; }}"
|
|
1565
|
-
>
|
|
1566
|
-
<span style="font-size:14px;">${api.icon}</span>
|
|
1567
|
-
<span style="flex:1;color:#555;">${api.label}</span>
|
|
1568
|
-
<span style="font-size:9px;color:#bbb;font-family:'SF Mono','Consolas',monospace;">${api.endpoint}</span>
|
|
1569
|
-
</div>
|
|
1570
|
-
`)}
|
|
1571
|
-
</div>
|
|
1572
|
-
|
|
1573
|
-
<!-- Manual data source -->
|
|
1574
|
-
<div style="margin-top:8px;padding:4px 8px;">
|
|
1575
|
-
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Datos Estaticos</div>
|
|
1576
|
-
<button style="width:100%;padding:6px;border:1px dashed #ccc;border-radius:5px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;"
|
|
1652
|
+
}}"
|
|
1653
|
+
@mouseenter="${(e) => { e.target.style.background = '#fff7ed'; e.target.style.borderColor = '#fed7aa'; }}"
|
|
1654
|
+
@mouseleave="${(e) => { e.target.style.background = ''; e.target.style.borderColor = 'transparent'; }}"
|
|
1655
|
+
>
|
|
1656
|
+
<span style="font-size:14px;">${api.icon}</span>
|
|
1657
|
+
<span style="flex:1;color:#555;">${api.label}</span>
|
|
1658
|
+
<span style="font-size:9px;color:#bbb;font-family:'SF Mono','Consolas',monospace;">${api.endpoint}</span>
|
|
1659
|
+
</div>
|
|
1660
|
+
`)}
|
|
1661
|
+
</div>
|
|
1662
|
+
|
|
1663
|
+
<!-- Manual data source -->
|
|
1664
|
+
<div style="margin-top:8px;padding:4px 8px;">
|
|
1665
|
+
<div style="font-size:9px;font-weight:600;color:#aaa;text-transform:uppercase;margin-bottom:4px;">Datos Estaticos</div>
|
|
1666
|
+
<button style="width:100%;padding:6px;border:1px dashed #ccc;border-radius:5px;background:none;cursor:pointer;font-size:11px;color:#888;font-family:inherit;"
|
|
1577
1667
|
@click="${() => {
|
|
1578
1668
|
const src = { id: 'static_' + Date.now(), name: 'Datos Manuales', endpoint: '', method: 'STATIC', fields: ['campo1', 'campo2', 'campo3'] };
|
|
1579
1669
|
this.apiSources = [...this.apiSources, src];
|
|
1580
|
-
}}"
|
|
1581
|
-
>+ Agregar Datos Estaticos</button>
|
|
1582
|
-
</div>
|
|
1583
|
-
</div>
|
|
1670
|
+
}}"
|
|
1671
|
+
>+ Agregar Datos Estaticos</button>
|
|
1672
|
+
</div>
|
|
1673
|
+
</div>
|
|
1584
1674
|
`;
|
|
1585
1675
|
}
|
|
1586
1676
|
// ─── Template Menu ─────────────────────────────────
|
|
@@ -1691,13 +1781,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1691
1781
|
], actions: [{ id: 'send', type: 'submit', label: 'Enviar Encuesta', variant: 'primary' }] },
|
|
1692
1782
|
},
|
|
1693
1783
|
];
|
|
1694
|
-
return html `
|
|
1695
|
-
<div style="position:absolute;top:100%;left:0;z-index:100;background:white;border:1px solid #ddd;border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,0.15);width:320px;max-height:400px;overflow-y:auto;margin-top:4px;">
|
|
1696
|
-
<div style="padding:10px 12px;border-bottom:1px solid #eee;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:0.5px;">
|
|
1697
|
-
Cargar Plantilla
|
|
1698
|
-
</div>
|
|
1699
|
-
${templates.map(t => html `
|
|
1700
|
-
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 14px;cursor:pointer;transition:background 0.1s;border-bottom:1px solid #f5f5f5;"
|
|
1784
|
+
return html `
|
|
1785
|
+
<div style="position:absolute;top:100%;left:0;z-index:100;background:white;border:1px solid #ddd;border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,0.15);width:320px;max-height:400px;overflow-y:auto;margin-top:4px;">
|
|
1786
|
+
<div style="padding:10px 12px;border-bottom:1px solid #eee;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:0.5px;">
|
|
1787
|
+
Cargar Plantilla
|
|
1788
|
+
</div>
|
|
1789
|
+
${templates.map(t => html `
|
|
1790
|
+
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 14px;cursor:pointer;transition:background 0.1s;border-bottom:1px solid #f5f5f5;"
|
|
1701
1791
|
@click="${() => {
|
|
1702
1792
|
this.schema = structuredClone(t.schema);
|
|
1703
1793
|
this.undoStack = [JSON.stringify(this.schema)];
|
|
@@ -1705,116 +1795,116 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1705
1795
|
this.selectedFieldId = null;
|
|
1706
1796
|
this.showTemplateMenu = false;
|
|
1707
1797
|
this.commitChange();
|
|
1708
|
-
}}"
|
|
1709
|
-
@mouseenter="${(e) => { e.currentTarget.style.background = '#f0f7ff'; }}"
|
|
1710
|
-
@mouseleave="${(e) => { e.currentTarget.style.background = ''; }}"
|
|
1711
|
-
>
|
|
1712
|
-
<span style="font-size:22px;margin-top:1px;">${t.icon}</span>
|
|
1713
|
-
<div>
|
|
1714
|
-
<div style="font-size:13px;font-weight:600;color:#333;">${t.title}</div>
|
|
1715
|
-
<div style="font-size:11px;color:#999;margin-top:1px;">${t.desc}</div>
|
|
1716
|
-
</div>
|
|
1717
|
-
</div>
|
|
1718
|
-
`)}
|
|
1719
|
-
<div style="padding:8px 14px;border-top:1px solid #eee;">
|
|
1720
|
-
<div style="font-size:10px;color:#bbb;text-align:center;">Clic en una plantilla para cargarla</div>
|
|
1721
|
-
</div>
|
|
1722
|
-
</div>
|
|
1798
|
+
}}"
|
|
1799
|
+
@mouseenter="${(e) => { e.currentTarget.style.background = '#f0f7ff'; }}"
|
|
1800
|
+
@mouseleave="${(e) => { e.currentTarget.style.background = ''; }}"
|
|
1801
|
+
>
|
|
1802
|
+
<span style="font-size:22px;margin-top:1px;">${t.icon}</span>
|
|
1803
|
+
<div>
|
|
1804
|
+
<div style="font-size:13px;font-weight:600;color:#333;">${t.title}</div>
|
|
1805
|
+
<div style="font-size:11px;color:#999;margin-top:1px;">${t.desc}</div>
|
|
1806
|
+
</div>
|
|
1807
|
+
</div>
|
|
1808
|
+
`)}
|
|
1809
|
+
<div style="padding:8px 14px;border-top:1px solid #eee;">
|
|
1810
|
+
<div style="font-size:10px;color:#bbb;text-align:center;">Clic en una plantilla para cargarla</div>
|
|
1811
|
+
</div>
|
|
1812
|
+
</div>
|
|
1723
1813
|
`;
|
|
1724
1814
|
}
|
|
1725
1815
|
renderApiAuth() {
|
|
1726
1816
|
if (this.apiLoggedIn) {
|
|
1727
|
-
return html `
|
|
1728
|
-
<div style="margin:4px;padding:10px;background:linear-gradient(135deg,#e8f5e9,#f1f8e9);border:1px solid #c8e6c9;border-radius:8px;">
|
|
1729
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
1730
|
-
<span style="width:28px;height:28px;border-radius:50%;background:#27ae60;color:white;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;">✓</span>
|
|
1731
|
-
<div style="flex:1;">
|
|
1732
|
-
<div style="font-size:11px;font-weight:600;color:#2e7d32;">Conectado</div>
|
|
1733
|
-
<div style="font-size:10px;color:#66bb6a;">${this.apiUser}${this.apiCompany ? ` — ${this.apiCompany}` : ''}</div>
|
|
1734
|
-
</div>
|
|
1735
|
-
<button style="border:none;background:none;cursor:pointer;font-size:14px;color:#999;padding:2px;" title="Cerrar sesion"
|
|
1736
|
-
@click="${() => { this.apiLoggedIn = false; this.apiToken = ''; this.apiUser = ''; this.apiCompany = ''; this.apiBranch = ''; }}"
|
|
1737
|
-
>✕</button>
|
|
1738
|
-
</div>
|
|
1739
|
-
<div style="font-size:9px;color:#81c784;font-family:'SF Mono','Consolas',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${this.apiBaseUrl}">
|
|
1740
|
-
🔗 ${this.apiBaseUrl}
|
|
1741
|
-
</div>
|
|
1742
|
-
</div>
|
|
1817
|
+
return html `
|
|
1818
|
+
<div style="margin:4px;padding:10px;background:linear-gradient(135deg,#e8f5e9,#f1f8e9);border:1px solid #c8e6c9;border-radius:8px;">
|
|
1819
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
1820
|
+
<span style="width:28px;height:28px;border-radius:50%;background:#27ae60;color:white;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;">✓</span>
|
|
1821
|
+
<div style="flex:1;">
|
|
1822
|
+
<div style="font-size:11px;font-weight:600;color:#2e7d32;">Conectado</div>
|
|
1823
|
+
<div style="font-size:10px;color:#66bb6a;">${this.apiUser}${this.apiCompany ? ` — ${this.apiCompany}` : ''}</div>
|
|
1824
|
+
</div>
|
|
1825
|
+
<button style="border:none;background:none;cursor:pointer;font-size:14px;color:#999;padding:2px;" title="Cerrar sesion"
|
|
1826
|
+
@click="${() => { this.apiLoggedIn = false; this.apiToken = ''; this.apiUser = ''; this.apiCompany = ''; this.apiBranch = ''; }}"
|
|
1827
|
+
>✕</button>
|
|
1828
|
+
</div>
|
|
1829
|
+
<div style="font-size:9px;color:#81c784;font-family:'SF Mono','Consolas',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${this.apiBaseUrl}">
|
|
1830
|
+
🔗 ${this.apiBaseUrl}
|
|
1831
|
+
</div>
|
|
1832
|
+
</div>
|
|
1743
1833
|
`;
|
|
1744
1834
|
}
|
|
1745
|
-
return html `
|
|
1746
|
-
<div style="margin:4px;padding:10px;background:#fafafa;border:1px solid #eee;border-radius:8px;">
|
|
1747
|
-
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;">
|
|
1748
|
-
<span style="font-size:14px;">🔐</span>
|
|
1749
|
-
<span style="font-size:11px;font-weight:600;color:#333;">Iniciar Sesion</span>
|
|
1750
|
-
</div>
|
|
1751
|
-
|
|
1752
|
-
${this.apiLoginError ? html `
|
|
1753
|
-
<div style="padding:6px 8px;background:#fde8e8;border:1px solid #f5c6cb;border-radius:5px;margin-bottom:8px;font-size:10px;color:#c62828;">
|
|
1754
|
-
⚠ ${this.apiLoginError}
|
|
1755
|
-
</div>
|
|
1756
|
-
` : ''}
|
|
1757
|
-
|
|
1758
|
-
<div style="margin-bottom:6px;">
|
|
1759
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">URL Base</label>
|
|
1760
|
-
<input id="api-base-url" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:white;box-sizing:border-box;outline:none;transition:border-color 0.15s;"
|
|
1761
|
-
.value="${this.apiBaseUrl}" placeholder="Vacio = proxy local (recomendado)"
|
|
1762
|
-
@input="${(e) => { this.apiBaseUrl = e.target.value; }}"
|
|
1763
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1764
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1765
|
-
/>
|
|
1766
|
-
</div>
|
|
1767
|
-
|
|
1768
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:6px;">
|
|
1769
|
-
<div>
|
|
1770
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Usuario</label>
|
|
1771
|
-
<input id="api-login-user" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1772
|
-
placeholder="SUP"
|
|
1773
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1774
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1775
|
-
/>
|
|
1776
|
-
</div>
|
|
1777
|
-
<div>
|
|
1778
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Clave</label>
|
|
1779
|
-
<input id="api-login-pass" type="password" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1780
|
-
placeholder="••••"
|
|
1781
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1782
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1835
|
+
return html `
|
|
1836
|
+
<div style="margin:4px;padding:10px;background:#fafafa;border:1px solid #eee;border-radius:8px;">
|
|
1837
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;">
|
|
1838
|
+
<span style="font-size:14px;">🔐</span>
|
|
1839
|
+
<span style="font-size:11px;font-weight:600;color:#333;">Iniciar Sesion</span>
|
|
1840
|
+
</div>
|
|
1841
|
+
|
|
1842
|
+
${this.apiLoginError ? html `
|
|
1843
|
+
<div style="padding:6px 8px;background:#fde8e8;border:1px solid #f5c6cb;border-radius:5px;margin-bottom:8px;font-size:10px;color:#c62828;">
|
|
1844
|
+
⚠ ${this.apiLoginError}
|
|
1845
|
+
</div>
|
|
1846
|
+
` : ''}
|
|
1847
|
+
|
|
1848
|
+
<div style="margin-bottom:6px;">
|
|
1849
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">URL Base</label>
|
|
1850
|
+
<input id="api-base-url" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:10px;font-family:'SF Mono','Consolas',monospace;background:white;box-sizing:border-box;outline:none;transition:border-color 0.15s;"
|
|
1851
|
+
.value="${this.apiBaseUrl}" placeholder="Vacio = proxy local (recomendado)"
|
|
1852
|
+
@input="${(e) => { this.apiBaseUrl = e.target.value; }}"
|
|
1853
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1854
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1855
|
+
/>
|
|
1856
|
+
</div>
|
|
1857
|
+
|
|
1858
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:6px;">
|
|
1859
|
+
<div>
|
|
1860
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Usuario</label>
|
|
1861
|
+
<input id="api-login-user" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1862
|
+
placeholder="SUP"
|
|
1863
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1864
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1865
|
+
/>
|
|
1866
|
+
</div>
|
|
1867
|
+
<div>
|
|
1868
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Clave</label>
|
|
1869
|
+
<input id="api-login-pass" type="password" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1870
|
+
placeholder="••••"
|
|
1871
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1872
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1783
1873
|
@keydown="${(e) => { if (e.key === 'Enter')
|
|
1784
|
-
this.doApiLogin(); }}"
|
|
1785
|
-
/>
|
|
1786
|
-
</div>
|
|
1787
|
-
</div>
|
|
1788
|
-
|
|
1789
|
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:8px;">
|
|
1790
|
-
<div>
|
|
1791
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Empresa (cod)</label>
|
|
1792
|
-
<input id="api-login-company" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1793
|
-
placeholder="01" value="01"
|
|
1794
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1795
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1796
|
-
/>
|
|
1797
|
-
</div>
|
|
1798
|
-
<div>
|
|
1799
|
-
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Sucursal</label>
|
|
1800
|
-
<input id="api-login-branch" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1801
|
-
placeholder="01" value="01"
|
|
1802
|
-
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1803
|
-
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1804
|
-
/>
|
|
1805
|
-
</div>
|
|
1806
|
-
</div>
|
|
1807
|
-
|
|
1808
|
-
<button style="width:100%;padding:8px;border:none;border-radius:6px;background:#1976d2;color:white;cursor:pointer;font-size:12px;font-weight:600;font-family:inherit;transition:all 0.15s;${this.apiLoginLoading ? 'opacity:0.7;pointer-events:none;' : ''}"
|
|
1809
|
-
@click="${this.doApiLogin}"
|
|
1810
|
-
@mouseenter="${(e) => { e.target.style.background = '#1565c0'; }}"
|
|
1811
|
-
@mouseleave="${(e) => { e.target.style.background = '#1976d2'; }}"
|
|
1812
|
-
>${this.apiLoginLoading ? '⏳ Conectando...' : '🔑 Iniciar Sesion'}</button>
|
|
1813
|
-
|
|
1814
|
-
<div style="margin-top:6px;font-size:9px;color:#bbb;text-align:center;">
|
|
1815
|
-
Usa las mismas credenciales de Zentto ERP
|
|
1816
|
-
</div>
|
|
1817
|
-
</div>
|
|
1874
|
+
this.doApiLogin(); }}"
|
|
1875
|
+
/>
|
|
1876
|
+
</div>
|
|
1877
|
+
</div>
|
|
1878
|
+
|
|
1879
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:8px;">
|
|
1880
|
+
<div>
|
|
1881
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Empresa (cod)</label>
|
|
1882
|
+
<input id="api-login-company" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1883
|
+
placeholder="01" value="01"
|
|
1884
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1885
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1886
|
+
/>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div>
|
|
1889
|
+
<label style="font-size:9px;font-weight:600;color:#999;display:block;margin-bottom:2px;">Sucursal</label>
|
|
1890
|
+
<input id="api-login-branch" style="width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:5px;font-size:11px;background:white;box-sizing:border-box;outline:none;font-family:inherit;"
|
|
1891
|
+
placeholder="01" value="01"
|
|
1892
|
+
@focus="${(e) => { e.target.style.borderColor = '#1976d2'; }}"
|
|
1893
|
+
@blur="${(e) => { e.target.style.borderColor = '#ddd'; }}"
|
|
1894
|
+
/>
|
|
1895
|
+
</div>
|
|
1896
|
+
</div>
|
|
1897
|
+
|
|
1898
|
+
<button style="width:100%;padding:8px;border:none;border-radius:6px;background:#1976d2;color:white;cursor:pointer;font-size:12px;font-weight:600;font-family:inherit;transition:all 0.15s;${this.apiLoginLoading ? 'opacity:0.7;pointer-events:none;' : ''}"
|
|
1899
|
+
@click="${this.doApiLogin}"
|
|
1900
|
+
@mouseenter="${(e) => { e.target.style.background = '#1565c0'; }}"
|
|
1901
|
+
@mouseleave="${(e) => { e.target.style.background = '#1976d2'; }}"
|
|
1902
|
+
>${this.apiLoginLoading ? '⏳ Conectando...' : '🔑 Iniciar Sesion'}</button>
|
|
1903
|
+
|
|
1904
|
+
<div style="margin-top:6px;font-size:9px;color:#bbb;text-align:center;">
|
|
1905
|
+
Usa las mismas credenciales de Zentto ERP
|
|
1906
|
+
</div>
|
|
1907
|
+
</div>
|
|
1818
1908
|
`;
|
|
1819
1909
|
}
|
|
1820
1910
|
async doApiLogin() {
|
|
@@ -1944,13 +2034,13 @@ let ZsPageDesigner = class ZsPageDesigner extends LitElement {
|
|
|
1944
2034
|
return Object.keys(sample).filter(k => !k.startsWith('_'));
|
|
1945
2035
|
}
|
|
1946
2036
|
renderToggle(label, value, onChange) {
|
|
1947
|
-
return html `
|
|
1948
|
-
<div class="prop-toggle">
|
|
1949
|
-
<button class="prop-switch ${value ? 'prop-switch--active' : ''}"
|
|
1950
|
-
@click="${() => onChange(!value)}"
|
|
1951
|
-
></button>
|
|
1952
|
-
<span class="prop-toggle-label">${label}</span>
|
|
1953
|
-
</div>
|
|
2037
|
+
return html `
|
|
2038
|
+
<div class="prop-toggle">
|
|
2039
|
+
<button class="prop-switch ${value ? 'prop-switch--active' : ''}"
|
|
2040
|
+
@click="${() => onChange(!value)}"
|
|
2041
|
+
></button>
|
|
2042
|
+
<span class="prop-toggle-label">${label}</span>
|
|
2043
|
+
</div>
|
|
1954
2044
|
`;
|
|
1955
2045
|
}
|
|
1956
2046
|
toggleSection(id) {
|