@memberjunction/ng-entity-viewer 5.11.0 → 5.12.0
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/lib/aggregate-panel/aggregate-panel.component.js +2 -2
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +2 -2
- package/dist/lib/confirm-dialog/confirm-dialog.component.js +2 -2
- package/dist/lib/duplicate-view-dialog/duplicate-view-dialog.component.js +2 -2
- package/dist/lib/entity-cards/entity-cards.component.js +4 -4
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +26 -3
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +196 -137
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/models/grid-types.js +4 -4
- package/dist/lib/entity-data-grid/models/grid-types.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +2 -2
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +5 -4
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +46 -69
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/pill/pill.component.js +2 -2
- package/dist/lib/pill/pill.component.js.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +2 -2
- package/dist/lib/shared-view-warning-dialog/shared-view-warning-dialog.component.js +2 -2
- package/dist/lib/view-config-panel/view-config-panel.component.js +2 -2
- package/dist/lib/view-header/view-header.component.js +2 -2
- package/dist/module.d.ts +15 -16
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +4 -6
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +0 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +0 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +10 -9
- package/dist/lib/pagination/pagination.component.d.ts +0 -60
- package/dist/lib/pagination/pagination.component.d.ts.map +0 -1
- package/dist/lib/pagination/pagination.component.js +0 -201
- package/dist/lib/pagination/pagination.component.js.map +0 -1
|
@@ -333,11 +333,11 @@ export class AggregatePanelComponent {
|
|
|
333
333
|
i0.ɵɵconditional(ctx.Collapsible ? 6 : -1);
|
|
334
334
|
i0.ɵɵadvance();
|
|
335
335
|
i0.ɵɵconditional(!ctx.IsCollapsed ? 7 : -1);
|
|
336
|
-
} }, dependencies: [i1.NgStyle], styles: ["\n\n\n.aggregate-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-
|
|
336
|
+
} }, dependencies: [i1.NgStyle], styles: ["\n\n\n.aggregate-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n\n\n.position-right[_ngcontent-%COMP%] {\n height: 100%;\n border-left-width: 1px;\n border-radius: 0 8px 8px 0;\n}\n\n.position-bottom[_ngcontent-%COMP%] {\n width: 100%;\n border-top-width: 1px;\n border-radius: 0 0 8px 8px;\n}\n\n\n\n.collapsed[_ngcontent-%COMP%] .panel-content[_ngcontent-%COMP%] {\n display: none;\n}\n\n.collapsed.position-right[_ngcontent-%COMP%] {\n width: auto !important;\n min-width: auto !important;\n}\n\n\n\n.panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n}\n\n.panel-header[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.panel-title[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n.panel-header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.loading-indicator[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.collapse-toggle[_ngcontent-%COMP%] {\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 12px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n}\n\n.collapse-toggle[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.05);\n color: var(--mj-text-primary);\n}\n\n\n\n.panel-content[_ngcontent-%COMP%] {\n padding: 16px;\n overflow-y: auto;\n flex: 1;\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 24px;\n color: var(--mj-text-disabled);\n text-align: center;\n}\n\n.empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n}\n\n\n\n.cards-container[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n}\n\n.layout-horizontal[_ngcontent-%COMP%] {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.layout-vertical[_ngcontent-%COMP%] {\n flex-direction: column;\n}\n\n.layout-grid[_ngcontent-%COMP%] {\n display: grid;\n gap: 12px;\n}\n\n\n\n.aggregate-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;\n}\n\n.aggregate-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n transform: translateY(-1px);\n}\n\n\n\n.card-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n color: var(--mj-brand-primary);\n font-size: 18px;\n flex-shrink: 0;\n}\n\n\n\n.card-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.card-value[_ngcontent-%COMP%] {\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.2;\n margin-bottom: 4px;\n word-break: break-word;\n}\n\n.card-value.loading[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n}\n\n.card-label[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-secondary);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n\n.style-success[_ngcontent-%COMP%] {\n border-left: 4px solid var(--mj-status-success);\n}\n\n.style-success[_ngcontent-%COMP%] .card-value[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n.style-success[_ngcontent-%COMP%] .card-icon[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success);\n}\n\n.style-warning[_ngcontent-%COMP%] {\n border-left: 4px solid var(--mj-status-warning);\n}\n\n.style-warning[_ngcontent-%COMP%] .card-value[_ngcontent-%COMP%] {\n color: var(--mj-status-warning-text);\n}\n\n.style-warning[_ngcontent-%COMP%] .card-icon[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.style-danger[_ngcontent-%COMP%] {\n border-left: 4px solid var(--mj-status-error);\n}\n\n.style-danger[_ngcontent-%COMP%] .card-value[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.style-danger[_ngcontent-%COMP%] .card-icon[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n.style-info[_ngcontent-%COMP%] {\n border-left: 4px solid var(--mj-brand-primary);\n}\n\n.style-info[_ngcontent-%COMP%] .card-value[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.style-info[_ngcontent-%COMP%] .card-icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.style-muted[_ngcontent-%COMP%] {\n border-left: 4px solid var(--mj-text-muted);\n}\n\n.style-muted[_ngcontent-%COMP%] .card-value[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n}\n\n.style-muted[_ngcontent-%COMP%] .card-icon[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n\n\n.position-bottom[_ngcontent-%COMP%] .cards-container.layout-vertical[_ngcontent-%COMP%] {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.position-bottom[_ngcontent-%COMP%] .cards-container.layout-vertical[_ngcontent-%COMP%] .aggregate-card[_ngcontent-%COMP%] {\n flex: 1 1 200px;\n max-width: 300px;\n}"] });
|
|
337
337
|
}
|
|
338
338
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AggregatePanelComponent, [{
|
|
339
339
|
type: Component,
|
|
340
|
-
args: [{ standalone: false, selector: 'mj-aggregate-panel', template: "<div class=\"aggregate-panel\"\n [class.position-right]=\"Position === 'right'\"\n [class.position-bottom]=\"Position === 'bottom'\"\n [class.collapsed]=\"IsCollapsed\"\n [ngStyle]=\"PanelStyle\">\n\n <!-- Panel Header -->\n <div class=\"panel-header\" (click)=\"ToggleCollapse()\">\n <span class=\"panel-title\">{{ Title }}</span>\n\n <div class=\"panel-header-right\">\n <!-- Loading indicator -->\n @if (Loading) {\n <span class=\"loading-indicator\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </span>\n }\n\n <!-- Collapse toggle -->\n @if (Collapsible) {\n <button class=\"collapse-toggle\" type=\"button\">\n <i class=\"fa-solid\" [class.fa-chevron-up]=\"!IsCollapsed\" [class.fa-chevron-down]=\"IsCollapsed\"></i>\n </button>\n }\n </div>\n </div>\n\n <!-- Panel Content -->\n @if (!IsCollapsed) {\n <div class=\"panel-content\">\n <!-- Empty state -->\n @if (CardAggregates.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n <p>No summaries configured</p>\n </div>\n }\n <!-- Cards Container -->\n <div class=\"cards-container\"\n [class.layout-horizontal]=\"Layout === 'horizontal'\"\n [class.layout-vertical]=\"Layout === 'vertical'\"\n [class.layout-grid]=\"Layout === 'grid'\"\n [ngStyle]=\"Layout === 'grid' ? {'grid-template-columns': GridTemplateColumns} : {}\">\n <!-- Individual Aggregate Cards -->\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\"\n [class]=\"GetStyleClass(agg)\"\n (click)=\"OnAggregateClick(agg)\"\n [title]=\"agg.description || agg.label\">\n <!-- Icon -->\n @if (agg.icon) {\n <div class=\"card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <!-- Content -->\n <div class=\"card-content\">\n <div class=\"card-value\" [class.loading]=\"Loading\">\n {{ Loading ? '...' : FormatValue(agg) }}\n </div>\n <div class=\"card-label\">{{ agg.label }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: ["/* Aggregate Panel Component Styles */\n\n.aggregate-panel {\n display: flex;\n flex-direction: column;\n background: var(--mj-
|
|
340
|
+
args: [{ standalone: false, selector: 'mj-aggregate-panel', template: "<div class=\"aggregate-panel\"\n [class.position-right]=\"Position === 'right'\"\n [class.position-bottom]=\"Position === 'bottom'\"\n [class.collapsed]=\"IsCollapsed\"\n [ngStyle]=\"PanelStyle\">\n\n <!-- Panel Header -->\n <div class=\"panel-header\" (click)=\"ToggleCollapse()\">\n <span class=\"panel-title\">{{ Title }}</span>\n\n <div class=\"panel-header-right\">\n <!-- Loading indicator -->\n @if (Loading) {\n <span class=\"loading-indicator\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </span>\n }\n\n <!-- Collapse toggle -->\n @if (Collapsible) {\n <button class=\"collapse-toggle\" type=\"button\">\n <i class=\"fa-solid\" [class.fa-chevron-up]=\"!IsCollapsed\" [class.fa-chevron-down]=\"IsCollapsed\"></i>\n </button>\n }\n </div>\n </div>\n\n <!-- Panel Content -->\n @if (!IsCollapsed) {\n <div class=\"panel-content\">\n <!-- Empty state -->\n @if (CardAggregates.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n <p>No summaries configured</p>\n </div>\n }\n <!-- Cards Container -->\n <div class=\"cards-container\"\n [class.layout-horizontal]=\"Layout === 'horizontal'\"\n [class.layout-vertical]=\"Layout === 'vertical'\"\n [class.layout-grid]=\"Layout === 'grid'\"\n [ngStyle]=\"Layout === 'grid' ? {'grid-template-columns': GridTemplateColumns} : {}\">\n <!-- Individual Aggregate Cards -->\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\"\n [class]=\"GetStyleClass(agg)\"\n (click)=\"OnAggregateClick(agg)\"\n [title]=\"agg.description || agg.label\">\n <!-- Icon -->\n @if (agg.icon) {\n <div class=\"card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <!-- Content -->\n <div class=\"card-content\">\n <div class=\"card-value\" [class.loading]=\"Loading\">\n {{ Loading ? '...' : FormatValue(agg) }}\n </div>\n <div class=\"card-label\">{{ agg.label }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: ["/* Aggregate Panel Component Styles */\n\n.aggregate-panel {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n/* Position variants */\n.position-right {\n height: 100%;\n border-left-width: 1px;\n border-radius: 0 8px 8px 0;\n}\n\n.position-bottom {\n width: 100%;\n border-top-width: 1px;\n border-radius: 0 0 8px 8px;\n}\n\n/* Collapsed state */\n.collapsed .panel-content {\n display: none;\n}\n\n.collapsed.position-right {\n width: auto !important;\n min-width: auto !important;\n}\n\n/* Panel Header */\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n}\n\n.panel-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.panel-title {\n font-weight: 600;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n.panel-header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.loading-indicator {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n.collapse-toggle {\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 12px;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n}\n\n.collapse-toggle:hover {\n background: rgba(0, 0, 0, 0.05);\n color: var(--mj-text-primary);\n}\n\n/* Panel Content */\n.panel-content {\n padding: 16px;\n overflow-y: auto;\n flex: 1;\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 24px;\n color: var(--mj-text-disabled);\n text-align: center;\n}\n\n.empty-state i {\n font-size: 32px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.empty-state p {\n margin: 0;\n font-size: 13px;\n}\n\n/* Cards Container */\n.cards-container {\n display: flex;\n gap: 12px;\n}\n\n.layout-horizontal {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.layout-vertical {\n flex-direction: column;\n}\n\n.layout-grid {\n display: grid;\n gap: 12px;\n}\n\n/* Aggregate Card */\n.aggregate-card {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;\n}\n\n.aggregate-card:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n transform: translateY(-1px);\n}\n\n/* Card Icon */\n.card-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n color: var(--mj-brand-primary);\n font-size: 18px;\n flex-shrink: 0;\n}\n\n/* Card Content */\n.card-content {\n flex: 1;\n min-width: 0;\n}\n\n.card-value {\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.2;\n margin-bottom: 4px;\n word-break: break-word;\n}\n\n.card-value.loading {\n color: var(--mj-text-disabled);\n}\n\n.card-label {\n font-size: 12px;\n color: var(--mj-text-secondary);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Conditional Style Classes */\n.style-success {\n border-left: 4px solid var(--mj-status-success);\n}\n\n.style-success .card-value {\n color: var(--mj-status-success);\n}\n\n.style-success .card-icon {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success);\n}\n\n.style-warning {\n border-left: 4px solid var(--mj-status-warning);\n}\n\n.style-warning .card-value {\n color: var(--mj-status-warning-text);\n}\n\n.style-warning .card-icon {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.style-danger {\n border-left: 4px solid var(--mj-status-error);\n}\n\n.style-danger .card-value {\n color: var(--mj-status-error);\n}\n\n.style-danger .card-icon {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n.style-info {\n border-left: 4px solid var(--mj-brand-primary);\n}\n\n.style-info .card-value {\n color: var(--mj-brand-primary);\n}\n\n.style-info .card-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.style-muted {\n border-left: 4px solid var(--mj-text-muted);\n}\n\n.style-muted .card-value {\n color: var(--mj-text-muted);\n}\n\n.style-muted .card-icon {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n/* Responsive adjustments for bottom position */\n.position-bottom .cards-container.layout-vertical {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.position-bottom .cards-container.layout-vertical .aggregate-card {\n flex: 1 1 200px;\n max-width: 300px;\n}\n"] }]
|
|
341
341
|
}], null, { Aggregates: [{
|
|
342
342
|
type: Input
|
|
343
343
|
}], Values: [{
|
|
@@ -931,11 +931,11 @@ export class AggregateSetupDialogComponent {
|
|
|
931
931
|
i0.ɵɵtextInterpolate1("", ctx.Aggregate ? "Update" : "Add", " Aggregate");
|
|
932
932
|
i0.ɵɵadvance(3);
|
|
933
933
|
i0.ɵɵconditional(ctx.ValidationMessage ? 74 : -1);
|
|
934
|
-
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 520px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid #e5e7eb;\n background: #f8fafc;\n border-radius: 16px 16px 0 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: #1f2937;\n}\n\n.header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n font-size: 18px;\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: #e5e7eb;\n color: #374151;\n}\n\n\n\n.mode-selector[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n padding: 16px 24px;\n background: #f8fafc;\n border-bottom: 1px solid #e5e7eb;\n}\n\n.mode-btn[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 16px;\n border: 2px solid #e5e7eb;\n border-radius: 12px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mode-btn[_ngcontent-%COMP%]:hover {\n border-color: #d1d5db;\n background: #fafafa;\n}\n\n.mode-btn.active[_ngcontent-%COMP%] {\n border-color: #3b82f6;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n}\n\n.mode-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n color: #6b7280;\n}\n\n.mode-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n.mode-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: #374151;\n}\n\n.mode-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: #1d4ed8;\n}\n\n\n\n.dialog-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 20px 24px;\n}\n\n\n\n.mode-content[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n}\n\n.mode-description[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 12px 14px;\n background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n border: 1px solid #bae6fd;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n color: #0369a1;\n line-height: 1.5;\n}\n\n.mode-description[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #0284c7;\n margin-top: 2px;\n flex-shrink: 0;\n}\n\n\n\n.form-section[_ngcontent-%COMP%] {\n margin-bottom: 18px;\n}\n\n.form-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 600;\n color: #374151;\n}\n\n.form-label.required[_ngcontent-%COMP%]::after {\n content: ' *';\n color: #dc2626;\n}\n\n.form-label[_ngcontent-%COMP%] .optional[_ngcontent-%COMP%] {\n font-weight: 400;\n color: #9ca3af;\n}\n\n.form-label.examples-label[_ngcontent-%COMP%] {\n color: #6b7280;\n}\n\n\n\n.form-input[_ngcontent-%COMP%], \n.form-select[_ngcontent-%COMP%], \n.form-textarea[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 14px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n font-size: 14px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n background: white;\n}\n\n.form-input[_ngcontent-%COMP%]:focus, \n.form-select[_ngcontent-%COMP%]:focus, \n.form-textarea[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n}\n\n.form-textarea[_ngcontent-%COMP%] {\n resize: vertical;\n min-height: 80px;\n}\n\n.form-textarea.code-input[_ngcontent-%COMP%] {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n}\n\n\n\n.function-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n}\n\n.function-btn[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 10px;\n border: 2px solid #e5e7eb;\n border-radius: 10px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.function-btn[_ngcontent-%COMP%]:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.function-btn.active[_ngcontent-%COMP%] {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.function-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n color: #6b7280;\n}\n\n.function-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n.function-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: #4b5563;\n}\n\n.function-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: #1d4ed8;\n}\n\n\n\n.field-hint[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 8px;\n padding: 10px 12px;\n background: #f8fafc;\n border-radius: 6px;\n font-size: 12px;\n color: #64748b;\n}\n\n.field-hint.warning[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: #92400e;\n}\n\n.field-hint.warning[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #f59e0b;\n}\n\n.field-hint.info[_ngcontent-%COMP%] {\n background: #eff6ff;\n color: #1e40af;\n}\n\n.field-hint.info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n.field-hint[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #94a3b8;\n margin-top: 1px;\n flex-shrink: 0;\n}\n\n\n\n.expression-preview[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 14px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n}\n\n.preview-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.preview-code[_ngcontent-%COMP%] {\n display: block;\n padding: 12px 14px;\n background: white;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: #1e293b;\n}\n\n\n\n.example-chips[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.example-chip[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n background: white;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 11px;\n color: #4b5563;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.example-chip[_ngcontent-%COMP%]:hover {\n border-color: #3b82f6;\n background: #eff6ff;\n color: #1d4ed8;\n}\n\n\n\n.smart-input-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.smart-input[_ngcontent-%COMP%] {\n min-height: 100px;\n}\n\n.smart-input[_ngcontent-%COMP%]:disabled {\n background: #f3f4f6;\n color: #6b7280;\n}\n\n.generate-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 20px;\n background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);\n border: none;\n border-radius: 10px;\n color: white;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n align-self: flex-start;\n}\n\n.generate-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35);\n}\n\n.generate-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n\n\n.generated-section[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 16px;\n background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);\n border: 1px solid #86efac;\n border-radius: 10px;\n}\n\n.generated-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.generated-header[_ngcontent-%COMP%] .form-label[_ngcontent-%COMP%] {\n margin-bottom: 0;\n color: #166534;\n}\n\n.clear-generated-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid #86efac;\n border-radius: 6px;\n background: white;\n font-size: 12px;\n font-weight: 500;\n color: #166534;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.clear-generated-btn[_ngcontent-%COMP%]:hover {\n background: #166534;\n color: white;\n}\n\n.generated-code[_ngcontent-%COMP%] {\n display: block;\n padding: 12px 14px;\n background: white;\n border: 1px solid #86efac;\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: #14532d;\n margin-bottom: 10px;\n}\n\n.generated-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n color: #166534;\n}\n\n.generated-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-top: 1px;\n}\n\n\n\n.smart-tips[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding: 14px;\n background: #fffbeb;\n border: 1px solid #fde68a;\n border-radius: 10px;\n}\n\n.tips-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n font-size: 13px;\n font-weight: 600;\n color: #92400e;\n}\n\n.tips-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #f59e0b;\n}\n\n.tips-list[_ngcontent-%COMP%] {\n margin: 0;\n padding-left: 20px;\n font-size: 12px;\n color: #78350f;\n line-height: 1.6;\n}\n\n\n\n.config-section[_ngcontent-%COMP%] {\n padding-top: 16px;\n}\n\n.section-divider[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n}\n\n.section-divider[_ngcontent-%COMP%]::before, \n.section-divider[_ngcontent-%COMP%]::after {\n content: '';\n flex: 1;\n height: 1px;\n background: #e5e7eb;\n}\n\n.section-divider[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: #9ca3af;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n\n\n.display-type-toggle[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n}\n\n.display-type-btn[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 16px 14px;\n border: 2px solid #e5e7eb;\n border-radius: 12px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.display-type-btn[_ngcontent-%COMP%]:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.display-type-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 22px;\n color: #6b7280;\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n.display-type-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: #374151;\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: #1d4ed8;\n}\n\n.display-type-btn[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #9ca3af;\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n color: #60a5fa;\n}\n\n\n\n.icon-selector[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.selected-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n background: #eff6ff;\n border: 1px solid #93c5fd;\n border-radius: 8px;\n width: fit-content;\n}\n\n.selected-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child {\n font-size: 20px;\n color: #3b82f6;\n}\n\n.clear-icon-btn[_ngcontent-%COMP%] {\n width: 24px;\n height: 24px;\n border: none;\n background: white;\n border-radius: 50%;\n cursor: pointer;\n color: #9ca3af;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n transition: all 0.15s ease;\n}\n\n.clear-icon-btn[_ngcontent-%COMP%]:hover {\n background: #dc2626;\n color: white;\n}\n\n.icon-grid[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.icon-btn[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.icon-btn[_ngcontent-%COMP%]:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.icon-btn.active[_ngcontent-%COMP%] {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.icon-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n color: #6b7280;\n}\n\n.icon-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-top: 1px solid #e5e7eb;\n background: #f8fafc;\n border-radius: 0 0 16px 16px;\n}\n\n.footer-left[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n}\n\n.footer-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 11px 18px;\n border: 1px solid #d1d5db;\n border-radius: 10px;\n background: white;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.footer-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f3f4f6;\n border-color: #9ca3af;\n}\n\n.footer-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.footer-btn.primary[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\n border-color: #3b82f6;\n color: white;\n}\n\n.footer-btn.primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\n border-color: #2563eb;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35);\n}\n\n\n\n.validation-message[_ngcontent-%COMP%] {\n position: absolute;\n bottom: 80px;\n left: 24px;\n right: 24px;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n font-size: 13px;\n color: #dc2626;\n}\n\n.validation-message[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n@media (max-width: 600px) {\n .dialog-panel[_ngcontent-%COMP%] {\n width: calc(100vw - 24px);\n max-height: calc(100vh - 40px);\n border-radius: 12px;\n }\n\n .dialog-header[_ngcontent-%COMP%], \n .dialog-footer[_ngcontent-%COMP%] {\n border-radius: 0;\n }\n\n .dialog-header[_ngcontent-%COMP%] {\n border-radius: 12px 12px 0 0;\n }\n\n .dialog-footer[_ngcontent-%COMP%] {\n border-radius: 0 0 12px 12px;\n }\n\n .mode-btn[_ngcontent-%COMP%] {\n padding: 10px 8px;\n }\n\n .mode-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .function-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .display-type-toggle[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n}"] });
|
|
934
|
+
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 520px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n\n\n\n.mode-selector[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n padding: 16px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mode-btn[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 16px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mode-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.mode-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.mode-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n color: var(--mj-text-muted);\n}\n\n.mode-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.mode-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mode-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: var(--mj-color-info-700);\n}\n\n\n\n.dialog-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 20px 24px;\n}\n\n\n\n.mode-content[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n}\n\n.mode-description[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 12px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface));\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n color: var(--mj-color-info-700);\n line-height: 1.5;\n}\n\n.mode-description[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n flex-shrink: 0;\n}\n\n\n\n.form-section[_ngcontent-%COMP%] {\n margin-bottom: 18px;\n}\n\n.form-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-label.required[_ngcontent-%COMP%]::after {\n content: ' *';\n color: var(--mj-status-error);\n}\n\n.form-label[_ngcontent-%COMP%] .optional[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--mj-text-disabled);\n}\n\n.form-label.examples-label[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n}\n\n\n\n.form-input[_ngcontent-%COMP%], \n.form-select[_ngcontent-%COMP%], \n.form-textarea[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n background: var(--mj-bg-surface);\n}\n\n.form-input[_ngcontent-%COMP%]:focus, \n.form-select[_ngcontent-%COMP%]:focus, \n.form-textarea[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.form-textarea[_ngcontent-%COMP%] {\n resize: vertical;\n min-height: 80px;\n}\n\n.form-textarea.code-input[_ngcontent-%COMP%] {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n}\n\n\n\n.function-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n}\n\n.function-btn[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 10px;\n border: 2px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.function-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.function-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.function-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 18px;\n color: var(--mj-text-muted);\n}\n\n.function-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.function-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.function-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: var(--mj-color-info-700);\n}\n\n\n\n.field-hint[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 8px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-card);\n border-radius: 6px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.field-hint.warning[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.field-hint.warning[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.field-hint.info[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.field-hint.info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.field-hint[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n margin-top: 1px;\n flex-shrink: 0;\n}\n\n\n\n.expression-preview[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n}\n\n.preview-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.preview-code[_ngcontent-%COMP%] {\n display: block;\n padding: 12px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n\n\n.example-chips[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.example-chip[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 11px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.example-chip[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-color-info-700);\n}\n\n\n\n.smart-input-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.smart-input[_ngcontent-%COMP%] {\n min-height: 100px;\n}\n\n.smart-input[_ngcontent-%COMP%]:disabled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.generate-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 20px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 10px;\n color: var(--mj-text-inverse);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n align-self: flex-start;\n}\n\n.generate-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.generate-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n\n\n.generated-section[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 16px;\n background: var(--mj-status-success-bg);\n border: 1px solid var(--mj-status-success-border);\n border-radius: 10px;\n}\n\n.generated-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.generated-header[_ngcontent-%COMP%] .form-label[_ngcontent-%COMP%] {\n margin-bottom: 0;\n color: var(--mj-status-success-text);\n}\n\n.clear-generated-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--mj-status-success-border);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-status-success-text);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.clear-generated-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-success-text);\n color: var(--mj-text-inverse);\n}\n\n.generated-code[_ngcontent-%COMP%] {\n display: block;\n padding: 12px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-success-border);\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: var(--mj-status-success-text);\n margin-bottom: 10px;\n}\n\n.generated-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n color: var(--mj-status-success-text);\n}\n\n.generated-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-top: 1px;\n}\n\n\n\n.smart-tips[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding: 14px;\n background: var(--mj-status-warning-bg);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: 10px;\n}\n\n.tips-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-status-warning-text);\n}\n\n.tips-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.tips-list[_ngcontent-%COMP%] {\n margin: 0;\n padding-left: 20px;\n font-size: 12px;\n color: var(--mj-status-warning-text);\n line-height: 1.6;\n}\n\n\n\n.config-section[_ngcontent-%COMP%] {\n padding-top: 16px;\n}\n\n.section-divider[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n}\n\n.section-divider[_ngcontent-%COMP%]::before, \n.section-divider[_ngcontent-%COMP%]::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--mj-border-default);\n}\n\n.section-divider[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n\n\n.display-type-toggle[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n}\n\n.display-type-btn[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 16px 14px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.display-type-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.display-type-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 22px;\n color: var(--mj-text-muted);\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.display-type-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n color: var(--mj-color-info-700);\n}\n\n.display-type-btn[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.display-type-btn.active[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n.icon-selector[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.selected-icon[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-bg-surface));\n border-radius: 8px;\n width: fit-content;\n}\n\n.selected-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.clear-icon-btn[_ngcontent-%COMP%] {\n width: 24px;\n height: 24px;\n border: none;\n background: var(--mj-bg-surface);\n border-radius: 50%;\n cursor: pointer;\n color: var(--mj-text-disabled);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n transition: all 0.15s ease;\n}\n\n.clear-icon-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.icon-grid[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.icon-btn[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.icon-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.icon-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.icon-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n color: var(--mj-text-muted);\n}\n\n.icon-btn.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n.footer-left[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n}\n\n.footer-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 11px 18px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.footer-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-text-disabled);\n}\n\n.footer-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.footer-btn.primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.footer-btn.primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n border-color: var(--mj-color-info-600);\n transform: translateY(-1px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n\n\n.validation-message[_ngcontent-%COMP%] {\n position: absolute;\n bottom: 80px;\n left: 24px;\n right: 24px;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n font-size: 13px;\n color: var(--mj-status-error);\n}\n\n.validation-message[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n@media (max-width: 600px) {\n .dialog-panel[_ngcontent-%COMP%] {\n width: calc(100vw - 24px);\n max-height: calc(100vh - 40px);\n border-radius: 12px;\n }\n\n .dialog-header[_ngcontent-%COMP%], \n .dialog-footer[_ngcontent-%COMP%] {\n border-radius: 0;\n }\n\n .dialog-header[_ngcontent-%COMP%] {\n border-radius: 12px 12px 0 0;\n }\n\n .dialog-footer[_ngcontent-%COMP%] {\n border-radius: 0 0 12px 12px;\n }\n\n .mode-btn[_ngcontent-%COMP%] {\n padding: 10px 8px;\n }\n\n .mode-btn[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .function-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .display-type-toggle[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n}"] });
|
|
935
935
|
}
|
|
936
936
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AggregateSetupDialogComponent, [{
|
|
937
937
|
type: Component,
|
|
938
|
-
args: [{ standalone: false, selector: 'mj-aggregate-setup-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"onClose()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n <span>{{ Aggregate ? 'Edit Aggregate' : 'Add Aggregate' }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"onClose()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Mode Selector -->\n <div class=\"mode-selector\">\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'simple'\"\n (click)=\"setMode('simple')\"\n title=\"Pick a column and function\">\n <i class=\"fa-solid fa-wand-sparkles\"></i>\n <span>Simple</span>\n </button>\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'advanced'\"\n (click)=\"setMode('advanced')\"\n title=\"Write custom SQL expression\">\n <i class=\"fa-solid fa-code\"></i>\n <span>Advanced</span>\n </button>\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'smart'\"\n (click)=\"setMode('smart')\"\n title=\"Describe in natural language\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Smart</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"dialog-content\">\n <!-- Simple Mode -->\n @if (Mode === 'simple') {\n <div class=\"mode-content simple-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>Select a column and an aggregate function to calculate</span>\n </div>\n\n <!-- Function Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Function</label>\n <div class=\"function-grid\">\n @for (func of AggregateFunctions; track func.value) {\n <button\n class=\"function-btn\"\n [class.active]=\"SelectedFunction === func.value\"\n (click)=\"selectFunction(func.value)\"\n [title]=\"func.label\">\n <i [class]=\"func.icon\"></i>\n <span>{{ func.label }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Column Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Column</label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"SelectedColumn\"\n (ngModelChange)=\"onColumnSelected($event)\">\n <option value=\"\">Select a column...</option>\n @for (field of AvailableFields; track field.ID) {\n <option [value]=\"field.Name\">{{ getFieldDisplayLabel(field) }}</option>\n }\n </select>\n @if (SelectedFunction !== 'COUNT' && SelectedFunction !== 'COUNT_DISTINCT' && NumericFields.length === 0) {\n <div class=\"field-hint warning\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>No numeric fields available. Use COUNT or switch to Advanced mode.</span>\n </div>\n }\n @if (SelectedFunction === 'COUNT' && !SelectedColumn) {\n <div class=\"field-hint info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>No column selected - will count all records (COUNT(*))</span>\n </div>\n }\n </div>\n\n <!-- Preview -->\n @if (SelectedFunction && (SelectedColumn || SelectedFunction === 'COUNT')) {\n <div class=\"expression-preview\">\n <label class=\"preview-label\">Expression Preview</label>\n <code class=\"preview-code\">{{\n !SelectedColumn && SelectedFunction === 'COUNT' ? 'COUNT(*)' :\n SelectedFunction === 'COUNT_DISTINCT' ? 'COUNT(DISTINCT ' + SelectedColumn + ')' :\n SelectedFunction + '(' + SelectedColumn + ')'\n }}</code>\n </div>\n }\n </div>\n }\n\n <!-- Advanced Mode -->\n @if (Mode === 'advanced') {\n <div class=\"mode-content advanced-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>Write a custom SQL aggregate expression. Use field names as they appear in the database.</span>\n </div>\n\n <div class=\"form-section\">\n <label class=\"form-label\">SQL Expression</label>\n <textarea\n class=\"form-textarea code-input\"\n [(ngModel)]=\"Expression\"\n placeholder=\"e.g., SUM(Amount * Quantity) or COUNT(CASE WHEN Status = 'Active' THEN 1 END)\"\n rows=\"3\"\n ></textarea>\n <div class=\"field-hint\">\n <i class=\"fa-solid fa-lightbulb\"></i>\n <span>Tip: Use brackets for column names with spaces, e.g., [Total Amount]</span>\n </div>\n </div>\n\n <!-- Quick Examples -->\n <div class=\"form-section\">\n <label class=\"form-label examples-label\">Quick Examples</label>\n <div class=\"example-chips\">\n <button class=\"example-chip\" (click)=\"Expression = 'SUM(Amount)'\">\n SUM(Amount)\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'AVG(Price * Quantity)'\">\n AVG(Price * Quantity)\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'COUNT(CASE WHEN Status = \\'Active\\' THEN 1 END)'\">\n COUNT with condition\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'SUM(CASE WHEN Type = \\'Credit\\' THEN Amount ELSE -Amount END)'\">\n Conditional SUM\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Smart Mode -->\n @if (Mode === 'smart') {\n <div class=\"mode-content smart-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Describe what you want to calculate in plain English. AI will generate the expression.</span>\n </div>\n\n <div class=\"form-section\">\n <label class=\"form-label\">What would you like to calculate?</label>\n <div class=\"smart-input-container\">\n <textarea\n class=\"form-textarea smart-input\"\n [(ngModel)]=\"SmartPrompt\"\n [disabled]=\"!!GeneratedExpression\"\n placeholder=\"e.g., 'Total revenue from completed orders' or 'Average order value for premium customers'\"\n rows=\"3\"\n ></textarea>\n @if (!GeneratedExpression) {\n <button\n class=\"generate-btn\"\n [disabled]=\"!SmartPrompt.trim() || IsGenerating\"\n (click)=\"onGenerateFromPrompt()\">\n @if (IsGenerating) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Generating...</span>\n } @else {\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Generate</span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Generated Expression -->\n @if (GeneratedExpression) {\n <div class=\"generated-section\">\n <div class=\"generated-header\">\n <label class=\"form-label\">Generated Expression</label>\n <button class=\"clear-generated-btn\" (click)=\"clearGeneratedExpression()\" title=\"Edit prompt\">\n <i class=\"fa-solid fa-pen\"></i>\n <span>Edit Prompt</span>\n </button>\n </div>\n <code class=\"generated-code\">{{ GeneratedExpression }}</code>\n <div class=\"generated-info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>This expression was generated from your description. Clear it to edit the prompt.</span>\n </div>\n </div>\n }\n\n <!-- Smart Mode Tips -->\n <div class=\"smart-tips\">\n <div class=\"tips-header\">\n <i class=\"fa-solid fa-lightbulb\"></i>\n <span>Tips for better results:</span>\n </div>\n <ul class=\"tips-list\">\n <li>Be specific about which fields to use</li>\n <li>Mention any conditions or filters</li>\n <li>Specify the type of calculation (sum, average, count, etc.)</li>\n </ul>\n </div>\n </div>\n }\n\n <!-- Common Configuration -->\n <div class=\"config-section\">\n <div class=\"section-divider\">\n <span>Display Options</span>\n </div>\n\n <!-- Label -->\n <div class=\"form-section\">\n <label class=\"form-label required\">Label</label>\n <input\n type=\"text\"\n class=\"form-input\"\n [(ngModel)]=\"Label\"\n placeholder=\"e.g., Total Revenue, Average Order Value\"\n />\n </div>\n\n <!-- Description (optional) -->\n <div class=\"form-section\">\n <label class=\"form-label\">Description <span class=\"optional\">(optional)</span></label>\n <input\n type=\"text\"\n class=\"form-input\"\n [(ngModel)]=\"Description\"\n placeholder=\"Brief explanation of what this shows\"\n />\n </div>\n\n <!-- Display Type -->\n <div class=\"form-section\">\n <label class=\"form-label\">Display As</label>\n <div class=\"display-type-toggle\">\n <button\n class=\"display-type-btn\"\n [class.active]=\"DisplayType === 'card'\"\n (click)=\"DisplayType = 'card'\">\n <i class=\"fa-solid fa-id-card\"></i>\n <span>Card</span>\n <small>Shows in summary panel</small>\n </button>\n <button\n class=\"display-type-btn\"\n [class.active]=\"DisplayType === 'column'\"\n (click)=\"DisplayType = 'column'\">\n <i class=\"fa-solid fa-table-columns\"></i>\n <span>Column Footer</span>\n <small>Shows at bottom of grid</small>\n </button>\n </div>\n </div>\n\n <!-- Icon Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Icon <span class=\"optional\">(optional)</span></label>\n <div class=\"icon-selector\">\n @if (Icon) {\n <div class=\"selected-icon\">\n <i [class]=\"Icon\"></i>\n <button class=\"clear-icon-btn\" (click)=\"clearIcon()\" title=\"Remove icon\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n <div class=\"icon-grid\">\n @for (icon of CommonIcons; track icon) {\n <button\n class=\"icon-btn\"\n [class.active]=\"Icon === icon\"\n (click)=\"selectIcon(icon)\"\n [title]=\"icon\">\n <i [class]=\"icon\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <div class=\"footer-left\">\n <button\n class=\"footer-btn save-btn primary\"\n [disabled]=\"!IsValid\"\n (click)=\"onSave()\">\n <i class=\"fa-solid fa-check\"></i>\n <span>{{ Aggregate ? 'Update' : 'Add' }} Aggregate</span>\n </button>\n </div>\n <button class=\"footer-btn cancel-btn\" (click)=\"onClose()\">\n Cancel\n </button>\n </div>\n\n <!-- Validation Message -->\n @if (ValidationMessage) {\n <div class=\"validation-message\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n <span>{{ ValidationMessage }}</span>\n </div>\n }\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 520px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n/* Dialog Header */\n.dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid #e5e7eb;\n background: #f8fafc;\n border-radius: 16px 16px 0 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: #1f2937;\n}\n\n.header-title i {\n color: #3b82f6;\n font-size: 18px;\n}\n\n.close-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: #6b7280;\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn:hover {\n background: #e5e7eb;\n color: #374151;\n}\n\n/* Mode Selector */\n.mode-selector {\n display: flex;\n gap: 8px;\n padding: 16px 24px;\n background: #f8fafc;\n border-bottom: 1px solid #e5e7eb;\n}\n\n.mode-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 16px;\n border: 2px solid #e5e7eb;\n border-radius: 12px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mode-btn:hover {\n border-color: #d1d5db;\n background: #fafafa;\n}\n\n.mode-btn.active {\n border-color: #3b82f6;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n}\n\n.mode-btn i {\n font-size: 18px;\n color: #6b7280;\n}\n\n.mode-btn.active i {\n color: #3b82f6;\n}\n\n.mode-btn span {\n font-size: 13px;\n font-weight: 600;\n color: #374151;\n}\n\n.mode-btn.active span {\n color: #1d4ed8;\n}\n\n/* Dialog Content */\n.dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px 24px;\n}\n\n/* Mode Content */\n.mode-content {\n margin-bottom: 20px;\n}\n\n.mode-description {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 12px 14px;\n background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);\n border: 1px solid #bae6fd;\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n color: #0369a1;\n line-height: 1.5;\n}\n\n.mode-description i {\n color: #0284c7;\n margin-top: 2px;\n flex-shrink: 0;\n}\n\n/* Form Sections */\n.form-section {\n margin-bottom: 18px;\n}\n\n.form-label {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 600;\n color: #374151;\n}\n\n.form-label.required::after {\n content: ' *';\n color: #dc2626;\n}\n\n.form-label .optional {\n font-weight: 400;\n color: #9ca3af;\n}\n\n.form-label.examples-label {\n color: #6b7280;\n}\n\n/* Form Inputs */\n.form-input,\n.form-select,\n.form-textarea {\n width: 100%;\n padding: 10px 14px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n font-size: 14px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n background: white;\n}\n\n.form-input:focus,\n.form-select:focus,\n.form-textarea:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n}\n\n.form-textarea {\n resize: vertical;\n min-height: 80px;\n}\n\n.form-textarea.code-input {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n}\n\n/* Function Grid */\n.function-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n}\n\n.function-btn {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 10px;\n border: 2px solid #e5e7eb;\n border-radius: 10px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.function-btn:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.function-btn.active {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.function-btn i {\n font-size: 18px;\n color: #6b7280;\n}\n\n.function-btn.active i {\n color: #3b82f6;\n}\n\n.function-btn span {\n font-size: 12px;\n font-weight: 500;\n color: #4b5563;\n}\n\n.function-btn.active span {\n color: #1d4ed8;\n}\n\n/* Field Hint */\n.field-hint {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 8px;\n padding: 10px 12px;\n background: #f8fafc;\n border-radius: 6px;\n font-size: 12px;\n color: #64748b;\n}\n\n.field-hint.warning {\n background: #fffbeb;\n color: #92400e;\n}\n\n.field-hint.warning i {\n color: #f59e0b;\n}\n\n.field-hint.info {\n background: #eff6ff;\n color: #1e40af;\n}\n\n.field-hint.info i {\n color: #3b82f6;\n}\n\n.field-hint i {\n color: #94a3b8;\n margin-top: 1px;\n flex-shrink: 0;\n}\n\n/* Expression Preview */\n.expression-preview {\n margin-top: 16px;\n padding: 14px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n}\n\n.preview-label {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.preview-code {\n display: block;\n padding: 12px 14px;\n background: white;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: #1e293b;\n}\n\n/* Example Chips */\n.example-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.example-chip {\n padding: 8px 12px;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n background: white;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 11px;\n color: #4b5563;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.example-chip:hover {\n border-color: #3b82f6;\n background: #eff6ff;\n color: #1d4ed8;\n}\n\n/* Smart Mode Input */\n.smart-input-container {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.smart-input {\n min-height: 100px;\n}\n\n.smart-input:disabled {\n background: #f3f4f6;\n color: #6b7280;\n}\n\n.generate-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 20px;\n background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);\n border: none;\n border-radius: 10px;\n color: white;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n align-self: flex-start;\n}\n\n.generate-btn:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35);\n}\n\n.generate-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n/* Generated Section */\n.generated-section {\n margin-top: 16px;\n padding: 16px;\n background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);\n border: 1px solid #86efac;\n border-radius: 10px;\n}\n\n.generated-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.generated-header .form-label {\n margin-bottom: 0;\n color: #166534;\n}\n\n.clear-generated-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid #86efac;\n border-radius: 6px;\n background: white;\n font-size: 12px;\n font-weight: 500;\n color: #166534;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.clear-generated-btn:hover {\n background: #166534;\n color: white;\n}\n\n.generated-code {\n display: block;\n padding: 12px 14px;\n background: white;\n border: 1px solid #86efac;\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: #14532d;\n margin-bottom: 10px;\n}\n\n.generated-info {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n color: #166534;\n}\n\n.generated-info i {\n margin-top: 1px;\n}\n\n/* Smart Tips */\n.smart-tips {\n margin-top: 20px;\n padding: 14px;\n background: #fffbeb;\n border: 1px solid #fde68a;\n border-radius: 10px;\n}\n\n.tips-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n font-size: 13px;\n font-weight: 600;\n color: #92400e;\n}\n\n.tips-header i {\n color: #f59e0b;\n}\n\n.tips-list {\n margin: 0;\n padding-left: 20px;\n font-size: 12px;\n color: #78350f;\n line-height: 1.6;\n}\n\n/* Config Section */\n.config-section {\n padding-top: 16px;\n}\n\n.section-divider {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n}\n\n.section-divider::before,\n.section-divider::after {\n content: '';\n flex: 1;\n height: 1px;\n background: #e5e7eb;\n}\n\n.section-divider span {\n font-size: 12px;\n font-weight: 600;\n color: #9ca3af;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n/* Display Type Toggle */\n.display-type-toggle {\n display: flex;\n gap: 12px;\n}\n\n.display-type-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 16px 14px;\n border: 2px solid #e5e7eb;\n border-radius: 12px;\n background: white;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.display-type-btn:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.display-type-btn.active {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.display-type-btn i {\n font-size: 22px;\n color: #6b7280;\n}\n\n.display-type-btn.active i {\n color: #3b82f6;\n}\n\n.display-type-btn span {\n font-size: 14px;\n font-weight: 600;\n color: #374151;\n}\n\n.display-type-btn.active span {\n color: #1d4ed8;\n}\n\n.display-type-btn small {\n font-size: 11px;\n color: #9ca3af;\n}\n\n.display-type-btn.active small {\n color: #60a5fa;\n}\n\n/* Icon Selector */\n.icon-selector {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.selected-icon {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n background: #eff6ff;\n border: 1px solid #93c5fd;\n border-radius: 8px;\n width: fit-content;\n}\n\n.selected-icon i:first-child {\n font-size: 20px;\n color: #3b82f6;\n}\n\n.clear-icon-btn {\n width: 24px;\n height: 24px;\n border: none;\n background: white;\n border-radius: 50%;\n cursor: pointer;\n color: #9ca3af;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n transition: all 0.15s ease;\n}\n\n.clear-icon-btn:hover {\n background: #dc2626;\n color: white;\n}\n\n.icon-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.icon-btn:hover {\n border-color: #d1d5db;\n background: #f9fafb;\n}\n\n.icon-btn.active {\n border-color: #3b82f6;\n background: #eff6ff;\n}\n\n.icon-btn i {\n font-size: 16px;\n color: #6b7280;\n}\n\n.icon-btn.active i {\n color: #3b82f6;\n}\n\n/* Dialog Footer */\n.dialog-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-top: 1px solid #e5e7eb;\n background: #f8fafc;\n border-radius: 0 0 16px 16px;\n}\n\n.footer-left {\n display: flex;\n gap: 10px;\n}\n\n.footer-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 11px 18px;\n border: 1px solid #d1d5db;\n border-radius: 10px;\n background: white;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.footer-btn:hover:not(:disabled) {\n background: #f3f4f6;\n border-color: #9ca3af;\n}\n\n.footer-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.footer-btn.primary {\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\n border-color: #3b82f6;\n color: white;\n}\n\n.footer-btn.primary:hover:not(:disabled) {\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\n border-color: #2563eb;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35);\n}\n\n/* Validation Message */\n.validation-message {\n position: absolute;\n bottom: 80px;\n left: 24px;\n right: 24px;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n font-size: 13px;\n color: #dc2626;\n}\n\n.validation-message i {\n flex-shrink: 0;\n}\n\n/* Responsive */\n@media (max-width: 600px) {\n .dialog-panel {\n width: calc(100vw - 24px);\n max-height: calc(100vh - 40px);\n border-radius: 12px;\n }\n\n .dialog-header,\n .dialog-footer {\n border-radius: 0;\n }\n\n .dialog-header {\n border-radius: 12px 12px 0 0;\n }\n\n .dialog-footer {\n border-radius: 0 0 12px 12px;\n }\n\n .mode-btn {\n padding: 10px 8px;\n }\n\n .mode-btn span {\n font-size: 11px;\n }\n\n .function-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .display-type-toggle {\n flex-direction: column;\n }\n}\n"] }]
|
|
938
|
+
args: [{ standalone: false, selector: 'mj-aggregate-setup-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"onClose()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n <span>{{ Aggregate ? 'Edit Aggregate' : 'Add Aggregate' }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"onClose()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Mode Selector -->\n <div class=\"mode-selector\">\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'simple'\"\n (click)=\"setMode('simple')\"\n title=\"Pick a column and function\">\n <i class=\"fa-solid fa-wand-sparkles\"></i>\n <span>Simple</span>\n </button>\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'advanced'\"\n (click)=\"setMode('advanced')\"\n title=\"Write custom SQL expression\">\n <i class=\"fa-solid fa-code\"></i>\n <span>Advanced</span>\n </button>\n <button\n class=\"mode-btn\"\n [class.active]=\"Mode === 'smart'\"\n (click)=\"setMode('smart')\"\n title=\"Describe in natural language\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Smart</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"dialog-content\">\n <!-- Simple Mode -->\n @if (Mode === 'simple') {\n <div class=\"mode-content simple-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>Select a column and an aggregate function to calculate</span>\n </div>\n\n <!-- Function Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Function</label>\n <div class=\"function-grid\">\n @for (func of AggregateFunctions; track func.value) {\n <button\n class=\"function-btn\"\n [class.active]=\"SelectedFunction === func.value\"\n (click)=\"selectFunction(func.value)\"\n [title]=\"func.label\">\n <i [class]=\"func.icon\"></i>\n <span>{{ func.label }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Column Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Column</label>\n <select\n class=\"form-select\"\n [(ngModel)]=\"SelectedColumn\"\n (ngModelChange)=\"onColumnSelected($event)\">\n <option value=\"\">Select a column...</option>\n @for (field of AvailableFields; track field.ID) {\n <option [value]=\"field.Name\">{{ getFieldDisplayLabel(field) }}</option>\n }\n </select>\n @if (SelectedFunction !== 'COUNT' && SelectedFunction !== 'COUNT_DISTINCT' && NumericFields.length === 0) {\n <div class=\"field-hint warning\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>No numeric fields available. Use COUNT or switch to Advanced mode.</span>\n </div>\n }\n @if (SelectedFunction === 'COUNT' && !SelectedColumn) {\n <div class=\"field-hint info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>No column selected - will count all records (COUNT(*))</span>\n </div>\n }\n </div>\n\n <!-- Preview -->\n @if (SelectedFunction && (SelectedColumn || SelectedFunction === 'COUNT')) {\n <div class=\"expression-preview\">\n <label class=\"preview-label\">Expression Preview</label>\n <code class=\"preview-code\">{{\n !SelectedColumn && SelectedFunction === 'COUNT' ? 'COUNT(*)' :\n SelectedFunction === 'COUNT_DISTINCT' ? 'COUNT(DISTINCT ' + SelectedColumn + ')' :\n SelectedFunction + '(' + SelectedColumn + ')'\n }}</code>\n </div>\n }\n </div>\n }\n\n <!-- Advanced Mode -->\n @if (Mode === 'advanced') {\n <div class=\"mode-content advanced-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>Write a custom SQL aggregate expression. Use field names as they appear in the database.</span>\n </div>\n\n <div class=\"form-section\">\n <label class=\"form-label\">SQL Expression</label>\n <textarea\n class=\"form-textarea code-input\"\n [(ngModel)]=\"Expression\"\n placeholder=\"e.g., SUM(Amount * Quantity) or COUNT(CASE WHEN Status = 'Active' THEN 1 END)\"\n rows=\"3\"\n ></textarea>\n <div class=\"field-hint\">\n <i class=\"fa-solid fa-lightbulb\"></i>\n <span>Tip: Use brackets for column names with spaces, e.g., [Total Amount]</span>\n </div>\n </div>\n\n <!-- Quick Examples -->\n <div class=\"form-section\">\n <label class=\"form-label examples-label\">Quick Examples</label>\n <div class=\"example-chips\">\n <button class=\"example-chip\" (click)=\"Expression = 'SUM(Amount)'\">\n SUM(Amount)\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'AVG(Price * Quantity)'\">\n AVG(Price * Quantity)\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'COUNT(CASE WHEN Status = \\'Active\\' THEN 1 END)'\">\n COUNT with condition\n </button>\n <button class=\"example-chip\" (click)=\"Expression = 'SUM(CASE WHEN Type = \\'Credit\\' THEN Amount ELSE -Amount END)'\">\n Conditional SUM\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Smart Mode -->\n @if (Mode === 'smart') {\n <div class=\"mode-content smart-mode\">\n <div class=\"mode-description\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Describe what you want to calculate in plain English. AI will generate the expression.</span>\n </div>\n\n <div class=\"form-section\">\n <label class=\"form-label\">What would you like to calculate?</label>\n <div class=\"smart-input-container\">\n <textarea\n class=\"form-textarea smart-input\"\n [(ngModel)]=\"SmartPrompt\"\n [disabled]=\"!!GeneratedExpression\"\n placeholder=\"e.g., 'Total revenue from completed orders' or 'Average order value for premium customers'\"\n rows=\"3\"\n ></textarea>\n @if (!GeneratedExpression) {\n <button\n class=\"generate-btn\"\n [disabled]=\"!SmartPrompt.trim() || IsGenerating\"\n (click)=\"onGenerateFromPrompt()\">\n @if (IsGenerating) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Generating...</span>\n } @else {\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Generate</span>\n }\n </button>\n }\n </div>\n </div>\n\n <!-- Generated Expression -->\n @if (GeneratedExpression) {\n <div class=\"generated-section\">\n <div class=\"generated-header\">\n <label class=\"form-label\">Generated Expression</label>\n <button class=\"clear-generated-btn\" (click)=\"clearGeneratedExpression()\" title=\"Edit prompt\">\n <i class=\"fa-solid fa-pen\"></i>\n <span>Edit Prompt</span>\n </button>\n </div>\n <code class=\"generated-code\">{{ GeneratedExpression }}</code>\n <div class=\"generated-info\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <span>This expression was generated from your description. Clear it to edit the prompt.</span>\n </div>\n </div>\n }\n\n <!-- Smart Mode Tips -->\n <div class=\"smart-tips\">\n <div class=\"tips-header\">\n <i class=\"fa-solid fa-lightbulb\"></i>\n <span>Tips for better results:</span>\n </div>\n <ul class=\"tips-list\">\n <li>Be specific about which fields to use</li>\n <li>Mention any conditions or filters</li>\n <li>Specify the type of calculation (sum, average, count, etc.)</li>\n </ul>\n </div>\n </div>\n }\n\n <!-- Common Configuration -->\n <div class=\"config-section\">\n <div class=\"section-divider\">\n <span>Display Options</span>\n </div>\n\n <!-- Label -->\n <div class=\"form-section\">\n <label class=\"form-label required\">Label</label>\n <input\n type=\"text\"\n class=\"form-input\"\n [(ngModel)]=\"Label\"\n placeholder=\"e.g., Total Revenue, Average Order Value\"\n />\n </div>\n\n <!-- Description (optional) -->\n <div class=\"form-section\">\n <label class=\"form-label\">Description <span class=\"optional\">(optional)</span></label>\n <input\n type=\"text\"\n class=\"form-input\"\n [(ngModel)]=\"Description\"\n placeholder=\"Brief explanation of what this shows\"\n />\n </div>\n\n <!-- Display Type -->\n <div class=\"form-section\">\n <label class=\"form-label\">Display As</label>\n <div class=\"display-type-toggle\">\n <button\n class=\"display-type-btn\"\n [class.active]=\"DisplayType === 'card'\"\n (click)=\"DisplayType = 'card'\">\n <i class=\"fa-solid fa-id-card\"></i>\n <span>Card</span>\n <small>Shows in summary panel</small>\n </button>\n <button\n class=\"display-type-btn\"\n [class.active]=\"DisplayType === 'column'\"\n (click)=\"DisplayType = 'column'\">\n <i class=\"fa-solid fa-table-columns\"></i>\n <span>Column Footer</span>\n <small>Shows at bottom of grid</small>\n </button>\n </div>\n </div>\n\n <!-- Icon Selection -->\n <div class=\"form-section\">\n <label class=\"form-label\">Icon <span class=\"optional\">(optional)</span></label>\n <div class=\"icon-selector\">\n @if (Icon) {\n <div class=\"selected-icon\">\n <i [class]=\"Icon\"></i>\n <button class=\"clear-icon-btn\" (click)=\"clearIcon()\" title=\"Remove icon\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n <div class=\"icon-grid\">\n @for (icon of CommonIcons; track icon) {\n <button\n class=\"icon-btn\"\n [class.active]=\"Icon === icon\"\n (click)=\"selectIcon(icon)\"\n [title]=\"icon\">\n <i [class]=\"icon\"></i>\n </button>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <div class=\"footer-left\">\n <button\n class=\"footer-btn save-btn primary\"\n [disabled]=\"!IsValid\"\n (click)=\"onSave()\">\n <i class=\"fa-solid fa-check\"></i>\n <span>{{ Aggregate ? 'Update' : 'Add' }} Aggregate</span>\n </button>\n </div>\n <button class=\"footer-btn cancel-btn\" (click)=\"onClose()\">\n Cancel\n </button>\n </div>\n\n <!-- Validation Message -->\n @if (ValidationMessage) {\n <div class=\"validation-message\">\n <i class=\"fa-solid fa-exclamation-circle\"></i>\n <span>{{ ValidationMessage }}</span>\n </div>\n }\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 520px;\n max-width: calc(100vw - 40px);\n max-height: calc(100vh - 60px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n/* Dialog Header */\n.dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title i {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn:hover {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n\n/* Mode Selector */\n.mode-selector {\n display: flex;\n gap: 8px;\n padding: 16px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mode-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 16px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mode-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.mode-btn.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.mode-btn i {\n font-size: 18px;\n color: var(--mj-text-muted);\n}\n\n.mode-btn.active i {\n color: var(--mj-brand-primary);\n}\n\n.mode-btn span {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mode-btn.active span {\n color: var(--mj-color-info-700);\n}\n\n/* Dialog Content */\n.dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px 24px;\n}\n\n/* Mode Content */\n.mode-content {\n margin-bottom: 20px;\n}\n\n.mode-description {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 12px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface));\n border-radius: 10px;\n margin-bottom: 20px;\n font-size: 13px;\n color: var(--mj-color-info-700);\n line-height: 1.5;\n}\n\n.mode-description i {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n flex-shrink: 0;\n}\n\n/* Form Sections */\n.form-section {\n margin-bottom: 18px;\n}\n\n.form-label {\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-label.required::after {\n content: ' *';\n color: var(--mj-status-error);\n}\n\n.form-label .optional {\n font-weight: 400;\n color: var(--mj-text-disabled);\n}\n\n.form-label.examples-label {\n color: var(--mj-text-muted);\n}\n\n/* Form Inputs */\n.form-input,\n.form-select,\n.form-textarea {\n width: 100%;\n padding: 10px 14px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n background: var(--mj-bg-surface);\n}\n\n.form-input:focus,\n.form-select:focus,\n.form-textarea:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.form-textarea {\n resize: vertical;\n min-height: 80px;\n}\n\n.form-textarea.code-input {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n}\n\n/* Function Grid */\n.function-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n}\n\n.function-btn {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 14px 10px;\n border: 2px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.function-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.function-btn.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.function-btn i {\n font-size: 18px;\n color: var(--mj-text-muted);\n}\n\n.function-btn.active i {\n color: var(--mj-brand-primary);\n}\n\n.function-btn span {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.function-btn.active span {\n color: var(--mj-color-info-700);\n}\n\n/* Field Hint */\n.field-hint {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 8px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-card);\n border-radius: 6px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.field-hint.warning {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.field-hint.warning i {\n color: var(--mj-status-warning);\n}\n\n.field-hint.info {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.field-hint.info i {\n color: var(--mj-brand-primary);\n}\n\n.field-hint i {\n color: var(--mj-text-disabled);\n margin-top: 1px;\n flex-shrink: 0;\n}\n\n/* Expression Preview */\n.expression-preview {\n margin-top: 16px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n}\n\n.preview-label {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.preview-code {\n display: block;\n padding: 12px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n/* Example Chips */\n.example-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.example-chip {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 11px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.example-chip:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-color-info-700);\n}\n\n/* Smart Mode Input */\n.smart-input-container {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.smart-input {\n min-height: 100px;\n}\n\n.smart-input:disabled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.generate-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 20px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 10px;\n color: var(--mj-text-inverse);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n align-self: flex-start;\n}\n\n.generate-btn:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.generate-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n/* Generated Section */\n.generated-section {\n margin-top: 16px;\n padding: 16px;\n background: var(--mj-status-success-bg);\n border: 1px solid var(--mj-status-success-border);\n border-radius: 10px;\n}\n\n.generated-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.generated-header .form-label {\n margin-bottom: 0;\n color: var(--mj-status-success-text);\n}\n\n.clear-generated-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--mj-status-success-border);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-status-success-text);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.clear-generated-btn:hover {\n background: var(--mj-status-success-text);\n color: var(--mj-text-inverse);\n}\n\n.generated-code {\n display: block;\n padding: 12px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-status-success-border);\n border-radius: 6px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n color: var(--mj-status-success-text);\n margin-bottom: 10px;\n}\n\n.generated-info {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n color: var(--mj-status-success-text);\n}\n\n.generated-info i {\n margin-top: 1px;\n}\n\n/* Smart Tips */\n.smart-tips {\n margin-top: 20px;\n padding: 14px;\n background: var(--mj-status-warning-bg);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: 10px;\n}\n\n.tips-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-status-warning-text);\n}\n\n.tips-header i {\n color: var(--mj-status-warning);\n}\n\n.tips-list {\n margin: 0;\n padding-left: 20px;\n font-size: 12px;\n color: var(--mj-status-warning-text);\n line-height: 1.6;\n}\n\n/* Config Section */\n.config-section {\n padding-top: 16px;\n}\n\n.section-divider {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n}\n\n.section-divider::before,\n.section-divider::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--mj-border-default);\n}\n\n.section-divider span {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n/* Display Type Toggle */\n.display-type-toggle {\n display: flex;\n gap: 12px;\n}\n\n.display-type-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 16px 14px;\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.display-type-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.display-type-btn.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.display-type-btn i {\n font-size: 22px;\n color: var(--mj-text-muted);\n}\n\n.display-type-btn.active i {\n color: var(--mj-brand-primary);\n}\n\n.display-type-btn span {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.display-type-btn.active span {\n color: var(--mj-color-info-700);\n}\n\n.display-type-btn small {\n font-size: 11px;\n color: var(--mj-text-disabled);\n}\n\n.display-type-btn.active small {\n color: var(--mj-brand-primary);\n}\n\n/* Icon Selector */\n.icon-selector {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.selected-icon {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, var(--mj-bg-surface));\n border-radius: 8px;\n width: fit-content;\n}\n\n.selected-icon i:first-child {\n font-size: 20px;\n color: var(--mj-brand-primary);\n}\n\n.clear-icon-btn {\n width: 24px;\n height: 24px;\n border: none;\n background: var(--mj-bg-surface);\n border-radius: 50%;\n cursor: pointer;\n color: var(--mj-text-disabled);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n transition: all 0.15s ease;\n}\n\n.clear-icon-btn:hover {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.icon-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.icon-btn:hover {\n border-color: var(--mj-border-strong);\n background: var(--mj-bg-surface-card);\n}\n\n.icon-btn.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n.icon-btn i {\n font-size: 16px;\n color: var(--mj-text-muted);\n}\n\n.icon-btn.active i {\n color: var(--mj-brand-primary);\n}\n\n/* Dialog Footer */\n.dialog-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n.footer-left {\n display: flex;\n gap: 10px;\n}\n\n.footer-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 11px 18px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.footer-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-text-disabled);\n}\n\n.footer-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.footer-btn.primary {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.footer-btn.primary:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n border-color: var(--mj-color-info-600);\n transform: translateY(-1px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n/* Validation Message */\n.validation-message {\n position: absolute;\n bottom: 80px;\n left: 24px;\n right: 24px;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: var(--mj-status-error-bg);\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n font-size: 13px;\n color: var(--mj-status-error);\n}\n\n.validation-message i {\n flex-shrink: 0;\n}\n\n/* Responsive */\n@media (max-width: 600px) {\n .dialog-panel {\n width: calc(100vw - 24px);\n max-height: calc(100vh - 40px);\n border-radius: 12px;\n }\n\n .dialog-header,\n .dialog-footer {\n border-radius: 0;\n }\n\n .dialog-header {\n border-radius: 12px 12px 0 0;\n }\n\n .dialog-footer {\n border-radius: 0 0 12px 12px;\n }\n\n .mode-btn {\n padding: 10px 8px;\n }\n\n .mode-btn span {\n font-size: 11px;\n }\n\n .function-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .display-type-toggle {\n flex-direction: column;\n }\n}\n"] }]
|
|
939
939
|
}], () => [{ type: i0.ChangeDetectorRef }], { Entity: [{
|
|
940
940
|
type: Input
|
|
941
941
|
}], Aggregate: [{
|
|
@@ -136,11 +136,11 @@ export class ConfirmDialogComponent {
|
|
|
136
136
|
i0.ɵɵtextInterpolate1(" ", ctx.ConfirmText, " ");
|
|
137
137
|
i0.ɵɵadvance(2);
|
|
138
138
|
i0.ɵɵtextInterpolate1(" ", ctx.CancelText, " ");
|
|
139
|
-
} }, styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background:
|
|
139
|
+
} }, styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n.dialog-content[_ngcontent-%COMP%] {\n padding: 24px;\n}\n\n.message[_ngcontent-%COMP%] {\n font-size: 15px;\n color: var(--mj-text-primary);\n line-height: 1.5;\n margin: 0;\n}\n\n.detail-message[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n line-height: 1.5;\n margin: 12px 0 0 0;\n}\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n\n\n.btn[_ngcontent-%COMP%] {\n padding: 9px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-confirm.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-confirm.btn-primary[_ngcontent-%COMP%]:hover {\n background: var(--mj-color-info-600);\n}\n\n.btn-confirm.btn-danger[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.btn-confirm.btn-danger[_ngcontent-%COMP%]:hover {\n background: var(--mj-status-error-text);\n}\n\n.btn-cancel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n}"] });
|
|
140
140
|
}
|
|
141
141
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ConfirmDialogComponent, [{
|
|
142
142
|
type: Component,
|
|
143
|
-
args: [{ standalone: false, selector: 'mj-ev-confirm-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"OnCancel()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"header-title\">\n <i [class]=\"Icon\"></i>\n <span>{{ Title }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"OnCancel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"dialog-content\">\n <p class=\"message\">{{ Message }}</p>\n @if (DetailMessage) {\n <p class=\"detail-message\">{{ DetailMessage }}</p>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <button\n class=\"btn btn-confirm\"\n [class.btn-primary]=\"ConfirmStyle === 'primary'\"\n [class.btn-danger]=\"ConfirmStyle === 'danger'\"\n (click)=\"OnConfirm()\">\n {{ ConfirmText }}\n </button>\n <button class=\"btn btn-cancel\" (click)=\"OnCancel()\">\n {{ CancelText }}\n </button>\n </div>\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background:
|
|
143
|
+
args: [{ standalone: false, selector: 'mj-ev-confirm-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"OnCancel()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"header-title\">\n <i [class]=\"Icon\"></i>\n <span>{{ Title }}</span>\n </div>\n <button class=\"close-btn\" (click)=\"OnCancel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"dialog-content\">\n <p class=\"message\">{{ Message }}</p>\n @if (DetailMessage) {\n <p class=\"detail-message\">{{ DetailMessage }}</p>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <button\n class=\"btn btn-confirm\"\n [class.btn-primary]=\"ConfirmStyle === 'primary'\"\n [class.btn-danger]=\"ConfirmStyle === 'danger'\"\n (click)=\"OnConfirm()\">\n {{ ConfirmText }}\n </button>\n <button class=\"btn btn-cancel\" (click)=\"OnCancel()\">\n {{ CancelText }}\n </button>\n </div>\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n/* Dialog Header */\n.dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 16px 16px 0 0;\n}\n\n.header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 17px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.header-title i {\n color: var(--mj-brand-primary);\n font-size: 18px;\n}\n\n.close-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.close-btn:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n/* Dialog Content */\n.dialog-content {\n padding: 24px;\n}\n\n.message {\n font-size: 15px;\n color: var(--mj-text-primary);\n line-height: 1.5;\n margin: 0;\n}\n\n.detail-message {\n font-size: 13px;\n color: var(--mj-text-muted);\n line-height: 1.5;\n margin: 12px 0 0 0;\n}\n\n/* Dialog Footer */\n.dialog-footer {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 16px 16px;\n}\n\n/* Buttons */\n.btn {\n padding: 9px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-confirm.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-confirm.btn-primary:hover {\n background: var(--mj-color-info-600);\n}\n\n.btn-confirm.btn-danger {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.btn-confirm.btn-danger:hover {\n background: var(--mj-status-error-text);\n}\n\n.btn-cancel {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel:hover {\n background: var(--mj-bg-surface-active);\n}\n"] }]
|
|
144
144
|
}], null, { IsOpen: [{
|
|
145
145
|
type: Input
|
|
146
146
|
}], Title: [{
|
|
@@ -162,11 +162,11 @@ export class DuplicateViewDialogComponent {
|
|
|
162
162
|
i0.ɵɵconditional(ctx.Summary ? 20 : -1);
|
|
163
163
|
i0.ɵɵadvance(2);
|
|
164
164
|
i0.ɵɵproperty("disabled", !ctx.NewName.trim());
|
|
165
|
-
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background:
|
|
165
|
+
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel], styles: ["\n\n.dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: _ngcontent-%COMP%_fadeIn 0.2s ease;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n.dialog-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n\n\n.dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px 24px 0;\n}\n\n.icon-circle[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.header-title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.close-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n}\n\n.close-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n.dialog-body[_ngcontent-%COMP%] {\n padding: 20px 24px;\n}\n\n.intro-text[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--mj-text-muted);\n line-height: 1.5;\n margin: 0 0 16px 0;\n}\n\n.intro-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n}\n\n\n\n.form-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-bottom: 16px;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n\n.form-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n font-family: inherit;\n color: var(--mj-text-primary);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n}\n\n.form-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.form-input.invalid[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n}\n\n.validation-error[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n\n\n.meta-summary[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n padding: 10px 14px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.meta-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.meta-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 11px;\n}\n\n\n\n.dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 24px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n\n\n.btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n}\n\n.btn-primary[_ngcontent-%COMP%]:disabled {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n cursor: not-allowed;\n}\n\n.btn-cancel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-active);\n}"] });
|
|
166
166
|
}
|
|
167
167
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DuplicateViewDialogComponent, [{
|
|
168
168
|
type: Component,
|
|
169
|
-
args: [{ standalone: false, selector: 'mj-duplicate-view-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"OnCancel()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"icon-circle\">\n <i class=\"fa-regular fa-copy\"></i>\n </div>\n <span class=\"header-title\">Duplicate View</span>\n <button class=\"close-btn\" (click)=\"OnCancel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"dialog-body\">\n <p class=\"intro-text\">\n Creating a copy of <strong>\"{{ SourceViewName }}\"</strong> with all its columns, filters, and sorting.\n </p>\n\n <!-- New Name Input -->\n <div class=\"form-group\">\n <label for=\"duplicateName\">New View Name</label>\n <input\n id=\"duplicateName\"\n type=\"text\"\n class=\"form-input\"\n [class.invalid]=\"NameTouched && !NewName.trim()\"\n [(ngModel)]=\"NewName\"\n (blur)=\"NameTouched = true\"\n (keydown.enter)=\"OnDuplicate()\"\n />\n @if (NameTouched && !NewName.trim()) {\n <span class=\"validation-error\">Name is required</span>\n }\n </div>\n\n <!-- Metadata Summary -->\n @if (Summary) {\n <div class=\"meta-summary\">\n @if (Summary.FilterCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-filter\"></i>\n {{ Summary.FilterCount }} filter{{ Summary.FilterCount !== 1 ? 's' : '' }}\n </span>\n }\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-table-columns\"></i>\n {{ Summary.ColumnCount }} column{{ Summary.ColumnCount !== 1 ? 's' : '' }}\n </span>\n @if (Summary.SortCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-arrow-down-short-wide\"></i>\n {{ Summary.SortCount }} sort{{ Summary.SortCount !== 1 ? 's' : '' }}\n </span>\n }\n @if (Summary.AggregateCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n {{ Summary.AggregateCount }} aggregate{{ Summary.AggregateCount !== 1 ? 's' : '' }}\n </span>\n }\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <button\n class=\"btn btn-primary\"\n (click)=\"OnDuplicate()\"\n [disabled]=\"!NewName.trim()\">\n <i class=\"fa-regular fa-copy\"></i>\n Duplicate\n </button>\n <button class=\"btn btn-cancel\" (click)=\"OnCancel()\">\n Cancel\n </button>\n </div>\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background:
|
|
169
|
+
args: [{ standalone: false, selector: 'mj-duplicate-view-dialog', template: "<!-- Dialog Backdrop -->\n@if (IsOpen) {\n <div class=\"dialog-backdrop\" (click)=\"OnCancel()\"></div>\n}\n\n<!-- Dialog Panel -->\n<div class=\"dialog-panel\" [class.open]=\"IsOpen\">\n <!-- Header -->\n <div class=\"dialog-header\">\n <div class=\"icon-circle\">\n <i class=\"fa-regular fa-copy\"></i>\n </div>\n <span class=\"header-title\">Duplicate View</span>\n <button class=\"close-btn\" (click)=\"OnCancel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Body -->\n <div class=\"dialog-body\">\n <p class=\"intro-text\">\n Creating a copy of <strong>\"{{ SourceViewName }}\"</strong> with all its columns, filters, and sorting.\n </p>\n\n <!-- New Name Input -->\n <div class=\"form-group\">\n <label for=\"duplicateName\">New View Name</label>\n <input\n id=\"duplicateName\"\n type=\"text\"\n class=\"form-input\"\n [class.invalid]=\"NameTouched && !NewName.trim()\"\n [(ngModel)]=\"NewName\"\n (blur)=\"NameTouched = true\"\n (keydown.enter)=\"OnDuplicate()\"\n />\n @if (NameTouched && !NewName.trim()) {\n <span class=\"validation-error\">Name is required</span>\n }\n </div>\n\n <!-- Metadata Summary -->\n @if (Summary) {\n <div class=\"meta-summary\">\n @if (Summary.FilterCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-filter\"></i>\n {{ Summary.FilterCount }} filter{{ Summary.FilterCount !== 1 ? 's' : '' }}\n </span>\n }\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-table-columns\"></i>\n {{ Summary.ColumnCount }} column{{ Summary.ColumnCount !== 1 ? 's' : '' }}\n </span>\n @if (Summary.SortCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-arrow-down-short-wide\"></i>\n {{ Summary.SortCount }} sort{{ Summary.SortCount !== 1 ? 's' : '' }}\n </span>\n }\n @if (Summary.AggregateCount > 0) {\n <span class=\"meta-item\">\n <i class=\"fa-solid fa-chart-simple\"></i>\n {{ Summary.AggregateCount }} aggregate{{ Summary.AggregateCount !== 1 ? 's' : '' }}\n </span>\n }\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"dialog-footer\">\n <button\n class=\"btn btn-primary\"\n (click)=\"OnDuplicate()\"\n [disabled]=\"!NewName.trim()\">\n <i class=\"fa-regular fa-copy\"></i>\n Duplicate\n </button>\n <button class=\"btn btn-cancel\" (click)=\"OnCancel()\">\n Cancel\n </button>\n </div>\n</div>\n", styles: ["/* Dialog Backdrop */\n.dialog-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 2000;\n animation: fadeIn 0.2s ease;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Dialog Panel */\n.dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.95);\n width: 420px;\n max-width: calc(100vw - 40px);\n background: var(--mj-bg-surface);\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);\n z-index: 2001;\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.dialog-panel.open {\n opacity: 1;\n pointer-events: auto;\n transform: translate(-50%, -50%) scale(1);\n}\n\n/* Dialog Header */\n.dialog-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px 24px 0;\n}\n\n.icon-circle {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.header-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.close-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: var(--mj-text-muted);\n transition: all 0.15s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n}\n\n.close-btn:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n/* Dialog Body */\n.dialog-body {\n padding: 20px 24px;\n}\n\n.intro-text {\n font-size: 14px;\n color: var(--mj-text-muted);\n line-height: 1.5;\n margin: 0 0 16px 0;\n}\n\n.intro-text strong {\n color: var(--mj-text-primary);\n}\n\n/* Form Group */\n.form-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-bottom: 16px;\n}\n\n.form-group label {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n\n.form-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 8px;\n font-size: 14px;\n font-family: inherit;\n color: var(--mj-text-primary);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n}\n\n.form-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.form-input.invalid {\n border-color: var(--mj-status-error);\n}\n\n.validation-error {\n font-size: 12px;\n color: var(--mj-status-error);\n}\n\n/* Metadata Summary */\n.meta-summary {\n display: flex;\n gap: 16px;\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n padding: 10px 14px;\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.meta-item i {\n color: var(--mj-brand-primary);\n font-size: 11px;\n}\n\n/* Dialog Footer */\n.dialog-footer {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 24px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n/* Buttons */\n.btn {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-color-info-600);\n}\n\n.btn-primary:disabled {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, var(--mj-bg-surface));\n cursor: not-allowed;\n}\n\n.btn-cancel {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n.btn-cancel:hover {\n background: var(--mj-bg-surface-active);\n}\n"] }]
|
|
170
170
|
}], () => [{ type: i0.ChangeDetectorRef }], { IsOpen: [{
|
|
171
171
|
type: Input
|
|
172
172
|
}], SourceViewName: [{
|
|
@@ -720,8 +720,8 @@ export class EntityCardsComponent {
|
|
|
720
720
|
}
|
|
721
721
|
getRecordColor(record) {
|
|
722
722
|
if (!this.entity)
|
|
723
|
-
return '
|
|
724
|
-
const colors = ['
|
|
723
|
+
return 'var(--mj-brand-primary)';
|
|
724
|
+
const colors = ['var(--mj-brand-primary)', 'var(--mj-status-success)', 'var(--mj-status-warning)', 'var(--mj-brand-primary)', 'var(--mj-status-error)', 'var(--mj-brand-primary)', 'var(--mj-text-muted)', 'var(--mj-text-secondary)'];
|
|
725
725
|
const pk = buildPkString(record, this.entity);
|
|
726
726
|
let hash = 0;
|
|
727
727
|
for (let i = 0; i < pk.length; i++) {
|
|
@@ -782,11 +782,11 @@ export class EntityCardsComponent {
|
|
|
782
782
|
} if (rf & 2) {
|
|
783
783
|
i0.ɵɵadvance();
|
|
784
784
|
i0.ɵɵconditional(ctx.isLoading && ctx.effectiveRecords.length === 0 ? 1 : 2);
|
|
785
|
-
} }, dependencies: [i1.LoadingComponent, i2.PillComponent], styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background:
|
|
785
|
+
} }, dependencies: [i1.LoadingComponent, i2.PillComponent], styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\nmj-entity-cards .no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\nmj-entity-cards .no-results p {\n margin: 0;\n font-size: 14px;\n}\n"], encapsulation: 2 });
|
|
786
786
|
}
|
|
787
787
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
|
|
788
788
|
type: Component,
|
|
789
|
-
args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, template: "<div class=\"cards-view-wrapper\">\n @if (isLoading && effectiveRecords.length === 0) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading records...\"></mj-loading>\n </div>\n } @else {\n <div class=\"cards-container\">\n @for (record of effectiveRecords; track getRecordTrackId(record, $index)) {\n <div\n class=\"data-card\"\n [class.selected]=\"isSelected(record)\"\n (click)=\"onCardClick(record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (getThumbnailType(record)) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"getThumbnailUrl(record)\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n <i [class]=\"getThumbnailUrl(record)\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n {{ getInitials(record) }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"highlightMatch(getFieldValue(record, effectiveTemplate?.titleField ?? null))\"></h3>\n @if (effectiveTemplate?.subtitleField; as subField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"getFieldValue(record, subField)\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"highlightMatch(getFieldValue(record, subField))\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, record)\" title=\"Open Record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n </div>\n\n <!-- Card Body -->\n <div class=\"card-body\">\n @if (effectiveTemplate?.descriptionField; as descField) {\n <p class=\"card-description\" [innerHTML]=\"highlightMatch(getTextValue(record, descField, 100))\"></p>\n }\n\n <!-- Display Fields -->\n @if (effectiveTemplate && effectiveTemplate.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (field of effectiveTemplate.displayFields; track field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"field.type === 'boolean'\" [class.field-text]=\"field.type === 'text'\">\n @switch (field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"getBooleanValue(record, field.name)\"\n [class.fa-minus]=\"!getBooleanValue(record, field.name)\"\n [class.bool-true]=\"getBooleanValue(record, field.name)\"\n [class.bool-false]=\"!getBooleanValue(record, field.name)\"></i>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ getNumericValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ getDateValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"highlightMatch(getTextValue(record, field.name, 40))\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField; as badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"getFieldValue(record, badgeField)\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (hasHiddenFieldMatch(record)) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + getHiddenMatchFieldName(record)\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ getHiddenMatchFieldName(record) }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <div class=\"no-results\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No records to display</p>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: white;\n border-radius: 12px;\n border: 1px solid #e0e0e0;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: #bdbdbd;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: #1976d2;\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: #1565c0;\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: #1976d2;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: white;\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: #757575;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: #f5f5f5;\n color: #1976d2;\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: #616161;\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid #f0f0f0;\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: #9e9e9e;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: #2e7d32;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: #bdbdbd;\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: #616161;\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: #424242;\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: #fafafa;\n border-top: 1px solid #f0f0f0;\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: #fff8e1;\n border-top: 1px solid #ffe082;\n font-size: 11px;\n color: #f57c00;\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: #9e9e9e;\n text-align: center;\n}\nmj-entity-cards .no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\nmj-entity-cards .no-results p {\n margin: 0;\n font-size: 14px;\n}\n"] }]
|
|
789
|
+
args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, template: "<div class=\"cards-view-wrapper\">\n @if (isLoading && effectiveRecords.length === 0) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading records...\"></mj-loading>\n </div>\n } @else {\n <div class=\"cards-container\">\n @for (record of effectiveRecords; track getRecordTrackId(record, $index)) {\n <div\n class=\"data-card\"\n [class.selected]=\"isSelected(record)\"\n (click)=\"onCardClick(record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (getThumbnailType(record)) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"getThumbnailUrl(record)\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n <i [class]=\"getThumbnailUrl(record)\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n {{ getInitials(record) }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"highlightMatch(getFieldValue(record, effectiveTemplate?.titleField ?? null))\"></h3>\n @if (effectiveTemplate?.subtitleField; as subField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"getFieldValue(record, subField)\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"highlightMatch(getFieldValue(record, subField))\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, record)\" title=\"Open Record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n </div>\n\n <!-- Card Body -->\n <div class=\"card-body\">\n @if (effectiveTemplate?.descriptionField; as descField) {\n <p class=\"card-description\" [innerHTML]=\"highlightMatch(getTextValue(record, descField, 100))\"></p>\n }\n\n <!-- Display Fields -->\n @if (effectiveTemplate && effectiveTemplate.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (field of effectiveTemplate.displayFields; track field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"field.type === 'boolean'\" [class.field-text]=\"field.type === 'text'\">\n @switch (field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"getBooleanValue(record, field.name)\"\n [class.fa-minus]=\"!getBooleanValue(record, field.name)\"\n [class.bool-true]=\"getBooleanValue(record, field.name)\"\n [class.bool-false]=\"!getBooleanValue(record, field.name)\"></i>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ getNumericValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ getDateValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"highlightMatch(getTextValue(record, field.name, 40))\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField; as badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"getFieldValue(record, badgeField)\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (hasHiddenFieldMatch(record)) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + getHiddenMatchFieldName(record)\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ getHiddenMatchFieldName(record) }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <div class=\"no-results\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No records to display</p>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: var(--mj-text-muted);\n text-align: center;\n}\nmj-entity-cards .no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\nmj-entity-cards .no-results p {\n margin: 0;\n font-size: 14px;\n}\n"] }]
|
|
790
790
|
}], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
|
|
791
791
|
type: Input
|
|
792
792
|
}], records: [{
|