tailjng 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/tailjng.mjs +4301 -29
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/colors/colors.service.d.ts +16 -0
- package/lib/colors/theme/elements/theme.service.d.ts +15 -0
- package/lib/colors/theme/theme.component.d.ts +87 -0
- package/lib/components/alert-dialog/alert-dialog.component.d.ts +24 -0
- package/lib/components/alert-dialog/elements/alert-dialog.interface.d.ts +41 -0
- package/lib/components/alert-dialog/elements/alert-dialog.service.d.ts +24 -0
- package/lib/components/alert-toast/alert-toast.component.d.ts +27 -0
- package/lib/components/alert-toast/elements/alert-toast.interface.d.ts +47 -0
- package/lib/components/alert-toast/elements/alert-toast.service.d.ts +26 -0
- package/lib/components/button/button.component.d.ts +35 -0
- package/lib/components/checkbox/checkbox.component.d.ts +21 -0
- package/lib/components/code-block/code-block.component.d.ts +17 -0
- package/lib/components/crud/card-component/card.component.d.ts +91 -0
- package/lib/components/crud/filter-component/elements/filter.interface.d.ts +62 -0
- package/lib/components/crud/filter-component/filter.component.d.ts +54 -0
- package/lib/components/crud/form-component/components/content-form/content-form.component.d.ts +8 -0
- package/lib/components/crud/form-component/form.component.d.ts +29 -0
- package/lib/components/crud/paginator-component/paginator.component.d.ts +27 -0
- package/lib/components/crud/table-component/elements/table.interface.d.ts +65 -0
- package/lib/components/crud/table-component/table.component.d.ts +97 -0
- package/lib/components/dialog/dialog.component.d.ts +37 -0
- package/lib/components/input/input.component.d.ts +47 -0
- package/lib/components/label/label.component.d.ts +13 -0
- package/lib/components/mode-toggle/mode-toggle.component.d.ts +15 -0
- package/lib/components/select/option/option.component.d.ts +15 -0
- package/lib/components/select/select.component.d.ts +93 -0
- package/lib/components/toggle-radio/toggle-radio.component.d.ts +48 -0
- package/lib/http/api-url.d.ts +2 -0
- package/lib/http/converter.service.d.ts +21 -0
- package/lib/http/crud-generic.service.d.ts +86 -0
- package/lib/http/http-error.service.d.ts +24 -0
- package/lib/http/http-rest.service.d.ts +9 -0
- package/lib/http/interface/api-response.d.ts +20 -0
- package/lib/service/calendar.service.d.ts +26 -0
- package/lib/shared/dialog.shared.d.ts +9 -0
- package/lib/shared/form.shared.d.ts +28 -0
- package/package.json +1 -1
- package/public-api.d.ts +26 -3
- package/lib/tailjng.component.d.ts +0 -5
- package/lib/tailjng.service.d.ts +0 -6
- /package/lib/{tooltip → components/tooltip}/tooltip.directive.d.ts +0 -0
- /package/lib/{tooltip → components/tooltip}/tooltip.service.d.ts +0 -0
package/fesm2022/tailjng.mjs
CHANGED
|
@@ -1,34 +1,70 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { PLATFORM_ID, Inject, Component, Injectable, TemplateRef, HostListener, Input, Directive, forwardRef, ViewChild, ViewEncapsulation, inject, EventEmitter, Output, CUSTOM_ELEMENTS_SCHEMA, signal, computed, InjectionToken, HostBinding, ContentChildren } from '@angular/core';
|
|
3
|
+
import * as i2 from 'lucide-angular';
|
|
4
|
+
import { Moon, Sun, LucideAngularModule, X, Loader2, Check, CircleHelp, Info, TriangleAlert, CircleX, CircleCheck, Search, Eye, ChevronDown, Copy, ChevronsRight, ChevronRight, ChevronLeft, ChevronsLeft, Cpu, Trash2, Eraser, ArrowDownWideNarrow, Filter, Trash, Edit, ChevronUp, ChevronsUpDown, MousePointerClick, CircleCheckBig, UserRoundSearch, KeyRound, Power, Ban, Plus, Asterisk, Save } from 'lucide-angular';
|
|
5
|
+
import * as i1$1 from '@angular/common';
|
|
6
|
+
import { isPlatformBrowser, NgClass, CommonModule } from '@angular/common';
|
|
7
|
+
import * as i1 from '@angular/forms';
|
|
8
|
+
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
9
|
+
import { trigger, transition, style, animate, state } from '@angular/animations';
|
|
10
|
+
import { isObservable, firstValueFrom, map, Subject, debounceTime, distinctUntilChanged, filter, BehaviorSubject } from 'rxjs';
|
|
11
|
+
import * as i1$2 from '@angular/common/http';
|
|
12
|
+
import { HttpParams } from '@angular/common/http';
|
|
13
|
+
import { formatDistanceToNowStrict } from 'date-fns';
|
|
14
|
+
import { es } from 'date-fns/locale';
|
|
15
|
+
import 'highlight.js/styles/github-dark.css';
|
|
16
|
+
import hljs from 'highlight.js/lib/core';
|
|
17
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
18
|
+
import typescript from 'highlight.js/lib/languages/typescript';
|
|
19
|
+
import bash from 'highlight.js/lib/languages/bash';
|
|
20
|
+
import css from 'highlight.js/lib/languages/css';
|
|
21
|
+
import scss from 'highlight.js/lib/languages/scss';
|
|
22
|
+
import html from 'highlight.js/lib/languages/xml';
|
|
23
|
+
import json from 'highlight.js/lib/languages/json';
|
|
3
24
|
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
class JModeToggleComponent {
|
|
26
|
+
platformId;
|
|
27
|
+
icons = {
|
|
28
|
+
sun: Sun,
|
|
29
|
+
moon: Moon
|
|
30
|
+
};
|
|
31
|
+
title = 'frontend';
|
|
32
|
+
theme = 'light';
|
|
33
|
+
constructor(platformId) {
|
|
34
|
+
this.platformId = platformId;
|
|
35
|
+
this.loadTheme();
|
|
36
|
+
}
|
|
37
|
+
// Guardar el tema en localStorage solo en el navegador y actualizar la variable
|
|
38
|
+
setTheme(theme) {
|
|
39
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
40
|
+
localStorage.setItem('theme', theme);
|
|
41
|
+
}
|
|
42
|
+
this.theme = theme; // Actualizar la variable theme
|
|
43
|
+
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
44
|
+
}
|
|
45
|
+
// Cargar el tema solo si está en el navegador y asignarlo a la variable
|
|
46
|
+
loadTheme() {
|
|
47
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
48
|
+
const storedTheme = localStorage.getItem('theme');
|
|
49
|
+
this.theme = storedTheme ? storedTheme : 'light'; // Asegurar que siempre haya un valor válido
|
|
50
|
+
document.documentElement.classList.toggle('dark', this.theme === 'dark');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Alternar entre claro y oscuro, y actualizar la variable
|
|
54
|
+
toggleDarkMode() {
|
|
55
|
+
const newTheme = this.theme === 'dark' ? 'light' : 'dark';
|
|
56
|
+
this.setTheme(newTheme);
|
|
57
|
+
}
|
|
58
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JModeToggleComponent, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component });
|
|
59
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JModeToggleComponent, isStandalone: true, selector: "JModeToggle", ngImport: i0, template: "<button (click)=\"toggleDarkMode()\"\n class=\"p-2 bg-gray-300 dark:bg-gray-700 text-black dark:text-white rounded-full cursor-pointer\">\n @if (theme === 'dark') {\n <lucide-icon [name]=\"icons['moon']\"></lucide-icon>\n } @else {\n <lucide-icon [name]=\"icons['sun']\"></lucide-icon>\n }\n</button>", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
|
|
8
60
|
}
|
|
9
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type:
|
|
10
|
-
type:
|
|
11
|
-
args: [{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TailjngComponent {
|
|
17
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: TailjngComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
18
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.7", type: TailjngComponent, isStandalone: true, selector: "lib-tailjng", ngImport: i0, template: `
|
|
19
|
-
<p>
|
|
20
|
-
tailjng works!
|
|
21
|
-
</p>
|
|
22
|
-
`, isInline: true, styles: [""] });
|
|
23
|
-
}
|
|
24
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: TailjngComponent, decorators: [{
|
|
25
|
-
type: Component,
|
|
26
|
-
args: [{ selector: 'lib-tailjng', imports: [], template: `
|
|
27
|
-
<p>
|
|
28
|
-
tailjng works!
|
|
29
|
-
</p>
|
|
30
|
-
` }]
|
|
31
|
-
}] });
|
|
61
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JModeToggleComponent, decorators: [{
|
|
62
|
+
type: Component,
|
|
63
|
+
args: [{ selector: 'JModeToggle', imports: [LucideAngularModule], template: "<button (click)=\"toggleDarkMode()\"\n class=\"p-2 bg-gray-300 dark:bg-gray-700 text-black dark:text-white rounded-full cursor-pointer\">\n @if (theme === 'dark') {\n <lucide-icon [name]=\"icons['moon']\"></lucide-icon>\n } @else {\n <lucide-icon [name]=\"icons['sun']\"></lucide-icon>\n }\n</button>" }]
|
|
64
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
65
|
+
type: Inject,
|
|
66
|
+
args: [PLATFORM_ID]
|
|
67
|
+
}] }] });
|
|
32
68
|
|
|
33
69
|
class TooltipService {
|
|
34
70
|
tooltipElement = null;
|
|
@@ -239,13 +275,4249 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImpor
|
|
|
239
275
|
args: ['mouseleave']
|
|
240
276
|
}] } });
|
|
241
277
|
|
|
278
|
+
class JLabelComponent {
|
|
279
|
+
for = '';
|
|
280
|
+
isRequired = false;
|
|
281
|
+
isConditioned = false;
|
|
282
|
+
isAutomated = false;
|
|
283
|
+
classes = '';
|
|
284
|
+
ngClass = {};
|
|
285
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
286
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JLabelComponent, isStandalone: true, selector: "JLabel", inputs: { for: "for", isRequired: "isRequired", isConditioned: "isConditioned", isAutomated: "isAutomated", classes: "classes", ngClass: "ngClass" }, ngImport: i0, template: "<label \r\n [for]=\"for\"\r\n [ngClass]=\"ngClass\"\r\n [class]=\"classes\"\r\n class=\"flex gap-1 items-center text-black dark:text-white\"\r\n>\r\n <ng-content></ng-content>\r\n\r\n @if (isRequired || isConditioned || isAutomated) {\r\n <span class=\"text-[8px]\" [ngClass]=\"{\r\n 'text-red-600 dark:text-red-300': isRequired,\r\n 'text-blue-600 dark:text-blue-300': !isRequired && isConditioned,\r\n 'text-purple-600 dark:text-purple-300': !isRequired && !isConditioned && isAutomated\r\n }\">\u2731</span>\r\n }\r\n</label>\r\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
287
|
+
}
|
|
288
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JLabelComponent, decorators: [{
|
|
289
|
+
type: Component,
|
|
290
|
+
args: [{ selector: 'JLabel', standalone: true, imports: [NgClass], template: "<label \r\n [for]=\"for\"\r\n [ngClass]=\"ngClass\"\r\n [class]=\"classes\"\r\n class=\"flex gap-1 items-center text-black dark:text-white\"\r\n>\r\n <ng-content></ng-content>\r\n\r\n @if (isRequired || isConditioned || isAutomated) {\r\n <span class=\"text-[8px]\" [ngClass]=\"{\r\n 'text-red-600 dark:text-red-300': isRequired,\r\n 'text-blue-600 dark:text-blue-300': !isRequired && isConditioned,\r\n 'text-purple-600 dark:text-purple-300': !isRequired && !isConditioned && isAutomated\r\n }\">\u2731</span>\r\n }\r\n</label>\r\n" }]
|
|
291
|
+
}], propDecorators: { for: [{
|
|
292
|
+
type: Input
|
|
293
|
+
}], isRequired: [{
|
|
294
|
+
type: Input
|
|
295
|
+
}], isConditioned: [{
|
|
296
|
+
type: Input
|
|
297
|
+
}], isAutomated: [{
|
|
298
|
+
type: Input
|
|
299
|
+
}], classes: [{
|
|
300
|
+
type: Input
|
|
301
|
+
}], ngClass: [{
|
|
302
|
+
type: Input
|
|
303
|
+
}] } });
|
|
304
|
+
|
|
305
|
+
class JInputComponent {
|
|
306
|
+
icons = {
|
|
307
|
+
x: X,
|
|
308
|
+
};
|
|
309
|
+
type = 'text';
|
|
310
|
+
placeholder = '';
|
|
311
|
+
disabled = false;
|
|
312
|
+
required = false;
|
|
313
|
+
name;
|
|
314
|
+
id;
|
|
315
|
+
classes = '';
|
|
316
|
+
ngClass = {};
|
|
317
|
+
accept = '';
|
|
318
|
+
multiple = false;
|
|
319
|
+
showImage = false;
|
|
320
|
+
clearButton = false;
|
|
321
|
+
min = 0;
|
|
322
|
+
max = 100;
|
|
323
|
+
step = 1;
|
|
324
|
+
isLabel = false;
|
|
325
|
+
simbol = '';
|
|
326
|
+
fileInputRef;
|
|
327
|
+
innerValue = '';
|
|
328
|
+
previewUrl = null;
|
|
329
|
+
get value() {
|
|
330
|
+
return this.innerValue;
|
|
331
|
+
}
|
|
332
|
+
get combinedNgClass() {
|
|
333
|
+
return {
|
|
334
|
+
...(this.ngClass || {}),
|
|
335
|
+
'opacity-50': this.disabled
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
set value(val) {
|
|
339
|
+
if (val !== this.innerValue) {
|
|
340
|
+
this.innerValue = val;
|
|
341
|
+
this.onChange(val);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// ControlValueAccessor
|
|
345
|
+
onChange = () => { };
|
|
346
|
+
onTouched = () => { };
|
|
347
|
+
writeValue(val) {
|
|
348
|
+
if (this.type !== 'file') {
|
|
349
|
+
this.innerValue = val ?? '';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
registerOnChange(fn) {
|
|
353
|
+
this.onChange = fn;
|
|
354
|
+
}
|
|
355
|
+
registerOnTouched(fn) {
|
|
356
|
+
this.onTouched = fn;
|
|
357
|
+
}
|
|
358
|
+
setDisabledState(isDisabled) {
|
|
359
|
+
// Si es tipo archivo, limpia
|
|
360
|
+
if (isDisabled && this.type === 'file') {
|
|
361
|
+
this.innerValue = null;
|
|
362
|
+
this.previewUrl = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
onInput(event) {
|
|
366
|
+
const target = event.target;
|
|
367
|
+
this.value = target.value;
|
|
368
|
+
this.onChange(this.value);
|
|
369
|
+
this.onTouched();
|
|
370
|
+
}
|
|
371
|
+
onFileSelected(event) {
|
|
372
|
+
const input = event.target;
|
|
373
|
+
const files = input.files;
|
|
374
|
+
if (files) {
|
|
375
|
+
const value = this.multiple ? Array.from(files) : files[0];
|
|
376
|
+
this.innerValue = value;
|
|
377
|
+
this.onChange(value);
|
|
378
|
+
this.onTouched();
|
|
379
|
+
if (!this.multiple && value instanceof File && value.type?.startsWith('image')) {
|
|
380
|
+
const reader = new FileReader();
|
|
381
|
+
reader.onload = () => {
|
|
382
|
+
this.previewUrl = reader.result;
|
|
383
|
+
};
|
|
384
|
+
reader.readAsDataURL(value);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
this.previewUrl = null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
clearInput() {
|
|
392
|
+
this.value = '';
|
|
393
|
+
this.onChange('');
|
|
394
|
+
this.onTouched();
|
|
395
|
+
}
|
|
396
|
+
clearFile() {
|
|
397
|
+
this.innerValue = null;
|
|
398
|
+
this.previewUrl = null;
|
|
399
|
+
this.onChange(this.multiple ? [] : null);
|
|
400
|
+
this.onTouched();
|
|
401
|
+
if (this.fileInputRef?.nativeElement) {
|
|
402
|
+
this.fileInputRef.nativeElement.value = '';
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
406
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JInputComponent, isStandalone: true, selector: "JInput", inputs: { type: "type", placeholder: "placeholder", disabled: "disabled", required: "required", name: "name", id: "id", classes: "classes", ngClass: "ngClass", accept: "accept", multiple: "multiple", showImage: "showImage", clearButton: "clearButton", min: "min", max: "max", step: "step", isLabel: "isLabel", simbol: "simbol" }, providers: [
|
|
407
|
+
{
|
|
408
|
+
provide: NG_VALUE_ACCESSOR,
|
|
409
|
+
useExisting: forwardRef(() => JInputComponent),
|
|
410
|
+
multi: true
|
|
411
|
+
}
|
|
412
|
+
], viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "@if (type === 'file') {\r\n <div class=\"relative w-full\">\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n [accept]=\"accept\"\r\n [multiple]=\"multiple\"\r\n (change)=\"onFileSelected($event)\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[40px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200 cursor-pointer\"\r\n (blur)=\"onTouched()\" />\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\"\r\n (click)=\"clearFile()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (previewUrl && showImage) {\r\n <div class=\"mt-2 w-full\">\r\n <img [src]=\"previewUrl\" alt=\"Vista previa\"\r\n class=\"w-full max-w-full h-auto rounded border border-border dark:border-dark-border shadow-sm\" />\r\n </div>\r\n }\r\n}\r\n\r\n@else if (type === 'textarea') {\r\n <div class=\"relative w-full\">\r\n <textarea\r\n [value]=\"value\"\r\n (input)=\"onInput($event)\"\r\n [placeholder]=\"placeholder\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\"\r\n ></textarea>\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\" class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-red-500\" (click)=\"clearInput()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n}\r\n\r\n@else if (type === 'range') {\r\n <div class=\"w-full\">\r\n\r\n @if (placeholder) {\r\n @if (isLabel) {\r\n <label [for]=\"id\" class=\"flex text-[15px] font-bold text-black dark:text-white justify-between mb-1 opacity-80\">\r\n <span>{{ placeholder }}</span>\r\n <span>{{ value | number:'1.0-2' }}{{simbol}}</span>\r\n </label>\r\n }@else {\r\n <label [for]=\"id\" class=\"block text-[15px] font-bold text-black dark:text-white text-center mb-1 opacity-80\">{{ placeholder }}</label>\r\n }\r\n }\r\n\r\n <div class=\"relative flex gap-2 w-full items-center justify-center align-center items-center justify-center align-center\">\r\n @if (min !== null) {\r\n <span class=\"text-[15px] font-bold text-gray-500 mt-1 text-center\">{{min}}{{simbol}}</span>\r\n }\r\n <input\r\n type=\"range\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [step]=\"step\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n [(ngModel)]=\"value\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"\r\n w-full h-5 bg-background dark:bg-dark-background border border-border dark:border-dark-border \r\n rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\r\n \r\n [&::-webkit-slider-thumb]:appearance-none\r\n [&::-webkit-slider-thumb]:h-8\r\n [&::-webkit-slider-thumb]:w-8\r\n [&::-webkit-slider-thumb]:rounded-full\r\n [&::-webkit-slider-thumb]:cursor-pointer\r\n [&::-webkit-slider-thumb]:bg-dark-primary\r\n dark:[&::-webkit-slider-thumb]:bg-primary\r\n [&::-webkit-slider-thumb:hover]:bg-primary\r\n dark:[&::-webkit-slider-thumb:hover]:bg-dark-primary\r\n \r\n [&::-moz-range-thumb]:h-4\r\n [&::-moz-range-thumb]:w-4\r\n [&::-moz-range-thumb]:rounded-full\r\n [&::-moz-range-thumb]:bg-primary\r\n [&::-moz-range-thumb]:cursor-pointer\r\n [&::-moz-range-thumb:hover]:bg-primary\r\n dark:[&::-moz-range-thumb:hover]:bg-dark-primary\r\n \r\n \r\n \" />\r\n\r\n @if (max) {\r\n <span class=\"text-[15px] font-bold text-gray-500 mt-1 text-center\">{{max}}{{simbol}}</span>\r\n }\r\n </div>\r\n\r\n @if (!isLabel) {\r\n <div class=\"text-xl font-bold text-gray-500 mt-1 text-center\">{{ value }}</div>\r\n }\r\n </div>\r\n}\r\n\r\n\r\n@else {\r\n <div class=\"relative w-full\">\r\n <input\r\n [type]=\"type\"\r\n [value]=\"value\"\r\n (input)=\"onInput($event)\"\r\n [placeholder]=\"placeholder\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\" />\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-red-500\"\r\n (click)=\"clearInput()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n}\r\n", styles: [".input::placeholder{color:#0006;transition:opacity .2s ease}.input:-webkit-autofill{-webkit-text-fill-color:black!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-primary) 15%,white) inset!important;caret-color:var(--color-primary)!important}.input:-webkit-autofill:hover,.input:-webkit-autofill:focus,.input:-webkit-autofill:active{-webkit-text-fill-color:black!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-primary) 15%,white) inset!important;caret-color:var(--color-primary)!important}.input[type=date]::-webkit-calendar-picker-indicator,.input[type=datetime-local]::-webkit-calendar-picker-indicator{filter:none!important;opacity:1!important;color:#000!important;background-color:transparent!important;-webkit-filter:brightness(0) invert(0)!important}.dark .input::placeholder{color:#fff6}.dark .input:-webkit-autofill{-webkit-text-fill-color:white!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-background) 15%,black) inset!important;caret-color:var(--color-dark-primary)!important}.dark .input:-webkit-autofill:hover,.dark .input:-webkit-autofill:focus,.dark .input:-webkit-autofill:active{-webkit-text-fill-color:white!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-background) 15%,black) inset!important;caret-color:var(--color-primary)!important}.dark .input[type=date]::-webkit-calendar-picker-indicator,.dark .input[type=datetime-local]::-webkit-calendar-picker-indicator{filter:none!important;opacity:1!important;color:#fff!important;background-color:transparent!important;-webkit-filter:brightness(0) invert(1)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }], encapsulation: i0.ViewEncapsulation.None });
|
|
413
|
+
}
|
|
414
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JInputComponent, decorators: [{
|
|
415
|
+
type: Component,
|
|
416
|
+
args: [{ selector: 'JInput', standalone: true, imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule], encapsulation: ViewEncapsulation.None, providers: [
|
|
417
|
+
{
|
|
418
|
+
provide: NG_VALUE_ACCESSOR,
|
|
419
|
+
useExisting: forwardRef(() => JInputComponent),
|
|
420
|
+
multi: true
|
|
421
|
+
}
|
|
422
|
+
], template: "@if (type === 'file') {\r\n <div class=\"relative w-full\">\r\n <input\r\n #fileInput\r\n type=\"file\"\r\n [accept]=\"accept\"\r\n [multiple]=\"multiple\"\r\n (change)=\"onFileSelected($event)\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[40px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200 cursor-pointer\"\r\n (blur)=\"onTouched()\" />\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\"\r\n (click)=\"clearFile()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (previewUrl && showImage) {\r\n <div class=\"mt-2 w-full\">\r\n <img [src]=\"previewUrl\" alt=\"Vista previa\"\r\n class=\"w-full max-w-full h-auto rounded border border-border dark:border-dark-border shadow-sm\" />\r\n </div>\r\n }\r\n}\r\n\r\n@else if (type === 'textarea') {\r\n <div class=\"relative w-full\">\r\n <textarea\r\n [value]=\"value\"\r\n (input)=\"onInput($event)\"\r\n [placeholder]=\"placeholder\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\"\r\n ></textarea>\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\" class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-red-500\" (click)=\"clearInput()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n}\r\n\r\n@else if (type === 'range') {\r\n <div class=\"w-full\">\r\n\r\n @if (placeholder) {\r\n @if (isLabel) {\r\n <label [for]=\"id\" class=\"flex text-[15px] font-bold text-black dark:text-white justify-between mb-1 opacity-80\">\r\n <span>{{ placeholder }}</span>\r\n <span>{{ value | number:'1.0-2' }}{{simbol}}</span>\r\n </label>\r\n }@else {\r\n <label [for]=\"id\" class=\"block text-[15px] font-bold text-black dark:text-white text-center mb-1 opacity-80\">{{ placeholder }}</label>\r\n }\r\n }\r\n\r\n <div class=\"relative flex gap-2 w-full items-center justify-center align-center items-center justify-center align-center\">\r\n @if (min !== null) {\r\n <span class=\"text-[15px] font-bold text-gray-500 mt-1 text-center\">{{min}}{{simbol}}</span>\r\n }\r\n <input\r\n type=\"range\"\r\n [min]=\"min\"\r\n [max]=\"max\"\r\n [step]=\"step\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n [(ngModel)]=\"value\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"\r\n w-full h-5 bg-background dark:bg-dark-background border border-border dark:border-dark-border \r\n rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\r\n \r\n [&::-webkit-slider-thumb]:appearance-none\r\n [&::-webkit-slider-thumb]:h-8\r\n [&::-webkit-slider-thumb]:w-8\r\n [&::-webkit-slider-thumb]:rounded-full\r\n [&::-webkit-slider-thumb]:cursor-pointer\r\n [&::-webkit-slider-thumb]:bg-dark-primary\r\n dark:[&::-webkit-slider-thumb]:bg-primary\r\n [&::-webkit-slider-thumb:hover]:bg-primary\r\n dark:[&::-webkit-slider-thumb:hover]:bg-dark-primary\r\n \r\n [&::-moz-range-thumb]:h-4\r\n [&::-moz-range-thumb]:w-4\r\n [&::-moz-range-thumb]:rounded-full\r\n [&::-moz-range-thumb]:bg-primary\r\n [&::-moz-range-thumb]:cursor-pointer\r\n [&::-moz-range-thumb:hover]:bg-primary\r\n dark:[&::-moz-range-thumb:hover]:bg-dark-primary\r\n \r\n \r\n \" />\r\n\r\n @if (max) {\r\n <span class=\"text-[15px] font-bold text-gray-500 mt-1 text-center\">{{max}}{{simbol}}</span>\r\n }\r\n </div>\r\n\r\n @if (!isLabel) {\r\n <div class=\"text-xl font-bold text-gray-500 mt-1 text-center\">{{ value }}</div>\r\n }\r\n </div>\r\n}\r\n\r\n\r\n@else {\r\n <div class=\"relative w-full\">\r\n <input\r\n [type]=\"type\"\r\n [value]=\"value\"\r\n (input)=\"onInput($event)\"\r\n [placeholder]=\"placeholder\"\r\n [disabled]=\"disabled\"\r\n [required]=\"required\"\r\n [name]=\"name ?? ''\"\r\n [id]=\"id\"\r\n (blur)=\"onTouched()\"\r\n [ngClass]=\"combinedNgClass\"\r\n [class]=\"classes\"\r\n class=\"input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200\" />\r\n\r\n @if (value && clearButton) {\r\n <button type=\"button\"\r\n class=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-red-500\"\r\n (click)=\"clearInput()\">\r\n <lucide-icon [name]=\"icons['x']\" class=\"w-4 h-4\" />\r\n </button>\r\n }\r\n </div>\r\n}\r\n", styles: [".input::placeholder{color:#0006;transition:opacity .2s ease}.input:-webkit-autofill{-webkit-text-fill-color:black!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-primary) 15%,white) inset!important;caret-color:var(--color-primary)!important}.input:-webkit-autofill:hover,.input:-webkit-autofill:focus,.input:-webkit-autofill:active{-webkit-text-fill-color:black!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-primary) 15%,white) inset!important;caret-color:var(--color-primary)!important}.input[type=date]::-webkit-calendar-picker-indicator,.input[type=datetime-local]::-webkit-calendar-picker-indicator{filter:none!important;opacity:1!important;color:#000!important;background-color:transparent!important;-webkit-filter:brightness(0) invert(0)!important}.dark .input::placeholder{color:#fff6}.dark .input:-webkit-autofill{-webkit-text-fill-color:white!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-background) 15%,black) inset!important;caret-color:var(--color-dark-primary)!important}.dark .input:-webkit-autofill:hover,.dark .input:-webkit-autofill:focus,.dark .input:-webkit-autofill:active{-webkit-text-fill-color:white!important;-webkit-box-shadow:0 0 0px 1000px color-mix(in srgb,var(--color-background) 15%,black) inset!important;caret-color:var(--color-primary)!important}.dark .input[type=date]::-webkit-calendar-picker-indicator,.dark .input[type=datetime-local]::-webkit-calendar-picker-indicator{filter:none!important;opacity:1!important;color:#fff!important;background-color:transparent!important;-webkit-filter:brightness(0) invert(1)!important}\n"] }]
|
|
423
|
+
}], propDecorators: { type: [{
|
|
424
|
+
type: Input
|
|
425
|
+
}], placeholder: [{
|
|
426
|
+
type: Input
|
|
427
|
+
}], disabled: [{
|
|
428
|
+
type: Input
|
|
429
|
+
}], required: [{
|
|
430
|
+
type: Input
|
|
431
|
+
}], name: [{
|
|
432
|
+
type: Input
|
|
433
|
+
}], id: [{
|
|
434
|
+
type: Input
|
|
435
|
+
}], classes: [{
|
|
436
|
+
type: Input
|
|
437
|
+
}], ngClass: [{
|
|
438
|
+
type: Input
|
|
439
|
+
}], accept: [{
|
|
440
|
+
type: Input
|
|
441
|
+
}], multiple: [{
|
|
442
|
+
type: Input
|
|
443
|
+
}], showImage: [{
|
|
444
|
+
type: Input
|
|
445
|
+
}], clearButton: [{
|
|
446
|
+
type: Input
|
|
447
|
+
}], min: [{
|
|
448
|
+
type: Input
|
|
449
|
+
}], max: [{
|
|
450
|
+
type: Input
|
|
451
|
+
}], step: [{
|
|
452
|
+
type: Input
|
|
453
|
+
}], isLabel: [{
|
|
454
|
+
type: Input
|
|
455
|
+
}], simbol: [{
|
|
456
|
+
type: Input
|
|
457
|
+
}], fileInputRef: [{
|
|
458
|
+
type: ViewChild,
|
|
459
|
+
args: ['fileInput']
|
|
460
|
+
}] } });
|
|
461
|
+
|
|
462
|
+
class JCheckboxComponent {
|
|
463
|
+
// Lucide icons
|
|
464
|
+
icons = {
|
|
465
|
+
check: Check,
|
|
466
|
+
loading: Loader2,
|
|
467
|
+
};
|
|
468
|
+
type = 'checkbox'; // Default type;
|
|
469
|
+
icon = this.icons['check']; // Default icon
|
|
470
|
+
iconSize = 15; // Default icon size
|
|
471
|
+
disabled; // Desactivar el checkbox
|
|
472
|
+
isLoading; // Indicar que el checkbox esta cargando
|
|
473
|
+
classes = ''; // Clases adicionales para el checkbox
|
|
474
|
+
title; // Estado del checkbox
|
|
475
|
+
isChecked = false; // Estado del checkbox
|
|
476
|
+
item; // Item
|
|
477
|
+
column; // Columna
|
|
478
|
+
// Funciones
|
|
479
|
+
getValue = () => false;
|
|
480
|
+
onCheckboxChange = () => { };
|
|
481
|
+
toggleSwitch = () => { };
|
|
482
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
483
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JCheckboxComponent, isStandalone: true, selector: "JCheckbox", inputs: { type: "type", icon: "icon", iconSize: "iconSize", disabled: "disabled", isLoading: "isLoading", classes: "classes", title: "title", isChecked: "isChecked", item: "item", column: "column", getValue: "getValue", onCheckboxChange: "onCheckboxChange", toggleSwitch: "toggleSwitch" }, ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center gap-2\">\r\n @if (title) {\r\n <span class=\"text-[8px] opacity-80\">{{title}}</span>\r\n }\r\n\r\n @if (type === 'checkbox') {\r\n <label class=\"relative inline-flex items-center justify-center\">\r\n <input \r\n type=\"checkbox\" \r\n class=\"absolute opacity-0 cursor-pointer peer\" \r\n [checked]=\"getValue(item, column)\"\r\n (change)=\"onCheckboxChange(item, column)\" \r\n [disabled]=\"disabled || column?.isDisaled\"\r\n >\r\n <span\r\n class=\"inline-flex items-center justify-center w-[25px] h-[25px] bg-white border border-primary dark:border-dark-border rounded transition-all duration-200 ease-in-out relative peer cursor-pointer peer-checked:bg-primary peer-checked:border-primary dark:peer-checked:bg-dark-primary dark:peer-checked:border-dark-primary\"\r\n [class]=\"classes\"\r\n [ngClass]=\"{'opacity-50' : column?.isDisaled}\"\r\n >\r\n <lucide-icon [name]=\"icon\" [size]=\"iconSize\" class=\"text-white\"></lucide-icon>\r\n </span>\r\n </label>\r\n }\r\n\r\n @if (type === 'switch') {\r\n @if (!isLoading) {\r\n <div onKeyPress \r\n class=\"relative inline-block w-[40px] h-[20px] cursor-pointer\" \r\n (click)=\"toggleSwitch(isChecked)\"\r\n >\r\n <!-- Slider background -->\r\n <div class=\"absolute inset-0 rounded-full transition-all duration-300\"\r\n [ngClass]=\"isChecked ? 'bg-blue-500' : 'bg-gray-200 dark:bg-gray-300'\">\r\n </div>\r\n\r\n <!-- Circle -->\r\n <div \r\n class=\"absolute top-[2px] left-[2px] bg-white rounded-full w-[16px] h-[16px] shadow-md transition-all duration-300\"\r\n [class]=\"classes\" \r\n [ngStyle]=\"{'transform': isChecked ? 'translateX(20px)' : 'translateX(0)'}\">\r\n </div>\r\n </div>\r\n } @else {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"20\" class=\"text-white animate-spin\"></lucide-icon>}\r\n }\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
|
|
484
|
+
}
|
|
485
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCheckboxComponent, decorators: [{
|
|
486
|
+
type: Component,
|
|
487
|
+
args: [{ selector: 'JCheckbox', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"flex flex-col items-center justify-center gap-2\">\r\n @if (title) {\r\n <span class=\"text-[8px] opacity-80\">{{title}}</span>\r\n }\r\n\r\n @if (type === 'checkbox') {\r\n <label class=\"relative inline-flex items-center justify-center\">\r\n <input \r\n type=\"checkbox\" \r\n class=\"absolute opacity-0 cursor-pointer peer\" \r\n [checked]=\"getValue(item, column)\"\r\n (change)=\"onCheckboxChange(item, column)\" \r\n [disabled]=\"disabled || column?.isDisaled\"\r\n >\r\n <span\r\n class=\"inline-flex items-center justify-center w-[25px] h-[25px] bg-white border border-primary dark:border-dark-border rounded transition-all duration-200 ease-in-out relative peer cursor-pointer peer-checked:bg-primary peer-checked:border-primary dark:peer-checked:bg-dark-primary dark:peer-checked:border-dark-primary\"\r\n [class]=\"classes\"\r\n [ngClass]=\"{'opacity-50' : column?.isDisaled}\"\r\n >\r\n <lucide-icon [name]=\"icon\" [size]=\"iconSize\" class=\"text-white\"></lucide-icon>\r\n </span>\r\n </label>\r\n }\r\n\r\n @if (type === 'switch') {\r\n @if (!isLoading) {\r\n <div onKeyPress \r\n class=\"relative inline-block w-[40px] h-[20px] cursor-pointer\" \r\n (click)=\"toggleSwitch(isChecked)\"\r\n >\r\n <!-- Slider background -->\r\n <div class=\"absolute inset-0 rounded-full transition-all duration-300\"\r\n [ngClass]=\"isChecked ? 'bg-blue-500' : 'bg-gray-200 dark:bg-gray-300'\">\r\n </div>\r\n\r\n <!-- Circle -->\r\n <div \r\n class=\"absolute top-[2px] left-[2px] bg-white rounded-full w-[16px] h-[16px] shadow-md transition-all duration-300\"\r\n [class]=\"classes\" \r\n [ngStyle]=\"{'transform': isChecked ? 'translateX(20px)' : 'translateX(0)'}\">\r\n </div>\r\n </div>\r\n } @else {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"20\" class=\"text-white animate-spin\"></lucide-icon>}\r\n }\r\n</div>" }]
|
|
488
|
+
}], propDecorators: { type: [{
|
|
489
|
+
type: Input
|
|
490
|
+
}], icon: [{
|
|
491
|
+
type: Input
|
|
492
|
+
}], iconSize: [{
|
|
493
|
+
type: Input
|
|
494
|
+
}], disabled: [{
|
|
495
|
+
type: Input
|
|
496
|
+
}], isLoading: [{
|
|
497
|
+
type: Input
|
|
498
|
+
}], classes: [{
|
|
499
|
+
type: Input
|
|
500
|
+
}], title: [{
|
|
501
|
+
type: Input
|
|
502
|
+
}], isChecked: [{
|
|
503
|
+
type: Input
|
|
504
|
+
}], item: [{
|
|
505
|
+
type: Input
|
|
506
|
+
}], column: [{
|
|
507
|
+
type: Input
|
|
508
|
+
}], getValue: [{
|
|
509
|
+
type: Input
|
|
510
|
+
}], onCheckboxChange: [{
|
|
511
|
+
type: Input
|
|
512
|
+
}], toggleSwitch: [{
|
|
513
|
+
type: Input
|
|
514
|
+
}] } });
|
|
515
|
+
|
|
516
|
+
class JColorsService {
|
|
517
|
+
// Variants
|
|
518
|
+
variants = {
|
|
519
|
+
primary: 'bg-primary dark:bg-dark-primary text-white dark:text-white hover:bg-dark-primary dark:hover:bg-dark-primary/60 dark:hover:text-white',
|
|
520
|
+
primary_secondary: 'bg-none text-dark-border dark:text-border border-dark-border dark:border-border hover:bg-dark-border/10 dark:hover:bg-border/10 shadow-md',
|
|
521
|
+
secondary: 'bg-background dark:bg-dark-background text-black dark:text-white hover:bg-accent dark:hover:bg-dark-accent/50',
|
|
522
|
+
success: 'bg-green-500 hover:bg-green-600 text-white border border-green-500 dark:border-green-600 shadow-md',
|
|
523
|
+
success_secondary: 'bg-none text-green-500 border-green-500 dark:border-green-600 hover:bg-green-500/10 dark:hover:bg-green-600/10 shadow-md',
|
|
524
|
+
info: 'bg-blue-500 hover:bg-blue-600 text-white border border-blue-500 dark:border-blue-600 shadow-md',
|
|
525
|
+
info_secondary: 'bg-none text-blue-500 border-blue-500 dark:border-blue-600 hover:bg-blue-500/10 dark:hover:bg-blue-600/10 shadow-md',
|
|
526
|
+
warning: 'bg-yellow-600 hover:bg-yellow-700 text-white border border-yellow-600 dark:border-yellow-700 shadow-md',
|
|
527
|
+
warning_secondary: 'bg-none text-yellow-600 border-yellow-600 dark:border-yellow-700 hover:bg-yellow-600/10 dark:hover:bg-yellow-700/10 shadow-md',
|
|
528
|
+
question: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-500 dark:border-purple-600 shadow-md',
|
|
529
|
+
question_secondary: 'bg-none text-purple-500 border-purple-500 dark:border-purple-600 hover:bg-purple-500/10 dark:hover:bg-purple-600/10 shadow-md',
|
|
530
|
+
error: 'bg-red-500 hover:bg-red-600 text-white border border-red-500 dark:border-red-600 shadow-md',
|
|
531
|
+
error_secondary: 'bg-none text-red-500 border-red-500 dark:border-red-600 hover:bg-red-500/10 dark:hover:bg-red-600/10 shadow-md',
|
|
532
|
+
loading: 'bg-gray-500 hover:bg-gray-600 text-white border border-gray-500 dark:border-gray-600 shadow-md',
|
|
533
|
+
loading_secondary: 'bg-none text-gray-500 border-gray-500 dark:border-gray-600 hover:bg-gray-500/10 dark:hover:bg-gray-600/10 shadow-md',
|
|
534
|
+
orange: 'bg-orange-500 hover:bg-orange-600 text-white border border-orange-500 dark:border-orange-600 shadow-md',
|
|
535
|
+
orange_secondary: 'bg-none text-orange-500 border-orange-500 dark:border-orange-600 hover:bg-orange-500/10 dark:hover:bg-orange-600/10 shadow-md',
|
|
536
|
+
cyan: 'bg-cyan-500 hover:bg-cyan-600 text-white border border-cyan-500 dark:border-cyan-600 shadow-md',
|
|
537
|
+
cyan_secondary: 'bg-none text-cyan-500 border-cyan-500 dark:border-cyan-600 hover:bg-cyan-500/10 dark:hover:bg-cyan-600/10 shadow-md',
|
|
538
|
+
purple: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-500 dark:border-purple-600 shadow-md',
|
|
539
|
+
purple_secondary: 'bg-none text-purple-500 border-purple-500 dark:border-purple-600 hover:bg-purple-500/10 dark:hover:bg-purple-600/10 shadow-md',
|
|
540
|
+
teal: 'bg-teal-500 hover:bg-teal-600 text-white border border-teal-500 dark:border-teal-600 shadow-md',
|
|
541
|
+
teal_secondary: 'bg-none text-teal-500 border-teal-500 dark:border-teal-600 hover:bg-teal-500/10 dark:hover:bg-teal-600/10 shadow-md',
|
|
542
|
+
pink: 'bg-pink-500 hover:bg-pink-600 text-white border border-pink-500 dark:border-pink-600 shadow-md',
|
|
543
|
+
pink_secondary: 'bg-none text-pink-500 border-pink-500 dark:border-pink-600 hover:bg-pink-500/10 dark:hover:bg-pink-600/10 shadow-md',
|
|
544
|
+
green: 'bg-green-500 hover:bg-green-600 text-white border border-green-500 dark:border-green-600 shadow-md',
|
|
545
|
+
green_secondary: 'bg-none text-green-500 border-green-500 dark:border-green-600 hover:bg-green-500/10 dark:hover:bg-green-600/10 shadow-md',
|
|
546
|
+
default: ' text-black dark:text-white shadow-md',
|
|
547
|
+
};
|
|
548
|
+
// Alerts
|
|
549
|
+
getAlertClass(type, monocromatic) {
|
|
550
|
+
if (!monocromatic) {
|
|
551
|
+
switch (type) {
|
|
552
|
+
case 'success': return 'border-green-500 bg-green-50 dark:bg-[#15241f]';
|
|
553
|
+
case 'error': return 'border-red-500 bg-red-50 dark:bg-[#21181c]';
|
|
554
|
+
case 'warning': return 'border-yellow-500 bg-yellow-50 dark:bg-[#1f1c1a]';
|
|
555
|
+
case 'info': return 'border-blue-500 bg-blue-50 dark:bg-[#1a1a24]';
|
|
556
|
+
case 'question': return 'border-purple-500 bg-purple-50 dark:bg-[#241732]';
|
|
557
|
+
case 'loading': return 'border-gray-500 bg-gray-50 dark:bg-[#15181e]';
|
|
558
|
+
default: return 'border-gray-500';
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
return 'bg-white dark:bg-dark-popover border-border dark:border-dark-border';
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Icons
|
|
566
|
+
getIconClass(type, monocromatic) {
|
|
567
|
+
if (!monocromatic) {
|
|
568
|
+
switch (type) {
|
|
569
|
+
case 'success': return 'text-green-500';
|
|
570
|
+
case 'error': return 'text-red-500';
|
|
571
|
+
case 'warning': return 'text-yellow-500';
|
|
572
|
+
case 'info': return 'text-blue-500';
|
|
573
|
+
case 'question': return 'text-purple-500';
|
|
574
|
+
case 'loading': return 'text-gray-500';
|
|
575
|
+
default: return 'text-primary';
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
return 'text-primary';
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Buttons
|
|
583
|
+
getButtonClass(type, monocromatic) {
|
|
584
|
+
if (!monocromatic) {
|
|
585
|
+
switch (type) {
|
|
586
|
+
case 'success': return { 'success': true };
|
|
587
|
+
case 'error': return { 'error': true };
|
|
588
|
+
case 'warning': return { 'warning': true };
|
|
589
|
+
case 'info': return { 'info': true };
|
|
590
|
+
case 'question': return { 'question': true };
|
|
591
|
+
case 'loading': return { 'loading': true };
|
|
592
|
+
default: return { 'primary': true };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
return { 'primary': true };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
getButtonSecondaryClass(type, monocromatic) {
|
|
600
|
+
if (!monocromatic) {
|
|
601
|
+
switch (type) {
|
|
602
|
+
case 'success': return { 'success_secondary': true };
|
|
603
|
+
case 'error': return { 'error_secondary': true };
|
|
604
|
+
case 'warning': return { 'warning_secondary': true };
|
|
605
|
+
case 'info': return { 'info_secondary': true };
|
|
606
|
+
case 'question': return { 'question_secondary': true };
|
|
607
|
+
case 'loading': return { 'loading_secondary': true };
|
|
608
|
+
default: return { 'secondary': true };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
return { 'secondary': true };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
616
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, providedIn: 'root' });
|
|
617
|
+
}
|
|
618
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, decorators: [{
|
|
619
|
+
type: Injectable,
|
|
620
|
+
args: [{
|
|
621
|
+
providedIn: 'root',
|
|
622
|
+
}]
|
|
623
|
+
}] });
|
|
624
|
+
|
|
625
|
+
class JButtonComponent {
|
|
626
|
+
icons = { loading: Loader2 };
|
|
627
|
+
colorsService = inject(JColorsService);
|
|
628
|
+
type = "button"; // Tipo de botón
|
|
629
|
+
disabled = false; // Estado deshabilitado
|
|
630
|
+
isLoading = false; // Estado de carga
|
|
631
|
+
icon; // Icono por defecto
|
|
632
|
+
iconSize = 15; // Tamaño de icono por defecto
|
|
633
|
+
text; // Color de icono por defecto
|
|
634
|
+
isChangeIcon = false; // Cambiar icono
|
|
635
|
+
iconChange; // Icono de cambio
|
|
636
|
+
tooltip = ""; // Texto de tooltip
|
|
637
|
+
tooltipPosition = "top"; // Posición de tooltip
|
|
638
|
+
clicked = new EventEmitter(); // Evento de clic
|
|
639
|
+
classes = ""; // Clases adicionales
|
|
640
|
+
ngClasses = {}; // Clases dinámicas con [ngClass]
|
|
641
|
+
// Verificar si una clase está presente en `classes` o `ngClasses`
|
|
642
|
+
hasClass(className) {
|
|
643
|
+
// Dividir la cadena de clases por espacios para verificar cada clase individualmente
|
|
644
|
+
const classArray = this.classes.split(" ");
|
|
645
|
+
return classArray.includes(className) || this.ngClasses[className];
|
|
646
|
+
}
|
|
647
|
+
// Definir clases según el tipo de botón (switch)
|
|
648
|
+
get variantClasses() {
|
|
649
|
+
return this.colorsService.variants[this.getActiveVariant()] || "min-w-[100px] text-black dark:text-white shadow-md";
|
|
650
|
+
}
|
|
651
|
+
// Obtener la variante activa
|
|
652
|
+
getActiveVariant() {
|
|
653
|
+
// Buscar la primera variante que coincida con las clases proporcionadas
|
|
654
|
+
const variant = Object.keys(this.colorsService.variants).find((variant) => this.hasClass(variant));
|
|
655
|
+
return variant ?? "default";
|
|
656
|
+
}
|
|
657
|
+
// Combina las clases base con las variantes
|
|
658
|
+
get computedClasses() {
|
|
659
|
+
return {
|
|
660
|
+
"flex gap-3 items-center justify-center font-semibold border border-border dark:border-dark-border px-3 py-2 rounded transition duration-300 select-none": true,
|
|
661
|
+
[this.variantClasses]: true, // Aplica las clases de la variante según el switch
|
|
662
|
+
"cursor-pointer": !this.disabled && !this.isLoading, // Cursor por defecto cuando está activo
|
|
663
|
+
"cursor-default opacity-50 pointer-events-none": this.disabled || this.isLoading, // Cursor deshabilitado
|
|
664
|
+
...this.ngClasses, // Permite usar validaciones dinámicas con [ngClass]
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
handleClick(event) {
|
|
668
|
+
if (!this.disabled && !this.isLoading) {
|
|
669
|
+
this.clicked.emit(event);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
673
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JButtonComponent, isStandalone: true, selector: "JButton", inputs: { type: "type", disabled: "disabled", isLoading: "isLoading", icon: "icon", iconSize: "iconSize", text: "text", isChangeIcon: "isChangeIcon", iconChange: "iconChange", tooltip: "tooltip", tooltipPosition: "tooltipPosition", classes: "classes", ngClasses: "ngClasses" }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\r\n [attr.type]=\"type\"\r\n [jTooltip]=\"tooltip\"\r\n [jTooltipPosition]=\"tooltipPosition\"\r\n [disabled]=\"disabled || isLoading\"\r\n [ngClass]=\"computedClasses\"\r\n [class]=\"classes\"\r\n (click)=\"handleClick($event)\"\r\n>\r\n <div class=\"flex items-center justify-center gap-2\">\r\n @if (isLoading) {\r\n <div [ngClass]=\"{ 'pt-1 pb-1': icon }\">\r\n <lucide-icon [name]=\"icons['loading']\" [size]=\"iconSize || 15\" class=\"animate-spin\" />\r\n </div>\r\n }\r\n @if (!isLoading && icon) {\r\n <div class=\"pt-1 pb-1\">\r\n @if (!isChangeIcon) {\r\n <lucide-icon [name]=\"icon\" [size]=\"iconSize\" />\r\n } @else {\r\n <lucide-icon [name]=\"iconChange\" [size]=\"iconSize\" />\r\n }\r\n </div>\r\n }\r\n @if (!isLoading && text) {\r\n <span>{{ text }}</span>\r\n }\r\n <ng-content></ng-content>\r\n </div>\r\n</button>\r\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "directive", type: JTooltipModule, selector: "[jTooltip]", inputs: ["jTooltip", "jTooltipPosition", "jTooltipShowArrow", "jTooltipOffsetX", "jTooltipOffsetY"] }] });
|
|
674
|
+
}
|
|
675
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JButtonComponent, decorators: [{
|
|
676
|
+
type: Component,
|
|
677
|
+
args: [{ selector: "JButton", standalone: true, imports: [NgClass, LucideAngularModule, JTooltipModule], template: "<button\r\n [attr.type]=\"type\"\r\n [jTooltip]=\"tooltip\"\r\n [jTooltipPosition]=\"tooltipPosition\"\r\n [disabled]=\"disabled || isLoading\"\r\n [ngClass]=\"computedClasses\"\r\n [class]=\"classes\"\r\n (click)=\"handleClick($event)\"\r\n>\r\n <div class=\"flex items-center justify-center gap-2\">\r\n @if (isLoading) {\r\n <div [ngClass]=\"{ 'pt-1 pb-1': icon }\">\r\n <lucide-icon [name]=\"icons['loading']\" [size]=\"iconSize || 15\" class=\"animate-spin\" />\r\n </div>\r\n }\r\n @if (!isLoading && icon) {\r\n <div class=\"pt-1 pb-1\">\r\n @if (!isChangeIcon) {\r\n <lucide-icon [name]=\"icon\" [size]=\"iconSize\" />\r\n } @else {\r\n <lucide-icon [name]=\"iconChange\" [size]=\"iconSize\" />\r\n }\r\n </div>\r\n }\r\n @if (!isLoading && text) {\r\n <span>{{ text }}</span>\r\n }\r\n <ng-content></ng-content>\r\n </div>\r\n</button>\r\n" }]
|
|
678
|
+
}], propDecorators: { type: [{
|
|
679
|
+
type: Input
|
|
680
|
+
}], disabled: [{
|
|
681
|
+
type: Input
|
|
682
|
+
}], isLoading: [{
|
|
683
|
+
type: Input
|
|
684
|
+
}], icon: [{
|
|
685
|
+
type: Input
|
|
686
|
+
}], iconSize: [{
|
|
687
|
+
type: Input
|
|
688
|
+
}], text: [{
|
|
689
|
+
type: Input
|
|
690
|
+
}], isChangeIcon: [{
|
|
691
|
+
type: Input
|
|
692
|
+
}], iconChange: [{
|
|
693
|
+
type: Input
|
|
694
|
+
}], tooltip: [{
|
|
695
|
+
type: Input
|
|
696
|
+
}], tooltipPosition: [{
|
|
697
|
+
type: Input
|
|
698
|
+
}], clicked: [{
|
|
699
|
+
type: Output
|
|
700
|
+
}], classes: [{
|
|
701
|
+
type: Input
|
|
702
|
+
}], ngClasses: [{
|
|
703
|
+
type: Input
|
|
704
|
+
}] } });
|
|
705
|
+
|
|
706
|
+
// src/app/core/shared/form.shared.ts
|
|
707
|
+
class JDialogShared {
|
|
708
|
+
openDialog = false;
|
|
709
|
+
constructor() { }
|
|
710
|
+
onOpen() {
|
|
711
|
+
this.openDialog = true;
|
|
712
|
+
}
|
|
713
|
+
onClose() {
|
|
714
|
+
this.openDialog = false;
|
|
715
|
+
}
|
|
716
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
717
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, providedIn: 'root' });
|
|
718
|
+
}
|
|
719
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, decorators: [{
|
|
720
|
+
type: Injectable,
|
|
721
|
+
args: [{
|
|
722
|
+
providedIn: 'root'
|
|
723
|
+
}]
|
|
724
|
+
}], ctorParameters: () => [] });
|
|
725
|
+
|
|
726
|
+
class JDialogComponent {
|
|
727
|
+
icons = {
|
|
728
|
+
x: X
|
|
729
|
+
};
|
|
730
|
+
position = 'center';
|
|
731
|
+
offset = {};
|
|
732
|
+
openModal = false;
|
|
733
|
+
closeModal = new EventEmitter();
|
|
734
|
+
dialogTemplate;
|
|
735
|
+
title = 'Dialog Title';
|
|
736
|
+
width = 500;
|
|
737
|
+
height = 300;
|
|
738
|
+
overlay = true;
|
|
739
|
+
draggable = false;
|
|
740
|
+
isDragging = false;
|
|
741
|
+
dragOffset = { x: 0, y: 0 };
|
|
742
|
+
constructor() { }
|
|
743
|
+
onOpen() {
|
|
744
|
+
this.openModal = true;
|
|
745
|
+
}
|
|
746
|
+
onClose() {
|
|
747
|
+
this.closeModal.emit();
|
|
748
|
+
}
|
|
749
|
+
getModalWidth() {
|
|
750
|
+
return typeof this.width === 'number' ? `${this.width}px` : this.width;
|
|
751
|
+
}
|
|
752
|
+
getModalHeight() {
|
|
753
|
+
if (this.height === 'auto')
|
|
754
|
+
return 'auto';
|
|
755
|
+
if (typeof this.height === 'number')
|
|
756
|
+
return `${this.height}px`;
|
|
757
|
+
return `${this.height || 40}px`;
|
|
758
|
+
}
|
|
759
|
+
handleEscape(event) {
|
|
760
|
+
if (this.openModal) {
|
|
761
|
+
this.onClose();
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
getPositionClass() {
|
|
765
|
+
switch (this.position) {
|
|
766
|
+
case 'leftCenter': return 'justify-start items-center';
|
|
767
|
+
case 'rightCenter': return 'justify-end items-center';
|
|
768
|
+
case 'topCenter': return 'justify-center items-start';
|
|
769
|
+
case 'bottomCenter': return 'justify-center items-end';
|
|
770
|
+
case 'leftTop': return 'justify-start items-start';
|
|
771
|
+
case 'leftBottom': return 'justify-start items-end';
|
|
772
|
+
case 'rightTop': return 'justify-end items-start';
|
|
773
|
+
case 'rightBottom': return 'justify-end items-end';
|
|
774
|
+
case 'center':
|
|
775
|
+
default: return 'justify-center items-center';
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
getOffsetStyles() {
|
|
779
|
+
return {
|
|
780
|
+
marginTop: this.offset.top !== undefined ? `${this.offset.top}px` : '',
|
|
781
|
+
marginBottom: this.offset.bottom !== undefined ? `${this.offset.bottom}px` : '',
|
|
782
|
+
marginLeft: this.offset.left !== undefined ? `${this.offset.left}px` : '',
|
|
783
|
+
marginRight: this.offset.right !== undefined ? `${this.offset.right}px` : '',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
startDrag(event) {
|
|
787
|
+
if (!this.draggable)
|
|
788
|
+
return;
|
|
789
|
+
this.isDragging = true;
|
|
790
|
+
const dialogElement = event.currentTarget.closest('[data-draggable-dialog]');
|
|
791
|
+
if (!dialogElement)
|
|
792
|
+
return;
|
|
793
|
+
const rect = dialogElement.getBoundingClientRect();
|
|
794
|
+
this.dragOffset = {
|
|
795
|
+
x: event.clientX - rect.left,
|
|
796
|
+
y: event.clientY - rect.top
|
|
797
|
+
};
|
|
798
|
+
dialogElement.style.position = 'fixed';
|
|
799
|
+
dialogElement.style.margin = '0';
|
|
800
|
+
dialogElement.style.transform = 'none';
|
|
801
|
+
dialogElement.style.zIndex = '1001';
|
|
802
|
+
const mouseMoveHandler = (moveEvent) => {
|
|
803
|
+
if (!this.isDragging)
|
|
804
|
+
return;
|
|
805
|
+
const newLeft = moveEvent.clientX - this.dragOffset.x;
|
|
806
|
+
const newTop = moveEvent.clientY - this.dragOffset.y;
|
|
807
|
+
// límites del viewport
|
|
808
|
+
const maxLeft = window.innerWidth - dialogElement.offsetWidth;
|
|
809
|
+
const maxTop = window.innerHeight - dialogElement.offsetHeight;
|
|
810
|
+
// aplicar límites
|
|
811
|
+
const boundedLeft = Math.min(Math.max(newLeft, 0), maxLeft);
|
|
812
|
+
const boundedTop = Math.min(Math.max(newTop, 0), maxTop);
|
|
813
|
+
dialogElement.style.left = `${boundedLeft}px`;
|
|
814
|
+
dialogElement.style.top = `${boundedTop}px`;
|
|
815
|
+
};
|
|
816
|
+
const mouseUpHandler = () => {
|
|
817
|
+
this.isDragging = false;
|
|
818
|
+
document.removeEventListener('mousemove', mouseMoveHandler);
|
|
819
|
+
document.removeEventListener('mouseup', mouseUpHandler);
|
|
820
|
+
};
|
|
821
|
+
document.addEventListener('mousemove', mouseMoveHandler);
|
|
822
|
+
document.addEventListener('mouseup', mouseUpHandler);
|
|
823
|
+
}
|
|
824
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
825
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JDialogComponent, isStandalone: true, selector: "JDialog", inputs: { position: "position", offset: "offset", openModal: "openModal", dialogTemplate: "dialogTemplate", title: "title", width: "width", height: "height", overlay: "overlay", draggable: "draggable" }, outputs: { closeModal: "closeModal" }, host: { listeners: { "document:keydown.escape": "handleEscape($event)" } }, ngImport: i0, template: "@if (openModal) {\r\n <!-- Overlay solo si se activa -->\r\n @if (overlay) {\r\n <div class=\"fixed inset-0 z-[999] bg-black/50\"></div>\r\n }\r\n \r\n <!-- Modal -->\r\n <div class=\"fixed inset-0 z-[1000] flex pointer-events-none\" [ngClass]=\"getPositionClass()\">\r\n \r\n <div @modalTransition\r\n class=\"pointer-events-auto bg-white dark:bg-foreground rounded-[12px] shadow-lg border-2 border-border dark:border-dark-border\"\r\n [ngStyle]=\"getOffsetStyles()\"\r\n data-draggable-dialog>\r\n \r\n <!-- Header draggable -->\r\n @if (draggable) {\r\n <div class=\"flex p-1 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm cursor-move\"\r\n (mousedown)=\"startDrag($event)\">\r\n <h3 class=\"text-[1.1em] font-semibold text-white leading-none\">{{ title }}</h3>\r\n <button type=\"button\" (click)=\"onClose()\"\r\n class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Header normal -->\r\n @if (!draggable) {\r\n <div class=\"flex p-1 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm\">\r\n <h3 class=\"text-[1.1em] font-semibold text-white leading-none\">{{ title }}</h3>\r\n <button type=\"button\" (click)=\"onClose()\"\r\n class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Content -->\r\n <div class=\"m-2\"\r\n [ngStyle]=\"{\r\n width: getModalWidth(),\r\n height: getModalHeight(),\r\n 'min-height': '40px',\r\n }\">\r\n @if (dialogTemplate) {\r\n <ng-container [ngTemplateOutlet]=\"dialogTemplate\"></ng-container>\r\n }\r\n </div>\r\n \r\n </div>\r\n </div>\r\n }\r\n ", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], animations: [
|
|
826
|
+
trigger('modalTransition', [
|
|
827
|
+
transition(':enter', [
|
|
828
|
+
style({ transform: 'translateY(1rem)', opacity: 0 }),
|
|
829
|
+
animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
|
|
830
|
+
]),
|
|
831
|
+
transition(':leave', [
|
|
832
|
+
animate('150ms ease-in', style({ transform: 'translateY(1rem)', opacity: 0 }))
|
|
833
|
+
])
|
|
834
|
+
])
|
|
835
|
+
] });
|
|
836
|
+
}
|
|
837
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogComponent, decorators: [{
|
|
838
|
+
type: Component,
|
|
839
|
+
args: [{ selector: 'JDialog', standalone: true, imports: [LucideAngularModule, CommonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], animations: [
|
|
840
|
+
trigger('modalTransition', [
|
|
841
|
+
transition(':enter', [
|
|
842
|
+
style({ transform: 'translateY(1rem)', opacity: 0 }),
|
|
843
|
+
animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
|
|
844
|
+
]),
|
|
845
|
+
transition(':leave', [
|
|
846
|
+
animate('150ms ease-in', style({ transform: 'translateY(1rem)', opacity: 0 }))
|
|
847
|
+
])
|
|
848
|
+
])
|
|
849
|
+
], template: "@if (openModal) {\r\n <!-- Overlay solo si se activa -->\r\n @if (overlay) {\r\n <div class=\"fixed inset-0 z-[999] bg-black/50\"></div>\r\n }\r\n \r\n <!-- Modal -->\r\n <div class=\"fixed inset-0 z-[1000] flex pointer-events-none\" [ngClass]=\"getPositionClass()\">\r\n \r\n <div @modalTransition\r\n class=\"pointer-events-auto bg-white dark:bg-foreground rounded-[12px] shadow-lg border-2 border-border dark:border-dark-border\"\r\n [ngStyle]=\"getOffsetStyles()\"\r\n data-draggable-dialog>\r\n \r\n <!-- Header draggable -->\r\n @if (draggable) {\r\n <div class=\"flex p-1 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm cursor-move\"\r\n (mousedown)=\"startDrag($event)\">\r\n <h3 class=\"text-[1.1em] font-semibold text-white leading-none\">{{ title }}</h3>\r\n <button type=\"button\" (click)=\"onClose()\"\r\n class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Header normal -->\r\n @if (!draggable) {\r\n <div class=\"flex p-1 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-[10px] font-semibold text-2sm\">\r\n <h3 class=\"text-[1.1em] font-semibold text-white leading-none\">{{ title }}</h3>\r\n <button type=\"button\" (click)=\"onClose()\"\r\n class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Content -->\r\n <div class=\"m-2\"\r\n [ngStyle]=\"{\r\n width: getModalWidth(),\r\n height: getModalHeight(),\r\n 'min-height': '40px',\r\n }\">\r\n @if (dialogTemplate) {\r\n <ng-container [ngTemplateOutlet]=\"dialogTemplate\"></ng-container>\r\n }\r\n </div>\r\n \r\n </div>\r\n </div>\r\n }\r\n " }]
|
|
850
|
+
}], ctorParameters: () => [], propDecorators: { position: [{
|
|
851
|
+
type: Input
|
|
852
|
+
}], offset: [{
|
|
853
|
+
type: Input
|
|
854
|
+
}], openModal: [{
|
|
855
|
+
type: Input
|
|
856
|
+
}], closeModal: [{
|
|
857
|
+
type: Output
|
|
858
|
+
}], dialogTemplate: [{
|
|
859
|
+
type: Input
|
|
860
|
+
}], title: [{
|
|
861
|
+
type: Input
|
|
862
|
+
}], width: [{
|
|
863
|
+
type: Input
|
|
864
|
+
}], height: [{
|
|
865
|
+
type: Input
|
|
866
|
+
}], overlay: [{
|
|
867
|
+
type: Input
|
|
868
|
+
}], draggable: [{
|
|
869
|
+
type: Input
|
|
870
|
+
}], handleEscape: [{
|
|
871
|
+
type: HostListener,
|
|
872
|
+
args: ['document:keydown.escape', ['$event']]
|
|
873
|
+
}] } });
|
|
874
|
+
|
|
875
|
+
class JAlertToastService {
|
|
876
|
+
toastsSignal = signal([]);
|
|
877
|
+
autoCloseTimers = new Map();
|
|
878
|
+
// Default auto-close delay in milliseconds
|
|
879
|
+
DEFAULT_AUTO_CLOSE_DELAY = 5000;
|
|
880
|
+
// Default action button text
|
|
881
|
+
DEFAULT_ACTION_BUTTON_TEXT = "Action";
|
|
882
|
+
// For compatibility with existing code
|
|
883
|
+
isOpenSignal = signal(false);
|
|
884
|
+
configSignal = signal(null);
|
|
885
|
+
isLoadingAction = signal(false);
|
|
886
|
+
isLoadingCancel = signal(false);
|
|
887
|
+
// Computed values for all toasts
|
|
888
|
+
toasts = computed(() => this.toastsSignal());
|
|
889
|
+
// For backward compatibility with existing code
|
|
890
|
+
isOpen = computed(() => this.isOpenSignal());
|
|
891
|
+
config = computed(() => this.configSignal());
|
|
892
|
+
isActionLoading = computed(() => this.isLoadingAction());
|
|
893
|
+
isCancelLoading = computed(() => this.isLoadingCancel());
|
|
894
|
+
getConfig() {
|
|
895
|
+
return this.configSignal();
|
|
896
|
+
}
|
|
897
|
+
AlertToast(config) {
|
|
898
|
+
const toastId = crypto.randomUUID();
|
|
899
|
+
// Store callbacks for this toast
|
|
900
|
+
const onActionCallback = "onAction" in config ? config.onAction : undefined;
|
|
901
|
+
const onCancelCallback = "onCancel" in config ? config.onCancel : undefined;
|
|
902
|
+
// Get action button text or use default
|
|
903
|
+
const actionNameButton = config.actionButtonText ?? this.DEFAULT_ACTION_BUTTON_TEXT;
|
|
904
|
+
const toast = {
|
|
905
|
+
id: toastId,
|
|
906
|
+
config,
|
|
907
|
+
isActionLoading: false,
|
|
908
|
+
isCancelLoading: false,
|
|
909
|
+
onActionCallback,
|
|
910
|
+
onCancelCallback,
|
|
911
|
+
actionNameButton, // Add the button text
|
|
912
|
+
createdAt: Date.now()
|
|
913
|
+
};
|
|
914
|
+
// Add the toast to our list
|
|
915
|
+
this.toastsSignal.update(toasts => [...toasts, toast]);
|
|
916
|
+
// Check if this toast should auto-close
|
|
917
|
+
// If autoClose is explicitly set to true OR
|
|
918
|
+
// if it's a success toast and autoClose is not explicitly set to false
|
|
919
|
+
const shouldAutoClose = config.autoClose === true ||
|
|
920
|
+
(config.type === 'success' && config.autoClose !== false);
|
|
921
|
+
if (shouldAutoClose) {
|
|
922
|
+
// Use the provided delay or the default
|
|
923
|
+
const delay = config.autoCloseDelay ?? this.DEFAULT_AUTO_CLOSE_DELAY;
|
|
924
|
+
const timerId = setTimeout(() => {
|
|
925
|
+
this.closeToastById(toastId);
|
|
926
|
+
this.autoCloseTimers.delete(toastId);
|
|
927
|
+
}, delay);
|
|
928
|
+
this.autoCloseTimers.set(toastId, timerId);
|
|
929
|
+
}
|
|
930
|
+
return toastId;
|
|
931
|
+
}
|
|
932
|
+
closeToastById(toastId) {
|
|
933
|
+
// Clear any auto-close timer
|
|
934
|
+
if (this.autoCloseTimers.has(toastId)) {
|
|
935
|
+
clearTimeout(this.autoCloseTimers.get(toastId));
|
|
936
|
+
this.autoCloseTimers.delete(toastId);
|
|
937
|
+
}
|
|
938
|
+
this.toastsSignal.update(toasts => toasts.filter(toast => toast.id !== toastId));
|
|
939
|
+
}
|
|
940
|
+
closeAllToasts() {
|
|
941
|
+
// Clear all timers
|
|
942
|
+
this.autoCloseTimers.forEach(timerId => clearTimeout(timerId));
|
|
943
|
+
this.autoCloseTimers.clear();
|
|
944
|
+
this.toastsSignal.set([]);
|
|
945
|
+
}
|
|
946
|
+
async executeToastAction(toastId, action) {
|
|
947
|
+
const toast = this.toastsSignal().find(t => t.id === toastId);
|
|
948
|
+
if (!toast)
|
|
949
|
+
return;
|
|
950
|
+
let callback;
|
|
951
|
+
let loadingProperty;
|
|
952
|
+
switch (action) {
|
|
953
|
+
case "action":
|
|
954
|
+
callback = toast.onActionCallback;
|
|
955
|
+
loadingProperty = 'isActionLoading';
|
|
956
|
+
break;
|
|
957
|
+
case "cancel":
|
|
958
|
+
callback = toast.onCancelCallback;
|
|
959
|
+
loadingProperty = 'isCancelLoading';
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
if (callback) {
|
|
963
|
+
// Update loading state
|
|
964
|
+
this.toastsSignal.update(toasts => toasts.map(t => t.id === toastId ? { ...t, [loadingProperty]: true } : t));
|
|
965
|
+
try {
|
|
966
|
+
await callback();
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
console.error(`Error en la acción ${action}:`, error);
|
|
970
|
+
}
|
|
971
|
+
finally {
|
|
972
|
+
// Update loading state back to false (in case the toast hasn't been closed)
|
|
973
|
+
this.toastsSignal.update(toasts => {
|
|
974
|
+
const updatedToasts = toasts.map(t => t.id === toastId ? { ...t, [loadingProperty]: false } : t);
|
|
975
|
+
return updatedToasts;
|
|
976
|
+
});
|
|
977
|
+
this.closeToastById(toastId);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
this.closeToastById(toastId);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// For backward compatibility with existing code
|
|
985
|
+
closeToast() {
|
|
986
|
+
this.closeAllToasts();
|
|
987
|
+
this.isOpenSignal.set(false);
|
|
988
|
+
this.configSignal.set(null);
|
|
989
|
+
}
|
|
990
|
+
// For backward compatibility with existing code
|
|
991
|
+
async executeAction(action) {
|
|
992
|
+
let callback;
|
|
993
|
+
let loadingSignal;
|
|
994
|
+
switch (action) {
|
|
995
|
+
case "action":
|
|
996
|
+
callback = "onAction" in (this.configSignal() || {})
|
|
997
|
+
? this.configSignal().onAction
|
|
998
|
+
: undefined;
|
|
999
|
+
loadingSignal = this.isLoadingAction;
|
|
1000
|
+
break;
|
|
1001
|
+
case "cancel":
|
|
1002
|
+
callback = "onCancel" in (this.configSignal() || {})
|
|
1003
|
+
? this.configSignal().onCancel
|
|
1004
|
+
: undefined;
|
|
1005
|
+
loadingSignal = this.isLoadingCancel;
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
if (callback) {
|
|
1009
|
+
loadingSignal.set(true);
|
|
1010
|
+
try {
|
|
1011
|
+
await callback();
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
console.error(`Error en la acción ${action}:`, error);
|
|
1015
|
+
}
|
|
1016
|
+
finally {
|
|
1017
|
+
loadingSignal.set(false);
|
|
1018
|
+
this.closeToast();
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
this.closeToast();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1026
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, providedIn: "root" });
|
|
1027
|
+
}
|
|
1028
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, decorators: [{
|
|
1029
|
+
type: Injectable,
|
|
1030
|
+
args: [{
|
|
1031
|
+
providedIn: "root",
|
|
1032
|
+
}]
|
|
1033
|
+
}] });
|
|
1034
|
+
|
|
1035
|
+
class JAlertToastComponent {
|
|
1036
|
+
colorsService;
|
|
1037
|
+
monocromatic = false;
|
|
1038
|
+
position = 'bottom-right';
|
|
1039
|
+
alertToastService = inject(JAlertToastService);
|
|
1040
|
+
constructor(colorsService) {
|
|
1041
|
+
this.colorsService = colorsService;
|
|
1042
|
+
}
|
|
1043
|
+
toasts = computed(() => this.alertToastService.toasts());
|
|
1044
|
+
icons = {
|
|
1045
|
+
success: CircleCheck,
|
|
1046
|
+
error: CircleX,
|
|
1047
|
+
warning: TriangleAlert,
|
|
1048
|
+
info: Info,
|
|
1049
|
+
question: CircleHelp,
|
|
1050
|
+
loading: Loader2,
|
|
1051
|
+
close: X,
|
|
1052
|
+
};
|
|
1053
|
+
getIcon(type) {
|
|
1054
|
+
return this.icons[type] || this.icons['info'];
|
|
1055
|
+
}
|
|
1056
|
+
handleAction(toastId, action) {
|
|
1057
|
+
this.alertToastService.executeToastAction(toastId, action);
|
|
1058
|
+
}
|
|
1059
|
+
closeToast(toastId) {
|
|
1060
|
+
this.alertToastService.closeToastById(toastId);
|
|
1061
|
+
}
|
|
1062
|
+
// Obtener la clase del toast
|
|
1063
|
+
getToastClass(type) {
|
|
1064
|
+
return this.colorsService.getAlertClass(type, this.monocromatic);
|
|
1065
|
+
}
|
|
1066
|
+
// Obtener la clase del icono
|
|
1067
|
+
getIconClass(type) {
|
|
1068
|
+
return this.colorsService.getIconClass(type, this.monocromatic);
|
|
1069
|
+
}
|
|
1070
|
+
// Obtener clase del boton
|
|
1071
|
+
getButtonClass(type) {
|
|
1072
|
+
return this.colorsService.getButtonClass(type, this.monocromatic);
|
|
1073
|
+
}
|
|
1074
|
+
// Obtener la clase secundaria del botón
|
|
1075
|
+
getButtonSecondaryClass(type) {
|
|
1076
|
+
return this.colorsService.getButtonSecondaryClass(type, this.monocromatic);
|
|
1077
|
+
}
|
|
1078
|
+
// Asignar posision del toast
|
|
1079
|
+
getPositionClass() {
|
|
1080
|
+
const base = 'fixed z-1000 flex flex-col gap-2 max-w-md';
|
|
1081
|
+
switch (this.position) {
|
|
1082
|
+
case 'top-left':
|
|
1083
|
+
return `${base} top-4 left-4`;
|
|
1084
|
+
case 'top-right':
|
|
1085
|
+
return `${base} top-20 right-4`;
|
|
1086
|
+
case 'bottom-left':
|
|
1087
|
+
return `${base} bottom-4 left-4`;
|
|
1088
|
+
case 'bottom-right':
|
|
1089
|
+
default:
|
|
1090
|
+
return `${base} bottom-4 right-4`;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastComponent, deps: [{ token: JColorsService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1094
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JAlertToastComponent, isStandalone: true, selector: "JAlertToast", inputs: { monocromatic: "monocromatic", position: "position" }, ngImport: i0, template: "<div [ngClass]=\"getPositionClass()\">\r\n @for (toast of toasts(); track toast.id) {\r\n <div @toastTransition\r\n class=\"relative w-100 border border-border border-l-4 p-4 rounded-lg shadow-lg overflow-hidden flex gap-3\"\r\n [ngClass]=\"getToastClass(toast.config.type)\">\r\n\r\n <!-- Close button -->\r\n <button\r\n class=\"absolute p-1 top-2 right-2 cursor-pointer rounded transition duration-300 hover:bg-background dark:hover:bg-dark-background\"\r\n [ngClass]=\"getIconClass(toast.config.type)\" \r\n (click)=\"closeToast(toast.id)\"\r\n >\r\n <lucide-icon [name]=\"icons['close']\" [size]=\"16\"></lucide-icon>\r\n </button>\r\n\r\n <!-- Icono grande en la esquina inferior izquierda -->\r\n <div [ngClass]=\"{ 'animate-spin': toast.config.type === 'loading'}\"\r\n class=\"absolute -bottom-5 opacity-15 dark:opacity-15 -left-5 text-black dark:text-white z-2 pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(toast.config.type)\" \r\n [size]=\"100\"\r\n [ngClass]=\"getIconClass(toast.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n\r\n <!-- Icono peque\u00F1o en la esquina superior derecha -->\r\n <div [ngClass]=\"{ 'animate-spin': toast.config.type === 'loading'}\"\r\n class=\"absolute top-5 opacity-15 dark:opacity-15 right-5 text-black dark:text-white z-2 pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(toast.config.type)\" \r\n [size]=\"30\"\r\n [ngClass]=\"getIconClass(toast.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"flex-1\">\r\n <h3 class=\"text-sm font-semibold text-black dark:text-white pb-2\">{{ toast.config.title }}</h3>\r\n <p class=\"text-sm text-black/80 dark:text-dark-muted-foreground\" [innerHTML]=\"toast.config.description\"></p>\r\n\r\n <!-- Action buttons -->\r\n <div class=\"flex justify-end gap-2 mt-2\">\r\n <!-- Bot\u00F3n Cancelar (No se muestra si es \"success\") -->\r\n @if (toast.config.type !== 'success' && toast.onCancelCallback) {\r\n <JButton \r\n (clicked)=\"handleAction(toast.id, 'cancel')\"\r\n [disabled]=\"toast.isCancelLoading || toast.isActionLoading\" \r\n [isLoading]=\"toast.isCancelLoading\"\r\n classes=\"text-[10px] min-w-auto\"\r\n [ngClasses]=\"getButtonSecondaryClass(toast.config.type)\"\r\n >\r\n Cancelar\r\n </JButton>\r\n }\r\n\r\n <!-- Bot\u00F3n action (No se muestra si es \"loading\") -->\r\n @if (toast.config.type !== 'loading' && toast.onActionCallback) {\r\n <JButton \r\n (clicked)=\"handleAction(toast.id, 'action')\"\r\n [disabled]=\"toast.isCancelLoading || toast.isActionLoading\" \r\n [isLoading]=\"toast.isActionLoading\"\r\n classes=\"text-[10px] min-w-auto\" \r\n [ngClasses]=\"getButtonClass(toast.config.type)\"\r\n >\r\n {{toast.actionNameButton}}\r\n </JButton>\r\n }\r\n </div>\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }], animations: [
|
|
1095
|
+
trigger("toastTransition", [
|
|
1096
|
+
transition(":enter", [
|
|
1097
|
+
style({ opacity: 0, transform: "translateY(10px)" }),
|
|
1098
|
+
animate("300ms ease-out", style({ opacity: 1, transform: "translateY(0)" }))
|
|
1099
|
+
]),
|
|
1100
|
+
transition(":leave", [
|
|
1101
|
+
animate("150ms ease-in", style({ opacity: 0, transform: "translateY(10px)" }))
|
|
1102
|
+
])
|
|
1103
|
+
])
|
|
1104
|
+
] });
|
|
1105
|
+
}
|
|
1106
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastComponent, decorators: [{
|
|
1107
|
+
type: Component,
|
|
1108
|
+
args: [{ selector: 'JAlertToast', imports: [LucideAngularModule, NgClass, JButtonComponent], animations: [
|
|
1109
|
+
trigger("toastTransition", [
|
|
1110
|
+
transition(":enter", [
|
|
1111
|
+
style({ opacity: 0, transform: "translateY(10px)" }),
|
|
1112
|
+
animate("300ms ease-out", style({ opacity: 1, transform: "translateY(0)" }))
|
|
1113
|
+
]),
|
|
1114
|
+
transition(":leave", [
|
|
1115
|
+
animate("150ms ease-in", style({ opacity: 0, transform: "translateY(10px)" }))
|
|
1116
|
+
])
|
|
1117
|
+
])
|
|
1118
|
+
], template: "<div [ngClass]=\"getPositionClass()\">\r\n @for (toast of toasts(); track toast.id) {\r\n <div @toastTransition\r\n class=\"relative w-100 border border-border border-l-4 p-4 rounded-lg shadow-lg overflow-hidden flex gap-3\"\r\n [ngClass]=\"getToastClass(toast.config.type)\">\r\n\r\n <!-- Close button -->\r\n <button\r\n class=\"absolute p-1 top-2 right-2 cursor-pointer rounded transition duration-300 hover:bg-background dark:hover:bg-dark-background\"\r\n [ngClass]=\"getIconClass(toast.config.type)\" \r\n (click)=\"closeToast(toast.id)\"\r\n >\r\n <lucide-icon [name]=\"icons['close']\" [size]=\"16\"></lucide-icon>\r\n </button>\r\n\r\n <!-- Icono grande en la esquina inferior izquierda -->\r\n <div [ngClass]=\"{ 'animate-spin': toast.config.type === 'loading'}\"\r\n class=\"absolute -bottom-5 opacity-15 dark:opacity-15 -left-5 text-black dark:text-white z-2 pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(toast.config.type)\" \r\n [size]=\"100\"\r\n [ngClass]=\"getIconClass(toast.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n\r\n <!-- Icono peque\u00F1o en la esquina superior derecha -->\r\n <div [ngClass]=\"{ 'animate-spin': toast.config.type === 'loading'}\"\r\n class=\"absolute top-5 opacity-15 dark:opacity-15 right-5 text-black dark:text-white z-2 pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(toast.config.type)\" \r\n [size]=\"30\"\r\n [ngClass]=\"getIconClass(toast.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"flex-1\">\r\n <h3 class=\"text-sm font-semibold text-black dark:text-white pb-2\">{{ toast.config.title }}</h3>\r\n <p class=\"text-sm text-black/80 dark:text-dark-muted-foreground\" [innerHTML]=\"toast.config.description\"></p>\r\n\r\n <!-- Action buttons -->\r\n <div class=\"flex justify-end gap-2 mt-2\">\r\n <!-- Bot\u00F3n Cancelar (No se muestra si es \"success\") -->\r\n @if (toast.config.type !== 'success' && toast.onCancelCallback) {\r\n <JButton \r\n (clicked)=\"handleAction(toast.id, 'cancel')\"\r\n [disabled]=\"toast.isCancelLoading || toast.isActionLoading\" \r\n [isLoading]=\"toast.isCancelLoading\"\r\n classes=\"text-[10px] min-w-auto\"\r\n [ngClasses]=\"getButtonSecondaryClass(toast.config.type)\"\r\n >\r\n Cancelar\r\n </JButton>\r\n }\r\n\r\n <!-- Bot\u00F3n action (No se muestra si es \"loading\") -->\r\n @if (toast.config.type !== 'loading' && toast.onActionCallback) {\r\n <JButton \r\n (clicked)=\"handleAction(toast.id, 'action')\"\r\n [disabled]=\"toast.isCancelLoading || toast.isActionLoading\" \r\n [isLoading]=\"toast.isActionLoading\"\r\n classes=\"text-[10px] min-w-auto\" \r\n [ngClasses]=\"getButtonClass(toast.config.type)\"\r\n >\r\n {{toast.actionNameButton}}\r\n </JButton>\r\n }\r\n </div>\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>" }]
|
|
1119
|
+
}], ctorParameters: () => [{ type: JColorsService }], propDecorators: { monocromatic: [{
|
|
1120
|
+
type: Input
|
|
1121
|
+
}], position: [{
|
|
1122
|
+
type: Input
|
|
1123
|
+
}] } });
|
|
1124
|
+
|
|
1125
|
+
class JAlertDialogService {
|
|
1126
|
+
isOpenSignal = signal(false);
|
|
1127
|
+
configSignal = signal(null);
|
|
1128
|
+
// Estados de carga para los botones
|
|
1129
|
+
isLoadingConfirm = signal(false);
|
|
1130
|
+
isLoadingCancel = signal(false);
|
|
1131
|
+
isLoadingRetry = signal(false);
|
|
1132
|
+
// Callbacks
|
|
1133
|
+
onConfirmCallback;
|
|
1134
|
+
onCancelCallback;
|
|
1135
|
+
onRetryCallback;
|
|
1136
|
+
isOpen = computed(() => this.isOpenSignal());
|
|
1137
|
+
config = computed(() => this.configSignal());
|
|
1138
|
+
isConfirmLoading = computed(() => this.isLoadingConfirm());
|
|
1139
|
+
isCancelLoading = computed(() => this.isLoadingCancel());
|
|
1140
|
+
isRetryLoading = computed(() => this.isLoadingRetry());
|
|
1141
|
+
// Add a dialogs computed property that returns an array with a single dialog when open
|
|
1142
|
+
dialogs = computed(() => {
|
|
1143
|
+
if (!this.isOpenSignal())
|
|
1144
|
+
return [];
|
|
1145
|
+
return [{
|
|
1146
|
+
config: this.configSignal(),
|
|
1147
|
+
isConfirmLoading: this.isLoadingConfirm(),
|
|
1148
|
+
isCancelLoading: this.isLoadingCancel(),
|
|
1149
|
+
isRetryLoading: this.isLoadingRetry()
|
|
1150
|
+
}];
|
|
1151
|
+
});
|
|
1152
|
+
getConfig() {
|
|
1153
|
+
return this.configSignal();
|
|
1154
|
+
}
|
|
1155
|
+
AlertDialog(config) {
|
|
1156
|
+
this.onConfirmCallback = "onConfirm" in config ? config.onConfirm : undefined;
|
|
1157
|
+
this.onCancelCallback = "onCancel" in config ? config.onCancel : undefined;
|
|
1158
|
+
this.onRetryCallback = "onRetry" in config ? config.onRetry : undefined;
|
|
1159
|
+
// Reset loading states
|
|
1160
|
+
this.isLoadingConfirm.set(false);
|
|
1161
|
+
this.isLoadingCancel.set(false);
|
|
1162
|
+
this.isLoadingRetry.set(false);
|
|
1163
|
+
this.configSignal.set(config);
|
|
1164
|
+
this.isOpenSignal.set(true);
|
|
1165
|
+
}
|
|
1166
|
+
closeDialog() {
|
|
1167
|
+
this.isOpenSignal.set(false);
|
|
1168
|
+
this.configSignal.set(null);
|
|
1169
|
+
}
|
|
1170
|
+
async executeAction(action) {
|
|
1171
|
+
let callback;
|
|
1172
|
+
let loadingSignal;
|
|
1173
|
+
switch (action) {
|
|
1174
|
+
case "confirm":
|
|
1175
|
+
callback = this.onConfirmCallback;
|
|
1176
|
+
loadingSignal = this.isLoadingConfirm;
|
|
1177
|
+
break;
|
|
1178
|
+
case "cancel":
|
|
1179
|
+
callback = this.onCancelCallback;
|
|
1180
|
+
loadingSignal = this.isLoadingCancel;
|
|
1181
|
+
break;
|
|
1182
|
+
case "retry":
|
|
1183
|
+
callback = this.onRetryCallback;
|
|
1184
|
+
loadingSignal = this.isLoadingRetry;
|
|
1185
|
+
break;
|
|
1186
|
+
}
|
|
1187
|
+
if (!callback) {
|
|
1188
|
+
this.closeDialog();
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
loadingSignal.set(true);
|
|
1192
|
+
try {
|
|
1193
|
+
const result = callback();
|
|
1194
|
+
// Soporte para Observable
|
|
1195
|
+
if (isObservable(result)) {
|
|
1196
|
+
await firstValueFrom(result);
|
|
1197
|
+
}
|
|
1198
|
+
// Soporte para Promise
|
|
1199
|
+
if (result instanceof Promise) {
|
|
1200
|
+
await result;
|
|
1201
|
+
}
|
|
1202
|
+
// Si es void, no hacemos nada extra
|
|
1203
|
+
}
|
|
1204
|
+
catch (error) {
|
|
1205
|
+
console.error(`Error en la acción ${action}:`, error);
|
|
1206
|
+
}
|
|
1207
|
+
finally {
|
|
1208
|
+
loadingSignal.set(false);
|
|
1209
|
+
this.closeDialog();
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1213
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, providedIn: "root" });
|
|
1214
|
+
}
|
|
1215
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, decorators: [{
|
|
1216
|
+
type: Injectable,
|
|
1217
|
+
args: [{
|
|
1218
|
+
providedIn: "root",
|
|
1219
|
+
}]
|
|
1220
|
+
}] });
|
|
1221
|
+
|
|
1222
|
+
class JAlertDialogComponent {
|
|
1223
|
+
colorsService;
|
|
1224
|
+
monocromatic = false;
|
|
1225
|
+
alertDialogService = inject(JAlertDialogService);
|
|
1226
|
+
constructor(colorsService) {
|
|
1227
|
+
this.colorsService = colorsService;
|
|
1228
|
+
}
|
|
1229
|
+
// Single computed property for dialogs
|
|
1230
|
+
dialogs = computed(() => this.alertDialogService.dialogs());
|
|
1231
|
+
icons = {
|
|
1232
|
+
success: CircleCheck,
|
|
1233
|
+
error: CircleX,
|
|
1234
|
+
warning: TriangleAlert,
|
|
1235
|
+
info: Info,
|
|
1236
|
+
question: CircleHelp,
|
|
1237
|
+
loading: Loader2,
|
|
1238
|
+
};
|
|
1239
|
+
getIcon(type) {
|
|
1240
|
+
return this.icons[type] || this.icons['info'];
|
|
1241
|
+
}
|
|
1242
|
+
handleAction(action) {
|
|
1243
|
+
this.alertDialogService.executeAction(action);
|
|
1244
|
+
}
|
|
1245
|
+
// Get the class for the toast
|
|
1246
|
+
getDialogClass(type) {
|
|
1247
|
+
return this.colorsService.getAlertClass(type, this.monocromatic);
|
|
1248
|
+
}
|
|
1249
|
+
// Get the class for the icon
|
|
1250
|
+
getIconClass(type) {
|
|
1251
|
+
return this.colorsService.getIconClass(type, this.monocromatic);
|
|
1252
|
+
}
|
|
1253
|
+
// Get the class for the button
|
|
1254
|
+
getButtonClass(type) {
|
|
1255
|
+
return this.colorsService.getButtonClass(type, this.monocromatic);
|
|
1256
|
+
}
|
|
1257
|
+
getButtonSecondaryClass(type) {
|
|
1258
|
+
return this.colorsService.getButtonSecondaryClass(type, this.monocromatic);
|
|
1259
|
+
}
|
|
1260
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogComponent, deps: [{ token: JColorsService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1261
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JAlertDialogComponent, isStandalone: true, selector: "JAlertDialog", inputs: { monocromatic: "monocromatic" }, ngImport: i0, template: "@for (dialog of dialogs(); track dialog.config.title) {\r\n <div class=\"fixed inset-0 z-1001 flex items-center justify-center bg-black/50\">\r\n <div @modalTransition\r\n class=\"relative w-100 border border-border p-4 rounded-lg shadow-5xl overflow-hidden\"\r\n [ngClass]=\"getDialogClass(dialog.config.type)\">\r\n \r\n <!-- Icono grande en la esquina inferior izquierda -->\r\n <div [ngClass]=\"{ 'animate-spin': dialog.config.type === 'loading'}\"\r\n class=\"absolute -bottom-5 opacity-25 dark:opacity-15 -left-5 text-black dark:text-white pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(dialog.config.type)\" \r\n [size]=\"130\"\r\n [ngClass]=\"getIconClass(dialog.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n \r\n <!-- Icono peque\u00F1o en la esquina superior derecha -->\r\n <div [ngClass]=\"{ 'animate-spin': dialog.config.type === 'loading'}\"\r\n class=\"absolute top-5 opacity-25 dark:opacity-15 right-5 text-black dark:text-white pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(dialog.config.type)\" \r\n [size]=\"30\"\r\n [ngClass]=\"getIconClass(dialog.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n \r\n <h3 class=\"text-lg font-semibold text-black dark:text-white pb-2\">{{ dialog.config.title }}</h3>\r\n <p class=\"text-black/80 dark:text-dark-muted-foreground\" [innerHTML]=\"dialog.config.description\"></p>\r\n \r\n <div class=\"flex justify-end gap-2 mt-4\">\r\n <!-- Bot\u00F3n Reintentar (Solo si el tipo es \"error\") -->\r\n @if (dialog.config.type === 'error' && dialog.config.onRetry) {\r\n <JButton \r\n (clicked)=\"handleAction('retry')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isRetryLoading\" \r\n [ngClasses]=\"getButtonSecondaryClass(dialog.config.type)\"\r\n >\r\n Reintentar\r\n </JButton>\r\n }\r\n \r\n <!-- Bot\u00F3n Cancelar (No se muestra si es \"success\") -->\r\n @if (dialog.config.type !== 'success' && dialog.config.onCancel) {\r\n <JButton \r\n (clicked)=\"handleAction('cancel')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isCancelLoading\" \r\n [ngClasses]=\"getButtonSecondaryClass(dialog.config.type)\"\r\n >\r\n Cancelar\r\n </JButton>\r\n }\r\n \r\n <!-- Bot\u00F3n Confirmar (No se muestra si es \"loading\") -->\r\n @if (dialog.config.type !== 'loading' && dialog?.config?.onConfirm) {\r\n <JButton \r\n (clicked)=\"handleAction('confirm')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isConfirmLoading\" \r\n [ngClasses]=\"getButtonClass(dialog.config.type)\"\r\n >\r\n Confirmar\r\n </JButton>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }], animations: [
|
|
1262
|
+
trigger("modalTransition", [
|
|
1263
|
+
transition(":enter", [
|
|
1264
|
+
style({ transform: "translateY(1rem)", opacity: 0 }),
|
|
1265
|
+
animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
|
|
1266
|
+
]),
|
|
1267
|
+
transition(":leave", [
|
|
1268
|
+
animate("150ms ease-in", style({ transform: "translateY(1rem)", opacity: 0 })),
|
|
1269
|
+
]),
|
|
1270
|
+
]),
|
|
1271
|
+
] });
|
|
1272
|
+
}
|
|
1273
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogComponent, decorators: [{
|
|
1274
|
+
type: Component,
|
|
1275
|
+
args: [{ selector: "JAlertDialog", imports: [LucideAngularModule, NgClass, JButtonComponent], animations: [
|
|
1276
|
+
trigger("modalTransition", [
|
|
1277
|
+
transition(":enter", [
|
|
1278
|
+
style({ transform: "translateY(1rem)", opacity: 0 }),
|
|
1279
|
+
animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
|
|
1280
|
+
]),
|
|
1281
|
+
transition(":leave", [
|
|
1282
|
+
animate("150ms ease-in", style({ transform: "translateY(1rem)", opacity: 0 })),
|
|
1283
|
+
]),
|
|
1284
|
+
]),
|
|
1285
|
+
], template: "@for (dialog of dialogs(); track dialog.config.title) {\r\n <div class=\"fixed inset-0 z-1001 flex items-center justify-center bg-black/50\">\r\n <div @modalTransition\r\n class=\"relative w-100 border border-border p-4 rounded-lg shadow-5xl overflow-hidden\"\r\n [ngClass]=\"getDialogClass(dialog.config.type)\">\r\n \r\n <!-- Icono grande en la esquina inferior izquierda -->\r\n <div [ngClass]=\"{ 'animate-spin': dialog.config.type === 'loading'}\"\r\n class=\"absolute -bottom-5 opacity-25 dark:opacity-15 -left-5 text-black dark:text-white pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(dialog.config.type)\" \r\n [size]=\"130\"\r\n [ngClass]=\"getIconClass(dialog.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n \r\n <!-- Icono peque\u00F1o en la esquina superior derecha -->\r\n <div [ngClass]=\"{ 'animate-spin': dialog.config.type === 'loading'}\"\r\n class=\"absolute top-5 opacity-25 dark:opacity-15 right-5 text-black dark:text-white pointer-events-none\">\r\n <lucide-icon \r\n [name]=\"getIcon(dialog.config.type)\" \r\n [size]=\"30\"\r\n [ngClass]=\"getIconClass(dialog.config.type)\"\r\n >\r\n </lucide-icon>\r\n </div>\r\n \r\n <h3 class=\"text-lg font-semibold text-black dark:text-white pb-2\">{{ dialog.config.title }}</h3>\r\n <p class=\"text-black/80 dark:text-dark-muted-foreground\" [innerHTML]=\"dialog.config.description\"></p>\r\n \r\n <div class=\"flex justify-end gap-2 mt-4\">\r\n <!-- Bot\u00F3n Reintentar (Solo si el tipo es \"error\") -->\r\n @if (dialog.config.type === 'error' && dialog.config.onRetry) {\r\n <JButton \r\n (clicked)=\"handleAction('retry')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isRetryLoading\" \r\n [ngClasses]=\"getButtonSecondaryClass(dialog.config.type)\"\r\n >\r\n Reintentar\r\n </JButton>\r\n }\r\n \r\n <!-- Bot\u00F3n Cancelar (No se muestra si es \"success\") -->\r\n @if (dialog.config.type !== 'success' && dialog.config.onCancel) {\r\n <JButton \r\n (clicked)=\"handleAction('cancel')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isCancelLoading\" \r\n [ngClasses]=\"getButtonSecondaryClass(dialog.config.type)\"\r\n >\r\n Cancelar\r\n </JButton>\r\n }\r\n \r\n <!-- Bot\u00F3n Confirmar (No se muestra si es \"loading\") -->\r\n @if (dialog.config.type !== 'loading' && dialog?.config?.onConfirm) {\r\n <JButton \r\n (clicked)=\"handleAction('confirm')\" \r\n [disabled]=\"dialog.isRetryLoading || dialog.isCancelLoading || dialog.isConfirmLoading\" \r\n [isLoading]=\"dialog.isConfirmLoading\" \r\n [ngClasses]=\"getButtonClass(dialog.config.type)\"\r\n >\r\n Confirmar\r\n </JButton>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }" }]
|
|
1286
|
+
}], ctorParameters: () => [{ type: JColorsService }], propDecorators: { monocromatic: [{
|
|
1287
|
+
type: Input
|
|
1288
|
+
}] } });
|
|
1289
|
+
|
|
1290
|
+
const API_URL = new InjectionToken('API_URL');
|
|
1291
|
+
|
|
1292
|
+
class JHttpParamsService {
|
|
1293
|
+
constructor() { }
|
|
1294
|
+
// Formatear parámetros para peticiones HTTP
|
|
1295
|
+
resParams(params) {
|
|
1296
|
+
// Construir HttpParams
|
|
1297
|
+
let httpParams = new HttpParams();
|
|
1298
|
+
// Recorrer todos los parámetros
|
|
1299
|
+
if (params) {
|
|
1300
|
+
Object.keys(params).forEach(key => {
|
|
1301
|
+
const value = params[key];
|
|
1302
|
+
// Si el valor es un objeto (ej. filter: { id_status: [1, 2] })
|
|
1303
|
+
if (typeof value === 'object' && value !== null) {
|
|
1304
|
+
Object.keys(value).forEach(subKey => {
|
|
1305
|
+
const subValue = value[subKey];
|
|
1306
|
+
// Si el subValor es un array (ej. id_status: [1, 2])
|
|
1307
|
+
if (Array.isArray(subValue)) {
|
|
1308
|
+
subValue.forEach(v => {
|
|
1309
|
+
httpParams = httpParams.append(`${key}[${subKey}]`, v);
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
// Si es un valor único
|
|
1314
|
+
httpParams = httpParams.append(`${key}[${subKey}]`, subValue);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
else {
|
|
1319
|
+
// Si es un valor plano (ej. id_status: 1)
|
|
1320
|
+
httpParams = httpParams.append(key, value);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
return httpParams;
|
|
1325
|
+
}
|
|
1326
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1327
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, providedIn: 'root' });
|
|
1328
|
+
}
|
|
1329
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, decorators: [{
|
|
1330
|
+
type: Injectable,
|
|
1331
|
+
args: [{
|
|
1332
|
+
providedIn: 'root'
|
|
1333
|
+
}]
|
|
1334
|
+
}], ctorParameters: () => [] });
|
|
1335
|
+
|
|
1336
|
+
class ConverterService {
|
|
1337
|
+
constructor() { }
|
|
1338
|
+
/**
|
|
1339
|
+
* Obtener la clave de ordenación correcta
|
|
1340
|
+
* @param sortColumn
|
|
1341
|
+
* @returns
|
|
1342
|
+
*/
|
|
1343
|
+
getSortKey(sortColumn) {
|
|
1344
|
+
// Verifica si sortColumn es un objeto y retorna la propiedad 'col'
|
|
1345
|
+
if (typeof sortColumn === 'object' && sortColumn !== null) {
|
|
1346
|
+
return sortColumn.col;
|
|
1347
|
+
}
|
|
1348
|
+
// Si no es un objeto, asume que es una cadena directa
|
|
1349
|
+
return sortColumn;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Convierte un objeto a un array de objetos con clave y valor para iniciar el formulario
|
|
1353
|
+
* @param formGroup
|
|
1354
|
+
* @returns
|
|
1355
|
+
*/
|
|
1356
|
+
initializeFormControls(formGroup) {
|
|
1357
|
+
const formControls = {};
|
|
1358
|
+
Object.keys(formGroup.controls).forEach(key => {
|
|
1359
|
+
formControls[`${key}_control`] = formGroup.get(key);
|
|
1360
|
+
});
|
|
1361
|
+
return formControls;
|
|
1362
|
+
}
|
|
1363
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1364
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, providedIn: 'root' });
|
|
1365
|
+
}
|
|
1366
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, decorators: [{
|
|
1367
|
+
type: Injectable,
|
|
1368
|
+
args: [{
|
|
1369
|
+
providedIn: 'root'
|
|
1370
|
+
}]
|
|
1371
|
+
}], ctorParameters: () => [] });
|
|
1372
|
+
|
|
1373
|
+
class JGenericService {
|
|
1374
|
+
baseUrl;
|
|
1375
|
+
http;
|
|
1376
|
+
HttpParamsService;
|
|
1377
|
+
converterService;
|
|
1378
|
+
constructor(baseUrl, http, HttpParamsService, converterService) {
|
|
1379
|
+
this.baseUrl = baseUrl;
|
|
1380
|
+
this.http = http;
|
|
1381
|
+
this.HttpParamsService = HttpParamsService;
|
|
1382
|
+
this.converterService = converterService;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Método genérico para obtener todos los registros desde un endpoint.
|
|
1386
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1387
|
+
* @param params Parámetros de la petición.
|
|
1388
|
+
* @returns Observable con la respuesta de la API.
|
|
1389
|
+
*/
|
|
1390
|
+
getAll(endpoint, params) {
|
|
1391
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1392
|
+
// Construir HttpParams
|
|
1393
|
+
let httpParams;
|
|
1394
|
+
if (params)
|
|
1395
|
+
httpParams = this.HttpParamsService.resParams(params);
|
|
1396
|
+
// Hacer la petición GET con los HttpParams formateados
|
|
1397
|
+
return this.http.get(url, { params: httpParams });
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Método genérico para obtener un registro desde un endpoint.
|
|
1401
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1402
|
+
* @param id Identificador del registro.
|
|
1403
|
+
* @returns Observable con la respuesta de la API.
|
|
1404
|
+
*/
|
|
1405
|
+
getId(endpoint, id) {
|
|
1406
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1407
|
+
return this.http.get(`${url}/${id}`).pipe(map(response => response.data[endpoint]));
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Método genérico para agregar un registro a un endpoint.
|
|
1411
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1412
|
+
* @param data Datos del registro a agregar.
|
|
1413
|
+
* @returns Observable con la respuesta de la API.
|
|
1414
|
+
*/
|
|
1415
|
+
create(endpoint, data) {
|
|
1416
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1417
|
+
return this.http.post(url, data);
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Método genérico para actualizar un registro en un endpoint.
|
|
1421
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1422
|
+
* @param id Identificador del registro.
|
|
1423
|
+
* @param data Datos del registro a actualizar.
|
|
1424
|
+
* @returns Observable con la respuesta de la API.
|
|
1425
|
+
*/
|
|
1426
|
+
update(endpoint, id, data) {
|
|
1427
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1428
|
+
return this.http.put(`${url}/${id}`, data);
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Método genérico para eliminar un registro de un endpoint.
|
|
1432
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1433
|
+
* @param id Identificador del registro.
|
|
1434
|
+
* @returns Observable con la respuesta de la API.
|
|
1435
|
+
*/
|
|
1436
|
+
delete(endpoint, id) {
|
|
1437
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1438
|
+
return this.http.delete(`${url}/${id}`);
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Método genérico para actualizar estados de un registro en un endpoint.
|
|
1442
|
+
* @param endpoint Distintivo del endpoint ('role', 'status', etc.)
|
|
1443
|
+
* @param id Identificador del registro.
|
|
1444
|
+
* @param data Datos de un registro booleano a actualizar.
|
|
1445
|
+
* @returns Observable con la respuesta de la API.
|
|
1446
|
+
*/
|
|
1447
|
+
enable(endpoint, id, data) {
|
|
1448
|
+
const url = `${this.baseUrl}/${endpoint}`;
|
|
1449
|
+
return this.http.put(`${url}/enable/${id}`, data);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Método genérico para obtener los parámetros de consulta para una tabla.
|
|
1453
|
+
* @param page Número de página actual.
|
|
1454
|
+
* @param limit Número de registros por página.
|
|
1455
|
+
* @param sort Objeto que contiene la columna y la dirección de ordenamiento.
|
|
1456
|
+
* @param filters Filtros aplicados a la consulta.
|
|
1457
|
+
* @param defaultFilters Filtros predeterminados aplicados a la consulta.
|
|
1458
|
+
* @param searchQuery Cadena de búsqueda.
|
|
1459
|
+
* @param columns Columnas a buscar.
|
|
1460
|
+
* @returns
|
|
1461
|
+
*/
|
|
1462
|
+
params({ page, limit, sort, filters, defaultFilters, searchQuery, columns }) {
|
|
1463
|
+
const params = {};
|
|
1464
|
+
if (page)
|
|
1465
|
+
params['page'] = page.toString();
|
|
1466
|
+
if (limit)
|
|
1467
|
+
params['limit'] = limit.toString();
|
|
1468
|
+
// Aplicar los filtros predeterminados enviados desde el padre
|
|
1469
|
+
Object.keys(defaultFilters ?? {}).forEach((key) => {
|
|
1470
|
+
if (!filters.hasOwnProperty(key)) {
|
|
1471
|
+
params[`filter[${key}]`] = defaultFilters[key];
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
// Aplicar el ordenamiento si se ha proporcionado
|
|
1475
|
+
if (sort?.column && sort?.direction !== 'none') {
|
|
1476
|
+
const sortKey = this.converterService.getSortKey(sort?.column);
|
|
1477
|
+
params['sortBy'] = sortKey;
|
|
1478
|
+
params['sortOrder'] = sort?.direction?.toUpperCase();
|
|
1479
|
+
}
|
|
1480
|
+
// Aplicar la búsqueda si se ha proporcionado
|
|
1481
|
+
if (searchQuery && searchQuery.trim() !== '') {
|
|
1482
|
+
params['search'] = searchQuery;
|
|
1483
|
+
params['searchFields'] = columns?.map(col => col);
|
|
1484
|
+
}
|
|
1485
|
+
// Aplicar los filtros si se han proporcionado
|
|
1486
|
+
if (Object.keys(filters).length > 0) {
|
|
1487
|
+
params['filter'] = filters;
|
|
1488
|
+
}
|
|
1489
|
+
return params;
|
|
1490
|
+
}
|
|
1491
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JGenericService, deps: [{ token: API_URL }, { token: i1$2.HttpClient }, { token: JHttpParamsService }, { token: ConverterService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1492
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JGenericService, providedIn: 'root' });
|
|
1493
|
+
}
|
|
1494
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JGenericService, decorators: [{
|
|
1495
|
+
type: Injectable,
|
|
1496
|
+
args: [{
|
|
1497
|
+
providedIn: 'root'
|
|
1498
|
+
}]
|
|
1499
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1500
|
+
type: Inject,
|
|
1501
|
+
args: [API_URL]
|
|
1502
|
+
}] }, { type: i1$2.HttpClient }, { type: JHttpParamsService }, { type: ConverterService }] });
|
|
1503
|
+
|
|
1504
|
+
class JErrorHandlerService {
|
|
1505
|
+
/**
|
|
1506
|
+
* Mapeo de códigos de estado HTTP a mensajes amigables y su tipo de alerta.
|
|
1507
|
+
*/
|
|
1508
|
+
errorMappings = {
|
|
1509
|
+
400: { type: 'info', title: 'Solicitud incorrecta', message: 'Los datos enviados no son válidos. Verifique e intente de nuevo.', persistent: false },
|
|
1510
|
+
401: { type: 'info', title: 'No autorizado', message: 'Debe iniciar sesión para acceder a esta función.', persistent: true },
|
|
1511
|
+
403: { type: 'error', title: 'Acceso denegado', message: 'No tiene permisos para realizar esta acción.', persistent: true },
|
|
1512
|
+
404: { type: 'info', title: 'No encontrado', message: 'El recurso solicitado no existe o fue eliminado.', persistent: false },
|
|
1513
|
+
409: { type: 'info', title: 'Conflicto', message: 'Conflicto en la solicitud. Verifique los datos ingresados.', persistent: false },
|
|
1514
|
+
422: { type: 'info', title: 'Datos no procesables', message: 'Los datos son válidos, pero no pueden ser procesados en este momento.', persistent: false },
|
|
1515
|
+
429: { type: 'info', title: 'Demasiadas solicitudes', message: 'Ha realizado demasiadas solicitudes. Intente más tarde.', persistent: true },
|
|
1516
|
+
500: { type: 'error', title: 'Error del servidor', message: 'Ocurrió un error inesperado. Intente más tarde.', persistent: true },
|
|
1517
|
+
503: { type: 'error', title: 'Servicio no disponible', message: 'El servidor está en mantenimiento o sobrecargado.', persistent: true }
|
|
1518
|
+
};
|
|
1519
|
+
constructor() { }
|
|
1520
|
+
/**
|
|
1521
|
+
* Manejo de errores HTTP y retorno de un mensaje estructurado con tipo de alerta.
|
|
1522
|
+
*/
|
|
1523
|
+
handleHttpError(error) {
|
|
1524
|
+
console.error('HTTP Error:', error); // Log para depuración
|
|
1525
|
+
// Si no hay error definido, retornamos mensaje genérico
|
|
1526
|
+
if (!error) {
|
|
1527
|
+
return {
|
|
1528
|
+
type: 'error',
|
|
1529
|
+
title: 'Error desconocido',
|
|
1530
|
+
message: 'Ocurrió un error inesperado. Intente de nuevo más tarde.',
|
|
1531
|
+
persistent: true
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const status = error?.status || 0;
|
|
1535
|
+
const backendMessage = error?.error?.msg || error?.message;
|
|
1536
|
+
const errorInfo = this.errorMappings[status] || {
|
|
1537
|
+
title: 'Error desconocido',
|
|
1538
|
+
message: 'Ocurrió un error inesperado. Intente de nuevo más tarde.',
|
|
1539
|
+
type: 'error',
|
|
1540
|
+
persistent: true
|
|
1541
|
+
};
|
|
1542
|
+
return {
|
|
1543
|
+
type: errorInfo.type,
|
|
1544
|
+
title: errorInfo.title,
|
|
1545
|
+
message: backendMessage || errorInfo.message,
|
|
1546
|
+
persistent: errorInfo.persistent
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1550
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, providedIn: 'root' });
|
|
1551
|
+
}
|
|
1552
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, decorators: [{
|
|
1553
|
+
type: Injectable,
|
|
1554
|
+
args: [{
|
|
1555
|
+
providedIn: 'root'
|
|
1556
|
+
}]
|
|
1557
|
+
}], ctorParameters: () => [] });
|
|
1558
|
+
|
|
1559
|
+
class OptionComponent {
|
|
1560
|
+
elementRef;
|
|
1561
|
+
value;
|
|
1562
|
+
disabled = false;
|
|
1563
|
+
// Used internally by JSelect
|
|
1564
|
+
get dataValue() {
|
|
1565
|
+
return this.value;
|
|
1566
|
+
}
|
|
1567
|
+
// Store the text content for display in the select
|
|
1568
|
+
_text = '';
|
|
1569
|
+
get text() {
|
|
1570
|
+
return this._text;
|
|
1571
|
+
}
|
|
1572
|
+
constructor(elementRef) {
|
|
1573
|
+
this.elementRef = elementRef;
|
|
1574
|
+
}
|
|
1575
|
+
ngAfterViewInit() {
|
|
1576
|
+
// Extract text content after view is initialized
|
|
1577
|
+
this._text = this.elementRef.nativeElement.textContent.trim();
|
|
1578
|
+
}
|
|
1579
|
+
// This will be called by JSelect to set the text content
|
|
1580
|
+
setTextContent(text) {
|
|
1581
|
+
this._text = text;
|
|
1582
|
+
}
|
|
1583
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: OptionComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1584
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.7", type: OptionComponent, isStandalone: true, selector: "JOption", inputs: { value: "value", disabled: "disabled" }, host: { properties: { "attr.data-value": "this.dataValue" } }, ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
1585
|
+
}
|
|
1586
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: OptionComponent, decorators: [{
|
|
1587
|
+
type: Component,
|
|
1588
|
+
args: [{ selector: 'JOption', standalone: true, imports: [CommonModule], template: `<ng-content></ng-content>` }]
|
|
1589
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{
|
|
1590
|
+
type: Input
|
|
1591
|
+
}], disabled: [{
|
|
1592
|
+
type: Input
|
|
1593
|
+
}], dataValue: [{
|
|
1594
|
+
type: HostBinding,
|
|
1595
|
+
args: ['attr.data-value']
|
|
1596
|
+
}] } });
|
|
1597
|
+
|
|
1598
|
+
class JSelectComponent {
|
|
1599
|
+
cdr;
|
|
1600
|
+
elementRef;
|
|
1601
|
+
genericService;
|
|
1602
|
+
// Lucide icons
|
|
1603
|
+
icons = {
|
|
1604
|
+
chevronDown: ChevronDown,
|
|
1605
|
+
view: Eye,
|
|
1606
|
+
x: X,
|
|
1607
|
+
check: Check,
|
|
1608
|
+
search: Search, // xxx
|
|
1609
|
+
loading: Loader2,
|
|
1610
|
+
};
|
|
1611
|
+
type = 'dropdown'; // Tipo de select
|
|
1612
|
+
btnIcon = this.icons['view']; // Icono del botón
|
|
1613
|
+
btnText = ''; // Texto del botón
|
|
1614
|
+
title = 'Seleccionar'; // Titulo del dropdown
|
|
1615
|
+
placeholder = 'Seleccione una opción'; // Texto cuando no hay selección
|
|
1616
|
+
showClear = false; // Mostrar botón para limpiar selección
|
|
1617
|
+
columns = []; // Columnas a mostrar en el dropdown ideal para la tabla
|
|
1618
|
+
options = []; // Opciones quemadas para el dropdown
|
|
1619
|
+
optionLabel = 'text'; // Propiedad a mostrar en el dropdown
|
|
1620
|
+
optionValue = 'value'; // Propiedad a usar como valor en el dropdown
|
|
1621
|
+
labelSeparator = ' ';
|
|
1622
|
+
isLoading = false;
|
|
1623
|
+
// Datos para el searchable
|
|
1624
|
+
endpoint = ''; // Endpoint para buscar datos
|
|
1625
|
+
loadOnInit = false; // Cargar datos al inicializar
|
|
1626
|
+
defaultFilters = {}; // Parámetros adicionales para la api
|
|
1627
|
+
searchFields = []; // Filtros adicionales para la búsqueda
|
|
1628
|
+
isSearch = true; // Habilitar la búsqueda
|
|
1629
|
+
isFilterSelect = false; // Es un select de filtro
|
|
1630
|
+
sort = 'ASC';
|
|
1631
|
+
updateVisibility = new EventEmitter();
|
|
1632
|
+
selectionChange = new EventEmitter();
|
|
1633
|
+
optionComponents;
|
|
1634
|
+
selectButton;
|
|
1635
|
+
// Selectores
|
|
1636
|
+
isColumnSelectorOpen = false;
|
|
1637
|
+
selectedValue = null;
|
|
1638
|
+
selectedLabel = '';
|
|
1639
|
+
internalOptions = [];
|
|
1640
|
+
// Para la búsqueda
|
|
1641
|
+
searchTerm = '';
|
|
1642
|
+
searchSubject = new Subject();
|
|
1643
|
+
searchSubscription;
|
|
1644
|
+
filteredOptions = [];
|
|
1645
|
+
// Dropdown positioning
|
|
1646
|
+
dropdownTop = 0;
|
|
1647
|
+
dropdownLeft = 0;
|
|
1648
|
+
dropdownWidth = 0;
|
|
1649
|
+
// Para implementar ControlValueAccessor
|
|
1650
|
+
onChange = () => { };
|
|
1651
|
+
onTouched = () => { };
|
|
1652
|
+
disabled = false;
|
|
1653
|
+
// Para detectar clicks fuera del componente
|
|
1654
|
+
clickOutsideListener;
|
|
1655
|
+
constructor(cdr, elementRef, genericService) {
|
|
1656
|
+
this.cdr = cdr;
|
|
1657
|
+
this.elementRef = elementRef;
|
|
1658
|
+
this.genericService = genericService;
|
|
1659
|
+
}
|
|
1660
|
+
ngOnInit() {
|
|
1661
|
+
// Configurar el debounce para la búsqueda
|
|
1662
|
+
this.searchSubscription = this.searchSubject.pipe(debounceTime(1000), distinctUntilChanged()).subscribe(() => {
|
|
1663
|
+
if (this.type === 'searchable') {
|
|
1664
|
+
this.loadData();
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
// Cargar datos al inicializar si es necesario
|
|
1668
|
+
if (this.loadOnInit && this.type === 'searchable') {
|
|
1669
|
+
this.loadData();
|
|
1670
|
+
}
|
|
1671
|
+
// Asignar el texto del botón si no se ha proporcionado
|
|
1672
|
+
this.updateSelectedLabel();
|
|
1673
|
+
}
|
|
1674
|
+
ngAfterContentInit() {
|
|
1675
|
+
if (this.type === 'dropdown') {
|
|
1676
|
+
this.setupClickOutsideListener();
|
|
1677
|
+
this.optionComponents.changes.subscribe(() => {
|
|
1678
|
+
this.processOptions();
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
ngAfterViewInit() {
|
|
1683
|
+
this.setupClickOutsideListener();
|
|
1684
|
+
if (this.type === 'dropdown') {
|
|
1685
|
+
setTimeout(() => {
|
|
1686
|
+
this.processOptions();
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
ngOnChanges(changes) {
|
|
1691
|
+
if (changes['options']) {
|
|
1692
|
+
this.processOptions();
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
ngOnDestroy() {
|
|
1696
|
+
if (this.clickOutsideListener) {
|
|
1697
|
+
document.removeEventListener('click', this.clickOutsideListener);
|
|
1698
|
+
}
|
|
1699
|
+
if (this.searchSubscription) {
|
|
1700
|
+
this.searchSubscription.unsubscribe();
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
// ======================================================
|
|
1704
|
+
// Métodos
|
|
1705
|
+
// ======================================================
|
|
1706
|
+
// Método para procesar las opciones del dropdown
|
|
1707
|
+
processOptions() {
|
|
1708
|
+
this.internalOptions = [];
|
|
1709
|
+
if (this.optionComponents && this.optionComponents.length > 0) {
|
|
1710
|
+
this.optionComponents.forEach(option => {
|
|
1711
|
+
this.internalOptions.push({
|
|
1712
|
+
value: option.value,
|
|
1713
|
+
text: option.text || 'Option'
|
|
1714
|
+
});
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
else if (this.options && this.options.length > 0 && typeof this.options[0] !== 'object') {
|
|
1718
|
+
this.internalOptions = this.options.map(option => ({
|
|
1719
|
+
value: option,
|
|
1720
|
+
text: option.toString()
|
|
1721
|
+
}));
|
|
1722
|
+
}
|
|
1723
|
+
else if (this.options && this.options.length > 0) {
|
|
1724
|
+
this.internalOptions = this.options.map(option => {
|
|
1725
|
+
const text = Array.isArray(this.optionLabel)
|
|
1726
|
+
? this.optionLabel.map(k => this.getNestedValue(option, k)).join(' ')
|
|
1727
|
+
: this.getNestedValue(option, this.optionLabel);
|
|
1728
|
+
return {
|
|
1729
|
+
value: option[this.optionValue],
|
|
1730
|
+
text,
|
|
1731
|
+
original: option
|
|
1732
|
+
};
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
this.filteredOptions = [...this.internalOptions];
|
|
1736
|
+
this.updateSelectedLabel();
|
|
1737
|
+
this.cdr.detectChanges();
|
|
1738
|
+
}
|
|
1739
|
+
// Actualizar la visibilidad de las columnas
|
|
1740
|
+
updateColumnVisibility() {
|
|
1741
|
+
if (this.type === 'multi-table') {
|
|
1742
|
+
this.updateVisibility.emit(this.columns);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
// Seleccionar una opción
|
|
1746
|
+
selectOption(option) {
|
|
1747
|
+
this.selectedValue = option.value;
|
|
1748
|
+
this.selectedLabel = option.text;
|
|
1749
|
+
this.onChange(this.selectedValue);
|
|
1750
|
+
this.selectionChange.emit(option.original ?? option.value);
|
|
1751
|
+
this.isColumnSelectorOpen = false;
|
|
1752
|
+
}
|
|
1753
|
+
// Limpiar la selección
|
|
1754
|
+
clearSelection(event) {
|
|
1755
|
+
event.stopPropagation();
|
|
1756
|
+
this.writeValue(null);
|
|
1757
|
+
this.onChange(null);
|
|
1758
|
+
this.selectionChange.emit(null);
|
|
1759
|
+
}
|
|
1760
|
+
// Limpier el término de búsqueda
|
|
1761
|
+
clearSearchTerm() {
|
|
1762
|
+
this.searchTerm = '';
|
|
1763
|
+
this.onSearchInput();
|
|
1764
|
+
}
|
|
1765
|
+
// Actualizar el texto del botón
|
|
1766
|
+
updateSelectedLabel() {
|
|
1767
|
+
if (this.selectedValue === null) {
|
|
1768
|
+
this.selectedLabel = this.placeholder;
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
const selectedOption = this.internalOptions.find(opt => opt.value === this.selectedValue);
|
|
1772
|
+
this.selectedLabel = selectedOption ? selectedOption.text : this.placeholder;
|
|
1773
|
+
}
|
|
1774
|
+
// Obtener el valor anidado de un objeto
|
|
1775
|
+
getNestedValue(obj, path) {
|
|
1776
|
+
return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? '';
|
|
1777
|
+
}
|
|
1778
|
+
// ======================================================
|
|
1779
|
+
// Search input service
|
|
1780
|
+
// ======================================================
|
|
1781
|
+
// Cargar datos desde el servicio
|
|
1782
|
+
loadData() {
|
|
1783
|
+
if (!this.endpoint)
|
|
1784
|
+
return;
|
|
1785
|
+
this.isLoading = true;
|
|
1786
|
+
const params = {};
|
|
1787
|
+
params['sortOrder'] = this.sort;
|
|
1788
|
+
// Aplicar los filtros predeterminados enviados desde el padre
|
|
1789
|
+
Object.keys(this.defaultFilters).forEach((key) => {
|
|
1790
|
+
params[`filter[${key}]`] = this.defaultFilters[key];
|
|
1791
|
+
});
|
|
1792
|
+
// Añadir término de búsqueda a los parámetros
|
|
1793
|
+
if (this.searchTerm && this.searchTerm.trim() !== '') {
|
|
1794
|
+
params['search'] = this.searchTerm;
|
|
1795
|
+
// Aplicar filtros de búsqueda adicionales
|
|
1796
|
+
const allSearchFields = [this.optionLabel, ...this.searchFields];
|
|
1797
|
+
params['searchFields'] = allSearchFields;
|
|
1798
|
+
}
|
|
1799
|
+
// Esperar 2s
|
|
1800
|
+
// setTimeout(() => {
|
|
1801
|
+
this.genericService.getAll(this.endpoint, params).subscribe({
|
|
1802
|
+
next: (response) => {
|
|
1803
|
+
// Procesar la respuesta según la estructura de tu API
|
|
1804
|
+
const data = response.data[this.endpoint] || [];
|
|
1805
|
+
this.options = data;
|
|
1806
|
+
// Procesar las opciones para el dropdown
|
|
1807
|
+
this.internalOptions = this.options.map(option => ({
|
|
1808
|
+
value: option[this.optionValue],
|
|
1809
|
+
text: this.resolveLabel(option),
|
|
1810
|
+
original: option
|
|
1811
|
+
}));
|
|
1812
|
+
this.filteredOptions = [...this.internalOptions];
|
|
1813
|
+
this.isLoading = false;
|
|
1814
|
+
this.updateSelectedLabel();
|
|
1815
|
+
this.cdr.detectChanges();
|
|
1816
|
+
},
|
|
1817
|
+
error: (error) => {
|
|
1818
|
+
console.error('Error fetching data:', error);
|
|
1819
|
+
this.isLoading = false;
|
|
1820
|
+
this.cdr.detectChanges();
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
// }, 2000);
|
|
1824
|
+
}
|
|
1825
|
+
// Manejar la entrada de búsqueda
|
|
1826
|
+
onSearchInput() {
|
|
1827
|
+
if (this.type === 'searchable') {
|
|
1828
|
+
// Para el tipo searchable, enviar la búsqueda al servicio
|
|
1829
|
+
this.searchSubject.next(this.searchTerm);
|
|
1830
|
+
}
|
|
1831
|
+
else {
|
|
1832
|
+
// Para otros tipos, filtrar localmente
|
|
1833
|
+
this.filterOptions();
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
// Filtrar opciones localmente
|
|
1837
|
+
filterOptions() {
|
|
1838
|
+
if (!this.searchTerm || this.searchTerm.trim() === '') {
|
|
1839
|
+
this.filteredOptions = [...this.internalOptions];
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const searchTermLower = this.searchTerm.toLowerCase();
|
|
1843
|
+
this.filteredOptions = this.internalOptions.filter(option => option.text.toLowerCase().includes(searchTermLower));
|
|
1844
|
+
}
|
|
1845
|
+
resolveLabel(option) {
|
|
1846
|
+
if (Array.isArray(this.optionLabel)) {
|
|
1847
|
+
return this.optionLabel
|
|
1848
|
+
.map((key) => this.getNestedValue(option, key))
|
|
1849
|
+
.filter(Boolean)
|
|
1850
|
+
.join(this.labelSeparator); // <-- usar separador personalizado
|
|
1851
|
+
}
|
|
1852
|
+
return this.getNestedValue(option, this.optionLabel);
|
|
1853
|
+
}
|
|
1854
|
+
// ======================================================
|
|
1855
|
+
// Elemento
|
|
1856
|
+
// ======================================================
|
|
1857
|
+
// Abrir o cerrar el dropdown
|
|
1858
|
+
toggleColumnSelector() {
|
|
1859
|
+
if (this.disabled)
|
|
1860
|
+
return;
|
|
1861
|
+
this.isColumnSelectorOpen = !this.isColumnSelectorOpen;
|
|
1862
|
+
if (this.isColumnSelectorOpen) {
|
|
1863
|
+
this.onTouched();
|
|
1864
|
+
this.updateDropdownPosition();
|
|
1865
|
+
// Cargar datos cuando se abre el dropdown si es de tipo searchable
|
|
1866
|
+
if (this.type === 'searchable' && !this.loadOnInit && this.shouldTriggerLoad()) {
|
|
1867
|
+
this.loadData();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
// Detectar clicks fuera del componente
|
|
1872
|
+
setupClickOutsideListener() {
|
|
1873
|
+
this.clickOutsideListener = (event) => {
|
|
1874
|
+
const clickedElement = event.target;
|
|
1875
|
+
const isOutsideDropdown = !this.elementRef.nativeElement.contains(clickedElement);
|
|
1876
|
+
if (this.isColumnSelectorOpen && isOutsideDropdown) {
|
|
1877
|
+
this.isColumnSelectorOpen = false;
|
|
1878
|
+
this.cdr.detectChanges();
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
document.addEventListener('click', this.clickOutsideListener);
|
|
1882
|
+
}
|
|
1883
|
+
// Obtener el ancho del botón
|
|
1884
|
+
getSelectButtonWidth() {
|
|
1885
|
+
if (this.selectButton) {
|
|
1886
|
+
const width = this.selectButton.nativeElement.offsetWidth;
|
|
1887
|
+
return `${width}px`;
|
|
1888
|
+
}
|
|
1889
|
+
return '250px'; // Default fallback width
|
|
1890
|
+
}
|
|
1891
|
+
// Actualizar la posición del dropdown
|
|
1892
|
+
updateDropdownPosition() {
|
|
1893
|
+
setTimeout(() => {
|
|
1894
|
+
if (!this.selectButton)
|
|
1895
|
+
return;
|
|
1896
|
+
// Get button position
|
|
1897
|
+
const button = this.selectButton.nativeElement;
|
|
1898
|
+
const buttonRect = button.getBoundingClientRect();
|
|
1899
|
+
// Find the closest form container or dialog
|
|
1900
|
+
let offsetParent = this.selectButton.nativeElement;
|
|
1901
|
+
let isInSidebar = false;
|
|
1902
|
+
// Check if we're inside a sidebar form
|
|
1903
|
+
while (offsetParent &&
|
|
1904
|
+
!offsetParent.classList.contains("content_form") &&
|
|
1905
|
+
!offsetParent.classList.contains("p-dialog")) {
|
|
1906
|
+
if (offsetParent.classList.contains("fixed") && offsetParent.classList.contains("right-0")) {
|
|
1907
|
+
isInSidebar = true;
|
|
1908
|
+
break;
|
|
1909
|
+
}
|
|
1910
|
+
offsetParent = offsetParent.parentElement;
|
|
1911
|
+
}
|
|
1912
|
+
// Get offsets based on container
|
|
1913
|
+
let offsetTop = 0;
|
|
1914
|
+
let offsetLeft = 0;
|
|
1915
|
+
if (isInSidebar ||
|
|
1916
|
+
(offsetParent &&
|
|
1917
|
+
(offsetParent.classList.contains("content_form") || offsetParent.classList.contains("p-dialog")))) {
|
|
1918
|
+
offsetTop = offsetParent ? offsetParent.getBoundingClientRect().top : 0;
|
|
1919
|
+
offsetLeft = offsetParent ? offsetParent.getBoundingClientRect().left : 0;
|
|
1920
|
+
}
|
|
1921
|
+
// Position directly below the button by default
|
|
1922
|
+
this.dropdownTop = buttonRect.bottom - offsetTop;
|
|
1923
|
+
this.dropdownLeft = buttonRect.left - offsetLeft;
|
|
1924
|
+
this.dropdownWidth = buttonRect.width;
|
|
1925
|
+
this.cdr.detectChanges();
|
|
1926
|
+
// Wait for dropdown to be in DOM
|
|
1927
|
+
setTimeout(() => {
|
|
1928
|
+
// Get dropdown element
|
|
1929
|
+
const dropdown = this.elementRef.nativeElement.querySelector(".absolute.z-\\[100\\]");
|
|
1930
|
+
if (!dropdown)
|
|
1931
|
+
return;
|
|
1932
|
+
// First use fixed positioning to handle scroll position correctly
|
|
1933
|
+
dropdown.style.position = 'fixed';
|
|
1934
|
+
dropdown.style.top = buttonRect.bottom + 'px';
|
|
1935
|
+
dropdown.style.left = buttonRect.left + 'px';
|
|
1936
|
+
dropdown.style.width = buttonRect.width + 'px';
|
|
1937
|
+
dropdown.style.zIndex = '100';
|
|
1938
|
+
// Wait for dropdown to render with fixed positioning
|
|
1939
|
+
setTimeout(() => {
|
|
1940
|
+
// Get dropdown dimensions
|
|
1941
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
1942
|
+
const viewportHeight = window.innerHeight;
|
|
1943
|
+
const documentWidth = document.documentElement.clientWidth;
|
|
1944
|
+
// Determine if we need to flip or adjust position
|
|
1945
|
+
let newFixedTop = buttonRect.bottom;
|
|
1946
|
+
let newFixedLeft = buttonRect.left;
|
|
1947
|
+
// Check if dropdown goes below viewport and flip it if needed
|
|
1948
|
+
if (buttonRect.bottom + dropdownRect.height > viewportHeight) {
|
|
1949
|
+
newFixedTop = buttonRect.top - dropdownRect.height;
|
|
1950
|
+
}
|
|
1951
|
+
// Check if dropdown goes beyond right edge
|
|
1952
|
+
if (buttonRect.left + dropdownRect.width > documentWidth) {
|
|
1953
|
+
newFixedLeft = buttonRect.left - dropdownRect.width + buttonRect.width;
|
|
1954
|
+
if (newFixedLeft < 0) {
|
|
1955
|
+
newFixedLeft = 5;
|
|
1956
|
+
if (dropdownRect.width > documentWidth) {
|
|
1957
|
+
dropdown.classList.add("constrain-width");
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
// Apply adjusted fixed position
|
|
1962
|
+
dropdown.style.top = newFixedTop - 5 + 'px';
|
|
1963
|
+
dropdown.style.left = newFixedLeft - 15 + 'px';
|
|
1964
|
+
// Es boton de filtro
|
|
1965
|
+
if (this.isFilterSelect) {
|
|
1966
|
+
setTimeout(() => {
|
|
1967
|
+
// Calculate absolute position based on the current fixed position
|
|
1968
|
+
let newAbsoluteTop;
|
|
1969
|
+
let newAbsoluteLeft;
|
|
1970
|
+
if (newFixedTop === buttonRect.bottom) {
|
|
1971
|
+
// Dropdown is below the button
|
|
1972
|
+
newAbsoluteTop = this.dropdownTop;
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
// Dropdown is above the button (flipped)
|
|
1976
|
+
newAbsoluteTop = buttonRect.top - dropdownRect.height - offsetTop;
|
|
1977
|
+
}
|
|
1978
|
+
if (newFixedLeft === buttonRect.left) {
|
|
1979
|
+
// Dropdown is aligned with left edge of button
|
|
1980
|
+
newAbsoluteLeft = this.dropdownLeft;
|
|
1981
|
+
}
|
|
1982
|
+
else if (newFixedLeft === buttonRect.left - dropdownRect.width + buttonRect.width) {
|
|
1983
|
+
// Dropdown is aligned with right edge of button
|
|
1984
|
+
newAbsoluteLeft = buttonRect.left - dropdownRect.width + buttonRect.width - offsetLeft;
|
|
1985
|
+
}
|
|
1986
|
+
else {
|
|
1987
|
+
// Dropdown is at a fixed position from left edge
|
|
1988
|
+
newAbsoluteLeft = newFixedLeft;
|
|
1989
|
+
}
|
|
1990
|
+
// Switch to absolute positioning
|
|
1991
|
+
dropdown.style.position = 'absolute';
|
|
1992
|
+
dropdown.style.top = newAbsoluteTop + 'px';
|
|
1993
|
+
dropdown.style.left = newAbsoluteLeft - 2 + 'px';
|
|
1994
|
+
// Update internal state
|
|
1995
|
+
this.dropdownTop = newAbsoluteTop;
|
|
1996
|
+
this.dropdownLeft = newAbsoluteLeft;
|
|
1997
|
+
this.cdr.detectChanges();
|
|
1998
|
+
}, 0);
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
// After positioning is done, switch to absolute positioning
|
|
2002
|
+
setTimeout(() => {
|
|
2003
|
+
// Get the current visual position of the dropdown (relative to viewport)
|
|
2004
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
2005
|
+
// Find the dropdown's offset parent for absolute positioning
|
|
2006
|
+
const dropdownOffsetParent = this.findPositionedParent(dropdown) || document.body;
|
|
2007
|
+
const parentRect = dropdownOffsetParent.getBoundingClientRect();
|
|
2008
|
+
// Calculate absolute position that will maintain the same visual position
|
|
2009
|
+
// Absolute positioning is relative to the offset parent
|
|
2010
|
+
const absoluteTop = dropdownRect.top - parentRect.top + dropdownOffsetParent.scrollTop;
|
|
2011
|
+
const absoluteLeft = dropdownRect.left - parentRect.left + dropdownOffsetParent.scrollLeft;
|
|
2012
|
+
// Switch to absolute positioning with calculated coordinates
|
|
2013
|
+
dropdown.style.position = 'absolute';
|
|
2014
|
+
dropdown.style.top = absoluteTop + 'px';
|
|
2015
|
+
dropdown.style.left = absoluteLeft + 'px';
|
|
2016
|
+
// Update internal state
|
|
2017
|
+
this.dropdownTop = absoluteTop;
|
|
2018
|
+
this.dropdownLeft = absoluteLeft;
|
|
2019
|
+
this.cdr.detectChanges();
|
|
2020
|
+
}, 0);
|
|
2021
|
+
}
|
|
2022
|
+
}, 0);
|
|
2023
|
+
}, 0);
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
// Helper method to find the offset parent for absolute positioning
|
|
2027
|
+
findPositionedParent(element) {
|
|
2028
|
+
if (!element)
|
|
2029
|
+
return null;
|
|
2030
|
+
let parent = element.parentElement;
|
|
2031
|
+
while (parent) {
|
|
2032
|
+
const position = window.getComputedStyle(parent).position;
|
|
2033
|
+
if (position === 'relative' || position === 'absolute' || position === 'fixed') {
|
|
2034
|
+
return parent;
|
|
2035
|
+
}
|
|
2036
|
+
parent = parent.parentElement;
|
|
2037
|
+
}
|
|
2038
|
+
return document.body; // Default to body if no positioned parent found
|
|
2039
|
+
}
|
|
2040
|
+
// Leer el valor seleccionado
|
|
2041
|
+
writeValue(value) {
|
|
2042
|
+
this.selectedValue = value;
|
|
2043
|
+
// Si las opciones ya están cargadas, intenta mostrar el label ahora mismo
|
|
2044
|
+
if (this.internalOptions.length > 0) {
|
|
2045
|
+
this.updateSelectedLabel();
|
|
2046
|
+
}
|
|
2047
|
+
// Si no hay opciones cargadas aún, espera a que se carguen en loadData()
|
|
2048
|
+
this.cdr.markForCheck();
|
|
2049
|
+
}
|
|
2050
|
+
// ================================================
|
|
2051
|
+
// Registrar cambios
|
|
2052
|
+
// ================================================
|
|
2053
|
+
// Registrar el cambio del valor seleccionado
|
|
2054
|
+
registerOnChange(fn) {
|
|
2055
|
+
this.onChange = fn;
|
|
2056
|
+
}
|
|
2057
|
+
// Registrar el evento de tocar
|
|
2058
|
+
registerOnTouched(fn) {
|
|
2059
|
+
this.onTouched = fn;
|
|
2060
|
+
}
|
|
2061
|
+
// Cargar elementos del searhable cuando es necesario
|
|
2062
|
+
shouldTriggerLoad() {
|
|
2063
|
+
const isSearchActive = this.searchTerm && this.searchTerm.trim() !== '';
|
|
2064
|
+
const isInitialState = this.selectedValue === null;
|
|
2065
|
+
return isSearchActive || isInitialState;
|
|
2066
|
+
}
|
|
2067
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JSelectComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: JGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2068
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JSelectComponent, isStandalone: true, selector: "JSelect", inputs: { type: "type", btnIcon: "btnIcon", btnText: "btnText", title: "title", placeholder: "placeholder", showClear: "showClear", columns: "columns", options: "options", optionLabel: "optionLabel", optionValue: "optionValue", labelSeparator: "labelSeparator", isLoading: "isLoading", endpoint: "endpoint", loadOnInit: "loadOnInit", defaultFilters: "defaultFilters", searchFields: "searchFields", isSearch: "isSearch", isFilterSelect: "isFilterSelect", sort: "sort", disabled: "disabled" }, outputs: { updateVisibility: "updateVisibility", selectionChange: "selectionChange" }, providers: [
|
|
2069
|
+
{
|
|
2070
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2071
|
+
useExisting: JSelectComponent,
|
|
2072
|
+
multi: true,
|
|
2073
|
+
}
|
|
2074
|
+
], queries: [{ propertyName: "optionComponents", predicate: OptionComponent }], viewQueries: [{ propertyName: "selectButton", first: true, predicate: ["selectButton"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"relative w-full h-full\">\r\n <!-- Bot\u00F3n para multi-checkbox -->\r\n @if (type === 'multi-table') {\r\n <div #selectButton class=\"min-w-[40px]\">\r\n <JButton (clicked)=\"toggleColumnSelector()\" classes=\"secondary\" [icon]=\"btnIcon\">{{btnText}}</JButton>\r\n </div>\r\n }\r\n\r\n <!-- Bot\u00F3n para dropdown -->\r\n @if (type === 'dropdown' || type === 'searchable') {\r\n <div #selectButton class=\"w-auto\">\r\n <button type=\"button\" [disabled]=\"disabled || isLoading\" (click)=\"toggleColumnSelector()\"\r\n class=\"flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none\" [ngClass]=\"{\r\n 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading\r\n }\"\r\n >\r\n <span class=\"truncate text-black dark:text-white\"\r\n [ngClass]=\"{'opacity-50' : selectedValue === null}\">{{selectedLabel}}</span>\r\n <div class=\"flex items-center\">\r\n @if (showClear && selectedValue !== null) {\r\n <button type=\"button\" (click)=\"clearSelection($event)\"\r\n class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"14\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n @if (!isLoading) {\r\n <lucide-icon [name]=\"icons['chevronDown']\" size=\"16\" class=\"transition duration-300 ease-in-out text-gray-400\" [ngClass]=\"{'rotate-180': isColumnSelectorOpen}\"></lucide-icon>\r\n } @else {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"16\" class=\"text-gray-400 animate-spin\"></lucide-icon>\r\n }\r\n </div>\r\n </button>\r\n </div>\r\n }\r\n</div>\r\n\r\n<!-- Dropdown positioned outside the flow -->\r\n@if (isColumnSelectorOpen) {\r\n<div @modalTransition\r\n class=\"absolute z-[100] min-w-[250px] mt-1 bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border\"\r\n [ngClass]=\"{'max-w-[250px]' :type === 'multi-table' }\" \r\n [style.width.px]=\"dropdownWidth\"\r\n [style.top.px]=\"dropdownTop\" \r\n [style.left.px]=\"dropdownLeft\"\r\n >\r\n <div class=\"pt-1 pl-3 pr-3 pb-3\">\r\n <div class=\"text-[10px] font-medium text-gray-500 dark:text-gray-500 mb-1\">{{title}}</div>\r\n\r\n <!-- Campo de b\u00FAsqueda para tipo searchable -->\r\n @if (type === 'searchable' && isSearch) {\r\n <div class=\"mb-2 relative\">\r\n <input \r\n type=\"text\" \r\n [(ngModel)]=\"searchTerm\" \r\n (input)=\"onSearchInput()\"\r\n placeholder=\"Buscar...\" \r\n class=\"text-black dark:text-white w-full px-3 py-2 text-sm border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary\"\r\n />\r\n\r\n <div class=\"absolute flex right-3 top-1/2 transform -translate-y-1/2 \">\r\n @if (searchTerm) {\r\n <button type=\"button\" (click)=\"clearSearchTerm()\" class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n <lucide-icon \r\n [name]=\"icons['search']\" \r\n size=\"16\" \r\n class=\"text-gray-400\">\r\n </lucide-icon>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n <!-- Multi-checkbox para columnas -->\r\n <div class=\"max-h-60 overflow-auto flex flex-col gap-1 scroll-element\">\r\n @if (type === 'multi-table') {\r\n @for (column of columns; track column.key) {\r\n <div onKeyDown\r\n class=\"flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent dark:hover:bg-dark-accent/50\"\r\n [ngClass]=\"{'bg-accent dark:bg-dark-accent/50' : column.visible}\"\r\n (click)=\"column.visible = !column.visible; updateColumnVisibility()\">\r\n\r\n <!-- Checkbox oculto -->\r\n <input type=\"checkbox\" [id]=\"'col-' + column.key\" [(ngModel)]=\"column.visible\" class=\"hidden\">\r\n\r\n <!-- \u00CDcono Check Visible Solo al Seleccionar -->\r\n <lucide-icon [name]=\"icons['check']\" size=\"15\" class=\"transition text-gray-400\"\r\n [ngClass]=\"{'opacity-100 text-white': column.visible, 'opacity-0': !column.visible}\">\r\n </lucide-icon>\r\n\r\n <!-- Etiqueta con Efectos de Hover e Iluminaci\u00F3n -->\r\n <label [for]=\"'col-' + column.key\" class=\"text-black dark:text-white text-sm font-medium w-full\">\r\n {{ column.label }}\r\n </label>\r\n </div>\r\n }\r\n @if (columns.length === 0) {\r\n <div class=\"px-3 py-2 text-sm text-gray-500\">No hay opciones disponibles</div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dropdown con opciones -->\r\n @if (type === 'dropdown' || type === 'searchable') {\r\n <div class=\"max-h-40 overflow-x-hidden overflow-y-auto flex flex-col gap-1 scroll-element\">\r\n @if (isLoading) {\r\n <div class=\"flex gap-3 text-black/50 dark:text-white/50 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"20\" class=\"animate-spin\"></lucide-icon>\r\n Cargando...\r\n </div>\r\n } @else {\r\n @for (option of filteredOptions; track option.value) {\r\n <div onKeyDown (click)=\"selectOption(option)\"\r\n class=\"px-3 py-2 rounded text-sm cursor-pointer text-black! dark:text-white! hover:bg-accent hover:dark:bg-dark-accent/50\"\r\n [ngClass]=\"{'bg-accent dark:bg-dark-accent/50': selectedValue === option.value, 'text-black': selectedValue === option.value}\">\r\n <div class=\"flex items-center break-all whitespace-normal overflow-hidden\">\r\n {{option.text}}\r\n </div>\r\n </div>\r\n }\r\n @if (filteredOptions.length === 0) {\r\n <div class=\"px-3 py-2 text-sm text-gray-500\">No hay opciones disponibles</div>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n}", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }], animations: [
|
|
2075
|
+
trigger("modalTransition", [
|
|
2076
|
+
transition(":enter", [
|
|
2077
|
+
style({ transform: "translateX(1rem)", opacity: 0 }),
|
|
2078
|
+
animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
|
|
2079
|
+
]),
|
|
2080
|
+
transition(":leave", [
|
|
2081
|
+
animate("150ms ease-in", style({ transform: "translateX(1rem)", opacity: 0 })),
|
|
2082
|
+
]),
|
|
2083
|
+
]),
|
|
2084
|
+
] });
|
|
2085
|
+
}
|
|
2086
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JSelectComponent, decorators: [{
|
|
2087
|
+
type: Component,
|
|
2088
|
+
args: [{ selector: 'JSelect', standalone: true, imports: [LucideAngularModule, JButtonComponent, CommonModule, FormsModule, ReactiveFormsModule], animations: [
|
|
2089
|
+
trigger("modalTransition", [
|
|
2090
|
+
transition(":enter", [
|
|
2091
|
+
style({ transform: "translateX(1rem)", opacity: 0 }),
|
|
2092
|
+
animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
|
|
2093
|
+
]),
|
|
2094
|
+
transition(":leave", [
|
|
2095
|
+
animate("150ms ease-in", style({ transform: "translateX(1rem)", opacity: 0 })),
|
|
2096
|
+
]),
|
|
2097
|
+
]),
|
|
2098
|
+
], providers: [
|
|
2099
|
+
{
|
|
2100
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2101
|
+
useExisting: JSelectComponent,
|
|
2102
|
+
multi: true,
|
|
2103
|
+
}
|
|
2104
|
+
], template: "<div class=\"relative w-full h-full\">\r\n <!-- Bot\u00F3n para multi-checkbox -->\r\n @if (type === 'multi-table') {\r\n <div #selectButton class=\"min-w-[40px]\">\r\n <JButton (clicked)=\"toggleColumnSelector()\" classes=\"secondary\" [icon]=\"btnIcon\">{{btnText}}</JButton>\r\n </div>\r\n }\r\n\r\n <!-- Bot\u00F3n para dropdown -->\r\n @if (type === 'dropdown' || type === 'searchable') {\r\n <div #selectButton class=\"w-auto\">\r\n <button type=\"button\" [disabled]=\"disabled || isLoading\" (click)=\"toggleColumnSelector()\"\r\n class=\"flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none\" [ngClass]=\"{\r\n 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading\r\n }\"\r\n >\r\n <span class=\"truncate text-black dark:text-white\"\r\n [ngClass]=\"{'opacity-50' : selectedValue === null}\">{{selectedLabel}}</span>\r\n <div class=\"flex items-center\">\r\n @if (showClear && selectedValue !== null) {\r\n <button type=\"button\" (click)=\"clearSelection($event)\"\r\n class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"14\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n @if (!isLoading) {\r\n <lucide-icon [name]=\"icons['chevronDown']\" size=\"16\" class=\"transition duration-300 ease-in-out text-gray-400\" [ngClass]=\"{'rotate-180': isColumnSelectorOpen}\"></lucide-icon>\r\n } @else {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"16\" class=\"text-gray-400 animate-spin\"></lucide-icon>\r\n }\r\n </div>\r\n </button>\r\n </div>\r\n }\r\n</div>\r\n\r\n<!-- Dropdown positioned outside the flow -->\r\n@if (isColumnSelectorOpen) {\r\n<div @modalTransition\r\n class=\"absolute z-[100] min-w-[250px] mt-1 bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border\"\r\n [ngClass]=\"{'max-w-[250px]' :type === 'multi-table' }\" \r\n [style.width.px]=\"dropdownWidth\"\r\n [style.top.px]=\"dropdownTop\" \r\n [style.left.px]=\"dropdownLeft\"\r\n >\r\n <div class=\"pt-1 pl-3 pr-3 pb-3\">\r\n <div class=\"text-[10px] font-medium text-gray-500 dark:text-gray-500 mb-1\">{{title}}</div>\r\n\r\n <!-- Campo de b\u00FAsqueda para tipo searchable -->\r\n @if (type === 'searchable' && isSearch) {\r\n <div class=\"mb-2 relative\">\r\n <input \r\n type=\"text\" \r\n [(ngModel)]=\"searchTerm\" \r\n (input)=\"onSearchInput()\"\r\n placeholder=\"Buscar...\" \r\n class=\"text-black dark:text-white w-full px-3 py-2 text-sm border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary\"\r\n />\r\n\r\n <div class=\"absolute flex right-3 top-1/2 transform -translate-y-1/2 \">\r\n @if (searchTerm) {\r\n <button type=\"button\" (click)=\"clearSearchTerm()\" class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n <lucide-icon \r\n [name]=\"icons['search']\" \r\n size=\"16\" \r\n class=\"text-gray-400\">\r\n </lucide-icon>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n <!-- Multi-checkbox para columnas -->\r\n <div class=\"max-h-60 overflow-auto flex flex-col gap-1 scroll-element\">\r\n @if (type === 'multi-table') {\r\n @for (column of columns; track column.key) {\r\n <div onKeyDown\r\n class=\"flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent dark:hover:bg-dark-accent/50\"\r\n [ngClass]=\"{'bg-accent dark:bg-dark-accent/50' : column.visible}\"\r\n (click)=\"column.visible = !column.visible; updateColumnVisibility()\">\r\n\r\n <!-- Checkbox oculto -->\r\n <input type=\"checkbox\" [id]=\"'col-' + column.key\" [(ngModel)]=\"column.visible\" class=\"hidden\">\r\n\r\n <!-- \u00CDcono Check Visible Solo al Seleccionar -->\r\n <lucide-icon [name]=\"icons['check']\" size=\"15\" class=\"transition text-gray-400\"\r\n [ngClass]=\"{'opacity-100 text-white': column.visible, 'opacity-0': !column.visible}\">\r\n </lucide-icon>\r\n\r\n <!-- Etiqueta con Efectos de Hover e Iluminaci\u00F3n -->\r\n <label [for]=\"'col-' + column.key\" class=\"text-black dark:text-white text-sm font-medium w-full\">\r\n {{ column.label }}\r\n </label>\r\n </div>\r\n }\r\n @if (columns.length === 0) {\r\n <div class=\"px-3 py-2 text-sm text-gray-500\">No hay opciones disponibles</div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dropdown con opciones -->\r\n @if (type === 'dropdown' || type === 'searchable') {\r\n <div class=\"max-h-40 overflow-x-hidden overflow-y-auto flex flex-col gap-1 scroll-element\">\r\n @if (isLoading) {\r\n <div class=\"flex gap-3 text-black/50 dark:text-white/50 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"20\" class=\"animate-spin\"></lucide-icon>\r\n Cargando...\r\n </div>\r\n } @else {\r\n @for (option of filteredOptions; track option.value) {\r\n <div onKeyDown (click)=\"selectOption(option)\"\r\n class=\"px-3 py-2 rounded text-sm cursor-pointer text-black! dark:text-white! hover:bg-accent hover:dark:bg-dark-accent/50\"\r\n [ngClass]=\"{'bg-accent dark:bg-dark-accent/50': selectedValue === option.value, 'text-black': selectedValue === option.value}\">\r\n <div class=\"flex items-center break-all whitespace-normal overflow-hidden\">\r\n {{option.text}}\r\n </div>\r\n </div>\r\n }\r\n @if (filteredOptions.length === 0) {\r\n <div class=\"px-3 py-2 text-sm text-gray-500\">No hay opciones disponibles</div>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n}" }]
|
|
2105
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: JGenericService }], propDecorators: { type: [{
|
|
2106
|
+
type: Input
|
|
2107
|
+
}], btnIcon: [{
|
|
2108
|
+
type: Input
|
|
2109
|
+
}], btnText: [{
|
|
2110
|
+
type: Input
|
|
2111
|
+
}], title: [{
|
|
2112
|
+
type: Input
|
|
2113
|
+
}], placeholder: [{
|
|
2114
|
+
type: Input
|
|
2115
|
+
}], showClear: [{
|
|
2116
|
+
type: Input
|
|
2117
|
+
}], columns: [{
|
|
2118
|
+
type: Input
|
|
2119
|
+
}], options: [{
|
|
2120
|
+
type: Input
|
|
2121
|
+
}], optionLabel: [{
|
|
2122
|
+
type: Input
|
|
2123
|
+
}], optionValue: [{
|
|
2124
|
+
type: Input
|
|
2125
|
+
}], labelSeparator: [{
|
|
2126
|
+
type: Input
|
|
2127
|
+
}], isLoading: [{
|
|
2128
|
+
type: Input
|
|
2129
|
+
}], endpoint: [{
|
|
2130
|
+
type: Input
|
|
2131
|
+
}], loadOnInit: [{
|
|
2132
|
+
type: Input
|
|
2133
|
+
}], defaultFilters: [{
|
|
2134
|
+
type: Input
|
|
2135
|
+
}], searchFields: [{
|
|
2136
|
+
type: Input
|
|
2137
|
+
}], isSearch: [{
|
|
2138
|
+
type: Input
|
|
2139
|
+
}], isFilterSelect: [{
|
|
2140
|
+
type: Input
|
|
2141
|
+
}], sort: [{
|
|
2142
|
+
type: Input
|
|
2143
|
+
}], updateVisibility: [{
|
|
2144
|
+
type: Output
|
|
2145
|
+
}], selectionChange: [{
|
|
2146
|
+
type: Output
|
|
2147
|
+
}], optionComponents: [{
|
|
2148
|
+
type: ContentChildren,
|
|
2149
|
+
args: [OptionComponent]
|
|
2150
|
+
}], selectButton: [{
|
|
2151
|
+
type: ViewChild,
|
|
2152
|
+
args: ['selectButton']
|
|
2153
|
+
}], disabled: [{
|
|
2154
|
+
type: Input
|
|
2155
|
+
}] } });
|
|
2156
|
+
|
|
2157
|
+
class JToggleRadioComponent {
|
|
2158
|
+
genericService;
|
|
2159
|
+
icons = {
|
|
2160
|
+
loading: Loader2,
|
|
2161
|
+
};
|
|
2162
|
+
options = [];
|
|
2163
|
+
optionLabel = 'label'; // Propiedad anidada para label
|
|
2164
|
+
optionValue = 'value'; // Propiedad para value
|
|
2165
|
+
endpoint = '';
|
|
2166
|
+
loadOnInit = false;
|
|
2167
|
+
defaultFilters = {};
|
|
2168
|
+
showClear = false;
|
|
2169
|
+
classes = '';
|
|
2170
|
+
classesElement = '';
|
|
2171
|
+
disabled = false;
|
|
2172
|
+
sort = 'ASC';
|
|
2173
|
+
selectFirstOnLoad = false;
|
|
2174
|
+
selectionChange = new EventEmitter();
|
|
2175
|
+
internalOptions = [];
|
|
2176
|
+
selectedValue = null;
|
|
2177
|
+
isLoading = false;
|
|
2178
|
+
constructor(genericService) {
|
|
2179
|
+
this.genericService = genericService;
|
|
2180
|
+
}
|
|
2181
|
+
ngOnInit() {
|
|
2182
|
+
if (this.endpoint && this.loadOnInit) {
|
|
2183
|
+
this.loadOptionsFromApi();
|
|
2184
|
+
}
|
|
2185
|
+
else {
|
|
2186
|
+
this.processOptions();
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
isComponentDisabled = false;
|
|
2190
|
+
setDisabledState(isDisabled) {
|
|
2191
|
+
this.isComponentDisabled = isDisabled;
|
|
2192
|
+
}
|
|
2193
|
+
// Cargar opciones desde el API
|
|
2194
|
+
loadOptionsFromApi() {
|
|
2195
|
+
this.isLoading = true;
|
|
2196
|
+
const params = {};
|
|
2197
|
+
params['sortOrder'] = this.sort;
|
|
2198
|
+
Object.keys(this.defaultFilters).forEach((key) => {
|
|
2199
|
+
params[`filter[${key}]`] = this.defaultFilters[key];
|
|
2200
|
+
});
|
|
2201
|
+
this.genericService.getAll(this.endpoint, params).subscribe({
|
|
2202
|
+
next: (res) => {
|
|
2203
|
+
const data = res.data[this.endpoint] || [];
|
|
2204
|
+
this.options = data;
|
|
2205
|
+
this.processOptions();
|
|
2206
|
+
this.isLoading = false;
|
|
2207
|
+
// Si el parámetro selectFirstOnLoad es true, seleccionar el primer elemento
|
|
2208
|
+
if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
|
|
2209
|
+
this.selectedValue = this.internalOptions[0].value;
|
|
2210
|
+
this.onChange(this.selectedValue);
|
|
2211
|
+
this.selectionChange.emit(this.selectedValue);
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
error: () => (this.isLoading = false),
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
// Función para obtener valores anidados de un objeto
|
|
2218
|
+
getNestedValue(obj, path) {
|
|
2219
|
+
return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? '';
|
|
2220
|
+
}
|
|
2221
|
+
// Procesar opciones para tener el valor y el label
|
|
2222
|
+
processOptions() {
|
|
2223
|
+
if (this.options.length > 0 && typeof this.options[0] === 'object') {
|
|
2224
|
+
this.internalOptions = this.options.map((opt) => ({
|
|
2225
|
+
value: opt[this.optionValue],
|
|
2226
|
+
label: this.getNestedValue(opt, this.optionLabel), // Usar la función para obtener propiedades anidadas
|
|
2227
|
+
}));
|
|
2228
|
+
}
|
|
2229
|
+
else {
|
|
2230
|
+
this.internalOptions = this.options.map((opt) => ({
|
|
2231
|
+
value: opt,
|
|
2232
|
+
label: opt.toString(),
|
|
2233
|
+
}));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
select(value) {
|
|
2237
|
+
if (this.disabled || this.isComponentDisabled)
|
|
2238
|
+
return;
|
|
2239
|
+
this.selectedValue = value;
|
|
2240
|
+
this.onChange(this.selectedValue);
|
|
2241
|
+
this.selectionChange.emit(this.selectedValue);
|
|
2242
|
+
}
|
|
2243
|
+
clear() {
|
|
2244
|
+
this.selectedValue = null;
|
|
2245
|
+
this.onChange(null);
|
|
2246
|
+
this.selectionChange.emit(null);
|
|
2247
|
+
}
|
|
2248
|
+
// ControlValueAccessor
|
|
2249
|
+
onChange = () => { };
|
|
2250
|
+
onTouched = () => { };
|
|
2251
|
+
writeValue(value) {
|
|
2252
|
+
this.selectedValue = value;
|
|
2253
|
+
}
|
|
2254
|
+
registerOnChange(fn) {
|
|
2255
|
+
this.onChange = fn;
|
|
2256
|
+
}
|
|
2257
|
+
registerOnTouched(fn) {
|
|
2258
|
+
this.onTouched = fn;
|
|
2259
|
+
}
|
|
2260
|
+
// Método para recargar las opciones cuando cambia el filtro
|
|
2261
|
+
reloadOptions() {
|
|
2262
|
+
this.loadOptionsFromApi(); // Recargar las opciones con los nuevos filtros
|
|
2263
|
+
}
|
|
2264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JToggleRadioComponent, deps: [{ token: JGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JToggleRadioComponent, isStandalone: true, selector: "JToggleRadio", inputs: { options: "options", optionLabel: "optionLabel", optionValue: "optionValue", endpoint: "endpoint", loadOnInit: "loadOnInit", defaultFilters: "defaultFilters", showClear: "showClear", classes: "classes", classesElement: "classesElement", disabled: "disabled", sort: "sort", selectFirstOnLoad: "selectFirstOnLoad" }, outputs: { selectionChange: "selectionChange" }, providers: [
|
|
2266
|
+
{
|
|
2267
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2268
|
+
useExisting: forwardRef(() => JToggleRadioComponent),
|
|
2269
|
+
multi: true,
|
|
2270
|
+
},
|
|
2271
|
+
], ngImport: i0, template: "@if (internalOptions.length > 0) {\r\n<div class=\"flex rounded-md overflow-hidden border border-border dark:border-dark-border text-sm min-h-[40px] select-none\" [ngClass]=\"{ 'opacity-50': disabled || isComponentDisabled }\" [class]=\"classes\">\r\n <ng-container>\r\n @for (opt of internalOptions; track $index) {\r\n <button\r\n type=\"button\"\r\n [disabled]=\"disabled || isComponentDisabled\"\r\n (click)=\"select(opt.value)\"\r\n [ngClass]=\"{\r\n 'bg-primary dark:bg-dark-primary text-white': selectedValue === opt.value,\r\n 'bg-background dark:bg-dark-background hover:bg-muted/80 hover:dark:bg-dark-muted/30 text-gray-700 dark:text-white': selectedValue !== opt.value,\r\n 'cursor-not-allowed pointer-events-none': disabled || isComponentDisabled\r\n }\"\r\n class=\"w-full py-2 px-4 font-medium border-r border-border last:border-none focus:outline-none transition-colors duration-200 ease-in-out cursor-pointer\"\r\n [class]=\"classesElement\"\r\n >\r\n {{ opt.label }}\r\n </button>\r\n }\r\n \r\n </ng-container>\r\n</div>\r\n} @else if (isLoading) {\r\n<div class=\"flex gap-2 items-center justify-center rounded-md overflow-hidden border border-border dark:border-dark-border h-[40px]\">\r\n <lucide-icon [name]=\"icons['loading']\" [size]=\"15\" class=\"text-gray-400 animate-spin\" />\r\n <span>Cargando...</span>\r\n</div> \r\n}@else {\r\n<div class=\"flex items-center justify-center rounded-md overflow-hidden border border-border dark:border-dark-border h-[40px]\">\r\n No hay opciones\r\n</div> \r\n}\r\n \r\n<!-- Clear button -->\r\n@if (showClear && selectedValue !== null) {\r\n <div class=\"mt-2\">\r\n <button \r\n type=\"button\" \r\n (click)=\"clear()\" \r\n [disabled]=\"disabled || isComponentDisabled\"\r\n class=\"text-xs text-red-500 underline disabled:opacity-50 disabled:cursor-not-allowed\">\r\n Limpiar selecci\u00F3n\r\n </button>\r\n </div>\r\n }\r\n ", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
2272
|
+
}
|
|
2273
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JToggleRadioComponent, decorators: [{
|
|
2274
|
+
type: Component,
|
|
2275
|
+
args: [{ selector: 'JToggleRadio', imports: [LucideAngularModule, CommonModule], providers: [
|
|
2276
|
+
{
|
|
2277
|
+
provide: NG_VALUE_ACCESSOR,
|
|
2278
|
+
useExisting: forwardRef(() => JToggleRadioComponent),
|
|
2279
|
+
multi: true,
|
|
2280
|
+
},
|
|
2281
|
+
], template: "@if (internalOptions.length > 0) {\r\n<div class=\"flex rounded-md overflow-hidden border border-border dark:border-dark-border text-sm min-h-[40px] select-none\" [ngClass]=\"{ 'opacity-50': disabled || isComponentDisabled }\" [class]=\"classes\">\r\n <ng-container>\r\n @for (opt of internalOptions; track $index) {\r\n <button\r\n type=\"button\"\r\n [disabled]=\"disabled || isComponentDisabled\"\r\n (click)=\"select(opt.value)\"\r\n [ngClass]=\"{\r\n 'bg-primary dark:bg-dark-primary text-white': selectedValue === opt.value,\r\n 'bg-background dark:bg-dark-background hover:bg-muted/80 hover:dark:bg-dark-muted/30 text-gray-700 dark:text-white': selectedValue !== opt.value,\r\n 'cursor-not-allowed pointer-events-none': disabled || isComponentDisabled\r\n }\"\r\n class=\"w-full py-2 px-4 font-medium border-r border-border last:border-none focus:outline-none transition-colors duration-200 ease-in-out cursor-pointer\"\r\n [class]=\"classesElement\"\r\n >\r\n {{ opt.label }}\r\n </button>\r\n }\r\n \r\n </ng-container>\r\n</div>\r\n} @else if (isLoading) {\r\n<div class=\"flex gap-2 items-center justify-center rounded-md overflow-hidden border border-border dark:border-dark-border h-[40px]\">\r\n <lucide-icon [name]=\"icons['loading']\" [size]=\"15\" class=\"text-gray-400 animate-spin\" />\r\n <span>Cargando...</span>\r\n</div> \r\n}@else {\r\n<div class=\"flex items-center justify-center rounded-md overflow-hidden border border-border dark:border-dark-border h-[40px]\">\r\n No hay opciones\r\n</div> \r\n}\r\n \r\n<!-- Clear button -->\r\n@if (showClear && selectedValue !== null) {\r\n <div class=\"mt-2\">\r\n <button \r\n type=\"button\" \r\n (click)=\"clear()\" \r\n [disabled]=\"disabled || isComponentDisabled\"\r\n class=\"text-xs text-red-500 underline disabled:opacity-50 disabled:cursor-not-allowed\">\r\n Limpiar selecci\u00F3n\r\n </button>\r\n </div>\r\n }\r\n " }]
|
|
2282
|
+
}], ctorParameters: () => [{ type: JGenericService }], propDecorators: { options: [{
|
|
2283
|
+
type: Input
|
|
2284
|
+
}], optionLabel: [{
|
|
2285
|
+
type: Input
|
|
2286
|
+
}], optionValue: [{
|
|
2287
|
+
type: Input
|
|
2288
|
+
}], endpoint: [{
|
|
2289
|
+
type: Input
|
|
2290
|
+
}], loadOnInit: [{
|
|
2291
|
+
type: Input
|
|
2292
|
+
}], defaultFilters: [{
|
|
2293
|
+
type: Input
|
|
2294
|
+
}], showClear: [{
|
|
2295
|
+
type: Input
|
|
2296
|
+
}], classes: [{
|
|
2297
|
+
type: Input
|
|
2298
|
+
}], classesElement: [{
|
|
2299
|
+
type: Input
|
|
2300
|
+
}], disabled: [{
|
|
2301
|
+
type: Input
|
|
2302
|
+
}], sort: [{
|
|
2303
|
+
type: Input
|
|
2304
|
+
}], selectFirstOnLoad: [{
|
|
2305
|
+
type: Input
|
|
2306
|
+
}], selectionChange: [{
|
|
2307
|
+
type: Output
|
|
2308
|
+
}] } });
|
|
2309
|
+
|
|
2310
|
+
class ThemeService {
|
|
2311
|
+
_theme = signal('light');
|
|
2312
|
+
themeSignal = this._theme.asReadonly();
|
|
2313
|
+
constructor() {
|
|
2314
|
+
this.initializeTheme();
|
|
2315
|
+
this.listenToExternalChanges(); // 👈 importante
|
|
2316
|
+
}
|
|
2317
|
+
initializeTheme() {
|
|
2318
|
+
const savedTheme = localStorage.getItem('theme');
|
|
2319
|
+
if (savedTheme) {
|
|
2320
|
+
this._theme.set(savedTheme);
|
|
2321
|
+
}
|
|
2322
|
+
else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
2323
|
+
this._theme.set('dark');
|
|
2324
|
+
}
|
|
2325
|
+
this.applyTheme();
|
|
2326
|
+
}
|
|
2327
|
+
listenToExternalChanges() {
|
|
2328
|
+
window.addEventListener('storage', (event) => {
|
|
2329
|
+
if (event.key === 'theme' && event.newValue) {
|
|
2330
|
+
const newTheme = event.newValue;
|
|
2331
|
+
this._theme.set(newTheme);
|
|
2332
|
+
this.applyTheme();
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
applyTheme() {
|
|
2337
|
+
const current = this._theme();
|
|
2338
|
+
document.documentElement.classList.toggle('dark', current === 'dark');
|
|
2339
|
+
}
|
|
2340
|
+
toggleTheme() {
|
|
2341
|
+
const newTheme = this._theme() === 'light' ? 'dark' : 'light';
|
|
2342
|
+
localStorage.setItem('theme', newTheme);
|
|
2343
|
+
this._theme.set(newTheme);
|
|
2344
|
+
this.applyTheme();
|
|
2345
|
+
}
|
|
2346
|
+
setTheme(theme) {
|
|
2347
|
+
localStorage.setItem('theme', theme);
|
|
2348
|
+
this._theme.set(theme);
|
|
2349
|
+
this.applyTheme();
|
|
2350
|
+
}
|
|
2351
|
+
getThemeMode() {
|
|
2352
|
+
return this._theme();
|
|
2353
|
+
}
|
|
2354
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2355
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
2356
|
+
}
|
|
2357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, decorators: [{
|
|
2358
|
+
type: Injectable,
|
|
2359
|
+
args: [{ providedIn: 'root' }]
|
|
2360
|
+
}], ctorParameters: () => [] });
|
|
2361
|
+
|
|
2362
|
+
class JContentFormComponent {
|
|
2363
|
+
columns = 1;
|
|
2364
|
+
rows = false;
|
|
2365
|
+
getClasses() {
|
|
2366
|
+
if (this.rows)
|
|
2367
|
+
return 'flex flex-row gap-3 items-center';
|
|
2368
|
+
const base = 'grid gap-2';
|
|
2369
|
+
const columnClassMap = {
|
|
2370
|
+
1: 'flex flex-col gap-1',
|
|
2371
|
+
2: 'grid-cols-1 sm:grid-cols-2',
|
|
2372
|
+
3: 'grid-cols-1 sm:grid-cols-3',
|
|
2373
|
+
4: 'grid-cols-1 sm:grid-cols-4',
|
|
2374
|
+
5: 'grid-cols-1 sm:grid-cols-5',
|
|
2375
|
+
6: 'grid-cols-1 sm:grid-cols-6',
|
|
2376
|
+
};
|
|
2377
|
+
return `${base} ${columnClassMap[this.columns] || columnClassMap[1]}`;
|
|
2378
|
+
}
|
|
2379
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JContentFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2380
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.7", type: JContentFormComponent, isStandalone: true, selector: "JContentForm", inputs: { columns: "columns", rows: "rows" }, ngImport: i0, template: "<div [ngClass]=\"getClasses()\">\r\n <ng-content></ng-content>\r\n</div>", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
2381
|
+
}
|
|
2382
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JContentFormComponent, decorators: [{
|
|
2383
|
+
type: Component,
|
|
2384
|
+
args: [{ selector: 'JContentForm', imports: [NgClass], template: "<div [ngClass]=\"getClasses()\">\r\n <ng-content></ng-content>\r\n</div>" }]
|
|
2385
|
+
}], propDecorators: { columns: [{
|
|
2386
|
+
type: Input
|
|
2387
|
+
}], rows: [{
|
|
2388
|
+
type: Input
|
|
2389
|
+
}] } });
|
|
2390
|
+
|
|
2391
|
+
class JThemeComponent {
|
|
2392
|
+
themeService = inject(ThemeService);
|
|
2393
|
+
icons = {
|
|
2394
|
+
copy: Copy,
|
|
2395
|
+
check: Check,
|
|
2396
|
+
sun: Sun,
|
|
2397
|
+
moon: Moon,
|
|
2398
|
+
};
|
|
2399
|
+
// Color picker state
|
|
2400
|
+
baseColor = '#415884';
|
|
2401
|
+
saturation = 34.01;
|
|
2402
|
+
lightness = 38.63;
|
|
2403
|
+
huePosition = 219;
|
|
2404
|
+
colorPickerPosition = { x: 50, y: 50 };
|
|
2405
|
+
// Sync preview with signal
|
|
2406
|
+
previewMode = localStorage.getItem('theme') || 'light';
|
|
2407
|
+
lastTheme = this.previewMode;
|
|
2408
|
+
isPickingColor = false;
|
|
2409
|
+
copied = false;
|
|
2410
|
+
themeCode = '';
|
|
2411
|
+
generatedColors;
|
|
2412
|
+
ngOnInit() {
|
|
2413
|
+
this.syncFromHSL();
|
|
2414
|
+
// Sincronizacion del tema con el localStorage
|
|
2415
|
+
setInterval(() => {
|
|
2416
|
+
const current = localStorage.getItem('theme');
|
|
2417
|
+
if (current && current !== this.lastTheme) {
|
|
2418
|
+
this.previewMode = current;
|
|
2419
|
+
this.lastTheme = current;
|
|
2420
|
+
this.generateTheme();
|
|
2421
|
+
}
|
|
2422
|
+
}, 300); // verifica cada 300ms
|
|
2423
|
+
}
|
|
2424
|
+
// Color picker gradient
|
|
2425
|
+
get hueGradient() {
|
|
2426
|
+
return `linear-gradient(to right,
|
|
2427
|
+
hsl(0, 100%, 50%),
|
|
2428
|
+
hsl(60, 100%, 50%),
|
|
2429
|
+
hsl(120, 100%, 50%),
|
|
2430
|
+
hsl(180, 100%, 50%),
|
|
2431
|
+
hsl(240, 100%, 50%),
|
|
2432
|
+
hsl(300, 100%, 50%),
|
|
2433
|
+
hsl(360, 100%, 50%))`;
|
|
2434
|
+
}
|
|
2435
|
+
getCurrentTheme() {
|
|
2436
|
+
return this.themeService.getThemeMode();
|
|
2437
|
+
}
|
|
2438
|
+
toggleTheme() {
|
|
2439
|
+
this.themeService.toggleTheme();
|
|
2440
|
+
}
|
|
2441
|
+
setPreviewMode(mode) {
|
|
2442
|
+
this.previewMode = mode;
|
|
2443
|
+
}
|
|
2444
|
+
startColorPicking(event) {
|
|
2445
|
+
this.isPickingColor = true;
|
|
2446
|
+
this.updateColorFromPosition(event);
|
|
2447
|
+
}
|
|
2448
|
+
updateColorPicking(event) {
|
|
2449
|
+
if (this.isPickingColor) {
|
|
2450
|
+
this.updateColorFromPosition(event);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
stopColorPicking() {
|
|
2454
|
+
this.isPickingColor = false;
|
|
2455
|
+
}
|
|
2456
|
+
updateColorFromPosition(event) {
|
|
2457
|
+
const target = event.target;
|
|
2458
|
+
const rect = target.getBoundingClientRect();
|
|
2459
|
+
const x = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
|
|
2460
|
+
const y = Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height));
|
|
2461
|
+
this.colorPickerPosition = { x: x * 100, y: y * 100 };
|
|
2462
|
+
this.saturation = x * 100;
|
|
2463
|
+
this.lightness = (1 - y) * 100;
|
|
2464
|
+
this.syncFromHSL();
|
|
2465
|
+
}
|
|
2466
|
+
updateHueFromInput(event) {
|
|
2467
|
+
const input = event.target;
|
|
2468
|
+
this.huePosition = parseInt(input.value);
|
|
2469
|
+
this.syncFromHSL();
|
|
2470
|
+
}
|
|
2471
|
+
updateFromHexInput() {
|
|
2472
|
+
if (!this.baseColor.startsWith('#'))
|
|
2473
|
+
this.baseColor = '#' + this.baseColor;
|
|
2474
|
+
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
2475
|
+
if (!hexRegex.test(this.baseColor))
|
|
2476
|
+
return;
|
|
2477
|
+
const { h, s, l } = this.hexToHSL(this.baseColor);
|
|
2478
|
+
this.huePosition = h;
|
|
2479
|
+
this.saturation = s;
|
|
2480
|
+
this.lightness = l;
|
|
2481
|
+
this.colorPickerPosition = { x: s, y: 100 - l };
|
|
2482
|
+
this.syncFromHSL();
|
|
2483
|
+
}
|
|
2484
|
+
syncFromHSL() {
|
|
2485
|
+
this.baseColor = this.hslToHex(this.huePosition, this.saturation, this.lightness);
|
|
2486
|
+
this.generateTheme();
|
|
2487
|
+
}
|
|
2488
|
+
hexToHSL(hex) {
|
|
2489
|
+
hex = hex.replace(/^#/, '');
|
|
2490
|
+
if (hex.length === 3)
|
|
2491
|
+
hex = hex.split('').map(c => c + c).join('');
|
|
2492
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
2493
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
2494
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
2495
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
2496
|
+
let h = 0, s = 0, l = (max + min) / 2;
|
|
2497
|
+
if (max !== min) {
|
|
2498
|
+
const d = max - min;
|
|
2499
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2500
|
+
switch (max) {
|
|
2501
|
+
case r:
|
|
2502
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
2503
|
+
break;
|
|
2504
|
+
case g:
|
|
2505
|
+
h = (b - r) / d + 2;
|
|
2506
|
+
break;
|
|
2507
|
+
case b:
|
|
2508
|
+
h = (r - g) / d + 4;
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
h *= 60;
|
|
2512
|
+
}
|
|
2513
|
+
return { h, s: s * 100, l: l * 100 };
|
|
2514
|
+
}
|
|
2515
|
+
hslToHex(h, s, l) {
|
|
2516
|
+
h %= 360;
|
|
2517
|
+
s /= 100;
|
|
2518
|
+
l /= 100;
|
|
2519
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
2520
|
+
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
|
|
2521
|
+
const m = l - c / 2;
|
|
2522
|
+
let r = 0, g = 0, b = 0;
|
|
2523
|
+
if (h < 60)
|
|
2524
|
+
[r, g, b] = [c, x, 0];
|
|
2525
|
+
else if (h < 120)
|
|
2526
|
+
[r, g, b] = [x, c, 0];
|
|
2527
|
+
else if (h < 180)
|
|
2528
|
+
[r, g, b] = [0, c, x];
|
|
2529
|
+
else if (h < 240)
|
|
2530
|
+
[r, g, b] = [0, x, c];
|
|
2531
|
+
else if (h < 300)
|
|
2532
|
+
[r, g, b] = [x, 0, c];
|
|
2533
|
+
else
|
|
2534
|
+
[r, g, b] = [c, 0, x];
|
|
2535
|
+
r = Math.round((r + m) * 255);
|
|
2536
|
+
g = Math.round((g + m) * 255);
|
|
2537
|
+
b = Math.round((b + m) * 255);
|
|
2538
|
+
return `#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
|
|
2539
|
+
}
|
|
2540
|
+
generateTheme() {
|
|
2541
|
+
this.baseColor = this.hslToHex(this.huePosition, this.saturation * 0.9, this.lightness);
|
|
2542
|
+
const vars = this.generatedColors = {
|
|
2543
|
+
background: this.hslToHex(this.huePosition, this.saturation, 95),
|
|
2544
|
+
foreground: this.hslToHex(210, 6, 10),
|
|
2545
|
+
card: this.hslToHex(this.huePosition, this.saturation * 0.3, 90),
|
|
2546
|
+
cardForeground: this.hslToHex(this.huePosition, this.saturation * 0.8, 15),
|
|
2547
|
+
popover: this.hslToHex(this.huePosition, this.saturation * 0.2, 95),
|
|
2548
|
+
popoverForeground: this.hslToHex(this.huePosition, this.saturation, 7),
|
|
2549
|
+
primary: this.baseColor,
|
|
2550
|
+
primaryForeground: '#FFFFFF',
|
|
2551
|
+
secondary: this.hslToHex(this.huePosition, this.saturation * 0.4, 75),
|
|
2552
|
+
secondaryForeground: '#000000',
|
|
2553
|
+
muted: this.hslToHex(this.huePosition, this.saturation * 0.1, 85),
|
|
2554
|
+
mutedForeground: this.hslToHex(this.huePosition, this.saturation * 0.5, 40),
|
|
2555
|
+
accent: this.hslToHex(this.huePosition, this.saturation * 0.1, 82),
|
|
2556
|
+
accentForeground: this.hslToHex(this.huePosition, this.saturation * 0.8, 15),
|
|
2557
|
+
destructive: '#BF3F3F',
|
|
2558
|
+
destructiveForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 95),
|
|
2559
|
+
border: this.hslToHex(this.huePosition, this.saturation * 0.5, 60),
|
|
2560
|
+
input: this.hslToHex(this.huePosition, this.saturation * 0.5, 60),
|
|
2561
|
+
ring: this.baseColor,
|
|
2562
|
+
darkBackground: this.mixHsl(this.huePosition, this.saturation * 0.7, 8, // base
|
|
2563
|
+
210, 6, 10, // toque azulado oscuro
|
|
2564
|
+
0.6 // intensidad del mix (15%)
|
|
2565
|
+
),
|
|
2566
|
+
darkForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
|
|
2567
|
+
darkCard: this.hslToHex(this.huePosition, this.saturation * 0.8, 8),
|
|
2568
|
+
darkCardForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
|
|
2569
|
+
darkPopover: this.hslToHex(this.huePosition, this.saturation * 0.8, 3),
|
|
2570
|
+
darkPopoverForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
|
|
2571
|
+
darkPrimary: this.hslToHex(this.huePosition, this.saturation * 0.7, 28),
|
|
2572
|
+
darkPrimaryForeground: '#FFFFFF',
|
|
2573
|
+
darkSecondary: this.hslToHex(this.huePosition, this.saturation * 0.6, 20),
|
|
2574
|
+
darkSecondaryForeground: '#FFFFFF',
|
|
2575
|
+
darkMuted: this.hslToHex(this.huePosition, this.saturation * 0.3, 25),
|
|
2576
|
+
darkMutedForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 60),
|
|
2577
|
+
darkAccent: this.hslToHex(this.huePosition, this.saturation * 0.3, 25),
|
|
2578
|
+
darkAccentForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
|
|
2579
|
+
darkDestructive: '#BF3F3F',
|
|
2580
|
+
darkDestructiveForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
|
|
2581
|
+
darkBorder: this.hslToHex(this.huePosition, this.saturation * 0.5, 45),
|
|
2582
|
+
darkInput: this.hslToHex(this.huePosition, this.saturation * 0.5, 45),
|
|
2583
|
+
darkRing: this.hslToHex(this.huePosition, this.saturation * 0.7, 28),
|
|
2584
|
+
};
|
|
2585
|
+
const toCssVar = (key, value) => ` --color-${key.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${value};`;
|
|
2586
|
+
const keys = Object.keys(vars);
|
|
2587
|
+
const lines = [];
|
|
2588
|
+
let darkStarted = false;
|
|
2589
|
+
for (const key of keys) {
|
|
2590
|
+
if (!darkStarted && key.startsWith('dark')) {
|
|
2591
|
+
lines.push(''); // Insertamos línea vacía justo antes del primer dark
|
|
2592
|
+
darkStarted = true;
|
|
2593
|
+
}
|
|
2594
|
+
lines.push(toCssVar(key, vars[key]));
|
|
2595
|
+
}
|
|
2596
|
+
lines.push(` --color-radius: 0.5rem;`);
|
|
2597
|
+
lines.push(` --color-dark-radius: 0.5rem;`);
|
|
2598
|
+
this.themeCode = `@theme {\n${lines.join('\n')}\n}`;
|
|
2599
|
+
}
|
|
2600
|
+
copyThemeToClipboard() {
|
|
2601
|
+
navigator.clipboard.writeText(this.themeCode);
|
|
2602
|
+
this.copied = true;
|
|
2603
|
+
setTimeout(() => (this.copied = false), 2000);
|
|
2604
|
+
}
|
|
2605
|
+
mixHsl(h1, s1, l1, h2, s2, l2, ratio) {
|
|
2606
|
+
const h = h1 * (1 - ratio) + h2 * ratio;
|
|
2607
|
+
const s = s1 * (1 - ratio) + s2 * ratio;
|
|
2608
|
+
const l = l1 * (1 - ratio) + l2 * ratio;
|
|
2609
|
+
return this.hslToHex(h, s, l);
|
|
2610
|
+
}
|
|
2611
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JThemeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2612
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.7", type: JThemeComponent, isStandalone: true, selector: "JTheme", ngImport: i0, template: "<!-- Main Content -->\r\n<main class=\"container mx-auto\">\r\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-8\">\r\n <!-- Controls Panel -->\r\n <div class=\"space-y-8 select-none\">\r\n <div>\r\n <h2 class=\"text-sm font-bold mb-1 opacity-60\">Selector de color</h2>\r\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <!-- Color Picker -->\r\n <div\r\n class=\"relative h-64 w-full overflow-hidden rounded-lg border border-border dark:border-dark-border\">\r\n <div class=\"absolute inset-0 bg-gradient-to-br from-white to-transparent\" style=\"z-index: 1;\">\r\n </div>\r\n <div class=\"absolute inset-0 bg-gradient-to-b from-transparent to-black\" style=\"z-index: 2;\">\r\n </div>\r\n <div class=\"absolute inset-0\"\r\n [style.background]=\"'linear-gradient(to right, hsl(' + huePosition + ', 0%, 50%), hsl(' + huePosition + ', 100%, 50%))'\"\r\n style=\"z-index: 0;\"></div>\r\n <div class=\"absolute w-6 h-6 rounded-full border-2 border-white transform -translate-x-1/2 -translate-y-1/2 cursor-pointer\"\r\n [style.left.%]=\"colorPickerPosition.x\" [style.top.%]=\"colorPickerPosition.y\"\r\n style=\"z-index: 3;\"></div>\r\n <div class=\"absolute inset-0 cursor-pointer\" (mousedown)=\"startColorPicking($event)\"\r\n (mousemove)=\"updateColorPicking($event)\" (mouseup)=\"stopColorPicking()\"\r\n (mouseleave)=\"stopColorPicking()\" style=\"z-index: 4;\"></div>\r\n </div>\r\n\r\n <!-- Color Spectrum -->\r\n <div class=\"space-y-4\">\r\n <div class=\"relative\">\r\n\r\n <!-- Picker bar -->\r\n <div class=\"h-8 w-full rounded-lg overflow-hidden border border-border dark:border-dark-border relative\">\r\n <div class=\"absolute inset-0\" [style.background]=\"hueGradient\"></div>\r\n <input type=\"range\" min=\"0\" max=\"360\" [value]=\"huePosition\"\r\n (input)=\"updateHueFromInput($event)\"\r\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\" />\r\n </div>\r\n \r\n <!-- Bolita fuera del flujo para que sobresalga -->\r\n <div class=\"absolute top-1/2 transform -translate-y-1/2 -translate-x-1/2 w-9 h-9 rounded-full border-2 border-white z-10 pointer-events-none\"\r\n [style.left.%]=\"(huePosition / 360) * 100\"\r\n [style.backgroundColor]=\"hslToHex(huePosition, 100, 50)\">\r\n </div>\r\n \r\n </div>\r\n \r\n\r\n <!-- Hex Input -->\r\n <JContentForm>\r\n <JContentForm>\r\n\r\n <JLabel for=\"baseColor\" text=\"Ingresa un valor Hex\" classes=\"opacity-60\" />\r\n\r\n <JContentForm [rows]=\"true\">\r\n <JContentForm class=\"flex-1\">\r\n <JInput id=\"baseColor\" type=\"text\" [(ngModel)]=\"baseColor\"\r\n (input)=\"updateFromHexInput()\" placeholder=\"#000000\" />\r\n </JContentForm>\r\n\r\n <!-- Color Preview -->\r\n <div class=\"h-[35px] w-[60px] rounded shadow-md \"\r\n [style.background-color]=\"baseColor\"></div>\r\n </JContentForm>\r\n </JContentForm>\r\n\r\n\r\n <!-- Saturation Slider -->\r\n <JInput type=\"range\" [min]=\"0\" [max]=\"100\" [(ngModel)]=\"saturation\"\r\n (input)=\"generateTheme()\" placeholder=\"Saturaci\u00F3n\" [isLabel]=\"true\" simbol=\"%\" />\r\n\r\n\r\n <!-- Lightness Slider -->\r\n <JInput type=\"range\" [min]=\"0\" [max]=\"100\" [(ngModel)]=\"lightness\" (input)=\"generateTheme()\"\r\n placeholder=\"Luminosidad\" [isLabel]=\"true\" simbol=\"%\" />\r\n\r\n </JContentForm>\r\n\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Code Preview -->\r\n <div class=\"relative\">\r\n <div\r\n class=\"h-70 bg-background dark:bg-dark-background overflow-auto rounded-lg font-mono text-sm whitespace-pre-wrap border border-border dark:border-dark-border scroll-element\">\r\n <div class=\"p-4 text-black dark:text-white\">\r\n <span>{{ themeCode }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"absolute top-4 right-4\">\r\n <JButton (clicked)=\"copyThemeToClipboard()\" [icon]=\"icons['copy']\" [iconChange]=\"icons['check']\"\r\n [isChangeIcon]=\"copied\" [iconSize]=\"15\" tooltip=\"Copiar\" classes=\"w-[25px] h-[25px]\" />\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</main>", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: JInputComponent, selector: "JInput", inputs: ["type", "placeholder", "disabled", "required", "name", "id", "classes", "ngClass", "accept", "multiple", "showImage", "clearButton", "min", "max", "step", "isLabel", "simbol"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: JLabelComponent, selector: "JLabel", inputs: ["for", "isRequired", "isConditioned", "isAutomated", "classes", "ngClass"] }, { kind: "component", type: JContentFormComponent, selector: "JContentForm", inputs: ["columns", "rows"] }] });
|
|
2613
|
+
}
|
|
2614
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JThemeComponent, decorators: [{
|
|
2615
|
+
type: Component,
|
|
2616
|
+
args: [{ selector: 'JTheme', standalone: true, imports: [LucideAngularModule, JInputComponent, JButtonComponent, FormsModule, JLabelComponent, JContentFormComponent], template: "<!-- Main Content -->\r\n<main class=\"container mx-auto\">\r\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-8\">\r\n <!-- Controls Panel -->\r\n <div class=\"space-y-8 select-none\">\r\n <div>\r\n <h2 class=\"text-sm font-bold mb-1 opacity-60\">Selector de color</h2>\r\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <!-- Color Picker -->\r\n <div\r\n class=\"relative h-64 w-full overflow-hidden rounded-lg border border-border dark:border-dark-border\">\r\n <div class=\"absolute inset-0 bg-gradient-to-br from-white to-transparent\" style=\"z-index: 1;\">\r\n </div>\r\n <div class=\"absolute inset-0 bg-gradient-to-b from-transparent to-black\" style=\"z-index: 2;\">\r\n </div>\r\n <div class=\"absolute inset-0\"\r\n [style.background]=\"'linear-gradient(to right, hsl(' + huePosition + ', 0%, 50%), hsl(' + huePosition + ', 100%, 50%))'\"\r\n style=\"z-index: 0;\"></div>\r\n <div class=\"absolute w-6 h-6 rounded-full border-2 border-white transform -translate-x-1/2 -translate-y-1/2 cursor-pointer\"\r\n [style.left.%]=\"colorPickerPosition.x\" [style.top.%]=\"colorPickerPosition.y\"\r\n style=\"z-index: 3;\"></div>\r\n <div class=\"absolute inset-0 cursor-pointer\" (mousedown)=\"startColorPicking($event)\"\r\n (mousemove)=\"updateColorPicking($event)\" (mouseup)=\"stopColorPicking()\"\r\n (mouseleave)=\"stopColorPicking()\" style=\"z-index: 4;\"></div>\r\n </div>\r\n\r\n <!-- Color Spectrum -->\r\n <div class=\"space-y-4\">\r\n <div class=\"relative\">\r\n\r\n <!-- Picker bar -->\r\n <div class=\"h-8 w-full rounded-lg overflow-hidden border border-border dark:border-dark-border relative\">\r\n <div class=\"absolute inset-0\" [style.background]=\"hueGradient\"></div>\r\n <input type=\"range\" min=\"0\" max=\"360\" [value]=\"huePosition\"\r\n (input)=\"updateHueFromInput($event)\"\r\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\" />\r\n </div>\r\n \r\n <!-- Bolita fuera del flujo para que sobresalga -->\r\n <div class=\"absolute top-1/2 transform -translate-y-1/2 -translate-x-1/2 w-9 h-9 rounded-full border-2 border-white z-10 pointer-events-none\"\r\n [style.left.%]=\"(huePosition / 360) * 100\"\r\n [style.backgroundColor]=\"hslToHex(huePosition, 100, 50)\">\r\n </div>\r\n \r\n </div>\r\n \r\n\r\n <!-- Hex Input -->\r\n <JContentForm>\r\n <JContentForm>\r\n\r\n <JLabel for=\"baseColor\" text=\"Ingresa un valor Hex\" classes=\"opacity-60\" />\r\n\r\n <JContentForm [rows]=\"true\">\r\n <JContentForm class=\"flex-1\">\r\n <JInput id=\"baseColor\" type=\"text\" [(ngModel)]=\"baseColor\"\r\n (input)=\"updateFromHexInput()\" placeholder=\"#000000\" />\r\n </JContentForm>\r\n\r\n <!-- Color Preview -->\r\n <div class=\"h-[35px] w-[60px] rounded shadow-md \"\r\n [style.background-color]=\"baseColor\"></div>\r\n </JContentForm>\r\n </JContentForm>\r\n\r\n\r\n <!-- Saturation Slider -->\r\n <JInput type=\"range\" [min]=\"0\" [max]=\"100\" [(ngModel)]=\"saturation\"\r\n (input)=\"generateTheme()\" placeholder=\"Saturaci\u00F3n\" [isLabel]=\"true\" simbol=\"%\" />\r\n\r\n\r\n <!-- Lightness Slider -->\r\n <JInput type=\"range\" [min]=\"0\" [max]=\"100\" [(ngModel)]=\"lightness\" (input)=\"generateTheme()\"\r\n placeholder=\"Luminosidad\" [isLabel]=\"true\" simbol=\"%\" />\r\n\r\n </JContentForm>\r\n\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Code Preview -->\r\n <div class=\"relative\">\r\n <div\r\n class=\"h-70 bg-background dark:bg-dark-background overflow-auto rounded-lg font-mono text-sm whitespace-pre-wrap border border-border dark:border-dark-border scroll-element\">\r\n <div class=\"p-4 text-black dark:text-white\">\r\n <span>{{ themeCode }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"absolute top-4 right-4\">\r\n <JButton (clicked)=\"copyThemeToClipboard()\" [icon]=\"icons['copy']\" [iconChange]=\"icons['check']\"\r\n [isChangeIcon]=\"copied\" [iconSize]=\"15\" tooltip=\"Copiar\" classes=\"w-[25px] h-[25px]\" />\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</main>" }]
|
|
2617
|
+
}] });
|
|
2618
|
+
|
|
2619
|
+
class JPaginatorComponent {
|
|
2620
|
+
Math = Math;
|
|
2621
|
+
// Lucide icons
|
|
2622
|
+
icons = {
|
|
2623
|
+
firstPage: ChevronsLeft,
|
|
2624
|
+
prevPage: ChevronLeft,
|
|
2625
|
+
nextPage: ChevronRight,
|
|
2626
|
+
lastPage: ChevronsRight,
|
|
2627
|
+
};
|
|
2628
|
+
isLoading = false;
|
|
2629
|
+
// Para rastrear qué botón está siendo cargado
|
|
2630
|
+
loadingButton = null;
|
|
2631
|
+
// Paginacion
|
|
2632
|
+
currentPage = 1;
|
|
2633
|
+
itemsPerPageOptions = [10];
|
|
2634
|
+
itemsPerPage = this.itemsPerPageOptions[0];
|
|
2635
|
+
totalItems = 0;
|
|
2636
|
+
// Paginas a mostrar
|
|
2637
|
+
pages = [];
|
|
2638
|
+
// Eventos de salida
|
|
2639
|
+
pageChange = new EventEmitter();
|
|
2640
|
+
get startIndex() {
|
|
2641
|
+
return (this.currentPage - 1) * this.itemsPerPage;
|
|
2642
|
+
}
|
|
2643
|
+
get endIndex() {
|
|
2644
|
+
return Math.min(this.startIndex + this.itemsPerPage - 1, this.totalItems - 1);
|
|
2645
|
+
}
|
|
2646
|
+
get totalPages() {
|
|
2647
|
+
return Math.ceil(this.totalItems / this.itemsPerPage);
|
|
2648
|
+
}
|
|
2649
|
+
onPageChange(page) {
|
|
2650
|
+
if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
|
|
2651
|
+
this.loadingButton = page;
|
|
2652
|
+
this.currentPage = page;
|
|
2653
|
+
this.pageChange.emit(this.currentPage);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
goToFirstPage() {
|
|
2657
|
+
if (!this.isLoading && this.currentPage !== 1) {
|
|
2658
|
+
this.loadingButton = 'first';
|
|
2659
|
+
this.onPageChange(1);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
goToPreviousPage() {
|
|
2663
|
+
if (!this.isLoading && this.currentPage > 1) {
|
|
2664
|
+
this.loadingButton = 'prev';
|
|
2665
|
+
this.onPageChange(this.currentPage - 1);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
goToNextPage() {
|
|
2669
|
+
if (!this.isLoading && this.currentPage < this.totalPages) {
|
|
2670
|
+
this.loadingButton = 'next';
|
|
2671
|
+
this.onPageChange(this.currentPage + 1);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
goToLastPage() {
|
|
2675
|
+
if (!this.isLoading && this.currentPage !== this.totalPages) {
|
|
2676
|
+
this.loadingButton = 'last';
|
|
2677
|
+
this.onPageChange(this.totalPages);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
// Verificar si un botón específico está cargando
|
|
2681
|
+
isButtonLoading(button) {
|
|
2682
|
+
return this.isLoading && this.loadingButton === button;
|
|
2683
|
+
}
|
|
2684
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JPaginatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2685
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JPaginatorComponent, isStandalone: true, selector: "JPaginator", inputs: { isLoading: "isLoading", currentPage: "currentPage", itemsPerPageOptions: "itemsPerPageOptions", itemsPerPage: "itemsPerPage", totalItems: "totalItems", pages: "pages" }, outputs: { pageChange: "pageChange" }, ngImport: i0, template: "<!-- Pagination -->\r\n<div class=\"flex flex-col sm:flex-row justify-between items-center mt-4 gap-5\">\r\n <div class=\"text-sm text-gray-700 dark:text-gray-300\">\r\n Mostrando {{ totalItems ? startIndex + 1 : 0 }} a {{ Math.min(endIndex + 1, totalItems) }} de {{ totalItems }}\r\n registros\r\n </div>\r\n\r\n <div class=\"flex space-x-1 justify-center items-center\">\r\n <!-- First page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['firstPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToFirstPage()\" \r\n [disabled]=\"currentPage === 1 || isLoading\"\r\n [isLoading]=\"isButtonLoading('first')\"\r\n classes=\"secondary h-[35px]\"\r\n [class.disabled]=\"currentPage === 1 || isLoading\" />\r\n\r\n <!-- Previous page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['prevPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToPreviousPage()\" \r\n [disabled]=\"currentPage === 1 || isLoading\"\r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('prev')\"\r\n [class.disabled]=\"currentPage === 1 || isLoading\" />\r\n\r\n <!-- Page numbers -->\r\n @for (page of pages; track page) {\r\n <JButton onKeyPress \r\n [text]=\"page\"\r\n [iconSize]=\"10\"\r\n (click)=\"onPageChange(page)\" \r\n classes=\"secondary h-[30px]\" \r\n [disabled]=\"currentPage === page || isLoading\"\r\n [isLoading]=\"isButtonLoading(page)\"\r\n [ngClasses]=\"{ 'primary' : currentPage === page }\" />\r\n }\r\n\r\n <!-- Next page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['nextPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToNextPage()\" \r\n [disabled]=\"currentPage === totalPages || isLoading\" \r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('next')\"\r\n [class.disabled]=\"currentPage === totalPages || isLoading\" />\r\n\r\n <!-- Last page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['lastPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToLastPage()\" \r\n [disabled]=\"currentPage === totalPages || isLoading\" \r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('last')\"\r\n [class.disabled]=\"currentPage === totalPages || isLoading\" />\r\n </div>\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }] });
|
|
2686
|
+
}
|
|
2687
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JPaginatorComponent, decorators: [{
|
|
2688
|
+
type: Component,
|
|
2689
|
+
args: [{ selector: 'JPaginator', imports: [LucideAngularModule, JButtonComponent], template: "<!-- Pagination -->\r\n<div class=\"flex flex-col sm:flex-row justify-between items-center mt-4 gap-5\">\r\n <div class=\"text-sm text-gray-700 dark:text-gray-300\">\r\n Mostrando {{ totalItems ? startIndex + 1 : 0 }} a {{ Math.min(endIndex + 1, totalItems) }} de {{ totalItems }}\r\n registros\r\n </div>\r\n\r\n <div class=\"flex space-x-1 justify-center items-center\">\r\n <!-- First page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['firstPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToFirstPage()\" \r\n [disabled]=\"currentPage === 1 || isLoading\"\r\n [isLoading]=\"isButtonLoading('first')\"\r\n classes=\"secondary h-[35px]\"\r\n [class.disabled]=\"currentPage === 1 || isLoading\" />\r\n\r\n <!-- Previous page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['prevPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToPreviousPage()\" \r\n [disabled]=\"currentPage === 1 || isLoading\"\r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('prev')\"\r\n [class.disabled]=\"currentPage === 1 || isLoading\" />\r\n\r\n <!-- Page numbers -->\r\n @for (page of pages; track page) {\r\n <JButton onKeyPress \r\n [text]=\"page\"\r\n [iconSize]=\"10\"\r\n (click)=\"onPageChange(page)\" \r\n classes=\"secondary h-[30px]\" \r\n [disabled]=\"currentPage === page || isLoading\"\r\n [isLoading]=\"isButtonLoading(page)\"\r\n [ngClasses]=\"{ 'primary' : currentPage === page }\" />\r\n }\r\n\r\n <!-- Next page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['nextPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToNextPage()\" \r\n [disabled]=\"currentPage === totalPages || isLoading\" \r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('next')\"\r\n [class.disabled]=\"currentPage === totalPages || isLoading\" />\r\n\r\n <!-- Last page -->\r\n <JButton onKeyPress \r\n [icon]=\"icons['lastPage']\"\r\n [iconSize]=\"16\"\r\n (click)=\"goToLastPage()\" \r\n [disabled]=\"currentPage === totalPages || isLoading\" \r\n classes=\"secondary h-[35px]\"\r\n [isLoading]=\"isButtonLoading('last')\"\r\n [class.disabled]=\"currentPage === totalPages || isLoading\" />\r\n </div>\r\n</div>" }]
|
|
2690
|
+
}], propDecorators: { isLoading: [{
|
|
2691
|
+
type: Input
|
|
2692
|
+
}], currentPage: [{
|
|
2693
|
+
type: Input
|
|
2694
|
+
}], itemsPerPageOptions: [{
|
|
2695
|
+
type: Input
|
|
2696
|
+
}], itemsPerPage: [{
|
|
2697
|
+
type: Input
|
|
2698
|
+
}], totalItems: [{
|
|
2699
|
+
type: Input
|
|
2700
|
+
}], pages: [{
|
|
2701
|
+
type: Input
|
|
2702
|
+
}], pageChange: [{
|
|
2703
|
+
type: Output
|
|
2704
|
+
}] } });
|
|
2705
|
+
|
|
2706
|
+
class JFilterComponent {
|
|
2707
|
+
dialog;
|
|
2708
|
+
alertToastService;
|
|
2709
|
+
cdr;
|
|
2710
|
+
// Lucide icons
|
|
2711
|
+
icons = {
|
|
2712
|
+
filter: Filter,
|
|
2713
|
+
filterList: ArrowDownWideNarrow,
|
|
2714
|
+
eraser: Eraser,
|
|
2715
|
+
transh: Trash2,
|
|
2716
|
+
check: Check,
|
|
2717
|
+
clear: X,
|
|
2718
|
+
search: Search,
|
|
2719
|
+
loading: Loader2,
|
|
2720
|
+
default: Cpu
|
|
2721
|
+
};
|
|
2722
|
+
isLoadingSearch = false;
|
|
2723
|
+
isLoadingPerPage = false;
|
|
2724
|
+
isLoadingAditionalButtons = {};
|
|
2725
|
+
searchPlaceholder = 'Buscar...';
|
|
2726
|
+
// Inputs
|
|
2727
|
+
columns = [];
|
|
2728
|
+
itemsPerPageOptions = [];
|
|
2729
|
+
// Outputs
|
|
2730
|
+
search = new EventEmitter();
|
|
2731
|
+
itemsPerPageChange = new EventEmitter();
|
|
2732
|
+
searchQueryChange = new EventEmitter();
|
|
2733
|
+
onItemsPerPageChangeEvent = new EventEmitter();
|
|
2734
|
+
clearFilters = new EventEmitter();
|
|
2735
|
+
// Para el binding bidireccional de itemsPerPage
|
|
2736
|
+
_itemsPerPage = 0;
|
|
2737
|
+
get itemsPerPage() {
|
|
2738
|
+
return this._itemsPerPage;
|
|
2739
|
+
}
|
|
2740
|
+
// Para el binding bidireccional de searchQuery
|
|
2741
|
+
_searchQuery = '';
|
|
2742
|
+
get searchQuery() {
|
|
2743
|
+
return this._searchQuery;
|
|
2744
|
+
}
|
|
2745
|
+
// Para el debounce de la búsqueda
|
|
2746
|
+
searchSubject = new Subject();
|
|
2747
|
+
searchSubscription;
|
|
2748
|
+
isInitialized = false;
|
|
2749
|
+
// Configuración de botones
|
|
2750
|
+
filtersButton = [];
|
|
2751
|
+
// Filtros de tabla
|
|
2752
|
+
filtersSelect = [];
|
|
2753
|
+
get visibleColumns() {
|
|
2754
|
+
return this.columns.filter(col => !col.hidden);
|
|
2755
|
+
}
|
|
2756
|
+
constructor(dialog, alertToastService, cdr) {
|
|
2757
|
+
this.dialog = dialog;
|
|
2758
|
+
this.alertToastService = alertToastService;
|
|
2759
|
+
this.cdr = cdr;
|
|
2760
|
+
}
|
|
2761
|
+
ngOnInit() {
|
|
2762
|
+
// Configurar el debounce para la búsqueda después de la inicialización
|
|
2763
|
+
this.searchSubscription = this.searchSubject
|
|
2764
|
+
.pipe(debounceTime(1000), // 1 segundo de debounce
|
|
2765
|
+
filter(() => this.isInitialized) // Solo procesar si está inicializado
|
|
2766
|
+
)
|
|
2767
|
+
.subscribe(() => { this.onSearch(); });
|
|
2768
|
+
// Marcar como inicializado después de configurar la suscripción
|
|
2769
|
+
setTimeout(() => { this.isInitialized = true; }, 0);
|
|
2770
|
+
// Normalizar botones
|
|
2771
|
+
this.filtersButton = this.normalizeFilterButtons(this.filtersButton);
|
|
2772
|
+
}
|
|
2773
|
+
ngOnDestroy() {
|
|
2774
|
+
// Limpiar la suscripción al destruir el componente
|
|
2775
|
+
if (this.searchSubscription) {
|
|
2776
|
+
this.searchSubscription.unsubscribe();
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
// =====================================================
|
|
2780
|
+
// Bindings
|
|
2781
|
+
// =====================================================
|
|
2782
|
+
// Binding bidireccional de itemsPerPage
|
|
2783
|
+
set itemsPerPage(value) {
|
|
2784
|
+
this._itemsPerPage = value;
|
|
2785
|
+
this.itemsPerPageChange.emit(value);
|
|
2786
|
+
}
|
|
2787
|
+
// Binding bidireccional de searchQuery
|
|
2788
|
+
set searchQuery(value) {
|
|
2789
|
+
// Solo emitir eventos si el valor ha cambiado
|
|
2790
|
+
if (this._searchQuery !== value) {
|
|
2791
|
+
this._searchQuery = value;
|
|
2792
|
+
this.searchQueryChange.emit(value);
|
|
2793
|
+
// Solo emitir al subject si ya se ha inicializado el componente
|
|
2794
|
+
if (this.isInitialized) {
|
|
2795
|
+
this.searchSubject.next(value);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
// =====================================================
|
|
2800
|
+
// Metodos
|
|
2801
|
+
// =====================================================
|
|
2802
|
+
// Buscar
|
|
2803
|
+
onSearch() {
|
|
2804
|
+
this.search.emit();
|
|
2805
|
+
}
|
|
2806
|
+
// Eliminar busqueda
|
|
2807
|
+
clearSearch() {
|
|
2808
|
+
this.searchQuery = '';
|
|
2809
|
+
this.onSearch();
|
|
2810
|
+
}
|
|
2811
|
+
// Mostrar items por pagina
|
|
2812
|
+
onItemsPerPageChange() {
|
|
2813
|
+
this.onItemsPerPageChangeEvent.emit();
|
|
2814
|
+
}
|
|
2815
|
+
// =====================================================
|
|
2816
|
+
// Botones de filtro
|
|
2817
|
+
// =====================================================
|
|
2818
|
+
// Normaliza los botones de filtro
|
|
2819
|
+
normalizeFilterButtons(buttons) {
|
|
2820
|
+
return buttons.map((button) => {
|
|
2821
|
+
// Si es un botón de filtro
|
|
2822
|
+
if (button.type === 'filter') {
|
|
2823
|
+
return {
|
|
2824
|
+
...button,
|
|
2825
|
+
icon: this.icons['filter'],
|
|
2826
|
+
iconChange: this.icons['filterList'],
|
|
2827
|
+
isChangeIcon: () => this.dialog.openDialog,
|
|
2828
|
+
clicked: () => {
|
|
2829
|
+
this.dialog.openDialog = !this.dialog.openDialog;
|
|
2830
|
+
},
|
|
2831
|
+
classes: 'bg-[#20638f] hover:bg-[#1d5a82] text-white'
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
// Si es un botón de limpiar
|
|
2835
|
+
if (button.type === 'clear') {
|
|
2836
|
+
return {
|
|
2837
|
+
...button,
|
|
2838
|
+
icon: this.icons['eraser'],
|
|
2839
|
+
iconChange: this.icons['transh'],
|
|
2840
|
+
tooltip: 'Limpiar filtros',
|
|
2841
|
+
clicked: () => {
|
|
2842
|
+
const hasActiveSearch = this.searchQuery?.trim().length > 0;
|
|
2843
|
+
const hasSelectedFilters = this.filtersSelect.some(f => {
|
|
2844
|
+
const actual = f.selected;
|
|
2845
|
+
const inicial = f.initSelected ?? null;
|
|
2846
|
+
return JSON.stringify(actual) !== JSON.stringify(inicial);
|
|
2847
|
+
});
|
|
2848
|
+
if (!hasActiveSearch && !hasSelectedFilters) {
|
|
2849
|
+
this.alertToastService.AlertToast({
|
|
2850
|
+
type: 'info',
|
|
2851
|
+
title: 'No se ha filtrado nada...',
|
|
2852
|
+
description: 'No hay filtros activos para limpiar',
|
|
2853
|
+
autoClose: true
|
|
2854
|
+
});
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
// Limpiar búsqueda
|
|
2858
|
+
if (hasActiveSearch) {
|
|
2859
|
+
this.clearSearch();
|
|
2860
|
+
}
|
|
2861
|
+
// Limpiar selects
|
|
2862
|
+
for (const filter of this.filtersSelect) {
|
|
2863
|
+
const initial = filter.initSelected ?? null;
|
|
2864
|
+
const current = filter.selected;
|
|
2865
|
+
if (JSON.stringify(current) !== JSON.stringify(initial)) {
|
|
2866
|
+
filter.selected = initial;
|
|
2867
|
+
setTimeout(() => {
|
|
2868
|
+
if (typeof filter.onSelected === 'function') {
|
|
2869
|
+
filter.onSelected(initial);
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
this.clearFilters.emit('clear');
|
|
2875
|
+
},
|
|
2876
|
+
isChangeIcon: () => {
|
|
2877
|
+
const hasActiveSearch = this.searchQuery?.trim().length > 0;
|
|
2878
|
+
const hasSelectedFilters = this.filtersSelect.some(f => {
|
|
2879
|
+
const actual = f.selected;
|
|
2880
|
+
const inicial = f.initSelected ?? null;
|
|
2881
|
+
return JSON.stringify(actual) !== JSON.stringify(inicial);
|
|
2882
|
+
});
|
|
2883
|
+
return !hasActiveSearch && !hasSelectedFilters ? false : true;
|
|
2884
|
+
},
|
|
2885
|
+
classes: 'bg-[#164666] hover:bg-[#133d5a] text-white'
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
// Podés extenderlo para otros tipos como 'clear', 'excel', etc.
|
|
2889
|
+
return button;
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
// Manejar el click en un botón de filtro
|
|
2893
|
+
filterBottomClick(button) {
|
|
2894
|
+
// Ejecuta la acción del padre si está definida
|
|
2895
|
+
if (button.clicked) {
|
|
2896
|
+
button.clicked();
|
|
2897
|
+
}
|
|
2898
|
+
this.isButtonLoading(button);
|
|
2899
|
+
}
|
|
2900
|
+
isButtonLoading(button) {
|
|
2901
|
+
return button.type ? this.isLoadingAditionalButtons[button.type] === 'loading' : false;
|
|
2902
|
+
}
|
|
2903
|
+
// =====================================================
|
|
2904
|
+
// Acciones para validacion de un tipo a funcion
|
|
2905
|
+
// =====================================================
|
|
2906
|
+
// Obtener el ícono de un botón de filtro
|
|
2907
|
+
getIsChangeIcon(button) {
|
|
2908
|
+
if (typeof button.isChangeIcon === 'function') {
|
|
2909
|
+
return button.isChangeIcon();
|
|
2910
|
+
}
|
|
2911
|
+
return button.isChangeIcon ?? false;
|
|
2912
|
+
}
|
|
2913
|
+
// Obtener el valor del tooltip de un botón
|
|
2914
|
+
getTooltip(button) {
|
|
2915
|
+
if (typeof button.tooltip === 'function') {
|
|
2916
|
+
return button.tooltip(); // Puedes pasarle data si necesitas
|
|
2917
|
+
}
|
|
2918
|
+
return button.tooltip ?? '';
|
|
2919
|
+
}
|
|
2920
|
+
// Evaluar si un botón está deshabilitado
|
|
2921
|
+
getDisabled(button) {
|
|
2922
|
+
if (typeof button.disabled === 'function') {
|
|
2923
|
+
return button.disabled();
|
|
2924
|
+
}
|
|
2925
|
+
return button.disabled ?? false;
|
|
2926
|
+
}
|
|
2927
|
+
// Evaluar si un botón es visible
|
|
2928
|
+
getIsVisible(button) {
|
|
2929
|
+
if (typeof button.isVisible === 'function') {
|
|
2930
|
+
return button.isVisible();
|
|
2931
|
+
}
|
|
2932
|
+
// Si no se define, por defecto es visible
|
|
2933
|
+
return button.isVisible !== false;
|
|
2934
|
+
}
|
|
2935
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFilterComponent, deps: [{ token: JDialogShared }, { token: JAlertToastService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2936
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JFilterComponent, isStandalone: true, selector: "JFilter", inputs: { isLoadingSearch: "isLoadingSearch", isLoadingPerPage: "isLoadingPerPage", isLoadingAditionalButtons: "isLoadingAditionalButtons", searchPlaceholder: "searchPlaceholder", columns: "columns", itemsPerPageOptions: "itemsPerPageOptions", itemsPerPage: "itemsPerPage", searchQuery: "searchQuery", filtersButton: "filtersButton", filtersSelect: "filtersSelect" }, outputs: { search: "search", itemsPerPageChange: "itemsPerPageChange", searchQueryChange: "searchQueryChange", onItemsPerPageChangeEvent: "onItemsPerPageChangeEvent", clearFilters: "clearFilters" }, ngImport: i0, template: "<div class=\"flex flex-col md:flex-row-reverse justify-between items-center mb-4 gap-3 select-none\">\r\n <div class=\"flex items-center gap-3 md:w-auto w-full\">\r\n <div class=\"relative w-full\">\r\n <input type=\"text\" [(ngModel)]=\"searchQuery\" [placeholder]=\"searchPlaceholder\"\r\n class=\"input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 pr-16 focus:outline-none focus:ring-2 focus:ring-primary\" />\r\n\r\n <!-- Iconos dentro del input -->\r\n <div class=\"absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2\">\r\n <!-- Icono de carga -->\r\n @if (isLoadingSearch) {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"18\" class=\"text-gray-400 animate-spin\">\r\n </lucide-icon>\r\n }\r\n\r\n <!-- Bot\u00F3n X para limpiar -->\r\n @if (searchQuery && !isLoadingSearch) {\r\n <button (click)=\"clearSearch()\"\r\n class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\"\r\n aria-label=\"Limpiar b\u00FAsqueda\">\r\n <lucide-icon [name]=\"icons['clear']\" size=\"16\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n <!-- Icono de b\u00FAsqueda -->\r\n @if (!isLoadingSearch && !searchQuery) {\r\n <lucide-icon [name]=\"icons['search']\" size=\"16\" class=\"text-gray-400\">\r\n </lucide-icon>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Boton de elementos por pagina -->\r\n <div class=\"min-w-[75px]\">\r\n <JSelect \r\n [isLoading]=\"isLoadingPerPage\"\r\n [(ngModel)]=\"itemsPerPage\" \r\n (selectionChange)=\"onItemsPerPageChange()\" \r\n placeholder=\"Mostrar\"\r\n title=\"Mostrar elementos\" [options]=\"itemsPerPageOptions\" \r\n />\r\n </div>\r\n\r\n <!-- Boton de mostrar columnas -->\r\n @if (columns.length > 0) {\r\n <div>\r\n <JSelect \r\n type=\"multi-table\" \r\n title=\"Seleccionar columnas\" \r\n [columns]=\"visibleColumns\" \r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"flex gap-3 text-sm w-full md:w-auto md:justify-center justify-between \" >\r\n\r\n <!-- Botones interactivos adicionales -->\r\n <div class=\"flex flex-row flex-wrap gap-2\">\r\n @for (button of filtersButton; track $index) {\r\n @if (getIsVisible(button)) {\r\n <JButton \r\n [icon]=\"button.icon\" \r\n [iconSize]=\"22\"\r\n [iconChange]=\"button.iconChange\" \r\n [isChangeIcon]=\"getIsChangeIcon(button)\"\r\n [isLoading]=\"isButtonLoading(button)\"\r\n [disabled]=\"getDisabled(button)\"\r\n [tooltip]=\"getTooltip(button)\"\r\n [tooltipPosition]=\"button.tooltipPosition ?? 'top'\"\r\n (clicked)=\"filterBottomClick(button)\" \r\n [classes]=\"button.classes ?? ''\" \r\n [ngClasses]=\"{'min-w-auto p-1! pl-2! pr-2!' : true}\"\r\n />\r\n }\r\n }\r\n </div>\r\n \r\n </div>\r\n</div>\r\n\r\n\r\n<!-- Contenedor defiltros -->\r\n <div class=\"select-none\">\r\n <JDialog\r\n [openModal]=\"dialog.openDialog\"\r\n (closeModal)=\"dialog.onClose()\"\r\n [dialogTemplate]=\"templateDialog\" \r\n position=\"leftCenter\"\r\n title=\"Filtros\"\r\n [width]=\"220\"\r\n height=\"auto\"\r\n [overlay]=\"false\"\r\n [draggable]=\"true\"\r\n />\r\n</div>\r\n\r\n<ng-template #templateDialog>\r\n <div class=\"m-0 pt-1\">\r\n @if (filtersSelect.length > 0) {\r\n <div class=\"flex flex-col gap-2\">\r\n @for (filter of filtersSelect; track $index) {\r\n <!-- Dropdown -->\r\n @if (filter.type === 'dropdown') {\r\n <div>\r\n <JSelect\r\n [type]=\"'dropdown'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected ? filter.onSelected($event) : null\"\r\n [optionLabel]=\"filter.optionLabel ?? ''\"\r\n [optionValue]=\"filter.optionValue ?? ''\"\r\n [placeholder]=\"filter.placeholder ?? ''\"\r\n [showClear]=\"filter.showClear ?? false\"\r\n [options]=\"filter.options\"\r\n />\r\n </div>\r\n\r\n }\r\n \r\n <!-- Searchable -->\r\n @if (filter.type === 'searchable') {\r\n <div>\r\n <JSelect\r\n [type]=\"'searchable'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected?.($event)\"\r\n [endpoint]=\"filter.endpoint\"\r\n [optionLabel]=\"filter.optionLabel\"\r\n [optionValue]=\"filter.optionValue\"\r\n [placeholder]=\"filter.placeholder ?? ''\"\r\n [showClear]=\"filter.showClear ?? false\"\r\n [loadOnInit]=\"filter.loadOnInit ?? false\"\r\n [isSearch]=\"filter.isSearch ?? true\"\r\n [searchFields]=\"filter.searchFields || []\"\r\n [defaultFilters]=\"filter.defaultFilters || []\"\r\n />\r\n </div>\r\n }\r\n \r\n <!-- Multi-table -->\r\n @if (filter.type === 'multi-table') {\r\n <div>\r\n <JSelect\r\n [type]=\"'multi-table'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected ? filter.onSelected($event) : null\"\r\n [columns]=\"filter.columns\"\r\n [btnText]=\"filter.btnText ?? ''\"\r\n [isFilterSelect]=\"true\"\r\n />\r\n </div>\r\n\r\n }\r\n }\r\n </div>\r\n \r\n } @else {\r\n <div class=\"text-center text-sm text-black dark:text-white pt-5 pb-5\">\r\n <p>No hay filtros disponibles</p>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: JSelectComponent, selector: "JSelect", inputs: ["type", "btnIcon", "btnText", "title", "placeholder", "showClear", "columns", "options", "optionLabel", "optionValue", "labelSeparator", "isLoading", "endpoint", "loadOnInit", "defaultFilters", "searchFields", "isSearch", "isFilterSelect", "sort", "disabled"], outputs: ["updateVisibility", "selectionChange"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }, { kind: "component", type: JDialogComponent, selector: "JDialog", inputs: ["position", "offset", "openModal", "dialogTemplate", "title", "width", "height", "overlay", "draggable"], outputs: ["closeModal"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
2937
|
+
}
|
|
2938
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFilterComponent, decorators: [{
|
|
2939
|
+
type: Component,
|
|
2940
|
+
args: [{ selector: 'JFilter', standalone: true, imports: [LucideAngularModule, JSelectComponent, JButtonComponent, JDialogComponent, FormsModule], template: "<div class=\"flex flex-col md:flex-row-reverse justify-between items-center mb-4 gap-3 select-none\">\r\n <div class=\"flex items-center gap-3 md:w-auto w-full\">\r\n <div class=\"relative w-full\">\r\n <input type=\"text\" [(ngModel)]=\"searchQuery\" [placeholder]=\"searchPlaceholder\"\r\n class=\"input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 pr-16 focus:outline-none focus:ring-2 focus:ring-primary\" />\r\n\r\n <!-- Iconos dentro del input -->\r\n <div class=\"absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2\">\r\n <!-- Icono de carga -->\r\n @if (isLoadingSearch) {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"18\" class=\"text-gray-400 animate-spin\">\r\n </lucide-icon>\r\n }\r\n\r\n <!-- Bot\u00F3n X para limpiar -->\r\n @if (searchQuery && !isLoadingSearch) {\r\n <button (click)=\"clearSearch()\"\r\n class=\"pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer\"\r\n aria-label=\"Limpiar b\u00FAsqueda\">\r\n <lucide-icon [name]=\"icons['clear']\" size=\"16\"></lucide-icon>\r\n </button>\r\n }\r\n\r\n <!-- Icono de b\u00FAsqueda -->\r\n @if (!isLoadingSearch && !searchQuery) {\r\n <lucide-icon [name]=\"icons['search']\" size=\"16\" class=\"text-gray-400\">\r\n </lucide-icon>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Boton de elementos por pagina -->\r\n <div class=\"min-w-[75px]\">\r\n <JSelect \r\n [isLoading]=\"isLoadingPerPage\"\r\n [(ngModel)]=\"itemsPerPage\" \r\n (selectionChange)=\"onItemsPerPageChange()\" \r\n placeholder=\"Mostrar\"\r\n title=\"Mostrar elementos\" [options]=\"itemsPerPageOptions\" \r\n />\r\n </div>\r\n\r\n <!-- Boton de mostrar columnas -->\r\n @if (columns.length > 0) {\r\n <div>\r\n <JSelect \r\n type=\"multi-table\" \r\n title=\"Seleccionar columnas\" \r\n [columns]=\"visibleColumns\" \r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"flex gap-3 text-sm w-full md:w-auto md:justify-center justify-between \" >\r\n\r\n <!-- Botones interactivos adicionales -->\r\n <div class=\"flex flex-row flex-wrap gap-2\">\r\n @for (button of filtersButton; track $index) {\r\n @if (getIsVisible(button)) {\r\n <JButton \r\n [icon]=\"button.icon\" \r\n [iconSize]=\"22\"\r\n [iconChange]=\"button.iconChange\" \r\n [isChangeIcon]=\"getIsChangeIcon(button)\"\r\n [isLoading]=\"isButtonLoading(button)\"\r\n [disabled]=\"getDisabled(button)\"\r\n [tooltip]=\"getTooltip(button)\"\r\n [tooltipPosition]=\"button.tooltipPosition ?? 'top'\"\r\n (clicked)=\"filterBottomClick(button)\" \r\n [classes]=\"button.classes ?? ''\" \r\n [ngClasses]=\"{'min-w-auto p-1! pl-2! pr-2!' : true}\"\r\n />\r\n }\r\n }\r\n </div>\r\n \r\n </div>\r\n</div>\r\n\r\n\r\n<!-- Contenedor defiltros -->\r\n <div class=\"select-none\">\r\n <JDialog\r\n [openModal]=\"dialog.openDialog\"\r\n (closeModal)=\"dialog.onClose()\"\r\n [dialogTemplate]=\"templateDialog\" \r\n position=\"leftCenter\"\r\n title=\"Filtros\"\r\n [width]=\"220\"\r\n height=\"auto\"\r\n [overlay]=\"false\"\r\n [draggable]=\"true\"\r\n />\r\n</div>\r\n\r\n<ng-template #templateDialog>\r\n <div class=\"m-0 pt-1\">\r\n @if (filtersSelect.length > 0) {\r\n <div class=\"flex flex-col gap-2\">\r\n @for (filter of filtersSelect; track $index) {\r\n <!-- Dropdown -->\r\n @if (filter.type === 'dropdown') {\r\n <div>\r\n <JSelect\r\n [type]=\"'dropdown'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected ? filter.onSelected($event) : null\"\r\n [optionLabel]=\"filter.optionLabel ?? ''\"\r\n [optionValue]=\"filter.optionValue ?? ''\"\r\n [placeholder]=\"filter.placeholder ?? ''\"\r\n [showClear]=\"filter.showClear ?? false\"\r\n [options]=\"filter.options\"\r\n />\r\n </div>\r\n\r\n }\r\n \r\n <!-- Searchable -->\r\n @if (filter.type === 'searchable') {\r\n <div>\r\n <JSelect\r\n [type]=\"'searchable'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected?.($event)\"\r\n [endpoint]=\"filter.endpoint\"\r\n [optionLabel]=\"filter.optionLabel\"\r\n [optionValue]=\"filter.optionValue\"\r\n [placeholder]=\"filter.placeholder ?? ''\"\r\n [showClear]=\"filter.showClear ?? false\"\r\n [loadOnInit]=\"filter.loadOnInit ?? false\"\r\n [isSearch]=\"filter.isSearch ?? true\"\r\n [searchFields]=\"filter.searchFields || []\"\r\n [defaultFilters]=\"filter.defaultFilters || []\"\r\n />\r\n </div>\r\n }\r\n \r\n <!-- Multi-table -->\r\n @if (filter.type === 'multi-table') {\r\n <div>\r\n <JSelect\r\n [type]=\"'multi-table'\"\r\n [(ngModel)]=\"filter.selected\"\r\n (selectionChange)=\"filter.onSelected ? filter.onSelected($event) : null\"\r\n [columns]=\"filter.columns\"\r\n [btnText]=\"filter.btnText ?? ''\"\r\n [isFilterSelect]=\"true\"\r\n />\r\n </div>\r\n\r\n }\r\n }\r\n </div>\r\n \r\n } @else {\r\n <div class=\"text-center text-sm text-black dark:text-white pt-5 pb-5\">\r\n <p>No hay filtros disponibles</p>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n" }]
|
|
2941
|
+
}], ctorParameters: () => [{ type: JDialogShared }, { type: JAlertToastService }, { type: i0.ChangeDetectorRef }], propDecorators: { isLoadingSearch: [{
|
|
2942
|
+
type: Input
|
|
2943
|
+
}], isLoadingPerPage: [{
|
|
2944
|
+
type: Input
|
|
2945
|
+
}], isLoadingAditionalButtons: [{
|
|
2946
|
+
type: Input
|
|
2947
|
+
}], searchPlaceholder: [{
|
|
2948
|
+
type: Input
|
|
2949
|
+
}], columns: [{
|
|
2950
|
+
type: Input
|
|
2951
|
+
}], itemsPerPageOptions: [{
|
|
2952
|
+
type: Input
|
|
2953
|
+
}], search: [{
|
|
2954
|
+
type: Output
|
|
2955
|
+
}], itemsPerPageChange: [{
|
|
2956
|
+
type: Output
|
|
2957
|
+
}], searchQueryChange: [{
|
|
2958
|
+
type: Output
|
|
2959
|
+
}], onItemsPerPageChangeEvent: [{
|
|
2960
|
+
type: Output
|
|
2961
|
+
}], clearFilters: [{
|
|
2962
|
+
type: Output
|
|
2963
|
+
}], itemsPerPage: [{
|
|
2964
|
+
type: Input
|
|
2965
|
+
}], searchQuery: [{
|
|
2966
|
+
type: Input
|
|
2967
|
+
}], filtersButton: [{
|
|
2968
|
+
type: Input
|
|
2969
|
+
}], filtersSelect: [{
|
|
2970
|
+
type: Input
|
|
2971
|
+
}] } });
|
|
2972
|
+
|
|
2973
|
+
class JCalendarService {
|
|
2974
|
+
// Variables
|
|
2975
|
+
nameDaysAb = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
|
|
2976
|
+
nameDays = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
|
|
2977
|
+
nameMontsAb = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
|
|
2978
|
+
nameMonts = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
|
2979
|
+
// visualizacion de calendario
|
|
2980
|
+
dialogState = new BehaviorSubject(false);
|
|
2981
|
+
dialogState$ = this.dialogState.asObservable();
|
|
2982
|
+
constructor() { }
|
|
2983
|
+
//===================================================================
|
|
2984
|
+
// Transformaciones de fecha
|
|
2985
|
+
//===================================================================
|
|
2986
|
+
// Mostrar calendario
|
|
2987
|
+
showDialog() {
|
|
2988
|
+
this.dialogState.next(true);
|
|
2989
|
+
}
|
|
2990
|
+
// Ocultar calendario
|
|
2991
|
+
hideDialog() {
|
|
2992
|
+
this.dialogState.next(false);
|
|
2993
|
+
}
|
|
2994
|
+
// Obtener solo el mes
|
|
2995
|
+
getMonthFromDate(date) {
|
|
2996
|
+
const parsedDate = typeof date === 'string' ? new Date(date) : date;
|
|
2997
|
+
const monthIndex = parsedDate.getMonth(); // getMonth() devuelve un índice basado en 0
|
|
2998
|
+
return this.nameMonts[monthIndex]; // Devuelve el nombre completo del mes en español
|
|
2999
|
+
}
|
|
3000
|
+
//===================================================================
|
|
3001
|
+
// Transformaciones de fecha
|
|
3002
|
+
//===================================================================
|
|
3003
|
+
// Obtener edad de la persona
|
|
3004
|
+
calculateAge(birth) {
|
|
3005
|
+
const birthDate = typeof birth === 'string' ? new Date(birth) : birth;
|
|
3006
|
+
const today = new Date();
|
|
3007
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
3008
|
+
const m = today.getMonth() - birthDate.getMonth();
|
|
3009
|
+
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
3010
|
+
age--;
|
|
3011
|
+
}
|
|
3012
|
+
return age;
|
|
3013
|
+
}
|
|
3014
|
+
// Obtener edad de la persona en años, meses y días
|
|
3015
|
+
calculateAgeComplete(birth) {
|
|
3016
|
+
const birthDate = typeof birth === 'string' ? new Date(birth) : birth;
|
|
3017
|
+
const today = new Date();
|
|
3018
|
+
let years = today.getFullYear() - birthDate.getFullYear();
|
|
3019
|
+
let months = today.getMonth() - birthDate.getMonth();
|
|
3020
|
+
let days = today.getDate() - birthDate.getDate();
|
|
3021
|
+
if (days < 0) {
|
|
3022
|
+
months--;
|
|
3023
|
+
const previousMonth = new Date(today.getFullYear(), today.getMonth(), 0);
|
|
3024
|
+
days += previousMonth.getDate();
|
|
3025
|
+
}
|
|
3026
|
+
if (months < 0) {
|
|
3027
|
+
years--;
|
|
3028
|
+
months += 12;
|
|
3029
|
+
}
|
|
3030
|
+
return { years, months, days };
|
|
3031
|
+
}
|
|
3032
|
+
// Transformar fecha en formato de string
|
|
3033
|
+
formatearFechaString(date, month) {
|
|
3034
|
+
const partesFecha = date.split('-'); // Dividir la fecha en partes [año, mes, día]
|
|
3035
|
+
const año = partesFecha[0];
|
|
3036
|
+
const mes = this.nameMonts[parseInt(partesFecha[1], 10) - 1];
|
|
3037
|
+
const día = parseInt(partesFecha[2], 10);
|
|
3038
|
+
let mes_fin;
|
|
3039
|
+
if (month) {
|
|
3040
|
+
mes_fin = mes + month;
|
|
3041
|
+
}
|
|
3042
|
+
else {
|
|
3043
|
+
mes_fin = mes;
|
|
3044
|
+
}
|
|
3045
|
+
return `${día} de ${mes_fin} del ${año}`;
|
|
3046
|
+
}
|
|
3047
|
+
// transformar fecha con formato de Date
|
|
3048
|
+
formatearFechaDate(date, month) {
|
|
3049
|
+
let new_date;
|
|
3050
|
+
if (month) {
|
|
3051
|
+
new_date = new Date(date.setMonth(month));
|
|
3052
|
+
}
|
|
3053
|
+
else {
|
|
3054
|
+
new_date = date;
|
|
3055
|
+
}
|
|
3056
|
+
const año = new_date.getFullYear();
|
|
3057
|
+
const mes = this.nameMonts[new_date.getMonth()];
|
|
3058
|
+
const día = new_date.getDate();
|
|
3059
|
+
return `${día} de ${mes} del ${año}`;
|
|
3060
|
+
}
|
|
3061
|
+
// Formatea una fecha como "MMM YYYY"
|
|
3062
|
+
formatMonthYear(date) {
|
|
3063
|
+
const months = this.nameMontsAb.map(month => month.toUpperCase());
|
|
3064
|
+
const month = months[date.getMonth()];
|
|
3065
|
+
const year = date.getFullYear().toString().slice(-2);
|
|
3066
|
+
return `${month} ${year}`;
|
|
3067
|
+
}
|
|
3068
|
+
// Formatear fecha y hora a region bogota
|
|
3069
|
+
formatDateToBogota(date) {
|
|
3070
|
+
// Ajustar la fecha a la zona horaria de Bogotá (UTC-5)
|
|
3071
|
+
const bogotaOffset = -5 * 60; // Offset en minutos
|
|
3072
|
+
const localTime = date.getTime();
|
|
3073
|
+
const localOffset = date.getTimezoneOffset() * 60000;
|
|
3074
|
+
const utc = localTime + localOffset;
|
|
3075
|
+
const bogotaTime = utc + (bogotaOffset * 60000);
|
|
3076
|
+
const bogotaDate = new Date(bogotaTime);
|
|
3077
|
+
// Formatear la fecha a una cadena en el formato 'yyyy-MM-ddTHH:mm:ss'
|
|
3078
|
+
const year = bogotaDate.getFullYear();
|
|
3079
|
+
const month = String(bogotaDate.getMonth() + 1).padStart(2, '0');
|
|
3080
|
+
const day = String(bogotaDate.getDate()).padStart(2, '0');
|
|
3081
|
+
const hours = String(bogotaDate.getHours()).padStart(2, '0');
|
|
3082
|
+
const minutes = String(bogotaDate.getMinutes()).padStart(2, '0');
|
|
3083
|
+
const seconds = String(bogotaDate.getSeconds()).padStart(2, '0');
|
|
3084
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
3085
|
+
}
|
|
3086
|
+
// Formatear el tiempo restante
|
|
3087
|
+
formatRelativeDate(date) {
|
|
3088
|
+
return formatDistanceToNowStrict(new Date(date), {
|
|
3089
|
+
locale: {
|
|
3090
|
+
...es,
|
|
3091
|
+
formatDistance: (token, count, options) => {
|
|
3092
|
+
const formatDistanceLocale = {
|
|
3093
|
+
lessThanXSeconds: 'hace menos de {{count}} segundos',
|
|
3094
|
+
xSeconds: 'hace {{count}} segundos',
|
|
3095
|
+
halfAMinute: 'hace medio minuto',
|
|
3096
|
+
lessThanXMinutes: 'hace menos de {{count}} minutos',
|
|
3097
|
+
xMinutes: 'hace {{count}} minutos',
|
|
3098
|
+
aboutXHours: 'hace aproximadamente {{count}} horas',
|
|
3099
|
+
xHours: 'hace {{count}} horas',
|
|
3100
|
+
xDays: 'hace {{count}} días',
|
|
3101
|
+
aboutXWeeks: 'hace aproximadamente {{count}} semanas',
|
|
3102
|
+
xWeeks: 'hace {{count}} semanas',
|
|
3103
|
+
aboutXMonths: 'hace aproximadamente {{count}} meses',
|
|
3104
|
+
xMonths: 'hace {{count}} meses',
|
|
3105
|
+
aboutXYears: 'hace aproximadamente {{count}} años',
|
|
3106
|
+
xYears: 'hace {{count}} años',
|
|
3107
|
+
overXYears: 'hace más de {{count}} años',
|
|
3108
|
+
almostXYears: 'hace casi {{count}} años'
|
|
3109
|
+
};
|
|
3110
|
+
let result = formatDistanceLocale[token];
|
|
3111
|
+
if (typeof count === 'number') {
|
|
3112
|
+
result = result.replace('{{count}}', count.toString());
|
|
3113
|
+
// Manejar el singular y plural
|
|
3114
|
+
if (token === 'xHours' && count === 1) {
|
|
3115
|
+
result = result.replace('horas', 'hora');
|
|
3116
|
+
}
|
|
3117
|
+
if (token === 'xDays' && count === 1) {
|
|
3118
|
+
result = result.replace('días', 'día');
|
|
3119
|
+
}
|
|
3120
|
+
if (token === 'xWeeks' && count === 1) {
|
|
3121
|
+
result = result.replace('semanas', 'semana');
|
|
3122
|
+
}
|
|
3123
|
+
if (token === 'xMonths' && count === 1) {
|
|
3124
|
+
result = result.replace('meses', 'mes');
|
|
3125
|
+
}
|
|
3126
|
+
if (token === 'xYears' && count === 1) {
|
|
3127
|
+
result = result.replace('años', 'año');
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return result;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3136
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, providedIn: 'root' });
|
|
3137
|
+
}
|
|
3138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, decorators: [{
|
|
3139
|
+
type: Injectable,
|
|
3140
|
+
args: [{
|
|
3141
|
+
providedIn: 'root'
|
|
3142
|
+
}]
|
|
3143
|
+
}], ctorParameters: () => [] });
|
|
3144
|
+
|
|
3145
|
+
class CardComponent {
|
|
3146
|
+
currencyPipe;
|
|
3147
|
+
genericService;
|
|
3148
|
+
alertToastService;
|
|
3149
|
+
converterService;
|
|
3150
|
+
calendarService;
|
|
3151
|
+
Math = Math;
|
|
3152
|
+
// Estados de carga
|
|
3153
|
+
dataLoaded = new EventEmitter();
|
|
3154
|
+
loadingStates = {
|
|
3155
|
+
initialLoad: 'idle',
|
|
3156
|
+
search: 'idle',
|
|
3157
|
+
itemsPerPage: 'idle',
|
|
3158
|
+
pagination: 'idle',
|
|
3159
|
+
sort: 'idle',
|
|
3160
|
+
aditionalButtons: {},
|
|
3161
|
+
checked: 'idle',
|
|
3162
|
+
action: 'idle',
|
|
3163
|
+
};
|
|
3164
|
+
// Lucide icons
|
|
3165
|
+
icons = {
|
|
3166
|
+
sortDefault: ChevronsUpDown,
|
|
3167
|
+
sortAsc: ChevronUp,
|
|
3168
|
+
sortDesc: ChevronDown,
|
|
3169
|
+
chevronLeft: ChevronLeft,
|
|
3170
|
+
chevronRight: ChevronRight,
|
|
3171
|
+
view: Eye,
|
|
3172
|
+
edit: Edit,
|
|
3173
|
+
delete: Trash,
|
|
3174
|
+
search: Search,
|
|
3175
|
+
check: Check,
|
|
3176
|
+
loading: Loader2
|
|
3177
|
+
};
|
|
3178
|
+
endpoint;
|
|
3179
|
+
columns = [];
|
|
3180
|
+
defaultFilters = {};
|
|
3181
|
+
isPaginator = true;
|
|
3182
|
+
isSearch = true;
|
|
3183
|
+
data = [];
|
|
3184
|
+
itemTemplate;
|
|
3185
|
+
// Expansion
|
|
3186
|
+
expandTemplate;
|
|
3187
|
+
expandedRows = new Set();
|
|
3188
|
+
// Pagination
|
|
3189
|
+
currentPage = 1;
|
|
3190
|
+
itemsPerPageOptions = [10, 25, 50, 100];
|
|
3191
|
+
itemsPerPage = this.itemsPerPageOptions[0];
|
|
3192
|
+
totalItems = 0;
|
|
3193
|
+
// Sorting
|
|
3194
|
+
sortColumn = null;
|
|
3195
|
+
sortDirection = 'none';
|
|
3196
|
+
sortingColumn = null;
|
|
3197
|
+
// Search
|
|
3198
|
+
searchQuery = '';
|
|
3199
|
+
searchPlaceholder = 'Buscar...';
|
|
3200
|
+
// Filters
|
|
3201
|
+
filters = {};
|
|
3202
|
+
// Datos filtrados y paginados
|
|
3203
|
+
displayData = [];
|
|
3204
|
+
// Pagination display
|
|
3205
|
+
pages = [];
|
|
3206
|
+
// Propiedades calculadas para su visualización
|
|
3207
|
+
get startIndex() {
|
|
3208
|
+
return (this.currentPage - 1) * this.itemsPerPage;
|
|
3209
|
+
}
|
|
3210
|
+
get totalPages() {
|
|
3211
|
+
return Math.ceil(this.totalItems / this.itemsPerPage);
|
|
3212
|
+
}
|
|
3213
|
+
// Configuración de botones
|
|
3214
|
+
filtersButton = [];
|
|
3215
|
+
// Filtros de tabla
|
|
3216
|
+
filtersSelect = [];
|
|
3217
|
+
constructor(currencyPipe, genericService, alertToastService, converterService, calendarService) {
|
|
3218
|
+
this.currencyPipe = currencyPipe;
|
|
3219
|
+
this.genericService = genericService;
|
|
3220
|
+
this.alertToastService = alertToastService;
|
|
3221
|
+
this.converterService = converterService;
|
|
3222
|
+
this.calendarService = calendarService;
|
|
3223
|
+
}
|
|
3224
|
+
ngOnInit() {
|
|
3225
|
+
this.columnDefaults();
|
|
3226
|
+
this.loadData();
|
|
3227
|
+
this.overrideFilterEvents();
|
|
3228
|
+
}
|
|
3229
|
+
overrideFilterEvents() {
|
|
3230
|
+
for (const filter of this.filtersSelect) {
|
|
3231
|
+
if (filter.type === 'dropdown' || filter.type === 'searchable') {
|
|
3232
|
+
const key = filter.optionValue ?? 'value';
|
|
3233
|
+
const deepKey = filter.deep ? `${filter.deep}.${key}` : key;
|
|
3234
|
+
const originalOnSelected = filter.onSelected;
|
|
3235
|
+
filter.onSelected = (value) => {
|
|
3236
|
+
const selectedValue = value?.[key] ?? value;
|
|
3237
|
+
if (selectedValue === null || selectedValue === undefined) {
|
|
3238
|
+
delete this.filters[deepKey];
|
|
3239
|
+
}
|
|
3240
|
+
else {
|
|
3241
|
+
this.filters[deepKey] = selectedValue;
|
|
3242
|
+
}
|
|
3243
|
+
// Llama al original
|
|
3244
|
+
if (typeof originalOnSelected === 'function') {
|
|
3245
|
+
originalOnSelected(value);
|
|
3246
|
+
}
|
|
3247
|
+
this.loadData('search');
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
// =====================================================
|
|
3253
|
+
// Obtener datos
|
|
3254
|
+
// =====================================================
|
|
3255
|
+
// Cargar datos desde el servidor
|
|
3256
|
+
loadData(loadingType = 'initialLoad', onFinally) {
|
|
3257
|
+
this.setLoadingState(loadingType, 'loading');
|
|
3258
|
+
const params = this.getQueryParams();
|
|
3259
|
+
// Simulando espera de API
|
|
3260
|
+
// setTimeout(() => {
|
|
3261
|
+
this.genericService.getAll(this.endpoint, params).subscribe({
|
|
3262
|
+
next: (response) => {
|
|
3263
|
+
this.data = response.data[this.endpoint] ?? [];
|
|
3264
|
+
if (response.meta?.page) {
|
|
3265
|
+
this.totalItems = response.meta.page.totalRecords;
|
|
3266
|
+
this.currentPage = response.meta.page.currentPage;
|
|
3267
|
+
}
|
|
3268
|
+
else {
|
|
3269
|
+
this.totalItems = this.data.length;
|
|
3270
|
+
}
|
|
3271
|
+
if (response.meta?.sort) {
|
|
3272
|
+
this.sortColumn = response.meta.sort.by;
|
|
3273
|
+
this.sortDirection = response.meta.sort.order.toLowerCase();
|
|
3274
|
+
}
|
|
3275
|
+
this.updateDisplayData();
|
|
3276
|
+
this.generatePagination();
|
|
3277
|
+
this.setLoadingState(loadingType, 'success');
|
|
3278
|
+
},
|
|
3279
|
+
error: (error) => {
|
|
3280
|
+
console.error('Error fetching data:', error);
|
|
3281
|
+
this.setLoadingState(loadingType, 'error');
|
|
3282
|
+
}
|
|
3283
|
+
}).add(() => {
|
|
3284
|
+
this.dataLoaded.emit();
|
|
3285
|
+
if (loadingType === 'sort') {
|
|
3286
|
+
this.sortingColumn = null;
|
|
3287
|
+
}
|
|
3288
|
+
// Llamar al callback si fue proporcionado
|
|
3289
|
+
if (onFinally) {
|
|
3290
|
+
onFinally();
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
// }, 2000);
|
|
3294
|
+
}
|
|
3295
|
+
// Actualizar los datos que se muestran en la tabla
|
|
3296
|
+
updateDisplayData() {
|
|
3297
|
+
this.displayData = this.data;
|
|
3298
|
+
}
|
|
3299
|
+
// =====================================================
|
|
3300
|
+
// Cambiar estado en campos booleanos
|
|
3301
|
+
// =====================================================
|
|
3302
|
+
// Método para cambiar el estado de un checkbox
|
|
3303
|
+
onCheckboxChange(item, column) {
|
|
3304
|
+
// Get the ID field name based on dataProperty
|
|
3305
|
+
const idField = `id_${this.endpoint}`;
|
|
3306
|
+
// Get the record ID
|
|
3307
|
+
const recordId = item[idField];
|
|
3308
|
+
// Get the current boolean value
|
|
3309
|
+
const currentValue = this.getValue(item, column);
|
|
3310
|
+
// Actualizar estado
|
|
3311
|
+
this.genericService.enable(this.endpoint, recordId, { [column.key]: !currentValue }).subscribe({
|
|
3312
|
+
next: (response) => {
|
|
3313
|
+
item[column.key] = !currentValue;
|
|
3314
|
+
this.alertToastService.AlertToast({
|
|
3315
|
+
type: "success",
|
|
3316
|
+
title: "Registro actualizado!",
|
|
3317
|
+
description: response.msg,
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
// Eliminar filtros
|
|
3323
|
+
onClearFilters(buttonType) {
|
|
3324
|
+
this.setAditionalButtonLoading(buttonType);
|
|
3325
|
+
this.loadData('initialLoad', () => {
|
|
3326
|
+
this.clearAditionalButtonLoading(buttonType);
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
// =====================================================
|
|
3330
|
+
// Columnas
|
|
3331
|
+
// =====================================================
|
|
3332
|
+
// Valores por defecto de las columnas
|
|
3333
|
+
columnDefaults() {
|
|
3334
|
+
// Valores por defecto de las columnas
|
|
3335
|
+
this.columns.forEach(column => {
|
|
3336
|
+
if (column.visible === undefined) {
|
|
3337
|
+
column.visible = true;
|
|
3338
|
+
}
|
|
3339
|
+
if (column.sortable === undefined) {
|
|
3340
|
+
column.sortable = true;
|
|
3341
|
+
}
|
|
3342
|
+
if (column.isSearchable === undefined) {
|
|
3343
|
+
column.isSearchable = true;
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
// Obtener el recuento de columnas visibles para colspan en estado vacío
|
|
3348
|
+
getVisibleColumnsCount() {
|
|
3349
|
+
return this.columns.filter(col => col.visible).length;
|
|
3350
|
+
}
|
|
3351
|
+
// =====================================================
|
|
3352
|
+
// Prosesamiento de datos
|
|
3353
|
+
// =====================================================
|
|
3354
|
+
// Obtener el valor de las celdas para identificar un campo booleano
|
|
3355
|
+
isBoolean(value) {
|
|
3356
|
+
return typeof value === 'boolean';
|
|
3357
|
+
}
|
|
3358
|
+
// Método para obtener el valor de las celdas dinámicamente
|
|
3359
|
+
getValue(item, column) {
|
|
3360
|
+
let value;
|
|
3361
|
+
// Si existe un valueGetter, se usa directamente
|
|
3362
|
+
if (typeof column.valueGetter === 'function') {
|
|
3363
|
+
value = column.valueGetter(item);
|
|
3364
|
+
}
|
|
3365
|
+
else {
|
|
3366
|
+
// Si no, se busca el valor por key (con soporte para claves anidadas)
|
|
3367
|
+
const keys = column.key.split('.');
|
|
3368
|
+
value = item;
|
|
3369
|
+
for (const key of keys) {
|
|
3370
|
+
if (value != null) {
|
|
3371
|
+
value = value[key];
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
value = null;
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
// Si value es null o undefined, retornar 'S/N'
|
|
3380
|
+
if (value === null || value === undefined) {
|
|
3381
|
+
return 'S/N';
|
|
3382
|
+
}
|
|
3383
|
+
// Aplicar formato según configuración de columna
|
|
3384
|
+
const formatted = this.formatData(value, column);
|
|
3385
|
+
// Si formatData no devuelve nada (valor no formateado), devolver el valor original
|
|
3386
|
+
return formatted ?? value;
|
|
3387
|
+
}
|
|
3388
|
+
// Formatear datos para la tabla
|
|
3389
|
+
formatData(value, column) {
|
|
3390
|
+
// Añadir formato de moneda pero solo con el simbolo de $
|
|
3391
|
+
if (column.isdollar && value !== null) {
|
|
3392
|
+
const transformedValue = this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
|
|
3393
|
+
return transformedValue ? transformedValue.replace('US', '') : null;
|
|
3394
|
+
}
|
|
3395
|
+
if (column.isCurrency && value !== null) {
|
|
3396
|
+
return this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
|
|
3397
|
+
}
|
|
3398
|
+
if (column.isDate && value !== null) {
|
|
3399
|
+
return new Date(value).toLocaleDateString();
|
|
3400
|
+
}
|
|
3401
|
+
if (column.isDateText && value !== null) {
|
|
3402
|
+
return this.calendarService.formatearFechaString(`${value}`);
|
|
3403
|
+
}
|
|
3404
|
+
if (column.isDateTime && value !== null) {
|
|
3405
|
+
return new Date(value).toLocaleString();
|
|
3406
|
+
}
|
|
3407
|
+
if (column.isDateTimeText && value !== null) {
|
|
3408
|
+
return new Date(value).toString();
|
|
3409
|
+
}
|
|
3410
|
+
if (column.isRelativeTime && value !== null) {
|
|
3411
|
+
return this.calendarService.formatRelativeDate(value);
|
|
3412
|
+
}
|
|
3413
|
+
if (column.isFirstWord && value !== null) {
|
|
3414
|
+
return value.split(' ')[0];
|
|
3415
|
+
}
|
|
3416
|
+
// Si no se aplica ningún formato, retornar el valor original
|
|
3417
|
+
return value;
|
|
3418
|
+
}
|
|
3419
|
+
// =====================================================
|
|
3420
|
+
// Parametros de busqueda
|
|
3421
|
+
// =====================================================
|
|
3422
|
+
// Obtener los parámetros de consulta para la solicitud de datos
|
|
3423
|
+
getQueryParams() {
|
|
3424
|
+
const params = this.genericService.params({
|
|
3425
|
+
page: this.currentPage,
|
|
3426
|
+
limit: this.itemsPerPage,
|
|
3427
|
+
sort: {
|
|
3428
|
+
column: this.sortColumn,
|
|
3429
|
+
direction: this.sortDirection,
|
|
3430
|
+
},
|
|
3431
|
+
filters: this.filters,
|
|
3432
|
+
defaultFilters: this.defaultFilters,
|
|
3433
|
+
});
|
|
3434
|
+
if (this.searchQuery && this.searchQuery.trim() !== '') {
|
|
3435
|
+
const baseSearchKeys = this.columns
|
|
3436
|
+
.filter(col => col.isSearchable)
|
|
3437
|
+
.map(col => col.key);
|
|
3438
|
+
const extraSearchKeys = this.columns
|
|
3439
|
+
.flatMap(col => col.extraSearchFields || []);
|
|
3440
|
+
const allSearchKeys = [...baseSearchKeys, ...extraSearchKeys];
|
|
3441
|
+
params['search'] = this.searchQuery;
|
|
3442
|
+
params['searchFields'] = allSearchKeys;
|
|
3443
|
+
}
|
|
3444
|
+
return params;
|
|
3445
|
+
}
|
|
3446
|
+
// =====================================================
|
|
3447
|
+
// Ordenamiento
|
|
3448
|
+
// =====================================================
|
|
3449
|
+
// Numero de pagina
|
|
3450
|
+
getRowNumber(index) {
|
|
3451
|
+
return this.startIndex + index + 1;
|
|
3452
|
+
}
|
|
3453
|
+
// Ordenamiento de columnas
|
|
3454
|
+
onSort(column) {
|
|
3455
|
+
// Evitar múltiples solicitudes de ordenamiento simultáneas
|
|
3456
|
+
if (this.isLoading('sort')) {
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
if (!column.sortable)
|
|
3460
|
+
return;
|
|
3461
|
+
// Guardar la columna que está siendo ordenada
|
|
3462
|
+
this.sortingColumn = column.key;
|
|
3463
|
+
// Verificamos si estamos tratando con la misma columna que el último ordenamiento
|
|
3464
|
+
const currentSortKey = this.converterService.getSortKey(this.sortColumn);
|
|
3465
|
+
const columnSortKey = this.converterService.getSortKey(column.key);
|
|
3466
|
+
// Sort direction logic
|
|
3467
|
+
if (currentSortKey === columnSortKey) {
|
|
3468
|
+
// Alternar la dirección del ordenamiento
|
|
3469
|
+
if (this.sortDirection === 'asc') {
|
|
3470
|
+
this.sortDirection = 'desc';
|
|
3471
|
+
}
|
|
3472
|
+
else if (this.sortDirection === 'desc') {
|
|
3473
|
+
this.sortDirection = 'none';
|
|
3474
|
+
}
|
|
3475
|
+
else {
|
|
3476
|
+
this.sortDirection = 'asc';
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
else {
|
|
3480
|
+
// Si se selecciona una nueva columna para ordenar, iniciar con orden ascendente
|
|
3481
|
+
this.sortColumn = column.key;
|
|
3482
|
+
this.sortDirection = 'asc';
|
|
3483
|
+
}
|
|
3484
|
+
this.loadData('sort');
|
|
3485
|
+
}
|
|
3486
|
+
// Obtener la dirección de ordenamiento
|
|
3487
|
+
getSortKey(value) {
|
|
3488
|
+
return this.converterService.getSortKey(value);
|
|
3489
|
+
}
|
|
3490
|
+
// Manejar el evento de tecla presionada en la ordenación
|
|
3491
|
+
onSortKeyPress(event, column) {
|
|
3492
|
+
// Si ya hay una ordenación en progreso, no permitir otra
|
|
3493
|
+
if (this.isLoading('sort')) {
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
3497
|
+
event.preventDefault();
|
|
3498
|
+
this.onSort(column);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
// =====================================================
|
|
3502
|
+
// Search
|
|
3503
|
+
// =====================================================
|
|
3504
|
+
// Search functionality
|
|
3505
|
+
onSearch() {
|
|
3506
|
+
this.currentPage = 1;
|
|
3507
|
+
this.loadData('search');
|
|
3508
|
+
}
|
|
3509
|
+
// Selector de items por página
|
|
3510
|
+
onItemsPerPageChange() {
|
|
3511
|
+
this.currentPage = 1;
|
|
3512
|
+
this.loadData('itemsPerPage');
|
|
3513
|
+
}
|
|
3514
|
+
// =====================================================
|
|
3515
|
+
// Paginator
|
|
3516
|
+
// =====================================================
|
|
3517
|
+
// Generar numeros de paginación
|
|
3518
|
+
generatePagination() {
|
|
3519
|
+
const totalPages = this.totalPages;
|
|
3520
|
+
const currentPage = this.currentPage;
|
|
3521
|
+
// Ver los 5 numeros de paginación
|
|
3522
|
+
const maxPagesToShow = 3;
|
|
3523
|
+
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
|
3524
|
+
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
|
3525
|
+
// Adjust if we're near the end
|
|
3526
|
+
if (endPage - startPage + 1 < maxPagesToShow) {
|
|
3527
|
+
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
|
3528
|
+
}
|
|
3529
|
+
this.pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
|
|
3530
|
+
}
|
|
3531
|
+
// Paginar
|
|
3532
|
+
handlePageChange(page) {
|
|
3533
|
+
this.currentPage = page;
|
|
3534
|
+
this.loadData('pagination');
|
|
3535
|
+
}
|
|
3536
|
+
// =====================================================
|
|
3537
|
+
// Opciones
|
|
3538
|
+
// =====================================================
|
|
3539
|
+
// Método para manejar el clic de un botón
|
|
3540
|
+
onButtonClick(button, element) {
|
|
3541
|
+
// Ejecuta la acción del padre si está definida
|
|
3542
|
+
if (button.clicked) {
|
|
3543
|
+
button.clicked(element);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
// Método para obtener un tooltip
|
|
3547
|
+
getTooltip(tooltip, data) {
|
|
3548
|
+
if (typeof tooltip === 'function') {
|
|
3549
|
+
return tooltip(data);
|
|
3550
|
+
}
|
|
3551
|
+
return tooltip ?? '';
|
|
3552
|
+
}
|
|
3553
|
+
// Método para obtener un icono
|
|
3554
|
+
getIcon(icon, data) {
|
|
3555
|
+
if (typeof icon === 'function') {
|
|
3556
|
+
return icon(data);
|
|
3557
|
+
}
|
|
3558
|
+
return icon;
|
|
3559
|
+
}
|
|
3560
|
+
// Evaluar si un botón está deshabilitado
|
|
3561
|
+
getDisabled(option, data) {
|
|
3562
|
+
if (typeof option.disabled === 'function') {
|
|
3563
|
+
return option.disabled(data);
|
|
3564
|
+
}
|
|
3565
|
+
return !!option.disabled;
|
|
3566
|
+
}
|
|
3567
|
+
// Evaluar si un botón es visible
|
|
3568
|
+
getIsVisible(option, data) {
|
|
3569
|
+
if (typeof option.isVisible === 'function') {
|
|
3570
|
+
return option.isVisible(data);
|
|
3571
|
+
}
|
|
3572
|
+
// Si no se define, por defecto es visible
|
|
3573
|
+
return option.isVisible !== false;
|
|
3574
|
+
}
|
|
3575
|
+
// Método para obtener la clase CSS de un botón
|
|
3576
|
+
mergeNgClasses(optionNgClass, data) {
|
|
3577
|
+
const baseClass = {
|
|
3578
|
+
'min-w-auto p-1! pl-2! pr-2!': true
|
|
3579
|
+
};
|
|
3580
|
+
let dynamicClass = {};
|
|
3581
|
+
if (typeof optionNgClass === 'function') {
|
|
3582
|
+
dynamicClass = optionNgClass(data);
|
|
3583
|
+
}
|
|
3584
|
+
else {
|
|
3585
|
+
dynamicClass = optionNgClass ?? {};
|
|
3586
|
+
}
|
|
3587
|
+
return {
|
|
3588
|
+
...baseClass,
|
|
3589
|
+
...(typeof dynamicClass === 'string' ? { [dynamicClass]: true } : dynamicClass)
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
// =====================================================
|
|
3593
|
+
// Parametros de carga
|
|
3594
|
+
// =====================================================
|
|
3595
|
+
// Método para verificar si un estado específico está cargando
|
|
3596
|
+
isLoading(state) {
|
|
3597
|
+
return this.loadingStates[state] === 'loading';
|
|
3598
|
+
}
|
|
3599
|
+
// Método para verificar si cualquier estado está cargando
|
|
3600
|
+
isAnyLoading() {
|
|
3601
|
+
return Object.values(this.loadingStates).some(state => state === 'loading');
|
|
3602
|
+
}
|
|
3603
|
+
// Método para actualizar un estado de carga
|
|
3604
|
+
setLoadingState(state, value) {
|
|
3605
|
+
if (state === 'aditionalButtons') {
|
|
3606
|
+
if (typeof value === 'string') {
|
|
3607
|
+
// Evita asignar un string directamente si se espera un objeto
|
|
3608
|
+
console.warn(`No puedes asignar '${value}' directamente a aditionalButtons. Usa setAditionalButtonLoading en su lugar.`);
|
|
3609
|
+
}
|
|
3610
|
+
else {
|
|
3611
|
+
this.loadingStates.aditionalButtons = value;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
else {
|
|
3615
|
+
this.loadingStates[state] = value;
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
// Activar loading
|
|
3619
|
+
setAditionalButtonLoading(buttonType, id) {
|
|
3620
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
3621
|
+
this.loadingStates.aditionalButtons[key] = 'loading';
|
|
3622
|
+
}
|
|
3623
|
+
// Limpiar loading
|
|
3624
|
+
clearAditionalButtonLoading(buttonType, id) {
|
|
3625
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
3626
|
+
this.loadingStates.aditionalButtons[key] = 'idle';
|
|
3627
|
+
}
|
|
3628
|
+
// Verificar loading
|
|
3629
|
+
isAditionalButtonLoading(buttonType, id) {
|
|
3630
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
3631
|
+
return this.loadingStates.aditionalButtons[key] === 'loading';
|
|
3632
|
+
}
|
|
3633
|
+
// ==================================================
|
|
3634
|
+
// Expandir filas
|
|
3635
|
+
// ==================================================
|
|
3636
|
+
// Método para verificar si la tabla tiene filas expandibles
|
|
3637
|
+
hasExpandable() {
|
|
3638
|
+
return this.columns.some(col => typeof col.expandTemplate === 'function');
|
|
3639
|
+
}
|
|
3640
|
+
// Método para verificar si una fila está expandida
|
|
3641
|
+
toggleRow(row) {
|
|
3642
|
+
if (this.expandedRows.has(row)) {
|
|
3643
|
+
this.expandedRows.delete(row);
|
|
3644
|
+
}
|
|
3645
|
+
else {
|
|
3646
|
+
this.expandedRows.add(row);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
// Método para obtener contenido expandido de una fila
|
|
3650
|
+
getExpandedContent(row) {
|
|
3651
|
+
const expandableColumn = this.columns.find(col => typeof col.expandTemplate === 'function');
|
|
3652
|
+
return expandableColumn?.expandTemplate?.(row) ?? '';
|
|
3653
|
+
}
|
|
3654
|
+
// Método para obtener el estado de expansión de una fila
|
|
3655
|
+
getExpansionState(row) {
|
|
3656
|
+
return this.expandedRows.has(row) ? 'expanded' : 'collapsed';
|
|
3657
|
+
}
|
|
3658
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: CardComponent, deps: [{ token: i1$1.CurrencyPipe }, { token: JGenericService }, { token: JAlertToastService }, { token: ConverterService }, { token: JCalendarService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3659
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: CardComponent, isStandalone: true, selector: "JCrudCard", inputs: { endpoint: "endpoint", columns: "columns", defaultFilters: "defaultFilters", isPaginator: "isPaginator", isSearch: "isSearch", itemTemplate: "itemTemplate", itemsPerPageOptions: "itemsPerPageOptions", searchPlaceholder: "searchPlaceholder", filtersButton: "filtersButton", filtersSelect: "filtersSelect" }, outputs: { dataLoaded: "dataLoaded" }, ngImport: i0, template: "<div class=\"w-full\">\r\n @if (isSearch) {\r\n <JFilter\r\n [(searchQuery)]=\"searchQuery\" \r\n (search)=\"onSearch()\" \r\n [searchPlaceholder]=\"searchPlaceholder\"\r\n [(itemsPerPage)]=\"itemsPerPage\"\r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n (onItemsPerPageChangeEvent)=\"onItemsPerPageChange()\"\r\n [isLoadingSearch]=\"isLoading('search')\" \r\n [isLoadingPerPage]=\"isLoading('itemsPerPage')\" \r\n [isLoadingAditionalButtons]=\"loadingStates.aditionalButtons\"\r\n (clearFilters)=\"onClearFilters($event)\"\r\n [filtersButton]=\"filtersButton\"\r\n [filtersSelect]=\"filtersSelect\"\r\n />\r\n }\r\n\r\n @if (isLoading('initialLoad') && displayData.length === 0) {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border\">\r\n <div class=\"flex flex-col gap-3 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"30\" class=\"text-primary animate-spin\"></lucide-icon>\r\n <p>Cargando datos...</p>\r\n </div>\r\n </div>\r\n } @else if (displayData.length > 0) {\r\n\r\n @if (itemTemplate) {\r\n <div class=\"flex flex-wrap gap-4 my-4\">\r\n @for (item of displayData; track item?.id) {\r\n <div class=\"w-full sm:w-[48%] md:w-[31%] xl:w-[23.5%] min-w-[250px] max-w-full flex flex-col gap-3 rounded-xl border border-border dark:border-dark-border bg-white dark:bg-foreground shadow-sm hover:shadow-lg hover:border-primary/50 hover:dark:border-primary transition-all duration-300\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item, getValue: getValue.bind(this), columns: columns, item: item }\" />\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px]\">\r\n <p>No se ha definido ninguna plantilla personalizada (<code>itemTemplate</code>).</p>\r\n </div>\r\n }\r\n\r\n } @else if (!isLoading('pagination')) {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border\">\r\n No hay datos disponibles\r\n </div>\r\n }\r\n\r\n @if (isPaginator) {\r\n <JPaginator\r\n [currentPage]=\"currentPage\" \r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n [itemsPerPage]=\"itemsPerPage\"\r\n [totalItems]=\"totalItems\" \r\n [pages]=\"pages\" \r\n (pageChange)=\"handlePageChange($event)\" \r\n [isLoading]=\"isLoading('pagination')\"\r\n />\r\n }\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: JPaginatorComponent, selector: "JPaginator", inputs: ["isLoading", "currentPage", "itemsPerPageOptions", "itemsPerPage", "totalItems", "pages"], outputs: ["pageChange"] }, { kind: "component", type: JFilterComponent, selector: "JFilter", inputs: ["isLoadingSearch", "isLoadingPerPage", "isLoadingAditionalButtons", "searchPlaceholder", "columns", "itemsPerPageOptions", "itemsPerPage", "searchQuery", "filtersButton", "filtersSelect"], outputs: ["search", "itemsPerPageChange", "searchQueryChange", "onItemsPerPageChangeEvent", "clearFilters"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], animations: [
|
|
3660
|
+
trigger('slideToggle', [
|
|
3661
|
+
state('collapsed', style({
|
|
3662
|
+
height: '0',
|
|
3663
|
+
opacity: 0,
|
|
3664
|
+
overflow: 'hidden',
|
|
3665
|
+
})),
|
|
3666
|
+
state('expanded', style({
|
|
3667
|
+
height: '*',
|
|
3668
|
+
opacity: 1,
|
|
3669
|
+
})),
|
|
3670
|
+
transition('collapsed <=> expanded', [
|
|
3671
|
+
animate('0.3s ease-in-out')
|
|
3672
|
+
])
|
|
3673
|
+
])
|
|
3674
|
+
] });
|
|
3675
|
+
}
|
|
3676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: CardComponent, decorators: [{
|
|
3677
|
+
type: Component,
|
|
3678
|
+
args: [{ selector: 'JCrudCard', standalone: true, imports: [CommonModule, FormsModule, JPaginatorComponent, JFilterComponent, LucideAngularModule], animations: [
|
|
3679
|
+
trigger('slideToggle', [
|
|
3680
|
+
state('collapsed', style({
|
|
3681
|
+
height: '0',
|
|
3682
|
+
opacity: 0,
|
|
3683
|
+
overflow: 'hidden',
|
|
3684
|
+
})),
|
|
3685
|
+
state('expanded', style({
|
|
3686
|
+
height: '*',
|
|
3687
|
+
opacity: 1,
|
|
3688
|
+
})),
|
|
3689
|
+
transition('collapsed <=> expanded', [
|
|
3690
|
+
animate('0.3s ease-in-out')
|
|
3691
|
+
])
|
|
3692
|
+
])
|
|
3693
|
+
], template: "<div class=\"w-full\">\r\n @if (isSearch) {\r\n <JFilter\r\n [(searchQuery)]=\"searchQuery\" \r\n (search)=\"onSearch()\" \r\n [searchPlaceholder]=\"searchPlaceholder\"\r\n [(itemsPerPage)]=\"itemsPerPage\"\r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n (onItemsPerPageChangeEvent)=\"onItemsPerPageChange()\"\r\n [isLoadingSearch]=\"isLoading('search')\" \r\n [isLoadingPerPage]=\"isLoading('itemsPerPage')\" \r\n [isLoadingAditionalButtons]=\"loadingStates.aditionalButtons\"\r\n (clearFilters)=\"onClearFilters($event)\"\r\n [filtersButton]=\"filtersButton\"\r\n [filtersSelect]=\"filtersSelect\"\r\n />\r\n }\r\n\r\n @if (isLoading('initialLoad') && displayData.length === 0) {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border\">\r\n <div class=\"flex flex-col gap-3 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"30\" class=\"text-primary animate-spin\"></lucide-icon>\r\n <p>Cargando datos...</p>\r\n </div>\r\n </div>\r\n } @else if (displayData.length > 0) {\r\n\r\n @if (itemTemplate) {\r\n <div class=\"flex flex-wrap gap-4 my-4\">\r\n @for (item of displayData; track item?.id) {\r\n <div class=\"w-full sm:w-[48%] md:w-[31%] xl:w-[23.5%] min-w-[250px] max-w-full flex flex-col gap-3 rounded-xl border border-border dark:border-dark-border bg-white dark:bg-foreground shadow-sm hover:shadow-lg hover:border-primary/50 hover:dark:border-primary transition-all duration-300\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item, getValue: getValue.bind(this), columns: columns, item: item }\" />\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px]\">\r\n <p>No se ha definido ninguna plantilla personalizada (<code>itemTemplate</code>).</p>\r\n </div>\r\n }\r\n\r\n } @else if (!isLoading('pagination')) {\r\n <div class=\"w-full flex justify-center items-center h-100 bg-white dark:bg-foreground rounded-[20px] border border-border dark:border-dark-border\">\r\n No hay datos disponibles\r\n </div>\r\n }\r\n\r\n @if (isPaginator) {\r\n <JPaginator\r\n [currentPage]=\"currentPage\" \r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n [itemsPerPage]=\"itemsPerPage\"\r\n [totalItems]=\"totalItems\" \r\n [pages]=\"pages\" \r\n (pageChange)=\"handlePageChange($event)\" \r\n [isLoading]=\"isLoading('pagination')\"\r\n />\r\n }\r\n</div>\r\n" }]
|
|
3694
|
+
}], ctorParameters: () => [{ type: i1$1.CurrencyPipe }, { type: JGenericService }, { type: JAlertToastService }, { type: ConverterService }, { type: JCalendarService }], propDecorators: { dataLoaded: [{
|
|
3695
|
+
type: Output
|
|
3696
|
+
}], endpoint: [{
|
|
3697
|
+
type: Input
|
|
3698
|
+
}], columns: [{
|
|
3699
|
+
type: Input
|
|
3700
|
+
}], defaultFilters: [{
|
|
3701
|
+
type: Input
|
|
3702
|
+
}], isPaginator: [{
|
|
3703
|
+
type: Input
|
|
3704
|
+
}], isSearch: [{
|
|
3705
|
+
type: Input
|
|
3706
|
+
}], itemTemplate: [{
|
|
3707
|
+
type: Input
|
|
3708
|
+
}], itemsPerPageOptions: [{
|
|
3709
|
+
type: Input
|
|
3710
|
+
}], searchPlaceholder: [{
|
|
3711
|
+
type: Input
|
|
3712
|
+
}], filtersButton: [{
|
|
3713
|
+
type: Input
|
|
3714
|
+
}], filtersSelect: [{
|
|
3715
|
+
type: Input
|
|
3716
|
+
}] } });
|
|
3717
|
+
|
|
3718
|
+
class JFormShared {
|
|
3719
|
+
alertToastService;
|
|
3720
|
+
icons = {
|
|
3721
|
+
add: Plus,
|
|
3722
|
+
edit: Edit,
|
|
3723
|
+
delete: Trash,
|
|
3724
|
+
ban: Ban,
|
|
3725
|
+
power: Power,
|
|
3726
|
+
default: Cpu,
|
|
3727
|
+
keyRound: KeyRound,
|
|
3728
|
+
userRoundSearch: UserRoundSearch,
|
|
3729
|
+
circleCheckBig: CircleCheckBig,
|
|
3730
|
+
mousePointerClick: MousePointerClick,
|
|
3731
|
+
};
|
|
3732
|
+
// Resetear formulario
|
|
3733
|
+
onResetCallback = null;
|
|
3734
|
+
isLoading = false;
|
|
3735
|
+
openForm = false;
|
|
3736
|
+
typeForm = 'create';
|
|
3737
|
+
formControls = {};
|
|
3738
|
+
messages = null;
|
|
3739
|
+
constructor(alertToastService) {
|
|
3740
|
+
this.alertToastService = alertToastService;
|
|
3741
|
+
}
|
|
3742
|
+
onOpen() {
|
|
3743
|
+
this.openForm = true;
|
|
3744
|
+
if (this.onResetCallback) {
|
|
3745
|
+
this.onResetCallback();
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
onClose() {
|
|
3749
|
+
this.openForm = false;
|
|
3750
|
+
}
|
|
3751
|
+
onValidateChange(validation) {
|
|
3752
|
+
if (validation) {
|
|
3753
|
+
this.alertToastService.AlertToast({
|
|
3754
|
+
type: "info",
|
|
3755
|
+
title: "Sin cambios...",
|
|
3756
|
+
description: "No se han realizado cambios en el formulario"
|
|
3757
|
+
});
|
|
3758
|
+
return true;
|
|
3759
|
+
}
|
|
3760
|
+
return false;
|
|
3761
|
+
}
|
|
3762
|
+
onTableDataLoaded() {
|
|
3763
|
+
this.isLoading = false;
|
|
3764
|
+
this.openForm = false;
|
|
3765
|
+
if (this.messages) {
|
|
3766
|
+
this.alertToastService.AlertToast({
|
|
3767
|
+
type: "success",
|
|
3768
|
+
...this.messages
|
|
3769
|
+
});
|
|
3770
|
+
this.messages = null;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, deps: [{ token: JAlertToastService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3774
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, providedIn: 'root' });
|
|
3775
|
+
}
|
|
3776
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, decorators: [{
|
|
3777
|
+
type: Injectable,
|
|
3778
|
+
args: [{
|
|
3779
|
+
providedIn: 'root'
|
|
3780
|
+
}]
|
|
3781
|
+
}], ctorParameters: () => [{ type: JAlertToastService }] });
|
|
3782
|
+
|
|
3783
|
+
class JFormComponent {
|
|
3784
|
+
icons = {
|
|
3785
|
+
x: X,
|
|
3786
|
+
save: Save,
|
|
3787
|
+
circleX: CircleX,
|
|
3788
|
+
info: Info,
|
|
3789
|
+
asterisk: Asterisk
|
|
3790
|
+
};
|
|
3791
|
+
formTemplate;
|
|
3792
|
+
submitForm = new EventEmitter();
|
|
3793
|
+
// Mostrar formulario lateral
|
|
3794
|
+
openForm = false;
|
|
3795
|
+
closeForm = new EventEmitter();
|
|
3796
|
+
typeForm = 'none';
|
|
3797
|
+
titleForm = 'REGISTRO';
|
|
3798
|
+
isLoading = false;
|
|
3799
|
+
checkboxes = [];
|
|
3800
|
+
constructor() { }
|
|
3801
|
+
onSubmit() {
|
|
3802
|
+
this.submitForm.emit();
|
|
3803
|
+
}
|
|
3804
|
+
onClose() {
|
|
3805
|
+
this.closeForm.emit();
|
|
3806
|
+
}
|
|
3807
|
+
handleEscape(event) {
|
|
3808
|
+
if (this.openForm) {
|
|
3809
|
+
this.onClose();
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3813
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JFormComponent, isStandalone: true, selector: "JCrudForm", inputs: { formTemplate: "formTemplate", openForm: "openForm", typeForm: "typeForm", isLoading: "isLoading", checkboxes: "checkboxes" }, outputs: { submitForm: "submitForm", closeForm: "closeForm" }, host: { listeners: { "document:keydown.escape": "handleEscape($event)" } }, ngImport: i0, template: "<section class=\"content_form\">\r\n <!-- Overlay -->\r\n @if (openForm) {\r\n <div onKeyPress class=\"fixed top-0 left-0 w-full h-full bg-black/50 z-[998] transition duration-300\" (click)=\"onClose()\"></div>\r\n }\r\n\r\n <!-- Sidebar personalizado -->\r\n @if (openForm) {\r\n <div @sidebarTransition\r\n class=\"fixed top-0 right-0 h-full w-[30em] max-w-full border border-border dark:border-dark-border rounded-tl-[15px] rounded-bl-[15px] text-white bg-white dark:bg-dark-background shadow-lg z-[999] flex flex-col\">\r\n <!-- Header -->\r\n <div class=\"flex p-2 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-tl-[15px] font-semibold text-2sm\">\r\n <div class=\"flex items-center gap-2\">\r\n <span>\r\n {{ typeForm !== 'none' ? typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR' : 'REGISTROS' }} {{ typeForm !== 'none' ? titleForm : '' }}\r\n </span>\r\n \r\n <div class=\"cursor-pointer\" \r\n [jTooltip]=\"tooltipContentChangePassword\" \r\n [jTooltipPosition]=\"'bottom'\" \r\n [jTooltipOffsetX]=\"25\"\r\n [jTooltipOffsetY]=\"-20\"\r\n >\r\n <lucide-icon [name]=\"icons['info']\" [size]=\"15\"></lucide-icon>\r\n </div>\r\n </div>\r\n\r\n\r\n <ng-template #tooltipContentChangePassword>\r\n <div class=\"text-[12px] leading-[15px]\">\r\n <span>Las etiquetas con \u2731 indican campos requeridos.</span> <br>\r\n <span><span class=\"text-red-600 dark:text-red-300\">\u2731</span> Obligatorio</span> <br>\r\n <span><span class=\"text-blue-600 dark:text-blue-300\">\u2731</span> Condicionado</span> <br>\r\n <span><span class=\"text-purple-600 dark:text-purple-300\">\u2731</span> Autom\u00E1tico</span> <br>\r\n </div>\r\n </ng-template>\r\n\r\n <div class=\"flex gap-2\">\r\n @for (cb of checkboxes; track $index) {\r\n @if (cb.isVisible) {\r\n <JCheckbox onKeyPress\r\n type=\"switch\"\r\n [title]=\"cb.title\"\r\n [isChecked]=\"cb.isChecked\"\r\n [isLoading]=\"cb.isLoading\"\r\n (click)=\"cb.onClick(!cb.isChecked, $index)\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <button type=\"button\" (click)=\"onClose()\" class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"p-4 flex-1 overflow-x-hidden overflow-y-auto scroll-element\">\r\n @if (formTemplate) {\r\n <ng-container [ngTemplateOutlet]=\"formTemplate\"></ng-container>\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n @if (typeForm !== 'none') {\r\n <div class=\"p-4 border-t border-border dark:border-dark-border rounded-bl-[15px] flex justify-center gap-3\">\r\n <JButton\r\n type=\"submit\" \r\n (clicked)=\"onSubmit()\" \r\n classes=\"primary\" \r\n [icon]=\"icons['save']\" \r\n [isLoading]=\"isLoading\"\r\n >{{typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR'}}</JButton> \r\n\r\n <JButton \r\n type=\"button\" \r\n (clicked)=\"onClose()\" \r\n classes=\"secondary\" \r\n [icon]=\"icons['circleX']\" \r\n text=\"CANCELAR\" \r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</section>", styles: [""], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }, { kind: "directive", type: JTooltipModule, selector: "[jTooltip]", inputs: ["jTooltip", "jTooltipPosition", "jTooltipShowArrow", "jTooltipOffsetX", "jTooltipOffsetY"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JCheckboxComponent, selector: "JCheckbox", inputs: ["type", "icon", "iconSize", "disabled", "isLoading", "classes", "title", "isChecked", "item", "column", "getValue", "onCheckboxChange", "toggleSwitch"] }], animations: [
|
|
3814
|
+
trigger('sidebarTransition', [
|
|
3815
|
+
transition(':enter', [
|
|
3816
|
+
style({ opacity: 0, transform: 'translateX(100%)' }),
|
|
3817
|
+
animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
|
|
3818
|
+
]),
|
|
3819
|
+
transition(':leave', [
|
|
3820
|
+
animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(100%)' }))
|
|
3821
|
+
])
|
|
3822
|
+
])
|
|
3823
|
+
] });
|
|
3824
|
+
}
|
|
3825
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormComponent, decorators: [{
|
|
3826
|
+
type: Component,
|
|
3827
|
+
args: [{ selector: 'JCrudForm', standalone: true, imports: [LucideAngularModule, JButtonComponent, JTooltipModule, ReactiveFormsModule, FormsModule, CommonModule, JCheckboxComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], animations: [
|
|
3828
|
+
trigger('sidebarTransition', [
|
|
3829
|
+
transition(':enter', [
|
|
3830
|
+
style({ opacity: 0, transform: 'translateX(100%)' }),
|
|
3831
|
+
animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
|
|
3832
|
+
]),
|
|
3833
|
+
transition(':leave', [
|
|
3834
|
+
animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(100%)' }))
|
|
3835
|
+
])
|
|
3836
|
+
])
|
|
3837
|
+
], template: "<section class=\"content_form\">\r\n <!-- Overlay -->\r\n @if (openForm) {\r\n <div onKeyPress class=\"fixed top-0 left-0 w-full h-full bg-black/50 z-[998] transition duration-300\" (click)=\"onClose()\"></div>\r\n }\r\n\r\n <!-- Sidebar personalizado -->\r\n @if (openForm) {\r\n <div @sidebarTransition\r\n class=\"fixed top-0 right-0 h-full w-[30em] max-w-full border border-border dark:border-dark-border rounded-tl-[15px] rounded-bl-[15px] text-white bg-white dark:bg-dark-background shadow-lg z-[999] flex flex-col\">\r\n <!-- Header -->\r\n <div class=\"flex p-2 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-tl-[15px] font-semibold text-2sm\">\r\n <div class=\"flex items-center gap-2\">\r\n <span>\r\n {{ typeForm !== 'none' ? typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR' : 'REGISTROS' }} {{ typeForm !== 'none' ? titleForm : '' }}\r\n </span>\r\n \r\n <div class=\"cursor-pointer\" \r\n [jTooltip]=\"tooltipContentChangePassword\" \r\n [jTooltipPosition]=\"'bottom'\" \r\n [jTooltipOffsetX]=\"25\"\r\n [jTooltipOffsetY]=\"-20\"\r\n >\r\n <lucide-icon [name]=\"icons['info']\" [size]=\"15\"></lucide-icon>\r\n </div>\r\n </div>\r\n\r\n\r\n <ng-template #tooltipContentChangePassword>\r\n <div class=\"text-[12px] leading-[15px]\">\r\n <span>Las etiquetas con \u2731 indican campos requeridos.</span> <br>\r\n <span><span class=\"text-red-600 dark:text-red-300\">\u2731</span> Obligatorio</span> <br>\r\n <span><span class=\"text-blue-600 dark:text-blue-300\">\u2731</span> Condicionado</span> <br>\r\n <span><span class=\"text-purple-600 dark:text-purple-300\">\u2731</span> Autom\u00E1tico</span> <br>\r\n </div>\r\n </ng-template>\r\n\r\n <div class=\"flex gap-2\">\r\n @for (cb of checkboxes; track $index) {\r\n @if (cb.isVisible) {\r\n <JCheckbox onKeyPress\r\n type=\"switch\"\r\n [title]=\"cb.title\"\r\n [isChecked]=\"cb.isChecked\"\r\n [isLoading]=\"cb.isLoading\"\r\n (click)=\"cb.onClick(!cb.isChecked, $index)\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <button type=\"button\" (click)=\"onClose()\" class=\"p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer\">\r\n <lucide-icon [name]=\"icons['x']\" size=\"16\"></lucide-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Content -->\r\n <div class=\"p-4 flex-1 overflow-x-hidden overflow-y-auto scroll-element\">\r\n @if (formTemplate) {\r\n <ng-container [ngTemplateOutlet]=\"formTemplate\"></ng-container>\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n @if (typeForm !== 'none') {\r\n <div class=\"p-4 border-t border-border dark:border-dark-border rounded-bl-[15px] flex justify-center gap-3\">\r\n <JButton\r\n type=\"submit\" \r\n (clicked)=\"onSubmit()\" \r\n classes=\"primary\" \r\n [icon]=\"icons['save']\" \r\n [isLoading]=\"isLoading\"\r\n >{{typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR'}}</JButton> \r\n\r\n <JButton \r\n type=\"button\" \r\n (clicked)=\"onClose()\" \r\n classes=\"secondary\" \r\n [icon]=\"icons['circleX']\" \r\n text=\"CANCELAR\" \r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</section>" }]
|
|
3838
|
+
}], ctorParameters: () => [], propDecorators: { formTemplate: [{
|
|
3839
|
+
type: Input
|
|
3840
|
+
}], submitForm: [{
|
|
3841
|
+
type: Output
|
|
3842
|
+
}], openForm: [{
|
|
3843
|
+
type: Input
|
|
3844
|
+
}], closeForm: [{
|
|
3845
|
+
type: Output
|
|
3846
|
+
}], typeForm: [{
|
|
3847
|
+
type: Input
|
|
3848
|
+
}], isLoading: [{
|
|
3849
|
+
type: Input
|
|
3850
|
+
}], checkboxes: [{
|
|
3851
|
+
type: Input
|
|
3852
|
+
}], handleEscape: [{
|
|
3853
|
+
type: HostListener,
|
|
3854
|
+
args: ['document:keydown.escape', ['$event']]
|
|
3855
|
+
}] } });
|
|
3856
|
+
|
|
3857
|
+
class JTableComponent {
|
|
3858
|
+
currencyPipe;
|
|
3859
|
+
genericService;
|
|
3860
|
+
alertToastService;
|
|
3861
|
+
converterService;
|
|
3862
|
+
calendarService;
|
|
3863
|
+
Math = Math;
|
|
3864
|
+
// Estados de carga
|
|
3865
|
+
dataLoaded = new EventEmitter();
|
|
3866
|
+
loadingStates = {
|
|
3867
|
+
initialLoad: 'idle',
|
|
3868
|
+
search: 'idle',
|
|
3869
|
+
itemsPerPage: 'idle',
|
|
3870
|
+
pagination: 'idle',
|
|
3871
|
+
sort: 'idle',
|
|
3872
|
+
aditionalButtons: {},
|
|
3873
|
+
checked: 'idle',
|
|
3874
|
+
action: 'idle',
|
|
3875
|
+
};
|
|
3876
|
+
// Lucide icons
|
|
3877
|
+
icons = {
|
|
3878
|
+
sortDefault: ChevronsUpDown,
|
|
3879
|
+
sortAsc: ChevronUp,
|
|
3880
|
+
sortDesc: ChevronDown,
|
|
3881
|
+
chevronLeft: ChevronLeft,
|
|
3882
|
+
chevronRight: ChevronRight,
|
|
3883
|
+
view: Eye,
|
|
3884
|
+
edit: Edit,
|
|
3885
|
+
delete: Trash,
|
|
3886
|
+
search: Search,
|
|
3887
|
+
check: Check,
|
|
3888
|
+
loading: Loader2
|
|
3889
|
+
};
|
|
3890
|
+
endpoint;
|
|
3891
|
+
columns = [];
|
|
3892
|
+
defaultFilters = {};
|
|
3893
|
+
isPaginator = true;
|
|
3894
|
+
isSearch = true;
|
|
3895
|
+
data = [];
|
|
3896
|
+
// Expansion
|
|
3897
|
+
expandTemplate;
|
|
3898
|
+
expandedRows = new Set();
|
|
3899
|
+
// Pagination
|
|
3900
|
+
currentPage = 1;
|
|
3901
|
+
itemsPerPageOptions = [10, 25, 50, 100];
|
|
3902
|
+
itemsPerPage = this.itemsPerPageOptions[0];
|
|
3903
|
+
totalItems = 0;
|
|
3904
|
+
// Sorting
|
|
3905
|
+
sortColumn = null;
|
|
3906
|
+
sortDirection = 'none';
|
|
3907
|
+
sortingColumn = null;
|
|
3908
|
+
// Search
|
|
3909
|
+
searchQuery = '';
|
|
3910
|
+
searchPlaceholder = 'Buscar...';
|
|
3911
|
+
// Filters
|
|
3912
|
+
filters = {};
|
|
3913
|
+
// Datos filtrados y paginados
|
|
3914
|
+
displayData = [];
|
|
3915
|
+
// Pagination display
|
|
3916
|
+
pages = [];
|
|
3917
|
+
// Check
|
|
3918
|
+
checked = false;
|
|
3919
|
+
checkedValues = [[true], [false]];
|
|
3920
|
+
checkedTitles = ["Activos", "Inactivos"];
|
|
3921
|
+
isChecked;
|
|
3922
|
+
titleChecked;
|
|
3923
|
+
// Propiedades calculadas para su visualización
|
|
3924
|
+
get startIndex() {
|
|
3925
|
+
return (this.currentPage - 1) * this.itemsPerPage;
|
|
3926
|
+
}
|
|
3927
|
+
get totalPages() {
|
|
3928
|
+
return Math.ceil(this.totalItems / this.itemsPerPage);
|
|
3929
|
+
}
|
|
3930
|
+
// Configuración de botones
|
|
3931
|
+
filtersButton = [];
|
|
3932
|
+
// Filtros de tabla
|
|
3933
|
+
filtersSelect = [];
|
|
3934
|
+
// Opciones de botones
|
|
3935
|
+
optionsTable = [];
|
|
3936
|
+
constructor(currencyPipe, genericService, alertToastService, converterService, calendarService) {
|
|
3937
|
+
this.currencyPipe = currencyPipe;
|
|
3938
|
+
this.genericService = genericService;
|
|
3939
|
+
this.alertToastService = alertToastService;
|
|
3940
|
+
this.converterService = converterService;
|
|
3941
|
+
this.calendarService = calendarService;
|
|
3942
|
+
}
|
|
3943
|
+
ngOnInit() {
|
|
3944
|
+
this.isChecked = this.checkedValues[0][0];
|
|
3945
|
+
this.titleChecked = this.checkedTitles[0];
|
|
3946
|
+
this.columnDefaults();
|
|
3947
|
+
this.loadData();
|
|
3948
|
+
this.overrideFilterEvents();
|
|
3949
|
+
}
|
|
3950
|
+
overrideFilterEvents() {
|
|
3951
|
+
for (const filter of this.filtersSelect) {
|
|
3952
|
+
if (filter.type === 'dropdown' || filter.type === 'searchable') {
|
|
3953
|
+
const key = filter.optionValue ?? 'value';
|
|
3954
|
+
const deepKey = filter.deep ? `${filter.deep}.${key}` : key;
|
|
3955
|
+
const originalOnSelected = filter.onSelected;
|
|
3956
|
+
filter.onSelected = (value) => {
|
|
3957
|
+
const selectedValue = value?.[key] ?? value;
|
|
3958
|
+
if (selectedValue === null || selectedValue === undefined) {
|
|
3959
|
+
delete this.filters[deepKey];
|
|
3960
|
+
}
|
|
3961
|
+
else {
|
|
3962
|
+
this.filters[deepKey] = selectedValue;
|
|
3963
|
+
}
|
|
3964
|
+
// Llama al original
|
|
3965
|
+
if (typeof originalOnSelected === 'function') {
|
|
3966
|
+
originalOnSelected(value);
|
|
3967
|
+
}
|
|
3968
|
+
this.loadData('search');
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
// =====================================================
|
|
3974
|
+
// Obtener datos
|
|
3975
|
+
// =====================================================
|
|
3976
|
+
// Cargar datos desde el servidor
|
|
3977
|
+
loadData(loadingType = 'initialLoad', onFinally) {
|
|
3978
|
+
this.setLoadingState(loadingType, 'loading');
|
|
3979
|
+
const params = this.getQueryParams();
|
|
3980
|
+
// Simulando espera de API
|
|
3981
|
+
// setTimeout(() => {
|
|
3982
|
+
this.genericService.getAll(this.endpoint, params).subscribe({
|
|
3983
|
+
next: (response) => {
|
|
3984
|
+
this.data = response.data[this.endpoint] ?? [];
|
|
3985
|
+
if (response.meta?.page) {
|
|
3986
|
+
this.totalItems = response.meta.page.totalRecords;
|
|
3987
|
+
this.currentPage = response.meta.page.currentPage;
|
|
3988
|
+
}
|
|
3989
|
+
else {
|
|
3990
|
+
this.totalItems = this.data.length;
|
|
3991
|
+
}
|
|
3992
|
+
if (response.meta?.sort) {
|
|
3993
|
+
this.sortColumn = response.meta.sort.by;
|
|
3994
|
+
this.sortDirection = response.meta.sort.order.toLowerCase();
|
|
3995
|
+
}
|
|
3996
|
+
this.updateDisplayData();
|
|
3997
|
+
this.generatePagination();
|
|
3998
|
+
this.setLoadingState(loadingType, 'success');
|
|
3999
|
+
},
|
|
4000
|
+
error: (error) => {
|
|
4001
|
+
console.error('Error fetching data:', error);
|
|
4002
|
+
this.setLoadingState(loadingType, 'error');
|
|
4003
|
+
}
|
|
4004
|
+
}).add(() => {
|
|
4005
|
+
this.dataLoaded.emit();
|
|
4006
|
+
if (loadingType === 'sort') {
|
|
4007
|
+
this.sortingColumn = null;
|
|
4008
|
+
}
|
|
4009
|
+
// Llamar al callback si fue proporcionado
|
|
4010
|
+
if (onFinally) {
|
|
4011
|
+
onFinally();
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
// }, 2000);
|
|
4015
|
+
}
|
|
4016
|
+
// Actualizar los datos que se muestran en la tabla
|
|
4017
|
+
updateDisplayData() {
|
|
4018
|
+
this.displayData = this.data;
|
|
4019
|
+
}
|
|
4020
|
+
// =====================================================
|
|
4021
|
+
// Cambiar estado en campos booleanos
|
|
4022
|
+
// =====================================================
|
|
4023
|
+
// Método para cambiar el estado de un checkbox
|
|
4024
|
+
onCheckboxChange(item, column) {
|
|
4025
|
+
// Get the ID field name based on dataProperty
|
|
4026
|
+
const idField = `id_${this.endpoint}`;
|
|
4027
|
+
// Get the record ID
|
|
4028
|
+
const recordId = item[idField];
|
|
4029
|
+
// Get the current boolean value
|
|
4030
|
+
const currentValue = this.getValue(item, column);
|
|
4031
|
+
// Actualizar estado
|
|
4032
|
+
this.genericService.enable(this.endpoint, recordId, { [column.key]: !currentValue }).subscribe({
|
|
4033
|
+
next: (response) => {
|
|
4034
|
+
item[column.key] = !currentValue;
|
|
4035
|
+
this.alertToastService.AlertToast({
|
|
4036
|
+
type: "success",
|
|
4037
|
+
title: "Registro actualizado!",
|
|
4038
|
+
description: response.msg,
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
});
|
|
4042
|
+
}
|
|
4043
|
+
// Cambiar activos o inactivos
|
|
4044
|
+
checkActiveInactive(isChecked) {
|
|
4045
|
+
this.isChecked = !isChecked;
|
|
4046
|
+
const index = this.isChecked ? 0 : 1;
|
|
4047
|
+
this.titleChecked = this.checkedTitles[index];
|
|
4048
|
+
// SOLO actualizamos la propiedad id_status sin tocar los demás filtros
|
|
4049
|
+
this.filters['id_status'] = this.checkedValues[index];
|
|
4050
|
+
this.filtersSelect = this.filtersSelect.map(filter => {
|
|
4051
|
+
if ('optionValue' in filter && filter.optionValue === 'id_status') {
|
|
4052
|
+
return {
|
|
4053
|
+
...filter,
|
|
4054
|
+
defaultFilters: {
|
|
4055
|
+
...(filter.hasOwnProperty('defaultFilters') ? filter.defaultFilters : {}),
|
|
4056
|
+
id_status: this.filters['id_status']
|
|
4057
|
+
},
|
|
4058
|
+
selected: null // limpia la selección previa para forzar el reload
|
|
4059
|
+
};
|
|
4060
|
+
}
|
|
4061
|
+
return filter;
|
|
4062
|
+
});
|
|
4063
|
+
this.currentPage = 1;
|
|
4064
|
+
this.loadData('checked');
|
|
4065
|
+
}
|
|
4066
|
+
// Eliminar filtros
|
|
4067
|
+
onClearFilters(buttonType) {
|
|
4068
|
+
this.setAditionalButtonLoading(buttonType);
|
|
4069
|
+
this.loadData('initialLoad', () => {
|
|
4070
|
+
this.clearAditionalButtonLoading(buttonType);
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
// =====================================================
|
|
4074
|
+
// Columnas
|
|
4075
|
+
// =====================================================
|
|
4076
|
+
// Valores por defecto de las columnas
|
|
4077
|
+
columnDefaults() {
|
|
4078
|
+
// Valores por defecto de las columnas
|
|
4079
|
+
this.columns.forEach(column => {
|
|
4080
|
+
if (column.visible === undefined) {
|
|
4081
|
+
column.visible = true;
|
|
4082
|
+
}
|
|
4083
|
+
if (column.sortable === undefined) {
|
|
4084
|
+
column.sortable = true;
|
|
4085
|
+
}
|
|
4086
|
+
if (column.isSearchable === undefined) {
|
|
4087
|
+
column.isSearchable = true;
|
|
4088
|
+
}
|
|
4089
|
+
});
|
|
4090
|
+
}
|
|
4091
|
+
// Obtener el recuento de columnas visibles para colspan en estado vacío
|
|
4092
|
+
getVisibleColumnsCount() {
|
|
4093
|
+
return this.columns.filter(col => col.visible).length;
|
|
4094
|
+
}
|
|
4095
|
+
// =====================================================
|
|
4096
|
+
// Prosesamiento de datos
|
|
4097
|
+
// =====================================================
|
|
4098
|
+
// Obtener el valor de las celdas para identificar un campo booleano
|
|
4099
|
+
isBoolean(value) {
|
|
4100
|
+
return typeof value === 'boolean';
|
|
4101
|
+
}
|
|
4102
|
+
// Método para obtener el valor de las celdas dinámicamente
|
|
4103
|
+
getValue(item, column) {
|
|
4104
|
+
let value;
|
|
4105
|
+
// Si existe un valueGetter, se usa directamente
|
|
4106
|
+
if (typeof column.valueGetter === 'function') {
|
|
4107
|
+
value = column.valueGetter(item);
|
|
4108
|
+
}
|
|
4109
|
+
else {
|
|
4110
|
+
// Si no, se busca el valor por key (con soporte para claves anidadas)
|
|
4111
|
+
const keys = column.key.split('.');
|
|
4112
|
+
value = item;
|
|
4113
|
+
for (const key of keys) {
|
|
4114
|
+
if (value != null) {
|
|
4115
|
+
value = value[key];
|
|
4116
|
+
}
|
|
4117
|
+
else {
|
|
4118
|
+
value = null;
|
|
4119
|
+
break;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
// Si value es null o undefined, retornar 'S/N'
|
|
4124
|
+
if (value === null || value === undefined) {
|
|
4125
|
+
return 'S/N';
|
|
4126
|
+
}
|
|
4127
|
+
// Aplicar formato según configuración de columna
|
|
4128
|
+
const formatted = this.formatData(value, column);
|
|
4129
|
+
// Si formatData no devuelve nada (valor no formateado), devolver el valor original
|
|
4130
|
+
return formatted ?? value;
|
|
4131
|
+
}
|
|
4132
|
+
// Formatear datos para la tabla
|
|
4133
|
+
formatData(value, column) {
|
|
4134
|
+
// Añadir formato de moneda pero solo con el simbolo de $
|
|
4135
|
+
if (column.isdollar && value !== null) {
|
|
4136
|
+
const transformedValue = this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
|
|
4137
|
+
return transformedValue ? transformedValue.replace('US', '') : null;
|
|
4138
|
+
}
|
|
4139
|
+
if (column.isCurrency && value !== null) {
|
|
4140
|
+
return this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
|
|
4141
|
+
}
|
|
4142
|
+
if (column.isDate && value !== null) {
|
|
4143
|
+
return new Date(value).toLocaleDateString();
|
|
4144
|
+
}
|
|
4145
|
+
if (column.isDateText && value !== null) {
|
|
4146
|
+
return this.calendarService.formatearFechaString(`${value}`);
|
|
4147
|
+
}
|
|
4148
|
+
if (column.isDateTime && value !== null) {
|
|
4149
|
+
return new Date(value).toLocaleString();
|
|
4150
|
+
}
|
|
4151
|
+
if (column.isDateTimeText && value !== null) {
|
|
4152
|
+
return new Date(value).toString();
|
|
4153
|
+
}
|
|
4154
|
+
// Si no se aplica ningún formato, retornar el valor original
|
|
4155
|
+
return value;
|
|
4156
|
+
}
|
|
4157
|
+
// =====================================================
|
|
4158
|
+
// Parametros de busqueda
|
|
4159
|
+
// =====================================================
|
|
4160
|
+
// Obtener los parámetros de consulta para la solicitud de datos
|
|
4161
|
+
getQueryParams() {
|
|
4162
|
+
const params = this.genericService.params({
|
|
4163
|
+
page: this.currentPage,
|
|
4164
|
+
limit: this.itemsPerPage,
|
|
4165
|
+
sort: {
|
|
4166
|
+
column: this.sortColumn,
|
|
4167
|
+
direction: this.sortDirection,
|
|
4168
|
+
},
|
|
4169
|
+
filters: this.filters,
|
|
4170
|
+
defaultFilters: this.defaultFilters,
|
|
4171
|
+
});
|
|
4172
|
+
if (this.searchQuery && this.searchQuery.trim() !== '') {
|
|
4173
|
+
const baseSearchKeys = this.columns
|
|
4174
|
+
.filter(col => col.isSearchable)
|
|
4175
|
+
.map(col => col.key);
|
|
4176
|
+
const extraSearchKeys = this.columns
|
|
4177
|
+
.flatMap(col => col.extraSearchFields || []);
|
|
4178
|
+
const allSearchKeys = [...baseSearchKeys, ...extraSearchKeys];
|
|
4179
|
+
params['search'] = this.searchQuery;
|
|
4180
|
+
params['searchFields'] = allSearchKeys;
|
|
4181
|
+
}
|
|
4182
|
+
return params;
|
|
4183
|
+
}
|
|
4184
|
+
// =====================================================
|
|
4185
|
+
// Ordenamiento
|
|
4186
|
+
// =====================================================
|
|
4187
|
+
// Numero de pagina
|
|
4188
|
+
getRowNumber(index) {
|
|
4189
|
+
return this.startIndex + index + 1;
|
|
4190
|
+
}
|
|
4191
|
+
// Ordenamiento de columnas
|
|
4192
|
+
onSort(column) {
|
|
4193
|
+
// Evitar múltiples solicitudes de ordenamiento simultáneas
|
|
4194
|
+
if (this.isLoading('sort')) {
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
if (!column.sortable)
|
|
4198
|
+
return;
|
|
4199
|
+
// Guardar la columna que está siendo ordenada
|
|
4200
|
+
this.sortingColumn = column.key;
|
|
4201
|
+
// Verificamos si estamos tratando con la misma columna que el último ordenamiento
|
|
4202
|
+
const currentSortKey = this.converterService.getSortKey(this.sortColumn);
|
|
4203
|
+
const columnSortKey = this.converterService.getSortKey(column.key);
|
|
4204
|
+
// Sort direction logic
|
|
4205
|
+
if (currentSortKey === columnSortKey) {
|
|
4206
|
+
// Alternar la dirección del ordenamiento
|
|
4207
|
+
if (this.sortDirection === 'asc') {
|
|
4208
|
+
this.sortDirection = 'desc';
|
|
4209
|
+
}
|
|
4210
|
+
else if (this.sortDirection === 'desc') {
|
|
4211
|
+
this.sortDirection = 'none';
|
|
4212
|
+
}
|
|
4213
|
+
else {
|
|
4214
|
+
this.sortDirection = 'asc';
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
else {
|
|
4218
|
+
// Si se selecciona una nueva columna para ordenar, iniciar con orden ascendente
|
|
4219
|
+
this.sortColumn = column.key;
|
|
4220
|
+
this.sortDirection = 'asc';
|
|
4221
|
+
}
|
|
4222
|
+
this.loadData('sort');
|
|
4223
|
+
}
|
|
4224
|
+
// Obtener la dirección de ordenamiento
|
|
4225
|
+
getSortKey(value) {
|
|
4226
|
+
return this.converterService.getSortKey(value);
|
|
4227
|
+
}
|
|
4228
|
+
// Manejar el evento de tecla presionada en la ordenación
|
|
4229
|
+
onSortKeyPress(event, column) {
|
|
4230
|
+
// Si ya hay una ordenación en progreso, no permitir otra
|
|
4231
|
+
if (this.isLoading('sort')) {
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
4235
|
+
event.preventDefault();
|
|
4236
|
+
this.onSort(column);
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
// =====================================================
|
|
4240
|
+
// Search
|
|
4241
|
+
// =====================================================
|
|
4242
|
+
// Search functionality
|
|
4243
|
+
onSearch() {
|
|
4244
|
+
this.currentPage = 1;
|
|
4245
|
+
this.loadData('search');
|
|
4246
|
+
}
|
|
4247
|
+
// Selector de items por página
|
|
4248
|
+
onItemsPerPageChange() {
|
|
4249
|
+
this.currentPage = 1;
|
|
4250
|
+
this.loadData('itemsPerPage');
|
|
4251
|
+
}
|
|
4252
|
+
// =====================================================
|
|
4253
|
+
// Paginator
|
|
4254
|
+
// =====================================================
|
|
4255
|
+
// Generar numeros de paginación
|
|
4256
|
+
generatePagination() {
|
|
4257
|
+
const totalPages = this.totalPages;
|
|
4258
|
+
const currentPage = this.currentPage;
|
|
4259
|
+
// Ver los 5 numeros de paginación
|
|
4260
|
+
const maxPagesToShow = 3;
|
|
4261
|
+
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
|
4262
|
+
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
|
4263
|
+
// Adjust if we're near the end
|
|
4264
|
+
if (endPage - startPage + 1 < maxPagesToShow) {
|
|
4265
|
+
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
|
4266
|
+
}
|
|
4267
|
+
this.pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
|
|
4268
|
+
}
|
|
4269
|
+
// Paginar
|
|
4270
|
+
handlePageChange(page) {
|
|
4271
|
+
this.currentPage = page;
|
|
4272
|
+
this.loadData('pagination');
|
|
4273
|
+
}
|
|
4274
|
+
// =====================================================
|
|
4275
|
+
// Opciones
|
|
4276
|
+
// =====================================================
|
|
4277
|
+
// Método para manejar el clic de un botón
|
|
4278
|
+
onButtonClick(button, element) {
|
|
4279
|
+
// Ejecuta la acción del padre si está definida
|
|
4280
|
+
if (button.clicked) {
|
|
4281
|
+
button.clicked(element);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
// Método para obtener un tooltip
|
|
4285
|
+
getTooltip(tooltip, data) {
|
|
4286
|
+
if (typeof tooltip === 'function') {
|
|
4287
|
+
return tooltip(data);
|
|
4288
|
+
}
|
|
4289
|
+
return tooltip ?? '';
|
|
4290
|
+
}
|
|
4291
|
+
// Método para obtener un icono
|
|
4292
|
+
getIcon(icon, data) {
|
|
4293
|
+
if (typeof icon === 'function') {
|
|
4294
|
+
return icon(data);
|
|
4295
|
+
}
|
|
4296
|
+
return icon;
|
|
4297
|
+
}
|
|
4298
|
+
// Evaluar si un botón está deshabilitado
|
|
4299
|
+
getDisabled(option, data) {
|
|
4300
|
+
if (typeof option.disabled === 'function') {
|
|
4301
|
+
return option.disabled(data);
|
|
4302
|
+
}
|
|
4303
|
+
return !!option.disabled;
|
|
4304
|
+
}
|
|
4305
|
+
// Evaluar si un botón es visible
|
|
4306
|
+
getIsVisible(option, data) {
|
|
4307
|
+
if (typeof option.isVisible === 'function') {
|
|
4308
|
+
return option.isVisible(data);
|
|
4309
|
+
}
|
|
4310
|
+
// Si no se define, por defecto es visible
|
|
4311
|
+
return option.isVisible !== false;
|
|
4312
|
+
}
|
|
4313
|
+
// Método para obtener la clase CSS de un botón
|
|
4314
|
+
mergeNgClasses(optionNgClass, data) {
|
|
4315
|
+
const baseClass = {
|
|
4316
|
+
'min-w-auto p-1! pl-2! pr-2!': true
|
|
4317
|
+
};
|
|
4318
|
+
let dynamicClass = {};
|
|
4319
|
+
if (typeof optionNgClass === 'function') {
|
|
4320
|
+
dynamicClass = optionNgClass(data);
|
|
4321
|
+
}
|
|
4322
|
+
else {
|
|
4323
|
+
dynamicClass = optionNgClass ?? {};
|
|
4324
|
+
}
|
|
4325
|
+
return {
|
|
4326
|
+
...baseClass,
|
|
4327
|
+
...(typeof dynamicClass === 'string' ? { [dynamicClass]: true } : dynamicClass)
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
// =====================================================
|
|
4331
|
+
// Parametros de carga
|
|
4332
|
+
// =====================================================
|
|
4333
|
+
// Método para verificar si un estado específico está cargando
|
|
4334
|
+
isLoading(state) {
|
|
4335
|
+
return this.loadingStates[state] === 'loading';
|
|
4336
|
+
}
|
|
4337
|
+
// Método para verificar si cualquier estado está cargando
|
|
4338
|
+
isAnyLoading() {
|
|
4339
|
+
return Object.values(this.loadingStates).some(state => state === 'loading');
|
|
4340
|
+
}
|
|
4341
|
+
// Método para actualizar un estado de carga
|
|
4342
|
+
setLoadingState(state, value) {
|
|
4343
|
+
if (state === 'aditionalButtons') {
|
|
4344
|
+
if (typeof value === 'string') {
|
|
4345
|
+
// Evita asignar un string directamente si se espera un objeto
|
|
4346
|
+
console.warn(`No puedes asignar '${value}' directamente a aditionalButtons. Usa setAditionalButtonLoading en su lugar.`);
|
|
4347
|
+
}
|
|
4348
|
+
else {
|
|
4349
|
+
this.loadingStates.aditionalButtons = value;
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
else {
|
|
4353
|
+
this.loadingStates[state] = value;
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
// Activar loading
|
|
4357
|
+
setAditionalButtonLoading(buttonType, id) {
|
|
4358
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
4359
|
+
this.loadingStates.aditionalButtons[key] = 'loading';
|
|
4360
|
+
}
|
|
4361
|
+
// Limpiar loading
|
|
4362
|
+
clearAditionalButtonLoading(buttonType, id) {
|
|
4363
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
4364
|
+
this.loadingStates.aditionalButtons[key] = 'idle';
|
|
4365
|
+
}
|
|
4366
|
+
// Verificar loading
|
|
4367
|
+
isAditionalButtonLoading(buttonType, id) {
|
|
4368
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
4369
|
+
return this.loadingStates.aditionalButtons[key] === 'loading';
|
|
4370
|
+
}
|
|
4371
|
+
// ==================================================
|
|
4372
|
+
// Expandir filas
|
|
4373
|
+
// ==================================================
|
|
4374
|
+
// Método para verificar si la tabla tiene filas expandibles
|
|
4375
|
+
hasExpandable() {
|
|
4376
|
+
return this.columns.some(col => typeof col.expandTemplate === 'function');
|
|
4377
|
+
}
|
|
4378
|
+
// Método para verificar si una fila está expandida
|
|
4379
|
+
toggleRow(row) {
|
|
4380
|
+
if (this.expandedRows.has(row)) {
|
|
4381
|
+
this.expandedRows.delete(row);
|
|
4382
|
+
}
|
|
4383
|
+
else {
|
|
4384
|
+
this.expandedRows.add(row);
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
// Método para obtener contenido expandido de una fila
|
|
4388
|
+
getExpandedContent(row) {
|
|
4389
|
+
const expandableColumn = this.columns.find(col => typeof col.expandTemplate === 'function');
|
|
4390
|
+
return expandableColumn?.expandTemplate?.(row) ?? '';
|
|
4391
|
+
}
|
|
4392
|
+
// Método para obtener el estado de expansión de una fila
|
|
4393
|
+
getExpansionState(row) {
|
|
4394
|
+
return this.expandedRows.has(row) ? 'expanded' : 'collapsed';
|
|
4395
|
+
}
|
|
4396
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JTableComponent, deps: [{ token: i1$1.CurrencyPipe }, { token: JGenericService }, { token: JAlertToastService }, { token: ConverterService }, { token: JCalendarService }], target: i0.ɵɵFactoryTarget.Component });
|
|
4397
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.7", type: JTableComponent, isStandalone: true, selector: "JCrudTable", inputs: { endpoint: "endpoint", columns: "columns", defaultFilters: "defaultFilters", isPaginator: "isPaginator", isSearch: "isSearch", itemsPerPageOptions: "itemsPerPageOptions", searchPlaceholder: "searchPlaceholder", checked: "checked", checkedValues: "checkedValues", checkedTitles: "checkedTitles", filtersButton: "filtersButton", filtersSelect: "filtersSelect", optionsTable: "optionsTable" }, outputs: { dataLoaded: "dataLoaded" }, ngImport: i0, template: "<div class=\"w-full mb-3\">\r\n\r\n @if (isSearch) {\r\n <JFilter \r\n [columns]=\"columns\" \r\n [(searchQuery)]=\"searchQuery\" \r\n (search)=\"onSearch()\" \r\n [searchPlaceholder]=\"searchPlaceholder\"\r\n [(itemsPerPage)]=\"itemsPerPage\"\r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n (onItemsPerPageChangeEvent)=\"onItemsPerPageChange()\"\r\n [isLoadingSearch]=\"isLoading('search')\" \r\n [isLoadingPerPage]=\"isLoading('itemsPerPage')\" \r\n [isLoadingAditionalButtons]=\"loadingStates.aditionalButtons\"\r\n (clearFilters)=\"onClearFilters($event)\"\r\n [filtersButton]=\"filtersButton\"\r\n [filtersSelect]=\"filtersSelect\"\r\n />\r\n }\r\n\r\n <!-- Table -->\r\n <div class=\"relative border border-border dark:border-dark-border rounded\">\r\n <div class=\"overflow-x-auto rounded scroll-element pr-0!\">\r\n <table class=\"min-w-full bg-background dark:bg-dark-background rounded\">\r\n <thead class=\"bg-primary dark:bg-dark-primary text-white dark:text-white select-none\">\r\n <tr>\r\n\r\n <!-- Expandable column -->\r\n @if (hasExpandable()) {\r\n <th class=\"px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\"></th>\r\n }\r\n\r\n <!-- Counter column -->\r\n <th\r\n class=\"px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\">\r\n N\u00B0\r\n </th>\r\n\r\n <!-- Data columns -->\r\n @for (column of columns; track column.key) {\r\n @if (column.visible) {\r\n <th\r\n class=\"px-2 py-2 text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\"\r\n (click)=\"onSort(column)\" (keydown)=\"onSortKeyPress($event, column)\" tabindex=\"0\"\r\n [class.pointer-events-none]=\"isLoading('sort') && column.key !== sortingColumn\">\r\n \r\n <div class=\"flex gap-1 items-center rounded p-3 justify-center\"\r\n [ngClass]=\"{'hover:bg-dark-primary dark:hover:bg-primary transition duration-300 ease-in-out border border-border/10 dark:border-dark-border' : column.sortable, 'text-center' : column.label === 'ID'}\"\r\n [class.cursor-pointer]=\"column.sortable\">\r\n {{ column.label }}\r\n \r\n @if (column.sortable) {\r\n <div class=\"ml-1\">\r\n <!-- Mostrar icono de carga si esta columna est\u00E1 siendo ordenada -->\r\n @if (isLoading('sort') && column.key === sortingColumn) {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"16\" class=\"text-white animate-spin\"></lucide-icon>\r\n } @else if (getSortKey(sortColumn) !== column.key || sortDirection === 'none') {\r\n <lucide-icon [name]=\"icons['sortDefault']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n } @else if (sortDirection === 'asc') {\r\n <lucide-icon [name]=\"icons['sortAsc']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n } @else {\r\n <lucide-icon [name]=\"icons['sortDesc']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n\r\n <!-- Actions column - Sticky header -->\r\n <th\r\n class=\"!sticky !right-0 bg-primary dark:bg-dark-primary min-w-[50px] px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold shadow-[-4px_0_5px_rgba(0,0,0,0.1)]\">\r\n\r\n @if (checked) {\r\n <JCheckbox onKeyPress\r\n type=\"switch\"\r\n [title]=\"titleChecked\"\r\n [isChecked]=\"isChecked\"\r\n (click)=\"checkActiveInactive(isChecked)\"\r\n [isLoading]=\"isLoading('checked')\"\r\n />\r\n } @else {\r\n <span class=\"text-[10px] opacity-80 border border-border dark:border-dark-border p-2 pl-3 pr-3 rounded-full\">Opciones</span>\r\n }\r\n <!-- Pseudoelemento para el borde central -->\r\n <div class=\"absolute top-[15px] bottom-[15px] left-0 w-[1px] bg-border dark:bg-dark-border\"></div>\r\n </th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @if (isLoading('initialLoad') && displayData.length === 0) {\r\n\r\n <!-- Loading state -->\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\"\r\n class=\"px-4 py-8 text-center text-sm text-black dark:text-white\">\r\n <div class=\"flex flex-col gap-3 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"30\" class=\"text-primary animate-spin\"></lucide-icon>\r\n <p>Cargando datos...</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else if (displayData.length > 0) {\r\n\r\n <!-- Data rows -->\r\n @for (item of displayData; track $index) {\r\n <tr onKeyPress class=\"hover:bg-primary/10 dark:hover:bg-dark-primary/10\" (click)=\"toggleRow(item)\" [ngClass]=\"{ 'cursor-pointer': hasExpandable() }\">\r\n\r\n <!-- Expandable column -->\r\n @if (hasExpandable()) {\r\n <td class=\"px-4 py-2 h-[50px] text-center text-xs border-b border-border dark:border-dark-border text-black dark:text-white\">\r\n <lucide-icon [name]=\"icons['chevronRight']\" size=\"16\" class=\"transition-transform duration-300\"\r\n [class]=\"expandedRows.has(item) ? 'rotate-90' : ''\" />\r\n </td>\r\n }\r\n\r\n <!-- Counter column -->\r\n <td class=\"px-4 py-2 h-[50px] text-center text-xs border-b border-border dark:border-dark-border text-black dark:text-white\">\r\n {{ getRowNumber($index) }}\r\n </td>\r\n\r\n <!-- Data columns -->\r\n @for (column of columns; track column.key) {\r\n @if (column.visible) {\r\n <td\r\n class=\"px-4 py-2 h-[50px] text-xs border-b border-border dark:border-dark-border text-black dark:text-white\"\r\n [ngClass]=\"{'text-center': column.label === 'ID'}\"\r\n [ngStyle]=\"column.styles\">\r\n \r\n <span [ngClass]=\"{'p-2 pl-4 pr-4 border border-border dark:border-dark-border rounded-full': column.isDecorator}\">\r\n @if (isBoolean(getValue(item, column))) {\r\n <JCheckbox\r\n [item]=\"item\"\r\n [column]=\"column\"\r\n [getValue]=\"getValue.bind(this)\"\r\n [onCheckboxChange]=\"onCheckboxChange.bind(this)\"\r\n [iconSize]=\"20\"\r\n [disabled]=\"column?.isDisaled\"\r\n />\r\n } @else {\r\n {{ getValue(item, column) }}\r\n }\r\n\r\n </span>\r\n </td>\r\n }\r\n }\r\n\r\n <!-- Actions column - Sticky cell -->\r\n <td\r\n class=\"!sticky !right-0 bg-background dark:bg-dark-background text-center whitespace-nowrap text-sm border-b border-border dark:border-dark-border shadow-[-4px_0_5px_rgba(0,0,0,0.1)] relative\">\r\n <div class=\"flex justify-center items-center space-x-2 min-w-[50px] px-4 py-2 h-[50px]\">\r\n @for (option of optionsTable; track $index) {\r\n @if (getIsVisible(option, item)) {\r\n <JButton onKeyPress \r\n [icon]=\"getIcon(option.icon, item)\"\r\n [iconSize]=\"20\" \r\n (clicked)=\"$event.stopPropagation(); onButtonClick(option, item)\"\r\n [tooltip]=\"getTooltip(option.tooltip ?? '', item)\"\r\n [tooltipPosition]=\"option.tooltipPosition ?? 'top'\"\r\n [disabled]=\"getDisabled(option, item)\"\r\n [isLoading]=\"isAditionalButtonLoading(option.type ?? '', item?.id_student_course)\"\r\n [classes]=\"option.classes ?? ''\"\r\n [ngClasses]=\"mergeNgClasses(option.ngClass, item)\" \r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Pseudoelemento para el borde central -->\r\n <div class=\"absolute top-[15px] bottom-[15px] left-0 w-[1px] bg-border dark:bg-dark-border\"></div>\r\n </td>\r\n </tr>\r\n\r\n @if (hasExpandable()) {\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\">\r\n <div [@slideToggle]=\"getExpansionState(item)\" [ngClass]=\"{'border-b border-border dark:border-dark-border': expandedRows.has(item)}\"\r\n class=\"overflow-hidden transition-all duration-300 ease-in-out\"\r\n >\r\n <div class=\"w-full border border-gray-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-3\">\r\n <div [innerHTML]=\"getExpandedContent(item)\"></div>\r\n </div> \r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n } @else if (!isLoading('pagination')) {\r\n <!-- Empty state -->\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\"\r\n class=\"px-4 py-8 text-center text-sm text-black dark:text-white\">\r\n No hay datos disponibles\r\n </td>\r\n </tr>\r\n }\r\n\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n\r\n @if (isPaginator) {\r\n <JPaginator \r\n [currentPage]=\"currentPage\" \r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n [itemsPerPage]=\"itemsPerPage\"\r\n [totalItems]=\"totalItems\" \r\n [pages]=\"pages\" \r\n (pageChange)=\"handlePageChange($event)\" \r\n [isLoading]=\"isLoading('pagination')\"\r\n />\r\n }\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: JPaginatorComponent, selector: "JPaginator", inputs: ["isLoading", "currentPage", "itemsPerPageOptions", "itemsPerPage", "totalItems", "pages"], outputs: ["pageChange"] }, { kind: "component", type: JFilterComponent, selector: "JFilter", inputs: ["isLoadingSearch", "isLoadingPerPage", "isLoadingAditionalButtons", "searchPlaceholder", "columns", "itemsPerPageOptions", "itemsPerPage", "searchQuery", "filtersButton", "filtersSelect"], outputs: ["search", "itemsPerPageChange", "searchQueryChange", "onItemsPerPageChangeEvent", "clearFilters"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i2.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }, { kind: "component", type: JCheckboxComponent, selector: "JCheckbox", inputs: ["type", "icon", "iconSize", "disabled", "isLoading", "classes", "title", "isChecked", "item", "column", "getValue", "onCheckboxChange", "toggleSwitch"] }], animations: [
|
|
4398
|
+
trigger('slideToggle', [
|
|
4399
|
+
state('collapsed', style({
|
|
4400
|
+
height: '0',
|
|
4401
|
+
opacity: 0,
|
|
4402
|
+
overflow: 'hidden',
|
|
4403
|
+
})),
|
|
4404
|
+
state('expanded', style({
|
|
4405
|
+
height: '*',
|
|
4406
|
+
opacity: 1,
|
|
4407
|
+
})),
|
|
4408
|
+
transition('collapsed <=> expanded', [
|
|
4409
|
+
animate('0.3s ease-in-out')
|
|
4410
|
+
])
|
|
4411
|
+
])
|
|
4412
|
+
] });
|
|
4413
|
+
}
|
|
4414
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JTableComponent, decorators: [{
|
|
4415
|
+
type: Component,
|
|
4416
|
+
args: [{ selector: 'JCrudTable', standalone: true, imports: [CommonModule, FormsModule, JPaginatorComponent, JFilterComponent, LucideAngularModule, JButtonComponent, JCheckboxComponent], animations: [
|
|
4417
|
+
trigger('slideToggle', [
|
|
4418
|
+
state('collapsed', style({
|
|
4419
|
+
height: '0',
|
|
4420
|
+
opacity: 0,
|
|
4421
|
+
overflow: 'hidden',
|
|
4422
|
+
})),
|
|
4423
|
+
state('expanded', style({
|
|
4424
|
+
height: '*',
|
|
4425
|
+
opacity: 1,
|
|
4426
|
+
})),
|
|
4427
|
+
transition('collapsed <=> expanded', [
|
|
4428
|
+
animate('0.3s ease-in-out')
|
|
4429
|
+
])
|
|
4430
|
+
])
|
|
4431
|
+
], template: "<div class=\"w-full mb-3\">\r\n\r\n @if (isSearch) {\r\n <JFilter \r\n [columns]=\"columns\" \r\n [(searchQuery)]=\"searchQuery\" \r\n (search)=\"onSearch()\" \r\n [searchPlaceholder]=\"searchPlaceholder\"\r\n [(itemsPerPage)]=\"itemsPerPage\"\r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n (onItemsPerPageChangeEvent)=\"onItemsPerPageChange()\"\r\n [isLoadingSearch]=\"isLoading('search')\" \r\n [isLoadingPerPage]=\"isLoading('itemsPerPage')\" \r\n [isLoadingAditionalButtons]=\"loadingStates.aditionalButtons\"\r\n (clearFilters)=\"onClearFilters($event)\"\r\n [filtersButton]=\"filtersButton\"\r\n [filtersSelect]=\"filtersSelect\"\r\n />\r\n }\r\n\r\n <!-- Table -->\r\n <div class=\"relative border border-border dark:border-dark-border rounded\">\r\n <div class=\"overflow-x-auto rounded scroll-element pr-0!\">\r\n <table class=\"min-w-full bg-background dark:bg-dark-background rounded\">\r\n <thead class=\"bg-primary dark:bg-dark-primary text-white dark:text-white select-none\">\r\n <tr>\r\n\r\n <!-- Expandable column -->\r\n @if (hasExpandable()) {\r\n <th class=\"px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\"></th>\r\n }\r\n\r\n <!-- Counter column -->\r\n <th\r\n class=\"px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\">\r\n N\u00B0\r\n </th>\r\n\r\n <!-- Data columns -->\r\n @for (column of columns; track column.key) {\r\n @if (column.visible) {\r\n <th\r\n class=\"px-2 py-2 text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold\"\r\n (click)=\"onSort(column)\" (keydown)=\"onSortKeyPress($event, column)\" tabindex=\"0\"\r\n [class.pointer-events-none]=\"isLoading('sort') && column.key !== sortingColumn\">\r\n \r\n <div class=\"flex gap-1 items-center rounded p-3 justify-center\"\r\n [ngClass]=\"{'hover:bg-dark-primary dark:hover:bg-primary transition duration-300 ease-in-out border border-border/10 dark:border-dark-border' : column.sortable, 'text-center' : column.label === 'ID'}\"\r\n [class.cursor-pointer]=\"column.sortable\">\r\n {{ column.label }}\r\n \r\n @if (column.sortable) {\r\n <div class=\"ml-1\">\r\n <!-- Mostrar icono de carga si esta columna est\u00E1 siendo ordenada -->\r\n @if (isLoading('sort') && column.key === sortingColumn) {\r\n <lucide-icon [name]=\"icons['loading']\" size=\"16\" class=\"text-white animate-spin\"></lucide-icon>\r\n } @else if (getSortKey(sortColumn) !== column.key || sortDirection === 'none') {\r\n <lucide-icon [name]=\"icons['sortDefault']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n } @else if (sortDirection === 'asc') {\r\n <lucide-icon [name]=\"icons['sortAsc']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n } @else {\r\n <lucide-icon [name]=\"icons['sortDesc']\" size=\"16\" class=\"text-white\"></lucide-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </th>\r\n }\r\n }\r\n\r\n <!-- Actions column - Sticky header -->\r\n <th\r\n class=\"!sticky !right-0 bg-primary dark:bg-dark-primary min-w-[50px] px-4 py-2 text-center text-xs font-medium text-white uppercase tracking-wider border-b border-border dark:border-dark-border font-bold shadow-[-4px_0_5px_rgba(0,0,0,0.1)]\">\r\n\r\n @if (checked) {\r\n <JCheckbox onKeyPress\r\n type=\"switch\"\r\n [title]=\"titleChecked\"\r\n [isChecked]=\"isChecked\"\r\n (click)=\"checkActiveInactive(isChecked)\"\r\n [isLoading]=\"isLoading('checked')\"\r\n />\r\n } @else {\r\n <span class=\"text-[10px] opacity-80 border border-border dark:border-dark-border p-2 pl-3 pr-3 rounded-full\">Opciones</span>\r\n }\r\n <!-- Pseudoelemento para el borde central -->\r\n <div class=\"absolute top-[15px] bottom-[15px] left-0 w-[1px] bg-border dark:bg-dark-border\"></div>\r\n </th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @if (isLoading('initialLoad') && displayData.length === 0) {\r\n\r\n <!-- Loading state -->\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\"\r\n class=\"px-4 py-8 text-center text-sm text-black dark:text-white\">\r\n <div class=\"flex flex-col gap-3 items-center justify-center py-4\">\r\n <lucide-icon [name]=\"icons['loading']\" size=\"30\" class=\"text-primary animate-spin\"></lucide-icon>\r\n <p>Cargando datos...</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else if (displayData.length > 0) {\r\n\r\n <!-- Data rows -->\r\n @for (item of displayData; track $index) {\r\n <tr onKeyPress class=\"hover:bg-primary/10 dark:hover:bg-dark-primary/10\" (click)=\"toggleRow(item)\" [ngClass]=\"{ 'cursor-pointer': hasExpandable() }\">\r\n\r\n <!-- Expandable column -->\r\n @if (hasExpandable()) {\r\n <td class=\"px-4 py-2 h-[50px] text-center text-xs border-b border-border dark:border-dark-border text-black dark:text-white\">\r\n <lucide-icon [name]=\"icons['chevronRight']\" size=\"16\" class=\"transition-transform duration-300\"\r\n [class]=\"expandedRows.has(item) ? 'rotate-90' : ''\" />\r\n </td>\r\n }\r\n\r\n <!-- Counter column -->\r\n <td class=\"px-4 py-2 h-[50px] text-center text-xs border-b border-border dark:border-dark-border text-black dark:text-white\">\r\n {{ getRowNumber($index) }}\r\n </td>\r\n\r\n <!-- Data columns -->\r\n @for (column of columns; track column.key) {\r\n @if (column.visible) {\r\n <td\r\n class=\"px-4 py-2 h-[50px] text-xs border-b border-border dark:border-dark-border text-black dark:text-white\"\r\n [ngClass]=\"{'text-center': column.label === 'ID'}\"\r\n [ngStyle]=\"column.styles\">\r\n \r\n <span [ngClass]=\"{'p-2 pl-4 pr-4 border border-border dark:border-dark-border rounded-full': column.isDecorator}\">\r\n @if (isBoolean(getValue(item, column))) {\r\n <JCheckbox\r\n [item]=\"item\"\r\n [column]=\"column\"\r\n [getValue]=\"getValue.bind(this)\"\r\n [onCheckboxChange]=\"onCheckboxChange.bind(this)\"\r\n [iconSize]=\"20\"\r\n [disabled]=\"column?.isDisaled\"\r\n />\r\n } @else {\r\n {{ getValue(item, column) }}\r\n }\r\n\r\n </span>\r\n </td>\r\n }\r\n }\r\n\r\n <!-- Actions column - Sticky cell -->\r\n <td\r\n class=\"!sticky !right-0 bg-background dark:bg-dark-background text-center whitespace-nowrap text-sm border-b border-border dark:border-dark-border shadow-[-4px_0_5px_rgba(0,0,0,0.1)] relative\">\r\n <div class=\"flex justify-center items-center space-x-2 min-w-[50px] px-4 py-2 h-[50px]\">\r\n @for (option of optionsTable; track $index) {\r\n @if (getIsVisible(option, item)) {\r\n <JButton onKeyPress \r\n [icon]=\"getIcon(option.icon, item)\"\r\n [iconSize]=\"20\" \r\n (clicked)=\"$event.stopPropagation(); onButtonClick(option, item)\"\r\n [tooltip]=\"getTooltip(option.tooltip ?? '', item)\"\r\n [tooltipPosition]=\"option.tooltipPosition ?? 'top'\"\r\n [disabled]=\"getDisabled(option, item)\"\r\n [isLoading]=\"isAditionalButtonLoading(option.type ?? '', item?.id_student_course)\"\r\n [classes]=\"option.classes ?? ''\"\r\n [ngClasses]=\"mergeNgClasses(option.ngClass, item)\" \r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Pseudoelemento para el borde central -->\r\n <div class=\"absolute top-[15px] bottom-[15px] left-0 w-[1px] bg-border dark:bg-dark-border\"></div>\r\n </td>\r\n </tr>\r\n\r\n @if (hasExpandable()) {\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\">\r\n <div [@slideToggle]=\"getExpansionState(item)\" [ngClass]=\"{'border-b border-border dark:border-dark-border': expandedRows.has(item)}\"\r\n class=\"overflow-hidden transition-all duration-300 ease-in-out\"\r\n >\r\n <div class=\"w-full border border-gray-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-3\">\r\n <div [innerHTML]=\"getExpandedContent(item)\"></div>\r\n </div> \r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n } @else if (!isLoading('pagination')) {\r\n <!-- Empty state -->\r\n <tr>\r\n <td [attr.colspan]=\"getVisibleColumnsCount() + 3\"\r\n class=\"px-4 py-8 text-center text-sm text-black dark:text-white\">\r\n No hay datos disponibles\r\n </td>\r\n </tr>\r\n }\r\n\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n\r\n @if (isPaginator) {\r\n <JPaginator \r\n [currentPage]=\"currentPage\" \r\n [itemsPerPageOptions]=\"itemsPerPageOptions\" \r\n [itemsPerPage]=\"itemsPerPage\"\r\n [totalItems]=\"totalItems\" \r\n [pages]=\"pages\" \r\n (pageChange)=\"handlePageChange($event)\" \r\n [isLoading]=\"isLoading('pagination')\"\r\n />\r\n }\r\n</div>" }]
|
|
4432
|
+
}], ctorParameters: () => [{ type: i1$1.CurrencyPipe }, { type: JGenericService }, { type: JAlertToastService }, { type: ConverterService }, { type: JCalendarService }], propDecorators: { dataLoaded: [{
|
|
4433
|
+
type: Output
|
|
4434
|
+
}], endpoint: [{
|
|
4435
|
+
type: Input
|
|
4436
|
+
}], columns: [{
|
|
4437
|
+
type: Input
|
|
4438
|
+
}], defaultFilters: [{
|
|
4439
|
+
type: Input
|
|
4440
|
+
}], isPaginator: [{
|
|
4441
|
+
type: Input
|
|
4442
|
+
}], isSearch: [{
|
|
4443
|
+
type: Input
|
|
4444
|
+
}], itemsPerPageOptions: [{
|
|
4445
|
+
type: Input
|
|
4446
|
+
}], searchPlaceholder: [{
|
|
4447
|
+
type: Input
|
|
4448
|
+
}], checked: [{
|
|
4449
|
+
type: Input
|
|
4450
|
+
}], checkedValues: [{
|
|
4451
|
+
type: Input
|
|
4452
|
+
}], checkedTitles: [{
|
|
4453
|
+
type: Input
|
|
4454
|
+
}], filtersButton: [{
|
|
4455
|
+
type: Input
|
|
4456
|
+
}], filtersSelect: [{
|
|
4457
|
+
type: Input
|
|
4458
|
+
}], optionsTable: [{
|
|
4459
|
+
type: Input
|
|
4460
|
+
}] } });
|
|
4461
|
+
|
|
4462
|
+
hljs.registerLanguage('ts', typescript);
|
|
4463
|
+
hljs.registerLanguage('js', javascript);
|
|
4464
|
+
hljs.registerLanguage('bash', bash);
|
|
4465
|
+
hljs.registerLanguage('css', css);
|
|
4466
|
+
hljs.registerLanguage('scss', scss);
|
|
4467
|
+
hljs.registerLanguage('html', html);
|
|
4468
|
+
hljs.registerLanguage('json', json);
|
|
4469
|
+
class JCodeBlockComponent {
|
|
4470
|
+
icons = {
|
|
4471
|
+
copy: Copy,
|
|
4472
|
+
check: Check
|
|
4473
|
+
};
|
|
4474
|
+
copied = false;
|
|
4475
|
+
code = '';
|
|
4476
|
+
language = 'ts';
|
|
4477
|
+
codeRef;
|
|
4478
|
+
highlightedCode = '';
|
|
4479
|
+
ngAfterViewInit() {
|
|
4480
|
+
this.codeRef.nativeElement.textContent = this.code;
|
|
4481
|
+
hljs.highlightElement(this.codeRef.nativeElement);
|
|
4482
|
+
}
|
|
4483
|
+
copyCode() {
|
|
4484
|
+
navigator.clipboard.writeText(this.code).then(() => {
|
|
4485
|
+
console.log('Copiado al portapapeles');
|
|
4486
|
+
});
|
|
4487
|
+
this.copied = true;
|
|
4488
|
+
setTimeout(() => (this.copied = false), 2000);
|
|
4489
|
+
}
|
|
4490
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCodeBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4491
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.7", type: JCodeBlockComponent, isStandalone: true, selector: "JCodeBlock", inputs: { code: "code", language: "language" }, viewQueries: [{ propertyName: "codeRef", first: true, predicate: ["codeRef"], descendants: true }], ngImport: i0, template: "<div class=\"relative rounded bg-foreground pl-5 pt-3 m-0 border border-border dark:border-dark-border\">\r\n <div class=\"absolute top-4 right-4\">\r\n <JButton (clicked)=\"copyCode()\" [icon]=\"icons['copy']\" [iconChange]=\"icons['check']\"\r\n [isChangeIcon]=\"copied\" [iconSize]=\"15\" tooltip=\"Copiar\" classes=\"w-[25px] h-[25px] text-white\" />\r\n </div>\r\n <pre class=\"p-5\">\r\n <code #codeRef class=\"language-{{language}}\"></code>\r\n </pre>\r\n</div>", styles: ["pre,code{margin:0!important;padding:0!important;background:transparent!important;line-height:1.5;font-size:.85rem}.hljs{background:transparent!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: JButtonComponent, selector: "JButton", inputs: ["type", "disabled", "isLoading", "icon", "iconSize", "text", "isChangeIcon", "iconChange", "tooltip", "tooltipPosition", "classes", "ngClasses"], outputs: ["clicked"] }] });
|
|
4492
|
+
}
|
|
4493
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCodeBlockComponent, decorators: [{
|
|
4494
|
+
type: Component,
|
|
4495
|
+
args: [{ selector: 'JCodeBlock', imports: [CommonModule, JButtonComponent], template: "<div class=\"relative rounded bg-foreground pl-5 pt-3 m-0 border border-border dark:border-dark-border\">\r\n <div class=\"absolute top-4 right-4\">\r\n <JButton (clicked)=\"copyCode()\" [icon]=\"icons['copy']\" [iconChange]=\"icons['check']\"\r\n [isChangeIcon]=\"copied\" [iconSize]=\"15\" tooltip=\"Copiar\" classes=\"w-[25px] h-[25px] text-white\" />\r\n </div>\r\n <pre class=\"p-5\">\r\n <code #codeRef class=\"language-{{language}}\"></code>\r\n </pre>\r\n</div>", styles: ["pre,code{margin:0!important;padding:0!important;background:transparent!important;line-height:1.5;font-size:.85rem}.hljs{background:transparent!important;border-radius:0!important}\n"] }]
|
|
4496
|
+
}], propDecorators: { code: [{
|
|
4497
|
+
type: Input
|
|
4498
|
+
}], language: [{
|
|
4499
|
+
type: Input
|
|
4500
|
+
}], codeRef: [{
|
|
4501
|
+
type: ViewChild,
|
|
4502
|
+
args: ['codeRef']
|
|
4503
|
+
}] } });
|
|
4504
|
+
|
|
4505
|
+
// =======================================
|
|
4506
|
+
// Publicar libreria
|
|
4507
|
+
// =======================================
|
|
4508
|
+
// ng build tailjng
|
|
4509
|
+
// cd .\dist\tailjng\
|
|
4510
|
+
// npm publish --access public
|
|
242
4511
|
/*
|
|
243
4512
|
* Public API Surface of tailjng
|
|
244
4513
|
*/
|
|
4514
|
+
// export * from './lib/tailjng.service';
|
|
4515
|
+
// export * from './lib/tailjng.component';
|
|
4516
|
+
// Mode Toggle
|
|
245
4517
|
|
|
246
4518
|
/**
|
|
247
4519
|
* Generated bundle index. Do not edit.
|
|
248
4520
|
*/
|
|
249
4521
|
|
|
250
|
-
export {
|
|
4522
|
+
export { API_URL, CardComponent, JAlertDialogComponent, JAlertDialogService, JAlertToastComponent, JAlertToastService, JButtonComponent, JCheckboxComponent, JCodeBlockComponent, JDialogComponent, JDialogShared, JErrorHandlerService, JFilterComponent, JFormComponent, JFormShared, JGenericService, JHttpParamsService, JInputComponent, JLabelComponent, JModeToggleComponent, JPaginatorComponent, JSelectComponent, JTableComponent, JThemeComponent, JToggleRadioComponent, JTooltipModule };
|
|
251
4523
|
//# sourceMappingURL=tailjng.mjs.map
|