From 2864f7d7cbcbad9f64e9bdedd19ac880e0d4329e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:49:17 +0000 Subject: [PATCH 1/2] Initial plan From 4173c47145a5b711f8ee2102a1e5cea7a586c234 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:01:30 +0000 Subject: [PATCH 2/2] Modernize admin form-builder UI/UX: design tokens, better field cards, live search, sticky sidebars, improved drag-and-drop feedback Co-authored-by: gabrielsolomon <17990591+gabrielsolomon@users.noreply.github.com> --- resources/assets/js/admin.js | 74 +++- resources/assets/scss/admin.scss | 326 ++++++++++++++---- .../views/admin/formbuilder-fields/index.php | 19 +- .../modules/lists/available.php | 21 +- .../modules/lists/existing.php | 89 ++--- .../modules/panels/index-actions.php | 58 ++-- .../modules/panels/index-available.php | 32 +- .../modules/panels/index-existing.php | 32 +- 8 files changed, 466 insertions(+), 185 deletions(-) diff --git a/resources/assets/js/admin.js b/resources/assets/js/admin.js index 056c517..ceba7ea 100644 --- a/resources/assets/js/admin.js +++ b/resources/assets/js/admin.js @@ -1,19 +1,79 @@ document.addEventListener("DOMContentLoaded", function () { + // ── Toast colour tokens (match CSS custom properties) ─────────────────── + var TOAST_COLORS = { + error: "#ef4444", + success: "#22c55e", + info: "#3b82f6" + }; + + // ── Sortable drag-and-drop ────────────────────────────────────────────── $("#form-fields-container .sortable").sortable({ + placeholder: "ui-sortable-placeholder", + forcePlaceholderSize: true, + opacity: 0.85, + revert: 120, + + start: function (event, ui) { + ui.placeholder.height(ui.item.outerHeight()); + ui.item.closest(".fields-container").addClass("fb-drag-over"); + }, + + stop: function (event, ui) { + ui.item.closest(".fields-container").removeClass("fb-drag-over"); + }, + update: function (event, ui) { - var order = $(this).sortable('serialize'); + var $list = $(this); + $list.removeClass("fb-drag-over"); + var order = $list.sortable("serialize"); $.ajax({ - url: $(this).data('url'), - type: 'post', - data: { - 'order': order - }, + url: $list.data("url"), + type: "post", + data: { order: order }, success: function (data) { - jQuery.jGrowl(data.message, {life: 10000, theme: data.type}); + if (typeof jQuery.jGrowl === "function") { + jQuery.jGrowl(data.message, { life: 4000, theme: data.type }); + } else { + fbToast(data.message, data.type); + } + }, + error: function () { + fbToast("Could not save field order. Please try again.", "error"); } }); } }); + + // ── Live search for available fields ──────────────────────────────────── + var $searchInput = $("#fb-field-search"); + if ($searchInput.length) { + $searchInput.on("input", function () { + var query = this.value.trim().toLowerCase(); + $("#fields-available .field").each(function () { + var label = $(this).find(".field-name").text().toLowerCase(); + $(this).toggle(query === "" || label.includes(query)); + }); + }); + } + + // ── Minimal toast fallback (no jQuery.jGrowl) ──────────────────────────── + function fbToast(message, type) { + var color = TOAST_COLORS[type] || TOAST_COLORS.info; + var el = document.createElement("div"); + el.textContent = message; + Object.assign(el.style, { + position: "fixed", bottom: "1.5rem", right: "1.5rem", zIndex: 9999, + background: color, color: "#fff", padding: ".75rem 1.25rem", + borderRadius: ".5rem", boxShadow: "0 4px 12px rgba(0,0,0,.15)", + fontSize: ".875rem", maxWidth: "320px", transition: "opacity .3s" + }); + document.body.appendChild(el); + setTimeout(function () { + el.style.opacity = "0"; + setTimeout(function () { el.remove(); }, 300); + }, 4000); + } + }); \ No newline at end of file diff --git a/resources/assets/scss/admin.scss b/resources/assets/scss/admin.scss index 67c67e6..5404b4b 100644 --- a/resources/assets/scss/admin.scss +++ b/resources/assets/scss/admin.scss @@ -1,100 +1,290 @@ -/** FORM TAB */ -#form-fields-container .form-panel { +// ─── Design tokens ────────────────────────────────────────────────────────── +:root { + --fb-radius: 0.5rem; + --fb-radius-sm: 0.375rem; + --fb-shadow-sm: 0 1px 3px rgba(0, 0, 0, .08), 0 1px 2px rgba(0, 0, 0, .05); + --fb-shadow: 0 4px 12px rgba(0, 0, 0, .1); + --fb-transition: 150ms ease; + --fb-icon-bg: #fef3c7; + --fb-icon-size: 2rem; + --fb-field-bg: #ffffff; + --fb-field-bg-hover: #f8fafc; + --fb-field-border: #e2e8f0; + --fb-field-border-hover: #94a3b8; + --fb-drag-handle-color: #cbd5e1; + --fb-badge-bg: #f1f5f9; + --fb-badge-color: #475569; + --fb-visible-color: #16a34a; + --fb-invisible-color: #dc2626; + --fb-panel-header-bg: #1e293b; + --fb-panel-header-color: #f8fafc; + --fb-available-add-bg: #dcfce7; + --fb-available-add-color: #15803d; + --fb-available-add-hover-bg: #bbf7d0; + --fb-empty-state-color: #94a3b8; +} +// ─── Container ────────────────────────────────────────────────────────────── +#form-fields-container { + .col-lg-6.existing, + .col-lg-3.available, + .col-lg-3.order-lg-last { + padding-top: 0; + } } -#form-fields-container .form-panel .header { - color: #fff; - background: #333; - font-weight: bold; - text-transform: uppercase; - padding: 3px 10px; - - -webkit-border-top-left-radius: 6px; - -webkit-border-top-right-radius: 6px; - -moz-border-radius-topleft: 6px; - -moz-border-radius-topright: 6px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; +// ─── Panel (shared) ───────────────────────────────────────────────────────── +.form-panel { + border: 1px solid var(--fb-field-border); + border-radius: var(--fb-radius); + overflow: hidden; + box-shadow: var(--fb-shadow-sm); + background: #fff; + + .header { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--fb-panel-header-color); + background: var(--fb-panel-header-bg); + font-weight: 600; + font-size: .8125rem; + letter-spacing: .05em; + text-transform: uppercase; + padding: .625rem 1rem; + } + + .fields-body { + padding: .5rem; + } } -.fields-body .field { - .field-icon { - display: inline-block; - height: 2em; - width: 2em; - padding: 3px; - background-color: $amber-200; - - svg { - width: 100%; - height: auto; +// ─── Existing fields ──────────────────────────────────────────────────────── +#form-fields-container .existing { + .form-panel { + margin-bottom: 1rem; + } + + .fields-body .fields-container { + display: flex; + flex-direction: column; + gap: .375rem; + min-height: 3rem; // drop-target area + + &.fb-drag-over { + background: #f0f9ff; + border-radius: var(--fb-radius-sm); + outline: 2px dashed #38bdf8; + } + + .field { + display: flex; + align-items: center; + gap: .625rem; + padding: .5rem .75rem; + background: var(--fb-field-bg); + border: 1px solid var(--fb-field-border); + border-radius: var(--fb-radius-sm); + box-shadow: var(--fb-shadow-sm); + transition: box-shadow var(--fb-transition), border-color var(--fb-transition), background var(--fb-transition); + cursor: grab; + + &:hover { + background: var(--fb-field-bg-hover); + border-color: var(--fb-field-border-hover); + box-shadow: var(--fb-shadow); + } + + &:active { + cursor: grabbing; + box-shadow: var(--fb-shadow); + } + + &.ui-sortable-placeholder { + visibility: visible !important; + border: 2px dashed #38bdf8; + background: #f0f9ff; + border-radius: var(--fb-radius-sm); + box-shadow: none; + } + + &.ui-sortable-helper { + box-shadow: var(--fb-shadow); + opacity: .9; + rotate: 1.5deg; + } + + // Drag handle hint + &::before { + content: "⋮⋮"; + font-size: .875rem; + letter-spacing: -2px; + color: var(--fb-drag-handle-color); + flex-shrink: 0; + user-select: none; + } } } } +// ─── Field icon ───────────────────────────────────────────────────────────── +.field-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + height: var(--fb-icon-size); + width: var(--fb-icon-size); + padding: .25rem; + background-color: var(--fb-icon-bg); + border-radius: var(--fb-radius-sm); + + svg { + width: 100%; + height: auto; + max-height: 1.25rem; + } +} + +// ─── Field flags (visibility / listing / filter) ───────────────────────────── +.field-flags { + display: inline-flex; + align-items: center; + gap: .25rem; + flex-shrink: 0; + + .field-invisible { color: var(--fb-invisible-color); } + .field-visible { color: var(--fb-visible-color); } +} + +// ─── Field type badge ──────────────────────────────────────────────────────── +.field-type-badge { + display: inline-flex; + align-items: center; + padding: .1875rem .5rem; + font-size: .6875rem; + font-weight: 500; + color: var(--fb-badge-color); + background: var(--fb-badge-bg); + border-radius: 999px; + white-space: nowrap; + flex-shrink: 0; +} + +// ─── Available fields panel ────────────────────────────────────────────────── #fields-available { - &.sortable .field { - cursor: move; + .fields-container { + display: grid; + grid-template-columns: 1fr; + gap: .375rem; } .field { - padding: calc($spacer / 2); + display: flex; + align-items: center; + gap: .5rem; + padding: .5rem .625rem; + border: 1px solid var(--fb-field-border); + border-radius: var(--fb-radius-sm); + background: var(--fb-field-bg); + transition: background var(--fb-transition), border-color var(--fb-transition); &:hover { - background-color: $gray-100; + background: var(--fb-field-bg-hover); + border-color: var(--fb-field-border-hover); } - .btn-group { - width: 48px; - float: right; + .field-name { + flex: 1; + font-size: .875rem; + font-weight: 500; + color: #334155; } - .btn-group .btn { - padding: 0 2px !important; + .btn-group { + flex-shrink: 0; + + .btn { + padding: .25rem .5rem; + font-size: .8125rem; + font-weight: 700; + border-radius: var(--fb-radius-sm); + background: var(--fb-available-add-bg); + color: var(--fb-available-add-color); + border: 1px solid #86efac; + line-height: 1; + + &:hover { + background: var(--fb-available-add-hover-bg); + } + } } } } -#form-fields-container .existing { - .form-pane { - margin-bottom: $spacer; +// ─── Empty state ───────────────────────────────────────────────────────────── +.fb-empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem 1rem; + color: var(--fb-empty-state-color); + text-align: center; + gap: .5rem; + + .fb-empty-state-icon { + font-size: 2rem; + opacity: .4; } + + p { + margin: 0; + font-size: .875rem; + } +} + +// ─── Sticky sidebars ───────────────────────────────────────────────────────── +.fb-sticky-sidebar { + position: sticky; + top: 1rem; } -#form-fields-container .existing .fields-body .fields-container { +// ─── Available-fields card header ──────────────────────────────────────────── +.fb-panel-header { + background: var(--fb-panel-header-bg) !important; + color: var(--fb-panel-header-color) !important; + font-weight: 600; + font-size: .8125rem; + letter-spacing: .05em; + text-transform: uppercase; +} - .field { - background-color: #F3F3D1; - border-color: #DBDBB0; - border-style: solid; - border-width: 0px 1px 1px 1px; - border-bottom-color: #c3c3c3; - - margin-bottom: 3px; - - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; - - /* (Optional) Apply a "closed-hand" cursor during drag operation. */ - &:active { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - } +// ─── Action panel ──────────────────────────────────────────────────────────── +.fb-actions-panel { + .card { + border-color: var(--fb-field-border); + border-radius: var(--fb-radius); + box-shadow: var(--fb-shadow-sm); + } - &:hover { - background-color: #EDEDAF; - } + .card-header { + background: var(--fb-panel-header-bg); + color: var(--fb-panel-header-color); + font-weight: 600; + font-size: .8125rem; + letter-spacing: .05em; + text-transform: uppercase; + border-radius: var(--fb-radius) var(--fb-radius) 0 0 !important; + } - .field-invisible { - color: #ed513f; - } + .card-body { + display: flex; + flex-direction: column; + gap: .75rem; + } - .field-visible { - color: #45b035; - } + .btn-danger { + width: 100%; } } diff --git a/resources/views/admin/formbuilder-fields/index.php b/resources/views/admin/formbuilder-fields/index.php index 2169ddf..29beb55 100644 --- a/resources/views/admin/formbuilder-fields/index.php +++ b/resources/views/admin/formbuilder-fields/index.php @@ -12,16 +12,25 @@ /** @var array $withParams */ $this->set('withParams', $withParams); ?> -
-
- load('modules/panels/index-actions', ['manager' => $manager]); ?> +
+ + +
+
+ load('modules/panels/index-available', ['manager' => $manager]); ?> +
+
load('modules/panels/index-existing', ['manager' => $manager]); ?>
-
- load('modules/panels/index-available', ['manager' => $manager]); ?> + +
+
+ load('modules/panels/index-actions', ['manager' => $manager]); ?> +
+
diff --git a/resources/views/admin/formbuilder-fields/modules/lists/available.php b/resources/views/admin/formbuilder-fields/modules/lists/available.php index 63a9917..818bdd2 100644 --- a/resources/views/admin/formbuilder-fields/modules/lists/available.php +++ b/resources/views/admin/formbuilder-fields/modules/lists/available.php @@ -15,22 +15,19 @@ } /** @var AbstractType $fields */ ?> -
+
-
- + compileURL('add', $addUrlParams + ['type' => $field->getName()]); ?> +
+
getIcon(); ?> - - - getLabel(); ?> - +
+ getLabel()); ?>
- compileURL('add', $addUrlParams + ['type' => $field->getName()]); - ?> - + + class="btn add-getName()); ?>" + title="trans('add'); ?>"> +
diff --git a/resources/views/admin/formbuilder-fields/modules/lists/existing.php b/resources/views/admin/formbuilder-fields/modules/lists/existing.php index 13d9e7a..d5a0105 100644 --- a/resources/views/admin/formbuilder-fields/modules/lists/existing.php +++ b/resources/views/admin/formbuilder-fields/modules/lists/existing.php @@ -7,60 +7,61 @@ ?>
+ +
+ +

trans('form-builder.fields.empty_state') ?? 'No fields yet — drag from the palette to add one.'; ?>

+
+ hasListing('public')) ? 'field-visible' : 'field-invisible'; - $filterIcons = ($field->hasFilter('public')) ? 'field-visible' : 'field-invisible'; - $visibleIcons = ($field->visible == 'no') ? 'field-invisible fas fa-eye-slash' : 'field-visible far fa-eye'; + $listIcons = ($field->hasListing('public')) ? 'field-visible fas fa-list' : 'field-invisible fas fa-list'; + $filterIcons = ($field->hasFilter('public')) ? 'field-visible fas fa-filter' : 'field-invisible fas fa-filter'; + $visibleIcons = ($field->visible === 'no') ? 'field-invisible fas fa-eye-slash' : 'field-visible far fa-eye'; $fieldType = $field->getType(); ?>
-
-
- getIcon(); ?> -
-
- - - -
+ class="field field-getRole(); ?>-getName(); ?>"> +
+ getIcon(); ?> +
+ +
+ + + +
+ +
+ getLabel()); ?> + isMandatory() ? '*' : ''; ?> +
-
- getLabel(); ?> - isMandatory() ? '*' : ''; ?> -
+ getLabel()); ?> -
- - [getLabel(); ?>] - -
-
- -
diff --git a/resources/views/admin/formbuilder-fields/modules/panels/index-actions.php b/resources/views/admin/formbuilder-fields/modules/panels/index-actions.php index 259aa47..bffe6a0 100644 --- a/resources/views/admin/formbuilder-fields/modules/panels/index-actions.php +++ b/resources/views/admin/formbuilder-fields/modules/panels/index-actions.php @@ -9,28 +9,38 @@ /** @var array $roles */ $roles = $this->roles; ?> -
- - Importa de la - - - -
+
+
+
+ trans('actions') ?? 'Actions'; ?> +
+
+ importLinks)) { ?> + + -

 

- -
- -
\ No newline at end of file +
+ +
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/formbuilder-fields/modules/panels/index-available.php b/resources/views/admin/formbuilder-fields/modules/panels/index-available.php index 3344c94..09ff230 100644 --- a/resources/views/admin/formbuilder-fields/modules/panels/index-available.php +++ b/resources/views/admin/formbuilder-fields/modules/panels/index-available.php @@ -17,27 +17,39 @@ ?>
-
+
getLabel('available'); ?>
+ +
+ +
+
1) { ?> -
+
getAvailable($role)->all(); - $id = 'fields-available-'.$role; + $id = 'fields-available-' . $role; ?> -
+

-

-
+ data-bs-parent="#fb-fields-accordion"> +
load('../lists/available', ['fields' => $fields, 'role' => $role]); ?>
@@ -47,7 +59,7 @@
getAvailable($role)->all(); ?> load('../lists/available', ['fields' => $fields, 'role' => $role]); ?> diff --git a/resources/views/admin/formbuilder-fields/modules/panels/index-existing.php b/resources/views/admin/formbuilder-fields/modules/panels/index-existing.php index b6fe563..19362cf 100644 --- a/resources/views/admin/formbuilder-fields/modules/panels/index-existing.php +++ b/resources/views/admin/formbuilder-fields/modules/panels/index-existing.php @@ -17,22 +17,24 @@ getExisting($role)->all(); + $count = count($fields); ?> - -
-
- getLabel('existing.'.$role) ?> -
-
- load( - '../lists/existing', - [ - 'fields' => $fields, - 'updateUrl' => $this->formBuilder->compileURL('order', $this->withParams), - ] - ); ?> -
+
+
+ getLabel('existing.' . $role) ?> + 0) { ?> + +
- +
+ load( + '../lists/existing', + [ + 'fields' => $fields, + 'updateUrl' => $this->formBuilder->compileURL('order', $this->withParams), + ] + ); ?> +
+