Add hierarchical tree view for category selection

Features:
- Tree view mode for categories with expand/collapse
- Product count badges with clickable preview popover
- Select parent with all children button
- Client-side tree filtering (refine search)
- Keyboard shortcuts: Ctrl+A (select all), Ctrl+D (clear)
- View mode switching between tree/list/columns
- Tree view as default for categories, respects user preference

Backend:
- Add previewCategoryProducts and previewCategoryPages AJAX handlers
- Support pagination and filtering in category previews

Styling:
- Consistent count-badge styling across tree and other views
- Loading and popover-open states for count badges

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 15:03:51 +01:00
parent b79a89bbb4
commit 7d79273743
37 changed files with 4620 additions and 1913 deletions

View File

@@ -117,7 +117,7 @@
// Results container
.dropdown-results {
padding: $es-spacing-sm;
padding: 0 $es-spacing-sm;
}
// Results count text
@@ -609,80 +609,7 @@
}
}
.tree-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: $es-text-muted;
cursor: pointer;
transition: transform $es-transition-fast;
i {
font-size: 12px;
}
}
.tree-toggle-placeholder {
width: 16px;
height: 16px;
}
.btn-select-children {
@include button-reset;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
color: $es-primary;
border-radius: $es-radius-sm;
transition: all $es-transition-fast;
&:hover {
background: $es-primary-light;
}
i {
font-size: 12px;
}
}
.btn-select-children-placeholder {
width: 18px;
height: 18px;
}
.tree-checkbox {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 16px;
height: 16px;
border: 2px solid $es-border-dark;
border-radius: 3px;
transition: all $es-transition-fast;
i {
display: none;
font-size: 10px;
color: $es-white;
}
}
.tree-icon {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
color: $es-text-muted;
i {
font-size: 14px;
}
}
// tree-toggle, btn-select-children, tree-checkbox, tree-icon styles in _tree.scss
.tree-info {
display: flex;
@@ -1521,14 +1448,11 @@ body > .target-search-dropdown,
color: $es-text-muted;
}
.dropdown-footer-actions {
display: flex;
align-items: center;
gap: $es-spacing-sm;
}
.btn-cancel-dropdown {
@include button-reset;
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.375rem 0.75rem;
font-size: $es-font-size-sm;
color: $es-text-secondary;
@@ -1539,6 +1463,12 @@ body > .target-search-dropdown,
&:hover {
background: $es-bg-hover;
color: $es-danger;
border-color: $es-danger;
}
i {
font-size: 10px;
}
kbd {
@@ -1566,6 +1496,11 @@ body > .target-search-dropdown,
&:hover {
background: $es-primary-hover;
border-color: $es-primary-hover;
}
i {
font-size: 10px;
}
kbd {
@@ -1702,7 +1637,7 @@ body > .target-search-dropdown,
// Results container
.dropdown-results {
padding: $es-spacing-sm;
padding: 0 $es-spacing-sm;
background: $es-white;
min-height: 200px;
}
@@ -1768,16 +1703,16 @@ body > .target-search-dropdown,
}
}
// View mode classes (applied to dropdown container)
&.view-cols-2 .dropdown-results { @include grid-columns(2); }
&.view-cols-3 .dropdown-results { @include grid-columns(3); }
&.view-cols-4 .dropdown-results { @include grid-columns(4); }
&.view-cols-5 .dropdown-results { @include grid-columns(5); }
&.view-cols-6 .dropdown-results { @include grid-columns(6); }
&.view-cols-7 .dropdown-results { @include grid-columns(7); }
&.view-cols-8 .dropdown-results { @include grid-columns(8); }
// View mode classes (applied to dropdown container) - no gap/padding for shared borders
&.view-cols-2 .dropdown-results { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-3 .dropdown-results { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-4 .dropdown-results { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-5 .dropdown-results { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-6 .dropdown-results { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-7 .dropdown-results { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
&.view-cols-8 .dropdown-results { display: grid; grid-template-columns: repeat(8, 1fr); gap: 0; padding: 0; border-top: 1px solid $es-border-color; border-left: 1px solid $es-border-color; }
// Grid view item styling (compact cards)
// Grid view item styling (compact cards with shared borders)
&.view-cols-2,
&.view-cols-3,
&.view-cols-4,
@@ -1789,9 +1724,11 @@ body > .target-search-dropdown,
flex-direction: column;
align-items: center;
text-align: center;
padding: 0;
border: 1px solid $es-border-color;
border-radius: $es-radius-sm;
padding: $es-spacing-sm;
border: none;
border-right: 1px solid $es-border-color;
border-bottom: 1px solid $es-border-color;
border-radius: 0;
.result-checkbox {
position: absolute;
@@ -1861,6 +1798,15 @@ body > .target-search-dropdown,
}
}
// Remove right border from last item in each row (per column count)
&.view-cols-2 .dropdown-results .dropdown-item:nth-child(2n) { border-right: none; }
&.view-cols-3 .dropdown-results .dropdown-item:nth-child(3n) { border-right: none; }
&.view-cols-4 .dropdown-results .dropdown-item:nth-child(4n) { border-right: none; }
&.view-cols-5 .dropdown-results .dropdown-item:nth-child(5n) { border-right: none; }
&.view-cols-6 .dropdown-results .dropdown-item:nth-child(6n) { border-right: none; }
&.view-cols-7 .dropdown-results .dropdown-item:nth-child(7n) { border-right: none; }
&.view-cols-8 .dropdown-results .dropdown-item:nth-child(8n) { border-right: none; }
// Smaller items for higher column counts
&.view-cols-5,
&.view-cols-6,
@@ -2175,80 +2121,7 @@ body > .target-search-dropdown,
}
}
.tree-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: $es-text-muted;
cursor: pointer;
transition: transform $es-transition-fast;
i {
font-size: 12px;
}
}
.tree-toggle-placeholder {
width: 16px;
height: 16px;
}
.btn-select-children {
@include button-reset;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
color: $es-primary;
border-radius: $es-radius-sm;
transition: all $es-transition-fast;
&:hover {
background: $es-primary-light;
}
i {
font-size: 12px;
}
}
.btn-select-children-placeholder {
width: 18px;
height: 18px;
}
.tree-checkbox {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 16px;
height: 16px;
border: 2px solid $es-border-dark;
border-radius: 3px;
transition: all $es-transition-fast;
i {
display: none;
font-size: 10px;
color: $es-white;
}
}
.tree-icon {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
color: $es-text-muted;
i {
font-size: 14px;
}
}
// tree-toggle, btn-select-children, tree-checkbox, tree-icon styles in _tree.scss
.tree-info {
display: flex;
@@ -2280,7 +2153,7 @@ body > .target-search-dropdown,
.dropdown-results {
max-height: 400px;
overflow-y: auto;
padding: $es-spacing-sm;
padding: 0 $es-spacing-sm;
@include custom-scrollbar;
}
@@ -2498,6 +2371,7 @@ body > .target-search-dropdown,
.entity-search-icon {
color: $es-text-muted;
flex-shrink: 0;
margin-left: $es-spacing-xs;
}
// Override Bootstrap/parent form input styles