diff --git a/angular.json b/angular.json index 57dc6e7..564b7ee 100644 --- a/angular.json +++ b/angular.json @@ -90,7 +90,7 @@ }, "defaultConfiguration": "development", "options": { - "port": 3001, + "port": 5000, "proxyConfig": "src/proxy.conf.js" } }, diff --git a/public/favicon/site.webmanifest b/public/favicon/site.webmanifest index edc6ed1..ea30e22 100644 --- a/public/favicon/site.webmanifest +++ b/public/favicon/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "صندوق فروشگاهی", - "short_name": "صندوق فروشگاهی", + "name": "مدیریت کسب‌وکار", + "short_name": "مدیریت کسب‌وکار", "icons": [ { "src": "/web-app-manifest-192x192.png", @@ -18,4 +18,4 @@ "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" -} \ No newline at end of file +} diff --git a/src/app.routes.ts b/src/app.routes.ts index 56594eb..0d79161 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -1,16 +1,5 @@ +import { SUPER_ADMIN_ROUTES } from '@/domains/superAdmin/routes'; import { AuthComponent } from '@/modules/auth/pages/auth.component'; -import { BANK_ACCOUNTS_ROUTES } from '@/modules/bankAccounts/constants'; -import { BANK_BRANCHES_ROUTES } from '@/modules/bankBranches/constants'; -import { CARDEX_ROUTES } from '@/modules/cardex/constants'; -import { CUSTOMERS_ROUTES } from '@/modules/customers/constants'; -import { DASHBOARD_ROUTES } from '@/modules/dashboard/constants'; -import { INVENTORIES_ROUTES } from '@/modules/inventories/constants'; -import { POSComponent } from '@/modules/pos/views/pos.component'; -import { PRODUCT_BRANDS_ROUTES } from '@/modules/productBrands/constants'; -import { PRODUCT_CATEGORIES_ROUTES } from '@/modules/productCategories/constants'; -import { PRODUCTS_ROUTES } from '@/modules/products/constants'; -import { SUPPLIERS_ROUTES } from '@/modules/suppliers/constants'; -import { USERS_ROUTES } from '@/modules/users/constants'; import { Routes } from '@angular/router'; import { AppLayout } from './app/layout/component/app.layout.component'; import { Dashboard } from './app/pages/dashboard/dashboard'; @@ -22,27 +11,17 @@ export const appRoutes: Routes = [ path: '', component: AppLayout, children: [ + SUPER_ADMIN_ROUTES, { path: 'ng', component: Dashboard }, - ...DASHBOARD_ROUTES, - ...USERS_ROUTES, - ...SUPPLIERS_ROUTES, - ...PRODUCT_BRANDS_ROUTES, - ...PRODUCT_CATEGORIES_ROUTES, - ...CUSTOMERS_ROUTES, - ...INVENTORIES_ROUTES, - ...PRODUCTS_ROUTES, - ...CARDEX_ROUTES, - ...BANK_BRANCHES_ROUTES, - ...BANK_ACCOUNTS_ROUTES, { path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') }, { path: 'documentation', component: Documentation }, { path: 'pages', loadChildren: () => import('./app/pages/pages.routes') }, ], }, - { - path: 'pos/:posId', - component: POSComponent, - }, + // { + // path: 'pos/:posId', + // component: POSComponent, + // }, { path: 'auth', component: AuthComponent, diff --git a/src/app/core/constants/menuItems.const.ts b/src/app/core/constants/menuItems.const.ts index ce982a3..bc1dab8 100644 --- a/src/app/core/constants/menuItems.const.ts +++ b/src/app/core/constants/menuItems.const.ts @@ -9,67 +9,82 @@ export const MENU_ITEMS = [ }, ], }, - { - label: 'انبارها', - icon: 'pi pi-fw pi-box', - items: [ - { - label: 'لیست انبارها', - icon: 'pi pi-fw pi-list', - routerLink: ['/inventories'], - }, - ], - }, - { - label: 'کالاها', - icon: 'pi pi-fw pi-tags', - items: [ - { - label: 'لیست کالاها', - icon: 'pi pi-fw pi-list', - routerLink: ['/products'], - }, - { - label: 'برندهای کالا', - icon: 'pi pi-fw pi-tags', - routerLink: ['/product-brands'], - }, - { - label: 'دسته‌بندی‌های کالا', - icon: 'pi pi-fw pi-list', - routerLink: ['/product-categories'], - }, - ], - }, - { - label: 'تامین‌کنندگان', - icon: 'pi pi-fw pi-users', - items: [ - { - label: 'لیست تامین‌کنندگان', - icon: 'pi pi-fw pi-list', - routerLink: ['/suppliers'], - }, - ], - }, + // { + // label: 'انبارها', + // icon: 'pi pi-fw pi-box', + // items: [ + // { + // label: 'لیست انبارها', + // icon: 'pi pi-fw pi-list', + // routerLink: ['/inventories'], + // }, + // ], + // }, + // { + // label: 'کالاها', + // icon: 'pi pi-fw pi-tags', + // items: [ + // { + // label: 'لیست کالاها', + // icon: 'pi pi-fw pi-list', + // routerLink: ['/products'], + // }, + // { + // label: 'برندهای کالا', + // icon: 'pi pi-fw pi-tags', + // routerLink: ['/product-brands'], + // }, + // { + // label: 'دسته‌بندی‌های کالا', + // icon: 'pi pi-fw pi-list', + // routerLink: ['/product-categories'], + // }, + // ], + // }, + // { + // label: 'تامین‌کنندگان', + // icon: 'pi pi-fw pi-users', + // items: [ + // { + // label: 'لیست تامین‌کنندگان', + // icon: 'pi pi-fw pi-list', + // routerLink: ['/suppliers'], + // }, + // ], + // }, { label: 'مدیریت سیستم', icon: 'pi pi-fw pi-cog', items: [ - { - label: 'شعب بانک‌ها', - icon: 'pi pi-fw pi-building', - routerLink: ['/bankBranches'], - }, - { - label: 'حساب‌های بانکی', - icon: 'pi pi-fw pi-credit-card', - routerLink: ['/bankAccounts'], - }, + // { + // label: 'حساب‌های بانکی', + // icon: 'pi pi-fw pi-credit-card', + // routerLink: ['/bankAccounts'], + // }, { label: 'کاربران', icon: 'pi pi-fw pi-user', - routerLink: ['/users'], + routerLink: ['/super_admin/users'], + }, + { + label: 'اصناف', + icon: 'pi pi-fw pi-building', + routerLink: ['/super_admin/guilds'], + }, + { + label: 'شرکای تجاری', + icon: 'pi pi-fw pi-credit-card', + routerLink: ['/super_admin/partners'], + }, + { + label: 'ارایه‌دهندگان', + icon: 'pi pi-fw pi-credit-card', + routerLink: ['/super_admin/providers'], + }, + { + label: 'لایسنس‌ها', + icon: 'pi pi-fw pi-credit-card', + routerLink: ['/super_admin/licenses'], }, ], }, diff --git a/src/app/core/interceptors/auth.interceptor copy.ts b/src/app/core/interceptors/auth.interceptor copy.ts deleted file mode 100644 index e1ef720..0000000 --- a/src/app/core/interceptors/auth.interceptor copy.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { HttpInterceptorFn } from '@angular/common/http'; -import { inject } from '@angular/core'; -import { BehaviorSubject, Observable, throwError } from 'rxjs'; -import { catchError, filter, switchMap, take } from 'rxjs/operators'; -import { AuthService } from '../services/auth.service'; - -/** - * HTTP Interceptor for token management - * - Adds Authorization header with Bearer token to requests - * - Handles token refresh on 401 responses - * - Prevents duplicate refresh token requests - */ -export const authInterceptor: HttpInterceptorFn = (req, next) => { - const authService = inject(AuthService); - - // Skip auth for certain endpoints - if (shouldSkipAuth(req.url)) { - return next(req); - } - - const token = authService.getToken(); - - // Add token to request if available - const authReq = token - ? req.clone({ - headers: req.headers.set('Authorization', `Bearer ${token}`), - }) - : req; - - return next(authReq).pipe( - catchError((error) => { - // Handle 401 Unauthorized - token might be expired - if (error.status === 401 && !isRefreshTokenRequest(req.url)) { - return handleTokenRefresh(authService, req, next); - } - - return throwError(() => error); - }) - ); -}; - -// Subject to track if refresh is in progress -const isRefreshing = new BehaviorSubject(false); - -/** - * Handle token refresh logic - */ -function handleTokenRefresh(authService: AuthService, req: any, next: any): Observable { - if (!isRefreshing.value) { - isRefreshing.next(true); - - const refreshToken = authService.getRefreshToken(); - if (!refreshToken) { - isRefreshing.next(false); - authService.logout(); - return throwError(() => new Error('توکنی یافت نشد')); - } - - return authService.refreshToken().pipe( - switchMap((response) => { - isRefreshing.next(false); - - // Retry original request with new token - const newAuthReq = req.clone({ - headers: req.headers.set('Authorization', `Bearer ${response.token}`), - }); - - return next(newAuthReq); - }), - catchError((refreshError) => { - isRefreshing.next(false); - authService.logout(); - return throwError(() => refreshError); - }) - ); - } else { - // Wait for refresh to complete, then retry request - return isRefreshing.pipe( - filter((refreshing) => !refreshing), - take(1), - switchMap(() => { - const token = authService.getToken(); - const newAuthReq = token - ? req.clone({ - headers: req.headers.set('Authorization', `Bearer ${token}`), - }) - : req; - - return next(newAuthReq); - }) - ); - } -} - -/** - * Check if the request should skip authentication - */ -function shouldSkipAuth(url: string): boolean { - const skipAuthUrls = [ - '/api/auth/login', - '/api/auth/register', - '/api/auth/forgot-password', - '/api/auth/verify-email', - '/api/public/', - ]; - - return skipAuthUrls.some((skipUrl) => url.includes(skipUrl)); -} - -/** - * Check if this is a refresh token request - */ -function isRefreshTokenRequest(url: string): boolean { - return url.includes('/api/auth/refresh'); -} diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index fb8b561..d08258a 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -56,12 +56,12 @@ function handleTokenRefresh(authService: AuthService, req: any, next: any): Obse return throwError(() => new Error('توکنی یافت نشد')); } - return authService.refreshToken().pipe( + return authService.doRefreshToken().pipe( switchMap((response) => { isRefreshing.next(false); const newAuthReq = req.clone({ - headers: req.headers.set('Authorization', `Bearer ${response.token}`), + headers: req.headers.set('Authorization', `Bearer ${response.accessToken}`), }); return next(newAuthReq); diff --git a/src/app/core/models/stockBalance.ts b/src/app/core/models/stockBalance.ts deleted file mode 100644 index 9c09773..0000000 --- a/src/app/core/models/stockBalance.ts +++ /dev/null @@ -1 +0,0 @@ -export interface StockBalance {} diff --git a/src/app/core/models/user.model.ts b/src/app/core/models/user.model.ts index b5db7a3..b8d2c3c 100644 --- a/src/app/core/models/user.model.ts +++ b/src/app/core/models/user.model.ts @@ -1,9 +1,8 @@ export enum UserRole { ADMIN = 'admin', - SCHOOL = 'school', - TEACHER = 'teacher', - STUDENTS = 'students', - GRADER = 'grader', + PARTNER = 'partner', + POS = 'pos', + PROVIDER = 'provider', SUPERADMIN = 'superadmin', } @@ -32,12 +31,31 @@ export interface Permission { export interface IAuthResponse { // user: User; - token: string; - fullName: string; - mustChangePassword: boolean; - role: string; + accessToken: string; refreshToken: string; - expiresIn: number; + account: IAuthAccountResponse; +} + +export interface IAuthAccountResponse { + id: string; + username: string; + type: TRoles; + status: string; + created_at: string; + user_id: string; + partner_id?: string; + business_id?: string; + provider_id?: string; + pos_id?: string; + user: IAuthAccountUser; +} +interface IAuthAccountUser { + id: string; + created_at: string; + mobile_number: string; + national_code: string; + first_name: string; + last_name: string; } export interface LoginCredentials { diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index c39084d..74b4768 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -9,336 +9,301 @@ import { BehaviorSubject, Observable, throwError } from 'rxjs'; import { catchError, finalize, tap } from 'rxjs/operators'; import { LOCAL_STORAGE_KEYS } from '../../../assets/constants'; import { - IAuthResponse, - ISignupRequestPayload, - IUserLoginInfo, - LoginCredentials, - Maybe, - TRoles, - User, + IAuthAccountResponse, + IAuthResponse, + ISignupRequestPayload, + IUserLoginInfo, + LoginCredentials, + Maybe, + TRoles, } from '../models'; import { CaptchaService } from './captcha.service'; @Injectable({ - providedIn: 'root', + providedIn: 'root', }) export class AuthService { - constructor() { - this.initializeAuth(); + constructor() { + this.initializeAuth(); + } + + readonly modulesLoginRoutes = { + // ADMIN: ADMIN_API_ROUTES.login(), + // SCHOOL: SCHOOLS_API_ROUTES.login(), + // TEACHER: TEACHERS_API_ROUTES.login(), + } as Record; + + readonly modulesSignupRoutes = { + // TEACHER: TEACHERS_API_ROUTES.signup(), + } as Record; + + readonly modulesGetInfoRoutes = { + // SCHOOL: SCHOOLS_API_ROUTES.me(), + } as Record; + + readonly modulesChangeInfoRoutes = { + // SCHOOL: SCHOOLS_API_ROUTES.editLoginInfo(), + } as Record; + + private readonly http = inject(HttpClient); + private readonly router = inject(Router); + private captchaService = inject(CaptchaService); + + private readonly currentAccountSubject = new BehaviorSubject>(null); + private readonly isLoadingSubject = new BehaviorSubject(false); + + readonly currentUser$ = this.currentAccountSubject.asObservable(); + readonly isLoading$ = this.isLoadingSubject.asObservable(); + + // Signals for reactive state management + // readonly currentUser = signal>(null); + readonly currentAccount = signal>(null); + readonly isLoading = signal(false); + readonly token = signal>(null); + readonly refreshToken = signal>(null); + readonly isAuthenticated = computed(() => { + return Boolean(this.token()); + }); + readonly userRole = computed(() => this.currentAccount()?.type); + + private initializeAuth(): void { + const token = localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); + + const accountData = localStorage.getItem(LOCAL_STORAGE_KEYS.USER_DATA); + + if (token && accountData) { + try { + this.token.set(token); + const account = JSON.parse(accountData) as IAuthAccountResponse; + this.setCurrentUser(account); + } catch (error) { + console.error('Error parsing stored user data:', error); + this.logout(); + } + } + } + + login(credentials: LoginCredentials): Observable { + const { username, password, captcha } = credentials; + this.isLoading.set(true); + this.isLoadingSubject.next(true); + const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha); + + return this.http + .post( + '/api/v1/auth/login', + { username, password }, + { + headers: baseHeaders, + }, + ) + .pipe( + tap((response) => { + this.handleAuthSuccess(response); + return response; + }), + catchError((error) => { + console.error('Login error:', error); + this.isLoadingSubject.next(false); + throw error; + }), + finalize(() => { + this.isLoading.set(false); + }), + ); + } + + signup(credentials: ISignupRequestPayload, role: TRoles): Observable { + this.isLoading.set(true); + this.isLoadingSubject.next(true); + // const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha); + + return this.http + .post( + this.modulesSignupRoutes[role], + credentials, + // { + // headers: baseHeaders, + // }, + ) + .pipe( + tap((response) => { + this.handleAuthSuccess(response); + return response; + }), + catchError((error) => { + console.error('Signup error:', error); + this.isLoadingSubject.next(false); + throw error; + }), + finalize(() => { + this.isLoading.set(false); + }), + ); + } + + logout(): void { + localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); + localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); + localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); + + this.setCurrentUser(null); + this.router.navigate(['/auth']); + } + + doRefreshToken(): Observable { + const refreshToken = this.getRefreshToken(); + if (!refreshToken) { + this.logout(); + return throwError(() => new Error('No refresh token available')); } - readonly modulesLoginRoutes = { - // ADMIN: ADMIN_API_ROUTES.login(), - // SCHOOL: SCHOOLS_API_ROUTES.login(), - // TEACHER: TEACHERS_API_ROUTES.login(), - } as Record; - - readonly modulesSignupRoutes = { - // TEACHER: TEACHERS_API_ROUTES.signup(), - } as Record; - - readonly modulesGetInfoRoutes = { - // SCHOOL: SCHOOLS_API_ROUTES.me(), - } as Record; - - readonly modulesChangeInfoRoutes = { - // SCHOOL: SCHOOLS_API_ROUTES.editLoginInfo(), - } as Record; - - private readonly http = inject(HttpClient); - private readonly router = inject(Router); - private captchaService = inject(CaptchaService); - - private readonly currentUserSubject = new BehaviorSubject>( - null, + return this.http.post('/api/auth/refresh', { refreshToken }).pipe( + tap((response) => { + this.handleAuthSuccess(response); + }), + catchError((error) => { + console.error('Token refresh error:', error); + this.logout(); + return throwError(() => error); + }), ); - private readonly isLoadingSubject = new BehaviorSubject(false); + } - readonly currentUser$ = this.currentUserSubject.asObservable(); - readonly isLoading$ = this.isLoadingSubject.asObservable(); - - // Signals for reactive state management - // readonly currentUser = signal>(null); - readonly currentUser = signal>(null); - readonly isLoading = signal(false); - readonly token = signal>(null); - readonly isAuthenticated = computed(() => { - return Boolean(this.token()); - }); - readonly userRole = computed(() => this.currentUser()?.role); - - private initializeAuth(): void { - const token = localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); - - const userData = localStorage.getItem(LOCAL_STORAGE_KEYS.USER_DATA); - - if (token && userData) { - try { - this.token.set(token); - const user = JSON.parse(userData) as User; - this.setCurrentUser(user); - } catch (error) { - console.error('Error parsing stored user data:', error); - this.logout(); - } - } - } - - login( - credentials: LoginCredentials, - role: TRoles, - ): Observable { - const { username, password, captcha } = credentials; - this.isLoading.set(true); - this.isLoadingSubject.next(true); - const baseHeaders = this.captchaService.buildCaptchaHeaders( - {}, - captcha, - ); - - return this.http - .post( - this.modulesLoginRoutes[role], - { username, password }, - { - headers: baseHeaders, - }, - ) - .pipe( - tap((response) => { - this.handleAuthSuccess(response); - return response; - }), - catchError((error) => { - console.error('Login error:', error); - this.isLoadingSubject.next(false); - throw error; - }), - finalize(() => { - this.isLoading.set(false); - }), - ); - } - - signup( - credentials: ISignupRequestPayload, - role: TRoles, - ): Observable { - this.isLoading.set(true); - this.isLoadingSubject.next(true); - // const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha); - - return this.http - .post( - this.modulesSignupRoutes[role], - credentials, - // { - // headers: baseHeaders, - // }, - ) - .pipe( - tap((response) => { - this.handleAuthSuccess(response); - return response; - }), - catchError((error) => { - console.error('Signup error:', error); - this.isLoadingSubject.next(false); - throw error; - }), - finalize(() => { - this.isLoading.set(false); - }), - ); - } - - logout(): void { - localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); - localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); - localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); - - this.setCurrentUser(null); - this.router.navigate(['/auth']); - } - - refreshToken(): Observable { - const refreshToken = this.getRefreshToken(); - if (!refreshToken) { - this.logout(); - return throwError(() => new Error('No refresh token available')); - } - - return this.http - .post('/api/auth/refresh', { refreshToken }) - .pipe( - tap((response) => { - this.handleAuthSuccess(response); - }), - catchError((error) => { - console.error('Token refresh error:', error); - this.logout(); - return throwError(() => error); - }), - ); - } - - getInfo(role: TRoles): Observable> { - // if (this.modulesGetInfoRoutes[role]) { - // return this.http.get(this.modulesGetInfoRoutes[role]).pipe( - // map((data) => { - // switch (role) { - // case 'SCHOOL': - // return this.prepareUserInfoBasedOnRole(role, data as ISchoolMeResponse); - // default: - // return data as Partial; - // } - // }), - // catchError((err) => { - // console.error('GetInfo error:', err); - // return throwError(() => err); - // }), - // ); - // } - - return throwError(() => new Error('متاسفانه مشکلی پیش آمده')); - } - changeInfo( - credentials: IUserLoginInfo, - role: TRoles, - ): Observable { - return this.http.post( - this.modulesChangeInfoRoutes[role], - credentials, - ); - } - - // prepareUserInfoBasedOnRole(role: TRoles, info: ISchoolMeResponse): Partial { - // if (role === 'SCHOOL') { - // return { - // username: info.username, - // email: info.managerInfo.email, - // mobile: info.managerInfo.mobile, - // }; - // } - // return {}; + getInfo(role: TRoles): Observable> { + // if (this.modulesGetInfoRoutes[role]) { + // return this.http.get(this.modulesGetInfoRoutes[role]).pipe( + // map((data) => { + // switch (role) { + // case 'SCHOOL': + // return this.prepareUserInfoBasedOnRole(role, data as ISchoolMeResponse); + // default: + // return data as Partial; + // } + // }), + // catchError((err) => { + // console.error('GetInfo error:', err); + // return throwError(() => err); + // }), + // ); // } - /** - * Get current access token - */ - getToken(): Maybe { - return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); + return throwError(() => new Error('متاسفانه مشکلی پیش آمده')); + } + changeInfo(credentials: IUserLoginInfo, role: TRoles): Observable { + return this.http.post(this.modulesChangeInfoRoutes[role], credentials); + } + + // prepareUserInfoBasedOnRole(role: TRoles, info: ISchoolMeResponse): Partial { + // if (role === 'SCHOOL') { + // return { + // username: info.username, + // email: info.managerInfo.email, + // mobile: info.managerInfo.mobile, + // }; + // } + // return {}; + // } + + /** + * Get current access token + */ + getToken(): Maybe { + return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); + } + + /** + * Get current refresh token + */ + getRefreshToken(): Maybe { + return localStorage.getItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); + } + + /** + * Check if token is expired (optional - requires JWT parsing) + */ + isTokenExpired(): boolean { + const token = this.getToken(); + if (!token) return true; + + try { + // Parse JWT token to check expiration + const payload = JSON.parse(atob(token.split('.')[1])); + const currentTime = Math.floor(Date.now() / 1000); + return payload.exp < currentTime; + } catch (error) { + console.error('Error parsing token:', error); + return true; + } + } + + /** + * Check if refresh token is expired + */ + isRefreshTokenExpired(): boolean { + const refreshToken = this.getRefreshToken(); + if (!refreshToken) return true; + + try { + // Parse JWT refresh token to check expiration + const payload = JSON.parse(atob(refreshToken.split('.')[1])); + const currentTime = Math.floor(Date.now() / 1000); + return payload.exp < currentTime; + } catch (error) { + console.error('Error parsing refresh token:', error); + return true; + } + } + + hasRole(role: TRoles): boolean { + return this.currentAccount()?.type === role; + } + + hasAnyRole(roles: TRoles[]): boolean { + const currentRole = this.currentAccount()?.type; + return currentRole ? roles.includes(currentRole) : false; + } + + hasPermission(permission: string): boolean { + const user = this.currentAccount(); + if (!user) return false; + + return true; + // return user.permissions.some( + // (p) => p.name === permission || `${p.resource}:${p.action}` === permission, + // ); + } + + canAccess(requiredRoles?: TRoles[], requiredPermissions?: string[]): boolean { + if (!this.isAuthenticated()) return false; + + if (requiredRoles && requiredRoles.length > 0) { + if (!this.hasAnyRole(requiredRoles)) return false; } - /** - * Get current refresh token - */ - getRefreshToken(): Maybe { - return localStorage.getItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); + if (requiredPermissions && requiredPermissions.length > 0) { + return requiredPermissions.every((permission) => this.hasPermission(permission)); } - /** - * Check if token is expired (optional - requires JWT parsing) - */ - isTokenExpired(): boolean { - const token = this.getToken(); - if (!token) return true; + return true; + } - try { - // Parse JWT token to check expiration - const payload = JSON.parse(atob(token.split('.')[1])); - const currentTime = Math.floor(Date.now() / 1000); - return payload.exp < currentTime; - } catch (error) { - console.error('Error parsing token:', error); - return true; - } - } + private handleAuthSuccess(response: IAuthResponse): void { + localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.accessToken); + this.token.set(response.accessToken); + this.refreshToken.set(response.refreshToken); + localStorage.setItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN, response.refreshToken); - /** - * Check if refresh token is expired - */ - isRefreshTokenExpired(): boolean { - const refreshToken = this.getRefreshToken(); - if (!refreshToken) return true; + this.setCurrentUser(response.account); + this.isLoadingSubject.next(false); - try { - // Parse JWT refresh token to check expiration - const payload = JSON.parse(atob(refreshToken.split('.')[1])); - const currentTime = Math.floor(Date.now() / 1000); - return payload.exp < currentTime; - } catch (error) { - console.error('Error parsing refresh token:', error); - return true; - } - } + // Navigate based on user role + // this.navigateByRole(response.user.role); + } - hasRole(role: TRoles): boolean { - return this.currentUser()?.role === role; - } - - hasAnyRole(roles: TRoles[]): boolean { - const currentRole = this.currentUser()?.role; - return currentRole ? roles.includes(currentRole) : false; - } - - hasPermission(permission: string): boolean { - const user = this.currentUser(); - if (!user) return false; - - return true; - // return user.permissions.some( - // (p) => p.name === permission || `${p.resource}:${p.action}` === permission, - // ); - } - - canAccess( - requiredRoles?: TRoles[], - requiredPermissions?: string[], - ): boolean { - if (!this.isAuthenticated()) return false; - - if (requiredRoles && requiredRoles.length > 0) { - if (!this.hasAnyRole(requiredRoles)) return false; - } - - if (requiredPermissions && requiredPermissions.length > 0) { - return requiredPermissions.every((permission) => - this.hasPermission(permission), - ); - } - - return true; - } - - private handleAuthSuccess(response: IAuthResponse): void { - localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.token); - this.token.set(response.token); - // localStorage.setItem('refresh_token', response.refreshToken); - const user = { - fullName: response.fullName, - role: response.role as TRoles, - }; - - localStorage.setItem( - LOCAL_STORAGE_KEYS.USER_DATA, - JSON.stringify(user), - ); - - this.setCurrentUser(user); - // this.isLoading.set(false); - this.currentUser.set({ - fullName: response.fullName, - role: response.role as TRoles, - }); - this.isLoadingSubject.next(false); - - // Navigate based on user role - // this.navigateByRole(response.user.role); - } - - private setCurrentUser(user: Maybe): void { - this.currentUser.set(user); - this.currentUserSubject.next(user); - } + private setCurrentUser(account: Maybe): void { + this.currentAccount.set(account); + this.currentAccountSubject.next(account); + localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(account)); + } } diff --git a/src/app/core/state/base-store.ts b/src/app/core/state/base-store.ts index 5f0e1f2..580cef0 100644 --- a/src/app/core/state/base-store.ts +++ b/src/app/core/state/base-store.ts @@ -6,13 +6,20 @@ import { IPaginatedQuery, IResponseMetadata } from '../models/service.model'; /** * Base interface for all state objects */ -export interface BaseState { +export interface BaseState { loading: boolean; error: Maybe; isRefreshing?: boolean; initialized: boolean; } +/** + * Base interface for entity + */ +export interface EntityState extends BaseState { + entity: Maybe; +} + /** * Base interface for paginated lists */ @@ -29,7 +36,7 @@ export interface PaginatedState extends BaseState { /** * Base interface for entity states (CRUD operations) */ -export interface EntityState extends BaseState { +export interface EntitiesState extends BaseState { entities: Record; selectedId: Maybe; ids: (string | number)[]; @@ -158,11 +165,31 @@ export abstract class BaseStore { } /** - * Entity store with CRUD operations + * Entity store */ export abstract class EntityStore< T, TState extends EntityState = EntityState, +> extends BaseStore { + // Computed selectors + readonly entity = computed(() => this._state().entity); + + /** + * Set entity + */ + setEntity(entity: Maybe): void { + this.patchState({ + entity, + } as Partial); + } +} + +/** + * Entity store with CRUD operations + */ +export abstract class EntitiesStore< + T, + TState extends EntitiesState = EntitiesState, > extends BaseStore { // Computed selectors readonly entities = computed(() => this._state().entities); diff --git a/src/app/core/state/index.ts b/src/app/core/state/index.ts index 94bfd65..93abf24 100644 --- a/src/app/core/state/index.ts +++ b/src/app/core/state/index.ts @@ -4,13 +4,14 @@ export * from './base-store'; // Global state management export * from './global/global.store'; -// State effects and utilities -export * from './state-effects.service'; +// // State effects and utilities +// export * from './state-effects.service'; // State management interfaces export type { Action, BaseState, + EntitiesState, EntityState, LoadingState, PaginatedState, diff --git a/src/app/core/state/state-effects.service.ts b/src/app/core/state/state-effects.service.ts deleted file mode 100644 index 5aecc05..0000000 --- a/src/app/core/state/state-effects.service.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { Injectable, inject } from '@angular/core'; -import { combineLatest, map, shareReplay, startWith } from 'rxjs'; -import { LOCAL_STORAGE_KEYS } from 'src/assets/constants'; -import { AuthStore } from '../../modules/auth/state/auth.store'; -import { GlobalStore } from './global/global.store'; - -/** - * State effects service for handling cross-store interactions - */ -@Injectable({ - providedIn: 'root', -}) -export class StateEffects { - private readonly authStore = inject(AuthStore); - private readonly globalStore = inject(GlobalStore); - - constructor() { - this.initializeEffects(); - } - - /** - * Initialize all effects - */ - private initializeEffects(): void { - this.setupAuthEffects(); - this.setupGlobalEffects(); - } - - /** - * Setup authentication-related effects - */ - private setupAuthEffects(): void { - // Update global notifications when auth state changes - this.authStore.state$.subscribe((authState) => { - // if (authState.isAuthenticated && authState.user) { - // this.globalStore.addNotification({ - // type: 'success', - // title: 'خوش آمدید', - // message: `${authState.user.firstName} ${authState.user.lastName} عزیز، خوش آمدید`, - // }); - // } - }); - - // Handle session expiry warnings - this.authStore.state$.subscribe((authState) => { - if (authState.sessionExpiry) { - const timeUntilExpiry = authState.sessionExpiry - Date.now(); - const minutesUntilExpiry = Math.floor(timeUntilExpiry / 60000); - - // Show warning when 5 minutes remain - if (minutesUntilExpiry === 5) { - this.globalStore.addNotification({ - type: 'warning', - title: 'هشدار انقضای جلسه', - message: 'جلسه شما در 5 دقیقه منقضی می‌شود', - actions: [ - { - label: 'تمدید جلسه', - action: () => this.authStore.refreshAuthToken().subscribe(), - type: 'primary', - }, - ], - }); - } - } - }); - } - - /** - * Setup global effects - */ - private setupGlobalEffects(): void { - // Auto-save preferences when they change - this.globalStore.state$.subscribe((globalState) => { - localStorage.setItem('user_preferences', JSON.stringify(globalState.preferences)); - }); - - // Handle theme changes - this.globalStore.state$.subscribe((globalState) => { - document.documentElement.setAttribute('data-theme', globalState.theme); - }); - - // Handle language changes - this.globalStore.state$.subscribe((globalState) => { - document.documentElement.setAttribute('lang', globalState.language); - document.documentElement.setAttribute('dir', globalState.language === 'fa' ? 'rtl' : 'ltr'); - }); - } -} - -/** - * State selectors for complex cross-store queries - */ -@Injectable({ - providedIn: 'root', -}) -export class StateSelectors { - private readonly authStore = inject(AuthStore); - private readonly globalStore = inject(GlobalStore); - - /** - * Combined application state for UI components - */ - readonly appState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe( - map(([authState, globalState]) => ({ - isAuthenticated: authState.isAuthenticated, - user: authState.user, - userRole: authState.user?.role, - loading: authState.loading || globalState.loading, - sidebarCollapsed: globalState.sidebarCollapsed, - theme: globalState.theme, - language: globalState.language, - notifications: globalState.notifications, - unreadCount: globalState.notifications.filter((n) => !n.read).length, - breadcrumbs: globalState.breadcrumbs, - isOnline: globalState.isOnline, - })), - shareReplay(1), - ); - - /** - * User permissions combined with role-based access - */ - readonly userAccess$ = this.authStore.state$.pipe( - map((authState) => ({ - isAuthenticated: authState.isAuthenticated, - role: authState.user?.role, - permissions: authState.permissions, - canAccessAdmin: authState.user?.role === 'ADMIN' || authState.user?.role === 'SUPERADMIN', - canAccessStudent: authState.user?.role === 'STUDENTS', - canAccessGrader: authState.user?.role === 'GRADER', - canAccessSchools: authState.user?.role === 'SCHOOL', - canAccessSuperAdmin: authState.user?.role === 'SUPERADMIN', - })), - shareReplay(1), - ); - - /** - * Navigation state for header/sidebar components - */ - readonly navigationState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe( - map(([authState, globalState]) => ({ - user: authState.user, - sidebarCollapsed: globalState.sidebarCollapsed, - breadcrumbs: globalState.breadcrumbs, - // userInitials: authState.user - // ? `${authState.user.firstName[0]}${authState.user.lastName[0]}` - // : '', - // userFullName: authState.user ? `${authState.user.firstName} ${authState.user.lastName}` : '', - })), - shareReplay(1), - ); - - /** - * Notification state for notification components - */ - readonly notificationState$ = this.globalStore.state$.pipe( - map((globalState) => ({ - notifications: globalState.notifications, - unreadCount: globalState.notifications.filter((n) => !n.read).length, - hasUnread: globalState.notifications.some((n) => !n.read), - recentNotifications: globalState.notifications - .sort((a, b) => b.timestamp - a.timestamp) - .slice(0, 5), - })), - shareReplay(1), - ); - - /** - * Loading state across all stores - */ - readonly loadingState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe( - map(([authState, globalState]) => ({ - isLoading: authState.loading || globalState.loading, - authLoading: authState.loading, - globalLoading: globalState.loading, - })), - startWith({ isLoading: false, authLoading: false, globalLoading: false }), - shareReplay(1), - ); -} - -/** - * State utilities for common operations - */ -@Injectable({ - providedIn: 'root', -}) -export class StateUtils { - private readonly authStore = inject(AuthStore); - private readonly globalStore = inject(GlobalStore); - - /** - * Check if user can access route - */ - canAccessRoute(requiredRoles?: string[], requiredPermissions?: string[]): boolean { - const authState = this.authStore.getCurrentState(); - - if (!authState.isAuthenticated) { - return false; - } - - if (requiredRoles && requiredRoles.length > 0) { - const userRole = authState.user?.role; - if (!userRole || !requiredRoles.includes(userRole)) { - return false; - } - } - - if (requiredPermissions && requiredPermissions.length > 0) { - const hasAllPermissions = requiredPermissions.every((permission) => - authState.permissions.includes(permission), - ); - if (!hasAllPermissions) { - return false; - } - } - - return true; - } - - /** - * Show success message - */ - showSuccess(title: string, message: string): void { - this.globalStore.addNotification({ - type: 'success', - title, - message, - }); - } - - /** - * Show error message - */ - showError(title: string, message: string): void { - this.globalStore.addNotification({ - type: 'error', - title, - message, - }); - } - - /** - * Show warning message - */ - showWarning(title: string, message: string): void { - this.globalStore.addNotification({ - type: 'warning', - title, - message, - }); - } - - /** - * Show info message - */ - showInfo(title: string, message: string): void { - this.globalStore.addNotification({ - type: 'info', - title, - message, - }); - } - - /** - * Update page breadcrumbs - */ - setBreadcrumbs(breadcrumbs: Array<{ label: string; url?: string; icon?: string }>): void { - this.globalStore.setBreadcrumbs(breadcrumbs); - } - - /** - * Get user display name - */ - getUserDisplayName(): string { - const user = this.authStore.getCurrentState().user; - return ''; - // return user ? `${user.firstName} ${user.lastName}` : ''; - } - - /** - * Get user initials - */ - getUserInitials(): string { - const user = this.authStore.getCurrentState().user; - return ''; - // return user ? `${user.firstName[0]}${user.lastName[0]}` : ''; - } - - /** - * Check if user is online - */ - isOnline(): boolean { - return this.globalStore.getCurrentState().isOnline; - } - - /** - * Get current theme - */ - getCurrentTheme(): 'light' | 'dark' { - return this.globalStore.getCurrentState().theme; - } - - /** - * Get current language - */ - getCurrentLanguage(): 'fa' | 'en' { - return this.globalStore.getCurrentState().language; - } - - /** - * Clear all application state (useful for logout) - */ - clearAllState(): void { - this.authStore.reset(); - this.globalStore.reset(); - - // Clear localStorage - localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); - localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); - localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); - localStorage.removeItem(LOCAL_STORAGE_KEYS.LAST_LOGIN_TIME); - localStorage.removeItem(LOCAL_STORAGE_KEYS.LOGIN_ATTEMPTS); - } -} diff --git a/src/app/domains/superAdmin/guilds/components/categories/form.component.html b/src/app/domains/superAdmin/guilds/components/categories/form.component.html new file mode 100644 index 0000000..68dfbcb --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/categories/form.component.html @@ -0,0 +1,7 @@ + +
+ + + + +
diff --git a/src/app/domains/superAdmin/guilds/components/categories/form.component.ts b/src/app/domains/superAdmin/guilds/components/categories/form.component.ts new file mode 100644 index 0000000..e7d925f --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/categories/form.component.ts @@ -0,0 +1,35 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; + +import { IGoodCategoriesResponse, IGoodCategoryRequest } from '../../models'; +import { GuildGoodCategoriesService } from '../../services/goodCategories.service'; + +@Component({ + selector: 'admin-guild-good-categories-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class GuildGoodCategoryFormComponent extends AbstractFormDialog< + IGoodCategoryRequest, + IGoodCategoriesResponse +> { + @Input() guildId!: string; + @Input() categoryId?: string; + + private service = inject(GuildGoodCategoriesService); + + form = this.fb.group({ + name: [this.initialValues?.name || '', [Validators.required]], + }); + + override submitForm(payload: IGoodCategoryRequest) { + if (this.editMode) { + return this.service.update(this.guildId!, this.categoryId!, payload); + } + return this.service.create(this.guildId!, payload); + } +} diff --git a/src/app/domains/superAdmin/guilds/components/categories/list.component.html b/src/app/domains/superAdmin/guilds/components/categories/list.component.html new file mode 100644 index 0000000..841ca68 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/categories/list.component.html @@ -0,0 +1,25 @@ + + + diff --git a/src/app/domains/superAdmin/guilds/components/categories/list.component.ts b/src/app/domains/superAdmin/guilds/components/categories/list.component.ts new file mode 100644 index 0000000..f0197ac --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/categories/list.component.ts @@ -0,0 +1,38 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject, Input } from '@angular/core'; +import { IGoodCategoriesResponse } from '../../models'; +import { GuildGoodCategoriesService } from '../../services/goodCategories.service'; +import { GuildGoodCategoryFormComponent } from './form.component'; + +@Component({ + selector: 'admin-guild-good-categories-list', + templateUrl: './list.component.html', + imports: [PageDataListComponent, GuildGoodCategoryFormComponent], +}) +export class GuildGoodCategoriesListComponent extends AbstractList { + @Input() guildId!: string; + @Input() fullHeight?: boolean; + @Input() header: IColumn[] = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'عنوان' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + service = inject(GuildGoodCategoriesService); + + override setColumns(): void { + this.columns = this.header; + } + + override getDataRequest() { + return this.service.getAll(this.guildId); + } +} diff --git a/src/app/domains/superAdmin/guilds/components/form.component.html b/src/app/domains/superAdmin/guilds/components/form.component.html new file mode 100644 index 0000000..cef6f46 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/form.component.html @@ -0,0 +1,9 @@ + +
+ + + + {{ editMode }} + + +
diff --git a/src/app/domains/superAdmin/guilds/components/form.component.ts b/src/app/domains/superAdmin/guilds/components/form.component.ts new file mode 100644 index 0000000..708d875 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/form.component.ts @@ -0,0 +1,68 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IGuildRequest, IGuildResponse } from '../models'; +import { GuildsService } from '../services/main.service'; + +interface T { + name: string; + code: string; +} + +@Component({ + selector: 'admin-guild-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class GuildFormComponent extends AbstractFormDialog { + @Input() guildId?: string; + private service = inject(GuildsService); + + form = this.fb.group({ + name: [ + { value: this.initialValues?.name, disabled: this.submitLoading() }, + Validators.required, + ], + code: [ + { value: this.initialValues?.code, disabled: this.submitLoading() }, + Validators.required, + ], + }); + + override ngOnChanges() { + this.form.patchValue(this.initialValues ?? {}); + if (this.editMode && !this.guildId) { + throw 'missing some arguments'; + } + } + + initForm() { + // const form = this.fb.group({ + // name: [ + // { value: this.initialValues?.name, disabled: !this.initialValues?.name }, + // Validators.required, + // ], + // code: [ + // { value: this.initialValues?.code, disabled: this.submitLoading() }, + // Validators.required, + // ], + // }); + // this.code = form.get('code')!.valueChanges.pipe( + // tap((code) => { + // const nameControl = form.get('name'); + // code === '1234' ? nameControl?.disable() : nameControl?.enable(); + // }), + // ); + // return form; + } + + override submitForm(payload: IGuildRequest) { + if (this.editMode) { + return this.service.update(this.guildId!, payload); + } + return this.service.create(payload); + } +} diff --git a/src/app/domains/superAdmin/guilds/components/goods/form.component.html b/src/app/domains/superAdmin/guilds/components/goods/form.component.html new file mode 100644 index 0000000..d31ca2c --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/goods/form.component.html @@ -0,0 +1,7 @@ + +
+ + + + +
diff --git a/src/app/domains/superAdmin/guilds/components/goods/form.component.ts b/src/app/domains/superAdmin/guilds/components/goods/form.component.ts new file mode 100644 index 0000000..f8a9c71 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/goods/form.component.ts @@ -0,0 +1,35 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; + +import { IGuildGoodRequest, IGuildGoodsResponse } from '../../models'; +import { GuildGoodsService } from '../../services/goods.service'; + +@Component({ + selector: 'admin-guild-good-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class GuildGoodCategoryFormComponent extends AbstractFormDialog< + IGuildGoodRequest, + IGuildGoodsResponse +> { + @Input() guildId!: string; + @Input() categoryId?: string; + + private service = inject(GuildGoodsService); + + form = this.fb.group({ + name: [this.initialValues?.name || '', [Validators.required]], + }); + + override submitForm(payload: IGuildGoodRequest) { + if (this.editMode) { + return this.service.update(this.guildId, this.categoryId!, payload); + } + return this.service.create(this.guildId, payload); + } +} diff --git a/src/app/domains/superAdmin/guilds/components/goods/list.component.html b/src/app/domains/superAdmin/guilds/components/goods/list.component.html new file mode 100644 index 0000000..ab908ea --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/goods/list.component.html @@ -0,0 +1,14 @@ + + diff --git a/src/app/domains/superAdmin/guilds/components/goods/list.component.ts b/src/app/domains/superAdmin/guilds/components/goods/list.component.ts new file mode 100644 index 0000000..6ec125a --- /dev/null +++ b/src/app/domains/superAdmin/guilds/components/goods/list.component.ts @@ -0,0 +1,40 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject, Input } from '@angular/core'; +import { IGuildGoodsResponse } from '../../models'; +import { GuildGoodsService } from '../../services/goods.service'; +import { GuildGoodCategoryFormComponent } from './form.component'; + +@Component({ + selector: 'admin-guild-goods-list', + templateUrl: './list.component.html', + imports: [PageDataListComponent, GuildGoodCategoryFormComponent], +}) +export class GuildGoodsListComponent extends AbstractList { + @Input() guildId!: string; + @Input() fullHeight?: boolean; + @Input() header: IColumn[] = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'عنوان' }, + { field: 'sku', header: 'شناسه‌ی عمومی' }, + { field: 'category', header: 'دسته‌بندی', type: 'nested', nestedPath: 'name' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + service = inject(GuildGoodsService); + + override setColumns(): void { + this.columns = this.header; + } + + override getDataRequest() { + return this.service.getAll(this.guildId); + } +} diff --git a/src/app/domains/superAdmin/guilds/constants/apiRoutes/goodCategories.ts b/src/app/domains/superAdmin/guilds/constants/apiRoutes/goodCategories.ts new file mode 100644 index 0000000..805dc00 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/apiRoutes/goodCategories.ts @@ -0,0 +1,6 @@ +const baseUrl = (guildId: string) => `/api/v1/admin/guilds/${guildId}/good_categories`; + +export const GUILD_GOOD_CATEGORIES_API_ROUTES = { + list: (guildId: string) => `${baseUrl(guildId)}`, + single: (guildId: string, id: string) => `${baseUrl(guildId)}/${id}`, +}; diff --git a/src/app/domains/superAdmin/guilds/constants/apiRoutes/goods.ts b/src/app/domains/superAdmin/guilds/constants/apiRoutes/goods.ts new file mode 100644 index 0000000..f83f7fc --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/apiRoutes/goods.ts @@ -0,0 +1,6 @@ +const baseUrl = (guildId: string) => `/api/v1/admin/guilds/${guildId}/goods`; + +export const GUILD_GOODS_API_ROUTES = { + list: (guildId: string) => `${baseUrl(guildId)}`, + single: (guildId: string, id: string) => `${baseUrl(guildId)}/${id}`, +}; diff --git a/src/app/domains/superAdmin/guilds/constants/apiRoutes/index.ts b/src/app/domains/superAdmin/guilds/constants/apiRoutes/index.ts new file mode 100644 index 0000000..0c01883 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/admin/guilds'; + +export const GUILDS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/modules/users/constants/index.ts b/src/app/domains/superAdmin/guilds/constants/index.ts similarity index 100% rename from src/app/modules/users/constants/index.ts rename to src/app/domains/superAdmin/guilds/constants/index.ts diff --git a/src/app/domains/superAdmin/guilds/constants/routes/goodCategories.ts b/src/app/domains/superAdmin/guilds/constants/routes/goodCategories.ts new file mode 100644 index 0000000..c05b2f4 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/routes/goodCategories.ts @@ -0,0 +1,26 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TGuildGoodCategoriesRouteNames = 'goodCategories'; + +export const guildGoodCategoriesNamedRoutes: NamedRoutes = { + goodCategories: { + path: 'good_categories', + loadComponent: () => + import('../../views/categories/list.component').then((m) => m.GuildGoodCategoriesComponent), + // @ts-ignore + meta: { + title: 'دسته‌بندی‌های کالا', + }, + }, + // goodCategory: { + // path: 'good_categories/:categoryId', + // loadComponent: () => import('../../views/categories/').then((m) => m.GuildComponent), + // // @ts-ignore + // meta: { + // title: 'دسته‌بندی‌ کالا', + // }, + // }, +}; + +export const GUILD_GOOD_CATEGORIES_ROUTES: Routes = Object.values(guildGoodCategoriesNamedRoutes); diff --git a/src/app/domains/superAdmin/guilds/constants/routes/goods.ts b/src/app/domains/superAdmin/guilds/constants/routes/goods.ts new file mode 100644 index 0000000..26848a8 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/routes/goods.ts @@ -0,0 +1,26 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TGuildGoodsRouteNames = 'goods'; + +export const guildGoodsNamedRoutes: NamedRoutes = { + goods: { + path: 'goods', + loadComponent: () => + import('../../views/goods/list.component').then((m) => m.GuildGoodsComponent), + // @ts-ignore + meta: { + title: 'کالاها', + }, + }, + // goodCategory: { + // path: 'good_categories/:categoryId', + // loadComponent: () => import('../../views/categories/').then((m) => m.GuildComponent), + // // @ts-ignore + // meta: { + // title: 'دسته‌بندی‌ کالا', + // }, + // }, +}; + +export const GUILD_GOODS_ROUTES: Routes = Object.values(guildGoodsNamedRoutes); diff --git a/src/app/domains/superAdmin/guilds/constants/routes/index.ts b/src/app/domains/superAdmin/guilds/constants/routes/index.ts new file mode 100644 index 0000000..80d8027 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/constants/routes/index.ts @@ -0,0 +1,51 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; +import { GUILD_GOOD_CATEGORIES_ROUTES } from './goodCategories'; +import { GUILD_GOODS_ROUTES } from './goods'; + +export type TGuildsRouteNames = 'guilds' | 'guild'; + +export const guildsNamedRoutes: NamedRoutes = { + guilds: { + path: 'guilds', + loadComponent: () => import('../../views/list.component').then((m) => m.GuildsComponent), + meta: { + title: 'اصناف', + pagePath: () => '/super_admin/guilds', + }, + }, + guild: { + path: 'guilds/:guildId', + loadComponent: () => import('../../views/single.component').then((m) => m.GuildComponent), + // @ts-ignore + meta: { + title: 'صنف', + pagePath: (guildId: string) => `/super_admin/guilds/${guildId}`, + }, + }, +}; + +export const GUILDS_ROUTES: Routes = [ + guildsNamedRoutes.guilds, + { + path: 'guilds/:guildId', + + children: [ + { + path: '', + loadComponent: () => import('../../views/single.component').then((m) => m.GuildComponent), + }, + + ...GUILD_GOOD_CATEGORIES_ROUTES, + ...GUILD_GOODS_ROUTES, + // { + // path: 'good_categories/:categoryId', + // loadComponent: () => import('../../views/categories/').then((m) => m.GuildComponent), + // // @ts-ignore + // meta: { + // title: 'دسته‌بندی‌ کالا', + // }, + // }, + ], + }, +]; diff --git a/src/app/domains/superAdmin/guilds/models/goods_io.d.ts b/src/app/domains/superAdmin/guilds/models/goods_io.d.ts new file mode 100644 index 0000000..ffa8579 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/models/goods_io.d.ts @@ -0,0 +1,18 @@ +import ISummary from '@/core/models'; + +export interface IGuildGoodsRawResponse { + id: string; + name: string; + sku: string; + category: ISummary; + description?: string; + created_at: string; + updated_at: string; +} +export interface IGuildGoodsResponse extends IGuildGoodsRawResponse {} + +export interface IGuildGoodRequest { + name: string; + sku: string; + description?: string; +} diff --git a/src/app/domains/superAdmin/guilds/models/index.ts b/src/app/domains/superAdmin/guilds/models/index.ts new file mode 100644 index 0000000..5447473 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/models/index.ts @@ -0,0 +1,2 @@ +export * from './goods_io'; +export * from './io'; diff --git a/src/app/domains/superAdmin/guilds/models/io.d.ts b/src/app/domains/superAdmin/guilds/models/io.d.ts new file mode 100644 index 0000000..638964f --- /dev/null +++ b/src/app/domains/superAdmin/guilds/models/io.d.ts @@ -0,0 +1,23 @@ +export interface IGuildRawResponse { + id: string; + name: string; + code: string; + created_at: string; +} +export interface IGuildResponse extends IGuildRawResponse {} + +export interface IGuildRequest { + name: string; + code: string; +} + +export interface IGoodCategoriesRawResponse { + id: string; + name: string; + created_at: string; +} +export interface IGoodCategoriesResponse extends IGoodCategoriesRawResponse {} + +export interface IGoodCategoryRequest { + name: string; +} diff --git a/src/app/domains/superAdmin/guilds/services/goodCategories.service.ts b/src/app/domains/superAdmin/guilds/services/goodCategories.service.ts new file mode 100644 index 0000000..4dc66b8 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/services/goodCategories.service.ts @@ -0,0 +1,38 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { GUILD_GOOD_CATEGORIES_API_ROUTES } from '../constants/apiRoutes/goodCategories'; +import { + IGoodCategoriesRawResponse, + IGoodCategoriesResponse, + IGoodCategoryRequest, +} from '../models'; + +@Injectable({ providedIn: 'root' }) +export class GuildGoodCategoriesService { + constructor(private http: HttpClient) {} + + private apiRoutes = GUILD_GOOD_CATEGORIES_API_ROUTES; + + getAll(guildId: string): Observable> { + return this.http.get>( + this.apiRoutes.list(guildId), + ); + } + getSingle(guildId: string, id: string): Observable { + return this.http.get(this.apiRoutes.single(guildId, id)); + } + + create(guildId: string, data: IGoodCategoryRequest): Observable { + return this.http.post(this.apiRoutes.list(guildId), data); + } + + update( + guildId: string, + id: string, + data: IGoodCategoryRequest, + ): Observable { + return this.http.patch(this.apiRoutes.single(guildId, id), data); + } +} diff --git a/src/app/domains/superAdmin/guilds/services/goods.service.ts b/src/app/domains/superAdmin/guilds/services/goods.service.ts new file mode 100644 index 0000000..d697fa4 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/services/goods.service.ts @@ -0,0 +1,28 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { GUILD_GOODS_API_ROUTES } from '../constants/apiRoutes/goods'; +import { IGuildGoodRequest, IGuildGoodsRawResponse, IGuildGoodsResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class GuildGoodsService { + constructor(private http: HttpClient) {} + + private apiRoutes = GUILD_GOODS_API_ROUTES; + + getAll(guildId: string): Observable> { + return this.http.get>(this.apiRoutes.list(guildId)); + } + getSingle(guildId: string, id: string): Observable { + return this.http.get(this.apiRoutes.single(guildId, id)); + } + + create(guildId: string, data: IGuildGoodRequest): Observable { + return this.http.post(this.apiRoutes.list(guildId), data); + } + + update(guildId: string, id: string, data: IGuildGoodRequest): Observable { + return this.http.patch(this.apiRoutes.single(guildId, id), data); + } +} diff --git a/src/app/domains/superAdmin/guilds/services/main.service.ts b/src/app/domains/superAdmin/guilds/services/main.service.ts new file mode 100644 index 0000000..c895475 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/services/main.service.ts @@ -0,0 +1,28 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { GUILDS_API_ROUTES } from '../constants'; +import { IGuildRawResponse, IGuildRequest, IGuildResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class GuildsService { + constructor(private http: HttpClient) {} + + private apiRoutes = GUILDS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(id: string): Observable { + return this.http.get(this.apiRoutes.single(id)); + } + + create(data: IGuildRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } + + update(id: string, data: IGuildRequest): Observable { + return this.http.patch(this.apiRoutes.single(id), data); + } +} diff --git a/src/app/domains/superAdmin/guilds/store/guild.store.ts b/src/app/domains/superAdmin/guilds/store/guild.store.ts new file mode 100644 index 0000000..9156e2e --- /dev/null +++ b/src/app/domains/superAdmin/guilds/store/guild.store.ts @@ -0,0 +1,53 @@ +import { EntityState, EntityStore } from '@/core/state'; +import { inject, Injectable } from '@angular/core'; +import { catchError, finalize, throwError } from 'rxjs'; +import { IGuildResponse } from '../models'; +import { GuildsService } from '../services/main.service'; + +interface GuildState extends EntityState {} + +@Injectable({ + providedIn: 'root', +}) +export class GuildStore extends EntityStore { + private readonly service = inject(GuildsService); + constructor() { + super({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + }); + } + + getData(guildId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(guildId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError(() => { + this.patchState({ + error: '', + }); + return throwError(''); + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + }); + } + + override reset(): void { + this.setState({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + }); + } +} diff --git a/src/app/domains/superAdmin/guilds/views/categories/list.component.html b/src/app/domains/superAdmin/guilds/views/categories/list.component.html new file mode 100644 index 0000000..caa2c36 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/categories/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/superAdmin/guilds/views/categories/list.component.ts b/src/app/domains/superAdmin/guilds/views/categories/list.component.ts new file mode 100644 index 0000000..972db06 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/categories/list.component.ts @@ -0,0 +1,14 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { Component, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { GuildGoodCategoriesListComponent } from '../../components/categories/list.component'; + +@Component({ + selector: 'admin-guild-good-categories', + templateUrl: './list.component.html', + imports: [GuildGoodCategoriesListComponent], +}) +export class GuildGoodCategoriesComponent { + route = inject(ActivatedRoute); + guildId = signal(this.route.snapshot.paramMap.get('guildId')!); +} diff --git a/src/app/domains/superAdmin/guilds/views/goods/list.component.html b/src/app/domains/superAdmin/guilds/views/goods/list.component.html new file mode 100644 index 0000000..7c26ef3 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/goods/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/superAdmin/guilds/views/goods/list.component.ts b/src/app/domains/superAdmin/guilds/views/goods/list.component.ts new file mode 100644 index 0000000..5812623 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/goods/list.component.ts @@ -0,0 +1,14 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { Component, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { GuildGoodsListComponent } from '../../components/goods/list.component'; + +@Component({ + selector: 'admin-guild_goods', + templateUrl: './list.component.html', + imports: [GuildGoodsListComponent], +}) +export class GuildGoodsComponent { + route = inject(ActivatedRoute); + guildId = signal(this.route.snapshot.paramMap.get('guildId')!); +} diff --git a/src/app/modules/users/views/index.ts b/src/app/domains/superAdmin/guilds/views/index.ts similarity index 100% rename from src/app/modules/users/views/index.ts rename to src/app/domains/superAdmin/guilds/views/index.ts diff --git a/src/app/domains/superAdmin/guilds/views/list.component.html b/src/app/domains/superAdmin/guilds/views/list.component.html new file mode 100644 index 0000000..cfafe3a --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/list.component.html @@ -0,0 +1,15 @@ + + diff --git a/src/app/domains/superAdmin/guilds/views/list.component.ts b/src/app/domains/superAdmin/guilds/views/list.component.ts new file mode 100644 index 0000000..b539564 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/list.component.ts @@ -0,0 +1,46 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { IPaginatedResponse } from '@/core/models/service.model'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { GuildFormComponent } from '../components/form.component'; +import { guildsNamedRoutes } from '../constants'; +import { IGuildResponse } from '../models'; +import { GuildsService } from '../services/main.service'; + +@Component({ + selector: 'admin-guilds', + templateUrl: './list.component.html', + imports: [PageDataListComponent, GuildFormComponent], +}) +export class GuildsComponent extends AbstractList { + private readonly service = inject(GuildsService); + private readonly router = inject(Router); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'عنوان' }, + { field: 'code', header: 'کد' }, + { field: 'businessActivitiesCount', header: 'تعداد فعالیت‌‌های اقتصادی‌ مرتبط' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ] as IColumn[]; + } + + override getDataRequest(): void | Observable> { + return this.service.getAll(); + } + + toSinglePage(guild: IGuildResponse) { + this.router.navigateByUrl(guildsNamedRoutes.guild.meta.pagePath!(guild.id)); + } +} diff --git a/src/app/domains/superAdmin/guilds/views/single.component.html b/src/app/domains/superAdmin/guilds/views/single.component.html new file mode 100644 index 0000000..c5d29aa --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/single.component.html @@ -0,0 +1,21 @@ +
+ +
+
+ + +
+
+
+ + + + + +
diff --git a/src/app/domains/superAdmin/guilds/views/single.component.ts b/src/app/domains/superAdmin/guilds/views/single.component.ts new file mode 100644 index 0000000..b03a447 --- /dev/null +++ b/src/app/domains/superAdmin/guilds/views/single.component.ts @@ -0,0 +1,37 @@ +import { AppCardComponent, KeyValueComponent } from '@/shared/components'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { GuildGoodCategoriesListComponent } from '../components/categories/list.component'; +import { GuildFormComponent } from '../components/form.component'; +import { GuildGoodsListComponent } from '../components/goods/list.component'; +import { GuildStore } from '../store/guild.store'; + +@Component({ + selector: 'admin-guild', + templateUrl: './single.component.html', + imports: [ + AppCardComponent, + KeyValueComponent, + GuildGoodCategoriesListComponent, + GuildGoodsListComponent, + GuildFormComponent, + ], +}) +export class GuildComponent { + private readonly store = inject(GuildStore); + private readonly route = inject(ActivatedRoute); + + readonly guildId = signal(this.route.snapshot.paramMap.get('guildId')!); + editMode = signal(false); + + readonly loading = computed(() => this.store.loading()); + readonly guild = computed(() => this.store.entity()); + + getData() { + this.store.getData(this.guildId()); + } + + ngOnInit() { + this.getData(); + } +} diff --git a/src/app/domains/superAdmin/licenses/components/form.component.html b/src/app/domains/superAdmin/licenses/components/form.component.html new file mode 100644 index 0000000..c79416b --- /dev/null +++ b/src/app/domains/superAdmin/licenses/components/form.component.html @@ -0,0 +1,13 @@ + +
+ + + +
diff --git a/src/app/domains/superAdmin/licenses/components/form.component.ts b/src/app/domains/superAdmin/licenses/components/form.component.ts new file mode 100644 index 0000000..de6f2f8 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/components/form.component.ts @@ -0,0 +1,25 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { ILicenseRequest, ILicenseResponse } from '../models'; +import { LicensesService } from '../services/main.service'; + +@Component({ + selector: 'license-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class LicenseFormComponent extends AbstractFormDialog { + private service = inject(LicensesService); + + form = this.fb.group({ + name: [this.initialValues?.name || '', [Validators.required]], + }); + + override submitForm(payload: ILicenseRequest) { + return this.service.create(payload); + } +} diff --git a/src/app/domains/superAdmin/licenses/constants/apiRoutes/index.ts b/src/app/domains/superAdmin/licenses/constants/apiRoutes/index.ts new file mode 100644 index 0000000..972e314 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/admin/partners'; + +export const LICENSES_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/shared/catalog/banks/constants/index.ts b/src/app/domains/superAdmin/licenses/constants/index.ts similarity index 52% rename from src/app/shared/catalog/banks/constants/index.ts rename to src/app/domains/superAdmin/licenses/constants/index.ts index b853783..ee61bd7 100644 --- a/src/app/shared/catalog/banks/constants/index.ts +++ b/src/app/domains/superAdmin/licenses/constants/index.ts @@ -1 +1,2 @@ export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/superAdmin/licenses/constants/routes/index.ts b/src/app/domains/superAdmin/licenses/constants/routes/index.ts new file mode 100644 index 0000000..a799774 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TLicensesRouteNames = 'licenses' | 'license'; + +export const licensesNamedRoutes: NamedRoutes = { + licenses: { + path: 'licenses', + loadComponent: () => import('../../views/list.component').then((m) => m.LicensesComponent), + meta: { + title: 'لایسنس‌ها', + pagePath: () => '/super_admin/licenses', + }, + }, + license: { + path: 'licenses/:licenseId', + loadComponent: () => import('../../views/single.component').then((m) => m.LicenseComponent), + meta: { + title: 'لایسنس', + pagePath: () => '/super_admin/licenses/:licenseId', + }, + }, +}; + +export const LICENSES_ROUTES: Routes = Object.values(licensesNamedRoutes); diff --git a/src/app/modules/users/models/index.ts b/src/app/domains/superAdmin/licenses/models/index.ts similarity index 100% rename from src/app/modules/users/models/index.ts rename to src/app/domains/superAdmin/licenses/models/index.ts diff --git a/src/app/domains/superAdmin/licenses/models/io.d.ts b/src/app/domains/superAdmin/licenses/models/io.d.ts new file mode 100644 index 0000000..6de799f --- /dev/null +++ b/src/app/domains/superAdmin/licenses/models/io.d.ts @@ -0,0 +1,9 @@ +export interface ILicenseRawResponse { + id: string; + name: string; +} +export interface ILicenseResponse extends ILicenseRawResponse {} + +export interface ILicenseRequest { + name: string; +} diff --git a/src/app/domains/superAdmin/licenses/services/main.service.ts b/src/app/domains/superAdmin/licenses/services/main.service.ts new file mode 100644 index 0000000..f876150 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/services/main.service.ts @@ -0,0 +1,28 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { LICENSES_API_ROUTES } from '../constants'; +import { ILicenseRawResponse, ILicenseRequest, ILicenseResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class LicensesService { + constructor(private http: HttpClient) {} + + private apiRoutes = LICENSES_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(userId: string): Observable { + return this.http.get(this.apiRoutes.single(userId)); + } + + create(userData: ILicenseRequest): Observable { + return this.http.post(this.apiRoutes.list(), userData); + } + + update(userId: string, userData: ILicenseRequest): Observable { + return this.http.patch(this.apiRoutes.single(userId), userData); + } +} diff --git a/src/app/domains/superAdmin/licenses/views/index.ts b/src/app/domains/superAdmin/licenses/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/superAdmin/licenses/views/list.component.html b/src/app/domains/superAdmin/licenses/views/list.component.html new file mode 100644 index 0000000..5552a1f --- /dev/null +++ b/src/app/domains/superAdmin/licenses/views/list.component.html @@ -0,0 +1,18 @@ + + + + diff --git a/src/app/domains/superAdmin/licenses/views/list.component.ts b/src/app/domains/superAdmin/licenses/views/list.component.ts new file mode 100644 index 0000000..0a16b28 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/views/list.component.ts @@ -0,0 +1,31 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { LicenseFormComponent } from '../components/form.component'; +import { ILicenseResponse } from '../models'; +import { LicensesService } from '../services/main.service'; + +@Component({ + selector: 'admin-licenses', + templateUrl: './list.component.html', + imports: [PageDataListComponent, LicenseFormComponent], +}) +export class LicensesComponent extends AbstractList { + private readonly service = inject(LicensesService); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } +} diff --git a/src/app/modules/users/views/single.component.html b/src/app/domains/superAdmin/licenses/views/single.component.html similarity index 100% rename from src/app/modules/users/views/single.component.html rename to src/app/domains/superAdmin/licenses/views/single.component.html diff --git a/src/app/domains/superAdmin/licenses/views/single.component.ts b/src/app/domains/superAdmin/licenses/views/single.component.ts new file mode 100644 index 0000000..c05caf2 --- /dev/null +++ b/src/app/domains/superAdmin/licenses/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'admin-license', + templateUrl: './single.component.html', +}) +export class LicenseComponent { + constructor() {} +} diff --git a/src/app/domains/superAdmin/partners/components/form.component.html b/src/app/domains/superAdmin/partners/components/form.component.html new file mode 100644 index 0000000..43eda4a --- /dev/null +++ b/src/app/domains/superAdmin/partners/components/form.component.html @@ -0,0 +1,14 @@ + +
+ + + + +
diff --git a/src/app/domains/superAdmin/partners/components/form.component.ts b/src/app/domains/superAdmin/partners/components/form.component.ts new file mode 100644 index 0000000..c6e1afb --- /dev/null +++ b/src/app/domains/superAdmin/partners/components/form.component.ts @@ -0,0 +1,32 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IPartnerRequest, IPartnerResponse } from '../models'; +import { PartnersService } from '../services/main.service'; + +@Component({ + selector: 'partner-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class GuildFormComponent extends AbstractFormDialog { + private service = inject(PartnersService); + + form = this.fb.group({ + name: this.fb.control(this.initialValues?.name || '', { + nonNullable: true, + validators: [Validators.required], + }), + code: this.fb.control(this.initialValues?.code || '', { + nonNullable: true, + validators: [Validators.required], + }), + }); + + override submitForm(payload: IPartnerRequest) { + return this.service.create(payload); + } +} diff --git a/src/app/domains/superAdmin/partners/constants/apiRoutes/index.ts b/src/app/domains/superAdmin/partners/constants/apiRoutes/index.ts new file mode 100644 index 0000000..ef2a53c --- /dev/null +++ b/src/app/domains/superAdmin/partners/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/admin/partners'; + +export const PARTNERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/shared/catalog/roles/constants/index.ts b/src/app/domains/superAdmin/partners/constants/index.ts similarity index 52% rename from src/app/shared/catalog/roles/constants/index.ts rename to src/app/domains/superAdmin/partners/constants/index.ts index b853783..ee61bd7 100644 --- a/src/app/shared/catalog/roles/constants/index.ts +++ b/src/app/domains/superAdmin/partners/constants/index.ts @@ -1 +1,2 @@ export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/superAdmin/partners/constants/routes/index.ts b/src/app/domains/superAdmin/partners/constants/routes/index.ts new file mode 100644 index 0000000..a2027d8 --- /dev/null +++ b/src/app/domains/superAdmin/partners/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPartnersRouteNames = 'partners' | 'partner'; + +export const partnersNamedRoutes: NamedRoutes = { + partners: { + path: 'partners', + loadComponent: () => import('../../views/list.component').then((m) => m.PartnersComponent), + meta: { + title: 'کاربران', + pagePath: () => '/super_admin/partners', + }, + }, + partner: { + path: 'partners/:partnerId', + loadComponent: () => import('../../views/single.component').then((m) => m.PartnerComponent), + meta: { + title: 'کاربر', + pagePath: () => '/super_admin/partners/:partnerId', + }, + }, +}; + +export const PARTNERS_ROUTES: Routes = Object.values(partnersNamedRoutes); diff --git a/src/app/shared/catalog/banks/models/index.ts b/src/app/domains/superAdmin/partners/models/index.ts similarity index 100% rename from src/app/shared/catalog/banks/models/index.ts rename to src/app/domains/superAdmin/partners/models/index.ts diff --git a/src/app/domains/superAdmin/partners/models/io.d.ts b/src/app/domains/superAdmin/partners/models/io.d.ts new file mode 100644 index 0000000..bdda44a --- /dev/null +++ b/src/app/domains/superAdmin/partners/models/io.d.ts @@ -0,0 +1,11 @@ +export interface IPartnerRawResponse { + id: string; + name: string; + code: string; +} +export interface IPartnerResponse extends IPartnerRawResponse {} + +export interface IPartnerRequest { + name: string; + code: string; +} diff --git a/src/app/domains/superAdmin/partners/services/main.service.ts b/src/app/domains/superAdmin/partners/services/main.service.ts new file mode 100644 index 0000000..7bb6b70 --- /dev/null +++ b/src/app/domains/superAdmin/partners/services/main.service.ts @@ -0,0 +1,28 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PARTNERS_API_ROUTES } from '../constants'; +import { IPartnerRawResponse, IPartnerRequest, IPartnerResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class PartnersService { + constructor(private http: HttpClient) {} + + private apiRoutes = PARTNERS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(userId: string): Observable { + return this.http.get(this.apiRoutes.single(userId)); + } + + create(userData: IPartnerRequest): Observable { + return this.http.post(this.apiRoutes.list(), userData); + } + + update(userId: string, userData: IPartnerRequest): Observable { + return this.http.patch(this.apiRoutes.single(userId), userData); + } +} diff --git a/src/app/domains/superAdmin/partners/shared/select.component.html b/src/app/domains/superAdmin/partners/shared/select.component.html new file mode 100644 index 0000000..998716c --- /dev/null +++ b/src/app/domains/superAdmin/partners/shared/select.component.html @@ -0,0 +1,30 @@ + + + @if (canInsert) { + +
+ +
+
+ } +
+ +
diff --git a/src/app/domains/superAdmin/partners/shared/select.component.ts b/src/app/domains/superAdmin/partners/shared/select.component.ts new file mode 100644 index 0000000..b8280a7 --- /dev/null +++ b/src/app/domains/superAdmin/partners/shared/select.component.ts @@ -0,0 +1,32 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { AbstractSelectComponent } from '@/shared/abstractClasses'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Button } from 'primeng/button'; +import { Select } from 'primeng/select'; +import { GuildFormComponent } from '../components/form.component'; +import { IPartnerResponse } from '../models'; +import { PartnersService } from '../services/main.service'; + +@Component({ + selector: 'app-partner-select', + templateUrl: './select.component.html', + imports: [UikitFieldComponent, Select, Button, ReactiveFormsModule, GuildFormComponent], +}) +export class InventoryBankAccountSelectComponent extends AbstractSelectComponent< + IPartnerResponse, + IPaginatedResponse +> { + @Input() override showClear: boolean = false; + @Input() label: string = ''; + private readonly service = inject(PartnersService); + + ngOnInit() { + this.getData(); + } + + getDataService() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/partners/views/index.ts b/src/app/domains/superAdmin/partners/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/superAdmin/partners/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/superAdmin/partners/views/list.component.html b/src/app/domains/superAdmin/partners/views/list.component.html new file mode 100644 index 0000000..df60cc1 --- /dev/null +++ b/src/app/domains/superAdmin/partners/views/list.component.html @@ -0,0 +1,18 @@ + + + + diff --git a/src/app/domains/superAdmin/partners/views/list.component.ts b/src/app/domains/superAdmin/partners/views/list.component.ts new file mode 100644 index 0000000..4410599 --- /dev/null +++ b/src/app/domains/superAdmin/partners/views/list.component.ts @@ -0,0 +1,33 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { GuildFormComponent } from '../components/form.component'; +import { IPartnerResponse } from '../models'; +import { PartnersService } from '../services/main.service'; + +@Component({ + selector: 'app-users', + templateUrl: './list.component.html', + imports: [PageDataListComponent, GuildFormComponent], +}) +export class PartnersComponent extends AbstractList { + private readonly service = inject(PartnersService); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'code', header: 'کد' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/partners/views/single.component.html b/src/app/domains/superAdmin/partners/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/domains/superAdmin/partners/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/domains/superAdmin/partners/views/single.component.ts b/src/app/domains/superAdmin/partners/views/single.component.ts new file mode 100644 index 0000000..074bbec --- /dev/null +++ b/src/app/domains/superAdmin/partners/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-user', + templateUrl: './single.component.html', +}) +export class PartnerComponent { + constructor() {} +} diff --git a/src/app/domains/superAdmin/providers/components/form.component.html b/src/app/domains/superAdmin/providers/components/form.component.html new file mode 100644 index 0000000..c79416b --- /dev/null +++ b/src/app/domains/superAdmin/providers/components/form.component.html @@ -0,0 +1,13 @@ + +
+ + + +
diff --git a/src/app/domains/superAdmin/providers/components/form.component.ts b/src/app/domains/superAdmin/providers/components/form.component.ts new file mode 100644 index 0000000..b6bf7f7 --- /dev/null +++ b/src/app/domains/superAdmin/providers/components/form.component.ts @@ -0,0 +1,25 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IProviderRequest, IProviderResponse } from '../models'; +import { ProvidersService } from '../services/main.service'; + +@Component({ + selector: 'provider-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class GuildFormComponent extends AbstractFormDialog { + private service = inject(ProvidersService); + + form = this.fb.group({ + name: [this.initialValues?.name || '', [Validators.required]], + }); + + override submitForm(payload: IProviderRequest) { + return this.service.create(payload); + } +} diff --git a/src/app/domains/superAdmin/providers/constants/apiRoutes/index.ts b/src/app/domains/superAdmin/providers/constants/apiRoutes/index.ts new file mode 100644 index 0000000..884a9ce --- /dev/null +++ b/src/app/domains/superAdmin/providers/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/admin/providers'; + +export const PROVIDERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/domains/superAdmin/providers/constants/index.ts b/src/app/domains/superAdmin/providers/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/superAdmin/providers/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/superAdmin/providers/constants/routes/index.ts b/src/app/domains/superAdmin/providers/constants/routes/index.ts new file mode 100644 index 0000000..89f86d1 --- /dev/null +++ b/src/app/domains/superAdmin/providers/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TProvidersRouteNames = 'providers' | 'provider'; + +export const providersNamedRoutes: NamedRoutes = { + providers: { + path: 'providers', + loadComponent: () => import('../../views/list.component').then((m) => m.PartnersComponent), + meta: { + title: 'ارایه‌دهندگان', + pagePath: () => '/super_admin/providers', + }, + }, + provider: { + path: 'providers/:providerId', + loadComponent: () => import('../../views/single.component').then((m) => m.ProviderComponent), + meta: { + title: 'ارایه‌دهنده', + pagePath: () => '/super_admin/providers/:providerId', + }, + }, +}; + +export const PROVIDERS_ROUTES: Routes = Object.values(providersNamedRoutes); diff --git a/src/app/shared/catalog/roles/models/index.ts b/src/app/domains/superAdmin/providers/models/index.ts similarity index 100% rename from src/app/shared/catalog/roles/models/index.ts rename to src/app/domains/superAdmin/providers/models/index.ts diff --git a/src/app/domains/superAdmin/providers/models/io.d.ts b/src/app/domains/superAdmin/providers/models/io.d.ts new file mode 100644 index 0000000..f848479 --- /dev/null +++ b/src/app/domains/superAdmin/providers/models/io.d.ts @@ -0,0 +1,9 @@ +export interface IProviderRawResponse { + id: string; + name: string; +} +export interface IProviderResponse extends IProviderRawResponse {} + +export interface IProviderRequest { + name: string; +} diff --git a/src/app/domains/superAdmin/providers/services/main.service.ts b/src/app/domains/superAdmin/providers/services/main.service.ts new file mode 100644 index 0000000..dc75d6f --- /dev/null +++ b/src/app/domains/superAdmin/providers/services/main.service.ts @@ -0,0 +1,28 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PROVIDERS_API_ROUTES } from '../constants'; +import { IProviderRawResponse, IProviderRequest, IProviderResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class ProvidersService { + constructor(private http: HttpClient) {} + + private apiRoutes = PROVIDERS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(userId: string): Observable { + return this.http.get(this.apiRoutes.single(userId)); + } + + create(userData: IProviderRequest): Observable { + return this.http.post(this.apiRoutes.list(), userData); + } + + update(userId: string, userData: IProviderRequest): Observable { + return this.http.patch(this.apiRoutes.single(userId), userData); + } +} diff --git a/src/app/domains/superAdmin/providers/shared/select.component.html b/src/app/domains/superAdmin/providers/shared/select.component.html new file mode 100644 index 0000000..2533a8e --- /dev/null +++ b/src/app/domains/superAdmin/providers/shared/select.component.html @@ -0,0 +1,30 @@ + + + @if (canInsert) { + +
+ +
+
+ } +
+ +
diff --git a/src/app/domains/superAdmin/providers/shared/select.component.ts b/src/app/domains/superAdmin/providers/shared/select.component.ts new file mode 100644 index 0000000..9e26b7a --- /dev/null +++ b/src/app/domains/superAdmin/providers/shared/select.component.ts @@ -0,0 +1,32 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { AbstractSelectComponent } from '@/shared/abstractClasses'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Button } from 'primeng/button'; +import { Select } from 'primeng/select'; +import { GuildFormComponent } from '../components/form.component'; +import { IProviderResponse } from '../models'; +import { ProvidersService } from '../services/main.service'; + +@Component({ + selector: 'app-provider-select', + templateUrl: './select.component.html', + imports: [UikitFieldComponent, Select, Button, ReactiveFormsModule, GuildFormComponent], +}) +export class InventoryBankAccountSelectComponent extends AbstractSelectComponent< + IProviderResponse, + IPaginatedResponse +> { + @Input() override showClear: boolean = false; + @Input() label: string = ''; + private readonly service = inject(ProvidersService); + + ngOnInit() { + this.getData(); + } + + getDataService() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/providers/views/index.ts b/src/app/domains/superAdmin/providers/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/superAdmin/providers/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/superAdmin/providers/views/list.component.html b/src/app/domains/superAdmin/providers/views/list.component.html new file mode 100644 index 0000000..5d15bb2 --- /dev/null +++ b/src/app/domains/superAdmin/providers/views/list.component.html @@ -0,0 +1,18 @@ + + + + diff --git a/src/app/domains/superAdmin/providers/views/list.component.ts b/src/app/domains/superAdmin/providers/views/list.component.ts new file mode 100644 index 0000000..0ba9bb8 --- /dev/null +++ b/src/app/domains/superAdmin/providers/views/list.component.ts @@ -0,0 +1,32 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { GuildFormComponent } from '../components/form.component'; +import { IProviderResponse } from '../models'; +import { ProvidersService } from '../services/main.service'; + +@Component({ + selector: 'app-users', + templateUrl: './list.component.html', + imports: [PageDataListComponent, GuildFormComponent], +}) +export class PartnersComponent extends AbstractList { + private readonly service = inject(ProvidersService); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/providers/views/single.component.html b/src/app/domains/superAdmin/providers/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/domains/superAdmin/providers/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/domains/superAdmin/providers/views/single.component.ts b/src/app/domains/superAdmin/providers/views/single.component.ts new file mode 100644 index 0000000..9a8277c --- /dev/null +++ b/src/app/domains/superAdmin/providers/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'admin-provider', + templateUrl: './single.component.html', +}) +export class ProviderComponent { + constructor() {} +} diff --git a/src/app/domains/superAdmin/routes.ts b/src/app/domains/superAdmin/routes.ts new file mode 100644 index 0000000..4c4a971 --- /dev/null +++ b/src/app/domains/superAdmin/routes.ts @@ -0,0 +1,18 @@ +import { Route } from '@angular/router'; +import { GUILDS_ROUTES } from './guilds/constants'; +import { LICENSES_ROUTES } from './licenses/constants'; +import { PARTNERS_ROUTES } from './partners/constants'; +import { PROVIDERS_ROUTES } from './providers/constants'; +import { USERS_ROUTES } from './users/constants'; + +export const SUPER_ADMIN_ROUTES = { + path: 'super_admin', + component: undefined, + children: [ + ...USERS_ROUTES, + ...GUILDS_ROUTES, + ...PARTNERS_ROUTES, + ...PROVIDERS_ROUTES, + ...LICENSES_ROUTES, + ], +} as Route; diff --git a/src/app/modules/users/components/form.component.html b/src/app/domains/superAdmin/users/components/form.component.html similarity index 87% rename from src/app/modules/users/components/form.component.html rename to src/app/domains/superAdmin/users/components/form.component.html index 1d42f5a..067eb25 100644 --- a/src/app/modules/users/components/form.component.html +++ b/src/app/domains/superAdmin/users/components/form.component.html @@ -1,9 +1,16 @@ - +
- + `${baseUrl}`, diff --git a/src/app/domains/superAdmin/users/constants/index.ts b/src/app/domains/superAdmin/users/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/superAdmin/users/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/users/constants/routes/index.ts b/src/app/domains/superAdmin/users/constants/routes/index.ts similarity index 86% rename from src/app/modules/users/constants/routes/index.ts rename to src/app/domains/superAdmin/users/constants/routes/index.ts index 14e9d75..450b429 100644 --- a/src/app/modules/users/constants/routes/index.ts +++ b/src/app/domains/superAdmin/users/constants/routes/index.ts @@ -9,7 +9,7 @@ export const usersNamedRoutes: NamedRoutes = { loadComponent: () => import('../../views/list.component').then((m) => m.UsersComponent), meta: { title: 'کاربران', - pagePath: () => '/users', + pagePath: () => '/super_admin/users', }, }, user: { @@ -17,7 +17,7 @@ export const usersNamedRoutes: NamedRoutes = { loadComponent: () => import('../../views/single.component').then((m) => m.UserComponent), meta: { title: 'کاربر', - pagePath: () => '/users/:userId', + pagePath: () => '/super_admin/users/:userId', }, }, }; diff --git a/src/app/domains/superAdmin/users/models/index.ts b/src/app/domains/superAdmin/users/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/superAdmin/users/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/users/models/io.d.ts b/src/app/domains/superAdmin/users/models/io.d.ts similarity index 100% rename from src/app/modules/users/models/io.d.ts rename to src/app/domains/superAdmin/users/models/io.d.ts diff --git a/src/app/modules/users/services/main.service.ts b/src/app/domains/superAdmin/users/services/main.service.ts similarity index 100% rename from src/app/modules/users/services/main.service.ts rename to src/app/domains/superAdmin/users/services/main.service.ts diff --git a/src/app/domains/superAdmin/users/views/index.ts b/src/app/domains/superAdmin/users/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/superAdmin/users/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/users/views/list.component.html b/src/app/domains/superAdmin/users/views/list.component.html similarity index 91% rename from src/app/modules/users/views/list.component.html rename to src/app/domains/superAdmin/users/views/list.component.html index 5e9afbd..0597bfb 100644 --- a/src/app/modules/users/views/list.component.html +++ b/src/app/domains/superAdmin/users/views/list.component.html @@ -11,8 +11,8 @@ [showAdd]="true" (onAdd)="openAddForm()" > - + diff --git a/src/app/domains/superAdmin/users/views/list.component.ts b/src/app/domains/superAdmin/users/views/list.component.ts new file mode 100644 index 0000000..7d96f73 --- /dev/null +++ b/src/app/domains/superAdmin/users/views/list.component.ts @@ -0,0 +1,34 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { UserFormComponent } from '../components/form.component'; +import { IUserResponse } from '../models'; +import { UsersService } from '../services/main.service'; + +@Component({ + selector: 'app-users', + templateUrl: './list.component.html', + imports: [PageDataListComponent, UserFormComponent], +}) +export class UsersComponent extends AbstractList { + private readonly service = inject(UsersService); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'fullname', header: 'نام' }, + { field: 'mobile_number', header: 'شماره موبایل' }, + { field: 'national_code', header: 'کد ملی' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/users/views/single.component.html b/src/app/domains/superAdmin/users/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/domains/superAdmin/users/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/users/views/single.component.ts b/src/app/domains/superAdmin/users/views/single.component.ts similarity index 100% rename from src/app/modules/users/views/single.component.ts rename to src/app/domains/superAdmin/users/views/single.component.ts diff --git a/src/app/layout/component/app.topbar.component.html b/src/app/layout/component/app.topbar.component.html index 7e7e58f..f411204 100644 --- a/src/app/layout/component/app.topbar.component.html +++ b/src/app/layout/component/app.topbar.component.html @@ -5,7 +5,7 @@ diff --git a/src/app/layout/component/app.topbar.component.ts b/src/app/layout/component/app.topbar.component.ts index adb7293..2dc4dc3 100644 --- a/src/app/layout/component/app.topbar.component.ts +++ b/src/app/layout/component/app.topbar.component.ts @@ -27,7 +27,7 @@ export class AppTopbar { this.layoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); } - username = computed(() => this.authService.currentUser()?.fullName || 'کاربر ناشناس'); + username = computed(() => this.authService.currentAccount()?.username || 'کاربر ناشناس'); logout = () => { this.authService.logout(); diff --git a/src/app/modules/auth/pages/auth.component.html b/src/app/modules/auth/pages/auth.component.html index a6eea8a..28a519a 100644 --- a/src/app/modules/auth/pages/auth.component.html +++ b/src/app/modules/auth/pages/auth.component.html @@ -7,20 +7,20 @@ class="flex flex-col items-center justify-center px-12 py-10 h-auto bg-surface-card border border-surface-border rounded-lg shadow w-full max-w-md" >
- صندوق فروشگاهی - به پنل صندوق فروشگاهی خوش آمدید. + مدیریت کسب‌وکار + به پنل مدیریت کسب‌وکار خوش آمدید.
- @if (activeStep() === "login") { - - } @else if (activeStep() === "signup") { + + + diff --git a/src/app/modules/auth/pages/auth.component.ts b/src/app/modules/auth/pages/auth.component.ts index 16ad78e..96c1fb9 100644 --- a/src/app/modules/auth/pages/auth.component.ts +++ b/src/app/modules/auth/pages/auth.component.ts @@ -4,16 +4,13 @@ import { Component, EventEmitter, inject, Input, Output, signal } from '@angular import { Router } from '@angular/router'; import images from 'src/assets/images'; import { LoginComponent } from './login/login.component'; -import { ModifyLoginInfoComponent } from './modifyLoginInfo/modify-login-info.component'; -import { OTPComponent } from './otp/otp.component'; -import { SignupComponent } from './signup/signup.component'; type TSteps = 'login' | 'otp' | 'modifyLoginInfo' | 'signup'; @Component({ selector: 'app-auth', templateUrl: './auth.component.html', - imports: [LoginComponent, OTPComponent, ModifyLoginInfoComponent, SignupComponent], + imports: [LoginComponent], }) export class AuthComponent { @Input() redirectUrl!: string; @@ -39,11 +36,11 @@ export class AuthComponent { onLoggedIn = (data: IAuthResponse) => { this.toastService.success({ text: 'شما با موفقیت وارد شدید.' }); - this.selectedRole.set(data.role.toUpperCase() as TRoles); - if (data.mustChangePassword) { - this.activeStep.set('modifyLoginInfo'); - return; - } + this.selectedRole.set(data.account.type.toUpperCase() as TRoles); + // if (data.mustChangePassword) { + // this.activeStep.set('modifyLoginInfo'); + // return; + // } if (this.submit.observed) { return this.submit.emit(Promise.resolve(true)); @@ -63,17 +60,17 @@ export class AuthComponent { if (!redirectUrl) { switch (this.selectedRole()) { - case 'SCHOOL': - redirectUrl = '/schools'; + case 'SUPERADMIN': + redirectUrl = '/super_admin'; break; case 'ADMIN': redirectUrl = '/admin'; break; - case 'TEACHER': - redirectUrl = '/teachers'; + case 'POS': + redirectUrl = '/poss'; break; - case 'STUDENTS': - redirectUrl = '/students'; + case 'PARTNER': + redirectUrl = '/partner'; break; } } diff --git a/src/app/modules/auth/pages/login/login.component.html b/src/app/modules/auth/pages/login/login.component.html index b516201..388f3ce 100644 --- a/src/app/modules/auth/pages/login/login.component.html +++ b/src/app/modules/auth/pages/login/login.component.html @@ -46,12 +46,12 @@ -->
-
@if (isCaptchaLoading()) { -
+
} @else if (isCaptchaExpired()) {
- +
} -
- +
--> +
- @if (loginForm.controls.role.value === "TEACHER") { +
diff --git a/src/app/modules/auth/pages/login/login.component.ts b/src/app/modules/auth/pages/login/login.component.ts index bd67bcd..84a372a 100644 --- a/src/app/modules/auth/pages/login/login.component.ts +++ b/src/app/modules/auth/pages/login/login.component.ts @@ -14,7 +14,6 @@ import { InputText } from 'primeng/inputtext'; import { Password } from 'primeng/password'; import { ProgressSpinnerModule } from 'primeng/progressspinner'; import { RadioButtonModule } from 'primeng/radiobutton'; -import { CaptchaService } from 'src/app/core/services/captcha.service'; // import { CENTRAL_AUTH_ROLES } from '../../constants/central-auth-roles.const'; @Component({ @@ -45,48 +44,47 @@ export class LoginComponent { private readonly authService = inject(AuthService); private readonly router = inject(Router); private readonly fb = inject(FormBuilder); - private readonly captchaService = inject(CaptchaService); private readonly toastService = inject(ToastService); readonly isLoading = computed(() => this.authService.isLoading()); readonly errorMessage = signal(''); readonly hidePassword = signal(true); - readonly isCaptchaExpired = this.captchaService.captchaIsExpired; - readonly isCaptchaLoading = this.captchaService.loading; - readonly captchaImageSrc = this.captchaService.captchaImageSrc; + // readonly isCaptchaExpired = this.captchaService.captchaIsExpired; + // readonly isCaptchaLoading = this.captchaService.loading; + // readonly captchaImageSrc = this.captchaService.captchaImageSrc; // readonly centralAuthRoles = CENTRAL_AUTH_ROLES; readonly loginForm = this.fb.group({ username: ['', [Validators.required]], password: ['', [Validators.required]], rememberMe: [false], - captcha: ['', [Validators.required]], - role: [this.defaultRole, [Validators.required]], + // captcha: ['', [Validators.required]], + // role: [this.defaultRole, [Validators.required]], }); - selectedRole = signal('SCHOOL'); + selectedRole = signal('SUPERADMIN'); ngOnInit() { - this.captchaService.requestNewCaptcha(); + // this.captchaService.requestNewCaptcha(); } togglePasswordVisibility(): void { this.hidePassword.set(!this.hidePassword()); } - resetCaptcha(): void { - this.fb.control('captcha').setValue(''); - this.captchaService.renewCaptcha(); - } + // resetCaptcha(): void { + // this.fb.control('captcha').setValue(''); + // this.captchaService.renewCaptcha(); + // } - onRefreshCaptcha(): void { - this.resetCaptcha(); - } - onRefreshCaptchaImage(): void { - this.resetCaptcha(); - // document - // .getElementById('captchaImage') - // ?.setAttribute('src', this.captchaService.captchaImageSrc()! + `&${new Date().getTime()}`); - } + // onRefreshCaptcha(): void { + // this.resetCaptcha(); + // } + // onRefreshCaptchaImage(): void { + // this.resetCaptcha(); + // // document + // // .getElementById('captchaImage') + // // ?.setAttribute('src', this.captchaService.captchaImageSrc()! + `&${new Date().getTime()}`); + // } toSignup() { this.onToSignup.emit(); @@ -95,27 +93,25 @@ export class LoginComponent { submit(): void { this.loginForm.markAllAsTouched(); if (this.loginForm.invalid) return; - if (!this.captchaService.captchaId()) { - this.toastService.error({ text: 'مقدار کپچا را وارد کنید' }); - } + // if (!this.captchaService.captchaId()) { + // this.toastService.error({ text: 'مقدار کپچا را وارد کنید' }); + // } const credentials: LoginCredentials = this.loginForm.value as LoginCredentials; - this.authService - .login(credentials, (this.defaultRole || this.loginForm.value.role)!) - .subscribe({ - next: (data) => { - this.errorMessage.set(''); - if (this.redirectUrl) { - this.router.navigateByUrl(this.redirectUrl.replace(/\/[^/]*$/, '')); - } else { - this.onSuccessfullySubmit.emit(data); - } - }, - error: (error) => { - this.resetCaptcha(); - this.errorMessage.set('نام کاربری یا رمز عبور اشتباه است'); - console.error('Login failed:', error); - }, - }); + this.authService.login(credentials).subscribe({ + next: (data) => { + this.errorMessage.set(''); + if (this.redirectUrl) { + this.router.navigateByUrl(this.redirectUrl.replace(/\/[^/]*$/, '')); + } else { + this.onSuccessfullySubmit.emit(data); + } + }, + error: (error) => { + // this.resetCaptcha(); + this.errorMessage.set('نام کاربری یا رمز عبور اشتباه است'); + console.error('Login failed:', error); + }, + }); } } diff --git a/src/app/modules/auth/pages/signup/signup.component.html b/src/app/modules/auth/pages/signup/signup.component.html index 6b68eaa..674efee 100644 --- a/src/app/modules/auth/pages/signup/signup.component.html +++ b/src/app/modules/auth/pages/signup/signup.component.html @@ -1,7 +1,7 @@
- + ; - token: Maybe; - refreshToken: Maybe; - isAuthenticated: boolean; - permissions: string[]; - loginAttempts: number; - lastLoginTime: Maybe; - sessionExpiry: Maybe; -} - -/** - * Login state for UI feedback - */ -export interface LoginState { - isLoggingIn: boolean; - isRefreshing: boolean; - loginError: Maybe; -} - -/** - * Authentication state store - */ -@Injectable({ - providedIn: 'root', -}) -export class AuthStore extends BaseStore { - private readonly http = inject(HttpClient); - private readonly router = inject(Router); - - // Login-specific state - private readonly _loginState = { - isLoggingIn: false, - isRefreshing: false, - loginError: null as Maybe, - }; - - constructor() { - super({ - initialized: false, - loading: false, - error: null, - user: null, - token: null, - refreshToken: null, - isAuthenticated: false, - permissions: [], - loginAttempts: 0, - lastLoginTime: null, - sessionExpiry: null, - }); - - this.initializeAuth(); - } - - // Computed selectors - readonly user = computed(() => this._state().user); - readonly token = computed(() => this._state().token); - readonly refreshToken = computed(() => this._state().refreshToken); - readonly isAuthenticated = computed(() => this._state().isAuthenticated); - readonly userRole = computed(() => this._state().user?.role); - readonly permissions = computed(() => this._state().permissions); - readonly loginAttempts = computed(() => this._state().loginAttempts); - readonly lastLoginTime = computed(() => this._state().lastLoginTime); - readonly sessionExpiry = computed(() => this._state().sessionExpiry); - - // Login state selectors - readonly isLoggingIn = computed(() => this._loginState.isLoggingIn); - readonly isRefreshing = computed(() => this._loginState.isRefreshing); - readonly loginError = computed(() => this._loginState.loginError); - - /** - * Initialize authentication from stored data - */ - private initializeAuth(): void { - const token = localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); - const refreshToken = localStorage.getItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); - const userData = localStorage.getItem(LOCAL_STORAGE_KEYS.USER_DATA); - const loginAttempts = parseInt(localStorage.getItem(LOCAL_STORAGE_KEYS.LOGIN_ATTEMPTS) || '0'); - const lastLoginTime = parseInt(localStorage.getItem(LOCAL_STORAGE_KEYS.LAST_LOGIN_TIME) || '0'); - - if (token && refreshToken && userData) { - try { - const user = JSON.parse(userData) as User; - const sessionExpiry = this.getTokenExpiry(token); - - this.patchState({ - user, - token, - refreshToken, - isAuthenticated: true, - permissions: [], - // permissions: user.permissions?.map((p) => `${p.resource}:${p.action}`) || [], - loginAttempts, - lastLoginTime: lastLoginTime || null, - sessionExpiry, - }); - - // Check if token is expired - if (this.isTokenExpired()) { - this.refreshAuthToken().subscribe(); - } - } catch (error) { - console.error('Error parsing stored user data:', error); - this.logout(); - } - } - } - - /** - * Login with credentials - */ - login(credentials: LoginCredentials) { - this._loginState.isLoggingIn = true; - this._loginState.loginError = null; - this.setLoading(true); - - return this.http.post('/api/auth/login', credentials).pipe( - tap((response) => { - this.handleAuthSuccess(response); - this._loginState.isLoggingIn = false; - }), - catchError((error) => { - this.handleAuthError(error); - return of(null); - }), - ); - } - - /** - * Logout user - */ - logout(): void { - // Clear stored data - localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); - localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); - localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); - localStorage.removeItem(LOCAL_STORAGE_KEYS.LAST_LOGIN_TIME); - - // Reset state - this.patchState({ - user: null, - token: null, - refreshToken: null, - isAuthenticated: false, - permissions: [], - lastLoginTime: null, - sessionExpiry: null, - loading: false, - error: null, - }); - - // Reset login state - this._loginState.isLoggingIn = false; - this._loginState.isRefreshing = false; - this._loginState.loginError = null; - - // Navigate to login - this.router.navigate(['/auth']); - } - - /** - * Refresh authentication token - */ - refreshAuthToken() { - const refreshToken = this._state().refreshToken; - if (!refreshToken) { - this.logout(); - return of(null); - } - - this._loginState.isRefreshing = true; - this.setLoading(true); - - return this.http.post('/api/auth/refresh', { refreshToken }).pipe( - tap((response) => { - this.handleAuthSuccess(response); - this._loginState.isRefreshing = false; - }), - catchError((error) => { - console.error('Token refresh failed:', error); - this.logout(); - return of(null); - }), - ); - } - - /** - * Check if user has specific role - */ - hasRole(role: TRoles): boolean { - return this._state().user?.role === role; - } - - /** - * Check if user has any of the specified roles - */ - hasAnyRole(roles: TRoles[]): boolean { - const userRole = this._state().user?.role; - return userRole ? roles.includes(userRole) : false; - } - - /** - * Check if user has specific permission - */ - hasPermission(permission: string): boolean { - const permissions = this._state().permissions; - return permissions.includes(permission); - } - - /** - * Check if user has all specified permissions - */ - hasAllPermissions(permissions: string[]): boolean { - const userPermissions = this._state().permissions; - return permissions.every((permission) => userPermissions.includes(permission)); - } - - /** - * Check if user has any of the specified permissions - */ - hasAnyPermission(permissions: string[]): boolean { - const userPermissions = this._state().permissions; - return permissions.some((permission) => userPermissions.includes(permission)); - } - - /** - * Check if user can access resource - */ - canAccess(requiredRoles?: TRoles[], requiredPermissions?: string[]): boolean { - if (!this._state().isAuthenticated) return false; - - if (requiredRoles && !this.hasAnyRole(requiredRoles)) { - return false; - } - - if (requiredPermissions && !this.hasAllPermissions(requiredPermissions)) { - return false; - } - - return true; - } - - /** - * Get current access token - */ - getToken(): string | null { - return this._state().token; - } - - /** - * Get current refresh token - */ - getRefreshToken(): string | null { - return this._state().refreshToken; - } - - /** - * Check if token is expired - */ - isTokenExpired(): boolean { - const sessionExpiry = this._state().sessionExpiry; - if (!sessionExpiry) return true; - - return Date.now() > sessionExpiry; - } - - /** - * Get time until token expires (in minutes) - */ - getTimeUntilExpiry(): number { - const sessionExpiry = this._state().sessionExpiry; - if (!sessionExpiry) return 0; - - return Math.max(0, Math.floor((sessionExpiry - Date.now()) / 60000)); - } - - /** - * Update user profile - */ - updateUserProfile(updates: Partial): void { - const currentUser = this._state().user; - if (!currentUser) return; - - const updatedUser = { ...currentUser, ...updates }; - - this.patchState({ user: updatedUser }); - localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(updatedUser)); - } - - /** - * Handle successful authentication - */ - private handleAuthSuccess(response: IAuthResponse): void { - const sessionExpiry = this.getTokenExpiry(response.token); - const currentTime = Date.now(); - - // Store in localStorage - localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.token); - localStorage.setItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN, response.refreshToken); - // localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(response.user)); - localStorage.setItem(LOCAL_STORAGE_KEYS.LAST_LOGIN_TIME, currentTime.toString()); - - // Update state - this.patchState({ - user: { fullName: response.fullName, role: response.role as TRoles }, - token: response.token, - refreshToken: response.refreshToken, - isAuthenticated: true, - // permissions: response.user.permissions?.map((p) => `${p.resource}:${p.action}`) || [], - lastLoginTime: currentTime, - sessionExpiry, - loading: false, - error: null, - }); - - // Reset login attempts on successful login - localStorage.removeItem(LOCAL_STORAGE_KEYS.LOGIN_ATTEMPTS); - - // Navigate based on user role - // this.navigateByRole(response.user.role); - } - - /** - * Handle authentication error - */ - private handleAuthError(error: any): void { - console.error('Authentication error:', error); - - // Increment login attempts - const attempts = this._state().loginAttempts + 1; - this.patchState({ loginAttempts: attempts }); - localStorage.setItem(LOCAL_STORAGE_KEYS.LOGIN_ATTEMPTS, attempts.toString()); - - // Set login error - this._loginState.isLoggingIn = false; - this._loginState.loginError = error.error?.message || 'خطا در ورود به سیستم'; - - if (this._loginState.loginError) { - this.setError(this._loginState.loginError); - } - } - - /** - * Navigate user based on their role - */ - private navigateByRole(role: TRoles): void { - const roleRoutes = { - [ROLES.ADMIN]: '/admin', - [ROLES.SCHOOL]: '/schools', - [ROLES.TEACHER]: '/teachers', - [ROLES.STUDENTS]: '/students', - [ROLES.GRADER]: '/grader', - [ROLES.SUPERADMIN]: '/superadmin', - }; - - this.router.navigate([roleRoutes[role]]); - } - - /** - * Extract token expiry from JWT - */ - private getTokenExpiry(token: string): number | null { - try { - const payload = JSON.parse(atob(token.split('.')[1])); - return payload.exp * 1000; // Convert to milliseconds - } catch (error) { - console.error('Error parsing token:', error); - return null; - } - } - - /** - * Reset state to initial values - */ - reset(): void { - this.setState({ - initialized: false, - loading: false, - error: null, - user: null, - token: null, - refreshToken: null, - isAuthenticated: false, - permissions: [], - loginAttempts: 0, - lastLoginTime: null, - sessionExpiry: null, - }); - - this._loginState.isLoggingIn = false; - this._loginState.isRefreshing = false; - this._loginState.loginError = null; - } -} diff --git a/src/app/modules/auth/state/index.ts b/src/app/modules/auth/state/index.ts deleted file mode 100644 index 4d0d523..0000000 --- a/src/app/modules/auth/state/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Authentication state management -export * from './auth.store'; - -// Types -export type { AuthState, LoginState } from './auth.store'; diff --git a/src/app/modules/dashboard/views/dashboard.component.html b/src/app/modules/dashboard/views/dashboard.component.html index dca4e49..b2f6209 100644 --- a/src/app/modules/dashboard/views/dashboard.component.html +++ b/src/app/modules/dashboard/views/dashboard.component.html @@ -1,7 +1,7 @@
- +
diff --git a/src/app/modules/dashboard/views/dashboard.component.ts b/src/app/modules/dashboard/views/dashboard.component.ts index 99642ec..6f00794 100644 --- a/src/app/modules/dashboard/views/dashboard.component.ts +++ b/src/app/modules/dashboard/views/dashboard.component.ts @@ -1,6 +1,3 @@ -import { StatisticsSharedTopAlertStocksComponent } from '@/modules/statistics/shared/components/top-alert-stocks.component'; -import { StatisticsSharedTopSalesComponent } from '@/modules/statistics/shared/components/top-sales.component'; -import { StatisticsSharedTopSupplierDebtsComponent } from '@/modules/statistics/shared/components/top-supplier-debts.component'; import { Component } from '@angular/core'; import images from 'src/assets/images'; @@ -8,9 +5,9 @@ import images from 'src/assets/images'; selector: 'app-dashboard', templateUrl: './dashboard.component.html', imports: [ - StatisticsSharedTopAlertStocksComponent, - StatisticsSharedTopSalesComponent, - StatisticsSharedTopSupplierDebtsComponent, + // StatisticsSharedTopAlertStocksComponent, + // StatisticsSharedTopSalesComponent, + // StatisticsSharedTopSupplierDebtsComponent, ], }) export class DashboardComponent { diff --git a/src/app/modules/pos/components/order/order-card.component.html b/src/app/modules/pos/components/order/order-card.component.html index a88abfa..9b8b61c 100644 --- a/src/app/modules/pos/components/order/order-card.component.html +++ b/src/app/modules/pos/components/order/order-card.component.html @@ -4,12 +4,12 @@

- + /> -->

diff --git a/src/app/modules/pos/components/order/order-card.component.ts b/src/app/modules/pos/components/order/order-card.component.ts index afb1ffa..12346d0 100644 --- a/src/app/modules/pos/components/order/order-card.component.ts +++ b/src/app/modules/pos/components/order/order-card.component.ts @@ -1,9 +1,8 @@ -import { Maybe } from '@/core'; -import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component'; -import { ICustomerResponse } from '@/modules/customers/models'; +// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component'; +// import { ICustomerResponse } from '@/modules/customers/models'; import { PriceMaskDirective } from '@/shared/directives'; import { UikitCounterComponent } from '@/uikit'; -import { Component, computed, inject, signal } from '@angular/core'; +import { Component, computed, inject } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ButtonDirective } from 'primeng/button'; import { InplaceModule } from 'primeng/inplace'; @@ -17,7 +16,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; selector: 'pos-order-card', templateUrl: './order-card.component.html', imports: [ - CustomersSelectComponent, + // CustomersSelectComponent, ButtonDirective, PriceMaskDirective, UikitCounterComponent, @@ -32,7 +31,7 @@ export class PosOrderCardComponent { private store = inject(POSStore); constructor() { - this.selectedCustomer.set(this.store.selectedCustomer()); + // this.selectedCustomer.set(this.store.selectedCustomer()); } posId = this.store.posId; @@ -41,7 +40,7 @@ export class PosOrderCardComponent { inOrderProducts = computed(() => this.store.inOrderProducts()); - selectedCustomer = signal>(null); + // selectedCustomer = signal>(null); removeProductFromOrder(productId: number) { this.store.removeFromInOrderProducts(productId); @@ -63,9 +62,9 @@ export class PosOrderCardComponent { this.store.resetInOrderProducts(); } - updateCustomer(customer: Maybe) { - this.store.setSelectedCustomer(customer); - } + // updateCustomer(customer: Maybe) { + // this.store.setSelectedCustomer(customer); + // } submit() { this.store.submitOrder(); diff --git a/src/app/modules/pos/store/index.ts b/src/app/modules/pos/store/index.ts index 39b2395..d63476f 100644 --- a/src/app/modules/pos/store/index.ts +++ b/src/app/modules/pos/store/index.ts @@ -1,5 +1,5 @@ import { Maybe } from '@/core'; -import { ICustomerResponse } from '@/modules/customers/models'; +// import { ICustomerResponse } from '@/modules/customers/models'; import { computed, Inject, Injectable, InjectionToken, signal } from '@angular/core'; import { map, of } from 'rxjs'; import { IPosInfoResponse, IPosProductCategoriesResponse, IPosStockResponse } from '../models'; @@ -19,7 +19,7 @@ interface IPosState { productCategories: Maybe; activeProductCategory: Maybe; inOrderProducts: IPosInOrderProduct[]; - selectedCustomer: Maybe; + selectedCustomer: Maybe; viewType: TViewType; searchQuery: string; } @@ -245,7 +245,7 @@ export class POSStore { this.setState({ inOrderProducts: [] }); } - setSelectedCustomer(customer: Maybe) { + setSelectedCustomer(customer: Maybe) { this.setState({ selectedCustomer: customer }); } diff --git a/src/app/modules/users/views/list.component.ts b/src/app/modules/users/views/list.component.ts deleted file mode 100644 index 89f02f9..0000000 --- a/src/app/modules/users/views/list.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; -import { - IColumn, - PageDataListComponent, -} from '@/shared/components/pageDataList/page-data-list.component'; -import { Component, signal, TemplateRef, ViewChild } from '@angular/core'; -import { UserFormComponent } from '../components/form.component'; -import { IUserResponse } from '../models'; -import { UsersService } from '../services/main.service'; - -@Component({ - selector: 'app-users', - templateUrl: './list.component.html', - imports: [PageDataListComponent, UserFormComponent, CatalogRoleTagComponent], -}) -export class UsersComponent { - constructor(private userService: UsersService) { - this.getData(); - } - @ViewChild('role', { static: true }) roleTpl!: TemplateRef; - - columns = [ - { field: 'id', header: 'شناسه' }, - { field: 'firstName', header: 'نام' }, - { field: 'lastName', header: 'نام خانوادگی' }, - { field: 'mobileNumber', header: 'شماره موبایل' }, - // { field: 'role', header: 'نقش', customDataModel: this.roleTpl }, - { - field: 'role', - header: 'نقش', - customDataModel: (item: IUserResponse) => item.role.name ?? '-', - }, - ] as IColumn[]; - - loading = signal(false); - items = signal([]); - visibleForm = signal(false); - - refresh() { - this.getData(); - } - - getData() { - this.loading.set(true); - this.userService.getAll().subscribe((res) => { - this.loading.set(false); - this.items.set(res.data); - }); - } - - openAddForm() { - this.visibleForm.set(true); - } -} diff --git a/src/app/pages/notfound/notfound.component.html b/src/app/pages/notfound/notfound.component.html index d388b20..17607f5 100644 --- a/src/app/pages/notfound/notfound.component.html +++ b/src/app/pages/notfound/notfound.component.html @@ -2,7 +2,7 @@
- صندوق فروشگاهی + مدیریت کسب‌وکار
- - - diff --git a/src/app/shared/catalog/banks/components/select/select.component.ts b/src/app/shared/catalog/banks/components/select/select.component.ts deleted file mode 100644 index 2dc17e7..0000000 --- a/src/app/shared/catalog/banks/components/select/select.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AbstractSelectComponent } from '@/shared/components'; -import { UikitFieldComponent } from '@/uikit'; -import { Component } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; -import { Select } from 'primeng/select'; -import { IBankResponse } from '../../models'; -import { BanksStore } from '../../store/main.store'; - -@Component({ - selector: 'banks-select-field', - templateUrl: './select.component.html', - imports: [ReactiveFormsModule, Select, UikitFieldComponent], -}) -export class BanksSelectComponent extends AbstractSelectComponent { - constructor(private store: BanksStore) { - super(); - this.getData(); - } - - getDataService() { - return this.store.getItems(); - } -} diff --git a/src/app/shared/catalog/banks/components/tag/tag.component.html b/src/app/shared/catalog/banks/components/tag/tag.component.html deleted file mode 100644 index d694fca..0000000 --- a/src/app/shared/catalog/banks/components/tag/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ selectedBank?.name }} - diff --git a/src/app/shared/catalog/banks/components/tag/tag.component.ts b/src/app/shared/catalog/banks/components/tag/tag.component.ts deleted file mode 100644 index 6fea533..0000000 --- a/src/app/shared/catalog/banks/components/tag/tag.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, inject, Input } from '@angular/core'; -import { BanksStore } from '../../store/main.store'; - -@Component({ - selector: 'app-catalog-banks-tag', - templateUrl: './tag.component.html', -}) -export class CatalogBanksComponent { - @Input() bankId!: number; - - private readonly store = inject(BanksStore); - - banks = this.store.banks; - - selectedBank = this.banks().find((bank) => bank.id === this.bankId); -} diff --git a/src/app/shared/catalog/banks/constants/apiRoutes/index.ts b/src/app/shared/catalog/banks/constants/apiRoutes/index.ts deleted file mode 100644 index 0cd6e36..0000000 --- a/src/app/shared/catalog/banks/constants/apiRoutes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -const baseUrl = '/api/v1/banks'; - -export const BANKS_API_ROUTES = { - list: () => `${baseUrl}`, -}; diff --git a/src/app/shared/catalog/banks/index.ts b/src/app/shared/catalog/banks/index.ts deleted file mode 100644 index 07635cb..0000000 --- a/src/app/shared/catalog/banks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './components'; diff --git a/src/app/shared/catalog/banks/models/io.d.ts b/src/app/shared/catalog/banks/models/io.d.ts deleted file mode 100644 index 37da4e1..0000000 --- a/src/app/shared/catalog/banks/models/io.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface IBankRawResponse { - id: number; - name: string; - shortName: string; -} - -export interface IBankResponse extends IBankRawResponse {} - -export interface IBankRequest { - firstName: string; - lastName: string; - email: string; - mobileNumber: string; - address: string; - city: string; - state: string; - country: string; - isActive: boolean; -} diff --git a/src/app/shared/catalog/banks/services/main.service.ts b/src/app/shared/catalog/banks/services/main.service.ts deleted file mode 100644 index 5619c79..0000000 --- a/src/app/shared/catalog/banks/services/main.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IPaginatedResponse } from '@/core/models/service.model'; -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { BANKS_API_ROUTES } from '../constants'; -import { IBankRawResponse, IBankResponse } from '../models'; - -@Injectable({ providedIn: 'root' }) -export class BanksService { - constructor(private http: HttpClient) {} - - private apiRoutes = BANKS_API_ROUTES; - - getAll(): Observable> { - return this.http.get>(this.apiRoutes.list()); - } -} diff --git a/src/app/shared/catalog/banks/store/main.store.ts b/src/app/shared/catalog/banks/store/main.store.ts deleted file mode 100644 index 0563b7c..0000000 --- a/src/app/shared/catalog/banks/store/main.store.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { BaseState, BaseStore } from '@/core/state'; -import { computed, Injectable } from '@angular/core'; -import { of } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; -import { IBankResponse } from '../models'; -import { BanksService } from '../services/main.service'; - -export interface banksState extends BaseState { - items: IBankResponse[]; -} - -@Injectable({ - providedIn: 'root', -}) -export class BanksStore extends BaseStore { - constructor(private service: BanksService) { - super({ - loading: false, - initialized: false, - error: null, - isRefreshing: false, - items: [], - }); - this.initial(); - } - - readonly banks = computed(() => this.getCurrentState().items || []); - - reset(): void { - this.setState({ - loading: false, - initialized: false, - error: null, - isRefreshing: false, - items: [], - }); - } - - initial() { - this.getItems().subscribe(); - } - - getItems() { - if (this.getCurrentState().items?.length === 0) { - this.setLoading(true); - return this.service.getAll().pipe( - map((res: { data: IBankResponse[] }) => { - this.patchState({ - items: res.data, - initialized: true, - }); - this.setLoading(false); - return res.data; - }), - catchError((err: any) => { - this.setError(err.message || 'Failed to load banks'); - this.setLoading(false); - return of([] as IBankResponse[]); - }), - ); - } else { - return of(this.getCurrentState().items); - } - } -} diff --git a/src/app/shared/catalog/gender/gender-select.component.html b/src/app/shared/catalog/gender/gender-select.component.html deleted file mode 100644 index c266dd4..0000000 --- a/src/app/shared/catalog/gender/gender-select.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/src/app/shared/catalog/gender/gender-select.component.ts b/src/app/shared/catalog/gender/gender-select.component.ts deleted file mode 100644 index a5a11ac..0000000 --- a/src/app/shared/catalog/gender/gender-select.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Maybe } from '@/core'; -import { UikitFieldComponent } from '@/uikit/uikit-field.component'; -import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; -import { AbstractControl, FormControl, ReactiveFormsModule } from '@angular/forms'; -import { Select } from 'primeng/select'; - -@Component({ - selector: 'app-gender-select', - templateUrl: './gender-select.component.html', - imports: [CommonModule, UikitFieldComponent, Select, ReactiveFormsModule], -}) -export class GenderSelectComponent { - @Input() control!: Maybe>; - @Input() size?: 'small' | 'large'; - - get formControl(): FormControl | undefined { - return this.control as FormControl | undefined; - } -} diff --git a/src/app/shared/catalog/gender/gender-tag.component.html b/src/app/shared/catalog/gender/gender-tag.component.html deleted file mode 100644 index f62d658..0000000 --- a/src/app/shared/catalog/gender/gender-tag.component.html +++ /dev/null @@ -1 +0,0 @@ -{{ genderTitle() }} diff --git a/src/app/shared/catalog/gender/gender-tag.component.ts b/src/app/shared/catalog/gender/gender-tag.component.ts deleted file mode 100644 index 2e10bd0..0000000 --- a/src/app/shared/catalog/gender/gender-tag.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, computed, Input } from '@angular/core'; - -@Component({ - selector: 'catalog-gender-tag', - templateUrl: './gender-tag.component.html', -}) -export class CatalogGenderTagComponent { - @Input() gender!: boolean; - - constructor() {} - - genderTitle = computed(() => (this.gender ? 'مرد' : 'زن')); -} diff --git a/src/app/shared/catalog/gender/index.ts b/src/app/shared/catalog/gender/index.ts deleted file mode 100644 index 13d1af8..0000000 --- a/src/app/shared/catalog/gender/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './gender-select.component'; -export * from './gender-tag.component'; diff --git a/src/app/shared/catalog/index.ts b/src/app/shared/catalog/index.ts index 7ec7667..e69de29 100644 --- a/src/app/shared/catalog/index.ts +++ b/src/app/shared/catalog/index.ts @@ -1,3 +0,0 @@ -export * from './gender'; -export * from './movementTypes'; -export * from './purchaseReceiptStatus'; diff --git a/src/app/shared/catalog/movementReferenceTypes/index.ts b/src/app/shared/catalog/movementReferenceTypes/index.ts deleted file mode 100644 index 7d1f2fb..0000000 --- a/src/app/shared/catalog/movementReferenceTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/movementReferenceTypes/tag.component.html b/src/app/shared/catalog/movementReferenceTypes/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/movementReferenceTypes/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/movementReferenceTypes/tag.component.ts b/src/app/shared/catalog/movementReferenceTypes/tag.component.ts deleted file mode 100644 index 2fb3e40..0000000 --- a/src/app/shared/catalog/movementReferenceTypes/tag.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { MovementType } from '../movementTypes'; -import { MovementReferenceType } from './types'; -import { getMovementReferenceTypeColor, getMovementReferenceTypeText } from './utils'; - -@Component({ - selector: 'catalog-movement-reference-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogMovementReferenceTagComponent { - @Input() type!: MovementReferenceType; - @Input() movementType?: MovementType; - @Input() counterName?: string; - constructor() {} - - get text() { - return getMovementReferenceTypeText(this.type, this.movementType, this.counterName); - } - get color() { - return getMovementReferenceTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/movementReferenceTypes/types.ts b/src/app/shared/catalog/movementReferenceTypes/types.ts deleted file mode 100644 index 4c3d142..0000000 --- a/src/app/shared/catalog/movementReferenceTypes/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const MovementReferenceType = { - PURCHASE: 'PURCHASE', - SALES: 'SALES', - ADJUSTMENT: 'ADJUSTMENT', - INVENTORY_TRANSFER: 'INVENTORY_TRANSFER', -} as const; - -export type MovementReferenceType = - (typeof MovementReferenceType)[keyof typeof MovementReferenceType]; diff --git a/src/app/shared/catalog/movementReferenceTypes/utils.ts b/src/app/shared/catalog/movementReferenceTypes/utils.ts deleted file mode 100644 index 420872b..0000000 --- a/src/app/shared/catalog/movementReferenceTypes/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { MovementType } from '../movementTypes'; -import { MovementReferenceType } from './types'; - -export const getMovementReferenceTypeText = ( - movementType: MovementReferenceType, - type?: MovementType, - counterName?: string, -): string => { - switch (movementType) { - case MovementReferenceType.PURCHASE: - return 'خرید'; - case MovementReferenceType.SALES: - return 'فروش'; - case MovementReferenceType.ADJUSTMENT: - return 'تعدیل'; - case MovementReferenceType.INVENTORY_TRANSFER: - if (type === MovementType.IN) { - return `دریافت از انبار ${counterName ?? 'دیگر'}`; - } else { - return `ارسال به انبار ${counterName ?? 'دیگر'}`; - } - default: - return 'انتقال بین انبارها'; - } -}; - -export const getMovementReferenceTypeColor = (movementType: MovementReferenceType): TagSeverity => { - switch (movementType) { - case MovementReferenceType.PURCHASE: - return 'success'; - case MovementReferenceType.SALES: - return 'danger'; - case MovementReferenceType.ADJUSTMENT: - return 'warn'; - case MovementReferenceType.INVENTORY_TRANSFER: - return 'info'; - } -}; diff --git a/src/app/shared/catalog/movementTypes/index.ts b/src/app/shared/catalog/movementTypes/index.ts deleted file mode 100644 index 7d1f2fb..0000000 --- a/src/app/shared/catalog/movementTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/movementTypes/tag.component.html b/src/app/shared/catalog/movementTypes/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/movementTypes/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/movementTypes/tag.component.ts b/src/app/shared/catalog/movementTypes/tag.component.ts deleted file mode 100644 index 6535fdd..0000000 --- a/src/app/shared/catalog/movementTypes/tag.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { MovementType } from './types'; -import { getMovementTypeColor, getMovementTypeText } from './utils'; - -@Component({ - selector: 'catalog-movement-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogMovementTagComponent { - @Input() type!: MovementType; - constructor() {} - - get text() { - return getMovementTypeText(this.type); - } - get color() { - return getMovementTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/movementTypes/types.ts b/src/app/shared/catalog/movementTypes/types.ts deleted file mode 100644 index 5dc9255..0000000 --- a/src/app/shared/catalog/movementTypes/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const MovementType = { - IN: 'IN', - OUT: 'OUT', - ADJUST: 'ADJUST', -} as const; - -export type MovementType = (typeof MovementType)[keyof typeof MovementType]; diff --git a/src/app/shared/catalog/movementTypes/utils.ts b/src/app/shared/catalog/movementTypes/utils.ts deleted file mode 100644 index bbb8a61..0000000 --- a/src/app/shared/catalog/movementTypes/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { MovementType } from './types'; - -export const getMovementTypeText = (movementType: MovementType): string => { - switch (movementType) { - case MovementType.IN: - return 'ورودی'; - case MovementType.OUT: - return 'خروجی'; - case MovementType.ADJUST: - return 'تعدیل'; - } -}; - -export const getMovementTypeColor = (movementType: MovementType): TagSeverity => { - switch (movementType) { - case MovementType.IN: - return 'success'; - case MovementType.OUT: - return 'danger'; - case MovementType.ADJUST: - return 'warn'; - } -}; diff --git a/src/app/shared/catalog/paymentMethodTypes/components/index.ts b/src/app/shared/catalog/paymentMethodTypes/components/index.ts deleted file mode 100644 index 162984a..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './select/select.component'; -export * from './tag/tag.component'; diff --git a/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.html b/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.html deleted file mode 100644 index c88f71c..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.ts b/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.ts deleted file mode 100644 index eea3af3..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/components/select/select.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Maybe } from '@/core'; -import { UikitFieldComponent } from '@/uikit'; -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { Select } from 'primeng/select'; -import { PaymentMethodType } from '../../types'; - -@Component({ - selector: 'app-catalog-payment-method-type-select-field', - templateUrl: './select.component.html', - imports: [ReactiveFormsModule, Select, UikitFieldComponent], -}) -export class PaymentMethodTypeSelectComponent { - @Input() control = new FormControl>(null); - @Input() showLabel: boolean = true; - @Input() showErrors: boolean = true; - @Input() showClear: boolean = false; - @Input() isFullDataOptionValue: boolean = false; - @Output() onChange = new EventEmitter>(); - - items = [ - { label: 'نقدی', value: 'CASH' }, - { label: 'کارت بانکی', value: 'CARD' }, - { label: 'حساب', value: 'BANK' }, - { label: 'چک', value: 'CHECK' }, - { label: 'سایر', value: 'OTHER' }, - ] as { label: string; value: PaymentMethodType }[]; -} diff --git a/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.html b/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.ts b/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.ts deleted file mode 100644 index 252731d..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/components/tag/tag.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { PaymentMethodType } from '../../types'; -import { getPaymentMethodTypeColor, getPaymentMethodTypeText } from '../../utils'; - -@Component({ - selector: 'app-catalog-payment-method-type-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogPaymentMethodTypeTagComponent { - @Input() type!: PaymentMethodType; - constructor() {} - - get text() { - return getPaymentMethodTypeText(this.type); - } - get color() { - return getPaymentMethodTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/paymentMethodTypes/index.ts b/src/app/shared/catalog/paymentMethodTypes/index.ts deleted file mode 100644 index 9d922da..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/tag/tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/paymentMethodTypes/types.ts b/src/app/shared/catalog/paymentMethodTypes/types.ts deleted file mode 100644 index 6fb954f..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const PaymentMethodType = { - CASH: 'CASH', - CARD: 'CARD', - BANK: 'BANK', - CHECK: 'CHECK', - OTHER: 'OTHER', -} as const; - -export type PaymentMethodType = (typeof PaymentMethodType)[keyof typeof PaymentMethodType]; diff --git a/src/app/shared/catalog/paymentMethodTypes/utils.ts b/src/app/shared/catalog/paymentMethodTypes/utils.ts deleted file mode 100644 index f577c56..0000000 --- a/src/app/shared/catalog/paymentMethodTypes/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { PaymentMethodType } from './types'; - -export const getPaymentMethodTypeText = (value: PaymentMethodType): string => { - switch (value) { - case PaymentMethodType.CASH: - return 'نقدی'; - case PaymentMethodType.CARD: - return 'کارت'; - case PaymentMethodType.BANK: - return 'بانک'; - case PaymentMethodType.CHECK: - return 'چک'; - case PaymentMethodType.OTHER: - return 'سایر'; - } -}; - -export const getPaymentMethodTypeColor = (value: PaymentMethodType): TagSeverity => { - switch (value) { - case PaymentMethodType.CASH: - return 'danger'; - case PaymentMethodType.CARD: - return 'info'; - case PaymentMethodType.BANK: - return 'success'; - case PaymentMethodType.CHECK: - return 'warn'; - case PaymentMethodType.OTHER: - return 'secondary'; - } -}; diff --git a/src/app/shared/catalog/paymentTypes/components/index.ts b/src/app/shared/catalog/paymentTypes/components/index.ts deleted file mode 100644 index b544a9c..0000000 --- a/src/app/shared/catalog/paymentTypes/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tag/tag.component'; diff --git a/src/app/shared/catalog/paymentTypes/components/tag/tag.component.html b/src/app/shared/catalog/paymentTypes/components/tag/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/paymentTypes/components/tag/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/paymentTypes/components/tag/tag.component.ts b/src/app/shared/catalog/paymentTypes/components/tag/tag.component.ts deleted file mode 100644 index 18548cc..0000000 --- a/src/app/shared/catalog/paymentTypes/components/tag/tag.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { PaymentType } from '../../types'; -import { getPaymentTypeColor, getPaymentTypeText } from '../../utils'; - -@Component({ - selector: 'app-catalog-payment-type-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogPaymentTypeTagComponent { - @Input() type!: PaymentType; - constructor() {} - - get text() { - return getPaymentTypeText(this.type); - } - get color() { - return getPaymentTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/paymentTypes/index.ts b/src/app/shared/catalog/paymentTypes/index.ts deleted file mode 100644 index 9d922da..0000000 --- a/src/app/shared/catalog/paymentTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/tag/tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/paymentTypes/types.ts b/src/app/shared/catalog/paymentTypes/types.ts deleted file mode 100644 index 85f60ed..0000000 --- a/src/app/shared/catalog/paymentTypes/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const PaymentType = { - PAYMENT: 'PAYMENT', - REFUND: 'REFUND', -} as const; - -export type PaymentType = (typeof PaymentType)[keyof typeof PaymentType]; diff --git a/src/app/shared/catalog/paymentTypes/utils.ts b/src/app/shared/catalog/paymentTypes/utils.ts deleted file mode 100644 index 8053171..0000000 --- a/src/app/shared/catalog/paymentTypes/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { PaymentType } from './types'; - -export const getPaymentTypeText = (value: PaymentType): string => { - switch (value) { - case PaymentType.PAYMENT: - return 'پرداخت'; - case PaymentType.REFUND: - return 'بازگشت وجه'; - } -}; - -export const getPaymentTypeColor = (value: PaymentType): TagSeverity => { - switch (value) { - case PaymentType.PAYMENT: - return 'success'; - case PaymentType.REFUND: - return 'warn'; - } -}; diff --git a/src/app/shared/catalog/purchaseReceiptStatus/index.ts b/src/app/shared/catalog/purchaseReceiptStatus/index.ts deleted file mode 100644 index 7d1f2fb..0000000 --- a/src/app/shared/catalog/purchaseReceiptStatus/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/purchaseReceiptStatus/tag.component.html b/src/app/shared/catalog/purchaseReceiptStatus/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/purchaseReceiptStatus/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/purchaseReceiptStatus/tag.component.ts b/src/app/shared/catalog/purchaseReceiptStatus/tag.component.ts deleted file mode 100644 index a6e3320..0000000 --- a/src/app/shared/catalog/purchaseReceiptStatus/tag.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { PurchaseReceiptStatus } from './types'; -import { getPurchaseReceiptStatusColor, getPurchaseReceiptStatusText } from './utils'; - -@Component({ - selector: 'app-catalog-purchase-receipt-status-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogPurchaseReceiptStatusTagComponent { - @Input() type!: PurchaseReceiptStatus; - constructor() {} - - get text() { - return getPurchaseReceiptStatusText(this.type); - } - get color() { - return getPurchaseReceiptStatusColor(this.type); - } -} diff --git a/src/app/shared/catalog/purchaseReceiptStatus/types.ts b/src/app/shared/catalog/purchaseReceiptStatus/types.ts deleted file mode 100644 index 7e0e608..0000000 --- a/src/app/shared/catalog/purchaseReceiptStatus/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PurchaseReceiptStatus = { - UNPAID: 'UNPAID', - PARTIALLY_PAID: 'PARTIALLY_PAID', - PAID: 'PAID', -} as const; - -export type PurchaseReceiptStatus = - (typeof PurchaseReceiptStatus)[keyof typeof PurchaseReceiptStatus]; diff --git a/src/app/shared/catalog/purchaseReceiptStatus/utils.ts b/src/app/shared/catalog/purchaseReceiptStatus/utils.ts deleted file mode 100644 index 32c9669..0000000 --- a/src/app/shared/catalog/purchaseReceiptStatus/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { PurchaseReceiptStatus } from './types'; - -export const getPurchaseReceiptStatusText = (value: PurchaseReceiptStatus): string => { - switch (value) { - case PurchaseReceiptStatus.UNPAID: - return 'پرداخت نشده'; - case PurchaseReceiptStatus.PARTIALLY_PAID: - return 'پرداخت شده جزئی'; - case PurchaseReceiptStatus.PAID: - return 'تسویه شده'; - } -}; - -export const getPurchaseReceiptStatusColor = (value: PurchaseReceiptStatus): TagSeverity => { - switch (value) { - case PurchaseReceiptStatus.UNPAID: - return 'danger'; - case PurchaseReceiptStatus.PARTIALLY_PAID: - return 'warn'; - case PurchaseReceiptStatus.PAID: - return 'success'; - } -}; diff --git a/src/app/shared/catalog/roles/components/index.ts b/src/app/shared/catalog/roles/components/index.ts deleted file mode 100644 index ee993c0..0000000 --- a/src/app/shared/catalog/roles/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './select.component'; -export * from './tag.component'; diff --git a/src/app/shared/catalog/roles/components/select.component.html b/src/app/shared/catalog/roles/components/select.component.html deleted file mode 100644 index be80c62..0000000 --- a/src/app/shared/catalog/roles/components/select.component.html +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/app/shared/catalog/roles/components/select.component.ts b/src/app/shared/catalog/roles/components/select.component.ts deleted file mode 100644 index cae8ba1..0000000 --- a/src/app/shared/catalog/roles/components/select.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Maybe } from '@/core'; -import { UikitFieldComponent } from '@/uikit'; -import { Component, Input, signal } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { Select } from 'primeng/select'; -import { IRoleResponse } from '../models'; -import { RolesService } from '../services'; - -@Component({ - selector: 'catalog-roles-select', - templateUrl: './select.component.html', - imports: [UikitFieldComponent, Select, ReactiveFormsModule], -}) -export class CatalogRolesComponent { - @Input() control!: FormControl>; - - constructor(private service: RolesService) { - this.getData(); - } - - roles = signal([]); - loading = signal(false); - - private getData() { - this.loading.set(true); - return this.service.getAll().subscribe({ - next: (res) => { - this.roles.set(res); - }, - error: () => { - this.roles.set([]); - }, - complete: () => { - this.loading.set(false); - }, - }); - } -} diff --git a/src/app/shared/catalog/roles/components/tag.component.html b/src/app/shared/catalog/roles/components/tag.component.html deleted file mode 100644 index 0ea9250..0000000 --- a/src/app/shared/catalog/roles/components/tag.component.html +++ /dev/null @@ -1 +0,0 @@ -{{ roleTitle() }} diff --git a/src/app/shared/catalog/roles/components/tag.component.ts b/src/app/shared/catalog/roles/components/tag.component.ts deleted file mode 100644 index 451711d..0000000 --- a/src/app/shared/catalog/roles/components/tag.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { IRoleResponse } from '../models'; - -@Component({ - selector: 'catalog-role-tag', - templateUrl: './tag.component.html', -}) -export class CatalogRoleTagComponent { - @Input() role!: IRoleResponse; - - constructor() {} - - roleTitle() { - return this.role.name; - } -} diff --git a/src/app/shared/catalog/roles/constants/apiRoutes/index.ts b/src/app/shared/catalog/roles/constants/apiRoutes/index.ts deleted file mode 100644 index 77d6a49..0000000 --- a/src/app/shared/catalog/roles/constants/apiRoutes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -const baseUrl = '/api/v1/roles'; - -export const ROLES_API_ROUTES = { - list: () => `${baseUrl}`, -}; diff --git a/src/app/shared/catalog/roles/index.ts b/src/app/shared/catalog/roles/index.ts deleted file mode 100644 index 5872a53..0000000 --- a/src/app/shared/catalog/roles/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './components'; -export * from './constants'; -export * from './models'; -export * from './services'; diff --git a/src/app/shared/catalog/roles/models/io.ts b/src/app/shared/catalog/roles/models/io.ts deleted file mode 100644 index 2670050..0000000 --- a/src/app/shared/catalog/roles/models/io.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IRoleRawResponse { - id: number; - name: string; - description: string; -} -export interface IRoleResponse extends IRoleRawResponse {} diff --git a/src/app/shared/catalog/roles/services/index.ts b/src/app/shared/catalog/roles/services/index.ts deleted file mode 100644 index 32ea052..0000000 --- a/src/app/shared/catalog/roles/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './main.service'; diff --git a/src/app/shared/catalog/roles/services/main.service.ts b/src/app/shared/catalog/roles/services/main.service.ts deleted file mode 100644 index e1da5fb..0000000 --- a/src/app/shared/catalog/roles/services/main.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IPaginatedResponse } from '@/core/models/service.model'; -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { map, Observable } from 'rxjs'; -import { ROLES_API_ROUTES } from '../constants'; -import { IRoleRawResponse, IRoleResponse } from '../models'; - -@Injectable({ providedIn: 'root' }) -export class RolesService { - constructor(private http: HttpClient) {} - - private apiRoutes = ROLES_API_ROUTES; - - getAll(): Observable { - return this.http - .get>(this.apiRoutes.list()) - .pipe(map((response) => response.data as IRoleResponse[])); - } -} diff --git a/src/app/shared/catalog/transactionReferenceTypes/index.ts b/src/app/shared/catalog/transactionReferenceTypes/index.ts deleted file mode 100644 index 7d1f2fb..0000000 --- a/src/app/shared/catalog/transactionReferenceTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/transactionReferenceTypes/tag.component.html b/src/app/shared/catalog/transactionReferenceTypes/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/transactionReferenceTypes/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/transactionReferenceTypes/tag.component.ts b/src/app/shared/catalog/transactionReferenceTypes/tag.component.ts deleted file mode 100644 index 00810c9..0000000 --- a/src/app/shared/catalog/transactionReferenceTypes/tag.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { BankTransactionReferenceType } from './types'; -import { - getBankAccountTransactionReferenceTypeColor, - getBankAccountTransactionReferenceTypeText, -} from './utils'; - -@Component({ - selector: 'catalog-bank-account-transaction-reference-type-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogBankAccountTransactionReferenceTypeTagComponent { - @Input() type!: BankTransactionReferenceType; - constructor() {} - - get text() { - return getBankAccountTransactionReferenceTypeText(this.type); - } - get color() { - return getBankAccountTransactionReferenceTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/transactionReferenceTypes/types.ts b/src/app/shared/catalog/transactionReferenceTypes/types.ts deleted file mode 100644 index 5339ca4..0000000 --- a/src/app/shared/catalog/transactionReferenceTypes/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const BankTransactionReferenceType = { - PURCHASE_PAYMENT: 'PURCHASE_PAYMENT', - PURCHASE_REFUND: 'PURCHASE_REFUND', - POS_SALE: 'POS_SALE', - POS_REFUND: 'POS_REFUND', - BANK_TRANSFER: 'BANK_TRANSFER', - MANUAL_ADJUSTMENT: 'MANUAL_ADJUSTMENT', -} as const; - -export type BankTransactionReferenceType = - (typeof BankTransactionReferenceType)[keyof typeof BankTransactionReferenceType]; diff --git a/src/app/shared/catalog/transactionReferenceTypes/utils.ts b/src/app/shared/catalog/transactionReferenceTypes/utils.ts deleted file mode 100644 index 4f04099..0000000 --- a/src/app/shared/catalog/transactionReferenceTypes/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { BankTransactionReferenceType } from './types'; - -export const getBankAccountTransactionReferenceTypeText = ( - type: BankTransactionReferenceType, -): string => { - switch (type) { - case BankTransactionReferenceType.BANK_TRANSFER: - return 'انتقال بانکی'; - case BankTransactionReferenceType.MANUAL_ADJUSTMENT: - return 'تعدیل دستی'; - case BankTransactionReferenceType.POS_REFUND: - return 'بازپرداخت فروشگاه'; - case BankTransactionReferenceType.POS_SALE: - return 'فروش فروشگاه'; - case BankTransactionReferenceType.PURCHASE_REFUND: - return 'بازپرداخت خرید'; - case BankTransactionReferenceType.PURCHASE_PAYMENT: - return 'پرداخت خرید'; - } -}; - -export const getBankAccountTransactionReferenceTypeColor = ( - type: BankTransactionReferenceType, -): TagSeverity => { - switch (type) { - case BankTransactionReferenceType.BANK_TRANSFER: - return 'secondary'; - case BankTransactionReferenceType.MANUAL_ADJUSTMENT: - return 'secondary'; - case BankTransactionReferenceType.POS_REFUND: - return 'warn'; - case BankTransactionReferenceType.POS_SALE: - return 'info'; - case BankTransactionReferenceType.PURCHASE_REFUND: - return 'warn'; - case BankTransactionReferenceType.PURCHASE_PAYMENT: - return 'info'; - } -}; diff --git a/src/app/shared/catalog/transactionTypes/index.ts b/src/app/shared/catalog/transactionTypes/index.ts deleted file mode 100644 index 7d1f2fb..0000000 --- a/src/app/shared/catalog/transactionTypes/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag.component'; -export * from './types'; -export * from './utils'; diff --git a/src/app/shared/catalog/transactionTypes/tag.component.html b/src/app/shared/catalog/transactionTypes/tag.component.html deleted file mode 100644 index d481d6b..0000000 --- a/src/app/shared/catalog/transactionTypes/tag.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - {{ text }} - diff --git a/src/app/shared/catalog/transactionTypes/tag.component.ts b/src/app/shared/catalog/transactionTypes/tag.component.ts deleted file mode 100644 index acebf01..0000000 --- a/src/app/shared/catalog/transactionTypes/tag.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Tag } from 'primeng/tag'; -import { BankAccountTransactionType } from './types'; -import { getBankAccountTransactionTypeColor, getBankAccountTransactionTypeText } from './utils'; - -@Component({ - selector: 'catalog-bank-account-transaction-type-tag', - templateUrl: './tag.component.html', - imports: [Tag], -}) -export class CatalogBankAccountTransactionTypeTagComponent { - @Input() type!: BankAccountTransactionType; - constructor() {} - - get text() { - return getBankAccountTransactionTypeText(this.type); - } - get color() { - return getBankAccountTransactionTypeColor(this.type); - } -} diff --git a/src/app/shared/catalog/transactionTypes/types.ts b/src/app/shared/catalog/transactionTypes/types.ts deleted file mode 100644 index 10be377..0000000 --- a/src/app/shared/catalog/transactionTypes/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const BankAccountTransactionType = { - DEPOSIT: 'DEPOSIT', - WITHDRAWAL: 'WITHDRAWAL', -} as const; - -export type BankAccountTransactionType = - (typeof BankAccountTransactionType)[keyof typeof BankAccountTransactionType]; diff --git a/src/app/shared/catalog/transactionTypes/utils.ts b/src/app/shared/catalog/transactionTypes/utils.ts deleted file mode 100644 index 7c8a17c..0000000 --- a/src/app/shared/catalog/transactionTypes/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TagSeverity } from '@/core/models/tag'; -import { BankAccountTransactionType } from './types'; - -export const getBankAccountTransactionTypeText = (type: BankAccountTransactionType): string => { - switch (type) { - case BankAccountTransactionType.DEPOSIT: - return 'واریز'; - case BankAccountTransactionType.WITHDRAWAL: - return 'برداشت'; - } -}; - -export const getBankAccountTransactionTypeColor = ( - type: BankAccountTransactionType, -): TagSeverity => { - switch (type) { - case BankAccountTransactionType.DEPOSIT: - return 'success'; - case BankAccountTransactionType.WITHDRAWAL: - return 'danger'; - } -}; diff --git a/src/app/shared/components/abstract-form.ts b/src/app/shared/components/abstract-form.ts deleted file mode 100644 index 3e9e169..0000000 --- a/src/app/shared/components/abstract-form.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ToastService } from '@/core/services/toast.service'; -import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; -import { Subscribable } from 'rxjs'; - -@Component({ - selector: 'abstract-form-dialog', - template: '', - imports: [ReactiveFormsModule], -}) -export abstract class AbstractForm { - @Input() initialValues?: Response; - - @Output() onSubmit = new EventEmitter(); - @Output() onClose = new EventEmitter(); - - protected readonly fb = inject(FormBuilder); - constructor(private toastService: ToastService) {} - - abstract form: ReturnType; - defaultValues: Partial = {}; - - abstract submitForm(payload: Request): Subscribable | void; - - submitLoading = signal(false); - - submit() { - this.form.markAllAsTouched(); - if (this.form.valid) { - this.form.disable(); - this.submitLoading.set(true); - this.submitForm(this.form.value as Request)?.subscribe({ - next: (res) => { - this.toastService.success({ - text: `با موفقیت ایجاد شد`, - }); - this.submitLoading.set(false); - this.form.enable(); - this.form.reset(); - this.onSubmit.emit(res); - }, - error: () => { - this.submitLoading.set(false); - this.form.enable(); - }, - complete: () => { - this.submitLoading.set(false); - this.form.enable(); - }, - }); - } - } - - close() { - this.onClose.emit(); - } -} diff --git a/src/app/shared/components/card-data.component.html b/src/app/shared/components/card-data.component.html index ece8684..e933b0a 100644 --- a/src/app/shared/components/card-data.component.html +++ b/src/app/shared/components/card-data.component.html @@ -2,7 +2,7 @@
- {{ cardTitle }} +
{{ cardTitle }}
@if (editable) { diff --git a/src/app/shared/components/card-data.component.ts b/src/app/shared/components/card-data.component.ts index 7765a31..a494d5d 100644 --- a/src/app/shared/components/card-data.component.ts +++ b/src/app/shared/components/card-data.component.ts @@ -1,6 +1,14 @@ import { Maybe } from '@/core'; import { CommonModule } from '@angular/common'; -import { Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; +import { + Component, + ContentChild, + EventEmitter, + Input, + Output, + signal, + TemplateRef, +} from '@angular/core'; import { ButtonDirective } from 'primeng/button'; import { Card } from 'primeng/card'; @@ -13,22 +21,30 @@ import { Card } from 'primeng/card'; export class AppCardComponent { @Input() cardTitle!: string; @Input() editable: boolean = false; - @Input() editMode: boolean = false; - @Output() onEdit = new EventEmitter(); + @Input() + set editMode(v: boolean) { + this.editModeSignal.set(!!v); + this.editModeChange.emit(!!v); + } + get editMode() { + return this.editModeSignal(); + } + private editModeSignal = signal(false); + @Output() editModeChange = new EventEmitter(); @ContentChild('header', { static: true }) header?: Maybe>; @ContentChild('moreAction', { static: true }) moreAction?: Maybe>; - constructor() { - if (this.editable) { - if (!this.onEdit) { - throw new Error('onEdit output must be bound when editable is true'); - } - } - } + // constructor() { + // if (this.editable) { + // if (!this.ed) { + // throw new Error('onEdit output must be bound when editable is true'); + // } + // } + // } onEditClick() { - this.onEdit.emit(); + this.editModeChange.emit(!this.editMode); } } diff --git a/src/app/shared/components/cardex/cardex.component.html b/src/app/shared/components/cardex/cardex.component.html deleted file mode 100644 index a35bd30..0000000 --- a/src/app/shared/components/cardex/cardex.component.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - ردیف - تاریخ - شرح - شماره - طرف حساب - انبار - ورودی - خروجی - مانده - - - تعداد - فی - تعداد - فی - تعداد - - - - - - - {{ i + 1 }} - - - - - {{ item.referenceId || "-" }} - {{ item.supplier?.name || "-" }} - {{ item.inventory.name || "-" }} - - {{ item.type === "IN" ? item.quantity : 0 }} - - - @if (item.type === "IN") { - - } @else { - 0 - } - - - {{ item.type === "OUT" ? item.quantity : 0 }} - - - @if (item.type === "OUT") { - - } @else { - 0 - } - - {{ item.remainedInStock }} - - - - - - - - - - - - diff --git a/src/app/shared/components/cardex/cardex.component.ts b/src/app/shared/components/cardex/cardex.component.ts deleted file mode 100644 index 303d280..0000000 --- a/src/app/shared/components/cardex/cardex.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { CatalogMovementReferenceTagComponent } from '@/shared/catalog/movementReferenceTypes'; -import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives'; -import { UikitEmptyStateComponent } from '@/uikit'; -import { Component, Input } from '@angular/core'; -import { TableModule } from 'primeng/table'; -import { ICardexItem } from './type'; - -@Component({ - selector: 'shared-cardex', - templateUrl: './cardex.component.html', - imports: [ - TableModule, - JalaliDateDirective, - CatalogMovementReferenceTagComponent, - PriceMaskDirective, - UikitEmptyStateComponent, - ], - hostDirectives: [PriceMaskDirective], -}) -export class CardexComponent { - @Input() items: ICardexItem[] = []; - @Input() loading: boolean = false; - constructor() {} -} diff --git a/src/app/shared/components/cardex/type.ts b/src/app/shared/components/cardex/type.ts deleted file mode 100644 index 2909844..0000000 --- a/src/app/shared/components/cardex/type.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IInventoryStockMovementResponse } from '@/modules/inventories/models'; - -export interface ICardexItem extends IInventoryStockMovementResponse {} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 919a41b..e3870c1 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -1,4 +1,4 @@ -export * from './abstract-select.component'; +// export * from './abstract-select.component'; export * from './breadcrumb.component'; export * from './card-data.component'; export * from './inlineConfirmation/inline-confirmation.component'; diff --git a/src/app/shared/components/pageDataList/page-data-list.component.html b/src/app/shared/components/pageDataList/page-data-list.component.html index 0ebd3b9..a340bb7 100644 --- a/src/app/shared/components/pageDataList/page-data-list.component.html +++ b/src/app/shared/components/pageDataList/page-data-list.component.html @@ -1,4 +1,4 @@ -
+
{ @Component({ selector: 'app-page-data-list', templateUrl: './page-data-list.component.html', + // host: { + // class: 'inline-block w-full', + // }, imports: [ CommonModule, TableActionRowComponent, @@ -46,7 +51,10 @@ export interface IColumn { ], }) export class PageDataListComponent { - constructor() {} + constructor( + private host: ElementRef, + private renderer: Renderer2, + ) {} @Input() pageTitle!: string; @Input() addNewCtaLabel?: string; @@ -64,7 +72,8 @@ export class PageDataListComponent { @Input() showDetails: boolean = false; @Input() showAdd: boolean = false; @Input() isFiltered: boolean = false; - @Input() fullHeight: boolean = false; + @Input() fullHeight?: boolean = false; + @Input() height: string = '500px'; @ContentChild('filter', { static: true }) filter!: TemplateRef | null; @ContentChild('caption', { static: true }) caption!: TemplateRef | null; @@ -78,8 +87,15 @@ export class PageDataListComponent { filterDrawerVisible = signal(false); ngOnInit() { + console.log(this.fullHeight); + console.log(this.height); + + this.renderer.setStyle( + this.host.nativeElement, + 'height', + this.fullHeight ? '100cqmin' : this.height, + ); // if (this.fullHeight) { - // this.renderer.setStyle(this.host.nativeElement, 'height', '100cqmin'); // } } diff --git a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html index 92260b4..39d7a06 100644 --- a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html +++ b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html @@ -1,4 +1,4 @@ -@if (showPaymentForm()) { + diff --git a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts index 02a0fb2..0fcbd94 100644 --- a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts +++ b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts @@ -1,55 +1,55 @@ -import { ToastService } from '@/core/services/toast.service'; -import { SupplierInvoicePayFormComponent } from '@/modules/suppliers/components/invoices/pay-form.component'; -import { Component, EventEmitter, Input, Output, signal } from '@angular/core'; -import { IPurchaseReceiptResponse } from '../../../modules/purchases/models'; -import { ConfirmationDialogService } from '../confirmationDialog/confirmation-dialog.service'; +// import { ToastService } from '@/core/services/toast.service'; +// import { SupplierInvoicePayFormComponent } from '@/modules/suppliers/components/invoices/pay-form.component'; +// import { Component, EventEmitter, Input, Output, signal } from '@angular/core'; +// import { IPurchaseReceiptResponse } from '../../../modules/purchases/models'; +// import { ConfirmationDialogService } from '../confirmationDialog/confirmation-dialog.service'; -@Component({ - selector: 'app-purchase-receipt-payment-wrapper', - templateUrl: './wrapper.component.html', - imports: [SupplierInvoicePayFormComponent], -}) -export class PurchaseReceiptPaymentWrapperComponent { - @Input() purchaseReceipt!: IPurchaseReceiptResponse; +// @Component({ +// selector: 'app-purchase-receipt-payment-wrapper', +// templateUrl: './wrapper.component.html', +// imports: [SupplierInvoicePayFormComponent], +// }) +// export class PurchaseReceiptPaymentWrapperComponent { +// @Input() purchaseReceipt!: IPurchaseReceiptResponse; - @Output() onSubmit = new EventEmitter(); +// @Output() onSubmit = new EventEmitter(); - showPaymentForm = signal(false); - constructor( - private confirmService: ConfirmationDialogService, - private toastService: ToastService, - ) { - setTimeout(() => { - this.showConfirm(); - }, 1); - } +// showPaymentForm = signal(false); +// constructor( +// private confirmService: ConfirmationDialogService, +// private toastService: ToastService, +// ) { +// setTimeout(() => { +// this.showConfirm(); +// }, 1); +// } - showConfirm() { - this.confirmService.confirm({ - message: 'آیا پرداخت فاکتور انجام شده است؟', - header: 'وضعیت پرداخت فاکتور', - acceptLabel: 'بله', - rejectLabel: 'خیر', - accept: () => { - this.toPaymentForm(); - }, - reject: () => { - this.toastService.info({ - text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.', - title: 'پرداخت فاکتور انجام نشد', - }); - }, - }); - } +// showConfirm() { +// this.confirmService.confirm({ +// message: 'آیا پرداخت فاکتور انجام شده است؟', +// header: 'وضعیت پرداخت فاکتور', +// acceptLabel: 'بله', +// rejectLabel: 'خیر', +// accept: () => { +// this.toPaymentForm(); +// }, +// reject: () => { +// this.toastService.info({ +// text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.', +// title: 'پرداخت فاکتور انجام نشد', +// }); +// }, +// }); +// } - submitted() { - console.log('submitted'); +// submitted() { +// console.log('submitted'); - this.showPaymentForm.set(false); - this.onSubmit.emit(); - } +// this.showPaymentForm.set(false); +// this.onSubmit.emit(); +// } - toPaymentForm() { - this.showPaymentForm.set(true); - } -} +// toPaymentForm() { +// this.showPaymentForm.set(true); +// } +// } diff --git a/src/app/uikit/select-field/select-field.component.html b/src/app/uikit/select-field/select-field.component.html new file mode 100644 index 0000000..d3afd0c --- /dev/null +++ b/src/app/uikit/select-field/select-field.component.html @@ -0,0 +1,32 @@ + diff --git a/src/app/uikit/select-field/select-field.component.ts b/src/app/uikit/select-field/select-field.component.ts new file mode 100644 index 0000000..79892ec --- /dev/null +++ b/src/app/uikit/select-field/select-field.component.ts @@ -0,0 +1,33 @@ +// import { Maybe } from '@/core'; +// import { UikitFieldComponent } from '@/uikit'; +// import { Component, EventEmitter, Input, model, Output, signal } from '@angular/core'; +// import { FormControl, ReactiveFormsModule } from '@angular/forms'; +// import { Button } from 'primeng/button'; +// import { Select } from 'primeng/select'; +// import { Observable } from 'rxjs'; + +// export interface ISelectItem { +// id: number; +// name: string; +// } + +// @Component({ +// selector: 'uikit-select-field', +// templateUrl: 'select-field.component.html', +// imports: [UikitFieldComponent, Select, Button, ReactiveFormsModule], +// }) +// export class SelectComponent extends AbstractSelectComponent { +// @Input() control = new FormControl>(null); +// @Input() canInsert: boolean = false; +// @Input() showLabel: boolean = true; +// @Input() showErrors: boolean = true; +// @Input() showClear: boolean = true; +// @Input() isFullDataOptionValue: boolean = false; +// @Input() optionValue = 'id'; +// @Input() optionLabel: string = 'name'; +// @Input() label!: string; +// @Input() placeholder!: string; +// @Input() addCtaLabel: string = 'ایجاد مورد جدید'; +// @Output() onChange = new EventEmitter>(); +// @Output() onSetDefaultDataItem = new EventEmitter(); +// } diff --git a/src/app/uikit/uikit-emptystate.component.html b/src/app/uikit/uikit-emptystate.component.html index 06dcd1e..9daab22 100644 --- a/src/app/uikit/uikit-emptystate.component.html +++ b/src/app/uikit/uikit-emptystate.component.html @@ -1,6 +1,6 @@ -
+
- + {{ title }} @if (description) { @@ -8,7 +8,7 @@ } @if (!hasCtaContent && ctaLabel && showCTA) { - + {{ ctaLabel }} diff --git a/src/assets/constants/userRoles.const.ts b/src/assets/constants/userRoles.const.ts index 95dd243..b172379 100644 --- a/src/assets/constants/userRoles.const.ts +++ b/src/assets/constants/userRoles.const.ts @@ -1,8 +1,7 @@ export enum ROLES { ADMIN = 'ADMIN', - SCHOOL = 'SCHOOL', - TEACHER = 'TEACHER', - STUDENTS = 'STUDENTS', - GRADER = 'GRADER', SUPERADMIN = 'SUPERADMIN', + POS = 'POS', + PARTNER = 'PARTNER', + PROVIDER = 'PROVIDER', } diff --git a/src/assets/layout/_typography.scss b/src/assets/layout/_typography.scss index 1742e9a..690643d 100644 --- a/src/assets/layout/_typography.scss +++ b/src/assets/layout/_typography.scss @@ -48,7 +48,7 @@ blockquote { hr { border-top: solid var(--surface-border); border-width: 1px 0 0 0; - margin: 1rem 0; + margin: 0.5rem 0; } p { diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 15584fc..782a2af 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,9 +1,9 @@ // Development environment configuration export const environment = { production: false, - apiBaseUrl: 'http://localhost:3000', + apiBaseUrl: 'http://localhost:5002', host: 'localhost', - port: 60749, + port: 5000, enableLogging: true, enableDebug: true, }; diff --git a/src/presets.ts b/src/presets.ts index c311f2d..71c0c52 100644 --- a/src/presets.ts +++ b/src/presets.ts @@ -23,6 +23,12 @@ const MyPreset = definePreset(Aura, { fontSize: '0.875rem', }, }, + + card: { + body: { + padding: '0.875rem 1rem', + }, + }, }, semantic: { primary: {