tailjng 0.0.1 → 0.0.2

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