@controladad/ng-base
Version:
Everything you need for Angular
1 lines • 650 kB
Source Map (JSON)
{"version":3,"file":"controladad-ng-base.mjs","sources":["../../../../libs/base/src/configs/tokens.ts","../../../../libs/base/src/configs/icons.ts","../../../../libs/base/src/configs/global-config.ts","../../../../libs/base/src/core/interceptors/api.interceptor.ts","../../../../libs/base/src/core/states/_base.store.ts","../../../../libs/base/src/core/states/app-base.store.ts","../../../../libs/base/src/shared/classes/sort-model.ts","../../../../libs/base/src/shared/classes/selection-model.ts","../../../../libs/base/src/shared/data/index.ts","../../../../libs/base/src/shared/classes/filter-model.ts","../../../../libs/base/src/shared/components/ui/icon/icon.component.ts","../../../../libs/base/src/shared/components/ui/icon/icon.component.html","../../../../libs/base/src/shared/components/ui/button/button.component.ts","../../../../libs/base/src/shared/components/ui/button/button.component.html","../../../../libs/base/src/shared/components/ui/checkbox/checkbox.component.ts","../../../../libs/base/src/shared/components/ui/checkbox/checkbox.component.html","../../../../libs/base/src/shared/components/ui/checkbox-group/checkbox-group.component.ts","../../../../libs/base/src/shared/components/ui/checkbox-group/checkbox-group.component.html","../../../../libs/base/src/shared/components/ui/chips/chips.component.ts","../../../../libs/base/src/shared/components/ui/chips/chips.component.html","../../../../libs/base/src/shared/components/ui/chips-group/chips-group.component.ts","../../../../libs/base/src/shared/components/ui/chips-group/chips-group.component.html","../../../../libs/base/src/shared/components/ui/control-error/control-error.component.ts","../../../../libs/base/src/shared/components/ui/control-error/control-error.component.html","../../../../libs/base/src/shared/directives/ui-radio-compare-with.directive.ts","../../../../libs/base/src/shared/directives/permission-hide.directive.ts","../../../../libs/base/src/shared/directives/for-number.directive.ts","../../../../libs/base/src/shared/directives/tooltip.directive.ts","../../../../libs/base/src/shared/directives/mask/config.ts","../../../../libs/base/src/shared/directives/mask/input-mask.directive.ts","../../../../libs/base/src/shared/directives/mask/constants.ts","../../../../libs/base/src/shared/directives/ng-is-in-view.directive.ts","../../../../libs/base/src/shared/components/ui/select-options/select-options.component.ts","../../../../libs/base/src/shared/components/ui/select-options/select-options.component.html","../../../../libs/base/src/shared/components/ui/select-options/select-options-trigger-for.directive.ts","../../../../libs/base/src/shared/components/ui/field/field.component.ts","../../../../libs/base/src/shared/components/ui/field/field.component.html","../../../../libs/base/src/shared/components/ui/date-picker/date-picker.component.ts","../../../../libs/base/src/shared/components/ui/date-picker/date-picker.component.html","../../../../libs/base/src/shared/components/ui/license-plate/license-plate.component.ts","../../../../libs/base/src/shared/components/ui/license-plate/license-plate.component.html","../../../../libs/base/src/shared/components/ui/pagination/pagination.component.ts","../../../../libs/base/src/shared/components/ui/pagination/pagination.component.html","../../../../libs/base/src/shared/components/ui/radio/radio.component.ts","../../../../libs/base/src/shared/components/ui/radio/radio.component.html","../../../../libs/base/src/shared/components/ui/slider/slider.component.ts","../../../../libs/base/src/shared/components/ui/slider/slider.component.html","../../../../libs/base/src/shared/components/ui/switch/switch.component.ts","../../../../libs/base/src/shared/components/ui/switch/switch.component.html","../../../../libs/base/src/shared/components/ui/tabs/tab.component.ts","../../../../libs/base/src/shared/components/ui/tabs/tabs.component.ts","../../../../libs/base/src/shared/components/ui/tabs/tabs.component.html","../../../../libs/base/src/shared/components/ui/tabs/index.ts","../../../../libs/base/src/shared/components/ui/loader-screen/loader-screen.component.ts","../../../../libs/base/src/shared/components/ui/loader-screen/loader-screen.component.html","../../../../libs/base/src/shared/components/ui/skeleton/skeleton.component.ts","../../../../libs/base/src/shared/components/ui/skeleton/skeleton.component.html","../../../../libs/base/src/shared/components/ui/field-view/field-view.component.ts","../../../../libs/base/src/shared/components/ui/field-view/field-view.component.html","../../../../libs/base/src/shared/components/features/bottom-controls/bottom-controls.component.ts","../../../../libs/base/src/shared/components/features/bottom-controls/bottom-controls.component.html","../../../../libs/base/src/shared/components/layouts/dialog-layout/dialog-layout.component.ts","../../../../libs/base/src/shared/components/layouts/dialog-layout/dialog-layout.component.html","../../../../libs/base/src/shared/components/features/form-builder/form-builder.ts","../../../../libs/base/src/shared/components/features/form-builder/control-builder.component.ts","../../../../libs/base/src/shared/components/features/form-builder/control-builder.component.html","../../../../libs/base/src/shared/components/features/form-builder/form-builder.component.ts","../../../../libs/base/src/shared/components/features/form-builder/form-builder.component.html","../../../../libs/base/src/shared/components/features/license-plate-select/license-plate-select.component.ts","../../../../libs/base/src/shared/components/features/license-plate-select/license-plate-select.component.html","../../../../libs/base/src/shared/components/features/login-form/login-form.component.ts","../../../../libs/base/src/shared/components/features/login-form/login-form.component.html","../../../../libs/base/src/shared/components/features/printable-table/printable-table.component.ts","../../../../libs/base/src/shared/components/features/printable-table/printable-table.component.html","../../../../libs/base/src/shared/components/features/snackbar/snackbar-base/snackbar-base.component.ts","../../../../libs/base/src/shared/components/features/snackbar/snackbar-base/snackbar-base.component.html","../../../../libs/base/src/shared/components/features/snackbar/snackbar.service.ts","../../../../libs/base/src/shared/components/features/table/table-filter-bar/table-filter-bar.component.ts","../../../../libs/base/src/shared/components/features/table/table-filter-bar/table-filter-bar.component.html","../../../../libs/base/src/shared/components/features/table/table-form-menu/table-form-menu.component.ts","../../../../libs/base/src/shared/components/features/table/table-form-menu/table-form-menu.component.html","../../../../libs/base/src/shared/components/features/table/columns/_table_col_base.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-selection/table-col-selection.component.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-selection/table-col-selection.component.html","../../../../libs/base/src/shared/components/features/table/columns/table-col-index/table-col-index.component.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-index/table-col-index.component.html","../../../../libs/base/src/shared/components/features/table/table-filter-menu/table-filter-menu.component.ts","../../../../libs/base/src/shared/components/features/table/table-filter-menu/table-filter-menu.component.html","../../../../libs/base/src/shared/components/features/table/table-filter/table-filter.component.ts","../../../../libs/base/src/shared/components/features/table/table-filter/table-filter.component.html","../../../../libs/base/src/shared/components/features/table/table-sort/table-sort.component.ts","../../../../libs/base/src/shared/components/features/table/table-sort/table-sort.component.html","../../../../libs/base/src/shared/pipes/badge.pipe.ts","../../../../libs/base/src/shared/pipes/typeof.pipe.ts","../../../../libs/base/src/shared/pipes/datefns.pipe.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-default/table-col-default.component.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-default/table-col-default.component.html","../../../../libs/base/src/shared/components/features/table/columns/table-col-action/table-col-action.component.ts","../../../../libs/base/src/shared/components/features/table/columns/table-col-action/table-col-action.component.html","../../../../libs/base/src/shared/components/features/table/table-header/table-header.component.ts","../../../../libs/base/src/shared/components/features/table/table-header/table-header.component.html","../../../../libs/base/src/shared/components/dialog/components/_base-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/prompt-dialog/prompt-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/prompt-dialog/prompt-dialog.component.html","../../../../libs/base/src/shared/components/dialog/dialog-invoker.service.ts","../../../../libs/base/src/shared/components/dialog/components/input-dialog/input-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/input-dialog/input-dialog.component.html","../../../../libs/base/src/shared/components/dialog/components/description-dialog/description-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/description-dialog/description-dialog.component.html","../../../../libs/base/src/shared/components/dialog/components/change-password-dialog/change-password-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/change-password-dialog/change-password-dialog.component.html","../../../../libs/base/src/shared/components/dialog/components/calendar-dialog/calendar-dialog.component.ts","../../../../libs/base/src/shared/components/dialog/components/calendar-dialog/calendar-dialog.component.html","../../../../libs/base/src/shared/components/dialog/dialog.service.ts","../../../../libs/base/src/shared/components/features/table/table.component.ts","../../../../libs/base/src/shared/components/features/table/table.component.html","../../../../libs/base/src/shared/components/features/table/adapters/table-strapi-adapter.service.ts","../../../../libs/base/src/shared/components/features/initialize-protection/initialize-protection.component.ts","../../../../libs/base/src/shared/components/features/initialize-protection/initialize-protection.component.html","../../../../libs/base/src/shared/components/features/image-uploader/image-uploader.component.ts","../../../../libs/base/src/shared/components/features/image-uploader/image-uploader.component.html","../../../../libs/base/src/core/helpers/errors.helper.ts","../../../../libs/base/src/core/helpers/date.helper.ts","../../../../libs/base/src/core/helpers/text.helper.ts","../../../../libs/base/src/core/helpers/router.helper.ts","../../../../libs/base/src/core/helpers/signal.helper.ts","../../../../libs/base/src/core/helpers/object.helper.ts","../../../../libs/base/src/core/helpers/observable.helper.ts","../../../../libs/base/src/core/helpers/array.helper.ts","../../../../libs/base/src/core/helpers/role.helper.ts","../../../../libs/base/src/core/helpers/providers.helper.ts","../../../../libs/base/src/core/helpers/api.helper.ts","../../../../libs/base/src/core/states/auth-base.store.ts","../../../../libs/base/src/core/interceptors/token.interceptor.ts","../../../../libs/base/src/core/interceptors/error.interceptor.ts","../../../../libs/base/src/core/adapters/material-datefns-jalali.adapter.ts","../../../../libs/base/src/core/adapters/get-offline.adapter.ts","../../../../libs/base/src/core/guards/auth.guard.ts","../../../../libs/base/src/core/guards/non-auth.guard.ts","../../../../libs/base/src/core/services/route-helper.service.ts","../../../../libs/base/src/core/services/_base-api.ts","../../../../libs/base/src/core/guards/permission.guard.ts","../../../../libs/base/src/core/models/api.model.ts","../../../../libs/base/src/globals.ts","../../../../libs/base/src/providers.ts","../../../../libs/base/src/controladad-ng-base.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport const API_BASEURL = new InjectionToken<string>('API_BASEURL');\nexport const ENVIRONMENT = new InjectionToken<any>('ENVIRONMENT');\n","import { inject } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { MatIconRegistry } from '@angular/material/icon';\n\n// prettier-ignore\nconst baseIcons = [\n 'add', 'arrow-down', 'arrow-up', 'arrow-left', 'arrow-right', 'calendar', 'camera', 'check', 'check-double',\n 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'clock', 'close', 'delete', 'dropdown', 'edit',\n 'edit-box', 'error', 'excel-file', 'eye', 'eye-slash', 'filter', 'filter-filled', 'hashtag', 'info', 'info-circle',\n 'list', 'location', 'location-check', 'login', 'logout', 'menu', 'nav', 'numeric-down', 'numeric-up', 'paper',\n 'paper-details', 'password', 'phone', 'plate', 'play', 'plus', 'power', 'print', 'question-circle', 'refresh',\n 'reports', 'save', 'search', 'settings', 'sort', 'sort-down', 'sort-up', 'time', 'trash', 'trash-alt', 'user',\n 'user-circle', 'users', 'wrench'\n] as const;\n\nexport type BASE_ICONS = (typeof baseIcons)[number];\n\nlet isBaseRegistered = false;\nexport function registerIcons(config?: {\n additionalIcons?: string[];\n additionalIconsDir?: string;\n}) {\n const sanitizer = inject(DomSanitizer);\n const iconRegistry = inject(MatIconRegistry);\n\n if (!isBaseRegistered) {\n for (const icon of baseIcons) {\n iconRegistry.addSvgIcon(icon, sanitizer.bypassSecurityTrustResourceUrl(`./assets/base/icons/${icon}.svg`));\n }\n isBaseRegistered = true;\n }\n\n for (const icon of config?.additionalIcons ?? []) {\n let dir = config?.additionalIconsDir ?? `./assets/icons`;\n if (dir.endsWith('/')) {\n dir = dir.slice(0, -1);\n }\n iconRegistry.addSvgIcon(icon, sanitizer.bypassSecurityTrustResourceUrl(`${dir}/${icon}.svg`));\n }\n}\n","export interface CacBaseLocalizationConfig {\n // array of supported languages, the first index will be selected as default.\n //\n // Default Value: ['en']\n langs: string[];\n localesPath?: string;\n localesData?: { [p: string]: { dateLocale?: any; dateFormats?: any; localeData?: any } };\n\n forceDateFnsLib?: 'jalali' | 'georgian' | undefined,\n}\n\nexport class CacGlobalConfig {\n // Do not change this variable manually, it's automatically updated via the provider.\n static defaultLang = 'en';\n static applicationName = '';\n static localization: CacBaseLocalizationConfig = {\n langs: ['en'],\n };\n // applies application name to store keys, for example if your store key is `auth`, it will become `app_name_auth`\n static applyPrefixToStorageKeys = true;\n\n static generateStoreKey(key: string) {\n return `${this.applyPrefixToStorageKeys && this.applicationName.length ? `${this.applicationName}_` : ''}${key}`;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { API_BASEURL } from '../../configs';\n\n@Injectable()\nexport class ApiInterceptor implements HttpInterceptor {\n readonly apiBaseUrl = inject(API_BASEURL);\n\n // using inject() causes circular error, idk why...\n // readonly apiBaseUrl = inject(API_BASEURL);\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n request = request.clone({\n url: request.url.startsWith('/') ? this.apiBaseUrl + request.url : request.url,\n });\n return next.handle(request);\n }\n}\n","import { createStore, Store, withProps } from '@ngneat/elf';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { localStorageStrategy, persistState, StateStorage } from '@ngneat/elf-persist-state';\nimport { CacGlobalConfig } from '../../configs';\nimport { Signal } from '@angular/core';\n\nexport class BaseStore<T extends object> {\n public state$;\n public signal!: Signal<T | undefined>;\n public store: Store<any, T>;\n\n constructor(public storeOpts: {\n key: string;\n // ignore mutations in key such as applying application as prefix (mutations can be controlled from Global Config)\n exactKey?: boolean;\n default?: T;\n // defaults to localStorage\n storageStrategy?: StateStorage;\n }) {\n const key = storeOpts.exactKey ? storeOpts.key : CacGlobalConfig.generateStoreKey(storeOpts.key);\n const store = createStore({ name: key }, withProps<T>(storeOpts.default ?? {} as never));\n persistState(store, {\n key: key,\n storage: storeOpts.storageStrategy ?? localStorageStrategy,\n });\n\n this.store = store;\n this.state$ = this.store.pipe();\n\n try {\n this.signal = toSignal(this.state$);\n } catch { /* empty */ }\n }\n\n get state() {\n return this.get();\n }\n\n get() {\n return this.store.getValue();\n }\n\n patch(value: Partial<T>) {\n return this.store.update((s) => ({\n ...s,\n ...value,\n }));\n }\n}\n","import { Injectable } from '@angular/core';\nimport { select } from '@ngneat/elf';\nimport { BaseStore } from './_base.store';\nimport { CacGlobalConfig } from '../../configs';\n\nexport interface AppBaseStoreProps {\n rememberMe?: boolean;\n lang?: string;\n}\n\nexport class _AppBaseStore<T extends AppBaseStoreProps> extends BaseStore<T> {\n constructor(defaults?: T) {\n super({\n key: 'app',\n default: defaults,\n });\n }\n\n rememberMe$ = this.store.pipe(select(this.rememberMe));\n lang$ = this.store.pipe(select(this.lang));\n\n rememberMe() {\n return this.get().rememberMe ?? false;\n }\n\n setRememberMe(value: boolean) {\n // @ts-ignore\n this.patch({\n rememberMe: value,\n });\n }\n\n lang() {\n return this.get().lang ?? CacGlobalConfig.defaultLang;\n }\n\n setLang(value: string) {\n // @ts-ignore\n this.patch({\n lang: value,\n });\n }\n}\n\n// This is dummy, used to make service out of the `_AppBaseStore`. For extension, `_AppBaseStore` should be used\n@Injectable({\n providedIn: 'root',\n})\nexport class AppBaseStore extends _AppBaseStore<AppBaseStoreProps> {\n constructor() {\n super();\n }\n}\n","import { signal } from '@angular/core';\nimport { debounceTime, Subject } from 'rxjs';\nimport { DataGetRequest, DataSortDirection } from '../../core';\n\nexport class SortModel {\n private _key = signal<string | undefined>(undefined);\n private _direction = signal<DataSortDirection>('desc');\n private _changes$ = new Subject<[string | undefined, DataSortDirection]>();\n\n key = this._key.asReadonly();\n direction = this._direction.asReadonly();\n changes$ = this._changes$.pipe(debounceTime(50));\n\n constructor(key?: string, direction?: DataSortDirection) {\n this._key.set(key);\n this._direction.set(direction ?? 'desc');\n }\n\n setKey(sort: string | undefined) {\n this._key.set(sort);\n this.emit();\n }\n setDirection(direction: DataSortDirection) {\n this._direction.set(direction);\n this.emit();\n }\n\n create(): DataGetRequest['sort'] | undefined {\n const key = this.key();\n return key\n ? {\n key: key,\n direction: this.direction(),\n }\n : undefined;\n }\n\n private emit() {\n this._changes$.next([this._key(), this._direction()]);\n }\n}\n","import { computed, DestroyRef, signal } from '@angular/core';\nimport { ItemToId } from '../../core';\nimport { FormControlExtended } from '@al00x/forms';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { startWith } from 'rxjs';\n\n// TODO: Update constructor initial parameters (turn into object, it's messy currently)\n// TODO: add formControl binding\n// TODO: turn off multiple mode by default\n\nexport class SelectionModel<T> {\n private _totalCount = 0;\n private _selectedCount = 0;\n private _multiple;\n private _itemToId: ItemToId<T>;\n private _currentViewItems: T[] = [];\n private _boundFormControl?: FormControlExtended;\n private _blockBoundUpdate = false;\n\n // unique identifier generated randomly\n id: string;\n\n public indeterminate = signal(false);\n public allSelected = signal(false);\n public selected = signal<T[]>([]);\n public selectedIds = signal<{ [p: string | number]: boolean }>({});\n public selectedCount = computed(() => this.selected()?.length ?? 0);\n public hasSelection = computed(() => this.selectedCount() !== 0);\n\n constructor(itemsCount?: number, multiple?: boolean, initial?: T[], itemToId?: ItemToId<T>) {\n this.id = crypto.randomUUID();\n this._totalCount = itemsCount ?? 0;\n this._multiple = multiple ?? true;\n this._itemToId = itemToId ?? (((t) => (t && typeof t === 'object' && 'id' in t ? t.id : t)) as ItemToId<T>);\n initial ? this.select(...initial) : null;\n }\n\n get isMultiple() {\n return this._multiple;\n }\n\n public select(...items: T[]) {\n if (this._multiple) {\n this.set(...[...new Set([...this.selected(), ...items])]);\n } else if (items.length) {\n // this.clear();\n this.set(items[0]);\n }\n }\n\n public deselect(...items: T[]) {\n const itemsId = items.map(this._itemToId);\n this.set(...this.selected().filter((t) => !itemsId.includes(this._itemToId(t))));\n }\n\n public toggle(...items: T[]) {\n const selected = this.selected();\n\n if (this._multiple) {\n let newItems: T[] = [...selected];\n if (newItems.length > 0) {\n for (const item of items) {\n const index = selected.findIndex((t) => this._itemToId(t) === this._itemToId(item));\n if (index !== -1) {\n newItems.splice(index, 1);\n } else {\n newItems.push(item);\n }\n }\n } else {\n newItems = [...items];\n }\n\n this.set(...newItems);\n } else {\n if (selected.some(t => t === items[0])) {\n this.clear();\n } else {\n this.set(items[0]);\n }\n }\n }\n\n public selectAll() {\n this.select(...this._currentViewItems);\n }\n\n public deselectAll() {\n this.deselect(...this._currentViewItems);\n }\n\n public toggleAll() {\n if (this._selectedCount === this._totalCount) {\n this.deselectAll();\n } else {\n this.selectAll();\n }\n }\n\n public clear() {\n if (this._selectedCount === 0) return;\n this.set();\n }\n\n public set(...items: T[]) {\n this.selected.set(items);\n this.selectedIds.set(\n items\n .map((t) => this._itemToId(t))\n .reduce(\n (pre, cur) => ({\n ...pre,\n [cur]: true,\n }),\n {},\n ),\n );\n this._selectedCount = items.length;\n\n this.calculateSelectionState();\n this.updateBoundedFormControl();\n }\n\n public isSelected(item: T) {\n return !!this.selectedIds()[this._itemToId(item)];\n }\n\n public setTotalCount(count: number) {\n this._totalCount = count;\n\n this.calculateSelectionState();\n }\n\n public setItems(items: T[], setTotalCount = true) {\n this._currentViewItems = items;\n if (setTotalCount) {\n this._totalCount = items.length;\n }\n\n this.calculateSelectionState();\n }\n\n public setItemToIdFn(fn: ItemToId<T>) {\n this._itemToId = fn;\n }\n\n public setMultiple(value?: boolean) {\n this._multiple = value === undefined ? true : value;\n }\n\n public bindFormControl(control: FormControlExtended | undefined, destroyRef?: DestroyRef) {\n this._boundFormControl = control;\n if (!control) return;\n\n control.valueChanges.pipe(startWith(control.value), takeUntilDestroyed(destroyRef)).subscribe((v) => {\n if (this._blockBoundUpdate || v === this.selectedIds()) {\n this._blockBoundUpdate = false;\n return;\n }\n\n if (v === null || v === undefined) {\n this.clear();\n } else {\n this.set(...(v instanceof Array ? v : [v]));\n }\n });\n }\n\n private updateBoundedFormControl() {\n if (!this._boundFormControl) return;\n\n this._blockBoundUpdate = true;\n this._boundFormControl.setValue(this.selectedIds());\n this._boundFormControl.setSelectedItems(this.selected());\n }\n\n private calculateSelectionState() {\n const selectedViewItemsCount = this._currentViewItems\n .map(this._itemToId)\n .filter((t) => this.selectedIds()[t]).length;\n if (this._selectedCount === 0 || selectedViewItemsCount === 0) {\n this.indeterminate.set(false);\n this.allSelected.set(false);\n } else if (this._selectedCount === this._totalCount) {\n this.indeterminate.set(false);\n this.allSelected.set(true);\n } else {\n this.indeterminate.set(selectedViewItemsCount !== this._currentViewItems.length);\n this.allSelected.set(!this.indeterminate());\n }\n }\n}\n","import type { ItemRecord } from '../../core';\n\nexport const ActiveValues: ItemRecord<boolean>[] = [\n { value: true, label: 'Active' },\n { value: false, label: 'Inactive' },\n];\n\nexport const SuspendedValues: ItemRecord<boolean>[] = [\n { value: false, label: 'Active' },\n { value: true, label: 'Inactive' },\n];\n\nexport const BooleanValues: ItemRecord<boolean>[] = [\n { value: true, label: 'Yes' },\n { value: false, label: 'No' },\n];\n","import { computed, signal } from '@angular/core';\nimport { BehaviorSubject, debounceTime, Subject } from 'rxjs';\nimport {\n DataFilterTypes,\n DataGetRequest,\n ItemRecords$,\n ArrayHelper, DateHelper\n} from '../../core';\nimport type { FormBuilderInputType, TableColumnData, TableColumnFilter, TableFilterOptions } from '../components';\nimport { BooleanValues } from '../data';\n\nexport interface FilterValue {\n key?: string;\n value?: Date | string | number | boolean | string[] | number[];\n displayText?: string | string[];\n type: DataFilterTypes;\n controlType?: FormBuilderInputType;\n}\n\nexport interface FilterOptions {\n prop: string;\n label: string;\n items?: ItemRecords$<any>;\n filterable?: TableColumnFilter;\n icon?: string;\n}\n\nexport interface FilterItem {\n // prop is used to identify the filter\n prop: string;\n values: FilterValue[] | undefined;\n // key is for the filtering purpose\n key: string;\n label: string;\n // The prob used, has been set manually and strictly (ex: when using col.filterable as a string)\n strictKey?: boolean;\n // Displayable text for filter\n formatted?: {\n full: string;\n prefix: string;\n text: string;\n suffix: string;\n };\n icon?: string;\n}\n\nexport type FilterObject = { [p: string]: FilterItem | undefined };\n\nexport type TableFilterObject = { [p: string]: FilterValue[] };\n\nexport class FilterModel {\n protected _filters = signal<FilterObject>({});\n private _changes$ = new Subject<FilterObject | undefined>();\n\n filters = computed(() => {\n const obj = this._filters();\n if (!obj) return this.overrideEmpty;\n Object.keys(obj).forEach((key) => obj[key] === undefined && delete obj[key]);\n if (Object.keys(obj).length === 0) return this.overrideEmpty;\n return obj as FilterObject;\n });\n filtersArray = computed(() => {\n const filters = this.filters();\n if (!filters) return [];\n return Object.values(filters) as FilterItem[];\n });\n hasFilter = computed(() => {\n return this.filters() !== undefined;\n });\n changes$ = this._changes$.pipe(debounceTime(50));\n\n constructor(\n init?: FilterObject,\n public overrideEmpty?: FilterObject,\n ) {\n this._filters.set(init ?? overrideEmpty ?? {});\n }\n\n set(data: FilterOptions, values: FilterValue[] | undefined, emit = true) {\n if (values === undefined || values.every((t) => t.value === undefined || t.value === null)) {\n this.remove(data.prop);\n return;\n }\n\n this._filters.set({\n ...this._filters(),\n [data.prop]: this.createFilterItem(data, values),\n });\n\n if (emit) this.emitChanges();\n }\n\n remove(prop: string) {\n this._filters.set({\n ...this._filters(),\n [prop]: undefined,\n });\n\n this.emitChanges();\n }\n\n clear() {\n this._filters.set({});\n\n this.emitChanges();\n }\n\n create(): DataGetRequest['filters'] {\n const filters = this.filtersArray();\n return filters\n .filter((t) => !!t.values && t.values.length > 0)\n .map((item) =>\n item.values!.map((v) => ({\n key: v.key ?? item.key,\n strictKey: !!item.strictKey || !!v.key,\n value: this.mapValueToString(v.value),\n type: v.type,\n })),\n )\n .reduce((pre, cur) => pre.concat(cur), []);\n }\n\n // Emit will be called automatically, use this is you disabled emit on any functions\n emitChanges() {\n this._changes$.next(this._filters());\n }\n\n protected createFilterItem(opts: FilterOptions, value?: FilterValue[]) {\n const item: FilterItem = {\n ...this.getKey(opts),\n values: undefined,\n prop: opts.prop,\n label: opts.label,\n icon: opts.icon,\n };\n return this.setValueForFilterItem(item, value, opts.items);\n }\n\n protected setValueForFilterItem(item: FilterItem, values?: FilterValue[], records?: ItemRecords$<any>) {\n const newValues = values\n ? values.every((t) => t.value === null || t.value === undefined)\n ? undefined\n : values.map((t) => {\n const value = t.value;\n\n if (value instanceof Date && t.controlType === 'date') {\n if (t.type === 'lower') {\n value.setHours(23, 59, 59, 999);\n } else {\n value.setHours(0, 0, 0, 0);\n }\n }\n\n return {\n ...t,\n value,\n } as typeof t;\n })\n : undefined;\n\n item.values = newValues;\n item.formatted = newValues ? this.formatFilterItem(item, records) : undefined;\n return item;\n }\n\n private mapValueToString(value: FilterValue['value']): string | string[] {\n if (value instanceof Array) {\n return value.map((t) => this.mapValueToString(t) as string);\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === 'boolean') {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'number') {\n return value.toString();\n }\n return value ?? '';\n }\n\n private mapValueToReadable(filterValue: FilterValue, records?: ItemRecords$<any>): string {\n const value = filterValue.value;\n if (value instanceof Date) {\n if (filterValue.controlType === 'datetime') {\n return DateHelper.format(value, 'HH:mm ,yyyy/MM/dd');\n }\n return DateHelper.format(value);\n }\n if (filterValue.displayText && filterValue.displayText.length) {\n return filterValue.displayText instanceof Array ? filterValue.displayText.join(', ') : filterValue.displayText;\n }\n if (records && records instanceof Array) {\n return ArrayHelper.getFromItemRecord(records, value)?.label ?? '';\n }\n if (typeof value === 'boolean') {\n return value ? $localize`:@@base.values.trueText:Yes` : $localize`:@@base.values.falseText:No`;\n }\n if (typeof value === 'number') {\n return value.toString();\n }\n return value instanceof Array ? (value.length ? value.join(', ') : '') : value ?? '';\n }\n\n private getKey(data: FilterOptions): { key: string; strictKey?: boolean } {\n if (data.filterable === true) {\n return { key: data.prop };\n } else if (typeof data.filterable === 'string') {\n return data.filterable !== '' ? { key: data.filterable, strictKey: true } : { key: data.prop };\n }\n return { key: data.prop };\n }\n\n private formatFilterItem(item: FilterItem, records?: ItemRecords$<any>): FilterItem['formatted'] {\n let prefix = '';\n let suffix = '';\n\n const equal = item.values?.find((t) => t.type === 'equal' || t.type === 'contains');\n const greater = item.values?.find((t) => t.type === 'greater');\n const lower = item.values?.find((t) => t.type === 'lower');\n\n const equalValue =\n equal?.value !== undefined && equal?.value !== null ? this.mapValueToReadable(equal, records) : undefined;\n const greaterValue =\n greater?.value !== undefined && greater?.value !== null ? this.mapValueToReadable(greater, records) : undefined;\n const lowerValue =\n lower?.value !== undefined && lower?.value !== null ? this.mapValueToReadable(lower, records) : undefined;\n\n if (equalValue !== undefined && equalValue !== null) {\n suffix += ` : ${equalValue}`;\n } else if (greaterValue !== undefined && greaterValue !== null && lowerValue !== undefined && lowerValue !== null) {\n prefix += `${greaterValue} < `;\n suffix += ` < ${lowerValue}`;\n } else if (greaterValue !== undefined && greaterValue !== null) {\n suffix += ` > ${greaterValue}`;\n } else if (lowerValue !== undefined && lowerValue !== null) {\n suffix += ` < ${lowerValue}`;\n }\n\n if (prefix === '' && suffix === '') return undefined;\n return {\n full: `${prefix}${item.label}${suffix}`,\n text: item.label,\n prefix: prefix,\n suffix: suffix,\n };\n }\n}\n\nexport class TableFilterModel extends FilterModel {\n private _columns?: TableColumnData<any>[];\n\n columnFilters: { [p: string]: TableFilterOptions[] } = {};\n columnLabels: { [p: string]: string } = {};\n\n columnsChanged$ = new BehaviorSubject<void>(undefined);\n\n constructor(\n public initValue?: FilterObject,\n public overrideEmptyValue?: TableFilterObject,\n ) {\n super(initValue);\n }\n\n override set(columnProp: FilterOptions | string, values: FilterValue[] | undefined, emit = true) {\n if (typeof columnProp === 'object') {\n super.set(columnProp, values, emit);\n return;\n }\n\n if (!this._columns) return;\n\n const col = this._columns.find((t) => t.prop === columnProp);\n\n if (!col) {\n console.error(`Table filter failed with the given prop: '${columnProp}'`);\n return;\n }\n\n super.set(col, values, emit);\n }\n\n setColumns(columns: typeof this._columns) {\n this._columns = columns;\n this.updateFilters();\n this.setOverrideEmpty();\n\n this.columnsChanged$.next();\n }\n\n getColumnFilters(columnProp: string): TableFilterOptions[] | undefined {\n return this.columnFilters[columnProp];\n }\n\n private setOverrideEmpty() {\n if (!this.overrideEmptyValue || !this._columns) return;\n\n this.overrideEmpty = {};\n for (const prop in this.overrideEmptyValue) {\n const column = this._columns.find((t) => t.prop === prop);\n if (!column) continue;\n\n const value = this.overrideEmptyValue[prop];\n const item = this.createFilterItem(column, value);\n this.overrideEmpty[item.prop] = item;\n }\n if (!this.initValue) {\n this._filters.set(this.overrideEmpty);\n }\n }\n\n private updateFilters() {\n if (!this._columns) return;\n\n this.columnFilters = {};\n this.columnLabels = {};\n for (const column of this._columns) {\n let filters: TableFilterOptions[] | undefined;\n if (column.filterable instanceof Array) {\n filters = column.filterable;\n } else if (column.filterable) {\n switch (column.type ?? 'text') {\n case 'number': {\n filters = [\n { inputType: 'number', type: 'equal' },\n { inputType: 'number', type: 'greater' },\n { inputType: 'number', type: 'lower' },\n ];\n break;\n }\n case 'boolean': {\n filters = [{ type: 'equal', controlType: 'select', items: column.items ?? BooleanValues }];\n break;\n }\n case 'plate':\n filters = [{ type: 'equal', controlType: 'plate' }];\n break;\n default: {\n filters = [\n {\n type: 'contains',\n controlType: column.items ? 'select' : 'input',\n items: column.items ?? undefined,\n },\n ];\n }\n }\n }\n\n if (filters) {\n this.columnLabels[column.prop] = column.label;\n this.columnFilters[column.prop] = filters;\n }\n }\n }\n}\n","import {\n Component,\n Input,\n OnChanges,\n SimpleChanges,\n HostBinding,\n Output,\n EventEmitter,\n OnInit,\n signal,\n ViewChild,\n AfterViewInit,\n InjectionToken,\n} from '@angular/core';\nimport { MatIcon, MatIconModule } from '@angular/material/icon';\nimport { NgStyle } from '@angular/common';\nimport { BASE_ICONS } from '../../../../configs';\nimport { Subscription } from 'rxjs';\nimport { componentWithDefaultConfig } from '../../../../core';\n\nexport type IconComponentType = InstanceType<typeof CacIconComponent>\nexport const ICON_COMPONENT_CONFIG = new InjectionToken<Partial<IconComponentType>>('IconComponent');\n\n@Component({\n selector: 'cac-icon',\n templateUrl: './icon.component.html',\n styleUrls: ['./icon.component.scss'],\n standalone: true,\n imports: [MatIconModule, NgStyle],\n})\nexport class CacIconComponent implements OnInit, OnChanges, AfterViewInit {\n @ViewChild('MatIcon') matIcon!: MatIcon;\n\n @Input() icon?: BASE_ICONS | string;\n @Input() disabled = false;\n @Input() size = '1.5rem';\n @Input() strokeWidth = 1.9;\n @Input() iconClass?: string;\n @Input() wrapperClass?: string;\n @Output() onClick = new EventEmitter<MouseEvent>();\n\n @HostBinding('class.pointer-events-none') pointerEventsNone = false;\n @HostBinding('style.width') thisWidth = '';\n @HostBinding('style.height') thisHeight = '';\n\n isClickable = signal(false);\n\n constructor() {\n componentWithDefaultConfig(this, ICON_COMPONENT_CONFIG);\n\n this.thisWidth = this.size;\n this.thisHeight = this.size;\n }\n\n ngOnInit() {\n this.isClickable.set(this.onClick.observed);\n }\n\n ngAfterViewInit() {\n // @ts-ignore\n const fetchSub = this.matIcon._currentIconFetch as Subscription;\n fetchSub.add(() => {\n const svgElement = this.matIcon._elementRef.nativeElement.children.item(0) as SVGElement;\n if (!svgElement) return;\n svgElement.style.strokeWidth = `${this.strokeWidth}`;\n });\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['disabled']) {\n this.pointerEventsNone = this.disabled;\n }\n if (changes['size']) {\n this.thisWidth = this.size;\n this.thisHeight = this.size;\n }\n }\n\n onClickEvent(e: MouseEvent) {\n if (this.disabled) return;\n\n this.onClick.emit(e);\n }\n}\n","<div\n role=\"presentation\"\n class=\"ui-icon relative {{ disabled ? 'disabled' : '' }} w-full h-full {{ wrapperClass }}\"\n [class.is-clickable]=\"isClickable()\"\n (click)=\"onClickEvent($event)\"\n >\n @if (!!icon) {\n <mat-icon\n #MatIcon\n class=\"{{ iconClass ?? '' }}\"\n [ngStyle]=\"{ width: size, height: size }\"\n [svgIcon]=\"icon\"\n ></mat-icon>\n }\n <ng-content></ng-content>\n</div>\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n EventEmitter,\n HostBinding,\n HostListener,\n InjectionToken,\n Input,\n OnChanges,\n OnInit,\n Output,\n signal,\n SimpleChanges,\n ViewChild,\n} from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { RouterLink } from '@angular/router';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { NgStyle, NgTemplateOutlet } from '@angular/common';\nimport { MatIconModule } from '@angular/material/icon';\nimport { pipe, tap, UnaryFunction } from 'rxjs';\nimport { ActionTypes, componentWithDefaultConfig, startWithTap } from '../../../../core';\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { CacIconComponent } from '../icon';\n\nexport interface ButtonClickEvent {\n event: MouseEvent;\n // If state is not provided, it will be toggled\n setLoading: (state?: boolean) => void;\n pipe: <T>() => UnaryFunction<T, T>;\n}\n\nexport type ButtonAppearanceType = 'stroked' | 'filled' | 'text';\n\nexport type ButtonThemeType = 'primary' | 'secondary' | 'tertiary' | 'error' | 'custom';\n\n// TODO: Fix Permissions\n\nexport type ButtonComponentType = InstanceType<typeof CacButtonComponent>\nexport const BUTTON_COMPONENT_CONFIG = new InjectionToken<Partial<ButtonComponentType>>('CacButtonComponent');\n\n@Component({\n selector: 'cac-button',\n templateUrl: './button.component.html',\n styleUrls: ['./button.component.scss'],\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n MatButtonModule,\n RouterLink,\n MatProgressSpinnerModule,\n NgTemplateOutlet,\n MatIconModule,\n NgStyle,\n MatTooltipModule,\n CacIconComponent\n],\n})\nexport class CacButtonComponent implements OnInit, AfterViewInit, OnChanges {\n @ViewChild('Button', { read: ElementRef }) btnElement?: ElementRef<HTMLButtonElement>;\n\n @Input() icon?: string;\n @Input() iconPosition: 'prefix' | 'suffix' = 'prefix';\n @Input() appearance: ButtonAppearanceType = 'filled';\n @Input() tonal = false;\n @Input() elevated = false;\n @Input() theme: ButtonThemeType = 'primary';\n @Input() disabled?: boolean | null = false;\n @Input('loading') loadingProp?: boolean = false;\n @Input() iconSize = '1.5rem';\n @Input() padding = '0.5rem 1rem';\n @Input() fitContent = false;\n @Input() align?: 'start' | 'end' | 'center' = 'center';\n // Navigate to the given route on click\n @Input() route?: string[];\n // TabIndex\n @Input() tab = 0;\n // Permission key\n @Input() permission?: string;\n // The action associated to a permission related to the current route\n @Input() action?: ActionTypes | ActionTypes[];\n\n @Output() onClick = new EventEmitter<ButtonClickEvent>();\n\n @HostBinding('class.filled') filledClass = true;\n @HostBinding('class.stroked') strokedClass = false;\n @HostBinding('class.text') textClass = false;\n @HostBinding('class.elevated') isElevated = false;\n @HostBinding('class.tonal') isTonal = false;\n @HostBinding('class.is-clicking') isClicking = false;\n @HostBinding('class.theme-primary') primaryClass = false;\n @HostBinding('class.theme-secondary') secondaryClass = false;\n @HostBinding('class.theme-tertiary') tertiaryClass = false;\n @HostBinding('class.theme-error') errorClass = false;\n @HostBinding('class.disabled') disabledClass = false;\n @HostBinding('class.cursor-not-allowed') cursorNotAllowed = false;\n\n loading = signal(false);\n insufficientPermission = signal(false);\n\n constructor() {\n componentWithDefaultConfig(this, BUTTON_COMPONENT_CONFIG);\n \n toObservable(this.insufficientPermission)\n .pipe(takeUntilDestroyed())\n .subscribe(() => {\n this.setDisabledClass();\n });\n }\n\n ngOnInit() {\n this.setTheme();\n this.checkPermission();\n }\n\n ngAfterViewInit() {\n setTimeout(() => {\n // Angular... doesn't bind tabIndex in template!\n this.btnElement!.nativeElement.tabIndex = this.tab;\n }, 5);\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['loading']) {\n this.loading.set(this.loadingProp ?? false);\n }\n if (changes['appearance']) {\n this.strokedClass = this.appearance === 'stroked';\n this.filledClass = this.appearance === 'filled';\n this.textClass = this.appearance === 'text';\n }\n if (changes['theme']) {\n this.setTheme();\n }\n if (changes['elevated']) {\n this.isElevated = this.elevated;\n }\n if (changes['tonal']) {\n this.isTonal = this.tonal;\n }\n this.setDisabledClass();\n }\n\n @HostListener('pointerdown', ['$event'])\n onPointerDown() {\n this.isClicking = true;\n }\n @HostListener('pointercancel', ['$event'])\n onPointerCancel() {\n this.isClicking = false;\n }\n @HostListener('pointerleave', ['$event'])\n onPointerLeave() {\n this.isClicking = false;\n }\n @HostListener('pointerup', ['$event'])\n onPointerUp() {\n this.isClicking = false;\n }\n\n onClickEvent(e: MouseEvent) {\n if (this.loadingProp || this.disabled || this.insufficientPermission() || this.loading()) return;\n\n this.onClick.emit(this.createClickEvent(e));\n }\n\n createClickEvent(mouseEvent: MouseEvent | undefined) {\n const e = {\n event: mouseEvent ?? new Event('click'),\n setLoading: (state) => {\n this.loading.set(state !== undefined ? state : !this.loading());\n },\n pipe: () =>\n pipe(\n startWithTap(() => e.setLoading(true)),\n tap({\n next: () => e.setLoading(false),\n error: () => e.setLoading(false),\n complete: () => e.setLoading(false),\n }),\n ),\n } as ButtonClickEvent;\n return e;\n }\n\n private setTheme() {\n this.primaryClass = false;\n this.secondaryClass = false;\n this.tertiaryClass = false;\n this.errorClass = false;\n if (this.theme === 'custom') return;\n this[`${this.theme}Class`] = true;\n }\n\n private checkPermission() {\n if (!this.action && !this.permission) return;\n\n // this.insufficientPermission.set(\n // this.permission ? !this.role.hasPermission(this.permission) : !this.role.hasActionPermission(this.action),\n // );\n }\n\n private setDisabledClass() {\n const insufficientPermission = this.insufficientPermission();\n this.disabledClass = (this.disabled ?? false) || insufficientPermission;\n\n if (insufficientPermission) {\n this.cursorNotAllowed = true;\n } else {\n this.cursorNotAllowed = false;\n }\n }\n}\n","<div\n class=\"ui-button-inherit-parent\"\n [matTooltip]=\"insufficientPermission() ? 'You have insufficient permission.' : ''\"\n >\n <button\n #Button\n mat-flat-button\n class=\"ui-button-inherit-parent ui-button\"\n [disabled]=\"(disabled ?? false) || loading() || insufficientPermission()\"\n [ngStyle]=\"{ padding: padding }\"\n (click)=\"onClickEvent($event)\"\n [routerLink]=\"route\"\n [class.fit-content]=\"fitContent\"\n [class.content-align-start]=\"align === 'start'\"\n [class.content-align-center]=\"align === 'center' || align === undefined\"\n [class.content-align-end]=\"align === 'end'\"\n >\n <div class=\"ui-button-content\" [class.loading]=\"loading()\">\n @if (iconPosition === 'prefix') {\n <ng-container [ngTemplateOutlet]=\"Icon\"></ng-container>\n }\n <div class=\"ui-button-content-portal\">\n <ng-content></ng-content>\n </div>\n @if (iconPosition === 'suffix') {\n <ng-container [ngTemplateOutlet]=\"Icon\"></ng-container>\n }\n @if (loading()) {\n <div class=\"ui-button-loading-spinner\">\n <mat-spinner color=\"primary\" diameter=\"16\" class=\"mx-2\"></mat-spinner>\n </div>\n }\n </div>\n </button>\n</div>\n\n<ng-template #Icon>\n @if (!!icon) {\n <cac-icon\n class=\"transition-colors\"\n [icon]=\"icon\"\n [style]=\"'width:' + iconSize + ';height:' + iconSize\"\n ></cac-icon>\n }\n</ng-template>\n","import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { AsyncPipe } from '@angular/common';\nimport { formControl } from '@al00x/forms';\n\n@Component({\n selector: 'cac-checkbox',\n standalone: true,\n imports: [MatCheckboxModule, ReactiveFormsModule, AsyncPipe],\n templateUrl: './checkbox.component.html',\n styleUrls: ['./checkbox.component.scss'],\n})\nexport class CacCheckboxComponent implements OnChanges {\n @Input() control = formControl(false);\n @Input() label?: string;\n @Input() checked = this.control.value;\n @Input() indeterminate = false;\n @Output() onChange = new EventEmitter<boolean>();\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['checked']) {\n this.control.setValue(this.checked);\n }\n }\n}\n","<mat-checkbox\n class=\"ui-checkbox {{ (control.readonly$ | async) ? 'pointer-events-none' : '' }}\"\n [formControl]=\"control\"\n [checked]=\"checked\"\n [indeterminate]=\"indeterminate\"\n (change)=\"onChange.emit($event.checked)\"\n>\n <ng-content></ng-content>\n {{ label }}\n</mat-checkbox>\n","import { AsyncPipe } from '@angular/common';\nimport { Component, Input, OnDestroy, OnChanges, SimpleChanges, signal } from '@angular/core';\nimport { ReactiveFormsModule, Validators } from '@angular/forms';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { Observable, of, startWith, Subscription } from 'rxjs';\nimport { ItemRecord, ItemRecords$ } from '../../../../core';\nimport { formControl, FormControlExtended } from '@al00x/forms';\n\n// TODO: UPDATE THIS COMPONENT\n// it has signal problem, and it isn't using our ui-checkbox\n// or at least sharing the styles with ui-checkbox.\n// it should be like ui-chips-group in implementation\n\n@Component({\n selector: 'cac-checkbox-group',\n standalone: true,\n imports: [MatCheckboxModule, ReactiveFormsModule, AsyncPipe],\n templateUrl: './checkbox-group.component.html',\n styleUrls: ['./checkbox-group.component.scss'],\n})\nexport class CacCheckboxGroupComponent<T extends string | number> implements OnChanges, OnDestroy {\n rea