tailjng 0.0.1 → 0.0.3

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.
Files changed (44) hide show
  1. package/fesm2022/tailjng.mjs +4249 -29
  2. package/fesm2022/tailjng.mjs.map +1 -1
  3. package/lib/colors/colors.service.d.ts +16 -0
  4. package/lib/colors/theme/elements/theme.service.d.ts +15 -0
  5. package/lib/colors/theme/theme.component.d.ts +87 -0
  6. package/lib/components/alert-dialog/alert-dialog.component.d.ts +24 -0
  7. package/lib/components/alert-dialog/elements/alert-dialog.interface.d.ts +41 -0
  8. package/lib/components/alert-dialog/elements/alert-dialog.service.d.ts +24 -0
  9. package/lib/components/alert-toast/alert-toast.component.d.ts +27 -0
  10. package/lib/components/alert-toast/elements/alert-toast.interface.d.ts +47 -0
  11. package/lib/components/alert-toast/elements/alert-toast.service.d.ts +26 -0
  12. package/lib/components/button/button.component.d.ts +35 -0
  13. package/lib/components/checkbox/checkbox.component.d.ts +21 -0
  14. package/lib/components/code-block/code-block.component.d.ts +17 -0
  15. package/lib/components/crud/card-component/card.component.d.ts +91 -0
  16. package/lib/components/crud/filter-component/elements/filter.interface.d.ts +62 -0
  17. package/lib/components/crud/filter-component/filter.component.d.ts +54 -0
  18. package/lib/components/crud/form-component/components/content-form/content-form.component.d.ts +8 -0
  19. package/lib/components/crud/form-component/form.component.d.ts +29 -0
  20. package/lib/components/crud/paginator-component/paginator.component.d.ts +27 -0
  21. package/lib/components/crud/table-component/elements/table.interface.d.ts +65 -0
  22. package/lib/components/crud/table-component/table.component.d.ts +97 -0
  23. package/lib/components/dialog/dialog.component.d.ts +37 -0
  24. package/lib/components/input/input.component.d.ts +47 -0
  25. package/lib/components/label/label.component.d.ts +13 -0
  26. package/lib/components/mode-toggle/mode-toggle.component.d.ts +15 -0
  27. package/lib/components/select/option/option.component.d.ts +15 -0
  28. package/lib/components/select/select.component.d.ts +93 -0
  29. package/lib/components/toggle-radio/toggle-radio.component.d.ts +48 -0
  30. package/lib/http/api-url.d.ts +2 -0
  31. package/lib/http/converter.service.d.ts +21 -0
  32. package/lib/http/crud-generic.service.d.ts +86 -0
  33. package/lib/http/http-error.service.d.ts +24 -0
  34. package/lib/http/http-rest.service.d.ts +9 -0
  35. package/lib/http/interface/api-response.d.ts +20 -0
  36. package/lib/service/calendar.service.d.ts +26 -0
  37. package/lib/shared/dialog.shared.d.ts +9 -0
  38. package/lib/shared/form.shared.d.ts +28 -0
  39. package/package.json +1 -1
  40. package/public-api.d.ts +25 -3
  41. package/lib/tailjng.component.d.ts +0 -5
  42. package/lib/tailjng.service.d.ts +0 -6
  43. /package/lib/{tooltip → components/tooltip}/tooltip.directive.d.ts +0 -0
  44. /package/lib/{tooltip → components/tooltip}/tooltip.service.d.ts +0 -0
@@ -1,34 +1,61 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Component, TemplateRef, HostListener, Input, Directive } from '@angular/core';
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';
3
15
 
4
- class TailjngService {
5
- constructor() { }
6
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: TailjngService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: TailjngService, providedIn: 'root' });
16
+ class JModeToggleComponent {
17
+ platformId;
18
+ icons = {
19
+ sun: Sun,
20
+ moon: Moon
21
+ };
22
+ title = 'frontend';
23
+ theme = 'light';
24
+ constructor(platformId) {
25
+ this.platformId = platformId;
26
+ this.loadTheme();
27
+ }
28
+ // Guardar el tema en localStorage solo en el navegador y actualizar la variable
29
+ setTheme(theme) {
30
+ if (isPlatformBrowser(this.platformId)) {
31
+ localStorage.setItem('theme', theme);
32
+ }
33
+ this.theme = theme; // Actualizar la variable theme
34
+ document.documentElement.classList.toggle('dark', theme === 'dark');
35
+ }
36
+ // Cargar el tema solo si está en el navegador y asignarlo a la variable
37
+ loadTheme() {
38
+ if (isPlatformBrowser(this.platformId)) {
39
+ const storedTheme = localStorage.getItem('theme');
40
+ this.theme = storedTheme ? storedTheme : 'light'; // Asegurar que siempre haya un valor válido
41
+ document.documentElement.classList.toggle('dark', this.theme === 'dark');
42
+ }
43
+ }
44
+ // Alternar entre claro y oscuro, y actualizar la variable
45
+ toggleDarkMode() {
46
+ const newTheme = this.theme === 'dark' ? 'light' : 'dark';
47
+ this.setTheme(newTheme);
48
+ }
49
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JModeToggleComponent, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component });
50
+ 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
51
  }
9
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: TailjngService, decorators: [{
10
- type: Injectable,
11
- args: [{
12
- providedIn: 'root'
13
- }]
14
- }], ctorParameters: () => [] });
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
- }] });
52
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JModeToggleComponent, decorators: [{
53
+ type: Component,
54
+ 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>" }]
55
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
56
+ type: Inject,
57
+ args: [PLATFORM_ID]
58
+ }] }] });
32
59
 
33
60
  class TooltipService {
34
61
  tooltipElement = null;
@@ -239,13 +266,4206 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImpor
239
266
  args: ['mouseleave']
240
267
  }] } });
241
268
 
269
+ class JLabelComponent {
270
+ for = '';
271
+ isRequired = false;
272
+ isConditioned = false;
273
+ isAutomated = false;
274
+ classes = '';
275
+ ngClass = {};
276
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
277
+ 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"] }] });
278
+ }
279
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JLabelComponent, decorators: [{
280
+ type: Component,
281
+ 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" }]
282
+ }], propDecorators: { for: [{
283
+ type: Input
284
+ }], isRequired: [{
285
+ type: Input
286
+ }], isConditioned: [{
287
+ type: Input
288
+ }], isAutomated: [{
289
+ type: Input
290
+ }], classes: [{
291
+ type: Input
292
+ }], ngClass: [{
293
+ type: Input
294
+ }] } });
295
+
296
+ class JInputComponent {
297
+ icons = {
298
+ x: X,
299
+ };
300
+ type = 'text';
301
+ placeholder = '';
302
+ disabled = false;
303
+ required = false;
304
+ name;
305
+ id;
306
+ classes = '';
307
+ ngClass = {};
308
+ accept = '';
309
+ multiple = false;
310
+ showImage = false;
311
+ clearButton = false;
312
+ min = 0;
313
+ max = 100;
314
+ step = 1;
315
+ isLabel = false;
316
+ simbol = '';
317
+ fileInputRef;
318
+ innerValue = '';
319
+ previewUrl = null;
320
+ get value() {
321
+ return this.innerValue;
322
+ }
323
+ get combinedNgClass() {
324
+ return {
325
+ ...(this.ngClass || {}),
326
+ 'opacity-50': this.disabled
327
+ };
328
+ }
329
+ set value(val) {
330
+ if (val !== this.innerValue) {
331
+ this.innerValue = val;
332
+ this.onChange(val);
333
+ }
334
+ }
335
+ // ControlValueAccessor
336
+ onChange = () => { };
337
+ onTouched = () => { };
338
+ writeValue(val) {
339
+ if (this.type !== 'file') {
340
+ this.innerValue = val ?? '';
341
+ }
342
+ }
343
+ registerOnChange(fn) {
344
+ this.onChange = fn;
345
+ }
346
+ registerOnTouched(fn) {
347
+ this.onTouched = fn;
348
+ }
349
+ setDisabledState(isDisabled) {
350
+ // Si es tipo archivo, limpia
351
+ if (isDisabled && this.type === 'file') {
352
+ this.innerValue = null;
353
+ this.previewUrl = null;
354
+ }
355
+ }
356
+ onInput(event) {
357
+ const target = event.target;
358
+ this.value = target.value;
359
+ this.onChange(this.value);
360
+ this.onTouched();
361
+ }
362
+ onFileSelected(event) {
363
+ const input = event.target;
364
+ const files = input.files;
365
+ if (files) {
366
+ const value = this.multiple ? Array.from(files) : files[0];
367
+ this.innerValue = value;
368
+ this.onChange(value);
369
+ this.onTouched();
370
+ if (!this.multiple && value instanceof File && value.type?.startsWith('image')) {
371
+ const reader = new FileReader();
372
+ reader.onload = () => {
373
+ this.previewUrl = reader.result;
374
+ };
375
+ reader.readAsDataURL(value);
376
+ }
377
+ else {
378
+ this.previewUrl = null;
379
+ }
380
+ }
381
+ }
382
+ clearInput() {
383
+ this.value = '';
384
+ this.onChange('');
385
+ this.onTouched();
386
+ }
387
+ clearFile() {
388
+ this.innerValue = null;
389
+ this.previewUrl = null;
390
+ this.onChange(this.multiple ? [] : null);
391
+ this.onTouched();
392
+ if (this.fileInputRef?.nativeElement) {
393
+ this.fileInputRef.nativeElement.value = '';
394
+ }
395
+ }
396
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
397
+ 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: [
398
+ {
399
+ provide: NG_VALUE_ACCESSOR,
400
+ useExisting: forwardRef(() => JInputComponent),
401
+ multi: true
402
+ }
403
+ ], 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 });
404
+ }
405
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JInputComponent, decorators: [{
406
+ type: Component,
407
+ args: [{ selector: 'JInput', standalone: true, imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule], encapsulation: ViewEncapsulation.None, providers: [
408
+ {
409
+ provide: NG_VALUE_ACCESSOR,
410
+ useExisting: forwardRef(() => JInputComponent),
411
+ multi: true
412
+ }
413
+ ], 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"] }]
414
+ }], propDecorators: { type: [{
415
+ type: Input
416
+ }], placeholder: [{
417
+ type: Input
418
+ }], disabled: [{
419
+ type: Input
420
+ }], required: [{
421
+ type: Input
422
+ }], name: [{
423
+ type: Input
424
+ }], id: [{
425
+ type: Input
426
+ }], classes: [{
427
+ type: Input
428
+ }], ngClass: [{
429
+ type: Input
430
+ }], accept: [{
431
+ type: Input
432
+ }], multiple: [{
433
+ type: Input
434
+ }], showImage: [{
435
+ type: Input
436
+ }], clearButton: [{
437
+ type: Input
438
+ }], min: [{
439
+ type: Input
440
+ }], max: [{
441
+ type: Input
442
+ }], step: [{
443
+ type: Input
444
+ }], isLabel: [{
445
+ type: Input
446
+ }], simbol: [{
447
+ type: Input
448
+ }], fileInputRef: [{
449
+ type: ViewChild,
450
+ args: ['fileInput']
451
+ }] } });
452
+
453
+ class JCheckboxComponent {
454
+ // Lucide icons
455
+ icons = {
456
+ check: Check,
457
+ loading: Loader2,
458
+ };
459
+ type = 'checkbox'; // Default type;
460
+ icon = this.icons['check']; // Default icon
461
+ iconSize = 15; // Default icon size
462
+ disabled; // Desactivar el checkbox
463
+ isLoading; // Indicar que el checkbox esta cargando
464
+ classes = ''; // Clases adicionales para el checkbox
465
+ title; // Estado del checkbox
466
+ isChecked = false; // Estado del checkbox
467
+ item; // Item
468
+ column; // Columna
469
+ // Funciones
470
+ getValue = () => false;
471
+ onCheckboxChange = () => { };
472
+ toggleSwitch = () => { };
473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
474
+ 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"] }] });
475
+ }
476
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCheckboxComponent, decorators: [{
477
+ type: Component,
478
+ 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>" }]
479
+ }], propDecorators: { type: [{
480
+ type: Input
481
+ }], icon: [{
482
+ type: Input
483
+ }], iconSize: [{
484
+ type: Input
485
+ }], disabled: [{
486
+ type: Input
487
+ }], isLoading: [{
488
+ type: Input
489
+ }], classes: [{
490
+ type: Input
491
+ }], title: [{
492
+ type: Input
493
+ }], isChecked: [{
494
+ type: Input
495
+ }], item: [{
496
+ type: Input
497
+ }], column: [{
498
+ type: Input
499
+ }], getValue: [{
500
+ type: Input
501
+ }], onCheckboxChange: [{
502
+ type: Input
503
+ }], toggleSwitch: [{
504
+ type: Input
505
+ }] } });
506
+
507
+ class JColorsService {
508
+ // Variants
509
+ variants = {
510
+ 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',
511
+ 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',
512
+ secondary: 'bg-background dark:bg-dark-background text-black dark:text-white hover:bg-accent dark:hover:bg-dark-accent/50',
513
+ success: 'bg-green-500 hover:bg-green-600 text-white border border-green-500 dark:border-green-600 shadow-md',
514
+ 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',
515
+ info: 'bg-blue-500 hover:bg-blue-600 text-white border border-blue-500 dark:border-blue-600 shadow-md',
516
+ 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',
517
+ warning: 'bg-yellow-600 hover:bg-yellow-700 text-white border border-yellow-600 dark:border-yellow-700 shadow-md',
518
+ 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',
519
+ question: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-500 dark:border-purple-600 shadow-md',
520
+ 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',
521
+ error: 'bg-red-500 hover:bg-red-600 text-white border border-red-500 dark:border-red-600 shadow-md',
522
+ 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',
523
+ loading: 'bg-gray-500 hover:bg-gray-600 text-white border border-gray-500 dark:border-gray-600 shadow-md',
524
+ 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',
525
+ orange: 'bg-orange-500 hover:bg-orange-600 text-white border border-orange-500 dark:border-orange-600 shadow-md',
526
+ 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',
527
+ cyan: 'bg-cyan-500 hover:bg-cyan-600 text-white border border-cyan-500 dark:border-cyan-600 shadow-md',
528
+ 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',
529
+ purple: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-500 dark:border-purple-600 shadow-md',
530
+ 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',
531
+ teal: 'bg-teal-500 hover:bg-teal-600 text-white border border-teal-500 dark:border-teal-600 shadow-md',
532
+ 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',
533
+ pink: 'bg-pink-500 hover:bg-pink-600 text-white border border-pink-500 dark:border-pink-600 shadow-md',
534
+ 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',
535
+ green: 'bg-green-500 hover:bg-green-600 text-white border border-green-500 dark:border-green-600 shadow-md',
536
+ 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',
537
+ default: ' text-black dark:text-white shadow-md',
538
+ };
539
+ // Alerts
540
+ getAlertClass(type, monocromatic) {
541
+ if (!monocromatic) {
542
+ switch (type) {
543
+ case 'success': return 'border-green-500 bg-green-50 dark:bg-[#15241f]';
544
+ case 'error': return 'border-red-500 bg-red-50 dark:bg-[#21181c]';
545
+ case 'warning': return 'border-yellow-500 bg-yellow-50 dark:bg-[#1f1c1a]';
546
+ case 'info': return 'border-blue-500 bg-blue-50 dark:bg-[#1a1a24]';
547
+ case 'question': return 'border-purple-500 bg-purple-50 dark:bg-[#241732]';
548
+ case 'loading': return 'border-gray-500 bg-gray-50 dark:bg-[#15181e]';
549
+ default: return 'border-gray-500';
550
+ }
551
+ }
552
+ else {
553
+ return 'bg-white dark:bg-dark-popover border-border dark:border-dark-border';
554
+ }
555
+ }
556
+ // Icons
557
+ getIconClass(type, monocromatic) {
558
+ if (!monocromatic) {
559
+ switch (type) {
560
+ case 'success': return 'text-green-500';
561
+ case 'error': return 'text-red-500';
562
+ case 'warning': return 'text-yellow-500';
563
+ case 'info': return 'text-blue-500';
564
+ case 'question': return 'text-purple-500';
565
+ case 'loading': return 'text-gray-500';
566
+ default: return 'text-primary';
567
+ }
568
+ }
569
+ else {
570
+ return 'text-primary';
571
+ }
572
+ }
573
+ // Buttons
574
+ getButtonClass(type, monocromatic) {
575
+ if (!monocromatic) {
576
+ switch (type) {
577
+ case 'success': return { 'success': true };
578
+ case 'error': return { 'error': true };
579
+ case 'warning': return { 'warning': true };
580
+ case 'info': return { 'info': true };
581
+ case 'question': return { 'question': true };
582
+ case 'loading': return { 'loading': true };
583
+ default: return { 'primary': true };
584
+ }
585
+ }
586
+ else {
587
+ return { 'primary': true };
588
+ }
589
+ }
590
+ getButtonSecondaryClass(type, monocromatic) {
591
+ if (!monocromatic) {
592
+ switch (type) {
593
+ case 'success': return { 'success_secondary': true };
594
+ case 'error': return { 'error_secondary': true };
595
+ case 'warning': return { 'warning_secondary': true };
596
+ case 'info': return { 'info_secondary': true };
597
+ case 'question': return { 'question_secondary': true };
598
+ case 'loading': return { 'loading_secondary': true };
599
+ default: return { 'secondary': true };
600
+ }
601
+ }
602
+ else {
603
+ return { 'secondary': true };
604
+ }
605
+ }
606
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
607
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, providedIn: 'root' });
608
+ }
609
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JColorsService, decorators: [{
610
+ type: Injectable,
611
+ args: [{
612
+ providedIn: 'root',
613
+ }]
614
+ }] });
615
+
616
+ class JButtonComponent {
617
+ icons = { loading: Loader2 };
618
+ colorsService = inject(JColorsService);
619
+ type = "button"; // Tipo de botón
620
+ disabled = false; // Estado deshabilitado
621
+ isLoading = false; // Estado de carga
622
+ icon; // Icono por defecto
623
+ iconSize = 15; // Tamaño de icono por defecto
624
+ text; // Color de icono por defecto
625
+ isChangeIcon = false; // Cambiar icono
626
+ iconChange; // Icono de cambio
627
+ tooltip = ""; // Texto de tooltip
628
+ tooltipPosition = "top"; // Posición de tooltip
629
+ clicked = new EventEmitter(); // Evento de clic
630
+ classes = ""; // Clases adicionales
631
+ ngClasses = {}; // Clases dinámicas con [ngClass]
632
+ // Verificar si una clase está presente en `classes` o `ngClasses`
633
+ hasClass(className) {
634
+ // Dividir la cadena de clases por espacios para verificar cada clase individualmente
635
+ const classArray = this.classes.split(" ");
636
+ return classArray.includes(className) || this.ngClasses[className];
637
+ }
638
+ // Definir clases según el tipo de botón (switch)
639
+ get variantClasses() {
640
+ return this.colorsService.variants[this.getActiveVariant()] || "min-w-[100px] text-black dark:text-white shadow-md";
641
+ }
642
+ // Obtener la variante activa
643
+ getActiveVariant() {
644
+ // Buscar la primera variante que coincida con las clases proporcionadas
645
+ const variant = Object.keys(this.colorsService.variants).find((variant) => this.hasClass(variant));
646
+ return variant ?? "default";
647
+ }
648
+ // Combina las clases base con las variantes
649
+ get computedClasses() {
650
+ return {
651
+ "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,
652
+ [this.variantClasses]: true, // Aplica las clases de la variante según el switch
653
+ "cursor-pointer": !this.disabled && !this.isLoading, // Cursor por defecto cuando está activo
654
+ "cursor-default opacity-50 pointer-events-none": this.disabled || this.isLoading, // Cursor deshabilitado
655
+ ...this.ngClasses, // Permite usar validaciones dinámicas con [ngClass]
656
+ };
657
+ }
658
+ handleClick(event) {
659
+ if (!this.disabled && !this.isLoading) {
660
+ this.clicked.emit(event);
661
+ }
662
+ }
663
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
664
+ 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"] }] });
665
+ }
666
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JButtonComponent, decorators: [{
667
+ type: Component,
668
+ 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" }]
669
+ }], propDecorators: { type: [{
670
+ type: Input
671
+ }], disabled: [{
672
+ type: Input
673
+ }], isLoading: [{
674
+ type: Input
675
+ }], icon: [{
676
+ type: Input
677
+ }], iconSize: [{
678
+ type: Input
679
+ }], text: [{
680
+ type: Input
681
+ }], isChangeIcon: [{
682
+ type: Input
683
+ }], iconChange: [{
684
+ type: Input
685
+ }], tooltip: [{
686
+ type: Input
687
+ }], tooltipPosition: [{
688
+ type: Input
689
+ }], clicked: [{
690
+ type: Output
691
+ }], classes: [{
692
+ type: Input
693
+ }], ngClasses: [{
694
+ type: Input
695
+ }] } });
696
+
697
+ // src/app/core/shared/form.shared.ts
698
+ class JDialogShared {
699
+ openDialog = false;
700
+ constructor() { }
701
+ onOpen() {
702
+ this.openDialog = true;
703
+ }
704
+ onClose() {
705
+ this.openDialog = false;
706
+ }
707
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
708
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, providedIn: 'root' });
709
+ }
710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogShared, decorators: [{
711
+ type: Injectable,
712
+ args: [{
713
+ providedIn: 'root'
714
+ }]
715
+ }], ctorParameters: () => [] });
716
+
717
+ class JDialogComponent {
718
+ icons = {
719
+ x: X
720
+ };
721
+ position = 'center';
722
+ offset = {};
723
+ openModal = false;
724
+ closeModal = new EventEmitter();
725
+ dialogTemplate;
726
+ title = 'Dialog Title';
727
+ width = 500;
728
+ height = 300;
729
+ overlay = true;
730
+ draggable = false;
731
+ isDragging = false;
732
+ dragOffset = { x: 0, y: 0 };
733
+ constructor() { }
734
+ onOpen() {
735
+ this.openModal = true;
736
+ }
737
+ onClose() {
738
+ this.closeModal.emit();
739
+ }
740
+ getModalWidth() {
741
+ return typeof this.width === 'number' ? `${this.width}px` : this.width;
742
+ }
743
+ getModalHeight() {
744
+ if (this.height === 'auto')
745
+ return 'auto';
746
+ if (typeof this.height === 'number')
747
+ return `${this.height}px`;
748
+ return `${this.height || 40}px`;
749
+ }
750
+ handleEscape(event) {
751
+ if (this.openModal) {
752
+ this.onClose();
753
+ }
754
+ }
755
+ getPositionClass() {
756
+ switch (this.position) {
757
+ case 'leftCenter': return 'justify-start items-center';
758
+ case 'rightCenter': return 'justify-end items-center';
759
+ case 'topCenter': return 'justify-center items-start';
760
+ case 'bottomCenter': return 'justify-center items-end';
761
+ case 'leftTop': return 'justify-start items-start';
762
+ case 'leftBottom': return 'justify-start items-end';
763
+ case 'rightTop': return 'justify-end items-start';
764
+ case 'rightBottom': return 'justify-end items-end';
765
+ case 'center':
766
+ default: return 'justify-center items-center';
767
+ }
768
+ }
769
+ getOffsetStyles() {
770
+ return {
771
+ marginTop: this.offset.top !== undefined ? `${this.offset.top}px` : '',
772
+ marginBottom: this.offset.bottom !== undefined ? `${this.offset.bottom}px` : '',
773
+ marginLeft: this.offset.left !== undefined ? `${this.offset.left}px` : '',
774
+ marginRight: this.offset.right !== undefined ? `${this.offset.right}px` : '',
775
+ };
776
+ }
777
+ startDrag(event) {
778
+ if (!this.draggable)
779
+ return;
780
+ this.isDragging = true;
781
+ const dialogElement = event.currentTarget.closest('[data-draggable-dialog]');
782
+ if (!dialogElement)
783
+ return;
784
+ const rect = dialogElement.getBoundingClientRect();
785
+ this.dragOffset = {
786
+ x: event.clientX - rect.left,
787
+ y: event.clientY - rect.top
788
+ };
789
+ dialogElement.style.position = 'fixed';
790
+ dialogElement.style.margin = '0';
791
+ dialogElement.style.transform = 'none';
792
+ dialogElement.style.zIndex = '1001';
793
+ const mouseMoveHandler = (moveEvent) => {
794
+ if (!this.isDragging)
795
+ return;
796
+ const newLeft = moveEvent.clientX - this.dragOffset.x;
797
+ const newTop = moveEvent.clientY - this.dragOffset.y;
798
+ // límites del viewport
799
+ const maxLeft = window.innerWidth - dialogElement.offsetWidth;
800
+ const maxTop = window.innerHeight - dialogElement.offsetHeight;
801
+ // aplicar límites
802
+ const boundedLeft = Math.min(Math.max(newLeft, 0), maxLeft);
803
+ const boundedTop = Math.min(Math.max(newTop, 0), maxTop);
804
+ dialogElement.style.left = `${boundedLeft}px`;
805
+ dialogElement.style.top = `${boundedTop}px`;
806
+ };
807
+ const mouseUpHandler = () => {
808
+ this.isDragging = false;
809
+ document.removeEventListener('mousemove', mouseMoveHandler);
810
+ document.removeEventListener('mouseup', mouseUpHandler);
811
+ };
812
+ document.addEventListener('mousemove', mouseMoveHandler);
813
+ document.addEventListener('mouseup', mouseUpHandler);
814
+ }
815
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
816
+ 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: [
817
+ trigger('modalTransition', [
818
+ transition(':enter', [
819
+ style({ transform: 'translateY(1rem)', opacity: 0 }),
820
+ animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
821
+ ]),
822
+ transition(':leave', [
823
+ animate('150ms ease-in', style({ transform: 'translateY(1rem)', opacity: 0 }))
824
+ ])
825
+ ])
826
+ ] });
827
+ }
828
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JDialogComponent, decorators: [{
829
+ type: Component,
830
+ args: [{ selector: 'JDialog', standalone: true, imports: [LucideAngularModule, CommonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], animations: [
831
+ trigger('modalTransition', [
832
+ transition(':enter', [
833
+ style({ transform: 'translateY(1rem)', opacity: 0 }),
834
+ animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
835
+ ]),
836
+ transition(':leave', [
837
+ animate('150ms ease-in', style({ transform: 'translateY(1rem)', opacity: 0 }))
838
+ ])
839
+ ])
840
+ ], 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 " }]
841
+ }], ctorParameters: () => [], propDecorators: { position: [{
842
+ type: Input
843
+ }], offset: [{
844
+ type: Input
845
+ }], openModal: [{
846
+ type: Input
847
+ }], closeModal: [{
848
+ type: Output
849
+ }], dialogTemplate: [{
850
+ type: Input
851
+ }], title: [{
852
+ type: Input
853
+ }], width: [{
854
+ type: Input
855
+ }], height: [{
856
+ type: Input
857
+ }], overlay: [{
858
+ type: Input
859
+ }], draggable: [{
860
+ type: Input
861
+ }], handleEscape: [{
862
+ type: HostListener,
863
+ args: ['document:keydown.escape', ['$event']]
864
+ }] } });
865
+
866
+ class JAlertToastService {
867
+ toastsSignal = signal([]);
868
+ autoCloseTimers = new Map();
869
+ // Default auto-close delay in milliseconds
870
+ DEFAULT_AUTO_CLOSE_DELAY = 5000;
871
+ // Default action button text
872
+ DEFAULT_ACTION_BUTTON_TEXT = "Action";
873
+ // For compatibility with existing code
874
+ isOpenSignal = signal(false);
875
+ configSignal = signal(null);
876
+ isLoadingAction = signal(false);
877
+ isLoadingCancel = signal(false);
878
+ // Computed values for all toasts
879
+ toasts = computed(() => this.toastsSignal());
880
+ // For backward compatibility with existing code
881
+ isOpen = computed(() => this.isOpenSignal());
882
+ config = computed(() => this.configSignal());
883
+ isActionLoading = computed(() => this.isLoadingAction());
884
+ isCancelLoading = computed(() => this.isLoadingCancel());
885
+ getConfig() {
886
+ return this.configSignal();
887
+ }
888
+ AlertToast(config) {
889
+ const toastId = crypto.randomUUID();
890
+ // Store callbacks for this toast
891
+ const onActionCallback = "onAction" in config ? config.onAction : undefined;
892
+ const onCancelCallback = "onCancel" in config ? config.onCancel : undefined;
893
+ // Get action button text or use default
894
+ const actionNameButton = config.actionButtonText ?? this.DEFAULT_ACTION_BUTTON_TEXT;
895
+ const toast = {
896
+ id: toastId,
897
+ config,
898
+ isActionLoading: false,
899
+ isCancelLoading: false,
900
+ onActionCallback,
901
+ onCancelCallback,
902
+ actionNameButton, // Add the button text
903
+ createdAt: Date.now()
904
+ };
905
+ // Add the toast to our list
906
+ this.toastsSignal.update(toasts => [...toasts, toast]);
907
+ // Check if this toast should auto-close
908
+ // If autoClose is explicitly set to true OR
909
+ // if it's a success toast and autoClose is not explicitly set to false
910
+ const shouldAutoClose = config.autoClose === true ||
911
+ (config.type === 'success' && config.autoClose !== false);
912
+ if (shouldAutoClose) {
913
+ // Use the provided delay or the default
914
+ const delay = config.autoCloseDelay ?? this.DEFAULT_AUTO_CLOSE_DELAY;
915
+ const timerId = setTimeout(() => {
916
+ this.closeToastById(toastId);
917
+ this.autoCloseTimers.delete(toastId);
918
+ }, delay);
919
+ this.autoCloseTimers.set(toastId, timerId);
920
+ }
921
+ return toastId;
922
+ }
923
+ closeToastById(toastId) {
924
+ // Clear any auto-close timer
925
+ if (this.autoCloseTimers.has(toastId)) {
926
+ clearTimeout(this.autoCloseTimers.get(toastId));
927
+ this.autoCloseTimers.delete(toastId);
928
+ }
929
+ this.toastsSignal.update(toasts => toasts.filter(toast => toast.id !== toastId));
930
+ }
931
+ closeAllToasts() {
932
+ // Clear all timers
933
+ this.autoCloseTimers.forEach(timerId => clearTimeout(timerId));
934
+ this.autoCloseTimers.clear();
935
+ this.toastsSignal.set([]);
936
+ }
937
+ async executeToastAction(toastId, action) {
938
+ const toast = this.toastsSignal().find(t => t.id === toastId);
939
+ if (!toast)
940
+ return;
941
+ let callback;
942
+ let loadingProperty;
943
+ switch (action) {
944
+ case "action":
945
+ callback = toast.onActionCallback;
946
+ loadingProperty = 'isActionLoading';
947
+ break;
948
+ case "cancel":
949
+ callback = toast.onCancelCallback;
950
+ loadingProperty = 'isCancelLoading';
951
+ break;
952
+ }
953
+ if (callback) {
954
+ // Update loading state
955
+ this.toastsSignal.update(toasts => toasts.map(t => t.id === toastId ? { ...t, [loadingProperty]: true } : t));
956
+ try {
957
+ await callback();
958
+ }
959
+ catch (error) {
960
+ console.error(`Error en la acción ${action}:`, error);
961
+ }
962
+ finally {
963
+ // Update loading state back to false (in case the toast hasn't been closed)
964
+ this.toastsSignal.update(toasts => {
965
+ const updatedToasts = toasts.map(t => t.id === toastId ? { ...t, [loadingProperty]: false } : t);
966
+ return updatedToasts;
967
+ });
968
+ this.closeToastById(toastId);
969
+ }
970
+ }
971
+ else {
972
+ this.closeToastById(toastId);
973
+ }
974
+ }
975
+ // For backward compatibility with existing code
976
+ closeToast() {
977
+ this.closeAllToasts();
978
+ this.isOpenSignal.set(false);
979
+ this.configSignal.set(null);
980
+ }
981
+ // For backward compatibility with existing code
982
+ async executeAction(action) {
983
+ let callback;
984
+ let loadingSignal;
985
+ switch (action) {
986
+ case "action":
987
+ callback = "onAction" in (this.configSignal() || {})
988
+ ? this.configSignal().onAction
989
+ : undefined;
990
+ loadingSignal = this.isLoadingAction;
991
+ break;
992
+ case "cancel":
993
+ callback = "onCancel" in (this.configSignal() || {})
994
+ ? this.configSignal().onCancel
995
+ : undefined;
996
+ loadingSignal = this.isLoadingCancel;
997
+ break;
998
+ }
999
+ if (callback) {
1000
+ loadingSignal.set(true);
1001
+ try {
1002
+ await callback();
1003
+ }
1004
+ catch (error) {
1005
+ console.error(`Error en la acción ${action}:`, error);
1006
+ }
1007
+ finally {
1008
+ loadingSignal.set(false);
1009
+ this.closeToast();
1010
+ }
1011
+ }
1012
+ else {
1013
+ this.closeToast();
1014
+ }
1015
+ }
1016
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1017
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, providedIn: "root" });
1018
+ }
1019
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastService, decorators: [{
1020
+ type: Injectable,
1021
+ args: [{
1022
+ providedIn: "root",
1023
+ }]
1024
+ }] });
1025
+
1026
+ class JAlertToastComponent {
1027
+ colorsService;
1028
+ monocromatic = false;
1029
+ position = 'bottom-right';
1030
+ alertToastService = inject(JAlertToastService);
1031
+ constructor(colorsService) {
1032
+ this.colorsService = colorsService;
1033
+ }
1034
+ toasts = computed(() => this.alertToastService.toasts());
1035
+ icons = {
1036
+ success: CircleCheck,
1037
+ error: CircleX,
1038
+ warning: TriangleAlert,
1039
+ info: Info,
1040
+ question: CircleHelp,
1041
+ loading: Loader2,
1042
+ close: X,
1043
+ };
1044
+ getIcon(type) {
1045
+ return this.icons[type] || this.icons['info'];
1046
+ }
1047
+ handleAction(toastId, action) {
1048
+ this.alertToastService.executeToastAction(toastId, action);
1049
+ }
1050
+ closeToast(toastId) {
1051
+ this.alertToastService.closeToastById(toastId);
1052
+ }
1053
+ // Obtener la clase del toast
1054
+ getToastClass(type) {
1055
+ return this.colorsService.getAlertClass(type, this.monocromatic);
1056
+ }
1057
+ // Obtener la clase del icono
1058
+ getIconClass(type) {
1059
+ return this.colorsService.getIconClass(type, this.monocromatic);
1060
+ }
1061
+ // Obtener clase del boton
1062
+ getButtonClass(type) {
1063
+ return this.colorsService.getButtonClass(type, this.monocromatic);
1064
+ }
1065
+ // Obtener la clase secundaria del botón
1066
+ getButtonSecondaryClass(type) {
1067
+ return this.colorsService.getButtonSecondaryClass(type, this.monocromatic);
1068
+ }
1069
+ // Asignar posision del toast
1070
+ getPositionClass() {
1071
+ const base = 'fixed z-1000 flex flex-col gap-2 max-w-md';
1072
+ switch (this.position) {
1073
+ case 'top-left':
1074
+ return `${base} top-4 left-4`;
1075
+ case 'top-right':
1076
+ return `${base} top-20 right-4`;
1077
+ case 'bottom-left':
1078
+ return `${base} bottom-4 left-4`;
1079
+ case 'bottom-right':
1080
+ default:
1081
+ return `${base} bottom-4 right-4`;
1082
+ }
1083
+ }
1084
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastComponent, deps: [{ token: JColorsService }], target: i0.ɵɵFactoryTarget.Component });
1085
+ 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: [
1086
+ trigger("toastTransition", [
1087
+ transition(":enter", [
1088
+ style({ opacity: 0, transform: "translateY(10px)" }),
1089
+ animate("300ms ease-out", style({ opacity: 1, transform: "translateY(0)" }))
1090
+ ]),
1091
+ transition(":leave", [
1092
+ animate("150ms ease-in", style({ opacity: 0, transform: "translateY(10px)" }))
1093
+ ])
1094
+ ])
1095
+ ] });
1096
+ }
1097
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertToastComponent, decorators: [{
1098
+ type: Component,
1099
+ args: [{ selector: 'JAlertToast', imports: [LucideAngularModule, NgClass, JButtonComponent], animations: [
1100
+ trigger("toastTransition", [
1101
+ transition(":enter", [
1102
+ style({ opacity: 0, transform: "translateY(10px)" }),
1103
+ animate("300ms ease-out", style({ opacity: 1, transform: "translateY(0)" }))
1104
+ ]),
1105
+ transition(":leave", [
1106
+ animate("150ms ease-in", style({ opacity: 0, transform: "translateY(10px)" }))
1107
+ ])
1108
+ ])
1109
+ ], 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>" }]
1110
+ }], ctorParameters: () => [{ type: JColorsService }], propDecorators: { monocromatic: [{
1111
+ type: Input
1112
+ }], position: [{
1113
+ type: Input
1114
+ }] } });
1115
+
1116
+ class JAlertDialogService {
1117
+ isOpenSignal = signal(false);
1118
+ configSignal = signal(null);
1119
+ // Estados de carga para los botones
1120
+ isLoadingConfirm = signal(false);
1121
+ isLoadingCancel = signal(false);
1122
+ isLoadingRetry = signal(false);
1123
+ // Callbacks
1124
+ onConfirmCallback;
1125
+ onCancelCallback;
1126
+ onRetryCallback;
1127
+ isOpen = computed(() => this.isOpenSignal());
1128
+ config = computed(() => this.configSignal());
1129
+ isConfirmLoading = computed(() => this.isLoadingConfirm());
1130
+ isCancelLoading = computed(() => this.isLoadingCancel());
1131
+ isRetryLoading = computed(() => this.isLoadingRetry());
1132
+ // Add a dialogs computed property that returns an array with a single dialog when open
1133
+ dialogs = computed(() => {
1134
+ if (!this.isOpenSignal())
1135
+ return [];
1136
+ return [{
1137
+ config: this.configSignal(),
1138
+ isConfirmLoading: this.isLoadingConfirm(),
1139
+ isCancelLoading: this.isLoadingCancel(),
1140
+ isRetryLoading: this.isLoadingRetry()
1141
+ }];
1142
+ });
1143
+ getConfig() {
1144
+ return this.configSignal();
1145
+ }
1146
+ AlertDialog(config) {
1147
+ this.onConfirmCallback = "onConfirm" in config ? config.onConfirm : undefined;
1148
+ this.onCancelCallback = "onCancel" in config ? config.onCancel : undefined;
1149
+ this.onRetryCallback = "onRetry" in config ? config.onRetry : undefined;
1150
+ // Reset loading states
1151
+ this.isLoadingConfirm.set(false);
1152
+ this.isLoadingCancel.set(false);
1153
+ this.isLoadingRetry.set(false);
1154
+ this.configSignal.set(config);
1155
+ this.isOpenSignal.set(true);
1156
+ }
1157
+ closeDialog() {
1158
+ this.isOpenSignal.set(false);
1159
+ this.configSignal.set(null);
1160
+ }
1161
+ async executeAction(action) {
1162
+ let callback;
1163
+ let loadingSignal;
1164
+ switch (action) {
1165
+ case "confirm":
1166
+ callback = this.onConfirmCallback;
1167
+ loadingSignal = this.isLoadingConfirm;
1168
+ break;
1169
+ case "cancel":
1170
+ callback = this.onCancelCallback;
1171
+ loadingSignal = this.isLoadingCancel;
1172
+ break;
1173
+ case "retry":
1174
+ callback = this.onRetryCallback;
1175
+ loadingSignal = this.isLoadingRetry;
1176
+ break;
1177
+ }
1178
+ if (!callback) {
1179
+ this.closeDialog();
1180
+ return;
1181
+ }
1182
+ loadingSignal.set(true);
1183
+ try {
1184
+ const result = callback();
1185
+ // Soporte para Observable
1186
+ if (isObservable(result)) {
1187
+ await firstValueFrom(result);
1188
+ }
1189
+ // Soporte para Promise
1190
+ if (result instanceof Promise) {
1191
+ await result;
1192
+ }
1193
+ // Si es void, no hacemos nada extra
1194
+ }
1195
+ catch (error) {
1196
+ console.error(`Error en la acción ${action}:`, error);
1197
+ }
1198
+ finally {
1199
+ loadingSignal.set(false);
1200
+ this.closeDialog();
1201
+ }
1202
+ }
1203
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1204
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, providedIn: "root" });
1205
+ }
1206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogService, decorators: [{
1207
+ type: Injectable,
1208
+ args: [{
1209
+ providedIn: "root",
1210
+ }]
1211
+ }] });
1212
+
1213
+ class JAlertDialogComponent {
1214
+ colorsService;
1215
+ monocromatic = false;
1216
+ alertDialogService = inject(JAlertDialogService);
1217
+ constructor(colorsService) {
1218
+ this.colorsService = colorsService;
1219
+ }
1220
+ // Single computed property for dialogs
1221
+ dialogs = computed(() => this.alertDialogService.dialogs());
1222
+ icons = {
1223
+ success: CircleCheck,
1224
+ error: CircleX,
1225
+ warning: TriangleAlert,
1226
+ info: Info,
1227
+ question: CircleHelp,
1228
+ loading: Loader2,
1229
+ };
1230
+ getIcon(type) {
1231
+ return this.icons[type] || this.icons['info'];
1232
+ }
1233
+ handleAction(action) {
1234
+ this.alertDialogService.executeAction(action);
1235
+ }
1236
+ // Get the class for the toast
1237
+ getDialogClass(type) {
1238
+ return this.colorsService.getAlertClass(type, this.monocromatic);
1239
+ }
1240
+ // Get the class for the icon
1241
+ getIconClass(type) {
1242
+ return this.colorsService.getIconClass(type, this.monocromatic);
1243
+ }
1244
+ // Get the class for the button
1245
+ getButtonClass(type) {
1246
+ return this.colorsService.getButtonClass(type, this.monocromatic);
1247
+ }
1248
+ getButtonSecondaryClass(type) {
1249
+ return this.colorsService.getButtonSecondaryClass(type, this.monocromatic);
1250
+ }
1251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogComponent, deps: [{ token: JColorsService }], target: i0.ɵɵFactoryTarget.Component });
1252
+ 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: [
1253
+ trigger("modalTransition", [
1254
+ transition(":enter", [
1255
+ style({ transform: "translateY(1rem)", opacity: 0 }),
1256
+ animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
1257
+ ]),
1258
+ transition(":leave", [
1259
+ animate("150ms ease-in", style({ transform: "translateY(1rem)", opacity: 0 })),
1260
+ ]),
1261
+ ]),
1262
+ ] });
1263
+ }
1264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JAlertDialogComponent, decorators: [{
1265
+ type: Component,
1266
+ args: [{ selector: "JAlertDialog", imports: [LucideAngularModule, NgClass, JButtonComponent], animations: [
1267
+ trigger("modalTransition", [
1268
+ transition(":enter", [
1269
+ style({ transform: "translateY(1rem)", opacity: 0 }),
1270
+ animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
1271
+ ]),
1272
+ transition(":leave", [
1273
+ animate("150ms ease-in", style({ transform: "translateY(1rem)", opacity: 0 })),
1274
+ ]),
1275
+ ]),
1276
+ ], 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 }" }]
1277
+ }], ctorParameters: () => [{ type: JColorsService }], propDecorators: { monocromatic: [{
1278
+ type: Input
1279
+ }] } });
1280
+
1281
+ const API_URL = new InjectionToken('API_URL');
1282
+
1283
+ class JHttpParamsService {
1284
+ constructor() { }
1285
+ // Formatear parámetros para peticiones HTTP
1286
+ resParams(params) {
1287
+ // Construir HttpParams
1288
+ let httpParams = new HttpParams();
1289
+ // Recorrer todos los parámetros
1290
+ if (params) {
1291
+ Object.keys(params).forEach(key => {
1292
+ const value = params[key];
1293
+ // Si el valor es un objeto (ej. filter: { id_status: [1, 2] })
1294
+ if (typeof value === 'object' && value !== null) {
1295
+ Object.keys(value).forEach(subKey => {
1296
+ const subValue = value[subKey];
1297
+ // Si el subValor es un array (ej. id_status: [1, 2])
1298
+ if (Array.isArray(subValue)) {
1299
+ subValue.forEach(v => {
1300
+ httpParams = httpParams.append(`${key}[${subKey}]`, v);
1301
+ });
1302
+ }
1303
+ else {
1304
+ // Si es un valor único
1305
+ httpParams = httpParams.append(`${key}[${subKey}]`, subValue);
1306
+ }
1307
+ });
1308
+ }
1309
+ else {
1310
+ // Si es un valor plano (ej. id_status: 1)
1311
+ httpParams = httpParams.append(key, value);
1312
+ }
1313
+ });
1314
+ }
1315
+ return httpParams;
1316
+ }
1317
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1318
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, providedIn: 'root' });
1319
+ }
1320
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JHttpParamsService, decorators: [{
1321
+ type: Injectable,
1322
+ args: [{
1323
+ providedIn: 'root'
1324
+ }]
1325
+ }], ctorParameters: () => [] });
1326
+
1327
+ class ConverterService {
1328
+ constructor() { }
1329
+ /**
1330
+ * Obtener la clave de ordenación correcta
1331
+ * @param sortColumn
1332
+ * @returns
1333
+ */
1334
+ getSortKey(sortColumn) {
1335
+ // Verifica si sortColumn es un objeto y retorna la propiedad 'col'
1336
+ if (typeof sortColumn === 'object' && sortColumn !== null) {
1337
+ return sortColumn.col;
1338
+ }
1339
+ // Si no es un objeto, asume que es una cadena directa
1340
+ return sortColumn;
1341
+ }
1342
+ /**
1343
+ * Convierte un objeto a un array de objetos con clave y valor para iniciar el formulario
1344
+ * @param formGroup
1345
+ * @returns
1346
+ */
1347
+ initializeFormControls(formGroup) {
1348
+ const formControls = {};
1349
+ Object.keys(formGroup.controls).forEach(key => {
1350
+ formControls[`${key}_control`] = formGroup.get(key);
1351
+ });
1352
+ return formControls;
1353
+ }
1354
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1355
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, providedIn: 'root' });
1356
+ }
1357
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ConverterService, decorators: [{
1358
+ type: Injectable,
1359
+ args: [{
1360
+ providedIn: 'root'
1361
+ }]
1362
+ }], ctorParameters: () => [] });
1363
+
1364
+ class JGenericService {
1365
+ baseUrl;
1366
+ http;
1367
+ HttpParamsService;
1368
+ converterService;
1369
+ constructor(baseUrl, http, HttpParamsService, converterService) {
1370
+ this.baseUrl = baseUrl;
1371
+ this.http = http;
1372
+ this.HttpParamsService = HttpParamsService;
1373
+ this.converterService = converterService;
1374
+ }
1375
+ /**
1376
+ * Método genérico para obtener todos los registros desde un endpoint.
1377
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1378
+ * @param params Parámetros de la petición.
1379
+ * @returns Observable con la respuesta de la API.
1380
+ */
1381
+ getAll(endpoint, params) {
1382
+ const url = `${this.baseUrl}/${endpoint}`;
1383
+ // Construir HttpParams
1384
+ let httpParams;
1385
+ if (params)
1386
+ httpParams = this.HttpParamsService.resParams(params);
1387
+ // Hacer la petición GET con los HttpParams formateados
1388
+ return this.http.get(url, { params: httpParams });
1389
+ }
1390
+ /**
1391
+ * Método genérico para obtener un registro desde un endpoint.
1392
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1393
+ * @param id Identificador del registro.
1394
+ * @returns Observable con la respuesta de la API.
1395
+ */
1396
+ getId(endpoint, id) {
1397
+ const url = `${this.baseUrl}/${endpoint}`;
1398
+ return this.http.get(`${url}/${id}`).pipe(map(response => response.data[endpoint]));
1399
+ }
1400
+ /**
1401
+ * Método genérico para agregar un registro a un endpoint.
1402
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1403
+ * @param data Datos del registro a agregar.
1404
+ * @returns Observable con la respuesta de la API.
1405
+ */
1406
+ create(endpoint, data) {
1407
+ const url = `${this.baseUrl}/${endpoint}`;
1408
+ return this.http.post(url, data);
1409
+ }
1410
+ /**
1411
+ * Método genérico para actualizar un registro en un endpoint.
1412
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1413
+ * @param id Identificador del registro.
1414
+ * @param data Datos del registro a actualizar.
1415
+ * @returns Observable con la respuesta de la API.
1416
+ */
1417
+ update(endpoint, id, data) {
1418
+ const url = `${this.baseUrl}/${endpoint}`;
1419
+ return this.http.put(`${url}/${id}`, data);
1420
+ }
1421
+ /**
1422
+ * Método genérico para eliminar un registro de un endpoint.
1423
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1424
+ * @param id Identificador del registro.
1425
+ * @returns Observable con la respuesta de la API.
1426
+ */
1427
+ delete(endpoint, id) {
1428
+ const url = `${this.baseUrl}/${endpoint}`;
1429
+ return this.http.delete(`${url}/${id}`);
1430
+ }
1431
+ /**
1432
+ * Método genérico para actualizar estados de un registro en un endpoint.
1433
+ * @param endpoint Distintivo del endpoint ('role', 'status', etc.)
1434
+ * @param id Identificador del registro.
1435
+ * @param data Datos de un registro booleano a actualizar.
1436
+ * @returns Observable con la respuesta de la API.
1437
+ */
1438
+ enable(endpoint, id, data) {
1439
+ const url = `${this.baseUrl}/${endpoint}`;
1440
+ return this.http.put(`${url}/enable/${id}`, data);
1441
+ }
1442
+ /**
1443
+ * Método genérico para obtener los parámetros de consulta para una tabla.
1444
+ * @param page Número de página actual.
1445
+ * @param limit Número de registros por página.
1446
+ * @param sort Objeto que contiene la columna y la dirección de ordenamiento.
1447
+ * @param filters Filtros aplicados a la consulta.
1448
+ * @param defaultFilters Filtros predeterminados aplicados a la consulta.
1449
+ * @param searchQuery Cadena de búsqueda.
1450
+ * @param columns Columnas a buscar.
1451
+ * @returns
1452
+ */
1453
+ params({ page, limit, sort, filters, defaultFilters, searchQuery, columns }) {
1454
+ const params = {};
1455
+ if (page)
1456
+ params['page'] = page.toString();
1457
+ if (limit)
1458
+ params['limit'] = limit.toString();
1459
+ // Aplicar los filtros predeterminados enviados desde el padre
1460
+ Object.keys(defaultFilters ?? {}).forEach((key) => {
1461
+ if (!filters.hasOwnProperty(key)) {
1462
+ params[`filter[${key}]`] = defaultFilters[key];
1463
+ }
1464
+ });
1465
+ // Aplicar el ordenamiento si se ha proporcionado
1466
+ if (sort?.column && sort?.direction !== 'none') {
1467
+ const sortKey = this.converterService.getSortKey(sort?.column);
1468
+ params['sortBy'] = sortKey;
1469
+ params['sortOrder'] = sort?.direction?.toUpperCase();
1470
+ }
1471
+ // Aplicar la búsqueda si se ha proporcionado
1472
+ if (searchQuery && searchQuery.trim() !== '') {
1473
+ params['search'] = searchQuery;
1474
+ params['searchFields'] = columns?.map(col => col);
1475
+ }
1476
+ // Aplicar los filtros si se han proporcionado
1477
+ if (Object.keys(filters).length > 0) {
1478
+ params['filter'] = filters;
1479
+ }
1480
+ return params;
1481
+ }
1482
+ 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 });
1483
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JGenericService, providedIn: 'root' });
1484
+ }
1485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JGenericService, decorators: [{
1486
+ type: Injectable,
1487
+ args: [{
1488
+ providedIn: 'root'
1489
+ }]
1490
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1491
+ type: Inject,
1492
+ args: [API_URL]
1493
+ }] }, { type: i1$2.HttpClient }, { type: JHttpParamsService }, { type: ConverterService }] });
1494
+
1495
+ class JErrorHandlerService {
1496
+ /**
1497
+ * Mapeo de códigos de estado HTTP a mensajes amigables y su tipo de alerta.
1498
+ */
1499
+ errorMappings = {
1500
+ 400: { type: 'info', title: 'Solicitud incorrecta', message: 'Los datos enviados no son válidos. Verifique e intente de nuevo.', persistent: false },
1501
+ 401: { type: 'info', title: 'No autorizado', message: 'Debe iniciar sesión para acceder a esta función.', persistent: true },
1502
+ 403: { type: 'error', title: 'Acceso denegado', message: 'No tiene permisos para realizar esta acción.', persistent: true },
1503
+ 404: { type: 'info', title: 'No encontrado', message: 'El recurso solicitado no existe o fue eliminado.', persistent: false },
1504
+ 409: { type: 'info', title: 'Conflicto', message: 'Conflicto en la solicitud. Verifique los datos ingresados.', persistent: false },
1505
+ 422: { type: 'info', title: 'Datos no procesables', message: 'Los datos son válidos, pero no pueden ser procesados en este momento.', persistent: false },
1506
+ 429: { type: 'info', title: 'Demasiadas solicitudes', message: 'Ha realizado demasiadas solicitudes. Intente más tarde.', persistent: true },
1507
+ 500: { type: 'error', title: 'Error del servidor', message: 'Ocurrió un error inesperado. Intente más tarde.', persistent: true },
1508
+ 503: { type: 'error', title: 'Servicio no disponible', message: 'El servidor está en mantenimiento o sobrecargado.', persistent: true }
1509
+ };
1510
+ constructor() { }
1511
+ /**
1512
+ * Manejo de errores HTTP y retorno de un mensaje estructurado con tipo de alerta.
1513
+ */
1514
+ handleHttpError(error) {
1515
+ console.error('HTTP Error:', error); // Log para depuración
1516
+ // Si no hay error definido, retornamos mensaje genérico
1517
+ if (!error) {
1518
+ return {
1519
+ type: 'error',
1520
+ title: 'Error desconocido',
1521
+ message: 'Ocurrió un error inesperado. Intente de nuevo más tarde.',
1522
+ persistent: true
1523
+ };
1524
+ }
1525
+ const status = error?.status || 0;
1526
+ const backendMessage = error?.error?.msg || error?.message;
1527
+ const errorInfo = this.errorMappings[status] || {
1528
+ title: 'Error desconocido',
1529
+ message: 'Ocurrió un error inesperado. Intente de nuevo más tarde.',
1530
+ type: 'error',
1531
+ persistent: true
1532
+ };
1533
+ return {
1534
+ type: errorInfo.type,
1535
+ title: errorInfo.title,
1536
+ message: backendMessage || errorInfo.message,
1537
+ persistent: errorInfo.persistent
1538
+ };
1539
+ }
1540
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1541
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, providedIn: 'root' });
1542
+ }
1543
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JErrorHandlerService, decorators: [{
1544
+ type: Injectable,
1545
+ args: [{
1546
+ providedIn: 'root'
1547
+ }]
1548
+ }], ctorParameters: () => [] });
1549
+
1550
+ class OptionComponent {
1551
+ elementRef;
1552
+ value;
1553
+ disabled = false;
1554
+ // Used internally by JSelect
1555
+ get dataValue() {
1556
+ return this.value;
1557
+ }
1558
+ // Store the text content for display in the select
1559
+ _text = '';
1560
+ get text() {
1561
+ return this._text;
1562
+ }
1563
+ constructor(elementRef) {
1564
+ this.elementRef = elementRef;
1565
+ }
1566
+ ngAfterViewInit() {
1567
+ // Extract text content after view is initialized
1568
+ this._text = this.elementRef.nativeElement.textContent.trim();
1569
+ }
1570
+ // This will be called by JSelect to set the text content
1571
+ setTextContent(text) {
1572
+ this._text = text;
1573
+ }
1574
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: OptionComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1575
+ 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 }] });
1576
+ }
1577
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: OptionComponent, decorators: [{
1578
+ type: Component,
1579
+ args: [{ selector: 'JOption', standalone: true, imports: [CommonModule], template: `<ng-content></ng-content>` }]
1580
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{
1581
+ type: Input
1582
+ }], disabled: [{
1583
+ type: Input
1584
+ }], dataValue: [{
1585
+ type: HostBinding,
1586
+ args: ['attr.data-value']
1587
+ }] } });
1588
+
1589
+ class JSelectComponent {
1590
+ cdr;
1591
+ elementRef;
1592
+ genericService;
1593
+ // Lucide icons
1594
+ icons = {
1595
+ chevronDown: ChevronDown,
1596
+ view: Eye,
1597
+ x: X,
1598
+ check: Check,
1599
+ search: Search, // xxx
1600
+ loading: Loader2,
1601
+ };
1602
+ type = 'dropdown'; // Tipo de select
1603
+ btnIcon = this.icons['view']; // Icono del botón
1604
+ btnText = ''; // Texto del botón
1605
+ title = 'Seleccionar'; // Titulo del dropdown
1606
+ placeholder = 'Seleccione una opción'; // Texto cuando no hay selección
1607
+ showClear = false; // Mostrar botón para limpiar selección
1608
+ columns = []; // Columnas a mostrar en el dropdown ideal para la tabla
1609
+ options = []; // Opciones quemadas para el dropdown
1610
+ optionLabel = 'text'; // Propiedad a mostrar en el dropdown
1611
+ optionValue = 'value'; // Propiedad a usar como valor en el dropdown
1612
+ labelSeparator = ' ';
1613
+ isLoading = false;
1614
+ // Datos para el searchable
1615
+ endpoint = ''; // Endpoint para buscar datos
1616
+ loadOnInit = false; // Cargar datos al inicializar
1617
+ defaultFilters = {}; // Parámetros adicionales para la api
1618
+ searchFields = []; // Filtros adicionales para la búsqueda
1619
+ isSearch = true; // Habilitar la búsqueda
1620
+ isFilterSelect = false; // Es un select de filtro
1621
+ sort = 'ASC';
1622
+ updateVisibility = new EventEmitter();
1623
+ selectionChange = new EventEmitter();
1624
+ optionComponents;
1625
+ selectButton;
1626
+ // Selectores
1627
+ isColumnSelectorOpen = false;
1628
+ selectedValue = null;
1629
+ selectedLabel = '';
1630
+ internalOptions = [];
1631
+ // Para la búsqueda
1632
+ searchTerm = '';
1633
+ searchSubject = new Subject();
1634
+ searchSubscription;
1635
+ filteredOptions = [];
1636
+ // Dropdown positioning
1637
+ dropdownTop = 0;
1638
+ dropdownLeft = 0;
1639
+ dropdownWidth = 0;
1640
+ // Para implementar ControlValueAccessor
1641
+ onChange = () => { };
1642
+ onTouched = () => { };
1643
+ disabled = false;
1644
+ // Para detectar clicks fuera del componente
1645
+ clickOutsideListener;
1646
+ constructor(cdr, elementRef, genericService) {
1647
+ this.cdr = cdr;
1648
+ this.elementRef = elementRef;
1649
+ this.genericService = genericService;
1650
+ }
1651
+ ngOnInit() {
1652
+ // Configurar el debounce para la búsqueda
1653
+ this.searchSubscription = this.searchSubject.pipe(debounceTime(1000), distinctUntilChanged()).subscribe(() => {
1654
+ if (this.type === 'searchable') {
1655
+ this.loadData();
1656
+ }
1657
+ });
1658
+ // Cargar datos al inicializar si es necesario
1659
+ if (this.loadOnInit && this.type === 'searchable') {
1660
+ this.loadData();
1661
+ }
1662
+ // Asignar el texto del botón si no se ha proporcionado
1663
+ this.updateSelectedLabel();
1664
+ }
1665
+ ngAfterContentInit() {
1666
+ if (this.type === 'dropdown') {
1667
+ this.setupClickOutsideListener();
1668
+ this.optionComponents.changes.subscribe(() => {
1669
+ this.processOptions();
1670
+ });
1671
+ }
1672
+ }
1673
+ ngAfterViewInit() {
1674
+ this.setupClickOutsideListener();
1675
+ if (this.type === 'dropdown') {
1676
+ setTimeout(() => {
1677
+ this.processOptions();
1678
+ });
1679
+ }
1680
+ }
1681
+ ngOnChanges(changes) {
1682
+ if (changes['options']) {
1683
+ this.processOptions();
1684
+ }
1685
+ }
1686
+ ngOnDestroy() {
1687
+ if (this.clickOutsideListener) {
1688
+ document.removeEventListener('click', this.clickOutsideListener);
1689
+ }
1690
+ if (this.searchSubscription) {
1691
+ this.searchSubscription.unsubscribe();
1692
+ }
1693
+ }
1694
+ // ======================================================
1695
+ // Métodos
1696
+ // ======================================================
1697
+ // Método para procesar las opciones del dropdown
1698
+ processOptions() {
1699
+ this.internalOptions = [];
1700
+ if (this.optionComponents && this.optionComponents.length > 0) {
1701
+ this.optionComponents.forEach(option => {
1702
+ this.internalOptions.push({
1703
+ value: option.value,
1704
+ text: option.text || 'Option'
1705
+ });
1706
+ });
1707
+ }
1708
+ else if (this.options && this.options.length > 0 && typeof this.options[0] !== 'object') {
1709
+ this.internalOptions = this.options.map(option => ({
1710
+ value: option,
1711
+ text: option.toString()
1712
+ }));
1713
+ }
1714
+ else if (this.options && this.options.length > 0) {
1715
+ this.internalOptions = this.options.map(option => {
1716
+ const text = Array.isArray(this.optionLabel)
1717
+ ? this.optionLabel.map(k => this.getNestedValue(option, k)).join(' ')
1718
+ : this.getNestedValue(option, this.optionLabel);
1719
+ return {
1720
+ value: option[this.optionValue],
1721
+ text,
1722
+ original: option
1723
+ };
1724
+ });
1725
+ }
1726
+ this.filteredOptions = [...this.internalOptions];
1727
+ this.updateSelectedLabel();
1728
+ this.cdr.detectChanges();
1729
+ }
1730
+ // Actualizar la visibilidad de las columnas
1731
+ updateColumnVisibility() {
1732
+ if (this.type === 'multi-table') {
1733
+ this.updateVisibility.emit(this.columns);
1734
+ }
1735
+ }
1736
+ // Seleccionar una opción
1737
+ selectOption(option) {
1738
+ this.selectedValue = option.value;
1739
+ this.selectedLabel = option.text;
1740
+ this.onChange(this.selectedValue);
1741
+ this.selectionChange.emit(option.original ?? option.value);
1742
+ this.isColumnSelectorOpen = false;
1743
+ }
1744
+ // Limpiar la selección
1745
+ clearSelection(event) {
1746
+ event.stopPropagation();
1747
+ this.writeValue(null);
1748
+ this.onChange(null);
1749
+ this.selectionChange.emit(null);
1750
+ }
1751
+ // Limpier el término de búsqueda
1752
+ clearSearchTerm() {
1753
+ this.searchTerm = '';
1754
+ this.onSearchInput();
1755
+ }
1756
+ // Actualizar el texto del botón
1757
+ updateSelectedLabel() {
1758
+ if (this.selectedValue === null) {
1759
+ this.selectedLabel = this.placeholder;
1760
+ return;
1761
+ }
1762
+ const selectedOption = this.internalOptions.find(opt => opt.value === this.selectedValue);
1763
+ this.selectedLabel = selectedOption ? selectedOption.text : this.placeholder;
1764
+ }
1765
+ // Obtener el valor anidado de un objeto
1766
+ getNestedValue(obj, path) {
1767
+ return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? '';
1768
+ }
1769
+ // ======================================================
1770
+ // Search input service
1771
+ // ======================================================
1772
+ // Cargar datos desde el servicio
1773
+ loadData() {
1774
+ if (!this.endpoint)
1775
+ return;
1776
+ this.isLoading = true;
1777
+ const params = {};
1778
+ params['sortOrder'] = this.sort;
1779
+ // Aplicar los filtros predeterminados enviados desde el padre
1780
+ Object.keys(this.defaultFilters).forEach((key) => {
1781
+ params[`filter[${key}]`] = this.defaultFilters[key];
1782
+ });
1783
+ // Añadir término de búsqueda a los parámetros
1784
+ if (this.searchTerm && this.searchTerm.trim() !== '') {
1785
+ params['search'] = this.searchTerm;
1786
+ // Aplicar filtros de búsqueda adicionales
1787
+ const allSearchFields = [this.optionLabel, ...this.searchFields];
1788
+ params['searchFields'] = allSearchFields;
1789
+ }
1790
+ // Esperar 2s
1791
+ // setTimeout(() => {
1792
+ this.genericService.getAll(this.endpoint, params).subscribe({
1793
+ next: (response) => {
1794
+ // Procesar la respuesta según la estructura de tu API
1795
+ const data = response.data[this.endpoint] || [];
1796
+ this.options = data;
1797
+ // Procesar las opciones para el dropdown
1798
+ this.internalOptions = this.options.map(option => ({
1799
+ value: option[this.optionValue],
1800
+ text: this.resolveLabel(option),
1801
+ original: option
1802
+ }));
1803
+ this.filteredOptions = [...this.internalOptions];
1804
+ this.isLoading = false;
1805
+ this.updateSelectedLabel();
1806
+ this.cdr.detectChanges();
1807
+ },
1808
+ error: (error) => {
1809
+ console.error('Error fetching data:', error);
1810
+ this.isLoading = false;
1811
+ this.cdr.detectChanges();
1812
+ }
1813
+ });
1814
+ // }, 2000);
1815
+ }
1816
+ // Manejar la entrada de búsqueda
1817
+ onSearchInput() {
1818
+ if (this.type === 'searchable') {
1819
+ // Para el tipo searchable, enviar la búsqueda al servicio
1820
+ this.searchSubject.next(this.searchTerm);
1821
+ }
1822
+ else {
1823
+ // Para otros tipos, filtrar localmente
1824
+ this.filterOptions();
1825
+ }
1826
+ }
1827
+ // Filtrar opciones localmente
1828
+ filterOptions() {
1829
+ if (!this.searchTerm || this.searchTerm.trim() === '') {
1830
+ this.filteredOptions = [...this.internalOptions];
1831
+ return;
1832
+ }
1833
+ const searchTermLower = this.searchTerm.toLowerCase();
1834
+ this.filteredOptions = this.internalOptions.filter(option => option.text.toLowerCase().includes(searchTermLower));
1835
+ }
1836
+ resolveLabel(option) {
1837
+ if (Array.isArray(this.optionLabel)) {
1838
+ return this.optionLabel
1839
+ .map((key) => this.getNestedValue(option, key))
1840
+ .filter(Boolean)
1841
+ .join(this.labelSeparator); // <-- usar separador personalizado
1842
+ }
1843
+ return this.getNestedValue(option, this.optionLabel);
1844
+ }
1845
+ // ======================================================
1846
+ // Elemento
1847
+ // ======================================================
1848
+ // Abrir o cerrar el dropdown
1849
+ toggleColumnSelector() {
1850
+ if (this.disabled)
1851
+ return;
1852
+ this.isColumnSelectorOpen = !this.isColumnSelectorOpen;
1853
+ if (this.isColumnSelectorOpen) {
1854
+ this.onTouched();
1855
+ this.updateDropdownPosition();
1856
+ // Cargar datos cuando se abre el dropdown si es de tipo searchable
1857
+ if (this.type === 'searchable' && !this.loadOnInit && this.shouldTriggerLoad()) {
1858
+ this.loadData();
1859
+ }
1860
+ }
1861
+ }
1862
+ // Detectar clicks fuera del componente
1863
+ setupClickOutsideListener() {
1864
+ this.clickOutsideListener = (event) => {
1865
+ const clickedElement = event.target;
1866
+ const isOutsideDropdown = !this.elementRef.nativeElement.contains(clickedElement);
1867
+ if (this.isColumnSelectorOpen && isOutsideDropdown) {
1868
+ this.isColumnSelectorOpen = false;
1869
+ this.cdr.detectChanges();
1870
+ }
1871
+ };
1872
+ document.addEventListener('click', this.clickOutsideListener);
1873
+ }
1874
+ // Obtener el ancho del botón
1875
+ getSelectButtonWidth() {
1876
+ if (this.selectButton) {
1877
+ const width = this.selectButton.nativeElement.offsetWidth;
1878
+ return `${width}px`;
1879
+ }
1880
+ return '250px'; // Default fallback width
1881
+ }
1882
+ // Actualizar la posición del dropdown
1883
+ updateDropdownPosition() {
1884
+ setTimeout(() => {
1885
+ if (!this.selectButton)
1886
+ return;
1887
+ // Get button position
1888
+ const button = this.selectButton.nativeElement;
1889
+ const buttonRect = button.getBoundingClientRect();
1890
+ // Find the closest form container or dialog
1891
+ let offsetParent = this.selectButton.nativeElement;
1892
+ let isInSidebar = false;
1893
+ // Check if we're inside a sidebar form
1894
+ while (offsetParent &&
1895
+ !offsetParent.classList.contains("content_form") &&
1896
+ !offsetParent.classList.contains("p-dialog")) {
1897
+ if (offsetParent.classList.contains("fixed") && offsetParent.classList.contains("right-0")) {
1898
+ isInSidebar = true;
1899
+ break;
1900
+ }
1901
+ offsetParent = offsetParent.parentElement;
1902
+ }
1903
+ // Get offsets based on container
1904
+ let offsetTop = 0;
1905
+ let offsetLeft = 0;
1906
+ if (isInSidebar ||
1907
+ (offsetParent &&
1908
+ (offsetParent.classList.contains("content_form") || offsetParent.classList.contains("p-dialog")))) {
1909
+ offsetTop = offsetParent ? offsetParent.getBoundingClientRect().top : 0;
1910
+ offsetLeft = offsetParent ? offsetParent.getBoundingClientRect().left : 0;
1911
+ }
1912
+ // Position directly below the button by default
1913
+ this.dropdownTop = buttonRect.bottom - offsetTop;
1914
+ this.dropdownLeft = buttonRect.left - offsetLeft;
1915
+ this.dropdownWidth = buttonRect.width;
1916
+ this.cdr.detectChanges();
1917
+ // Wait for dropdown to be in DOM
1918
+ setTimeout(() => {
1919
+ // Get dropdown element
1920
+ const dropdown = this.elementRef.nativeElement.querySelector(".absolute.z-\\[100\\]");
1921
+ if (!dropdown)
1922
+ return;
1923
+ // First use fixed positioning to handle scroll position correctly
1924
+ dropdown.style.position = 'fixed';
1925
+ dropdown.style.top = buttonRect.bottom + 'px';
1926
+ dropdown.style.left = buttonRect.left + 'px';
1927
+ dropdown.style.width = buttonRect.width + 'px';
1928
+ dropdown.style.zIndex = '100';
1929
+ // Wait for dropdown to render with fixed positioning
1930
+ setTimeout(() => {
1931
+ // Get dropdown dimensions
1932
+ const dropdownRect = dropdown.getBoundingClientRect();
1933
+ const viewportHeight = window.innerHeight;
1934
+ const documentWidth = document.documentElement.clientWidth;
1935
+ // Determine if we need to flip or adjust position
1936
+ let newFixedTop = buttonRect.bottom;
1937
+ let newFixedLeft = buttonRect.left;
1938
+ // Check if dropdown goes below viewport and flip it if needed
1939
+ if (buttonRect.bottom + dropdownRect.height > viewportHeight) {
1940
+ newFixedTop = buttonRect.top - dropdownRect.height;
1941
+ }
1942
+ // Check if dropdown goes beyond right edge
1943
+ if (buttonRect.left + dropdownRect.width > documentWidth) {
1944
+ newFixedLeft = buttonRect.left - dropdownRect.width + buttonRect.width;
1945
+ if (newFixedLeft < 0) {
1946
+ newFixedLeft = 5;
1947
+ if (dropdownRect.width > documentWidth) {
1948
+ dropdown.classList.add("constrain-width");
1949
+ }
1950
+ }
1951
+ }
1952
+ // Apply adjusted fixed position
1953
+ dropdown.style.top = newFixedTop - 5 + 'px';
1954
+ dropdown.style.left = newFixedLeft - 15 + 'px';
1955
+ // Es boton de filtro
1956
+ if (this.isFilterSelect) {
1957
+ setTimeout(() => {
1958
+ // Calculate absolute position based on the current fixed position
1959
+ let newAbsoluteTop;
1960
+ let newAbsoluteLeft;
1961
+ if (newFixedTop === buttonRect.bottom) {
1962
+ // Dropdown is below the button
1963
+ newAbsoluteTop = this.dropdownTop;
1964
+ }
1965
+ else {
1966
+ // Dropdown is above the button (flipped)
1967
+ newAbsoluteTop = buttonRect.top - dropdownRect.height - offsetTop;
1968
+ }
1969
+ if (newFixedLeft === buttonRect.left) {
1970
+ // Dropdown is aligned with left edge of button
1971
+ newAbsoluteLeft = this.dropdownLeft;
1972
+ }
1973
+ else if (newFixedLeft === buttonRect.left - dropdownRect.width + buttonRect.width) {
1974
+ // Dropdown is aligned with right edge of button
1975
+ newAbsoluteLeft = buttonRect.left - dropdownRect.width + buttonRect.width - offsetLeft;
1976
+ }
1977
+ else {
1978
+ // Dropdown is at a fixed position from left edge
1979
+ newAbsoluteLeft = newFixedLeft;
1980
+ }
1981
+ // Switch to absolute positioning
1982
+ dropdown.style.position = 'absolute';
1983
+ dropdown.style.top = newAbsoluteTop + 'px';
1984
+ dropdown.style.left = newAbsoluteLeft - 2 + 'px';
1985
+ // Update internal state
1986
+ this.dropdownTop = newAbsoluteTop;
1987
+ this.dropdownLeft = newAbsoluteLeft;
1988
+ this.cdr.detectChanges();
1989
+ }, 0);
1990
+ }
1991
+ else {
1992
+ // After positioning is done, switch to absolute positioning
1993
+ setTimeout(() => {
1994
+ // Get the current visual position of the dropdown (relative to viewport)
1995
+ const dropdownRect = dropdown.getBoundingClientRect();
1996
+ // Find the dropdown's offset parent for absolute positioning
1997
+ const dropdownOffsetParent = this.findPositionedParent(dropdown) || document.body;
1998
+ const parentRect = dropdownOffsetParent.getBoundingClientRect();
1999
+ // Calculate absolute position that will maintain the same visual position
2000
+ // Absolute positioning is relative to the offset parent
2001
+ const absoluteTop = dropdownRect.top - parentRect.top + dropdownOffsetParent.scrollTop;
2002
+ const absoluteLeft = dropdownRect.left - parentRect.left + dropdownOffsetParent.scrollLeft;
2003
+ // Switch to absolute positioning with calculated coordinates
2004
+ dropdown.style.position = 'absolute';
2005
+ dropdown.style.top = absoluteTop + 'px';
2006
+ dropdown.style.left = absoluteLeft + 'px';
2007
+ // Update internal state
2008
+ this.dropdownTop = absoluteTop;
2009
+ this.dropdownLeft = absoluteLeft;
2010
+ this.cdr.detectChanges();
2011
+ }, 0);
2012
+ }
2013
+ }, 0);
2014
+ }, 0);
2015
+ });
2016
+ }
2017
+ // Helper method to find the offset parent for absolute positioning
2018
+ findPositionedParent(element) {
2019
+ if (!element)
2020
+ return null;
2021
+ let parent = element.parentElement;
2022
+ while (parent) {
2023
+ const position = window.getComputedStyle(parent).position;
2024
+ if (position === 'relative' || position === 'absolute' || position === 'fixed') {
2025
+ return parent;
2026
+ }
2027
+ parent = parent.parentElement;
2028
+ }
2029
+ return document.body; // Default to body if no positioned parent found
2030
+ }
2031
+ // Leer el valor seleccionado
2032
+ writeValue(value) {
2033
+ this.selectedValue = value;
2034
+ // Si las opciones ya están cargadas, intenta mostrar el label ahora mismo
2035
+ if (this.internalOptions.length > 0) {
2036
+ this.updateSelectedLabel();
2037
+ }
2038
+ // Si no hay opciones cargadas aún, espera a que se carguen en loadData()
2039
+ this.cdr.markForCheck();
2040
+ }
2041
+ // ================================================
2042
+ // Registrar cambios
2043
+ // ================================================
2044
+ // Registrar el cambio del valor seleccionado
2045
+ registerOnChange(fn) {
2046
+ this.onChange = fn;
2047
+ }
2048
+ // Registrar el evento de tocar
2049
+ registerOnTouched(fn) {
2050
+ this.onTouched = fn;
2051
+ }
2052
+ // Cargar elementos del searhable cuando es necesario
2053
+ shouldTriggerLoad() {
2054
+ const isSearchActive = this.searchTerm && this.searchTerm.trim() !== '';
2055
+ const isInitialState = this.selectedValue === null;
2056
+ return isSearchActive || isInitialState;
2057
+ }
2058
+ 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 });
2059
+ 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: [
2060
+ {
2061
+ provide: NG_VALUE_ACCESSOR,
2062
+ useExisting: JSelectComponent,
2063
+ multi: true,
2064
+ }
2065
+ ], 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: [
2066
+ trigger("modalTransition", [
2067
+ transition(":enter", [
2068
+ style({ transform: "translateX(1rem)", opacity: 0 }),
2069
+ animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
2070
+ ]),
2071
+ transition(":leave", [
2072
+ animate("150ms ease-in", style({ transform: "translateX(1rem)", opacity: 0 })),
2073
+ ]),
2074
+ ]),
2075
+ ] });
2076
+ }
2077
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JSelectComponent, decorators: [{
2078
+ type: Component,
2079
+ args: [{ selector: 'JSelect', standalone: true, imports: [LucideAngularModule, JButtonComponent, CommonModule, FormsModule, ReactiveFormsModule], animations: [
2080
+ trigger("modalTransition", [
2081
+ transition(":enter", [
2082
+ style({ transform: "translateX(1rem)", opacity: 0 }),
2083
+ animate("300ms ease-out", style({ transform: "translateY(0)", opacity: 1 })),
2084
+ ]),
2085
+ transition(":leave", [
2086
+ animate("150ms ease-in", style({ transform: "translateX(1rem)", opacity: 0 })),
2087
+ ]),
2088
+ ]),
2089
+ ], providers: [
2090
+ {
2091
+ provide: NG_VALUE_ACCESSOR,
2092
+ useExisting: JSelectComponent,
2093
+ multi: true,
2094
+ }
2095
+ ], 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}" }]
2096
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: JGenericService }], propDecorators: { type: [{
2097
+ type: Input
2098
+ }], btnIcon: [{
2099
+ type: Input
2100
+ }], btnText: [{
2101
+ type: Input
2102
+ }], title: [{
2103
+ type: Input
2104
+ }], placeholder: [{
2105
+ type: Input
2106
+ }], showClear: [{
2107
+ type: Input
2108
+ }], columns: [{
2109
+ type: Input
2110
+ }], options: [{
2111
+ type: Input
2112
+ }], optionLabel: [{
2113
+ type: Input
2114
+ }], optionValue: [{
2115
+ type: Input
2116
+ }], labelSeparator: [{
2117
+ type: Input
2118
+ }], isLoading: [{
2119
+ type: Input
2120
+ }], endpoint: [{
2121
+ type: Input
2122
+ }], loadOnInit: [{
2123
+ type: Input
2124
+ }], defaultFilters: [{
2125
+ type: Input
2126
+ }], searchFields: [{
2127
+ type: Input
2128
+ }], isSearch: [{
2129
+ type: Input
2130
+ }], isFilterSelect: [{
2131
+ type: Input
2132
+ }], sort: [{
2133
+ type: Input
2134
+ }], updateVisibility: [{
2135
+ type: Output
2136
+ }], selectionChange: [{
2137
+ type: Output
2138
+ }], optionComponents: [{
2139
+ type: ContentChildren,
2140
+ args: [OptionComponent]
2141
+ }], selectButton: [{
2142
+ type: ViewChild,
2143
+ args: ['selectButton']
2144
+ }], disabled: [{
2145
+ type: Input
2146
+ }] } });
2147
+
2148
+ class JToggleRadioComponent {
2149
+ genericService;
2150
+ icons = {
2151
+ loading: Loader2,
2152
+ };
2153
+ options = [];
2154
+ optionLabel = 'label'; // Propiedad anidada para label
2155
+ optionValue = 'value'; // Propiedad para value
2156
+ endpoint = '';
2157
+ loadOnInit = false;
2158
+ defaultFilters = {};
2159
+ showClear = false;
2160
+ classes = '';
2161
+ classesElement = '';
2162
+ disabled = false;
2163
+ sort = 'ASC';
2164
+ selectFirstOnLoad = false;
2165
+ selectionChange = new EventEmitter();
2166
+ internalOptions = [];
2167
+ selectedValue = null;
2168
+ isLoading = false;
2169
+ constructor(genericService) {
2170
+ this.genericService = genericService;
2171
+ }
2172
+ ngOnInit() {
2173
+ if (this.endpoint && this.loadOnInit) {
2174
+ this.loadOptionsFromApi();
2175
+ }
2176
+ else {
2177
+ this.processOptions();
2178
+ }
2179
+ }
2180
+ isComponentDisabled = false;
2181
+ setDisabledState(isDisabled) {
2182
+ this.isComponentDisabled = isDisabled;
2183
+ }
2184
+ // Cargar opciones desde el API
2185
+ loadOptionsFromApi() {
2186
+ this.isLoading = true;
2187
+ const params = {};
2188
+ params['sortOrder'] = this.sort;
2189
+ Object.keys(this.defaultFilters).forEach((key) => {
2190
+ params[`filter[${key}]`] = this.defaultFilters[key];
2191
+ });
2192
+ this.genericService.getAll(this.endpoint, params).subscribe({
2193
+ next: (res) => {
2194
+ const data = res.data[this.endpoint] || [];
2195
+ this.options = data;
2196
+ this.processOptions();
2197
+ this.isLoading = false;
2198
+ // Si el parámetro selectFirstOnLoad es true, seleccionar el primer elemento
2199
+ if (this.selectFirstOnLoad && this.internalOptions.length > 0) {
2200
+ this.selectedValue = this.internalOptions[0].value;
2201
+ this.onChange(this.selectedValue);
2202
+ this.selectionChange.emit(this.selectedValue);
2203
+ }
2204
+ },
2205
+ error: () => (this.isLoading = false),
2206
+ });
2207
+ }
2208
+ // Función para obtener valores anidados de un objeto
2209
+ getNestedValue(obj, path) {
2210
+ return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? '';
2211
+ }
2212
+ // Procesar opciones para tener el valor y el label
2213
+ processOptions() {
2214
+ if (this.options.length > 0 && typeof this.options[0] === 'object') {
2215
+ this.internalOptions = this.options.map((opt) => ({
2216
+ value: opt[this.optionValue],
2217
+ label: this.getNestedValue(opt, this.optionLabel), // Usar la función para obtener propiedades anidadas
2218
+ }));
2219
+ }
2220
+ else {
2221
+ this.internalOptions = this.options.map((opt) => ({
2222
+ value: opt,
2223
+ label: opt.toString(),
2224
+ }));
2225
+ }
2226
+ }
2227
+ select(value) {
2228
+ if (this.disabled || this.isComponentDisabled)
2229
+ return;
2230
+ this.selectedValue = value;
2231
+ this.onChange(this.selectedValue);
2232
+ this.selectionChange.emit(this.selectedValue);
2233
+ }
2234
+ clear() {
2235
+ this.selectedValue = null;
2236
+ this.onChange(null);
2237
+ this.selectionChange.emit(null);
2238
+ }
2239
+ // ControlValueAccessor
2240
+ onChange = () => { };
2241
+ onTouched = () => { };
2242
+ writeValue(value) {
2243
+ this.selectedValue = value;
2244
+ }
2245
+ registerOnChange(fn) {
2246
+ this.onChange = fn;
2247
+ }
2248
+ registerOnTouched(fn) {
2249
+ this.onTouched = fn;
2250
+ }
2251
+ // Método para recargar las opciones cuando cambia el filtro
2252
+ reloadOptions() {
2253
+ this.loadOptionsFromApi(); // Recargar las opciones con los nuevos filtros
2254
+ }
2255
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JToggleRadioComponent, deps: [{ token: JGenericService }], target: i0.ɵɵFactoryTarget.Component });
2256
+ 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: [
2257
+ {
2258
+ provide: NG_VALUE_ACCESSOR,
2259
+ useExisting: forwardRef(() => JToggleRadioComponent),
2260
+ multi: true,
2261
+ },
2262
+ ], 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"] }] });
2263
+ }
2264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JToggleRadioComponent, decorators: [{
2265
+ type: Component,
2266
+ args: [{ selector: 'JToggleRadio', imports: [LucideAngularModule, CommonModule], providers: [
2267
+ {
2268
+ provide: NG_VALUE_ACCESSOR,
2269
+ useExisting: forwardRef(() => JToggleRadioComponent),
2270
+ multi: true,
2271
+ },
2272
+ ], 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 " }]
2273
+ }], ctorParameters: () => [{ type: JGenericService }], propDecorators: { options: [{
2274
+ type: Input
2275
+ }], optionLabel: [{
2276
+ type: Input
2277
+ }], optionValue: [{
2278
+ type: Input
2279
+ }], endpoint: [{
2280
+ type: Input
2281
+ }], loadOnInit: [{
2282
+ type: Input
2283
+ }], defaultFilters: [{
2284
+ type: Input
2285
+ }], showClear: [{
2286
+ type: Input
2287
+ }], classes: [{
2288
+ type: Input
2289
+ }], classesElement: [{
2290
+ type: Input
2291
+ }], disabled: [{
2292
+ type: Input
2293
+ }], sort: [{
2294
+ type: Input
2295
+ }], selectFirstOnLoad: [{
2296
+ type: Input
2297
+ }], selectionChange: [{
2298
+ type: Output
2299
+ }] } });
2300
+
2301
+ class ThemeService {
2302
+ _theme = signal('light');
2303
+ themeSignal = this._theme.asReadonly();
2304
+ constructor() {
2305
+ this.initializeTheme();
2306
+ this.listenToExternalChanges(); // 👈 importante
2307
+ }
2308
+ initializeTheme() {
2309
+ const savedTheme = localStorage.getItem('theme');
2310
+ if (savedTheme) {
2311
+ this._theme.set(savedTheme);
2312
+ }
2313
+ else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
2314
+ this._theme.set('dark');
2315
+ }
2316
+ this.applyTheme();
2317
+ }
2318
+ listenToExternalChanges() {
2319
+ window.addEventListener('storage', (event) => {
2320
+ if (event.key === 'theme' && event.newValue) {
2321
+ const newTheme = event.newValue;
2322
+ this._theme.set(newTheme);
2323
+ this.applyTheme();
2324
+ }
2325
+ });
2326
+ }
2327
+ applyTheme() {
2328
+ const current = this._theme();
2329
+ document.documentElement.classList.toggle('dark', current === 'dark');
2330
+ }
2331
+ toggleTheme() {
2332
+ const newTheme = this._theme() === 'light' ? 'dark' : 'light';
2333
+ localStorage.setItem('theme', newTheme);
2334
+ this._theme.set(newTheme);
2335
+ this.applyTheme();
2336
+ }
2337
+ setTheme(theme) {
2338
+ localStorage.setItem('theme', theme);
2339
+ this._theme.set(theme);
2340
+ this.applyTheme();
2341
+ }
2342
+ getThemeMode() {
2343
+ return this._theme();
2344
+ }
2345
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2346
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
2347
+ }
2348
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: ThemeService, decorators: [{
2349
+ type: Injectable,
2350
+ args: [{ providedIn: 'root' }]
2351
+ }], ctorParameters: () => [] });
2352
+
2353
+ class JContentFormComponent {
2354
+ columns = 1;
2355
+ rows = false;
2356
+ getClasses() {
2357
+ if (this.rows)
2358
+ return 'flex flex-row gap-3 items-center';
2359
+ const base = 'grid gap-2';
2360
+ const columnClassMap = {
2361
+ 1: 'flex flex-col gap-1',
2362
+ 2: 'grid-cols-1 sm:grid-cols-2',
2363
+ 3: 'grid-cols-1 sm:grid-cols-3',
2364
+ 4: 'grid-cols-1 sm:grid-cols-4',
2365
+ 5: 'grid-cols-1 sm:grid-cols-5',
2366
+ 6: 'grid-cols-1 sm:grid-cols-6',
2367
+ };
2368
+ return `${base} ${columnClassMap[this.columns] || columnClassMap[1]}`;
2369
+ }
2370
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JContentFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2371
+ 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"] }] });
2372
+ }
2373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JContentFormComponent, decorators: [{
2374
+ type: Component,
2375
+ args: [{ selector: 'JContentForm', imports: [NgClass], template: "<div [ngClass]=\"getClasses()\">\r\n <ng-content></ng-content>\r\n</div>" }]
2376
+ }], propDecorators: { columns: [{
2377
+ type: Input
2378
+ }], rows: [{
2379
+ type: Input
2380
+ }] } });
2381
+
2382
+ class JThemeComponent {
2383
+ themeService = inject(ThemeService);
2384
+ icons = {
2385
+ copy: Copy,
2386
+ check: Check,
2387
+ sun: Sun,
2388
+ moon: Moon,
2389
+ };
2390
+ // Color picker state
2391
+ baseColor = '#415884';
2392
+ saturation = 34.01;
2393
+ lightness = 38.63;
2394
+ huePosition = 219;
2395
+ colorPickerPosition = { x: 50, y: 50 };
2396
+ // Sync preview with signal
2397
+ previewMode = localStorage.getItem('theme') || 'light';
2398
+ lastTheme = this.previewMode;
2399
+ isPickingColor = false;
2400
+ copied = false;
2401
+ themeCode = '';
2402
+ generatedColors;
2403
+ ngOnInit() {
2404
+ this.syncFromHSL();
2405
+ // Sincronizacion del tema con el localStorage
2406
+ setInterval(() => {
2407
+ const current = localStorage.getItem('theme');
2408
+ if (current && current !== this.lastTheme) {
2409
+ this.previewMode = current;
2410
+ this.lastTheme = current;
2411
+ this.generateTheme();
2412
+ }
2413
+ }, 300); // verifica cada 300ms
2414
+ }
2415
+ // Color picker gradient
2416
+ get hueGradient() {
2417
+ return `linear-gradient(to right,
2418
+ hsl(0, 100%, 50%),
2419
+ hsl(60, 100%, 50%),
2420
+ hsl(120, 100%, 50%),
2421
+ hsl(180, 100%, 50%),
2422
+ hsl(240, 100%, 50%),
2423
+ hsl(300, 100%, 50%),
2424
+ hsl(360, 100%, 50%))`;
2425
+ }
2426
+ getCurrentTheme() {
2427
+ return this.themeService.getThemeMode();
2428
+ }
2429
+ toggleTheme() {
2430
+ this.themeService.toggleTheme();
2431
+ }
2432
+ setPreviewMode(mode) {
2433
+ this.previewMode = mode;
2434
+ }
2435
+ startColorPicking(event) {
2436
+ this.isPickingColor = true;
2437
+ this.updateColorFromPosition(event);
2438
+ }
2439
+ updateColorPicking(event) {
2440
+ if (this.isPickingColor) {
2441
+ this.updateColorFromPosition(event);
2442
+ }
2443
+ }
2444
+ stopColorPicking() {
2445
+ this.isPickingColor = false;
2446
+ }
2447
+ updateColorFromPosition(event) {
2448
+ const target = event.target;
2449
+ const rect = target.getBoundingClientRect();
2450
+ const x = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
2451
+ const y = Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height));
2452
+ this.colorPickerPosition = { x: x * 100, y: y * 100 };
2453
+ this.saturation = x * 100;
2454
+ this.lightness = (1 - y) * 100;
2455
+ this.syncFromHSL();
2456
+ }
2457
+ updateHueFromInput(event) {
2458
+ const input = event.target;
2459
+ this.huePosition = parseInt(input.value);
2460
+ this.syncFromHSL();
2461
+ }
2462
+ updateFromHexInput() {
2463
+ if (!this.baseColor.startsWith('#'))
2464
+ this.baseColor = '#' + this.baseColor;
2465
+ const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
2466
+ if (!hexRegex.test(this.baseColor))
2467
+ return;
2468
+ const { h, s, l } = this.hexToHSL(this.baseColor);
2469
+ this.huePosition = h;
2470
+ this.saturation = s;
2471
+ this.lightness = l;
2472
+ this.colorPickerPosition = { x: s, y: 100 - l };
2473
+ this.syncFromHSL();
2474
+ }
2475
+ syncFromHSL() {
2476
+ this.baseColor = this.hslToHex(this.huePosition, this.saturation, this.lightness);
2477
+ this.generateTheme();
2478
+ }
2479
+ hexToHSL(hex) {
2480
+ hex = hex.replace(/^#/, '');
2481
+ if (hex.length === 3)
2482
+ hex = hex.split('').map(c => c + c).join('');
2483
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
2484
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
2485
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
2486
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
2487
+ let h = 0, s = 0, l = (max + min) / 2;
2488
+ if (max !== min) {
2489
+ const d = max - min;
2490
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2491
+ switch (max) {
2492
+ case r:
2493
+ h = (g - b) / d + (g < b ? 6 : 0);
2494
+ break;
2495
+ case g:
2496
+ h = (b - r) / d + 2;
2497
+ break;
2498
+ case b:
2499
+ h = (r - g) / d + 4;
2500
+ break;
2501
+ }
2502
+ h *= 60;
2503
+ }
2504
+ return { h, s: s * 100, l: l * 100 };
2505
+ }
2506
+ hslToHex(h, s, l) {
2507
+ h %= 360;
2508
+ s /= 100;
2509
+ l /= 100;
2510
+ const c = (1 - Math.abs(2 * l - 1)) * s;
2511
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
2512
+ const m = l - c / 2;
2513
+ let r = 0, g = 0, b = 0;
2514
+ if (h < 60)
2515
+ [r, g, b] = [c, x, 0];
2516
+ else if (h < 120)
2517
+ [r, g, b] = [x, c, 0];
2518
+ else if (h < 180)
2519
+ [r, g, b] = [0, c, x];
2520
+ else if (h < 240)
2521
+ [r, g, b] = [0, x, c];
2522
+ else if (h < 300)
2523
+ [r, g, b] = [x, 0, c];
2524
+ else
2525
+ [r, g, b] = [c, 0, x];
2526
+ r = Math.round((r + m) * 255);
2527
+ g = Math.round((g + m) * 255);
2528
+ b = Math.round((b + m) * 255);
2529
+ return `#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
2530
+ }
2531
+ generateTheme() {
2532
+ this.baseColor = this.hslToHex(this.huePosition, this.saturation * 0.9, this.lightness);
2533
+ const vars = this.generatedColors = {
2534
+ background: this.hslToHex(this.huePosition, this.saturation, 95),
2535
+ foreground: this.hslToHex(210, 6, 10),
2536
+ card: this.hslToHex(this.huePosition, this.saturation * 0.3, 90),
2537
+ cardForeground: this.hslToHex(this.huePosition, this.saturation * 0.8, 15),
2538
+ popover: this.hslToHex(this.huePosition, this.saturation * 0.2, 95),
2539
+ popoverForeground: this.hslToHex(this.huePosition, this.saturation, 7),
2540
+ primary: this.baseColor,
2541
+ primaryForeground: '#FFFFFF',
2542
+ secondary: this.hslToHex(this.huePosition, this.saturation * 0.4, 75),
2543
+ secondaryForeground: '#000000',
2544
+ muted: this.hslToHex(this.huePosition, this.saturation * 0.1, 85),
2545
+ mutedForeground: this.hslToHex(this.huePosition, this.saturation * 0.5, 40),
2546
+ accent: this.hslToHex(this.huePosition, this.saturation * 0.1, 82),
2547
+ accentForeground: this.hslToHex(this.huePosition, this.saturation * 0.8, 15),
2548
+ destructive: '#BF3F3F',
2549
+ destructiveForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 95),
2550
+ border: this.hslToHex(this.huePosition, this.saturation * 0.5, 60),
2551
+ input: this.hslToHex(this.huePosition, this.saturation * 0.5, 60),
2552
+ ring: this.baseColor,
2553
+ darkBackground: this.mixHsl(this.huePosition, this.saturation * 0.7, 8, // base
2554
+ 210, 6, 10, // toque azulado oscuro
2555
+ 0.6 // intensidad del mix (15%)
2556
+ ),
2557
+ darkForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
2558
+ darkCard: this.hslToHex(this.huePosition, this.saturation * 0.8, 8),
2559
+ darkCardForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
2560
+ darkPopover: this.hslToHex(this.huePosition, this.saturation * 0.8, 3),
2561
+ darkPopoverForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
2562
+ darkPrimary: this.hslToHex(this.huePosition, this.saturation * 0.7, 28),
2563
+ darkPrimaryForeground: '#FFFFFF',
2564
+ darkSecondary: this.hslToHex(this.huePosition, this.saturation * 0.6, 20),
2565
+ darkSecondaryForeground: '#FFFFFF',
2566
+ darkMuted: this.hslToHex(this.huePosition, this.saturation * 0.3, 25),
2567
+ darkMutedForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 60),
2568
+ darkAccent: this.hslToHex(this.huePosition, this.saturation * 0.3, 25),
2569
+ darkAccentForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
2570
+ darkDestructive: '#BF3F3F',
2571
+ darkDestructiveForeground: this.hslToHex(this.huePosition, this.saturation * 0.2, 90),
2572
+ darkBorder: this.hslToHex(this.huePosition, this.saturation * 0.5, 45),
2573
+ darkInput: this.hslToHex(this.huePosition, this.saturation * 0.5, 45),
2574
+ darkRing: this.hslToHex(this.huePosition, this.saturation * 0.7, 28),
2575
+ };
2576
+ const toCssVar = (key, value) => ` --color-${key.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${value};`;
2577
+ const keys = Object.keys(vars);
2578
+ const lines = [];
2579
+ let darkStarted = false;
2580
+ for (const key of keys) {
2581
+ if (!darkStarted && key.startsWith('dark')) {
2582
+ lines.push(''); // Insertamos línea vacía justo antes del primer dark
2583
+ darkStarted = true;
2584
+ }
2585
+ lines.push(toCssVar(key, vars[key]));
2586
+ }
2587
+ lines.push(` --color-radius: 0.5rem;`);
2588
+ lines.push(` --color-dark-radius: 0.5rem;`);
2589
+ this.themeCode = `@theme {\n${lines.join('\n')}\n}`;
2590
+ }
2591
+ copyThemeToClipboard() {
2592
+ navigator.clipboard.writeText(this.themeCode);
2593
+ this.copied = true;
2594
+ setTimeout(() => (this.copied = false), 2000);
2595
+ }
2596
+ mixHsl(h1, s1, l1, h2, s2, l2, ratio) {
2597
+ const h = h1 * (1 - ratio) + h2 * ratio;
2598
+ const s = s1 * (1 - ratio) + s2 * ratio;
2599
+ const l = l1 * (1 - ratio) + l2 * ratio;
2600
+ return this.hslToHex(h, s, l);
2601
+ }
2602
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JThemeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2603
+ 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"] }] });
2604
+ }
2605
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JThemeComponent, decorators: [{
2606
+ type: Component,
2607
+ 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>" }]
2608
+ }] });
2609
+
2610
+ class JPaginatorComponent {
2611
+ Math = Math;
2612
+ // Lucide icons
2613
+ icons = {
2614
+ firstPage: ChevronsLeft,
2615
+ prevPage: ChevronLeft,
2616
+ nextPage: ChevronRight,
2617
+ lastPage: ChevronsRight,
2618
+ };
2619
+ isLoading = false;
2620
+ // Para rastrear qué botón está siendo cargado
2621
+ loadingButton = null;
2622
+ // Paginacion
2623
+ currentPage = 1;
2624
+ itemsPerPageOptions = [10];
2625
+ itemsPerPage = this.itemsPerPageOptions[0];
2626
+ totalItems = 0;
2627
+ // Paginas a mostrar
2628
+ pages = [];
2629
+ // Eventos de salida
2630
+ pageChange = new EventEmitter();
2631
+ get startIndex() {
2632
+ return (this.currentPage - 1) * this.itemsPerPage;
2633
+ }
2634
+ get endIndex() {
2635
+ return Math.min(this.startIndex + this.itemsPerPage - 1, this.totalItems - 1);
2636
+ }
2637
+ get totalPages() {
2638
+ return Math.ceil(this.totalItems / this.itemsPerPage);
2639
+ }
2640
+ onPageChange(page) {
2641
+ if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
2642
+ this.loadingButton = page;
2643
+ this.currentPage = page;
2644
+ this.pageChange.emit(this.currentPage);
2645
+ }
2646
+ }
2647
+ goToFirstPage() {
2648
+ if (!this.isLoading && this.currentPage !== 1) {
2649
+ this.loadingButton = 'first';
2650
+ this.onPageChange(1);
2651
+ }
2652
+ }
2653
+ goToPreviousPage() {
2654
+ if (!this.isLoading && this.currentPage > 1) {
2655
+ this.loadingButton = 'prev';
2656
+ this.onPageChange(this.currentPage - 1);
2657
+ }
2658
+ }
2659
+ goToNextPage() {
2660
+ if (!this.isLoading && this.currentPage < this.totalPages) {
2661
+ this.loadingButton = 'next';
2662
+ this.onPageChange(this.currentPage + 1);
2663
+ }
2664
+ }
2665
+ goToLastPage() {
2666
+ if (!this.isLoading && this.currentPage !== this.totalPages) {
2667
+ this.loadingButton = 'last';
2668
+ this.onPageChange(this.totalPages);
2669
+ }
2670
+ }
2671
+ // Verificar si un botón específico está cargando
2672
+ isButtonLoading(button) {
2673
+ return this.isLoading && this.loadingButton === button;
2674
+ }
2675
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JPaginatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2676
+ 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"] }] });
2677
+ }
2678
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JPaginatorComponent, decorators: [{
2679
+ type: Component,
2680
+ 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>" }]
2681
+ }], propDecorators: { isLoading: [{
2682
+ type: Input
2683
+ }], currentPage: [{
2684
+ type: Input
2685
+ }], itemsPerPageOptions: [{
2686
+ type: Input
2687
+ }], itemsPerPage: [{
2688
+ type: Input
2689
+ }], totalItems: [{
2690
+ type: Input
2691
+ }], pages: [{
2692
+ type: Input
2693
+ }], pageChange: [{
2694
+ type: Output
2695
+ }] } });
2696
+
2697
+ class JFilterComponent {
2698
+ dialog;
2699
+ alertToastService;
2700
+ cdr;
2701
+ // Lucide icons
2702
+ icons = {
2703
+ filter: Filter,
2704
+ filterList: ArrowDownWideNarrow,
2705
+ eraser: Eraser,
2706
+ transh: Trash2,
2707
+ check: Check,
2708
+ clear: X,
2709
+ search: Search,
2710
+ loading: Loader2,
2711
+ default: Cpu
2712
+ };
2713
+ isLoadingSearch = false;
2714
+ isLoadingPerPage = false;
2715
+ isLoadingAditionalButtons = {};
2716
+ searchPlaceholder = 'Buscar...';
2717
+ // Inputs
2718
+ columns = [];
2719
+ itemsPerPageOptions = [];
2720
+ // Outputs
2721
+ search = new EventEmitter();
2722
+ itemsPerPageChange = new EventEmitter();
2723
+ searchQueryChange = new EventEmitter();
2724
+ onItemsPerPageChangeEvent = new EventEmitter();
2725
+ clearFilters = new EventEmitter();
2726
+ // Para el binding bidireccional de itemsPerPage
2727
+ _itemsPerPage = 0;
2728
+ get itemsPerPage() {
2729
+ return this._itemsPerPage;
2730
+ }
2731
+ // Para el binding bidireccional de searchQuery
2732
+ _searchQuery = '';
2733
+ get searchQuery() {
2734
+ return this._searchQuery;
2735
+ }
2736
+ // Para el debounce de la búsqueda
2737
+ searchSubject = new Subject();
2738
+ searchSubscription;
2739
+ isInitialized = false;
2740
+ // Configuración de botones
2741
+ filtersButton = [];
2742
+ // Filtros de tabla
2743
+ filtersSelect = [];
2744
+ get visibleColumns() {
2745
+ return this.columns.filter(col => !col.hidden);
2746
+ }
2747
+ constructor(dialog, alertToastService, cdr) {
2748
+ this.dialog = dialog;
2749
+ this.alertToastService = alertToastService;
2750
+ this.cdr = cdr;
2751
+ }
2752
+ ngOnInit() {
2753
+ // Configurar el debounce para la búsqueda después de la inicialización
2754
+ this.searchSubscription = this.searchSubject
2755
+ .pipe(debounceTime(1000), // 1 segundo de debounce
2756
+ filter(() => this.isInitialized) // Solo procesar si está inicializado
2757
+ )
2758
+ .subscribe(() => { this.onSearch(); });
2759
+ // Marcar como inicializado después de configurar la suscripción
2760
+ setTimeout(() => { this.isInitialized = true; }, 0);
2761
+ // Normalizar botones
2762
+ this.filtersButton = this.normalizeFilterButtons(this.filtersButton);
2763
+ }
2764
+ ngOnDestroy() {
2765
+ // Limpiar la suscripción al destruir el componente
2766
+ if (this.searchSubscription) {
2767
+ this.searchSubscription.unsubscribe();
2768
+ }
2769
+ }
2770
+ // =====================================================
2771
+ // Bindings
2772
+ // =====================================================
2773
+ // Binding bidireccional de itemsPerPage
2774
+ set itemsPerPage(value) {
2775
+ this._itemsPerPage = value;
2776
+ this.itemsPerPageChange.emit(value);
2777
+ }
2778
+ // Binding bidireccional de searchQuery
2779
+ set searchQuery(value) {
2780
+ // Solo emitir eventos si el valor ha cambiado
2781
+ if (this._searchQuery !== value) {
2782
+ this._searchQuery = value;
2783
+ this.searchQueryChange.emit(value);
2784
+ // Solo emitir al subject si ya se ha inicializado el componente
2785
+ if (this.isInitialized) {
2786
+ this.searchSubject.next(value);
2787
+ }
2788
+ }
2789
+ }
2790
+ // =====================================================
2791
+ // Metodos
2792
+ // =====================================================
2793
+ // Buscar
2794
+ onSearch() {
2795
+ this.search.emit();
2796
+ }
2797
+ // Eliminar busqueda
2798
+ clearSearch() {
2799
+ this.searchQuery = '';
2800
+ this.onSearch();
2801
+ }
2802
+ // Mostrar items por pagina
2803
+ onItemsPerPageChange() {
2804
+ this.onItemsPerPageChangeEvent.emit();
2805
+ }
2806
+ // =====================================================
2807
+ // Botones de filtro
2808
+ // =====================================================
2809
+ // Normaliza los botones de filtro
2810
+ normalizeFilterButtons(buttons) {
2811
+ return buttons.map((button) => {
2812
+ // Si es un botón de filtro
2813
+ if (button.type === 'filter') {
2814
+ return {
2815
+ ...button,
2816
+ icon: this.icons['filter'],
2817
+ iconChange: this.icons['filterList'],
2818
+ isChangeIcon: () => this.dialog.openDialog,
2819
+ clicked: () => {
2820
+ this.dialog.openDialog = !this.dialog.openDialog;
2821
+ },
2822
+ classes: 'bg-[#20638f] hover:bg-[#1d5a82] text-white'
2823
+ };
2824
+ }
2825
+ // Si es un botón de limpiar
2826
+ if (button.type === 'clear') {
2827
+ return {
2828
+ ...button,
2829
+ icon: this.icons['eraser'],
2830
+ iconChange: this.icons['transh'],
2831
+ tooltip: 'Limpiar filtros',
2832
+ clicked: () => {
2833
+ const hasActiveSearch = this.searchQuery?.trim().length > 0;
2834
+ const hasSelectedFilters = this.filtersSelect.some(f => {
2835
+ const actual = f.selected;
2836
+ const inicial = f.initSelected ?? null;
2837
+ return JSON.stringify(actual) !== JSON.stringify(inicial);
2838
+ });
2839
+ if (!hasActiveSearch && !hasSelectedFilters) {
2840
+ this.alertToastService.AlertToast({
2841
+ type: 'info',
2842
+ title: 'No se ha filtrado nada...',
2843
+ description: 'No hay filtros activos para limpiar',
2844
+ autoClose: true
2845
+ });
2846
+ return;
2847
+ }
2848
+ // Limpiar búsqueda
2849
+ if (hasActiveSearch) {
2850
+ this.clearSearch();
2851
+ }
2852
+ // Limpiar selects
2853
+ for (const filter of this.filtersSelect) {
2854
+ const initial = filter.initSelected ?? null;
2855
+ const current = filter.selected;
2856
+ if (JSON.stringify(current) !== JSON.stringify(initial)) {
2857
+ filter.selected = initial;
2858
+ setTimeout(() => {
2859
+ if (typeof filter.onSelected === 'function') {
2860
+ filter.onSelected(initial);
2861
+ }
2862
+ });
2863
+ }
2864
+ }
2865
+ this.clearFilters.emit('clear');
2866
+ },
2867
+ isChangeIcon: () => {
2868
+ const hasActiveSearch = this.searchQuery?.trim().length > 0;
2869
+ const hasSelectedFilters = this.filtersSelect.some(f => {
2870
+ const actual = f.selected;
2871
+ const inicial = f.initSelected ?? null;
2872
+ return JSON.stringify(actual) !== JSON.stringify(inicial);
2873
+ });
2874
+ return !hasActiveSearch && !hasSelectedFilters ? false : true;
2875
+ },
2876
+ classes: 'bg-[#164666] hover:bg-[#133d5a] text-white'
2877
+ };
2878
+ }
2879
+ // Podés extenderlo para otros tipos como 'clear', 'excel', etc.
2880
+ return button;
2881
+ });
2882
+ }
2883
+ // Manejar el click en un botón de filtro
2884
+ filterBottomClick(button) {
2885
+ // Ejecuta la acción del padre si está definida
2886
+ if (button.clicked) {
2887
+ button.clicked();
2888
+ }
2889
+ this.isButtonLoading(button);
2890
+ }
2891
+ isButtonLoading(button) {
2892
+ return button.type ? this.isLoadingAditionalButtons[button.type] === 'loading' : false;
2893
+ }
2894
+ // =====================================================
2895
+ // Acciones para validacion de un tipo a funcion
2896
+ // =====================================================
2897
+ // Obtener el ícono de un botón de filtro
2898
+ getIsChangeIcon(button) {
2899
+ if (typeof button.isChangeIcon === 'function') {
2900
+ return button.isChangeIcon();
2901
+ }
2902
+ return button.isChangeIcon ?? false;
2903
+ }
2904
+ // Obtener el valor del tooltip de un botón
2905
+ getTooltip(button) {
2906
+ if (typeof button.tooltip === 'function') {
2907
+ return button.tooltip(); // Puedes pasarle data si necesitas
2908
+ }
2909
+ return button.tooltip ?? '';
2910
+ }
2911
+ // Evaluar si un botón está deshabilitado
2912
+ getDisabled(button) {
2913
+ if (typeof button.disabled === 'function') {
2914
+ return button.disabled();
2915
+ }
2916
+ return button.disabled ?? false;
2917
+ }
2918
+ // Evaluar si un botón es visible
2919
+ getIsVisible(button) {
2920
+ if (typeof button.isVisible === 'function') {
2921
+ return button.isVisible();
2922
+ }
2923
+ // Si no se define, por defecto es visible
2924
+ return button.isVisible !== false;
2925
+ }
2926
+ 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 });
2927
+ 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"] }] });
2928
+ }
2929
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFilterComponent, decorators: [{
2930
+ type: Component,
2931
+ 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" }]
2932
+ }], ctorParameters: () => [{ type: JDialogShared }, { type: JAlertToastService }, { type: i0.ChangeDetectorRef }], propDecorators: { isLoadingSearch: [{
2933
+ type: Input
2934
+ }], isLoadingPerPage: [{
2935
+ type: Input
2936
+ }], isLoadingAditionalButtons: [{
2937
+ type: Input
2938
+ }], searchPlaceholder: [{
2939
+ type: Input
2940
+ }], columns: [{
2941
+ type: Input
2942
+ }], itemsPerPageOptions: [{
2943
+ type: Input
2944
+ }], search: [{
2945
+ type: Output
2946
+ }], itemsPerPageChange: [{
2947
+ type: Output
2948
+ }], searchQueryChange: [{
2949
+ type: Output
2950
+ }], onItemsPerPageChangeEvent: [{
2951
+ type: Output
2952
+ }], clearFilters: [{
2953
+ type: Output
2954
+ }], itemsPerPage: [{
2955
+ type: Input
2956
+ }], searchQuery: [{
2957
+ type: Input
2958
+ }], filtersButton: [{
2959
+ type: Input
2960
+ }], filtersSelect: [{
2961
+ type: Input
2962
+ }] } });
2963
+
2964
+ class JCalendarService {
2965
+ // Variables
2966
+ nameDaysAb = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
2967
+ nameDays = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
2968
+ nameMontsAb = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
2969
+ nameMonts = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
2970
+ // visualizacion de calendario
2971
+ dialogState = new BehaviorSubject(false);
2972
+ dialogState$ = this.dialogState.asObservable();
2973
+ constructor() { }
2974
+ //===================================================================
2975
+ // Transformaciones de fecha
2976
+ //===================================================================
2977
+ // Mostrar calendario
2978
+ showDialog() {
2979
+ this.dialogState.next(true);
2980
+ }
2981
+ // Ocultar calendario
2982
+ hideDialog() {
2983
+ this.dialogState.next(false);
2984
+ }
2985
+ // Obtener solo el mes
2986
+ getMonthFromDate(date) {
2987
+ const parsedDate = typeof date === 'string' ? new Date(date) : date;
2988
+ const monthIndex = parsedDate.getMonth(); // getMonth() devuelve un índice basado en 0
2989
+ return this.nameMonts[monthIndex]; // Devuelve el nombre completo del mes en español
2990
+ }
2991
+ //===================================================================
2992
+ // Transformaciones de fecha
2993
+ //===================================================================
2994
+ // Obtener edad de la persona
2995
+ calculateAge(birth) {
2996
+ const birthDate = typeof birth === 'string' ? new Date(birth) : birth;
2997
+ const today = new Date();
2998
+ let age = today.getFullYear() - birthDate.getFullYear();
2999
+ const m = today.getMonth() - birthDate.getMonth();
3000
+ if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
3001
+ age--;
3002
+ }
3003
+ return age;
3004
+ }
3005
+ // Obtener edad de la persona en años, meses y días
3006
+ calculateAgeComplete(birth) {
3007
+ const birthDate = typeof birth === 'string' ? new Date(birth) : birth;
3008
+ const today = new Date();
3009
+ let years = today.getFullYear() - birthDate.getFullYear();
3010
+ let months = today.getMonth() - birthDate.getMonth();
3011
+ let days = today.getDate() - birthDate.getDate();
3012
+ if (days < 0) {
3013
+ months--;
3014
+ const previousMonth = new Date(today.getFullYear(), today.getMonth(), 0);
3015
+ days += previousMonth.getDate();
3016
+ }
3017
+ if (months < 0) {
3018
+ years--;
3019
+ months += 12;
3020
+ }
3021
+ return { years, months, days };
3022
+ }
3023
+ // Transformar fecha en formato de string
3024
+ formatearFechaString(date, month) {
3025
+ const partesFecha = date.split('-'); // Dividir la fecha en partes [año, mes, día]
3026
+ const año = partesFecha[0];
3027
+ const mes = this.nameMonts[parseInt(partesFecha[1], 10) - 1];
3028
+ const día = parseInt(partesFecha[2], 10);
3029
+ let mes_fin;
3030
+ if (month) {
3031
+ mes_fin = mes + month;
3032
+ }
3033
+ else {
3034
+ mes_fin = mes;
3035
+ }
3036
+ return `${día} de ${mes_fin} del ${año}`;
3037
+ }
3038
+ // transformar fecha con formato de Date
3039
+ formatearFechaDate(date, month) {
3040
+ let new_date;
3041
+ if (month) {
3042
+ new_date = new Date(date.setMonth(month));
3043
+ }
3044
+ else {
3045
+ new_date = date;
3046
+ }
3047
+ const año = new_date.getFullYear();
3048
+ const mes = this.nameMonts[new_date.getMonth()];
3049
+ const día = new_date.getDate();
3050
+ return `${día} de ${mes} del ${año}`;
3051
+ }
3052
+ // Formatea una fecha como "MMM YYYY"
3053
+ formatMonthYear(date) {
3054
+ const months = this.nameMontsAb.map(month => month.toUpperCase());
3055
+ const month = months[date.getMonth()];
3056
+ const year = date.getFullYear().toString().slice(-2);
3057
+ return `${month} ${year}`;
3058
+ }
3059
+ // Formatear fecha y hora a region bogota
3060
+ formatDateToBogota(date) {
3061
+ // Ajustar la fecha a la zona horaria de Bogotá (UTC-5)
3062
+ const bogotaOffset = -5 * 60; // Offset en minutos
3063
+ const localTime = date.getTime();
3064
+ const localOffset = date.getTimezoneOffset() * 60000;
3065
+ const utc = localTime + localOffset;
3066
+ const bogotaTime = utc + (bogotaOffset * 60000);
3067
+ const bogotaDate = new Date(bogotaTime);
3068
+ // Formatear la fecha a una cadena en el formato 'yyyy-MM-ddTHH:mm:ss'
3069
+ const year = bogotaDate.getFullYear();
3070
+ const month = String(bogotaDate.getMonth() + 1).padStart(2, '0');
3071
+ const day = String(bogotaDate.getDate()).padStart(2, '0');
3072
+ const hours = String(bogotaDate.getHours()).padStart(2, '0');
3073
+ const minutes = String(bogotaDate.getMinutes()).padStart(2, '0');
3074
+ const seconds = String(bogotaDate.getSeconds()).padStart(2, '0');
3075
+ return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
3076
+ }
3077
+ // Formatear el tiempo restante
3078
+ formatRelativeDate(date) {
3079
+ return formatDistanceToNowStrict(new Date(date), {
3080
+ locale: {
3081
+ ...es,
3082
+ formatDistance: (token, count, options) => {
3083
+ const formatDistanceLocale = {
3084
+ lessThanXSeconds: 'hace menos de {{count}} segundos',
3085
+ xSeconds: 'hace {{count}} segundos',
3086
+ halfAMinute: 'hace medio minuto',
3087
+ lessThanXMinutes: 'hace menos de {{count}} minutos',
3088
+ xMinutes: 'hace {{count}} minutos',
3089
+ aboutXHours: 'hace aproximadamente {{count}} horas',
3090
+ xHours: 'hace {{count}} horas',
3091
+ xDays: 'hace {{count}} días',
3092
+ aboutXWeeks: 'hace aproximadamente {{count}} semanas',
3093
+ xWeeks: 'hace {{count}} semanas',
3094
+ aboutXMonths: 'hace aproximadamente {{count}} meses',
3095
+ xMonths: 'hace {{count}} meses',
3096
+ aboutXYears: 'hace aproximadamente {{count}} años',
3097
+ xYears: 'hace {{count}} años',
3098
+ overXYears: 'hace más de {{count}} años',
3099
+ almostXYears: 'hace casi {{count}} años'
3100
+ };
3101
+ let result = formatDistanceLocale[token];
3102
+ if (typeof count === 'number') {
3103
+ result = result.replace('{{count}}', count.toString());
3104
+ // Manejar el singular y plural
3105
+ if (token === 'xHours' && count === 1) {
3106
+ result = result.replace('horas', 'hora');
3107
+ }
3108
+ if (token === 'xDays' && count === 1) {
3109
+ result = result.replace('días', 'día');
3110
+ }
3111
+ if (token === 'xWeeks' && count === 1) {
3112
+ result = result.replace('semanas', 'semana');
3113
+ }
3114
+ if (token === 'xMonths' && count === 1) {
3115
+ result = result.replace('meses', 'mes');
3116
+ }
3117
+ if (token === 'xYears' && count === 1) {
3118
+ result = result.replace('años', 'año');
3119
+ }
3120
+ }
3121
+ return result;
3122
+ }
3123
+ }
3124
+ });
3125
+ }
3126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3127
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, providedIn: 'root' });
3128
+ }
3129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JCalendarService, decorators: [{
3130
+ type: Injectable,
3131
+ args: [{
3132
+ providedIn: 'root'
3133
+ }]
3134
+ }], ctorParameters: () => [] });
3135
+
3136
+ class CardComponent {
3137
+ currencyPipe;
3138
+ genericService;
3139
+ alertToastService;
3140
+ converterService;
3141
+ calendarService;
3142
+ Math = Math;
3143
+ // Estados de carga
3144
+ dataLoaded = new EventEmitter();
3145
+ loadingStates = {
3146
+ initialLoad: 'idle',
3147
+ search: 'idle',
3148
+ itemsPerPage: 'idle',
3149
+ pagination: 'idle',
3150
+ sort: 'idle',
3151
+ aditionalButtons: {},
3152
+ checked: 'idle',
3153
+ action: 'idle',
3154
+ };
3155
+ // Lucide icons
3156
+ icons = {
3157
+ sortDefault: ChevronsUpDown,
3158
+ sortAsc: ChevronUp,
3159
+ sortDesc: ChevronDown,
3160
+ chevronLeft: ChevronLeft,
3161
+ chevronRight: ChevronRight,
3162
+ view: Eye,
3163
+ edit: Edit,
3164
+ delete: Trash,
3165
+ search: Search,
3166
+ check: Check,
3167
+ loading: Loader2
3168
+ };
3169
+ endpoint;
3170
+ columns = [];
3171
+ defaultFilters = {};
3172
+ isPaginator = true;
3173
+ isSearch = true;
3174
+ data = [];
3175
+ itemTemplate;
3176
+ // Expansion
3177
+ expandTemplate;
3178
+ expandedRows = new Set();
3179
+ // Pagination
3180
+ currentPage = 1;
3181
+ itemsPerPageOptions = [10, 25, 50, 100];
3182
+ itemsPerPage = this.itemsPerPageOptions[0];
3183
+ totalItems = 0;
3184
+ // Sorting
3185
+ sortColumn = null;
3186
+ sortDirection = 'none';
3187
+ sortingColumn = null;
3188
+ // Search
3189
+ searchQuery = '';
3190
+ searchPlaceholder = 'Buscar...';
3191
+ // Filters
3192
+ filters = {};
3193
+ // Datos filtrados y paginados
3194
+ displayData = [];
3195
+ // Pagination display
3196
+ pages = [];
3197
+ // Propiedades calculadas para su visualización
3198
+ get startIndex() {
3199
+ return (this.currentPage - 1) * this.itemsPerPage;
3200
+ }
3201
+ get totalPages() {
3202
+ return Math.ceil(this.totalItems / this.itemsPerPage);
3203
+ }
3204
+ // Configuración de botones
3205
+ filtersButton = [];
3206
+ // Filtros de tabla
3207
+ filtersSelect = [];
3208
+ constructor(currencyPipe, genericService, alertToastService, converterService, calendarService) {
3209
+ this.currencyPipe = currencyPipe;
3210
+ this.genericService = genericService;
3211
+ this.alertToastService = alertToastService;
3212
+ this.converterService = converterService;
3213
+ this.calendarService = calendarService;
3214
+ }
3215
+ ngOnInit() {
3216
+ this.columnDefaults();
3217
+ this.loadData();
3218
+ this.overrideFilterEvents();
3219
+ }
3220
+ overrideFilterEvents() {
3221
+ for (const filter of this.filtersSelect) {
3222
+ if (filter.type === 'dropdown' || filter.type === 'searchable') {
3223
+ const key = filter.optionValue ?? 'value';
3224
+ const deepKey = filter.deep ? `${filter.deep}.${key}` : key;
3225
+ const originalOnSelected = filter.onSelected;
3226
+ filter.onSelected = (value) => {
3227
+ const selectedValue = value?.[key] ?? value;
3228
+ if (selectedValue === null || selectedValue === undefined) {
3229
+ delete this.filters[deepKey];
3230
+ }
3231
+ else {
3232
+ this.filters[deepKey] = selectedValue;
3233
+ }
3234
+ // Llama al original
3235
+ if (typeof originalOnSelected === 'function') {
3236
+ originalOnSelected(value);
3237
+ }
3238
+ this.loadData('search');
3239
+ };
3240
+ }
3241
+ }
3242
+ }
3243
+ // =====================================================
3244
+ // Obtener datos
3245
+ // =====================================================
3246
+ // Cargar datos desde el servidor
3247
+ loadData(loadingType = 'initialLoad', onFinally) {
3248
+ this.setLoadingState(loadingType, 'loading');
3249
+ const params = this.getQueryParams();
3250
+ // Simulando espera de API
3251
+ // setTimeout(() => {
3252
+ this.genericService.getAll(this.endpoint, params).subscribe({
3253
+ next: (response) => {
3254
+ this.data = response.data[this.endpoint] ?? [];
3255
+ if (response.meta?.page) {
3256
+ this.totalItems = response.meta.page.totalRecords;
3257
+ this.currentPage = response.meta.page.currentPage;
3258
+ }
3259
+ else {
3260
+ this.totalItems = this.data.length;
3261
+ }
3262
+ if (response.meta?.sort) {
3263
+ this.sortColumn = response.meta.sort.by;
3264
+ this.sortDirection = response.meta.sort.order.toLowerCase();
3265
+ }
3266
+ this.updateDisplayData();
3267
+ this.generatePagination();
3268
+ this.setLoadingState(loadingType, 'success');
3269
+ },
3270
+ error: (error) => {
3271
+ console.error('Error fetching data:', error);
3272
+ this.setLoadingState(loadingType, 'error');
3273
+ }
3274
+ }).add(() => {
3275
+ this.dataLoaded.emit();
3276
+ if (loadingType === 'sort') {
3277
+ this.sortingColumn = null;
3278
+ }
3279
+ // Llamar al callback si fue proporcionado
3280
+ if (onFinally) {
3281
+ onFinally();
3282
+ }
3283
+ });
3284
+ // }, 2000);
3285
+ }
3286
+ // Actualizar los datos que se muestran en la tabla
3287
+ updateDisplayData() {
3288
+ this.displayData = this.data;
3289
+ }
3290
+ // =====================================================
3291
+ // Cambiar estado en campos booleanos
3292
+ // =====================================================
3293
+ // Método para cambiar el estado de un checkbox
3294
+ onCheckboxChange(item, column) {
3295
+ // Get the ID field name based on dataProperty
3296
+ const idField = `id_${this.endpoint}`;
3297
+ // Get the record ID
3298
+ const recordId = item[idField];
3299
+ // Get the current boolean value
3300
+ const currentValue = this.getValue(item, column);
3301
+ // Actualizar estado
3302
+ this.genericService.enable(this.endpoint, recordId, { [column.key]: !currentValue }).subscribe({
3303
+ next: (response) => {
3304
+ item[column.key] = !currentValue;
3305
+ this.alertToastService.AlertToast({
3306
+ type: "success",
3307
+ title: "Registro actualizado!",
3308
+ description: response.msg,
3309
+ });
3310
+ }
3311
+ });
3312
+ }
3313
+ // Eliminar filtros
3314
+ onClearFilters(buttonType) {
3315
+ this.setAditionalButtonLoading(buttonType);
3316
+ this.loadData('initialLoad', () => {
3317
+ this.clearAditionalButtonLoading(buttonType);
3318
+ });
3319
+ }
3320
+ // =====================================================
3321
+ // Columnas
3322
+ // =====================================================
3323
+ // Valores por defecto de las columnas
3324
+ columnDefaults() {
3325
+ // Valores por defecto de las columnas
3326
+ this.columns.forEach(column => {
3327
+ if (column.visible === undefined) {
3328
+ column.visible = true;
3329
+ }
3330
+ if (column.sortable === undefined) {
3331
+ column.sortable = true;
3332
+ }
3333
+ if (column.isSearchable === undefined) {
3334
+ column.isSearchable = true;
3335
+ }
3336
+ });
3337
+ }
3338
+ // Obtener el recuento de columnas visibles para colspan en estado vacío
3339
+ getVisibleColumnsCount() {
3340
+ return this.columns.filter(col => col.visible).length;
3341
+ }
3342
+ // =====================================================
3343
+ // Prosesamiento de datos
3344
+ // =====================================================
3345
+ // Obtener el valor de las celdas para identificar un campo booleano
3346
+ isBoolean(value) {
3347
+ return typeof value === 'boolean';
3348
+ }
3349
+ // Método para obtener el valor de las celdas dinámicamente
3350
+ getValue(item, column) {
3351
+ let value;
3352
+ // Si existe un valueGetter, se usa directamente
3353
+ if (typeof column.valueGetter === 'function') {
3354
+ value = column.valueGetter(item);
3355
+ }
3356
+ else {
3357
+ // Si no, se busca el valor por key (con soporte para claves anidadas)
3358
+ const keys = column.key.split('.');
3359
+ value = item;
3360
+ for (const key of keys) {
3361
+ if (value != null) {
3362
+ value = value[key];
3363
+ }
3364
+ else {
3365
+ value = null;
3366
+ break;
3367
+ }
3368
+ }
3369
+ }
3370
+ // Si value es null o undefined, retornar 'S/N'
3371
+ if (value === null || value === undefined) {
3372
+ return 'S/N';
3373
+ }
3374
+ // Aplicar formato según configuración de columna
3375
+ const formatted = this.formatData(value, column);
3376
+ // Si formatData no devuelve nada (valor no formateado), devolver el valor original
3377
+ return formatted ?? value;
3378
+ }
3379
+ // Formatear datos para la tabla
3380
+ formatData(value, column) {
3381
+ // Añadir formato de moneda pero solo con el simbolo de $
3382
+ if (column.isdollar && value !== null) {
3383
+ const transformedValue = this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
3384
+ return transformedValue ? transformedValue.replace('US', '') : null;
3385
+ }
3386
+ if (column.isCurrency && value !== null) {
3387
+ return this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
3388
+ }
3389
+ if (column.isDate && value !== null) {
3390
+ return new Date(value).toLocaleDateString();
3391
+ }
3392
+ if (column.isDateText && value !== null) {
3393
+ return this.calendarService.formatearFechaString(`${value}`);
3394
+ }
3395
+ if (column.isDateTime && value !== null) {
3396
+ return new Date(value).toLocaleString();
3397
+ }
3398
+ if (column.isDateTimeText && value !== null) {
3399
+ return new Date(value).toString();
3400
+ }
3401
+ if (column.isRelativeTime && value !== null) {
3402
+ return this.calendarService.formatRelativeDate(value);
3403
+ }
3404
+ if (column.isFirstWord && value !== null) {
3405
+ return value.split(' ')[0];
3406
+ }
3407
+ // Si no se aplica ningún formato, retornar el valor original
3408
+ return value;
3409
+ }
3410
+ // =====================================================
3411
+ // Parametros de busqueda
3412
+ // =====================================================
3413
+ // Obtener los parámetros de consulta para la solicitud de datos
3414
+ getQueryParams() {
3415
+ const params = this.genericService.params({
3416
+ page: this.currentPage,
3417
+ limit: this.itemsPerPage,
3418
+ sort: {
3419
+ column: this.sortColumn,
3420
+ direction: this.sortDirection,
3421
+ },
3422
+ filters: this.filters,
3423
+ defaultFilters: this.defaultFilters,
3424
+ });
3425
+ if (this.searchQuery && this.searchQuery.trim() !== '') {
3426
+ const baseSearchKeys = this.columns
3427
+ .filter(col => col.isSearchable)
3428
+ .map(col => col.key);
3429
+ const extraSearchKeys = this.columns
3430
+ .flatMap(col => col.extraSearchFields || []);
3431
+ const allSearchKeys = [...baseSearchKeys, ...extraSearchKeys];
3432
+ params['search'] = this.searchQuery;
3433
+ params['searchFields'] = allSearchKeys;
3434
+ }
3435
+ return params;
3436
+ }
3437
+ // =====================================================
3438
+ // Ordenamiento
3439
+ // =====================================================
3440
+ // Numero de pagina
3441
+ getRowNumber(index) {
3442
+ return this.startIndex + index + 1;
3443
+ }
3444
+ // Ordenamiento de columnas
3445
+ onSort(column) {
3446
+ // Evitar múltiples solicitudes de ordenamiento simultáneas
3447
+ if (this.isLoading('sort')) {
3448
+ return;
3449
+ }
3450
+ if (!column.sortable)
3451
+ return;
3452
+ // Guardar la columna que está siendo ordenada
3453
+ this.sortingColumn = column.key;
3454
+ // Verificamos si estamos tratando con la misma columna que el último ordenamiento
3455
+ const currentSortKey = this.converterService.getSortKey(this.sortColumn);
3456
+ const columnSortKey = this.converterService.getSortKey(column.key);
3457
+ // Sort direction logic
3458
+ if (currentSortKey === columnSortKey) {
3459
+ // Alternar la dirección del ordenamiento
3460
+ if (this.sortDirection === 'asc') {
3461
+ this.sortDirection = 'desc';
3462
+ }
3463
+ else if (this.sortDirection === 'desc') {
3464
+ this.sortDirection = 'none';
3465
+ }
3466
+ else {
3467
+ this.sortDirection = 'asc';
3468
+ }
3469
+ }
3470
+ else {
3471
+ // Si se selecciona una nueva columna para ordenar, iniciar con orden ascendente
3472
+ this.sortColumn = column.key;
3473
+ this.sortDirection = 'asc';
3474
+ }
3475
+ this.loadData('sort');
3476
+ }
3477
+ // Obtener la dirección de ordenamiento
3478
+ getSortKey(value) {
3479
+ return this.converterService.getSortKey(value);
3480
+ }
3481
+ // Manejar el evento de tecla presionada en la ordenación
3482
+ onSortKeyPress(event, column) {
3483
+ // Si ya hay una ordenación en progreso, no permitir otra
3484
+ if (this.isLoading('sort')) {
3485
+ return;
3486
+ }
3487
+ if (event.key === 'Enter' || event.key === ' ') {
3488
+ event.preventDefault();
3489
+ this.onSort(column);
3490
+ }
3491
+ }
3492
+ // =====================================================
3493
+ // Search
3494
+ // =====================================================
3495
+ // Search functionality
3496
+ onSearch() {
3497
+ this.currentPage = 1;
3498
+ this.loadData('search');
3499
+ }
3500
+ // Selector de items por página
3501
+ onItemsPerPageChange() {
3502
+ this.currentPage = 1;
3503
+ this.loadData('itemsPerPage');
3504
+ }
3505
+ // =====================================================
3506
+ // Paginator
3507
+ // =====================================================
3508
+ // Generar numeros de paginación
3509
+ generatePagination() {
3510
+ const totalPages = this.totalPages;
3511
+ const currentPage = this.currentPage;
3512
+ // Ver los 5 numeros de paginación
3513
+ const maxPagesToShow = 3;
3514
+ let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
3515
+ let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
3516
+ // Adjust if we're near the end
3517
+ if (endPage - startPage + 1 < maxPagesToShow) {
3518
+ startPage = Math.max(1, endPage - maxPagesToShow + 1);
3519
+ }
3520
+ this.pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
3521
+ }
3522
+ // Paginar
3523
+ handlePageChange(page) {
3524
+ this.currentPage = page;
3525
+ this.loadData('pagination');
3526
+ }
3527
+ // =====================================================
3528
+ // Opciones
3529
+ // =====================================================
3530
+ // Método para manejar el clic de un botón
3531
+ onButtonClick(button, element) {
3532
+ // Ejecuta la acción del padre si está definida
3533
+ if (button.clicked) {
3534
+ button.clicked(element);
3535
+ }
3536
+ }
3537
+ // Método para obtener un tooltip
3538
+ getTooltip(tooltip, data) {
3539
+ if (typeof tooltip === 'function') {
3540
+ return tooltip(data);
3541
+ }
3542
+ return tooltip ?? '';
3543
+ }
3544
+ // Método para obtener un icono
3545
+ getIcon(icon, data) {
3546
+ if (typeof icon === 'function') {
3547
+ return icon(data);
3548
+ }
3549
+ return icon;
3550
+ }
3551
+ // Evaluar si un botón está deshabilitado
3552
+ getDisabled(option, data) {
3553
+ if (typeof option.disabled === 'function') {
3554
+ return option.disabled(data);
3555
+ }
3556
+ return !!option.disabled;
3557
+ }
3558
+ // Evaluar si un botón es visible
3559
+ getIsVisible(option, data) {
3560
+ if (typeof option.isVisible === 'function') {
3561
+ return option.isVisible(data);
3562
+ }
3563
+ // Si no se define, por defecto es visible
3564
+ return option.isVisible !== false;
3565
+ }
3566
+ // Método para obtener la clase CSS de un botón
3567
+ mergeNgClasses(optionNgClass, data) {
3568
+ const baseClass = {
3569
+ 'min-w-auto p-1! pl-2! pr-2!': true
3570
+ };
3571
+ let dynamicClass = {};
3572
+ if (typeof optionNgClass === 'function') {
3573
+ dynamicClass = optionNgClass(data);
3574
+ }
3575
+ else {
3576
+ dynamicClass = optionNgClass ?? {};
3577
+ }
3578
+ return {
3579
+ ...baseClass,
3580
+ ...(typeof dynamicClass === 'string' ? { [dynamicClass]: true } : dynamicClass)
3581
+ };
3582
+ }
3583
+ // =====================================================
3584
+ // Parametros de carga
3585
+ // =====================================================
3586
+ // Método para verificar si un estado específico está cargando
3587
+ isLoading(state) {
3588
+ return this.loadingStates[state] === 'loading';
3589
+ }
3590
+ // Método para verificar si cualquier estado está cargando
3591
+ isAnyLoading() {
3592
+ return Object.values(this.loadingStates).some(state => state === 'loading');
3593
+ }
3594
+ // Método para actualizar un estado de carga
3595
+ setLoadingState(state, value) {
3596
+ if (state === 'aditionalButtons') {
3597
+ if (typeof value === 'string') {
3598
+ // Evita asignar un string directamente si se espera un objeto
3599
+ console.warn(`No puedes asignar '${value}' directamente a aditionalButtons. Usa setAditionalButtonLoading en su lugar.`);
3600
+ }
3601
+ else {
3602
+ this.loadingStates.aditionalButtons = value;
3603
+ }
3604
+ }
3605
+ else {
3606
+ this.loadingStates[state] = value;
3607
+ }
3608
+ }
3609
+ // Activar loading
3610
+ setAditionalButtonLoading(buttonType, id) {
3611
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
3612
+ this.loadingStates.aditionalButtons[key] = 'loading';
3613
+ }
3614
+ // Limpiar loading
3615
+ clearAditionalButtonLoading(buttonType, id) {
3616
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
3617
+ this.loadingStates.aditionalButtons[key] = 'idle';
3618
+ }
3619
+ // Verificar loading
3620
+ isAditionalButtonLoading(buttonType, id) {
3621
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
3622
+ return this.loadingStates.aditionalButtons[key] === 'loading';
3623
+ }
3624
+ // ==================================================
3625
+ // Expandir filas
3626
+ // ==================================================
3627
+ // Método para verificar si la tabla tiene filas expandibles
3628
+ hasExpandable() {
3629
+ return this.columns.some(col => typeof col.expandTemplate === 'function');
3630
+ }
3631
+ // Método para verificar si una fila está expandida
3632
+ toggleRow(row) {
3633
+ if (this.expandedRows.has(row)) {
3634
+ this.expandedRows.delete(row);
3635
+ }
3636
+ else {
3637
+ this.expandedRows.add(row);
3638
+ }
3639
+ }
3640
+ // Método para obtener contenido expandido de una fila
3641
+ getExpandedContent(row) {
3642
+ const expandableColumn = this.columns.find(col => typeof col.expandTemplate === 'function');
3643
+ return expandableColumn?.expandTemplate?.(row) ?? '';
3644
+ }
3645
+ // Método para obtener el estado de expansión de una fila
3646
+ getExpansionState(row) {
3647
+ return this.expandedRows.has(row) ? 'expanded' : 'collapsed';
3648
+ }
3649
+ 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 });
3650
+ 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: [
3651
+ trigger('slideToggle', [
3652
+ state('collapsed', style({
3653
+ height: '0',
3654
+ opacity: 0,
3655
+ overflow: 'hidden',
3656
+ })),
3657
+ state('expanded', style({
3658
+ height: '*',
3659
+ opacity: 1,
3660
+ })),
3661
+ transition('collapsed <=> expanded', [
3662
+ animate('0.3s ease-in-out')
3663
+ ])
3664
+ ])
3665
+ ] });
3666
+ }
3667
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: CardComponent, decorators: [{
3668
+ type: Component,
3669
+ args: [{ selector: 'JCrudCard', standalone: true, imports: [CommonModule, FormsModule, JPaginatorComponent, JFilterComponent, LucideAngularModule], animations: [
3670
+ trigger('slideToggle', [
3671
+ state('collapsed', style({
3672
+ height: '0',
3673
+ opacity: 0,
3674
+ overflow: 'hidden',
3675
+ })),
3676
+ state('expanded', style({
3677
+ height: '*',
3678
+ opacity: 1,
3679
+ })),
3680
+ transition('collapsed <=> expanded', [
3681
+ animate('0.3s ease-in-out')
3682
+ ])
3683
+ ])
3684
+ ], 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" }]
3685
+ }], ctorParameters: () => [{ type: i1$1.CurrencyPipe }, { type: JGenericService }, { type: JAlertToastService }, { type: ConverterService }, { type: JCalendarService }], propDecorators: { dataLoaded: [{
3686
+ type: Output
3687
+ }], endpoint: [{
3688
+ type: Input
3689
+ }], columns: [{
3690
+ type: Input
3691
+ }], defaultFilters: [{
3692
+ type: Input
3693
+ }], isPaginator: [{
3694
+ type: Input
3695
+ }], isSearch: [{
3696
+ type: Input
3697
+ }], itemTemplate: [{
3698
+ type: Input
3699
+ }], itemsPerPageOptions: [{
3700
+ type: Input
3701
+ }], searchPlaceholder: [{
3702
+ type: Input
3703
+ }], filtersButton: [{
3704
+ type: Input
3705
+ }], filtersSelect: [{
3706
+ type: Input
3707
+ }] } });
3708
+
3709
+ class JFormShared {
3710
+ alertToastService;
3711
+ icons = {
3712
+ add: Plus,
3713
+ edit: Edit,
3714
+ delete: Trash,
3715
+ ban: Ban,
3716
+ power: Power,
3717
+ default: Cpu,
3718
+ keyRound: KeyRound,
3719
+ userRoundSearch: UserRoundSearch,
3720
+ circleCheckBig: CircleCheckBig,
3721
+ mousePointerClick: MousePointerClick,
3722
+ };
3723
+ // Resetear formulario
3724
+ onResetCallback = null;
3725
+ isLoading = false;
3726
+ openForm = false;
3727
+ typeForm = 'create';
3728
+ formControls = {};
3729
+ messages = null;
3730
+ constructor(alertToastService) {
3731
+ this.alertToastService = alertToastService;
3732
+ }
3733
+ onOpen() {
3734
+ this.openForm = true;
3735
+ if (this.onResetCallback) {
3736
+ this.onResetCallback();
3737
+ }
3738
+ }
3739
+ onClose() {
3740
+ this.openForm = false;
3741
+ }
3742
+ onValidateChange(validation) {
3743
+ if (validation) {
3744
+ this.alertToastService.AlertToast({
3745
+ type: "info",
3746
+ title: "Sin cambios...",
3747
+ description: "No se han realizado cambios en el formulario"
3748
+ });
3749
+ return true;
3750
+ }
3751
+ return false;
3752
+ }
3753
+ onTableDataLoaded() {
3754
+ this.isLoading = false;
3755
+ this.openForm = false;
3756
+ if (this.messages) {
3757
+ this.alertToastService.AlertToast({
3758
+ type: "success",
3759
+ ...this.messages
3760
+ });
3761
+ this.messages = null;
3762
+ }
3763
+ }
3764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, deps: [{ token: JAlertToastService }], target: i0.ɵɵFactoryTarget.Injectable });
3765
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, providedIn: 'root' });
3766
+ }
3767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormShared, decorators: [{
3768
+ type: Injectable,
3769
+ args: [{
3770
+ providedIn: 'root'
3771
+ }]
3772
+ }], ctorParameters: () => [{ type: JAlertToastService }] });
3773
+
3774
+ class JFormComponent {
3775
+ icons = {
3776
+ x: X,
3777
+ save: Save,
3778
+ circleX: CircleX,
3779
+ info: Info,
3780
+ asterisk: Asterisk
3781
+ };
3782
+ formTemplate;
3783
+ submitForm = new EventEmitter();
3784
+ // Mostrar formulario lateral
3785
+ openForm = false;
3786
+ closeForm = new EventEmitter();
3787
+ typeForm = 'none';
3788
+ titleForm = 'REGISTRO';
3789
+ isLoading = false;
3790
+ checkboxes = [];
3791
+ constructor() { }
3792
+ onSubmit() {
3793
+ this.submitForm.emit();
3794
+ }
3795
+ onClose() {
3796
+ this.closeForm.emit();
3797
+ }
3798
+ handleEscape(event) {
3799
+ if (this.openForm) {
3800
+ this.onClose();
3801
+ }
3802
+ }
3803
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3804
+ 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: [
3805
+ trigger('sidebarTransition', [
3806
+ transition(':enter', [
3807
+ style({ opacity: 0, transform: 'translateX(100%)' }),
3808
+ animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
3809
+ ]),
3810
+ transition(':leave', [
3811
+ animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(100%)' }))
3812
+ ])
3813
+ ])
3814
+ ] });
3815
+ }
3816
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JFormComponent, decorators: [{
3817
+ type: Component,
3818
+ args: [{ selector: 'JCrudForm', standalone: true, imports: [LucideAngularModule, JButtonComponent, JTooltipModule, ReactiveFormsModule, FormsModule, CommonModule, JCheckboxComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], animations: [
3819
+ trigger('sidebarTransition', [
3820
+ transition(':enter', [
3821
+ style({ opacity: 0, transform: 'translateX(100%)' }),
3822
+ animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0)' }))
3823
+ ]),
3824
+ transition(':leave', [
3825
+ animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(100%)' }))
3826
+ ])
3827
+ ])
3828
+ ], 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>" }]
3829
+ }], ctorParameters: () => [], propDecorators: { formTemplate: [{
3830
+ type: Input
3831
+ }], submitForm: [{
3832
+ type: Output
3833
+ }], openForm: [{
3834
+ type: Input
3835
+ }], closeForm: [{
3836
+ type: Output
3837
+ }], typeForm: [{
3838
+ type: Input
3839
+ }], isLoading: [{
3840
+ type: Input
3841
+ }], checkboxes: [{
3842
+ type: Input
3843
+ }], handleEscape: [{
3844
+ type: HostListener,
3845
+ args: ['document:keydown.escape', ['$event']]
3846
+ }] } });
3847
+
3848
+ class JTableComponent {
3849
+ currencyPipe;
3850
+ genericService;
3851
+ alertToastService;
3852
+ converterService;
3853
+ calendarService;
3854
+ Math = Math;
3855
+ // Estados de carga
3856
+ dataLoaded = new EventEmitter();
3857
+ loadingStates = {
3858
+ initialLoad: 'idle',
3859
+ search: 'idle',
3860
+ itemsPerPage: 'idle',
3861
+ pagination: 'idle',
3862
+ sort: 'idle',
3863
+ aditionalButtons: {},
3864
+ checked: 'idle',
3865
+ action: 'idle',
3866
+ };
3867
+ // Lucide icons
3868
+ icons = {
3869
+ sortDefault: ChevronsUpDown,
3870
+ sortAsc: ChevronUp,
3871
+ sortDesc: ChevronDown,
3872
+ chevronLeft: ChevronLeft,
3873
+ chevronRight: ChevronRight,
3874
+ view: Eye,
3875
+ edit: Edit,
3876
+ delete: Trash,
3877
+ search: Search,
3878
+ check: Check,
3879
+ loading: Loader2
3880
+ };
3881
+ endpoint;
3882
+ columns = [];
3883
+ defaultFilters = {};
3884
+ isPaginator = true;
3885
+ isSearch = true;
3886
+ data = [];
3887
+ // Expansion
3888
+ expandTemplate;
3889
+ expandedRows = new Set();
3890
+ // Pagination
3891
+ currentPage = 1;
3892
+ itemsPerPageOptions = [10, 25, 50, 100];
3893
+ itemsPerPage = this.itemsPerPageOptions[0];
3894
+ totalItems = 0;
3895
+ // Sorting
3896
+ sortColumn = null;
3897
+ sortDirection = 'none';
3898
+ sortingColumn = null;
3899
+ // Search
3900
+ searchQuery = '';
3901
+ searchPlaceholder = 'Buscar...';
3902
+ // Filters
3903
+ filters = {};
3904
+ // Datos filtrados y paginados
3905
+ displayData = [];
3906
+ // Pagination display
3907
+ pages = [];
3908
+ // Check
3909
+ checked = false;
3910
+ checkedValues = [[true], [false]];
3911
+ checkedTitles = ["Activos", "Inactivos"];
3912
+ isChecked;
3913
+ titleChecked;
3914
+ // Propiedades calculadas para su visualización
3915
+ get startIndex() {
3916
+ return (this.currentPage - 1) * this.itemsPerPage;
3917
+ }
3918
+ get totalPages() {
3919
+ return Math.ceil(this.totalItems / this.itemsPerPage);
3920
+ }
3921
+ // Configuración de botones
3922
+ filtersButton = [];
3923
+ // Filtros de tabla
3924
+ filtersSelect = [];
3925
+ // Opciones de botones
3926
+ optionsTable = [];
3927
+ constructor(currencyPipe, genericService, alertToastService, converterService, calendarService) {
3928
+ this.currencyPipe = currencyPipe;
3929
+ this.genericService = genericService;
3930
+ this.alertToastService = alertToastService;
3931
+ this.converterService = converterService;
3932
+ this.calendarService = calendarService;
3933
+ }
3934
+ ngOnInit() {
3935
+ this.isChecked = this.checkedValues[0][0];
3936
+ this.titleChecked = this.checkedTitles[0];
3937
+ this.columnDefaults();
3938
+ this.loadData();
3939
+ this.overrideFilterEvents();
3940
+ }
3941
+ overrideFilterEvents() {
3942
+ for (const filter of this.filtersSelect) {
3943
+ if (filter.type === 'dropdown' || filter.type === 'searchable') {
3944
+ const key = filter.optionValue ?? 'value';
3945
+ const deepKey = filter.deep ? `${filter.deep}.${key}` : key;
3946
+ const originalOnSelected = filter.onSelected;
3947
+ filter.onSelected = (value) => {
3948
+ const selectedValue = value?.[key] ?? value;
3949
+ if (selectedValue === null || selectedValue === undefined) {
3950
+ delete this.filters[deepKey];
3951
+ }
3952
+ else {
3953
+ this.filters[deepKey] = selectedValue;
3954
+ }
3955
+ // Llama al original
3956
+ if (typeof originalOnSelected === 'function') {
3957
+ originalOnSelected(value);
3958
+ }
3959
+ this.loadData('search');
3960
+ };
3961
+ }
3962
+ }
3963
+ }
3964
+ // =====================================================
3965
+ // Obtener datos
3966
+ // =====================================================
3967
+ // Cargar datos desde el servidor
3968
+ loadData(loadingType = 'initialLoad', onFinally) {
3969
+ this.setLoadingState(loadingType, 'loading');
3970
+ const params = this.getQueryParams();
3971
+ // Simulando espera de API
3972
+ // setTimeout(() => {
3973
+ this.genericService.getAll(this.endpoint, params).subscribe({
3974
+ next: (response) => {
3975
+ this.data = response.data[this.endpoint] ?? [];
3976
+ if (response.meta?.page) {
3977
+ this.totalItems = response.meta.page.totalRecords;
3978
+ this.currentPage = response.meta.page.currentPage;
3979
+ }
3980
+ else {
3981
+ this.totalItems = this.data.length;
3982
+ }
3983
+ if (response.meta?.sort) {
3984
+ this.sortColumn = response.meta.sort.by;
3985
+ this.sortDirection = response.meta.sort.order.toLowerCase();
3986
+ }
3987
+ this.updateDisplayData();
3988
+ this.generatePagination();
3989
+ this.setLoadingState(loadingType, 'success');
3990
+ },
3991
+ error: (error) => {
3992
+ console.error('Error fetching data:', error);
3993
+ this.setLoadingState(loadingType, 'error');
3994
+ }
3995
+ }).add(() => {
3996
+ this.dataLoaded.emit();
3997
+ if (loadingType === 'sort') {
3998
+ this.sortingColumn = null;
3999
+ }
4000
+ // Llamar al callback si fue proporcionado
4001
+ if (onFinally) {
4002
+ onFinally();
4003
+ }
4004
+ });
4005
+ // }, 2000);
4006
+ }
4007
+ // Actualizar los datos que se muestran en la tabla
4008
+ updateDisplayData() {
4009
+ this.displayData = this.data;
4010
+ }
4011
+ // =====================================================
4012
+ // Cambiar estado en campos booleanos
4013
+ // =====================================================
4014
+ // Método para cambiar el estado de un checkbox
4015
+ onCheckboxChange(item, column) {
4016
+ // Get the ID field name based on dataProperty
4017
+ const idField = `id_${this.endpoint}`;
4018
+ // Get the record ID
4019
+ const recordId = item[idField];
4020
+ // Get the current boolean value
4021
+ const currentValue = this.getValue(item, column);
4022
+ // Actualizar estado
4023
+ this.genericService.enable(this.endpoint, recordId, { [column.key]: !currentValue }).subscribe({
4024
+ next: (response) => {
4025
+ item[column.key] = !currentValue;
4026
+ this.alertToastService.AlertToast({
4027
+ type: "success",
4028
+ title: "Registro actualizado!",
4029
+ description: response.msg,
4030
+ });
4031
+ }
4032
+ });
4033
+ }
4034
+ // Cambiar activos o inactivos
4035
+ checkActiveInactive(isChecked) {
4036
+ this.isChecked = !isChecked;
4037
+ const index = this.isChecked ? 0 : 1;
4038
+ this.titleChecked = this.checkedTitles[index];
4039
+ // SOLO actualizamos la propiedad id_status sin tocar los demás filtros
4040
+ this.filters['id_status'] = this.checkedValues[index];
4041
+ this.filtersSelect = this.filtersSelect.map(filter => {
4042
+ if ('optionValue' in filter && filter.optionValue === 'id_status') {
4043
+ return {
4044
+ ...filter,
4045
+ defaultFilters: {
4046
+ ...(filter.hasOwnProperty('defaultFilters') ? filter.defaultFilters : {}),
4047
+ id_status: this.filters['id_status']
4048
+ },
4049
+ selected: null // limpia la selección previa para forzar el reload
4050
+ };
4051
+ }
4052
+ return filter;
4053
+ });
4054
+ this.currentPage = 1;
4055
+ this.loadData('checked');
4056
+ }
4057
+ // Eliminar filtros
4058
+ onClearFilters(buttonType) {
4059
+ this.setAditionalButtonLoading(buttonType);
4060
+ this.loadData('initialLoad', () => {
4061
+ this.clearAditionalButtonLoading(buttonType);
4062
+ });
4063
+ }
4064
+ // =====================================================
4065
+ // Columnas
4066
+ // =====================================================
4067
+ // Valores por defecto de las columnas
4068
+ columnDefaults() {
4069
+ // Valores por defecto de las columnas
4070
+ this.columns.forEach(column => {
4071
+ if (column.visible === undefined) {
4072
+ column.visible = true;
4073
+ }
4074
+ if (column.sortable === undefined) {
4075
+ column.sortable = true;
4076
+ }
4077
+ if (column.isSearchable === undefined) {
4078
+ column.isSearchable = true;
4079
+ }
4080
+ });
4081
+ }
4082
+ // Obtener el recuento de columnas visibles para colspan en estado vacío
4083
+ getVisibleColumnsCount() {
4084
+ return this.columns.filter(col => col.visible).length;
4085
+ }
4086
+ // =====================================================
4087
+ // Prosesamiento de datos
4088
+ // =====================================================
4089
+ // Obtener el valor de las celdas para identificar un campo booleano
4090
+ isBoolean(value) {
4091
+ return typeof value === 'boolean';
4092
+ }
4093
+ // Método para obtener el valor de las celdas dinámicamente
4094
+ getValue(item, column) {
4095
+ let value;
4096
+ // Si existe un valueGetter, se usa directamente
4097
+ if (typeof column.valueGetter === 'function') {
4098
+ value = column.valueGetter(item);
4099
+ }
4100
+ else {
4101
+ // Si no, se busca el valor por key (con soporte para claves anidadas)
4102
+ const keys = column.key.split('.');
4103
+ value = item;
4104
+ for (const key of keys) {
4105
+ if (value != null) {
4106
+ value = value[key];
4107
+ }
4108
+ else {
4109
+ value = null;
4110
+ break;
4111
+ }
4112
+ }
4113
+ }
4114
+ // Si value es null o undefined, retornar 'S/N'
4115
+ if (value === null || value === undefined) {
4116
+ return 'S/N';
4117
+ }
4118
+ // Aplicar formato según configuración de columna
4119
+ const formatted = this.formatData(value, column);
4120
+ // Si formatData no devuelve nada (valor no formateado), devolver el valor original
4121
+ return formatted ?? value;
4122
+ }
4123
+ // Formatear datos para la tabla
4124
+ formatData(value, column) {
4125
+ // Añadir formato de moneda pero solo con el simbolo de $
4126
+ if (column.isdollar && value !== null) {
4127
+ const transformedValue = this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
4128
+ return transformedValue ? transformedValue.replace('US', '') : null;
4129
+ }
4130
+ if (column.isCurrency && value !== null) {
4131
+ return this.currencyPipe.transform(value, 'USD', 'symbol', '1.2-2');
4132
+ }
4133
+ if (column.isDate && value !== null) {
4134
+ return new Date(value).toLocaleDateString();
4135
+ }
4136
+ if (column.isDateText && value !== null) {
4137
+ return this.calendarService.formatearFechaString(`${value}`);
4138
+ }
4139
+ if (column.isDateTime && value !== null) {
4140
+ return new Date(value).toLocaleString();
4141
+ }
4142
+ if (column.isDateTimeText && value !== null) {
4143
+ return new Date(value).toString();
4144
+ }
4145
+ // Si no se aplica ningún formato, retornar el valor original
4146
+ return value;
4147
+ }
4148
+ // =====================================================
4149
+ // Parametros de busqueda
4150
+ // =====================================================
4151
+ // Obtener los parámetros de consulta para la solicitud de datos
4152
+ getQueryParams() {
4153
+ const params = this.genericService.params({
4154
+ page: this.currentPage,
4155
+ limit: this.itemsPerPage,
4156
+ sort: {
4157
+ column: this.sortColumn,
4158
+ direction: this.sortDirection,
4159
+ },
4160
+ filters: this.filters,
4161
+ defaultFilters: this.defaultFilters,
4162
+ });
4163
+ if (this.searchQuery && this.searchQuery.trim() !== '') {
4164
+ const baseSearchKeys = this.columns
4165
+ .filter(col => col.isSearchable)
4166
+ .map(col => col.key);
4167
+ const extraSearchKeys = this.columns
4168
+ .flatMap(col => col.extraSearchFields || []);
4169
+ const allSearchKeys = [...baseSearchKeys, ...extraSearchKeys];
4170
+ params['search'] = this.searchQuery;
4171
+ params['searchFields'] = allSearchKeys;
4172
+ }
4173
+ return params;
4174
+ }
4175
+ // =====================================================
4176
+ // Ordenamiento
4177
+ // =====================================================
4178
+ // Numero de pagina
4179
+ getRowNumber(index) {
4180
+ return this.startIndex + index + 1;
4181
+ }
4182
+ // Ordenamiento de columnas
4183
+ onSort(column) {
4184
+ // Evitar múltiples solicitudes de ordenamiento simultáneas
4185
+ if (this.isLoading('sort')) {
4186
+ return;
4187
+ }
4188
+ if (!column.sortable)
4189
+ return;
4190
+ // Guardar la columna que está siendo ordenada
4191
+ this.sortingColumn = column.key;
4192
+ // Verificamos si estamos tratando con la misma columna que el último ordenamiento
4193
+ const currentSortKey = this.converterService.getSortKey(this.sortColumn);
4194
+ const columnSortKey = this.converterService.getSortKey(column.key);
4195
+ // Sort direction logic
4196
+ if (currentSortKey === columnSortKey) {
4197
+ // Alternar la dirección del ordenamiento
4198
+ if (this.sortDirection === 'asc') {
4199
+ this.sortDirection = 'desc';
4200
+ }
4201
+ else if (this.sortDirection === 'desc') {
4202
+ this.sortDirection = 'none';
4203
+ }
4204
+ else {
4205
+ this.sortDirection = 'asc';
4206
+ }
4207
+ }
4208
+ else {
4209
+ // Si se selecciona una nueva columna para ordenar, iniciar con orden ascendente
4210
+ this.sortColumn = column.key;
4211
+ this.sortDirection = 'asc';
4212
+ }
4213
+ this.loadData('sort');
4214
+ }
4215
+ // Obtener la dirección de ordenamiento
4216
+ getSortKey(value) {
4217
+ return this.converterService.getSortKey(value);
4218
+ }
4219
+ // Manejar el evento de tecla presionada en la ordenación
4220
+ onSortKeyPress(event, column) {
4221
+ // Si ya hay una ordenación en progreso, no permitir otra
4222
+ if (this.isLoading('sort')) {
4223
+ return;
4224
+ }
4225
+ if (event.key === 'Enter' || event.key === ' ') {
4226
+ event.preventDefault();
4227
+ this.onSort(column);
4228
+ }
4229
+ }
4230
+ // =====================================================
4231
+ // Search
4232
+ // =====================================================
4233
+ // Search functionality
4234
+ onSearch() {
4235
+ this.currentPage = 1;
4236
+ this.loadData('search');
4237
+ }
4238
+ // Selector de items por página
4239
+ onItemsPerPageChange() {
4240
+ this.currentPage = 1;
4241
+ this.loadData('itemsPerPage');
4242
+ }
4243
+ // =====================================================
4244
+ // Paginator
4245
+ // =====================================================
4246
+ // Generar numeros de paginación
4247
+ generatePagination() {
4248
+ const totalPages = this.totalPages;
4249
+ const currentPage = this.currentPage;
4250
+ // Ver los 5 numeros de paginación
4251
+ const maxPagesToShow = 3;
4252
+ let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
4253
+ let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
4254
+ // Adjust if we're near the end
4255
+ if (endPage - startPage + 1 < maxPagesToShow) {
4256
+ startPage = Math.max(1, endPage - maxPagesToShow + 1);
4257
+ }
4258
+ this.pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
4259
+ }
4260
+ // Paginar
4261
+ handlePageChange(page) {
4262
+ this.currentPage = page;
4263
+ this.loadData('pagination');
4264
+ }
4265
+ // =====================================================
4266
+ // Opciones
4267
+ // =====================================================
4268
+ // Método para manejar el clic de un botón
4269
+ onButtonClick(button, element) {
4270
+ // Ejecuta la acción del padre si está definida
4271
+ if (button.clicked) {
4272
+ button.clicked(element);
4273
+ }
4274
+ }
4275
+ // Método para obtener un tooltip
4276
+ getTooltip(tooltip, data) {
4277
+ if (typeof tooltip === 'function') {
4278
+ return tooltip(data);
4279
+ }
4280
+ return tooltip ?? '';
4281
+ }
4282
+ // Método para obtener un icono
4283
+ getIcon(icon, data) {
4284
+ if (typeof icon === 'function') {
4285
+ return icon(data);
4286
+ }
4287
+ return icon;
4288
+ }
4289
+ // Evaluar si un botón está deshabilitado
4290
+ getDisabled(option, data) {
4291
+ if (typeof option.disabled === 'function') {
4292
+ return option.disabled(data);
4293
+ }
4294
+ return !!option.disabled;
4295
+ }
4296
+ // Evaluar si un botón es visible
4297
+ getIsVisible(option, data) {
4298
+ if (typeof option.isVisible === 'function') {
4299
+ return option.isVisible(data);
4300
+ }
4301
+ // Si no se define, por defecto es visible
4302
+ return option.isVisible !== false;
4303
+ }
4304
+ // Método para obtener la clase CSS de un botón
4305
+ mergeNgClasses(optionNgClass, data) {
4306
+ const baseClass = {
4307
+ 'min-w-auto p-1! pl-2! pr-2!': true
4308
+ };
4309
+ let dynamicClass = {};
4310
+ if (typeof optionNgClass === 'function') {
4311
+ dynamicClass = optionNgClass(data);
4312
+ }
4313
+ else {
4314
+ dynamicClass = optionNgClass ?? {};
4315
+ }
4316
+ return {
4317
+ ...baseClass,
4318
+ ...(typeof dynamicClass === 'string' ? { [dynamicClass]: true } : dynamicClass)
4319
+ };
4320
+ }
4321
+ // =====================================================
4322
+ // Parametros de carga
4323
+ // =====================================================
4324
+ // Método para verificar si un estado específico está cargando
4325
+ isLoading(state) {
4326
+ return this.loadingStates[state] === 'loading';
4327
+ }
4328
+ // Método para verificar si cualquier estado está cargando
4329
+ isAnyLoading() {
4330
+ return Object.values(this.loadingStates).some(state => state === 'loading');
4331
+ }
4332
+ // Método para actualizar un estado de carga
4333
+ setLoadingState(state, value) {
4334
+ if (state === 'aditionalButtons') {
4335
+ if (typeof value === 'string') {
4336
+ // Evita asignar un string directamente si se espera un objeto
4337
+ console.warn(`No puedes asignar '${value}' directamente a aditionalButtons. Usa setAditionalButtonLoading en su lugar.`);
4338
+ }
4339
+ else {
4340
+ this.loadingStates.aditionalButtons = value;
4341
+ }
4342
+ }
4343
+ else {
4344
+ this.loadingStates[state] = value;
4345
+ }
4346
+ }
4347
+ // Activar loading
4348
+ setAditionalButtonLoading(buttonType, id) {
4349
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
4350
+ this.loadingStates.aditionalButtons[key] = 'loading';
4351
+ }
4352
+ // Limpiar loading
4353
+ clearAditionalButtonLoading(buttonType, id) {
4354
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
4355
+ this.loadingStates.aditionalButtons[key] = 'idle';
4356
+ }
4357
+ // Verificar loading
4358
+ isAditionalButtonLoading(buttonType, id) {
4359
+ const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
4360
+ return this.loadingStates.aditionalButtons[key] === 'loading';
4361
+ }
4362
+ // ==================================================
4363
+ // Expandir filas
4364
+ // ==================================================
4365
+ // Método para verificar si la tabla tiene filas expandibles
4366
+ hasExpandable() {
4367
+ return this.columns.some(col => typeof col.expandTemplate === 'function');
4368
+ }
4369
+ // Método para verificar si una fila está expandida
4370
+ toggleRow(row) {
4371
+ if (this.expandedRows.has(row)) {
4372
+ this.expandedRows.delete(row);
4373
+ }
4374
+ else {
4375
+ this.expandedRows.add(row);
4376
+ }
4377
+ }
4378
+ // Método para obtener contenido expandido de una fila
4379
+ getExpandedContent(row) {
4380
+ const expandableColumn = this.columns.find(col => typeof col.expandTemplate === 'function');
4381
+ return expandableColumn?.expandTemplate?.(row) ?? '';
4382
+ }
4383
+ // Método para obtener el estado de expansión de una fila
4384
+ getExpansionState(row) {
4385
+ return this.expandedRows.has(row) ? 'expanded' : 'collapsed';
4386
+ }
4387
+ 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 });
4388
+ 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: [
4389
+ trigger('slideToggle', [
4390
+ state('collapsed', style({
4391
+ height: '0',
4392
+ opacity: 0,
4393
+ overflow: 'hidden',
4394
+ })),
4395
+ state('expanded', style({
4396
+ height: '*',
4397
+ opacity: 1,
4398
+ })),
4399
+ transition('collapsed <=> expanded', [
4400
+ animate('0.3s ease-in-out')
4401
+ ])
4402
+ ])
4403
+ ] });
4404
+ }
4405
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.7", ngImport: i0, type: JTableComponent, decorators: [{
4406
+ type: Component,
4407
+ args: [{ selector: 'JCrudTable', standalone: true, imports: [CommonModule, FormsModule, JPaginatorComponent, JFilterComponent, LucideAngularModule, JButtonComponent, JCheckboxComponent], animations: [
4408
+ trigger('slideToggle', [
4409
+ state('collapsed', style({
4410
+ height: '0',
4411
+ opacity: 0,
4412
+ overflow: 'hidden',
4413
+ })),
4414
+ state('expanded', style({
4415
+ height: '*',
4416
+ opacity: 1,
4417
+ })),
4418
+ transition('collapsed <=> expanded', [
4419
+ animate('0.3s ease-in-out')
4420
+ ])
4421
+ ])
4422
+ ], 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>" }]
4423
+ }], ctorParameters: () => [{ type: i1$1.CurrencyPipe }, { type: JGenericService }, { type: JAlertToastService }, { type: ConverterService }, { type: JCalendarService }], propDecorators: { dataLoaded: [{
4424
+ type: Output
4425
+ }], endpoint: [{
4426
+ type: Input
4427
+ }], columns: [{
4428
+ type: Input
4429
+ }], defaultFilters: [{
4430
+ type: Input
4431
+ }], isPaginator: [{
4432
+ type: Input
4433
+ }], isSearch: [{
4434
+ type: Input
4435
+ }], itemsPerPageOptions: [{
4436
+ type: Input
4437
+ }], searchPlaceholder: [{
4438
+ type: Input
4439
+ }], checked: [{
4440
+ type: Input
4441
+ }], checkedValues: [{
4442
+ type: Input
4443
+ }], checkedTitles: [{
4444
+ type: Input
4445
+ }], filtersButton: [{
4446
+ type: Input
4447
+ }], filtersSelect: [{
4448
+ type: Input
4449
+ }], optionsTable: [{
4450
+ type: Input
4451
+ }] } });
4452
+
4453
+ // =======================================
4454
+ // Publicar libreria
4455
+ // =======================================
4456
+ // ng build tailjng
4457
+ // cd .\dist\tailjng\
4458
+ // npm publish --access public
242
4459
  /*
243
4460
  * Public API Surface of tailjng
244
4461
  */
4462
+ // export * from './lib/tailjng.service';
4463
+ // export * from './lib/tailjng.component';
4464
+ // Mode Toggle
245
4465
 
246
4466
  /**
247
4467
  * Generated bundle index. Do not edit.
248
4468
  */
249
4469
 
250
- export { JTooltipModule, TailjngComponent, TailjngService };
4470
+ export { API_URL, CardComponent, JAlertDialogComponent, JAlertDialogService, JAlertToastComponent, JAlertToastService, JButtonComponent, JCheckboxComponent, JDialogComponent, JDialogShared, JErrorHandlerService, JFilterComponent, JFormComponent, JFormShared, JGenericService, JHttpParamsService, JInputComponent, JLabelComponent, JModeToggleComponent, JPaginatorComponent, JSelectComponent, JTableComponent, JThemeComponent, JToggleRadioComponent, JTooltipModule };
251
4471
  //# sourceMappingURL=tailjng.mjs.map