@kuzntsv/uikit 1.0.90 → 1.0.93
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 -3
- package/fesm2022/kuzntsv-uikit-interceptors.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit-ui.mjs +24 -17
- package/fesm2022/kuzntsv-uikit-ui.mjs.map +1 -1
- package/fesm2022/kuzntsv-uikit.mjs +2 -1
- package/fesm2022/kuzntsv-uikit.mjs.map +1 -1
- package/package.json +1 -1
- package/types/kuzntsv-uikit-ui.d.ts +33 -34
- package/types/kuzntsv-uikit.d.ts +1 -0
|
@@ -19,6 +19,7 @@ const DEFAULT_TIMEOUT = new InjectionToken("DEFAULT_TIMEOUT", {
|
|
|
19
19
|
});
|
|
20
20
|
const REQUEST_TIMEOUT = new HttpContextToken(() => inject(DEFAULT_TIMEOUT));
|
|
21
21
|
const SKIP_LOADER = new HttpContextToken(() => false);
|
|
22
|
+
const SKIP_NOTIFICATION = new HttpContextToken(() => false);
|
|
22
23
|
const APP_ENVIRONMENT = new InjectionToken("ApplicationEnvironment");
|
|
23
24
|
const UIKIT_NOTIFICATION_TOKEN = new InjectionToken("UikitNotificationToken");
|
|
24
25
|
var AuthEventService = class AuthEventService {
|
|
@@ -461,6 +462,6 @@ function formatZodError(error, contextName) {
|
|
|
461
462
|
return ` • Ошибка ${path ? `в "${path}"` : "в корне"}: ${issue.expected ? `ожидалось "${issue.expected}"` : ""}${issue.received ? `, получено "${issue.received}"` : ""}. Сообщение: ${issue.message}`;
|
|
462
463
|
}).join("\n")}\n`;
|
|
463
464
|
}
|
|
464
|
-
export { ADMIN_ROLE, API_NAME, APP_ENVIRONMENT, AuthEventService, AuthService, BYPASS_AUTH, DEFAULT_TIMEOUT, HIDE_ROLE_MARKER, LoaderService, NotificationService, REQUEST_TIMEOUT, RoleService, SKIP_LOADER, SignalSelectionModel, UIKIT_CONFIG, UIKIT_NOTIFICATION_TOKEN, User, UserService, dateToString, formatZodError, hasArrayIntersection };
|
|
465
|
+
export { ADMIN_ROLE, API_NAME, APP_ENVIRONMENT, AuthEventService, AuthService, BYPASS_AUTH, DEFAULT_TIMEOUT, HIDE_ROLE_MARKER, LoaderService, NotificationService, REQUEST_TIMEOUT, RoleService, SKIP_LOADER, SKIP_NOTIFICATION, SignalSelectionModel, UIKIT_CONFIG, UIKIT_NOTIFICATION_TOKEN, User, UserService, dateToString, formatZodError, hasArrayIntersection };
|
|
465
466
|
|
|
466
467
|
//# sourceMappingURL=kuzntsv-uikit.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kuzntsv-uikit.mjs","names":[],"sources":["../../src/config/lib-config.ts","../../src/constants/auth.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","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","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/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","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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"}
|
package/package.json
CHANGED
|
@@ -1310,15 +1310,15 @@ export declare abstract class BaseSignalFilterClass<F extends SafeFilterState> {
|
|
|
1310
1310
|
"filters": "filtersChange";
|
|
1311
1311
|
}, never, never, true, never>;
|
|
1312
1312
|
}
|
|
1313
|
-
export declare abstract class BaseSignalPageClass<
|
|
1313
|
+
export declare abstract class BaseSignalPageClass<TDomain extends BaseSchemaClass, TForm extends BaseSchemaClass = TDomain, TFilter extends SafeFilterState = Record<string, never>> implements OnDestroy {
|
|
1314
1314
|
protected dialog: MatDialog;
|
|
1315
1315
|
protected injector: Injector;
|
|
1316
1316
|
protected entityService: any;
|
|
1317
1317
|
protected entityEditComponent?: any;
|
|
1318
1318
|
protected extraData?: any;
|
|
1319
|
-
protected abstract readonly
|
|
1320
|
-
protected readonly filterSchema?: z.ZodType<
|
|
1321
|
-
protected readonly defaultFilters?:
|
|
1319
|
+
protected abstract readonly schemaDTO: z.ZodType<TDomain>;
|
|
1320
|
+
protected readonly filterSchema?: z.ZodType<TFilter>;
|
|
1321
|
+
protected readonly defaultFilters?: TFilter;
|
|
1322
1322
|
private get storagePrefix();
|
|
1323
1323
|
defaultPageIndex: number;
|
|
1324
1324
|
defaultPageSize: number;
|
|
@@ -1332,27 +1332,27 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1332
1332
|
/**
|
|
1333
1333
|
* Создание записи: вызывается перед открытием диалога.
|
|
1334
1334
|
*/
|
|
1335
|
-
beforeOpenDialogOnNewEvent: fnType<
|
|
1335
|
+
beforeOpenDialogOnNewEvent: fnType<TDomain>;
|
|
1336
1336
|
/**
|
|
1337
1337
|
* Редактирование записи: вызывается перед открытием диалога.
|
|
1338
1338
|
*/
|
|
1339
|
-
beforeOpenDialogOnEditEvent: fnType<
|
|
1339
|
+
beforeOpenDialogOnEditEvent: fnType<TDomain>;
|
|
1340
1340
|
/**
|
|
1341
1341
|
* Создание записи: вызывается перед сохранением объекта.
|
|
1342
1342
|
*/
|
|
1343
|
-
beforeSaveOnNewEvent: fnType<
|
|
1343
|
+
beforeSaveOnNewEvent: fnType<TDomain>;
|
|
1344
1344
|
/**
|
|
1345
1345
|
* Редактирование записи: вызывается перед сохранением объекта.
|
|
1346
1346
|
*/
|
|
1347
|
-
beforeSaveOnEditEvent: fnType<
|
|
1347
|
+
beforeSaveOnEditEvent: fnType<TDomain>;
|
|
1348
1348
|
/**
|
|
1349
1349
|
* Создание записи: вызывается после сохранения объекта.
|
|
1350
1350
|
*/
|
|
1351
|
-
afterSaveOnNewEvent: fnCbType<
|
|
1351
|
+
afterSaveOnNewEvent: fnCbType<TDomain>;
|
|
1352
1352
|
/**
|
|
1353
1353
|
* Редактирование записи: вызывается после сохранения объекта.
|
|
1354
1354
|
*/
|
|
1355
|
-
afterSaveOnEditEvent: fnCbType<
|
|
1355
|
+
afterSaveOnEditEvent: fnCbType<TDomain>;
|
|
1356
1356
|
editorRoles: string[];
|
|
1357
1357
|
executeRoles: string[];
|
|
1358
1358
|
/** Список ролей, для которых скрыть элемент. */
|
|
@@ -1360,16 +1360,12 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1360
1360
|
/** Разрешен выбор нескольких строк. */
|
|
1361
1361
|
allowMultiSelect: boolean;
|
|
1362
1362
|
/** Выбранные строки в таблице. */
|
|
1363
|
-
selected:
|
|
1364
|
-
protected fnMapEntity: fnMapEntityType<
|
|
1365
|
-
protected dataResource: HttpResourceRef<
|
|
1363
|
+
selected: TForm[];
|
|
1364
|
+
protected fnMapEntity: fnMapEntityType<TForm> | undefined;
|
|
1365
|
+
protected dataResource: HttpResourceRef<TForm[]>;
|
|
1366
1366
|
protected countResource: HttpResourceRef<number>;
|
|
1367
|
-
/**
|
|
1368
|
-
* 1. АБСТРАКТНЫЕ ФАБРИКИ: Компонент сам определит,
|
|
1369
|
-
* какие методы какого сервиса вызвать для инициализации.
|
|
1370
|
-
*/
|
|
1371
1367
|
/** Данные для табличного компонента. */
|
|
1372
|
-
data: import("@angular/core").Signal<
|
|
1368
|
+
data: import("@angular/core").Signal<TForm[]>;
|
|
1373
1369
|
/** Количество записей по фильтру. */
|
|
1374
1370
|
length: import("@angular/core").Signal<number>;
|
|
1375
1371
|
/** Глобальный индикатор загрузки (для спиннера на странице) */
|
|
@@ -1380,7 +1376,7 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1380
1376
|
removeBtnDisabled: boolean;
|
|
1381
1377
|
/** Разрешено ли отправлять событие rowDoubleClicked при двойном щелчке мышью. */
|
|
1382
1378
|
dblClickedEventEnable: boolean;
|
|
1383
|
-
opt: IContextMenuOptions<
|
|
1379
|
+
opt: IContextMenuOptions<TForm>[];
|
|
1384
1380
|
protected router: Router;
|
|
1385
1381
|
/** Сервис для работы с LocalStorage. */
|
|
1386
1382
|
protected localStorageService: LocalStorageService;
|
|
@@ -1394,11 +1390,10 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1394
1390
|
/** Разово прочитать сохраненные значения из localStorage при старте страницы,
|
|
1395
1391
|
* провалидировать их через Zod-схему и записать в сигнал filters */
|
|
1396
1392
|
protected initFilters(): void;
|
|
1397
|
-
clearEvent(): void;
|
|
1398
1393
|
private getInitialFilters2;
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1394
|
+
/** Событие очистки фильтра. */
|
|
1395
|
+
clearEvent(): void;
|
|
1396
|
+
filters: import("@angular/core").WritableSignal<TFilter>;
|
|
1402
1397
|
/** Очистка фильтра. (для очистки присвоить пустой объект '{}'). */
|
|
1403
1398
|
clearFilters: import("@angular/core").WritableSignal<any>;
|
|
1404
1399
|
/** Состояние сортировки, пагинации. */
|
|
@@ -1414,20 +1409,20 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1414
1409
|
* @param key - имя фильтра.
|
|
1415
1410
|
* @param value - значение.
|
|
1416
1411
|
*/
|
|
1417
|
-
setFilter<K extends keyof
|
|
1412
|
+
setFilter<K extends keyof TFilter>(key: K, value: TFilter[K]): void;
|
|
1418
1413
|
/**
|
|
1419
1414
|
* Обновление нескольких фильтров одновременно (пачкой)
|
|
1420
1415
|
* @param fields Пачка фильтров в виде объекта { имяПоля: значение }
|
|
1421
1416
|
*/
|
|
1422
|
-
setFilters(fields: Partial<
|
|
1417
|
+
setFilters(fields: Partial<TFilter>): void;
|
|
1423
1418
|
onPageChange(pageState: PageState): void;
|
|
1424
1419
|
onSortChange(sortState: SortState): void;
|
|
1425
1420
|
/** Событие изменение выбранных записей. */
|
|
1426
|
-
onSelectionChange(selected:
|
|
1421
|
+
onSelectionChange(selected: TForm[]): void;
|
|
1427
1422
|
/** Обновление доступности кнопки "Изменить" - вызывается при изменении выбранных записей. */
|
|
1428
|
-
editBtnDisabledUpdate(selected:
|
|
1423
|
+
editBtnDisabledUpdate(selected: TForm[]): void;
|
|
1429
1424
|
/** Обновление доступности кнопки "Удалить" - вызывается при изменении выбранных записей. */
|
|
1430
|
-
removeBtnDisabledUpdate(selected:
|
|
1425
|
+
removeBtnDisabledUpdate(selected: TForm[]): void;
|
|
1431
1426
|
/** Событие двойной клик. */
|
|
1432
1427
|
onRowDoubleClicked(): void;
|
|
1433
1428
|
/** Событие создание. */
|
|
@@ -1438,17 +1433,21 @@ export declare abstract class BaseSignalPageClass<T extends BaseSchemaClass, F e
|
|
|
1438
1433
|
removeEvent(): void;
|
|
1439
1434
|
/** Событие обновления данных. */
|
|
1440
1435
|
refreshEvent(): void;
|
|
1441
|
-
/** Событие очистки фильтра. */
|
|
1442
1436
|
/** Изменение сообщения об удалении. */
|
|
1443
1437
|
onRemovalMessageChange(message: string): void;
|
|
1444
1438
|
/** Изменение заголовка страницы. */
|
|
1445
1439
|
onPageTitleChange(title: string): void;
|
|
1446
|
-
openDialog(entityComponent: any, data: IEditDialogData<
|
|
1440
|
+
openDialog(entityComponent: any, data: IEditDialogData<TDomain>, dialog: any): any;
|
|
1447
1441
|
openDialogRemoveConfirm(removalMessage: string): MatDialogRef<DialogComponent> | undefined;
|
|
1448
|
-
protected onMiddleButton(baseUrl: string, queryParamName?: string, queryParamValue?: string): ($event: MouseEvent, item:
|
|
1449
|
-
protected onActionMiddleButton(baseUrl: string, queryParamName?: string, queryParamValue?: string): ($event: MouseEvent, item:
|
|
1450
|
-
|
|
1451
|
-
|
|
1442
|
+
protected onMiddleButton(baseUrl: string, queryParamName?: string, queryParamValue?: string): ($event: MouseEvent, item: TForm | undefined, contextMenu: MatMenuTrigger) => void;
|
|
1443
|
+
protected onActionMiddleButton(baseUrl: string, queryParamName?: string, queryParamValue?: string): ($event: MouseEvent, item: TForm | undefined) => void;
|
|
1444
|
+
/**
|
|
1445
|
+
* Маппинг: Форма -> Домен.
|
|
1446
|
+
* Реализация по умолчанию объединяет старый домен и новые поля формы.
|
|
1447
|
+
*/
|
|
1448
|
+
protected formModelToDomainModel(formModel: TForm, originalDomain?: TDomain): TDomain;
|
|
1449
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<BaseSignalPageClass<any, any, any>, never>;
|
|
1450
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<BaseSignalPageClass<any, any, any>, never, never, {}, {}, never, never, true, never>;
|
|
1452
1451
|
}
|
|
1453
1452
|
export declare abstract class BaseSignalServiceClass<T extends BaseSchemaClass> {
|
|
1454
1453
|
abstract baseRoute: string;
|
package/types/kuzntsv-uikit.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export declare const BYPASS_AUTH: HttpContextToken<boolean>;
|
|
|
32
32
|
export declare const DEFAULT_TIMEOUT: InjectionToken<number>;
|
|
33
33
|
export declare const REQUEST_TIMEOUT: HttpContextToken<number>;
|
|
34
34
|
export declare const SKIP_LOADER: HttpContextToken<boolean>;
|
|
35
|
+
export declare const SKIP_NOTIFICATION: HttpContextToken<boolean>;
|
|
35
36
|
export interface AppEnvironment {
|
|
36
37
|
appVersion: string;
|
|
37
38
|
production: boolean;
|