@kuzntsv/uikit 1.0.113 → 1.0.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/kuzntsv-uikit-interceptors.mjs +4 -4
- package/fesm2022/kuzntsv-uikit-interceptors.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit-ui.mjs +1663 -1626
- package/fesm2022/kuzntsv-uikit-ui.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit.mjs +3 -3
- package/fesm2022/kuzntsv-uikit.mjs.map +1 -1
- package/package.json +1 -1
- package/types/kuzntsv-uikit-ui.d.ts +47 -45
- package/types/kuzntsv-uikit.d.ts +0 -1
|
@@ -2,8 +2,8 @@ import * as i0 from "@angular/core";
|
|
|
2
2
|
import { ChangeDetectionStrategy, Component, DestroyRef, Injectable, InjectionToken, Service, computed, inject, signal } from "@angular/core";
|
|
3
3
|
import { HttpClient, HttpContextToken, HttpHeaders, HttpParams } from "@angular/common/http";
|
|
4
4
|
import { Subject, catchError, tap, throwError } from "rxjs";
|
|
5
|
-
import { Router } from "@angular/router";
|
|
6
5
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
6
|
+
import { Router } from "@angular/router";
|
|
7
7
|
import { Overlay } from "@angular/cdk/overlay";
|
|
8
8
|
import { ComponentPortal } from "@angular/cdk/portal";
|
|
9
9
|
import { MatProgressSpinner } from "@angular/material/progress-spinner";
|
|
@@ -123,7 +123,6 @@ var AuthService = class AuthService {
|
|
|
123
123
|
roles = this._roles.asReadonly();
|
|
124
124
|
loggedIn = this._loggedIn.asReadonly();
|
|
125
125
|
defaultOptions = { headers: new HttpHeaders().set("content-type", "application/x-www-form-urlencoded") };
|
|
126
|
-
notificationService = inject(UIKIT_NOTIFICATION_TOKEN);
|
|
127
126
|
userService = inject(UserService);
|
|
128
127
|
eventService = inject(AuthEventService);
|
|
129
128
|
destroyRef = inject(DestroyRef);
|
|
@@ -200,7 +199,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
200
199
|
});
|
|
201
200
|
function hasArrayIntersection(arrayA, arrayB) {
|
|
202
201
|
if (!arrayA || !arrayB) return false;
|
|
203
|
-
|
|
202
|
+
const setB = new Set(arrayB);
|
|
203
|
+
return arrayA.some((item) => setB.has(item));
|
|
204
204
|
}
|
|
205
205
|
var RoleService = class RoleService {
|
|
206
206
|
authService = inject(AuthService);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kuzntsv-uikit.mjs","names":[],"sources":["../../src/config/lib-config.ts","../../src/constants/auth.constants.ts","../../src/constants/http-headers.constants.ts","../../src/http/http-context-tokens.ts","../../src/models/interfaces/environment.interface.ts","../../src/models/interfaces/notification-service.interface.ts","../../src/services/auth-event.service.ts","../../src/services/auth-user.service.ts","../../src/services/auth.service.ts","../../src/utils/array.utils.ts","../../src/services/auth-role.service.ts","../../src/shared/progress-spinner/progress-spinner.component.ts","../../src/services/loader.service.ts","../../src/services/notification.service.ts","../../src/utils/date.utils.ts","../../src/utils/signal-selection-model.ts","../../src/utils/zod-error-formatter.ts"],"sourcesContent":["import { InjectionToken } from \"@angular/core\";\r\n\r\nexport interface UikitConfig {\r\n apiUrls: {\r\n default: string;\r\n auth: string;\r\n [key: string]: string;\r\n };\r\n auth: {\r\n disabled?: boolean; // true = авторизация полностью отключена\r\n userStorageKey: string; // Например, 'app_user_jwt'\r\n tokenRoute: string; // Например, '/jwt'\r\n // refreshTokenStorageKey: string; // Ключ для Refresh токена (например, 'refresh_token')\r\n // refreshEndpoint: string; // Относительный путь (например, '/auth/refresh')\r\n loginRoute: string; // Например, '/auth/login'\r\n publicUrls: string[]; // Относительные пути, которые не требуют токена (например, '/login', '/assets')\r\n };\r\n}\r\n\r\nexport const UIKIT_CONFIG = new InjectionToken<UikitConfig>('UIKIT_CONFIG');\r\n","/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const HIDE_ROLE_MARKER = 'HIDE';\r\n\r\n/**\r\n * Системная роль Администратора, которая игнорирует любые ограничения прав.\r\n */\r\nexport const ADMIN_ROLE = 'ADMIN';\r\n","/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const X_SKIP_NOTIFICATION = 'X-Skip-Notification';\r\n\r\n/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const X_SKIP_LOADER = 'X-Skip-Loader';","import { HttpContextToken } from '@angular/common/http';\r\nimport { inject, InjectionToken } from '@angular/core';\r\n\r\n// Токен, в который мы будем передавать строку-идентификатор нужного API\r\n// По умолчанию пустая строка означает \"использовать дефолтное (основное) API\"\r\nexport const API_NAME = new HttpContextToken<string>(() => '');\r\n\r\n// Иногда внутри сервиса нужно сделать один специфический запрос (например, стянуть публичный JSON-файл конфигурации), не добавляя его глобально в publicUrls.\r\n// Используя подход с HttpContext, мы можем добавить в библиотеку маркер пропуска авторизации:\r\n// А в коде приложения вызывать так:\r\n// this.http.get('/assets/config.json', { \r\n// context: new HttpContext().set(BYPASS_AUTH, true) \r\n// });\r\nexport const BYPASS_AUTH = new HttpContextToken<boolean>(() => false);\r\n\r\n// DI-токен, который переопределяем в appConfig\r\nexport const DEFAULT_TIMEOUT = new InjectionToken<number>('DEFAULT_TIMEOUT', {\r\n providedIn: 'root',\r\n factory: () => 10000 // Запасной дефолт (10 сек), если в appConfig ничего не указали\r\n});\r\n\r\n// Если для каких-то тяжелых запросов (например, загрузка больших файлов или генерация отчетов) \r\n// вам нужно увеличить таймаут, вы можете передавать его значение прямо в запросе через HttpContext.\r\nexport const REQUEST_TIMEOUT = new HttpContextToken<number>(() => inject(DEFAULT_TIMEOUT));\r\n\r\n// Передать в токене значение true, для запросов (например, автосохранение черновика \r\n// или периодический опрос сервера), при которых показывать блокирующий спиннер пользователю не нужно.\r\nexport const SKIP_LOADER = new HttpContextToken<boolean>(() => false);\r\n\r\n// Передайте true для запросов, ошибки которых (например, 404 при проверке уникальности)\r\n// не должны приводить к показу глобального нотификатора.\r\nexport const SKIP_NOTIFICATION = new HttpContextToken<boolean>(() => false);\r\n","import { InjectionToken } from \"@angular/core\";\r\n\r\nexport interface AppEnvironment {\r\n appVersion: string;\r\n production: boolean;\r\n \r\n // Объявляем словарь адресов. \r\n // Ключи default и auth — обязательные, остальные — любые строки.\r\n apiUrls: {\r\n default: string;\r\n auth: string;\r\n [key: string]: string; // <-- Это позволяет писать billing, analytics, crm и т.д.\r\n };\r\n};\r\n\r\nexport const APP_ENVIRONMENT = new InjectionToken<AppEnvironment>('ApplicationEnvironment');","import { InjectionToken } from \"@angular/core\";\r\n\r\n/**\r\n * Сервис уведомлений.\r\n */\r\nexport interface INotificationService {\r\n /**\r\n * Показ всплывашки с сообщением.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n openSnackBar(message: string, action: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о успехе.\r\n *\r\n * @param message сообщение\r\n * @param duration задержка в миллисекундах\r\n */\r\n success(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о информации.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n info(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о сообщения помощи.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n helper(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о предупреждении.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n warning(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление об ошибке.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n error(message: string, duration?: number): void;\r\n}\r\n\r\nexport const UIKIT_NOTIFICATION_TOKEN = new InjectionToken<INotificationService>('UikitNotificationToken');\r\n","import { Injectable } from '@angular/core';\r\nimport { Subject } from 'rxjs';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class AuthEventService {\r\n // Ошибки авторизации\r\n private readonly authErrorSource = new Subject<void>();\r\n\r\n // Стрим для прослушивания ошибок авторизации (экспортируется наружу)\r\n readonly authError$ = this.authErrorSource.asObservable();\r\n\r\n /** Вызывается в интерцепторе при 401 ошибке */\r\n notify401(): void {\r\n this.authErrorSource.next();\r\n }\r\n}","import { inject, Service } from '@angular/core';\r\nimport { UIKIT_CONFIG } from '../config';\r\n/**\r\n * Сущность пользователя из API\r\n */\r\nexport class User {\r\n id: number = 0;\r\n username: string = '';\r\n firstName: string = '';\r\n lastName: string = '';\r\n prof: string = '';\r\n depart: number = 0;\r\n area: string = '';\r\n tabnum: string = '';\r\n mail: string = '';\r\n phone: string = '';\r\n roles: string = '';\r\n rolesArray: string[] = [];\r\n roleList: string[] = [];\r\n idHuman: string = '';\r\n token: string = '';\r\n // Время действия токена\r\n expires_in: number = 0;\r\n}\r\n@Service()\r\n/**\r\n * Сервис для работы с объектом в local Storage\r\n */\r\nexport class UserService {\r\n private user: User;\r\n\r\n private config = inject(UIKIT_CONFIG);\r\n\r\n constructor() {\r\n const saved = localStorage.getItem(this.config.auth.userStorageKey);\r\n this.user = saved ? JSON.parse(saved) : new User();\r\n }\r\n\r\n get dept(): number {\r\n return this.user?.depart;\r\n }\r\n\r\n /** Получить данные о пользователе */\r\n get userData(): User {\r\n return this.user;\r\n }\r\n\r\n /** Изменить данные пользователя в localStorage */\r\n set userData(user: User) {\r\n this.user = user;\r\n localStorage.setItem(this.config.auth.userStorageKey, JSON.stringify(user));\r\n }\r\n\r\n /** Удаляет данные о пользователе из localStorage. */\r\n removeUser() {\r\n localStorage.removeItem(this.config.auth.userStorageKey);\r\n }\r\n}\r\n","import { DestroyRef, Injectable, inject, signal } from '@angular/core';\r\nimport { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';\r\nimport { Router } from '@angular/router';\r\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\r\n\r\nimport { AuthEventService } from './auth-event.service';\r\nimport { User, UserService } from './auth-user.service';\r\nimport { UIKIT_CONFIG } from '../config';\r\nimport { UIKIT_NOTIFICATION_TOKEN } from '../models';\r\nimport { catchError, Observable, tap, throwError } from 'rxjs';\r\n\r\n// interface RefreshResponse {\r\n// accessToken: string;\r\n// refreshToken: string;\r\n// }\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class AuthService {\r\n // Хранит роли пользователя\r\n private readonly _roles = signal<string[]>([]);\r\n // Состояние авторизации\r\n private readonly _loggedIn = signal<boolean>(false);\r\n\r\n readonly roles = this._roles.asReadonly();\r\n readonly loggedIn = this._loggedIn.asReadonly();\r\n\r\n // Заголовки для URL-encoded формы\r\n private readonly defaultOptions = {\r\n headers: new HttpHeaders().set(\r\n 'content-type',\r\n 'application/x-www-form-urlencoded',\r\n ),\r\n };\r\n\r\n private readonly notificationService = inject(UIKIT_NOTIFICATION_TOKEN);\r\n private readonly userService = inject(UserService);\r\n private readonly eventService = inject(AuthEventService);\r\n private readonly destroyRef = inject(DestroyRef);\r\n private readonly http = inject(HttpClient);\r\n private readonly config = inject(UIKIT_CONFIG);\r\n // Внедряем роутер опционально, чтобы библиотека не падала, если роутера нет в приложении\r\n private readonly router = inject(Router, { optional: true });\r\n\r\n constructor() {\r\n const initialUser = this.userService.userData as User | null;\r\n\r\n if (initialUser?.token) {\r\n this._loggedIn.set(true);\r\n this._roles.set(initialUser.roleList ?? []);\r\n }\r\n\r\n // Слушаем шину событий: если интерцептор поймал 401, сбрасываем сессию\r\n this.eventService.authError$\r\n .pipe(takeUntilDestroyed(this.destroyRef))\r\n .subscribe(() => this.logout());\r\n }\r\n\r\n /**\r\n * Запрос авторизации\r\n * Возвращает Observable, позволяя компоненту реагировать на статус запроса.\r\n */\r\n login(username: string, password: string, idCas: number): Observable<User> {\r\n let params = new HttpParams()\r\n .set('username', username)\r\n .set('password', password); // HttpParams закодирует спецсимволы сам!\r\n\r\n if (idCas) {\r\n params = params.set('idcas', idCas.toString());\r\n }\r\n\r\n const url = `${this.config.apiUrls.auth}${this.config.auth.tokenRoute}`;\r\n\r\n return this.http\r\n .post<User>(url, params.toString(), this.defaultOptions)\r\n .pipe(\r\n tap((data) => this.successLoginAction(data)),\r\n catchError((error) => {\r\n console.log('Login error:', error);\r\n return throwError(() => error);\r\n }),\r\n );\r\n }\r\n\r\n /** Aвторизация по пропуску */\r\n loginByPermit(permit: string, idCas: number): Observable<User> {\r\n let params = new HttpParams().set('permit', permit);\r\n if (idCas) {\r\n params = params.set('idcas', idCas.toString());\r\n }\r\n\r\n const url = `${this.config.apiUrls.auth}${this.config.auth.tokenRoute}`;\r\n\r\n return this.http.post<User>(url, params.toString(), this.defaultOptions).pipe(\r\n tap((data) => this.successLoginAction(data)),\r\n catchError((error) => {\r\n console.log('Permit login error:', error); \r\n return throwError(() => error);\r\n }),\r\n );\r\n }\r\n\r\n logout(): void {\r\n // Убирает(удаляет) пользователя из локального хранилища когда он разлогинивается\r\n this.userService.removeUser();\r\n this._loggedIn.set(false);\r\n this._roles.set([]);\r\n\r\n // localStorage.removeItem(this.config.auth.userStorageKey);\r\n // localStorage.removeItem(this.config.auth.refreshTokenStorageKey);\r\n // ЛОГИКА СОХРАНЕНИЯ URL ПРИ РАЗЛОГИНЕ (Ваш улучшенный кусок кода)\r\n if (this.router) {\r\n const currentUrl = this.router.url;\r\n\r\n // Проверяем, не находимся ли мы уже на странице логина, чтобы избежать зацикливания\r\n if (currentUrl.includes(this.config.auth.loginRoute)) {\r\n return;\r\n }\r\n\r\n // Безопасное формирование query-параметров через Router\r\n this.router.navigate([this.config.auth.loginRoute], {\r\n queryParams: { returnUrl: encodeURIComponent(currentUrl) },\r\n });\r\n }\r\n }\r\n\r\n /** При успешной авторизации */\r\n private successLoginAction(user: User) {\r\n this.userService.userData = user;\r\n this._loggedIn.set(true);\r\n this._roles.set(user.roleList ?? []);\r\n }\r\n\r\n // // Метод отправляет старый refresh-токен на сервер\r\n // refreshToken(): Observable<RefreshResponse> {\r\n // const refreshToken = localStorage.getItem(this.config.auth.refreshTokenStorageKey);\r\n\r\n // if (!refreshToken) {\r\n // this.logout();\r\n // return throwError(() => new Error('No refresh token available'));\r\n // }\r\n\r\n // // Собираем абсолютный урл (берем default baseUrl + эндпоинт рефреша)\r\n // const baseUrl = this.config.apiUrls.default.replace(/\\/\\$/, '');\r\n // const url = `${baseUrl}${this.config.auth.refreshEndpoint}`;\r\n\r\n // // Передаем токен (в зависимости от вашего бэкенда: в body или в куках/заголовках)\r\n // return this.http.post<RefreshResponse>(url, { refreshToken }).pipe(\r\n // tap(response => {\r\n // // Сохраняем новые токены в локалсторадж\r\n // localStorage.setItem(this.config.auth.tokenStorageKey, response.accessToken);\r\n // localStorage.setItem(this.config.auth.refreshTokenStorageKey, response.refreshToken);\r\n // }),\r\n // catchError(err => {\r\n // // Если даже рефреш-токен невалиден (истек) — принудительный разлогин\r\n // this.logout();\r\n // return throwError(() => err);\r\n // })\r\n // );\r\n // }\r\n}\r\n","/**\r\n * Проверяет, пересекаются ли два массива (есть ли хотя бы один общий элемент).\r\n */\r\nexport function hasArrayIntersection<T>(arrayA: T[], arrayB: T[]): boolean {\r\n if (!arrayA || !arrayB) return false;\r\n return arrayA.some(item => arrayB.includes(item));\r\n}\r\n","import { inject, Injectable } from '@angular/core';\r\n\r\nimport { AuthService } from './auth.service';\r\nimport { hasArrayIntersection } from '../utils/array.utils';\r\nimport { ADMIN_ROLE, HIDE_ROLE_MARKER } from '../constants';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\n/** Вычисление разрешений по ролям */ \r\nexport class RoleService {\r\n private readonly authService = inject(AuthService);\r\n\r\n /**\r\n * Проверяет, заблокирован ли доступ к элементу на основе списка ограничений.\r\n * Возвращает true, если у пользователя есть запрещенная роль или передан маркер 'HIDE'.\r\n * Администратор (ADMIN) всегда игнорирует любые запреты и маркер 'HIDE'.\r\n */\r\n public hasForbiddenRole(forbiddenRoles: string[]): boolean {\r\n if (!forbiddenRoles || forbiddenRoles.length === 0) return false;\r\n\r\n const userRoles = this.authService.roles() ?? [];\r\n\r\n // Если пользователь ADMIN — он игнорирует списки запретов\r\n if (userRoles.includes(ADMIN_ROLE)) return false;\r\n\r\n // Если в условиях зашит маркер 'HIDE' — скрываем элемент от обычного пользователя\r\n if (forbiddenRoles.includes(HIDE_ROLE_MARKER)) return true;\r\n\r\n // В остальных случаях проверяем стандартное пересечение ролей\r\n return hasArrayIntersection(userRoles, forbiddenRoles);\r\n }\r\n\r\n /**\r\n * Проверяет, есть ли у пользователя хотя бы одна из разрешенных ролей.\r\n * ADMIN всегда возвращает true.\r\n */\r\npublic hasAnyRole(allowedRoles: string[]): boolean {\r\n const userRoles = this.authService.roles() ?? [];\r\n if (userRoles.includes(ADMIN_ROLE)) return true;\r\n \r\n return hasArrayIntersection(userRoles, allowedRoles);\r\n}\r\n}\r\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\r\nimport { MatProgressSpinner } from '@angular/material/progress-spinner';\r\n\r\n@Component({\r\n selector: 'ngx-uik-loader',\r\n template: `\r\n <div class=\"loading-indicator\">\r\n <mat-progress-spinner mode=\"indeterminate\" aria-label=\"Загрузка...\"/>\r\n </div>\r\n `,\r\n styles: [\r\n `\r\n /* Разместить по центру */\r\n .loading-indicator {\r\n position: fixed;\r\n z-index: 999;\r\n height: 2em;\r\n width: 2em;\r\n overflow: initial;\r\n margin: auto;\r\n inset: 0; /* 2. Современное CSS-свойство взамен top/left/bottom/right */\r\n }\r\n\r\n /* Заблокировать оверлей */\r\n .loading-indicator::before {\r\n content: '';\r\n display: block;\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background-color: rgba(0, 0, 0, 0.3);\r\n }\r\n `,\r\n ],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n imports: [MatProgressSpinner],\r\n})\r\nexport class ProgressSpinnerComponent {}\r\n","import { Overlay, OverlayRef } from '@angular/cdk/overlay';\r\nimport { ComponentPortal } from '@angular/cdk/portal';\r\nimport { inject, OnDestroy, Service } from '@angular/core';\r\n\r\nimport { ProgressSpinnerComponent } from '../shared/progress-spinner';\r\n\r\n@Service()\r\nexport class LoaderService implements OnDestroy {\r\n private overlayRef: OverlayRef | null = null;\r\n private readonly overlay = inject(Overlay);\r\n\r\n // Счетчик для контроля параллельных HTTP-запросов\r\n private activeRequests = 0;\r\n\r\n public show(): void {\r\n this.activeRequests++;\r\n\r\n if (!this.overlayRef) {\r\n this.overlayRef = this.overlay.create({\r\n hasBackdrop: false, // Настройки бэкдропа теперь берет на себя стилистика лоадера\r\n positionStrategy: this.overlay\r\n .position()\r\n .global()\r\n .centerHorizontally()\r\n .centerVertically(),\r\n });\r\n }\r\n if (!this.overlayRef.hasAttached()) {\r\n const spinnerPortal = new ComponentPortal(ProgressSpinnerComponent);\r\n this.overlayRef.attach(spinnerPortal);\r\n }\r\n }\r\n\r\n public hide(): void {\r\n if (this.activeRequests > 0) {\r\n this.activeRequests--;\r\n }\r\n // Скрываем спиннер только тогда, когда завершился ПОСЛЕДНИЙ активный запрос\r\n if (this.activeRequests === 0 && this.overlayRef && this.overlayRef.hasAttached()) {\r\n this.overlayRef.detach();\r\n }\r\n }\r\n\r\n /**\r\n * Принудительный сброс (например, при смене роута или критической ошибке)\r\n */\r\n public reset(): void {\r\n this.activeRequests = 0;\r\n if (this.overlayRef && this.overlayRef.hasAttached()) {\r\n this.overlayRef.detach();\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n if (this.overlayRef) {\r\n this.overlayRef.dispose(); // Полностью удаляет элемент оверлея из DOM\r\n }\r\n }\r\n}\r\n","import { inject, Service } from '@angular/core';\r\nimport { MatSnackBar } from '@angular/material/snack-bar';\r\n\r\nimport { INotificationService } from '../models';\r\n\r\n// Сервис для вывода сообщений(ошибки, успеха)\r\n@Service()\r\nexport class NotificationService implements INotificationService {\r\n private readonly duration = 5000;\r\n private readonly _snackBar = inject(MatSnackBar);\r\n\r\n openSnackBar(message: string, action: string, duration: number = this.duration) {\r\n this._snackBar.open(message, action, {\r\n duration,\r\n panelClass: ['red-snackbar'],\r\n });\r\n }\r\n\r\n success(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n info(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n helper(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n warning(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n error(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n}\r\n","/**\r\n * Преобразует дату в локальную строковую дату (Europe/Samara).\r\n * Чистая функция, независимая от контекста Angular.\r\n */\r\nexport function dateToString(\r\n date: string | number | Date | null | undefined,\r\n): string {\r\n if (!date) return '';\r\n\r\n return new Date(date).toLocaleString('ru-RU', {\r\n timeZone: 'Europe/Samara',\r\n });\r\n}\r\n","import { computed, signal, type Signal } from '@angular/core';\r\n\r\nexport class SignalSelectionModel<T> {\r\n private readonly _selectedSet = signal<Set<T>>(new Set());\r\n\r\n // Свойства-сигналы для эффективной работы в Angular шаблонах\r\n readonly selected: Signal<T[]> = computed(() => Array.from(this._selectedSet()));\r\n readonly isEmpty = computed(() => this._selectedSet().size === 0);\r\n readonly hasValue = computed(() => this._selectedSet().size > 0);\r\n\r\n constructor(\r\n public readonly multiple = false, // Поле multiple как в CDK\r\n initialValues: T[] = [],\r\n private _compareWith?: (o1: T, o2: T) => boolean // Поддержка сравнения по ID (как в MatSelect)\r\n ) {\r\n if (initialValues.length > 0) {\r\n this.select(...initialValues);\r\n }\r\n }\r\n\r\n // Проверка наличия элемента с учетом кастомного сравнения\r\n isSelected(value: T): boolean {\r\n const set = this._selectedSet();\r\n if (!this._compareWith) {\r\n return set.has(value);\r\n }\r\n \r\n for (const item of set) {\r\n if (this._compareWith(item, value)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n select(...values: T[]): void {\r\n this._selectedSet.update(set => {\r\n const newSet = new Set(set);\r\n \r\n if (!this.multiple) {\r\n newSet.clear();\r\n if (values.length > 0) {\r\n this._addWithCompare(newSet, values[values.length - 1]);\r\n }\r\n } else {\r\n values.forEach(v => this._addWithCompare(newSet, v));\r\n }\r\n return newSet;\r\n });\r\n }\r\n\r\n deselect(...values: T[]): void {\r\n if (values.length === 0) return; // Ранний выход, если массив пустой\r\n\r\n this._selectedSet.update(set => {\r\n const newSet = new Set(set);\r\n if (this._compareWith) {\r\n const toDelete = new Set<T>(); // Сюда складываем кандидатов на удаление\r\n \r\n for (const value of values) {\r\n for (const current of newSet) {\r\n if (this._compareWith(current, value)) {\r\n toDelete.add(current);\r\n }\r\n }\r\n }\r\n // Удаляем только после того, как все циклы по newSet завершены\r\n toDelete.forEach(item => newSet.delete(item));\r\n } else {\r\n values.forEach(v => newSet.delete(v));\r\n }\r\n return newSet;\r\n });\r\n }\r\n\r\n toggle(value: T): void {\r\n this.isSelected(value) ? this.deselect(value) : this.select(value);\r\n }\r\n\r\n clear(): void {\r\n if (this._selectedSet().size === 0) return; \r\n this._selectedSet.set(new Set());\r\n }\r\n\r\n // Вспомогательный метод для корректного добавления объектов\r\n private _addWithCompare(set: Set<T>, value: T): void {\r\n if (this._compareWith) {\r\n let duplicate: T | undefined;\r\n for (const current of set) {\r\n if (this._compareWith(current, value)) {\r\n duplicate = current;\r\n break;\r\n }\r\n }\r\n if (duplicate !== undefined) {\r\n set.delete(duplicate);\r\n }\r\n }\r\n set.add(value);\r\n }\r\n}\r\n","import { ZodError } from 'zod';\r\n\r\nexport function formatZodError(error: ZodError, contextName: string): string {\r\n const issuesText = error.issues\r\n .map((issue) => {\r\n // Форматируем путь: [0, \"user\"] -> \"[0] -> user\"\r\n const path = issue.path\r\n .map((p) => (typeof p === 'number' ? `[${p}]` : p))\r\n .join(' -> ');\r\n\r\n const location = path ? `в \"${path}\"` : 'в корне';\r\n const expected = (issue as any).expected\r\n ? `ожидалось \"${(issue as any).expected}\"`\r\n : '';\r\n const received = (issue as any).received\r\n ? `, получено \"${(issue as any).received}\"`\r\n : '';\r\n\r\n return ` • Ошибка ${location}: ${expected}${received}. Сообщение: ${issue.message}`;\r\n })\r\n .join('\\n');\r\n\r\n return `\\n[Zod Валидация] Ошибка парсинга для ресурса: ${contextName}\\n${issuesText}\\n`;\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"kuzntsv-uikit.mjs","names":[],"sources":["../../src/config/lib-config.ts","../../src/constants/auth.constants.ts","../../src/constants/http-headers.constants.ts","../../src/http/http-context-tokens.ts","../../src/models/interfaces/environment.interface.ts","../../src/models/interfaces/notification-service.interface.ts","../../src/services/auth-event.service.ts","../../src/services/auth-user.service.ts","../../src/services/auth.service.ts","../../src/utils/array.utils.ts","../../src/services/auth-role.service.ts","../../src/shared/progress-spinner/progress-spinner.component.ts","../../src/services/loader.service.ts","../../src/services/notification.service.ts","../../src/utils/date.utils.ts","../../src/utils/signal-selection-model.ts","../../src/utils/zod-error-formatter.ts"],"sourcesContent":["import { InjectionToken } from \"@angular/core\";\r\n\r\nexport interface UikitConfig {\r\n apiUrls: {\r\n default: string;\r\n auth: string;\r\n [key: string]: string;\r\n };\r\n auth: {\r\n disabled?: boolean; // true = авторизация полностью отключена\r\n userStorageKey: string; // Например, 'app_user_jwt'\r\n tokenRoute: string; // Например, '/jwt'\r\n // refreshTokenStorageKey: string; // Ключ для Refresh токена (например, 'refresh_token')\r\n // refreshEndpoint: string; // Относительный путь (например, '/auth/refresh')\r\n loginRoute: string; // Например, '/auth/login'\r\n publicUrls: string[]; // Относительные пути, которые не требуют токена (например, '/login', '/assets')\r\n };\r\n}\r\n\r\nexport const UIKIT_CONFIG = new InjectionToken<UikitConfig>('UIKIT_CONFIG');\r\n","/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const HIDE_ROLE_MARKER = 'HIDE';\r\n\r\n/**\r\n * Системная роль Администратора, которая игнорирует любые ограничения прав.\r\n */\r\nexport const ADMIN_ROLE = 'ADMIN';\r\n","/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const X_SKIP_NOTIFICATION = 'X-Skip-Notification';\r\n\r\n/**\r\n * Специальный маркер-роль в массиве ограничений, \r\n * который полностью скрывает элемент от обычных пользователей.\r\n */\r\nexport const X_SKIP_LOADER = 'X-Skip-Loader';","import { HttpContextToken } from '@angular/common/http';\r\nimport { inject, InjectionToken } from '@angular/core';\r\n\r\n// Токен, в который мы будем передавать строку-идентификатор нужного API\r\n// По умолчанию пустая строка означает \"использовать дефолтное (основное) API\"\r\nexport const API_NAME = new HttpContextToken<string>(() => '');\r\n\r\n// Иногда внутри сервиса нужно сделать один специфический запрос (например, стянуть публичный JSON-файл конфигурации), не добавляя его глобально в publicUrls.\r\n// Используя подход с HttpContext, мы можем добавить в библиотеку маркер пропуска авторизации:\r\n// А в коде приложения вызывать так:\r\n// this.http.get('/assets/config.json', { \r\n// context: new HttpContext().set(BYPASS_AUTH, true) \r\n// });\r\nexport const BYPASS_AUTH = new HttpContextToken<boolean>(() => false);\r\n\r\n// DI-токен, который переопределяем в appConfig\r\nexport const DEFAULT_TIMEOUT = new InjectionToken<number>('DEFAULT_TIMEOUT', {\r\n providedIn: 'root',\r\n factory: () => 10000 // Запасной дефолт (10 сек), если в appConfig ничего не указали\r\n});\r\n\r\n// Если для каких-то тяжелых запросов (например, загрузка больших файлов или генерация отчетов) \r\n// вам нужно увеличить таймаут, вы можете передавать его значение прямо в запросе через HttpContext.\r\nexport const REQUEST_TIMEOUT = new HttpContextToken<number>(() => inject(DEFAULT_TIMEOUT));\r\n\r\n// Передать в токене значение true, для запросов (например, автосохранение черновика \r\n// или периодический опрос сервера), при которых показывать блокирующий спиннер пользователю не нужно.\r\nexport const SKIP_LOADER = new HttpContextToken<boolean>(() => false);\r\n\r\n// Передайте true для запросов, ошибки которых (например, 404 при проверке уникальности)\r\n// не должны приводить к показу глобального нотификатора.\r\nexport const SKIP_NOTIFICATION = new HttpContextToken<boolean>(() => false);\r\n","import { InjectionToken } from \"@angular/core\";\r\n\r\nexport interface AppEnvironment {\r\n appVersion: string;\r\n production: boolean;\r\n \r\n // Объявляем словарь адресов. \r\n // Ключи default и auth — обязательные, остальные — любые строки.\r\n apiUrls: {\r\n default: string;\r\n auth: string;\r\n [key: string]: string; // <-- Это позволяет писать billing, analytics, crm и т.д.\r\n };\r\n};\r\n\r\nexport const APP_ENVIRONMENT = new InjectionToken<AppEnvironment>('ApplicationEnvironment');","import { InjectionToken } from \"@angular/core\";\r\n\r\n/**\r\n * Сервис уведомлений.\r\n */\r\nexport interface INotificationService {\r\n /**\r\n * Показ всплывашки с сообщением.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n openSnackBar(message: string, action: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о успехе.\r\n *\r\n * @param message сообщение\r\n * @param duration задержка в миллисекундах\r\n */\r\n success(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о информации.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n info(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о сообщения помощи.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n helper(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление о предупреждении.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n warning(message: string, duration?: number): void;\r\n\r\n /**\r\n * Уведомление об ошибке.\r\n *\r\n * @param message сообщение.\r\n * @param duration задержка в миллисекундах.\r\n */\r\n error(message: string, duration?: number): void;\r\n}\r\n\r\nexport const UIKIT_NOTIFICATION_TOKEN = new InjectionToken<INotificationService>('UikitNotificationToken');\r\n","import { Injectable } from '@angular/core';\r\nimport { Subject } from 'rxjs';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class AuthEventService {\r\n // Ошибки авторизации\r\n private readonly authErrorSource = new Subject<void>();\r\n\r\n // Стрим для прослушивания ошибок авторизации (экспортируется наружу)\r\n readonly authError$ = this.authErrorSource.asObservable();\r\n\r\n /** Вызывается в интерцепторе при 401 ошибке */\r\n notify401(): void {\r\n this.authErrorSource.next();\r\n }\r\n}","import { inject, Service } from '@angular/core';\r\nimport { UIKIT_CONFIG } from '../config';\r\n/**\r\n * Сущность пользователя из API\r\n */\r\nexport class User {\r\n id: number = 0;\r\n username: string = '';\r\n firstName: string = '';\r\n lastName: string = '';\r\n prof: string = '';\r\n depart: number = 0;\r\n area: string = '';\r\n tabnum: string = '';\r\n mail: string = '';\r\n phone: string = '';\r\n roles: string = '';\r\n rolesArray: string[] = [];\r\n roleList: string[] = [];\r\n idHuman: string = '';\r\n token: string = '';\r\n // Время действия токена\r\n expires_in: number = 0;\r\n}\r\n@Service()\r\n/**\r\n * Сервис для работы с объектом в local Storage\r\n */\r\nexport class UserService {\r\n private user: User;\r\n\r\n private config = inject(UIKIT_CONFIG);\r\n\r\n constructor() {\r\n const saved = localStorage.getItem(this.config.auth.userStorageKey);\r\n this.user = saved ? JSON.parse(saved) : new User();\r\n }\r\n\r\n get dept(): number {\r\n return this.user?.depart;\r\n }\r\n\r\n /** Получить данные о пользователе */\r\n get userData(): User {\r\n return this.user;\r\n }\r\n\r\n /** Изменить данные пользователя в localStorage */\r\n set userData(user: User) {\r\n this.user = user;\r\n localStorage.setItem(this.config.auth.userStorageKey, JSON.stringify(user));\r\n }\r\n\r\n /** Удаляет данные о пользователе из localStorage. */\r\n removeUser() {\r\n localStorage.removeItem(this.config.auth.userStorageKey);\r\n }\r\n}\r\n","import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';\r\nimport { DestroyRef, inject, Injectable, signal } from '@angular/core';\r\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\r\nimport { Router } from '@angular/router';\r\n\r\nimport { catchError, Observable, tap, throwError } from 'rxjs';\r\nimport { UIKIT_CONFIG } from '../config';\r\nimport { AuthEventService } from './auth-event.service';\r\nimport { User, UserService } from './auth-user.service';\r\n\r\n// interface RefreshResponse {\r\n// accessToken: string;\r\n// refreshToken: string;\r\n// }\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class AuthService {\r\n // Хранит роли пользователя\r\n private readonly _roles = signal<string[]>([]);\r\n // Состояние авторизации\r\n private readonly _loggedIn = signal<boolean>(false);\r\n\r\n readonly roles = this._roles.asReadonly();\r\n readonly loggedIn = this._loggedIn.asReadonly();\r\n\r\n // Заголовки для URL-encoded формы\r\n private readonly defaultOptions = {\r\n headers: new HttpHeaders().set(\r\n 'content-type',\r\n 'application/x-www-form-urlencoded',\r\n ),\r\n };\r\n\r\n // private readonly notificationService = inject(UIKIT_NOTIFICATION_TOKEN);\r\n private readonly userService = inject(UserService);\r\n private readonly eventService = inject(AuthEventService);\r\n private readonly destroyRef = inject(DestroyRef);\r\n private readonly http = inject(HttpClient);\r\n private readonly config = inject(UIKIT_CONFIG);\r\n // Внедряем роутер опционально, чтобы библиотека не падала, если роутера нет в приложении\r\n private readonly router = inject(Router, { optional: true });\r\n\r\n constructor() {\r\n const initialUser = this.userService.userData as User | null;\r\n\r\n if (initialUser?.token) {\r\n this._loggedIn.set(true);\r\n this._roles.set(initialUser.roleList ?? []);\r\n }\r\n\r\n // Слушаем шину событий: если интерцептор поймал 401, сбрасываем сессию\r\n this.eventService.authError$\r\n .pipe(takeUntilDestroyed(this.destroyRef))\r\n .subscribe(() => this.logout());\r\n }\r\n\r\n /**\r\n * Запрос авторизации\r\n * Возвращает Observable, позволяя компоненту реагировать на статус запроса.\r\n */\r\n login(username: string, password: string, idCas: number): Observable<User> {\r\n let params = new HttpParams()\r\n .set('username', username)\r\n .set('password', password); // HttpParams закодирует спецсимволы сам!\r\n\r\n if (idCas) {\r\n params = params.set('idcas', idCas.toString());\r\n }\r\n\r\n const url = `${this.config.apiUrls.auth}${this.config.auth.tokenRoute}`;\r\n\r\n return this.http\r\n .post<User>(url, params.toString(), this.defaultOptions)\r\n .pipe(\r\n tap((data) => this.successLoginAction(data)),\r\n catchError((error) => {\r\n console.log('Login error:', error);\r\n return throwError(() => error);\r\n }),\r\n );\r\n }\r\n\r\n /** Aвторизация по пропуску */\r\n loginByPermit(permit: string, idCas: number): Observable<User> {\r\n let params = new HttpParams().set('permit', permit);\r\n if (idCas) {\r\n params = params.set('idcas', idCas.toString());\r\n }\r\n\r\n const url = `${this.config.apiUrls.auth}${this.config.auth.tokenRoute}`;\r\n\r\n return this.http.post<User>(url, params.toString(), this.defaultOptions).pipe(\r\n tap((data) => this.successLoginAction(data)),\r\n catchError((error) => {\r\n console.log('Permit login error:', error); \r\n return throwError(() => error);\r\n }),\r\n );\r\n }\r\n\r\n logout(): void {\r\n // Убирает(удаляет) пользователя из локального хранилища когда он разлогинивается\r\n this.userService.removeUser();\r\n this._loggedIn.set(false);\r\n this._roles.set([]);\r\n\r\n // localStorage.removeItem(this.config.auth.userStorageKey);\r\n // localStorage.removeItem(this.config.auth.refreshTokenStorageKey);\r\n // ЛОГИКА СОХРАНЕНИЯ URL ПРИ РАЗЛОГИНЕ (Ваш улучшенный кусок кода)\r\n if (this.router) {\r\n const currentUrl = this.router.url;\r\n\r\n // Проверяем, не находимся ли мы уже на странице логина, чтобы избежать зацикливания\r\n if (currentUrl.includes(this.config.auth.loginRoute)) {\r\n return;\r\n }\r\n\r\n // Безопасное формирование query-параметров через Router\r\n this.router.navigate([this.config.auth.loginRoute], {\r\n queryParams: { returnUrl: encodeURIComponent(currentUrl) },\r\n });\r\n }\r\n }\r\n\r\n /** При успешной авторизации */\r\n private successLoginAction(user: User) {\r\n this.userService.userData = user;\r\n this._loggedIn.set(true);\r\n this._roles.set(user.roleList ?? []);\r\n }\r\n\r\n // // Метод отправляет старый refresh-токен на сервер\r\n // refreshToken(): Observable<RefreshResponse> {\r\n // const refreshToken = localStorage.getItem(this.config.auth.refreshTokenStorageKey);\r\n\r\n // if (!refreshToken) {\r\n // this.logout();\r\n // return throwError(() => new Error('No refresh token available'));\r\n // }\r\n\r\n // // Собираем абсолютный урл (берем default baseUrl + эндпоинт рефреша)\r\n // const baseUrl = this.config.apiUrls.default.replace(/\\/\\$/, '');\r\n // const url = `${baseUrl}${this.config.auth.refreshEndpoint}`;\r\n\r\n // // Передаем токен (в зависимости от вашего бэкенда: в body или в куках/заголовках)\r\n // return this.http.post<RefreshResponse>(url, { refreshToken }).pipe(\r\n // tap(response => {\r\n // // Сохраняем новые токены в локалсторадж\r\n // localStorage.setItem(this.config.auth.tokenStorageKey, response.accessToken);\r\n // localStorage.setItem(this.config.auth.refreshTokenStorageKey, response.refreshToken);\r\n // }),\r\n // catchError(err => {\r\n // // Если даже рефреш-токен невалиден (истек) — принудительный разлогин\r\n // this.logout();\r\n // return throwError(() => err);\r\n // })\r\n // );\r\n // }\r\n}\r\n","/**\r\n * Проверяет, пересекаются ли два массива (есть ли хотя бы один общий элемент).\r\n */\r\nexport function hasArrayIntersection<T>(arrayA: T[], arrayB: T[]): boolean {\r\n if (!arrayA || !arrayB) return false;\r\n // // сложность O(N * M)\r\n // return arrayA.some(item => arrayB.includes(item));\r\n // оптимизация с помощью Set до сложности O(N + M)\r\n const setB = new Set(arrayB);\r\n return arrayA.some(item => setB.has(item));\r\n}\r\n","import { inject, Injectable } from '@angular/core';\r\n\r\nimport { AuthService } from './auth.service';\r\nimport { hasArrayIntersection } from '../utils/array.utils';\r\nimport { ADMIN_ROLE, HIDE_ROLE_MARKER } from '../constants';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\n/** Вычисление разрешений по ролям */ \r\nexport class RoleService {\r\n private readonly authService = inject(AuthService);\r\n\r\n /**\r\n * Проверяет, заблокирован ли доступ к элементу на основе списка ограничений.\r\n * Возвращает true, если у пользователя есть запрещенная роль или передан маркер 'HIDE'.\r\n * Администратор (ADMIN) всегда игнорирует любые запреты и маркер 'HIDE'.\r\n */\r\n public hasForbiddenRole(forbiddenRoles: string[]): boolean {\r\n if (!forbiddenRoles || forbiddenRoles.length === 0) return false;\r\n\r\n const userRoles = this.authService.roles() ?? [];\r\n\r\n // Если пользователь ADMIN — он игнорирует списки запретов\r\n if (userRoles.includes(ADMIN_ROLE)) return false;\r\n\r\n // Если в условиях зашит маркер 'HIDE' — скрываем элемент от обычного пользователя\r\n if (forbiddenRoles.includes(HIDE_ROLE_MARKER)) return true;\r\n\r\n // В остальных случаях проверяем стандартное пересечение ролей\r\n return hasArrayIntersection(userRoles, forbiddenRoles);\r\n }\r\n\r\n /**\r\n * Проверяет, есть ли у пользователя хотя бы одна из разрешенных ролей.\r\n * ADMIN всегда возвращает true.\r\n */\r\npublic hasAnyRole(allowedRoles: string[]): boolean {\r\n const userRoles = this.authService.roles() ?? [];\r\n if (userRoles.includes(ADMIN_ROLE)) return true;\r\n \r\n return hasArrayIntersection(userRoles, allowedRoles);\r\n}\r\n}\r\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\r\nimport { MatProgressSpinner } from '@angular/material/progress-spinner';\r\n\r\n@Component({\r\n selector: 'ngx-uik-loader',\r\n template: `\r\n <div class=\"loading-indicator\">\r\n <mat-progress-spinner mode=\"indeterminate\" aria-label=\"Загрузка...\"/>\r\n </div>\r\n `,\r\n styles: [\r\n `\r\n /* Разместить по центру */\r\n .loading-indicator {\r\n position: fixed;\r\n z-index: 999;\r\n height: 2em;\r\n width: 2em;\r\n overflow: initial;\r\n margin: auto;\r\n inset: 0; /* 2. Современное CSS-свойство взамен top/left/bottom/right */\r\n }\r\n\r\n /* Заблокировать оверлей */\r\n .loading-indicator::before {\r\n content: '';\r\n display: block;\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background-color: rgba(0, 0, 0, 0.3);\r\n }\r\n `,\r\n ],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n imports: [MatProgressSpinner],\r\n})\r\nexport class ProgressSpinnerComponent {}\r\n","import { Overlay, OverlayRef } from '@angular/cdk/overlay';\r\nimport { ComponentPortal } from '@angular/cdk/portal';\r\nimport { inject, OnDestroy, Service } from '@angular/core';\r\n\r\nimport { ProgressSpinnerComponent } from '../shared/progress-spinner';\r\n\r\n@Service()\r\nexport class LoaderService implements OnDestroy {\r\n private overlayRef: OverlayRef | null = null;\r\n private readonly overlay = inject(Overlay);\r\n\r\n // Счетчик для контроля параллельных HTTP-запросов\r\n private activeRequests = 0;\r\n\r\n public show(): void {\r\n this.activeRequests++;\r\n\r\n if (!this.overlayRef) {\r\n this.overlayRef = this.overlay.create({\r\n hasBackdrop: false, // Настройки бэкдропа теперь берет на себя стилистика лоадера\r\n positionStrategy: this.overlay\r\n .position()\r\n .global()\r\n .centerHorizontally()\r\n .centerVertically(),\r\n });\r\n }\r\n if (!this.overlayRef.hasAttached()) {\r\n const spinnerPortal = new ComponentPortal(ProgressSpinnerComponent);\r\n this.overlayRef.attach(spinnerPortal);\r\n }\r\n }\r\n\r\n public hide(): void {\r\n if (this.activeRequests > 0) {\r\n this.activeRequests--;\r\n }\r\n // Скрываем спиннер только тогда, когда завершился ПОСЛЕДНИЙ активный запрос\r\n if (this.activeRequests === 0 && this.overlayRef && this.overlayRef.hasAttached()) {\r\n this.overlayRef.detach();\r\n }\r\n }\r\n\r\n /**\r\n * Принудительный сброс (например, при смене роута или критической ошибке)\r\n */\r\n public reset(): void {\r\n this.activeRequests = 0;\r\n if (this.overlayRef && this.overlayRef.hasAttached()) {\r\n this.overlayRef.detach();\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n if (this.overlayRef) {\r\n this.overlayRef.dispose(); // Полностью удаляет элемент оверлея из DOM\r\n }\r\n }\r\n}\r\n","import { inject, Service } from '@angular/core';\r\nimport { MatSnackBar } from '@angular/material/snack-bar';\r\n\r\nimport { INotificationService } from '../models';\r\n\r\n// Сервис для вывода сообщений(ошибки, успеха)\r\n@Service()\r\nexport class NotificationService implements INotificationService {\r\n private readonly duration = 5000;\r\n private readonly _snackBar = inject(MatSnackBar);\r\n\r\n openSnackBar(message: string, action: string, duration: number = this.duration) {\r\n this._snackBar.open(message, action, {\r\n duration,\r\n panelClass: ['red-snackbar'],\r\n });\r\n }\r\n\r\n success(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n info(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n helper(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n warning(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n error(message: string, duration: number = this.duration) {\r\n this.openSnackBar(message, 'Закрыть', duration);\r\n }\r\n\r\n}\r\n","/**\r\n * Преобразует дату в локальную строковую дату (Europe/Samara).\r\n * Чистая функция, независимая от контекста Angular.\r\n */\r\nexport function dateToString(\r\n date: string | number | Date | null | undefined,\r\n): string {\r\n if (!date) return '';\r\n\r\n return new Date(date).toLocaleString('ru-RU', {\r\n timeZone: 'Europe/Samara',\r\n });\r\n}\r\n","import { computed, signal, type Signal } from '@angular/core';\r\n\r\nexport class SignalSelectionModel<T> {\r\n private readonly _selectedSet = signal<Set<T>>(new Set());\r\n\r\n // Свойства-сигналы для эффективной работы в Angular шаблонах\r\n readonly selected: Signal<T[]> = computed(() => Array.from(this._selectedSet()));\r\n readonly isEmpty = computed(() => this._selectedSet().size === 0);\r\n readonly hasValue = computed(() => this._selectedSet().size > 0);\r\n\r\n constructor(\r\n public readonly multiple = false, // Поле multiple как в CDK\r\n initialValues: T[] = [],\r\n private _compareWith?: (o1: T, o2: T) => boolean // Поддержка сравнения по ID (как в MatSelect)\r\n ) {\r\n if (initialValues.length > 0) {\r\n this.select(...initialValues);\r\n }\r\n }\r\n\r\n // Проверка наличия элемента с учетом кастомного сравнения\r\n isSelected(value: T): boolean {\r\n const set = this._selectedSet();\r\n if (!this._compareWith) {\r\n return set.has(value);\r\n }\r\n \r\n for (const item of set) {\r\n if (this._compareWith(item, value)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n select(...values: T[]): void {\r\n this._selectedSet.update(set => {\r\n const newSet = new Set(set);\r\n \r\n if (!this.multiple) {\r\n newSet.clear();\r\n if (values.length > 0) {\r\n this._addWithCompare(newSet, values[values.length - 1]);\r\n }\r\n } else {\r\n values.forEach(v => this._addWithCompare(newSet, v));\r\n }\r\n return newSet;\r\n });\r\n }\r\n\r\n deselect(...values: T[]): void {\r\n if (values.length === 0) return; // Ранний выход, если массив пустой\r\n\r\n this._selectedSet.update(set => {\r\n const newSet = new Set(set);\r\n if (this._compareWith) {\r\n const toDelete = new Set<T>(); // Сюда складываем кандидатов на удаление\r\n \r\n for (const value of values) {\r\n for (const current of newSet) {\r\n if (this._compareWith(current, value)) {\r\n toDelete.add(current);\r\n }\r\n }\r\n }\r\n // Удаляем только после того, как все циклы по newSet завершены\r\n toDelete.forEach(item => newSet.delete(item));\r\n } else {\r\n values.forEach(v => newSet.delete(v));\r\n }\r\n return newSet;\r\n });\r\n }\r\n\r\n toggle(value: T): void {\r\n this.isSelected(value) ? this.deselect(value) : this.select(value);\r\n }\r\n\r\n clear(): void {\r\n if (this._selectedSet().size === 0) return; \r\n this._selectedSet.set(new Set());\r\n }\r\n\r\n // Вспомогательный метод для корректного добавления объектов\r\n private _addWithCompare(set: Set<T>, value: T): void {\r\n if (this._compareWith) {\r\n let duplicate: T | undefined;\r\n for (const current of set) {\r\n if (this._compareWith(current, value)) {\r\n duplicate = current;\r\n break;\r\n }\r\n }\r\n if (duplicate !== undefined) {\r\n set.delete(duplicate);\r\n }\r\n }\r\n set.add(value);\r\n }\r\n}\r\n","import { ZodError } from 'zod';\r\n\r\nexport function formatZodError(error: ZodError, contextName: string): string {\r\n const issuesText = error.issues\r\n .map((issue) => {\r\n // Форматируем путь: [0, \"user\"] -> \"[0] -> user\"\r\n const path = issue.path\r\n .map((p) => (typeof p === 'number' ? `[${p}]` : p))\r\n .join(' -> ');\r\n\r\n const location = path ? `в \"${path}\"` : 'в корне';\r\n const expected = (issue as any).expected\r\n ? `ожидалось \"${(issue as any).expected}\"`\r\n : '';\r\n const received = (issue as any).received\r\n ? `, получено \"${(issue as any).received}\"`\r\n : '';\r\n\r\n return ` • Ошибка ${location}: ${expected}${received}. Сообщение: ${issue.message}`;\r\n })\r\n .join('\\n');\r\n\r\n return `\\n[Zod Валидация] Ошибка парсинга для ресурса: ${contextName}\\n${issuesText}\\n`;\r\n}\r\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import z, { z as z$1 } from "zod";
|
|
2
|
-
import * as i0 from "@angular/core";
|
|
3
|
-
import { EnvironmentProviders, InjectionToken, OnDestroy, PipeTransform, WritableSignal } from "@angular/core";
|
|
4
1
|
import { LoaderService, NotificationService, RoleService, SignalSelectionModel } from "@kuzntsv/uikit";
|
|
2
|
+
import z, { ZodObject, z as z$1 } from "zod";
|
|
5
3
|
import { Observable, Subject, Subscription } from "rxjs";
|
|
6
4
|
import { MatMenuTrigger } from "@angular/material/menu";
|
|
7
5
|
import { MatPaginator, PageEvent } from "@angular/material/paginator";
|
|
8
6
|
import { MatSort, Sort } from "@angular/material/sort";
|
|
9
7
|
import { MatTableDataSource } from "@angular/material/table";
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
import { EnvironmentProviders, InjectionToken, OnDestroy, PipeTransform, WritableSignal } from "@angular/core";
|
|
10
10
|
import { FormGroup } from "@angular/forms";
|
|
11
11
|
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
|
|
12
12
|
import { SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from "@angular/platform-browser";
|
|
@@ -16,38 +16,6 @@ import { FieldTree, FormValueControl, SchemaPath } from "@angular/forms/signals"
|
|
|
16
16
|
import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete";
|
|
17
17
|
import { NativeDateAdapter } from "@angular/material/core";
|
|
18
18
|
import { MatSelectChange } from "@angular/material/select";
|
|
19
|
-
declare const loginSchema: z$1.ZodObject<{
|
|
20
|
-
username: z$1.ZodString;
|
|
21
|
-
password: z$1.ZodString;
|
|
22
|
-
}, z$1.core.$strip>;
|
|
23
|
-
type Authenticate = z$1.infer<typeof loginSchema>;
|
|
24
|
-
export declare class LoginPageComponent {
|
|
25
|
-
pending: import("@angular/core").WritableSignal<boolean>;
|
|
26
|
-
isKiosk: import("@angular/core").WritableSignal<boolean>;
|
|
27
|
-
/**
|
|
28
|
-
* Идентификатор АС получаем автоматически из ActivatedRoute Data!
|
|
29
|
-
* (Для этого в настройках роутера должен быть включен withComponentInputBinding())
|
|
30
|
-
*/
|
|
31
|
-
idCas: import("@angular/core").InputSignal<number>;
|
|
32
|
-
private readonly authService;
|
|
33
|
-
private readonly router;
|
|
34
|
-
private readonly notificationService;
|
|
35
|
-
constructor();
|
|
36
|
-
onSubmit(event: Authenticate): Promise<void>;
|
|
37
|
-
onSubmitPermit(permit: string): Promise<void>;
|
|
38
|
-
private startLoading;
|
|
39
|
-
private finishLoading;
|
|
40
|
-
private handleError;
|
|
41
|
-
private handleSuccessLogin;
|
|
42
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<LoginPageComponent, never>;
|
|
43
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<LoginPageComponent, "ngx-uik-login-page", never, {
|
|
44
|
-
"idCas": {
|
|
45
|
-
"alias": "idCas";
|
|
46
|
-
"required": false;
|
|
47
|
-
"isSignal": true;
|
|
48
|
-
};
|
|
49
|
-
}, {}, never, never, true, never>;
|
|
50
|
-
}
|
|
51
19
|
export type ObjectKeys<T extends string | number | symbol = string, K = any> = { [key in T]: K; };
|
|
52
20
|
export type fnType<T> = ((data: T, extraData?: any) => void) | undefined;
|
|
53
21
|
export type fnCbType<T> = ((callback: () => void, data: T, extraData?: any) => void) | undefined;
|
|
@@ -668,12 +636,11 @@ export interface Topic {
|
|
|
668
636
|
getTopic(baseUrl: string, user: SseUser): string;
|
|
669
637
|
}
|
|
670
638
|
export declare class SseService<T> {
|
|
671
|
-
private
|
|
672
|
-
private
|
|
673
|
-
private
|
|
639
|
+
private readonly destroyRef;
|
|
640
|
+
private activeEventSource;
|
|
641
|
+
private readonly stop$;
|
|
674
642
|
constructor();
|
|
675
643
|
createSseEventSource(baseUrl: string, user: SseUser, topic: Topic): Observable<T>;
|
|
676
|
-
private reconnectOnError;
|
|
677
644
|
closeSseEventSource(): void;
|
|
678
645
|
static ɵfac: i0.ɵɵFactoryDeclaration<SseService<any>, never>;
|
|
679
646
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
@@ -876,6 +843,8 @@ export declare abstract class BaseSignalEditClass<TDomain extends Record<string,
|
|
|
876
843
|
protected formAction: import("@angular/core").Signal<Action>;
|
|
877
844
|
protected formActionIsAdd: import("@angular/core").Signal<boolean>;
|
|
878
845
|
protected formActionIsEdit: import("@angular/core").Signal<boolean>;
|
|
846
|
+
/** Наследник может передать чистую DTO схему для очистки (опционально). */
|
|
847
|
+
protected domainSchema?: ZodObject<any>;
|
|
879
848
|
protected domainModel: import("@angular/core").Signal<TDomain>;
|
|
880
849
|
protected formModel: WritableSignal<TForm>;
|
|
881
850
|
/** Форма. */
|
|
@@ -899,6 +868,7 @@ export declare abstract class BaseSignalEditClass<TDomain extends Record<string,
|
|
|
899
868
|
protected domainModelToFormModel(domainModel: TDomain): TForm;
|
|
900
869
|
/**
|
|
901
870
|
* Маппинг: Форма -> Домен.
|
|
871
|
+
* Если передана схема домена (domainSchema), Zod сам удалит лишние поля и вернет чистый DTO
|
|
902
872
|
* Реализация по умолчанию объединяет старый домен и новые поля формы.
|
|
903
873
|
*/
|
|
904
874
|
protected formModelToDomainModel(formModel: TForm, originalDomain?: TDomain): TDomain;
|
|
@@ -1054,9 +1024,9 @@ export declare abstract class BaseSignalPageClass<TDomain extends BaseSchemaClas
|
|
|
1054
1024
|
/** Выбранные строки в таблице. */
|
|
1055
1025
|
selected: import("@angular/core").WritableSignal<TForm[]>;
|
|
1056
1026
|
/** Доступность кнопки "Изменить". */
|
|
1057
|
-
editBtnDisabled: import("@angular/core").Signal<boolean>;
|
|
1027
|
+
protected editBtnDisabled: import("@angular/core").Signal<boolean>;
|
|
1058
1028
|
/** Доступность кнопки "Удалить". */
|
|
1059
|
-
removeBtnDisabled: import("@angular/core").Signal<boolean>;
|
|
1029
|
+
protected removeBtnDisabled: import("@angular/core").Signal<boolean>;
|
|
1060
1030
|
/** Маппинг TDomain в TForm */
|
|
1061
1031
|
protected fnMapEntity: fnMapEntityType<TForm> | undefined;
|
|
1062
1032
|
/** Ссылка на ресурс получения данных, определяет потомок. */
|
|
@@ -1259,12 +1229,12 @@ export declare const MY_NATIVE_FORMATS: {
|
|
|
1259
1229
|
};
|
|
1260
1230
|
};
|
|
1261
1231
|
};
|
|
1262
|
-
export declare const dateRangeFormSchema:
|
|
1232
|
+
export declare const dateRangeFormSchema: z$1.ZodObject<{
|
|
1263
1233
|
begin: z$1.ZodNullable<z$1.ZodInstanceOf<Date>>;
|
|
1264
1234
|
end: z$1.ZodNullable<z$1.ZodInstanceOf<Date>>;
|
|
1265
1235
|
}, z$1.core.$strip>;
|
|
1266
|
-
export type DateRangeForm = z$1.output<
|
|
1267
|
-
export declare const dateRangeSchema:
|
|
1236
|
+
export type DateRangeForm = z$1.output<typeof dateRangeFormSchema>;
|
|
1237
|
+
export declare const dateRangeSchema: z$1.ZodPipe<z$1.ZodObject<{
|
|
1268
1238
|
begin: z$1.ZodNullable<z$1.ZodInstanceOf<Date>>;
|
|
1269
1239
|
end: z$1.ZodNullable<z$1.ZodInstanceOf<Date>>;
|
|
1270
1240
|
}, z$1.core.$strip>, z$1.ZodTransform<{
|
|
@@ -1274,11 +1244,43 @@ export declare const dateRangeSchema: (isRequired: boolean) => z$1.ZodPipe<z$1.Z
|
|
|
1274
1244
|
begin: Date | null;
|
|
1275
1245
|
end: Date | null;
|
|
1276
1246
|
}>>;
|
|
1277
|
-
export type DateRange = z$1.output<
|
|
1247
|
+
export type DateRange = z$1.output<typeof dateRangeSchema>;
|
|
1278
1248
|
export declare class EmptyComponent {
|
|
1279
1249
|
static ɵfac: i0.ɵɵFactoryDeclaration<EmptyComponent, never>;
|
|
1280
1250
|
static ɵcmp: i0.ɵɵComponentDeclaration<EmptyComponent, "ngx-uik-empty-route-guard-holder", never, {}, {}, never, never, true, never>;
|
|
1281
1251
|
}
|
|
1252
|
+
declare const loginSchema: z$1.ZodObject<{
|
|
1253
|
+
username: z$1.ZodString;
|
|
1254
|
+
password: z$1.ZodString;
|
|
1255
|
+
}, z$1.core.$strip>;
|
|
1256
|
+
type Authenticate = z$1.infer<typeof loginSchema>;
|
|
1257
|
+
export declare class LoginPageComponent {
|
|
1258
|
+
pending: import("@angular/core").WritableSignal<boolean>;
|
|
1259
|
+
isKiosk: import("@angular/core").WritableSignal<boolean>;
|
|
1260
|
+
/**
|
|
1261
|
+
* Идентификатор АС получаем автоматически из ActivatedRoute Data!
|
|
1262
|
+
* (Для этого в настройках роутера должен быть включен withComponentInputBinding())
|
|
1263
|
+
*/
|
|
1264
|
+
idCas: import("@angular/core").InputSignal<number>;
|
|
1265
|
+
private readonly authService;
|
|
1266
|
+
private readonly router;
|
|
1267
|
+
private readonly notificationService;
|
|
1268
|
+
constructor();
|
|
1269
|
+
onSubmit(event: Authenticate): Promise<void>;
|
|
1270
|
+
onSubmitPermit(permit: string): Promise<void>;
|
|
1271
|
+
private startLoading;
|
|
1272
|
+
private finishLoading;
|
|
1273
|
+
private handleError;
|
|
1274
|
+
private handleSuccessLogin;
|
|
1275
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LoginPageComponent, never>;
|
|
1276
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LoginPageComponent, "ngx-uik-login-page", never, {
|
|
1277
|
+
"idCas": {
|
|
1278
|
+
"alias": "idCas";
|
|
1279
|
+
"required": false;
|
|
1280
|
+
"isSignal": true;
|
|
1281
|
+
};
|
|
1282
|
+
}, {}, never, never, true, never>;
|
|
1283
|
+
}
|
|
1282
1284
|
export declare class NavBarComponent extends BaseNavBarMenuClass {
|
|
1283
1285
|
/** Видимость панели, когда loggedIn === false. */
|
|
1284
1286
|
visibleOnLogout: import("@angular/core").InputSignal<boolean>;
|
package/types/kuzntsv-uikit.d.ts
CHANGED
|
@@ -166,7 +166,6 @@ export declare class AuthService {
|
|
|
166
166
|
readonly roles: import("@angular/core").Signal<string[]>;
|
|
167
167
|
readonly loggedIn: import("@angular/core").Signal<boolean>;
|
|
168
168
|
private readonly defaultOptions;
|
|
169
|
-
private readonly notificationService;
|
|
170
169
|
private readonly userService;
|
|
171
170
|
private readonly eventService;
|
|
172
171
|
private readonly destroyRef;
|