diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..04bc49e Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000..107a390 Binary files /dev/null and b/public/favicon/apple-touch-icon.png differ diff --git a/public/favicon/favicon-96x96.png b/public/favicon/favicon-96x96.png new file mode 100644 index 0000000..9cc83f1 Binary files /dev/null and b/public/favicon/favicon-96x96.png differ diff --git a/public/favicon/favicon.ico b/public/favicon/favicon.ico new file mode 100644 index 0000000..04bc49e Binary files /dev/null and b/public/favicon/favicon.ico differ diff --git a/public/favicon/favicon.svg b/public/favicon/favicon.svg new file mode 100644 index 0000000..9d8298e --- /dev/null +++ b/public/favicon/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/public/favicon/site.webmanifest b/public/favicon/site.webmanifest new file mode 100644 index 0000000..edc6ed1 --- /dev/null +++ b/public/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "صندوق فروشگاهی", + "short_name": "صندوق فروشگاهی", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/favicon/web-app-manifest-192x192.png b/public/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000..56bb138 Binary files /dev/null and b/public/favicon/web-app-manifest-192x192.png differ diff --git a/public/favicon/web-app-manifest-512x512.png b/public/favicon/web-app-manifest-512x512.png new file mode 100644 index 0000000..f82b4c3 Binary files /dev/null and b/public/favicon/web-app-manifest-512x512.png differ diff --git a/src/app.routes.ts b/src/app.routes.ts index 96e5ba3..e4d32e9 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -1,23 +1,35 @@ +import { CUSTOMERS_ROUTES } from '@/modules/customers/constants'; +import { INVENTORIES_ROUTES } from '@/modules/inventories/constants'; +import { PRODUCT_BRANDS_ROUTES } from '@/modules/productBrands/constants'; +import { PRODUCT_CATEGORIES_ROUTES } from '@/modules/productCategories/constants'; +import { STORES_ROUTES } from '@/modules/stores/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'; +import { AppLayout } from './app/layout/component/app.layout.component'; import { Dashboard } from './app/pages/dashboard/dashboard'; import { Documentation } from './app/pages/documentation/documentation'; -import { Landing } from './app/pages/landing/landing'; -import { Notfound } from './app/pages/notfound/notfound'; +import { Notfound } from './app/pages/notfound/notfound.component'; export const appRoutes: Routes = [ - { - path: '', - component: AppLayout, - children: [ - { path: '', component: Dashboard }, - { path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') }, - { path: 'documentation', component: Documentation }, - { path: 'pages', loadChildren: () => import('./app/pages/pages.routes') } - ] - }, - { path: 'landing', component: Landing }, - { path: 'notfound', component: Notfound }, - { path: 'auth', loadChildren: () => import('./app/pages/auth/auth.routes') }, - { path: '**', redirectTo: '/notfound' } + { + path: '', + component: AppLayout, + children: [ + { path: '', component: Dashboard }, + ...USERS_ROUTES, + ...STORES_ROUTES, + ...SUPPLIERS_ROUTES, + ...PRODUCT_BRANDS_ROUTES, + ...PRODUCT_CATEGORIES_ROUTES, + ...CUSTOMERS_ROUTES, + ...INVENTORIES_ROUTES, + { path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') }, + { path: 'documentation', component: Documentation }, + { path: 'pages', loadChildren: () => import('./app/pages/pages.routes') }, + ], + }, + { path: 'notfound', component: Notfound }, + { path: 'auth', loadChildren: () => import('./app/pages/auth/auth.routes') }, + { path: '**', redirectTo: '/notfound' }, ]; diff --git a/src/app/core/constants/index.ts b/src/app/core/constants/index.ts index eb1aa70..c826a2c 100644 --- a/src/app/core/constants/index.ts +++ b/src/app/core/constants/index.ts @@ -1,2 +1,3 @@ export * from './defaultData.const'; +export * from './menuItems.const'; export * from './roles.const'; diff --git a/src/app/core/constants/menuItems.const.ts b/src/app/core/constants/menuItems.const.ts new file mode 100644 index 0000000..d616106 --- /dev/null +++ b/src/app/core/constants/menuItems.const.ts @@ -0,0 +1,71 @@ +import { MenuItem } from 'primeng/api'; +export const MENU_ITEMS = [ + { + items: [ + { + label: 'داشبورد', + icon: 'pi pi-fw pi-home', + routerLink: ['/dashboard'], + }, + ], + }, + { + 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-store', + routerLink: ['/stores'], + }, + { + label: 'کاربران', + icon: 'pi pi-fw pi-user', + routerLink: ['/users'], + }, + ], + }, +] as MenuItem[]; diff --git a/src/app/layout/component/app.configurator.component.html b/src/app/layout/component/app.configurator.component.html new file mode 100644 index 0000000..2ce1b60 --- /dev/null +++ b/src/app/layout/component/app.configurator.component.html @@ -0,0 +1,49 @@ +
+
+ Primary +
+ @for (primaryColor of primaryColors(); track primaryColor.name) { + + } +
+
+
+ Surface +
+ @for (surface of surfaces; track surface.name) { + + } +
+
+
+ Presets + +
+
+ Menu Mode + +
+
diff --git a/src/app/layout/component/app.configurator.component.ts b/src/app/layout/component/app.configurator.component.ts new file mode 100644 index 0000000..ce0a696 --- /dev/null +++ b/src/app/layout/component/app.configurator.component.ts @@ -0,0 +1,424 @@ +import { CommonModule, isPlatformBrowser } from '@angular/common'; +import { Component, computed, inject, PLATFORM_ID, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { $t, updatePreset, updateSurfacePalette } from '@primeuix/themes'; +import Aura from '@primeuix/themes/aura'; +import Lara from '@primeuix/themes/lara'; +import Nora from '@primeuix/themes/nora'; +import { PrimeNG } from 'primeng/config'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { LayoutService } from '../service/layout.service'; + +const presets = { + Aura, + Lara, + Nora, +} as const; + +declare type KeyOfType = keyof T extends infer U ? U : never; + +declare type SurfacesType = { + name?: string; + palette?: { + 0?: string; + 50?: string; + 100?: string; + 200?: string; + 300?: string; + 400?: string; + 500?: string; + 600?: string; + 700?: string; + 800?: string; + 900?: string; + 950?: string; + }; +}; + +@Component({ + selector: 'app-configurator', + standalone: true, + imports: [CommonModule, FormsModule, SelectButtonModule], + templateUrl: './app.configurator.component.html', + host: { + class: + 'hidden absolute top-13 right-0 w-72 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]', + }, +}) +export class AppConfigurator { + router = inject(Router); + + config: PrimeNG = inject(PrimeNG); + + layoutService: LayoutService = inject(LayoutService); + + platformId = inject(PLATFORM_ID); + + primeng = inject(PrimeNG); + + presets = Object.keys(presets); + + showMenuModeButton = signal(!this.router.url.includes('auth')); + + menuModeOptions = [ + { label: 'Static', value: 'static' }, + { label: 'Overlay', value: 'overlay' }, + ]; + + ngOnInit() { + if (isPlatformBrowser(this.platformId)) { + this.onPresetChange(this.layoutService.layoutConfig().preset); + } + } + + surfaces: SurfacesType[] = [ + { + name: 'slate', + palette: { + 0: '#ffffff', + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', + 950: '#020617', + }, + }, + { + name: 'gray', + palette: { + 0: '#ffffff', + 50: '#f9fafb', + 100: '#f3f4f6', + 200: '#e5e7eb', + 300: '#d1d5db', + 400: '#9ca3af', + 500: '#6b7280', + 600: '#4b5563', + 700: '#374151', + 800: '#1f2937', + 900: '#111827', + 950: '#030712', + }, + }, + { + name: 'zinc', + palette: { + 0: '#ffffff', + 50: '#fafafa', + 100: '#f4f4f5', + 200: '#e4e4e7', + 300: '#d4d4d8', + 400: '#a1a1aa', + 500: '#71717a', + 600: '#52525b', + 700: '#3f3f46', + 800: '#27272a', + 900: '#18181b', + 950: '#09090b', + }, + }, + { + name: 'neutral', + palette: { + 0: '#ffffff', + 50: '#fafafa', + 100: '#f5f5f5', + 200: '#e5e5e5', + 300: '#d4d4d4', + 400: '#a3a3a3', + 500: '#737373', + 600: '#525252', + 700: '#404040', + 800: '#262626', + 900: '#171717', + 950: '#0a0a0a', + }, + }, + { + name: 'stone', + palette: { + 0: '#ffffff', + 50: '#fafaf9', + 100: '#f5f5f4', + 200: '#e7e5e4', + 300: '#d6d3d1', + 400: '#a8a29e', + 500: '#78716c', + 600: '#57534e', + 700: '#44403c', + 800: '#292524', + 900: '#1c1917', + 950: '#0c0a09', + }, + }, + { + name: 'soho', + palette: { + 0: '#ffffff', + 50: '#ececec', + 100: '#dedfdf', + 200: '#c4c4c6', + 300: '#adaeb0', + 400: '#97979b', + 500: '#7f8084', + 600: '#6a6b70', + 700: '#55565b', + 800: '#3f4046', + 900: '#2c2c34', + 950: '#16161d', + }, + }, + { + name: 'viva', + palette: { + 0: '#ffffff', + 50: '#f3f3f3', + 100: '#e7e7e8', + 200: '#cfd0d0', + 300: '#b7b8b9', + 400: '#9fa1a1', + 500: '#87898a', + 600: '#6e7173', + 700: '#565a5b', + 800: '#3e4244', + 900: '#262b2c', + 950: '#0e1315', + }, + }, + { + name: 'ocean', + palette: { + 0: '#ffffff', + 50: '#fbfcfc', + 100: '#F7F9F8', + 200: '#EFF3F2', + 300: '#DADEDD', + 400: '#B1B7B6', + 500: '#828787', + 600: '#5F7274', + 700: '#415B61', + 800: '#29444E', + 900: '#183240', + 950: '#0c1920', + }, + }, + ]; + + selectedPrimaryColor = computed(() => { + return this.layoutService.layoutConfig().primary; + }); + + selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface); + + selectedPreset = computed(() => this.layoutService.layoutConfig().preset); + + menuMode = computed(() => this.layoutService.layoutConfig().menuMode); + + primaryColors = computed(() => { + const presetPalette = + presets[this.layoutService.layoutConfig().preset as KeyOfType].primitive; + const colors = [ + 'emerald', + 'green', + 'lime', + 'orange', + 'amber', + 'yellow', + 'teal', + 'cyan', + 'sky', + 'blue', + 'indigo', + 'violet', + 'purple', + 'fuchsia', + 'pink', + 'rose', + ]; + const palettes: SurfacesType[] = [{ name: 'noir', palette: {} }]; + + colors.forEach((color) => { + palettes.push({ + name: color, + palette: presetPalette?.[ + color as KeyOfType + ] as SurfacesType['palette'], + }); + }); + + return palettes; + }); + + getPresetExt() { + const color: SurfacesType = + this.primaryColors().find((c) => c.name === this.selectedPrimaryColor()) || {}; + const preset = this.layoutService.layoutConfig().preset; + + if (color.name === 'noir') { + return { + semantic: { + primary: { + 50: '{surface.50}', + 100: '{surface.100}', + 200: '{surface.200}', + 300: '{surface.300}', + 400: '{surface.400}', + 500: '{surface.500}', + 600: '{surface.600}', + 700: '{surface.700}', + 800: '{surface.800}', + 900: '{surface.900}', + 950: '{surface.950}', + }, + colorScheme: { + light: { + primary: { + color: '{primary.950}', + contrastColor: '#ffffff', + hoverColor: '{primary.800}', + activeColor: '{primary.700}', + }, + highlight: { + background: '{primary.950}', + focusBackground: '{primary.700}', + color: '#ffffff', + focusColor: '#ffffff', + }, + }, + dark: { + primary: { + color: '{primary.50}', + contrastColor: '{primary.950}', + hoverColor: '{primary.200}', + activeColor: '{primary.300}', + }, + highlight: { + background: '{primary.50}', + focusBackground: '{primary.300}', + color: '{primary.950}', + focusColor: '{primary.950}', + }, + }, + }, + }, + }; + } else { + if (preset === 'Nora') { + return { + semantic: { + primary: color.palette, + colorScheme: { + light: { + primary: { + color: '{primary.600}', + contrastColor: '#ffffff', + hoverColor: '{primary.700}', + activeColor: '{primary.800}', + }, + highlight: { + background: '{primary.600}', + focusBackground: '{primary.700}', + color: '#ffffff', + focusColor: '#ffffff', + }, + }, + dark: { + primary: { + color: '{primary.500}', + contrastColor: '{surface.900}', + hoverColor: '{primary.400}', + activeColor: '{primary.300}', + }, + highlight: { + background: '{primary.500}', + focusBackground: '{primary.400}', + color: '{surface.900}', + focusColor: '{surface.900}', + }, + }, + }, + }, + }; + } else { + return { + semantic: { + primary: color.palette, + colorScheme: { + light: { + primary: { + color: '{primary.500}', + contrastColor: '#ffffff', + hoverColor: '{primary.600}', + activeColor: '{primary.700}', + }, + highlight: { + background: '{primary.50}', + focusBackground: '{primary.100}', + color: '{primary.700}', + focusColor: '{primary.800}', + }, + }, + dark: { + primary: { + color: '{primary.400}', + contrastColor: '{surface.900}', + hoverColor: '{primary.300}', + activeColor: '{primary.200}', + }, + highlight: { + background: 'color-mix(in srgb, {primary.400}, transparent 84%)', + focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)', + color: 'rgba(255,255,255,.87)', + focusColor: 'rgba(255,255,255,.87)', + }, + }, + }, + }, + }; + } + } + } + + updateColors(event: any, type: string, color: any) { + if (type === 'primary') { + this.layoutService.layoutConfig.update((state) => ({ ...state, primary: color.name })); + } else if (type === 'surface') { + this.layoutService.layoutConfig.update((state) => ({ ...state, surface: color.name })); + } + this.applyTheme(type, color); + + event.stopPropagation(); + } + + applyTheme(type: string, color: any) { + if (type === 'primary') { + updatePreset(this.getPresetExt()); + } else if (type === 'surface') { + updateSurfacePalette(color.palette); + } + } + + onPresetChange(event: any) { + this.layoutService.layoutConfig.update((state) => ({ ...state, preset: event })); + const preset = presets[event as KeyOfType]; + const surfacePalette = this.surfaces.find( + (s) => s.name === this.selectedSurfaceColor(), + )?.palette; + $t() + .preset(preset) + .preset(this.getPresetExt()) + .surfacePalette(surfacePalette) + .use({ useDefaultOptions: true }); + } + + onMenuModeChange(event: string) { + this.layoutService.layoutConfig.update((prev) => ({ ...prev, menuMode: event })); + } +} diff --git a/src/app/layout/component/app.configurator.ts b/src/app/layout/component/app.configurator.ts deleted file mode 100644 index 49d0784..0000000 --- a/src/app/layout/component/app.configurator.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { CommonModule, isPlatformBrowser } from '@angular/common'; -import { Component, computed, inject, PLATFORM_ID, signal } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { Router } from '@angular/router'; -import { $t, updatePreset, updateSurfacePalette } from '@primeuix/themes'; -import Aura from '@primeuix/themes/aura'; -import Lara from '@primeuix/themes/lara'; -import Nora from '@primeuix/themes/nora'; -import { PrimeNG } from 'primeng/config'; -import { SelectButtonModule } from 'primeng/selectbutton'; -import { LayoutService } from '../service/layout.service'; - -const presets = { - Aura, - Lara, - Nora -} as const; - -declare type KeyOfType = keyof T extends infer U ? U : never; - -declare type SurfacesType = { - name?: string; - palette?: { - 0?: string; - 50?: string; - 100?: string; - 200?: string; - 300?: string; - 400?: string; - 500?: string; - 600?: string; - 700?: string; - 800?: string; - 900?: string; - 950?: string; - }; -}; - -@Component({ - selector: 'app-configurator', - standalone: true, - imports: [CommonModule, FormsModule, SelectButtonModule], - template: ` -
-
- Primary -
- @for (primaryColor of primaryColors(); track primaryColor.name) { - - } -
-
-
- Surface -
- @for (surface of surfaces; track surface.name) { - - } -
-
-
- Presets - -
-
- Menu Mode - -
-
- `, - host: { - class: 'hidden absolute top-13 right-0 w-72 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]' - } -}) -export class AppConfigurator { - router = inject(Router); - - config: PrimeNG = inject(PrimeNG); - - layoutService: LayoutService = inject(LayoutService); - - platformId = inject(PLATFORM_ID); - - primeng = inject(PrimeNG); - - presets = Object.keys(presets); - - showMenuModeButton = signal(!this.router.url.includes('auth')); - - menuModeOptions = [ - { label: 'Static', value: 'static' }, - { label: 'Overlay', value: 'overlay' } - ]; - - ngOnInit() { - if (isPlatformBrowser(this.platformId)) { - this.onPresetChange(this.layoutService.layoutConfig().preset); - } - } - - surfaces: SurfacesType[] = [ - { - name: 'slate', - palette: { - 0: '#ffffff', - 50: '#f8fafc', - 100: '#f1f5f9', - 200: '#e2e8f0', - 300: '#cbd5e1', - 400: '#94a3b8', - 500: '#64748b', - 600: '#475569', - 700: '#334155', - 800: '#1e293b', - 900: '#0f172a', - 950: '#020617' - } - }, - { - name: 'gray', - palette: { - 0: '#ffffff', - 50: '#f9fafb', - 100: '#f3f4f6', - 200: '#e5e7eb', - 300: '#d1d5db', - 400: '#9ca3af', - 500: '#6b7280', - 600: '#4b5563', - 700: '#374151', - 800: '#1f2937', - 900: '#111827', - 950: '#030712' - } - }, - { - name: 'zinc', - palette: { - 0: '#ffffff', - 50: '#fafafa', - 100: '#f4f4f5', - 200: '#e4e4e7', - 300: '#d4d4d8', - 400: '#a1a1aa', - 500: '#71717a', - 600: '#52525b', - 700: '#3f3f46', - 800: '#27272a', - 900: '#18181b', - 950: '#09090b' - } - }, - { - name: 'neutral', - palette: { - 0: '#ffffff', - 50: '#fafafa', - 100: '#f5f5f5', - 200: '#e5e5e5', - 300: '#d4d4d4', - 400: '#a3a3a3', - 500: '#737373', - 600: '#525252', - 700: '#404040', - 800: '#262626', - 900: '#171717', - 950: '#0a0a0a' - } - }, - { - name: 'stone', - palette: { - 0: '#ffffff', - 50: '#fafaf9', - 100: '#f5f5f4', - 200: '#e7e5e4', - 300: '#d6d3d1', - 400: '#a8a29e', - 500: '#78716c', - 600: '#57534e', - 700: '#44403c', - 800: '#292524', - 900: '#1c1917', - 950: '#0c0a09' - } - }, - { - name: 'soho', - palette: { - 0: '#ffffff', - 50: '#ececec', - 100: '#dedfdf', - 200: '#c4c4c6', - 300: '#adaeb0', - 400: '#97979b', - 500: '#7f8084', - 600: '#6a6b70', - 700: '#55565b', - 800: '#3f4046', - 900: '#2c2c34', - 950: '#16161d' - } - }, - { - name: 'viva', - palette: { - 0: '#ffffff', - 50: '#f3f3f3', - 100: '#e7e7e8', - 200: '#cfd0d0', - 300: '#b7b8b9', - 400: '#9fa1a1', - 500: '#87898a', - 600: '#6e7173', - 700: '#565a5b', - 800: '#3e4244', - 900: '#262b2c', - 950: '#0e1315' - } - }, - { - name: 'ocean', - palette: { - 0: '#ffffff', - 50: '#fbfcfc', - 100: '#F7F9F8', - 200: '#EFF3F2', - 300: '#DADEDD', - 400: '#B1B7B6', - 500: '#828787', - 600: '#5F7274', - 700: '#415B61', - 800: '#29444E', - 900: '#183240', - 950: '#0c1920' - } - } - ]; - - selectedPrimaryColor = computed(() => { - return this.layoutService.layoutConfig().primary; - }); - - selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface); - - selectedPreset = computed(() => this.layoutService.layoutConfig().preset); - - menuMode = computed(() => this.layoutService.layoutConfig().menuMode); - - primaryColors = computed(() => { - const presetPalette = presets[this.layoutService.layoutConfig().preset as KeyOfType].primitive; - const colors = ['emerald', 'green', 'lime', 'orange', 'amber', 'yellow', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose']; - const palettes: SurfacesType[] = [{ name: 'noir', palette: {} }]; - - colors.forEach((color) => { - palettes.push({ - name: color, - palette: presetPalette?.[color as KeyOfType] as SurfacesType['palette'] - }); - }); - - return palettes; - }); - - getPresetExt() { - const color: SurfacesType = this.primaryColors().find((c) => c.name === this.selectedPrimaryColor()) || {}; - const preset = this.layoutService.layoutConfig().preset; - - if (color.name === 'noir') { - return { - semantic: { - primary: { - 50: '{surface.50}', - 100: '{surface.100}', - 200: '{surface.200}', - 300: '{surface.300}', - 400: '{surface.400}', - 500: '{surface.500}', - 600: '{surface.600}', - 700: '{surface.700}', - 800: '{surface.800}', - 900: '{surface.900}', - 950: '{surface.950}' - }, - colorScheme: { - light: { - primary: { - color: '{primary.950}', - contrastColor: '#ffffff', - hoverColor: '{primary.800}', - activeColor: '{primary.700}' - }, - highlight: { - background: '{primary.950}', - focusBackground: '{primary.700}', - color: '#ffffff', - focusColor: '#ffffff' - } - }, - dark: { - primary: { - color: '{primary.50}', - contrastColor: '{primary.950}', - hoverColor: '{primary.200}', - activeColor: '{primary.300}' - }, - highlight: { - background: '{primary.50}', - focusBackground: '{primary.300}', - color: '{primary.950}', - focusColor: '{primary.950}' - } - } - } - } - }; - } else { - if (preset === 'Nora') { - return { - semantic: { - primary: color.palette, - colorScheme: { - light: { - primary: { - color: '{primary.600}', - contrastColor: '#ffffff', - hoverColor: '{primary.700}', - activeColor: '{primary.800}' - }, - highlight: { - background: '{primary.600}', - focusBackground: '{primary.700}', - color: '#ffffff', - focusColor: '#ffffff' - } - }, - dark: { - primary: { - color: '{primary.500}', - contrastColor: '{surface.900}', - hoverColor: '{primary.400}', - activeColor: '{primary.300}' - }, - highlight: { - background: '{primary.500}', - focusBackground: '{primary.400}', - color: '{surface.900}', - focusColor: '{surface.900}' - } - } - } - } - }; - } else { - return { - semantic: { - primary: color.palette, - colorScheme: { - light: { - primary: { - color: '{primary.500}', - contrastColor: '#ffffff', - hoverColor: '{primary.600}', - activeColor: '{primary.700}' - }, - highlight: { - background: '{primary.50}', - focusBackground: '{primary.100}', - color: '{primary.700}', - focusColor: '{primary.800}' - } - }, - dark: { - primary: { - color: '{primary.400}', - contrastColor: '{surface.900}', - hoverColor: '{primary.300}', - activeColor: '{primary.200}' - }, - highlight: { - background: 'color-mix(in srgb, {primary.400}, transparent 84%)', - focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)', - color: 'rgba(255,255,255,.87)', - focusColor: 'rgba(255,255,255,.87)' - } - } - } - } - }; - } - } - } - - updateColors(event: any, type: string, color: any) { - if (type === 'primary') { - this.layoutService.layoutConfig.update((state) => ({ ...state, primary: color.name })); - } else if (type === 'surface') { - this.layoutService.layoutConfig.update((state) => ({ ...state, surface: color.name })); - } - this.applyTheme(type, color); - - event.stopPropagation(); - } - - applyTheme(type: string, color: any) { - if (type === 'primary') { - updatePreset(this.getPresetExt()); - } else if (type === 'surface') { - updateSurfacePalette(color.palette); - } - } - - onPresetChange(event: any) { - this.layoutService.layoutConfig.update((state) => ({ ...state, preset: event })); - const preset = presets[event as KeyOfType]; - const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette; - $t().preset(preset).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true }); - } - - onMenuModeChange(event: string) { - this.layoutService.layoutConfig.update((prev) => ({ ...prev, menuMode: event })); - } -} diff --git a/src/app/layout/component/app.floatingconfigurator.component.html b/src/app/layout/component/app.floatingconfigurator.component.html new file mode 100644 index 0000000..054c14c --- /dev/null +++ b/src/app/layout/component/app.floatingconfigurator.component.html @@ -0,0 +1,7 @@ +
+ +
+ + +
+
diff --git a/src/app/layout/component/app.floatingconfigurator.component.ts b/src/app/layout/component/app.floatingconfigurator.component.ts new file mode 100644 index 0000000..736a760 --- /dev/null +++ b/src/app/layout/component/app.floatingconfigurator.component.ts @@ -0,0 +1,23 @@ +import { CommonModule } from '@angular/common'; +import { Component, computed, inject, input } from '@angular/core'; +import { ButtonModule } from 'primeng/button'; +import { StyleClassModule } from 'primeng/styleclass'; +import { LayoutService } from '../service/layout.service'; +import { AppConfigurator } from './app.configurator.component'; + +@Component({ + selector: 'app-floating-configurator', + imports: [CommonModule, ButtonModule, StyleClassModule, AppConfigurator], + templateUrl: './app.floatingconfigurator.component.html', +}) +export class AppFloatingConfigurator { + LayoutService = inject(LayoutService); + + float = input(true); + + isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme); + + toggleDarkMode() { + this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); + } +} diff --git a/src/app/layout/component/app.floatingconfigurator.ts b/src/app/layout/component/app.floatingconfigurator.ts deleted file mode 100644 index 2754722..0000000 --- a/src/app/layout/component/app.floatingconfigurator.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {Component, computed, inject, input} from '@angular/core'; -import { ButtonModule } from 'primeng/button'; -import { StyleClassModule } from 'primeng/styleclass'; -import { AppConfigurator } from './app.configurator'; -import { LayoutService } from '../service/layout.service'; -import {CommonModule} from "@angular/common"; - -@Component({ - selector: 'app-floating-configurator', - imports: [CommonModule, ButtonModule, StyleClassModule, AppConfigurator], - template: ` -
- -
- - -
-
- ` -}) -export class AppFloatingConfigurator { - LayoutService = inject(LayoutService); - - float = input(true); - - isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme); - - toggleDarkMode() { - this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); - } - -} diff --git a/src/app/layout/component/app.footer.component.html b/src/app/layout/component/app.footer.component.html new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/app/layout/component/app.footer.component.html @@ -0,0 +1 @@ + diff --git a/src/app/layout/component/app.footer.component.ts b/src/app/layout/component/app.footer.component.ts new file mode 100644 index 0000000..f182132 --- /dev/null +++ b/src/app/layout/component/app.footer.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'app-footer', + templateUrl: './app.footer.component.html', +}) +export class AppFooter {} diff --git a/src/app/layout/component/app.footer.ts b/src/app/layout/component/app.footer.ts deleted file mode 100644 index 5548818..0000000 --- a/src/app/layout/component/app.footer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - standalone: true, - selector: 'app-footer', - template: `` -}) -export class AppFooter {} diff --git a/src/app/layout/component/app.layout.component.html b/src/app/layout/component/app.layout.component.html new file mode 100644 index 0000000..22150f8 --- /dev/null +++ b/src/app/layout/component/app.layout.component.html @@ -0,0 +1,31 @@ +
+ + @if (fullLoading) { +
+ +
+ } @else { + @if (showMenu()) { + + } +
+
+ @if (showBreadcrumb) { + + } +
+ +
+ @if (content) { + + } @else { + + } +
+
+
+ +
+ } +
+
diff --git a/src/app/layout/component/app.layout.component.ts b/src/app/layout/component/app.layout.component.ts new file mode 100644 index 0000000..567e98f --- /dev/null +++ b/src/app/layout/component/app.layout.component.ts @@ -0,0 +1,153 @@ +import { BreadcrumbService } from '@/core/services'; +import { BreadcrumbComponent } from '@/shared/components'; +import { CommonModule } from '@angular/common'; +import { + Component, + computed, + ContentChild, + inject, + Input, + Renderer2, + TemplateRef, + ViewChild, +} from '@angular/core'; +import { NavigationEnd, Router, RouterModule } from '@angular/router'; +import { ProgressSpinner } from 'primeng/progressspinner'; +import { filter, Subscription } from 'rxjs'; +import { LayoutService } from '../service/layout.service'; +import { AppFooter } from './app.footer.component'; +import { AppSidebar } from './app.sidebar.component'; +import { AppTopbar } from './app.topbar.component'; + +@Component({ + selector: 'app-layout', + standalone: true, + imports: [ + CommonModule, + AppTopbar, + AppSidebar, + RouterModule, + AppFooter, + BreadcrumbComponent, + ProgressSpinner, + ], + templateUrl: './app.layout.component.html', +}) +export class AppLayout { + overlayMenuOpenSubscription: Subscription; + + menuOutsideClickListener: any; + + @Input() fullLoading: boolean = false; + + @ViewChild(AppSidebar) appSidebar!: AppSidebar; + + @ViewChild(AppTopbar) appTopBar!: AppTopbar; + @ContentChild('content', { static: true }) content?: TemplateRef | null; + + private breadcrumbService = inject(BreadcrumbService); + + constructor( + public layoutService: LayoutService, + public renderer: Renderer2, + public router: Router, + ) { + this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => { + if (!this.menuOutsideClickListener) { + this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => { + if (this.isOutsideClicked(event)) { + this.hideMenu(); + } + }); + } + + if (this.layoutService.layoutState().staticMenuMobileActive) { + this.blockBodyScroll(); + } + }); + + this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => { + this.hideMenu(); + }); + } + + isOutsideClicked(event: MouseEvent) { + const sidebarEl = document.querySelector('.layout-sidebar'); + const topbarEl = document.querySelector('.layout-menu-button'); + const eventTarget = event.target as Node; + + return !( + sidebarEl?.isSameNode(eventTarget) || + sidebarEl?.contains(eventTarget) || + topbarEl?.isSameNode(eventTarget) || + topbarEl?.contains(eventTarget) + ); + } + + hideMenu() { + this.layoutService.layoutState.update((prev) => ({ + ...prev, + overlayMenuActive: false, + staticMenuMobileActive: false, + menuHoverActive: false, + })); + if (this.menuOutsideClickListener) { + this.menuOutsideClickListener(); + this.menuOutsideClickListener = null; + } + this.unblockBodyScroll(); + } + + blockBodyScroll(): void { + if (document.body.classList) { + document.body.classList.add('blocked-scroll'); + } else { + document.body.className += ' blocked-scroll'; + } + } + + unblockBodyScroll(): void { + if (document.body.classList) { + document.body.classList.remove('blocked-scroll'); + } else { + document.body.className = document.body.className.replace( + new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), + ' ', + ); + } + } + + get containerClass() { + return { + 'layout-overlay': this.layoutService.layoutConfig().menuMode === 'overlay', + 'layout-static': this.layoutService.layoutConfig().menuMode === 'static', + 'layout-static-inactive': + this.layoutService.layoutState().staticMenuDesktopInactive && + this.layoutService.layoutConfig().menuMode === 'static', + 'layout-overlay-active': this.layoutService.layoutState().overlayMenuActive, + 'layout-mobile-active': this.layoutService.layoutState().staticMenuMobileActive, + }; + } + + get showBreadcrumb(): boolean { + return this.breadcrumbService.items.length > 0; + } + + get isFixedContentSize(): boolean { + return this.layoutService.isFixedContentSize() || false; + } + + showMenu = computed(() => { + return this.layoutService.menuItems().length > 0; + }); + + ngOnDestroy() { + if (this.overlayMenuOpenSubscription) { + this.overlayMenuOpenSubscription.unsubscribe(); + } + + if (this.menuOutsideClickListener) { + this.menuOutsideClickListener(); + } + } +} diff --git a/src/app/layout/component/app.layout.ts b/src/app/layout/component/app.layout.ts deleted file mode 100644 index de6142d..0000000 --- a/src/app/layout/component/app.layout.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Component, Renderer2, ViewChild } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { NavigationEnd, Router, RouterModule } from '@angular/router'; -import { filter, Subscription } from 'rxjs'; -import { AppTopbar } from './app.topbar'; -import { AppSidebar } from './app.sidebar'; -import { AppFooter } from './app.footer'; -import { LayoutService } from '../service/layout.service'; - -@Component({ - selector: 'app-layout', - standalone: true, - imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter], - template: `
- - -
-
- -
- -
-
-
` -}) -export class AppLayout { - overlayMenuOpenSubscription: Subscription; - - menuOutsideClickListener: any; - - @ViewChild(AppSidebar) appSidebar!: AppSidebar; - - @ViewChild(AppTopbar) appTopBar!: AppTopbar; - - constructor( - public layoutService: LayoutService, - public renderer: Renderer2, - public router: Router - ) { - this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => { - if (!this.menuOutsideClickListener) { - this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => { - if (this.isOutsideClicked(event)) { - this.hideMenu(); - } - }); - } - - if (this.layoutService.layoutState().staticMenuMobileActive) { - this.blockBodyScroll(); - } - }); - - this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => { - this.hideMenu(); - }); - } - - isOutsideClicked(event: MouseEvent) { - const sidebarEl = document.querySelector('.layout-sidebar'); - const topbarEl = document.querySelector('.layout-menu-button'); - const eventTarget = event.target as Node; - - return !(sidebarEl?.isSameNode(eventTarget) || sidebarEl?.contains(eventTarget) || topbarEl?.isSameNode(eventTarget) || topbarEl?.contains(eventTarget)); - } - - hideMenu() { - this.layoutService.layoutState.update((prev) => ({ ...prev, overlayMenuActive: false, staticMenuMobileActive: false, menuHoverActive: false })); - if (this.menuOutsideClickListener) { - this.menuOutsideClickListener(); - this.menuOutsideClickListener = null; - } - this.unblockBodyScroll(); - } - - blockBodyScroll(): void { - if (document.body.classList) { - document.body.classList.add('blocked-scroll'); - } else { - document.body.className += ' blocked-scroll'; - } - } - - unblockBodyScroll(): void { - if (document.body.classList) { - document.body.classList.remove('blocked-scroll'); - } else { - document.body.className = document.body.className.replace(new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); - } - } - - get containerClass() { - return { - 'layout-overlay': this.layoutService.layoutConfig().menuMode === 'overlay', - 'layout-static': this.layoutService.layoutConfig().menuMode === 'static', - 'layout-static-inactive': this.layoutService.layoutState().staticMenuDesktopInactive && this.layoutService.layoutConfig().menuMode === 'static', - 'layout-overlay-active': this.layoutService.layoutState().overlayMenuActive, - 'layout-mobile-active': this.layoutService.layoutState().staticMenuMobileActive - }; - } - - ngOnDestroy() { - if (this.overlayMenuOpenSubscription) { - this.overlayMenuOpenSubscription.unsubscribe(); - } - - if (this.menuOutsideClickListener) { - this.menuOutsideClickListener(); - } - } -} diff --git a/src/app/layout/component/app.menu.component.html b/src/app/layout/component/app.menu.component.html new file mode 100644 index 0000000..877cb1c --- /dev/null +++ b/src/app/layout/component/app.menu.component.html @@ -0,0 +1,10 @@ +
    + + @if (!item.separator) { +
  • + } + @if (item.separator) { + + } +
    +
diff --git a/src/app/layout/component/app.menu.component.ts b/src/app/layout/component/app.menu.component.ts new file mode 100644 index 0000000..08130ec --- /dev/null +++ b/src/app/layout/component/app.menu.component.ts @@ -0,0 +1,19 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AppMenuitem } from './app.menuitem.component'; +import { LayoutService } from '../service/layout.service'; + +@Component({ + selector: 'app-menu', + standalone: true, + imports: [CommonModule, AppMenuitem, RouterModule], + templateUrl: './app.menu.component.html', +}) +export class AppMenu { + private layoutService = inject(LayoutService); + + get model() { + return this.layoutService.menuItems(); + } +} diff --git a/src/app/layout/component/app.menu.ts b/src/app/layout/component/app.menu.ts deleted file mode 100644 index ae77ce7..0000000 --- a/src/app/layout/component/app.menu.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; -import { MenuItem } from 'primeng/api'; -import { AppMenuitem } from './app.menuitem'; - -@Component({ - selector: 'app-menu', - standalone: true, - imports: [CommonModule, AppMenuitem, RouterModule], - template: `
    - -
  • - -
    -
` -}) -export class AppMenu { - model: MenuItem[] = []; - - ngOnInit() { - this.model = [ - { - label: 'Home', - items: [{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/'] }] - }, - { - label: 'UI Components', - items: [ - { label: 'Form Layout', icon: 'pi pi-fw pi-id-card', routerLink: ['/uikit/formlayout'] }, - { label: 'Input', icon: 'pi pi-fw pi-check-square', routerLink: ['/uikit/input'] }, - { label: 'Button', icon: 'pi pi-fw pi-mobile', class: 'rotated-icon', routerLink: ['/uikit/button'] }, - { label: 'Table', icon: 'pi pi-fw pi-table', routerLink: ['/uikit/table'] }, - { label: 'List', icon: 'pi pi-fw pi-list', routerLink: ['/uikit/list'] }, - { label: 'Tree', icon: 'pi pi-fw pi-share-alt', routerLink: ['/uikit/tree'] }, - { label: 'Panel', icon: 'pi pi-fw pi-tablet', routerLink: ['/uikit/panel'] }, - { label: 'Overlay', icon: 'pi pi-fw pi-clone', routerLink: ['/uikit/overlay'] }, - { label: 'Media', icon: 'pi pi-fw pi-image', routerLink: ['/uikit/media'] }, - { label: 'Menu', icon: 'pi pi-fw pi-bars', routerLink: ['/uikit/menu'] }, - { label: 'Message', icon: 'pi pi-fw pi-comment', routerLink: ['/uikit/message'] }, - { label: 'File', icon: 'pi pi-fw pi-file', routerLink: ['/uikit/file'] }, - { label: 'Chart', icon: 'pi pi-fw pi-chart-bar', routerLink: ['/uikit/charts'] }, - { label: 'Timeline', icon: 'pi pi-fw pi-calendar', routerLink: ['/uikit/timeline'] }, - { label: 'Misc', icon: 'pi pi-fw pi-circle', routerLink: ['/uikit/misc'] } - ] - }, - { - label: 'Pages', - icon: 'pi pi-fw pi-briefcase', - routerLink: ['/pages'], - items: [ - { - label: 'Landing', - icon: 'pi pi-fw pi-globe', - routerLink: ['/landing'] - }, - { - label: 'Auth', - icon: 'pi pi-fw pi-user', - items: [ - { - label: 'Login', - icon: 'pi pi-fw pi-sign-in', - routerLink: ['/auth/login'] - }, - { - label: 'Error', - icon: 'pi pi-fw pi-times-circle', - routerLink: ['/auth/error'] - }, - { - label: 'Access Denied', - icon: 'pi pi-fw pi-lock', - routerLink: ['/auth/access'] - } - ] - }, - { - label: 'Crud', - icon: 'pi pi-fw pi-pencil', - routerLink: ['/pages/crud'] - }, - { - label: 'Not Found', - icon: 'pi pi-fw pi-exclamation-circle', - routerLink: ['/pages/notfound'] - }, - { - label: 'Empty', - icon: 'pi pi-fw pi-circle-off', - routerLink: ['/pages/empty'] - } - ] - }, - { - label: 'Hierarchy', - items: [ - { - label: 'Submenu 1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { - label: 'Submenu 1.1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-bookmark' } - ] - }, - { - label: 'Submenu 1.2', - icon: 'pi pi-fw pi-bookmark', - items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }] - } - ] - }, - { - label: 'Submenu 2', - icon: 'pi pi-fw pi-bookmark', - items: [ - { - label: 'Submenu 2.1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' } - ] - }, - { - label: 'Submenu 2.2', - icon: 'pi pi-fw pi-bookmark', - items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }] - } - ] - } - ] - }, - { - label: 'Get Started', - items: [ - { - label: 'Documentation', - icon: 'pi pi-fw pi-book', - routerLink: ['/documentation'] - }, - { - label: 'View Source', - icon: 'pi pi-fw pi-github', - url: 'https://github.com/primefaces/sakai-ng', - target: '_blank' - } - ] - } - ]; - } -} diff --git a/src/app/layout/component/app.menuitem.component.html b/src/app/layout/component/app.menuitem.component.html new file mode 100644 index 0000000..95b26a1 --- /dev/null +++ b/src/app/layout/component/app.menuitem.component.html @@ -0,0 +1,58 @@ + +
{{ item.label }}
+ + + + + + + + {{ item.label }} + + + + + + + {{ item.label }} + + + +
    + +
  • +
    +
+
diff --git a/src/app/layout/component/app.menuitem.component.ts b/src/app/layout/component/app.menuitem.component.ts new file mode 100644 index 0000000..8a68bf0 --- /dev/null +++ b/src/app/layout/component/app.menuitem.component.ts @@ -0,0 +1,166 @@ +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { CommonModule } from '@angular/common'; +import { Component, HostBinding, Input } from '@angular/core'; +import { NavigationEnd, Router, RouterModule } from '@angular/router'; +import { MenuItem } from 'primeng/api'; +import { RippleModule } from 'primeng/ripple'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { LayoutService } from '../service/layout.service'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '[app-menuitem]', + imports: [CommonModule, RouterModule, RippleModule], + templateUrl: './app.menuitem.component.html', + animations: [ + trigger('children', [ + state( + 'collapsed', + style({ + height: '0', + }), + ), + state( + 'expanded', + style({ + height: '*', + }), + ), + transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')), + ]), + ], + + providers: [LayoutService], +}) +export class AppMenuitem { + @Input() item!: MenuItem; + + @Input() index!: number; + + @Input() @HostBinding('class.layout-root-menuitem') root!: boolean; + + @Input() parentKey!: string; + + active = false; + + menuSourceSubscription: Subscription; + + menuResetSubscription: Subscription; + + key: string = ''; + + constructor( + public router: Router, + private layoutService: LayoutService, + ) { + this.menuSourceSubscription = this.layoutService.menuSource$.subscribe((value) => { + Promise.resolve(null).then(() => { + if (value.routeEvent) { + this.active = + value.key === this.key || value.key.startsWith(this.key + '-') ? true : false; + } else { + if (value.key !== this.key && !value.key.startsWith(this.key + '-')) { + this.active = false; + } + } + }); + }); + + this.menuResetSubscription = this.layoutService.resetSource$.subscribe(() => { + this.active = false; + }); + + this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => { + // always update active state on navigation so parent items can + // detect if any descendant route is active + this.updateActiveStateFromRoute(); + }); + } + + ngOnInit() { + this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index); + + if (this.item.routerLink) { + this.updateActiveStateFromRoute(); + } + } + + updateActiveStateFromRoute() { + const opts = { + paths: 'subset' as any, + queryParams: 'ignored' as any, + matrixParams: 'subset' as any, + fragment: 'ignored' as any, + }; + + let activeRoute = false; + + if (this.item.routerLink) { + activeRoute = this.router.isActive(this.item.routerLink[0], opts); + } + + // if this item itself is not active, check its descendants + if (!activeRoute && this.item.items && this.item.items.length) { + const anyChildActive = (items: MenuItem[]): boolean => { + for (const it of items) { + if (it.routerLink && this.router.isActive(it.routerLink[0], opts)) { + return true; + } + if (it.items && it.items.length && anyChildActive(it.items)) { + return true; + } + } + return false; + }; + + activeRoute = anyChildActive(this.item.items); + } + + if (activeRoute) { + // notify layout that this menu (or its descendant) is active + this.layoutService.onMenuStateChange({ key: this.key, routeEvent: true }); + } + } + + itemClick(event: Event) { + // avoid processing disabled items + if (this.item.disabled) { + event.preventDefault(); + return; + } + + // execute command + if (this.item.command) { + this.item.command({ originalEvent: event, item: this.item }); + } + + // toggle active state + if (this.item.items) { + this.active = !this.active; + } + + this.layoutService.onMenuStateChange({ key: this.key }); + } + + get submenuAnimation() { + return this.root ? 'expanded' : this.active ? 'expanded' : 'collapsed'; + } + + @HostBinding('class.active-menuitem') + get activeClass() { + // mark item as active when itself or any of its submenu items are active + // include root items as well so parents highlight when a child route is active + return this.active; + } + + ngOnDestroy() { + if (this.menuSourceSubscription) { + this.menuSourceSubscription.unsubscribe(); + } + + if (this.menuResetSubscription) { + this.menuResetSubscription.unsubscribe(); + } + } +} diff --git a/src/app/layout/component/app.menuitem.ts b/src/app/layout/component/app.menuitem.ts deleted file mode 100644 index 3b70b84..0000000 --- a/src/app/layout/component/app.menuitem.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Component, HostBinding, Input } from '@angular/core'; -import { NavigationEnd, Router, RouterModule } from '@angular/router'; -import { animate, state, style, transition, trigger } from '@angular/animations'; -import { Subscription } from 'rxjs'; -import { filter } from 'rxjs/operators'; -import { CommonModule } from '@angular/common'; -import { RippleModule } from 'primeng/ripple'; -import { MenuItem } from 'primeng/api'; -import { LayoutService } from '../service/layout.service'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: '[app-menuitem]', - imports: [CommonModule, RouterModule, RippleModule], - template: ` - -
{{ item.label }}
- - - {{ item.label }} - - - - - {{ item.label }} - - - -
    - -
  • -
    -
-
- `, - animations: [ - trigger('children', [ - state( - 'collapsed', - style({ - height: '0' - }) - ), - state( - 'expanded', - style({ - height: '*' - }) - ), - transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) - ]) - ], - providers: [LayoutService] -}) -export class AppMenuitem { - @Input() item!: MenuItem; - - @Input() index!: number; - - @Input() @HostBinding('class.layout-root-menuitem') root!: boolean; - - @Input() parentKey!: string; - - active = false; - - menuSourceSubscription: Subscription; - - menuResetSubscription: Subscription; - - key: string = ''; - - constructor( - public router: Router, - private layoutService: LayoutService - ) { - this.menuSourceSubscription = this.layoutService.menuSource$.subscribe((value) => { - Promise.resolve(null).then(() => { - if (value.routeEvent) { - this.active = value.key === this.key || value.key.startsWith(this.key + '-') ? true : false; - } else { - if (value.key !== this.key && !value.key.startsWith(this.key + '-')) { - this.active = false; - } - } - }); - }); - - this.menuResetSubscription = this.layoutService.resetSource$.subscribe(() => { - this.active = false; - }); - - this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((params) => { - if (this.item.routerLink) { - this.updateActiveStateFromRoute(); - } - }); - } - - ngOnInit() { - this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index); - - if (this.item.routerLink) { - this.updateActiveStateFromRoute(); - } - } - - updateActiveStateFromRoute() { - let activeRoute = this.router.isActive(this.item.routerLink[0], { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }); - - if (activeRoute) { - this.layoutService.onMenuStateChange({ key: this.key, routeEvent: true }); - } - } - - itemClick(event: Event) { - // avoid processing disabled items - if (this.item.disabled) { - event.preventDefault(); - return; - } - - // execute command - if (this.item.command) { - this.item.command({ originalEvent: event, item: this.item }); - } - - // toggle active state - if (this.item.items) { - this.active = !this.active; - } - - this.layoutService.onMenuStateChange({ key: this.key }); - } - - get submenuAnimation() { - return this.root ? 'expanded' : this.active ? 'expanded' : 'collapsed'; - } - - @HostBinding('class.active-menuitem') - get activeClass() { - return this.active && !this.root; - } - - ngOnDestroy() { - if (this.menuSourceSubscription) { - this.menuSourceSubscription.unsubscribe(); - } - - if (this.menuResetSubscription) { - this.menuResetSubscription.unsubscribe(); - } - } -} diff --git a/src/app/layout/component/app.sidebar.component.html b/src/app/layout/component/app.sidebar.component.html new file mode 100644 index 0000000..98e10e1 --- /dev/null +++ b/src/app/layout/component/app.sidebar.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/layout/component/app.sidebar.component.ts b/src/app/layout/component/app.sidebar.component.ts new file mode 100644 index 0000000..56fca6f --- /dev/null +++ b/src/app/layout/component/app.sidebar.component.ts @@ -0,0 +1,12 @@ +import { Component, ElementRef } from '@angular/core'; +import { AppMenu } from './app.menu.component'; + +@Component({ + selector: 'app-sidebar', + standalone: true, + imports: [AppMenu], + templateUrl: './app.sidebar.component.html', +}) +export class AppSidebar { + constructor(public el: ElementRef) {} +} diff --git a/src/app/layout/component/app.sidebar.ts b/src/app/layout/component/app.sidebar.ts deleted file mode 100644 index 0aba02f..0000000 --- a/src/app/layout/component/app.sidebar.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, ElementRef } from '@angular/core'; -import { AppMenu } from './app.menu'; - -@Component({ - selector: 'app-sidebar', - standalone: true, - imports: [AppMenu], - template: `
- -
` -}) -export class AppSidebar { - constructor(public el: ElementRef) {} -} diff --git a/src/app/layout/component/app.topbar.component.html b/src/app/layout/component/app.topbar.component.html new file mode 100644 index 0000000..7e7e58f --- /dev/null +++ b/src/app/layout/component/app.topbar.component.html @@ -0,0 +1,40 @@ +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+
+
diff --git a/src/app/layout/component/app.topbar.component.ts b/src/app/layout/component/app.topbar.component.ts new file mode 100644 index 0000000..adb7293 --- /dev/null +++ b/src/app/layout/component/app.topbar.component.ts @@ -0,0 +1,53 @@ +import { AuthService } from '@/core/services/auth.service'; +import { CommonModule } from '@angular/common'; +import { Component, computed, inject } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { MenuItem } from 'primeng/api'; +import { Button } from 'primeng/button'; +import { MenuModule } from 'primeng/menu'; +import { StyleClassModule } from 'primeng/styleclass'; +import images from 'src/assets/images'; +import { LayoutService } from '../service/layout.service'; + +@Component({ + selector: 'app-topbar', + standalone: true, + imports: [RouterModule, CommonModule, StyleClassModule, MenuModule, Button], + templateUrl: './app.topbar.component.html', +}) +export class AppTopbar { + items!: MenuItem[]; + + constructor(public layoutService: LayoutService) {} + private authService: AuthService = inject(AuthService); + + readonly logo = images.logo; + + toggleDarkMode() { + this.layoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); + } + + username = computed(() => this.authService.currentUser()?.fullName || 'کاربر ناشناس'); + + logout = () => { + this.authService.logout(); + }; + + profileMenuItems: MenuItem[] = [ + { + label: this.username(), + icon: 'pi pi-user', + disabled: true, + }, + { + label: 'راهنمای سامانه', + icon: 'pi pi-question-circle', + disabled: true, + }, + { + label: 'خروج', + icon: 'pi pi-sign-out', + command: this.logout, + }, + ]; +} diff --git a/src/app/layout/component/app.topbar.ts b/src/app/layout/component/app.topbar.ts deleted file mode 100644 index 62545da..0000000 --- a/src/app/layout/component/app.topbar.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { MenuItem } from 'primeng/api'; -import { StyleClassModule } from 'primeng/styleclass'; -import { LayoutService } from '../service/layout.service'; -import { AppConfigurator } from './app.configurator'; - -@Component({ - selector: 'app-topbar', - standalone: true, - imports: [RouterModule, CommonModule, StyleClassModule, AppConfigurator], - template: `
- - -
-
- -
- - -
-
- - - - -
-
`, -}) -export class AppTopbar { - items!: MenuItem[]; - - constructor(public layoutService: LayoutService) {} - - toggleDarkMode() { - this.layoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); - } -} diff --git a/src/app/layout/service/layout.service.ts b/src/app/layout/service/layout.service.ts index dc78b22..cf25a34 100644 --- a/src/app/layout/service/layout.service.ts +++ b/src/app/layout/service/layout.service.ts @@ -1,4 +1,6 @@ -import { Injectable, computed, effect, signal } from '@angular/core'; +import { MENU_ITEMS } from '@/core/constants'; +import { computed, effect, Injectable, signal } from '@angular/core'; +import { MenuItem } from 'primeng/api'; import { Subject } from 'rxjs'; export interface layoutConfig { @@ -15,6 +17,7 @@ interface LayoutState { configSidebarVisible?: boolean; staticMenuMobileActive?: boolean; menuHoverActive?: boolean; + isFixedContentSize?: boolean; } interface MenuChangeEvent { @@ -28,7 +31,7 @@ interface MenuChangeEvent { export class LayoutService { _config: layoutConfig = { preset: 'Aura', - primary: 'violet', + primary: 'blue', surface: null, darkTheme: localStorage.getItem('isDarkTheme') === 'true', menuMode: 'static', @@ -40,6 +43,7 @@ export class LayoutService { configSidebarVisible: false, staticMenuMobileActive: false, menuHoverActive: false, + isFixedContentSize: true, }; layoutConfig = signal(this._config); @@ -54,6 +58,8 @@ export class LayoutService { private resetSource = new Subject(); + public menuItems = signal(MENU_ITEMS); + menuSource$ = this.menuSource.asObservable(); resetSource$ = this.resetSource.asObservable(); @@ -76,11 +82,14 @@ export class LayoutService { isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay'); + isFixedContentSize = computed(() => this.layoutState().isFixedContentSize); + transitionComplete = signal(false); private initialized = false; constructor() { + this.handleDarkModeTransition(this._config); effect(() => { const config = this.layoutConfig(); if (config) { @@ -121,8 +130,16 @@ export class LayoutService { .catch(() => {}); } + changeIsFixedContentSize(status: boolean) { + this.layoutState.update((prev) => ({ + ...prev, + isFixedContentSize: status, + })); + } + toggleDarkMode(config?: layoutConfig): void { const _config = config || this.layoutConfig(); + localStorage.setItem('isDarkTheme', JSON.stringify(_config.darkTheme)); if (_config.darkTheme) { document.documentElement.classList.add('app-dark'); } else { @@ -186,4 +203,8 @@ export class LayoutService { reset() { this.resetSource.next(true); } + + setMenuItems(items: MenuItem[]) { + this.menuItems.set(items); + } } diff --git a/src/app/modules/auth/pages/auth.component.html b/src/app/modules/auth/pages/auth.component.html index 0a20d2d..a6eea8a 100644 --- a/src/app/modules/auth/pages/auth.component.html +++ b/src/app/modules/auth/pages/auth.component.html @@ -7,8 +7,8 @@ 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") { diff --git a/src/app/modules/customers/components/form.component.html b/src/app/modules/customers/components/form.component.html new file mode 100644 index 0000000..e6ab161 --- /dev/null +++ b/src/app/modules/customers/components/form.component.html @@ -0,0 +1,14 @@ + +
+ + + + + + + + + + + +
diff --git a/src/app/modules/customers/components/form.component.ts b/src/app/modules/customers/components/form.component.ts new file mode 100644 index 0000000..dbdb04d --- /dev/null +++ b/src/app/modules/customers/components/form.component.ts @@ -0,0 +1,74 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { ICustomerRequest, ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'customer-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class CustomerFormComponent { + @Input() initialValues?: ICustomerResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: CustomersService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + firstName: [this.initialValues?.firstName || null, [Validators.required]], + lastName: [this.initialValues?.lastName || null, [Validators.required]], + email: [this.initialValues?.email || null, [Validators.required, Validators.email]], + mobileNumber: [this.initialValues?.mobileNumber || null, [Validators.required]], + address: [this.initialValues?.address || null, [Validators.required]], + city: [this.initialValues?.city || null, [Validators.required]], + state: [this.initialValues?.state || null, [Validators.required]], + country: [this.initialValues?.country || null, [Validators.required]], + isActive: [this.initialValues?.isActive || false, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as ICustomerRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/customers/constants/apiRoutes/index.ts b/src/app/modules/customers/constants/apiRoutes/index.ts new file mode 100644 index 0000000..bb3175d --- /dev/null +++ b/src/app/modules/customers/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/customers'; + +export const CUSTOMERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (customerId: string) => `${baseUrl}/${customerId}`, +}; diff --git a/src/app/modules/customers/constants/index.ts b/src/app/modules/customers/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/customers/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/customers/constants/routes/index.ts b/src/app/modules/customers/constants/routes/index.ts new file mode 100644 index 0000000..9213c5d --- /dev/null +++ b/src/app/modules/customers/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TCustomersRouteNames = 'customers' | 'customer'; + +export const customersNamedRoutes: NamedRoutes = { + customers: { + path: 'customers', + loadComponent: () => import('../../views/list.component').then((m) => m.CustomersComponent), + meta: { + title: 'مشتریان', + pagePath: () => '/customers', + }, + }, + customer: { + path: 'customers/:customerId', + loadComponent: () => import('../../views/single.component').then((m) => m.CustomerComponent), + meta: { + title: 'مشتری', + pagePath: () => '/customers/:customerId', + }, + }, +}; + +export const CUSTOMERS_ROUTES: Routes = Object.values(customersNamedRoutes); diff --git a/src/app/modules/customers/models/index.ts b/src/app/modules/customers/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/customers/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/customers/models/io.d.ts b/src/app/modules/customers/models/io.d.ts new file mode 100644 index 0000000..7deef4d --- /dev/null +++ b/src/app/modules/customers/models/io.d.ts @@ -0,0 +1,29 @@ +export interface ICustomerRawResponse { + id: number; + firstName: string; + lastName: string; + email: string; + mobileNumber: string; + address: string; + city: string; + state: string; + country: string; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + deletedAt: Date; +} + +export interface ICustomerResponse extends ICustomerRawResponse {} + +export interface ICustomerRequest { + firstName: string; + lastName: string; + email: string; + mobileNumber: string; + address: string; + city: string; + state: string; + country: string; + isActive: boolean; +} diff --git a/src/app/modules/customers/services/main.service.ts b/src/app/modules/customers/services/main.service.ts new file mode 100644 index 0000000..8cdbcaf --- /dev/null +++ b/src/app/modules/customers/services/main.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CUSTOMERS_API_ROUTES } from '../constants'; +import { ICustomerRawResponse, ICustomerRequest, ICustomerResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class CustomersService { + constructor(private http: HttpClient) {} + + private apiRoutes = CUSTOMERS_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(customerId: string): Observable { + return this.http.get(this.apiRoutes.single(customerId)); + } + + create(data: ICustomerRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/customers/views/index.ts b/src/app/modules/customers/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/customers/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/customers/views/list.component.html b/src/app/modules/customers/views/list.component.html new file mode 100644 index 0000000..60a32bf --- /dev/null +++ b/src/app/modules/customers/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/customers/views/list.component.ts b/src/app/modules/customers/views/list.component.ts new file mode 100644 index 0000000..5c305fb --- /dev/null +++ b/src/app/modules/customers/views/list.component.ts @@ -0,0 +1,45 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'app-customers', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class CustomersComponent { + constructor(private customerService: CustomersService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'firstName', header: 'نام' }, + { field: 'lastName', header: 'نام خانوادگی' }, + { field: 'email', header: 'ایمیل' }, + { field: 'mobileNumber', header: 'شماره موبایل' }, + { field: 'address', header: 'آدرس' }, + { field: 'city', header: 'شهر' }, + { field: 'state', header: 'استان' }, + { field: 'country', header: 'کشور' }, + { field: 'isActive', header: 'فعال' }, + { field: 'createdAt', header: 'تاریخ ایجاد' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.customerService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/customers/views/single.component.html b/src/app/modules/customers/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/customers/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/customers/views/single.component.ts b/src/app/modules/customers/views/single.component.ts new file mode 100644 index 0000000..77d906c --- /dev/null +++ b/src/app/modules/customers/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-customer', + templateUrl: './single.component.html', +}) +export class CustomerComponent { + constructor() {} +} diff --git a/src/app/modules/inventories/components/form.component.html b/src/app/modules/inventories/components/form.component.html new file mode 100644 index 0000000..2899af2 --- /dev/null +++ b/src/app/modules/inventories/components/form.component.html @@ -0,0 +1,8 @@ + +
+ + + + + +
diff --git a/src/app/modules/inventories/components/form.component.ts b/src/app/modules/inventories/components/form.component.ts new file mode 100644 index 0000000..245f311 --- /dev/null +++ b/src/app/modules/inventories/components/form.component.ts @@ -0,0 +1,68 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IInventoryRequest, IInventoryResponse } from '../models'; +import { InventoriesService } from '../services/main.service'; + +@Component({ + selector: 'inventory-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class InventoryFormComponent { + @Input() initialValues?: IInventoryResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: InventoriesService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: [this.initialValues?.name || null, [Validators.required]], + location: [this.initialValues?.location || null, [Validators.required]], + isActive: [this.initialValues?.isActive || false, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IInventoryRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/inventories/constants/apiRoutes/index.ts b/src/app/modules/inventories/constants/apiRoutes/index.ts new file mode 100644 index 0000000..7f00c77 --- /dev/null +++ b/src/app/modules/inventories/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/inventories'; + +export const INVENTORIES_API_ROUTES = { + list: () => `${baseUrl}`, + single: (inventoryId: string) => `${baseUrl}/${inventoryId}`, +}; diff --git a/src/app/modules/inventories/constants/index.ts b/src/app/modules/inventories/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/inventories/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/inventories/constants/routes/index.ts b/src/app/modules/inventories/constants/routes/index.ts new file mode 100644 index 0000000..188fc36 --- /dev/null +++ b/src/app/modules/inventories/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TInventoriesRouteNames = 'inventories' | 'inventory'; + +export const inventoriesNamedRoutes: NamedRoutes = { + inventories: { + path: 'inventories', + loadComponent: () => import('../../views/list.component').then((m) => m.InventoriesComponent), + meta: { + title: 'انبارها', + pagePath: () => '/inventories', + }, + }, + inventory: { + path: 'inventories/:inventoryId', + loadComponent: () => import('../../views/single.component').then((m) => m.InventoryComponent), + meta: { + title: 'انبار', + pagePath: () => '/inventories/:inventoryId', + }, + }, +}; + +export const INVENTORIES_ROUTES: Routes = Object.values(inventoriesNamedRoutes); diff --git a/src/app/modules/inventories/models/index.ts b/src/app/modules/inventories/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/inventories/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/inventories/models/io.d.ts b/src/app/modules/inventories/models/io.d.ts new file mode 100644 index 0000000..579d4e4 --- /dev/null +++ b/src/app/modules/inventories/models/io.d.ts @@ -0,0 +1,14 @@ +export interface IInventoryRawResponse { + id: number; + name: string; + location: string; + isActive: boolean; +} + +export interface IInventoryResponse extends IInventoryRawResponse {} + +export interface IInventoryRequest { + name: string; + location: string; + isActive: boolean; +} diff --git a/src/app/modules/inventories/services/main.service.ts b/src/app/modules/inventories/services/main.service.ts new file mode 100644 index 0000000..df7b9f9 --- /dev/null +++ b/src/app/modules/inventories/services/main.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { INVENTORIES_API_ROUTES } from '../constants'; +import { IInventoryRawResponse, IInventoryRequest, IInventoryResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class InventoriesService { + constructor(private http: HttpClient) {} + + private apiRoutes = INVENTORIES_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(inventoryId: string): Observable { + return this.http.get(this.apiRoutes.single(inventoryId)); + } + + create(data: IInventoryRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/inventories/views/index.ts b/src/app/modules/inventories/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/inventories/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/inventories/views/list.component.html b/src/app/modules/inventories/views/list.component.html new file mode 100644 index 0000000..f01f345 --- /dev/null +++ b/src/app/modules/inventories/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/inventories/views/list.component.ts b/src/app/modules/inventories/views/list.component.ts new file mode 100644 index 0000000..0cf6c14 --- /dev/null +++ b/src/app/modules/inventories/views/list.component.ts @@ -0,0 +1,37 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { IInventoryResponse } from '../models'; +import { InventoriesService } from '../services/main.service'; + +@Component({ + selector: 'app-inventories', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class InventoriesComponent { + constructor(private inventoryService: InventoriesService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'location', header: 'موقعیت' }, + { field: 'isActive', header: 'فعال' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.inventoryService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/inventories/views/single.component.html b/src/app/modules/inventories/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/inventories/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/inventories/views/single.component.ts b/src/app/modules/inventories/views/single.component.ts new file mode 100644 index 0000000..aaf9e1f --- /dev/null +++ b/src/app/modules/inventories/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-inventory', + templateUrl: './single.component.html', +}) +export class InventoryComponent { + constructor() {} +} diff --git a/src/app/modules/productBrands/components/form.component.html b/src/app/modules/productBrands/components/form.component.html new file mode 100644 index 0000000..615cd93 --- /dev/null +++ b/src/app/modules/productBrands/components/form.component.html @@ -0,0 +1,8 @@ + +
+ + + + + +
diff --git a/src/app/modules/productBrands/components/form.component.ts b/src/app/modules/productBrands/components/form.component.ts new file mode 100644 index 0000000..333c134 --- /dev/null +++ b/src/app/modules/productBrands/components/form.component.ts @@ -0,0 +1,68 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IProductBrandRequest, IProductBrandResponse } from '../models'; +import { ProductBrandsService } from '../services/main.service'; + +@Component({ + selector: 'product-brand-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class ProductBrandFormComponent { + @Input() initialValues?: IProductBrandResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: ProductBrandsService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: [this.initialValues?.name || null, [Validators.required]], + description: [this.initialValues?.description || null, [Validators.required]], + imageUrl: [this.initialValues?.imageUrl || null, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IProductBrandRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/productBrands/constants/apiRoutes/index.ts b/src/app/modules/productBrands/constants/apiRoutes/index.ts new file mode 100644 index 0000000..381bee9 --- /dev/null +++ b/src/app/modules/productBrands/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/product-brands'; + +export const PRODUCT_BRANDS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (brandId: string) => `${baseUrl}/${brandId}`, +}; diff --git a/src/app/modules/productBrands/constants/index.ts b/src/app/modules/productBrands/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/productBrands/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/productBrands/constants/routes/index.ts b/src/app/modules/productBrands/constants/routes/index.ts new file mode 100644 index 0000000..cdffb33 --- /dev/null +++ b/src/app/modules/productBrands/constants/routes/index.ts @@ -0,0 +1,26 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TProductBrandsRouteNames = 'productBrands' | 'productBrand'; + +export const productBrandsNamedRoutes: NamedRoutes = { + productBrands: { + path: 'product-brands', + loadComponent: () => import('../../views/list.component').then((m) => m.ProductBrandsComponent), + meta: { + title: 'برندهای محصول', + pagePath: () => '/product-brands', + }, + }, + productBrand: { + path: 'product-brands/:brandId', + loadComponent: () => + import('../../views/single.component').then((m) => m.ProductBrandComponent), + meta: { + title: 'برند محصول', + pagePath: () => '/product-brands/:brandId', + }, + }, +}; + +export const PRODUCT_BRANDS_ROUTES: Routes = Object.values(productBrandsNamedRoutes); diff --git a/src/app/modules/productBrands/models/index.ts b/src/app/modules/productBrands/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/productBrands/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/productBrands/models/io.d.ts b/src/app/modules/productBrands/models/io.d.ts new file mode 100644 index 0000000..ed1e14e --- /dev/null +++ b/src/app/modules/productBrands/models/io.d.ts @@ -0,0 +1,17 @@ +export interface IProductBrandRawResponse { + id: number; + name: string; + description: string; + imageUrl: string; + createdAt: Date; + updatedAt: Date; + deletedAt: Date; +} + +export interface IProductBrandResponse extends IProductBrandRawResponse {} + +export interface IProductBrandRequest { + name: string; + description: string; + imageUrl: string; +} diff --git a/src/app/modules/productBrands/services/main.service.ts b/src/app/modules/productBrands/services/main.service.ts new file mode 100644 index 0000000..523349b --- /dev/null +++ b/src/app/modules/productBrands/services/main.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PRODUCT_BRANDS_API_ROUTES } from '../constants'; +import { IProductBrandRawResponse, IProductBrandRequest, IProductBrandResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class ProductBrandsService { + constructor(private http: HttpClient) {} + + private apiRoutes = PRODUCT_BRANDS_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(brandId: string): Observable { + return this.http.get(this.apiRoutes.single(brandId)); + } + + create(data: IProductBrandRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/productBrands/views/index.ts b/src/app/modules/productBrands/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/productBrands/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/productBrands/views/list.component.html b/src/app/modules/productBrands/views/list.component.html new file mode 100644 index 0000000..d827102 --- /dev/null +++ b/src/app/modules/productBrands/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/productBrands/views/list.component.ts b/src/app/modules/productBrands/views/list.component.ts new file mode 100644 index 0000000..c3025f1 --- /dev/null +++ b/src/app/modules/productBrands/views/list.component.ts @@ -0,0 +1,39 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { IProductBrandResponse } from '../models'; +import { ProductBrandsService } from '../services/main.service'; + +@Component({ + selector: 'app-product-brands', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class ProductBrandsComponent { + constructor(private productBrandService: ProductBrandsService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'description', header: 'توضیحات' }, + { field: 'imageUrl', header: 'تصویر' }, + { field: 'createdAt', header: 'تاریخ ایجاد' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.productBrandService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/productBrands/views/single.component.html b/src/app/modules/productBrands/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/productBrands/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/productBrands/views/single.component.ts b/src/app/modules/productBrands/views/single.component.ts new file mode 100644 index 0000000..9375bfe --- /dev/null +++ b/src/app/modules/productBrands/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-product-brand', + templateUrl: './single.component.html', +}) +export class ProductBrandComponent { + constructor() {} +} diff --git a/src/app/modules/productCategories/components/form.component.html b/src/app/modules/productCategories/components/form.component.html new file mode 100644 index 0000000..436e732 --- /dev/null +++ b/src/app/modules/productCategories/components/form.component.html @@ -0,0 +1,14 @@ + +
+ + + + + +
diff --git a/src/app/modules/productCategories/components/form.component.ts b/src/app/modules/productCategories/components/form.component.ts new file mode 100644 index 0000000..580d2e5 --- /dev/null +++ b/src/app/modules/productCategories/components/form.component.ts @@ -0,0 +1,68 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IProductCategoryRequest, IProductCategoryResponse } from '../models'; +import { ProductCategoriesService } from '../services/main.service'; + +@Component({ + selector: 'product-category-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class ProductCategoryFormComponent { + @Input() initialValues?: IProductCategoryResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: ProductCategoriesService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: [this.initialValues?.name || null, [Validators.required]], + description: [this.initialValues?.description || null, [Validators.required]], + imageUrl: [this.initialValues?.imageUrl || null, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IProductCategoryRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/productCategories/constants/apiRoutes/index.ts b/src/app/modules/productCategories/constants/apiRoutes/index.ts new file mode 100644 index 0000000..c24a2d2 --- /dev/null +++ b/src/app/modules/productCategories/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/product-categories'; + +export const PRODUCT_CATEGORIES_API_ROUTES = { + list: () => `${baseUrl}`, + single: (categoryId: string) => `${baseUrl}/${categoryId}`, +}; diff --git a/src/app/modules/productCategories/constants/index.ts b/src/app/modules/productCategories/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/productCategories/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/productCategories/constants/routes/index.ts b/src/app/modules/productCategories/constants/routes/index.ts new file mode 100644 index 0000000..72b0386 --- /dev/null +++ b/src/app/modules/productCategories/constants/routes/index.ts @@ -0,0 +1,27 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TProductCategoriesRouteNames = 'productCategories' | 'productCategory'; + +export const productCategoriesNamedRoutes: NamedRoutes = { + productCategories: { + path: 'product-categories', + loadComponent: () => + import('../../views/list.component').then((m) => m.ProductCategoriesComponent), + meta: { + title: 'دسته‌بندی محصولات', + pagePath: () => '/product-categories', + }, + }, + productCategory: { + path: 'product-categories/:categoryId', + loadComponent: () => + import('../../views/single.component').then((m) => m.ProductCategoryComponent), + meta: { + title: 'دسته‌بندی محصول', + pagePath: () => '/product-categories/:categoryId', + }, + }, +}; + +export const PRODUCT_CATEGORIES_ROUTES: Routes = Object.values(productCategoriesNamedRoutes); diff --git a/src/app/modules/productCategories/models/index.ts b/src/app/modules/productCategories/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/productCategories/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/productCategories/models/io.d.ts b/src/app/modules/productCategories/models/io.d.ts new file mode 100644 index 0000000..42ff440 --- /dev/null +++ b/src/app/modules/productCategories/models/io.d.ts @@ -0,0 +1,15 @@ +export interface IProductCategoryRawResponse { + id: number; + name: string; + description: string; + imageUrl: string; + createdAt: Date; +} + +export interface IProductCategoryResponse extends IProductCategoryRawResponse {} + +export interface IProductCategoryRequest { + name: string; + description: string; + imageUrl: string; +} diff --git a/src/app/modules/productCategories/services/main.service.ts b/src/app/modules/productCategories/services/main.service.ts new file mode 100644 index 0000000..741d9dd --- /dev/null +++ b/src/app/modules/productCategories/services/main.service.ts @@ -0,0 +1,28 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PRODUCT_CATEGORIES_API_ROUTES } from '../constants'; +import { + IProductCategoryRawResponse, + IProductCategoryRequest, + IProductCategoryResponse, +} from '../models'; + +@Injectable({ providedIn: 'root' }) +export class ProductCategoriesService { + constructor(private http: HttpClient) {} + + private apiRoutes = PRODUCT_CATEGORIES_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(categoryId: string): Observable { + return this.http.get(this.apiRoutes.single(categoryId)); + } + + create(data: IProductCategoryRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/productCategories/views/index.ts b/src/app/modules/productCategories/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/productCategories/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/productCategories/views/list.component.html b/src/app/modules/productCategories/views/list.component.html new file mode 100644 index 0000000..0213263 --- /dev/null +++ b/src/app/modules/productCategories/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/productCategories/views/list.component.ts b/src/app/modules/productCategories/views/list.component.ts new file mode 100644 index 0000000..0a7fc7f --- /dev/null +++ b/src/app/modules/productCategories/views/list.component.ts @@ -0,0 +1,38 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { IProductCategoryResponse } from '../models'; +import { ProductCategoriesService } from '../services/main.service'; + +@Component({ + selector: 'app-product-categories', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class ProductCategoriesComponent { + constructor(private productCategoryService: ProductCategoriesService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'description', header: 'توضیحات' }, + { field: 'imageUrl', header: 'تصویر' }, + { field: 'createdAt', header: 'تاریخ ایجاد' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.productCategoryService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/productCategories/views/single.component.html b/src/app/modules/productCategories/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/productCategories/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/productCategories/views/single.component.ts b/src/app/modules/productCategories/views/single.component.ts new file mode 100644 index 0000000..41d37f8 --- /dev/null +++ b/src/app/modules/productCategories/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-product-category', + templateUrl: './single.component.html', +}) +export class ProductCategoryComponent { + constructor() {} +} diff --git a/src/app/modules/stores/components/form.component.html b/src/app/modules/stores/components/form.component.html new file mode 100644 index 0000000..bec62ff --- /dev/null +++ b/src/app/modules/stores/components/form.component.html @@ -0,0 +1,8 @@ + +
+ + + + + +
diff --git a/src/app/modules/stores/components/form.component.ts b/src/app/modules/stores/components/form.component.ts new file mode 100644 index 0000000..de58e79 --- /dev/null +++ b/src/app/modules/stores/components/form.component.ts @@ -0,0 +1,68 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IStoreRequest, IStoreResponse } from '../models'; +import { StoresService } from '../services/main.service'; + +@Component({ + selector: 'store-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class StoreFormComponent { + @Input() initialValues?: IStoreResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: StoresService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: [this.initialValues?.name || null, [Validators.required]], + location: [this.initialValues?.location || null, [Validators.required]], + isActive: [this.initialValues?.isActive || false, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IStoreRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/stores/constants/apiRoutes/index.ts b/src/app/modules/stores/constants/apiRoutes/index.ts new file mode 100644 index 0000000..3fc2cdc --- /dev/null +++ b/src/app/modules/stores/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/stores'; + +export const STORES_API_ROUTES = { + list: () => `${baseUrl}`, + single: (storeId: string) => `${baseUrl}/${storeId}`, +}; diff --git a/src/app/modules/stores/constants/index.ts b/src/app/modules/stores/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/stores/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/stores/constants/routes/index.ts b/src/app/modules/stores/constants/routes/index.ts new file mode 100644 index 0000000..3840c58 --- /dev/null +++ b/src/app/modules/stores/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TStoresRouteNames = 'stores' | 'store'; + +export const storesNamedRoutes: NamedRoutes = { + stores: { + path: 'stores', + loadComponent: () => import('../../views/list.component').then((m) => m.StoresComponent), + meta: { + title: 'فروشگاه‌ها', + pagePath: () => '/stores', + }, + }, + store: { + path: 'stores/:storeId', + loadComponent: () => import('../../views/single.component').then((m) => m.StoreComponent), + meta: { + title: 'فروشگاه', + pagePath: () => '/stores/:storeId', + }, + }, +}; + +export const STORES_ROUTES: Routes = Object.values(storesNamedRoutes); diff --git a/src/app/modules/stores/models/index.ts b/src/app/modules/stores/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/stores/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/stores/models/io.d.ts b/src/app/modules/stores/models/io.d.ts new file mode 100644 index 0000000..6978821 --- /dev/null +++ b/src/app/modules/stores/models/io.d.ts @@ -0,0 +1,16 @@ +export interface IStoreRawResponse { + id: number; + name: string; + location: string; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +export interface IStoreResponse extends IStoreRawResponse {} + +export interface IStoreRequest { + name: string; + location: string; + isActive: boolean; +} diff --git a/src/app/modules/stores/services/main.service.ts b/src/app/modules/stores/services/main.service.ts new file mode 100644 index 0000000..8f8edb1 --- /dev/null +++ b/src/app/modules/stores/services/main.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { STORES_API_ROUTES } from '../constants'; +import { IStoreRawResponse, IStoreRequest, IStoreResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class StoresService { + constructor(private http: HttpClient) {} + + private apiRoutes = STORES_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(storeId: string): Observable { + return this.http.get(this.apiRoutes.single(storeId)); + } + + create(data: IStoreRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/stores/views/index.ts b/src/app/modules/stores/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/stores/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/stores/views/list.component.html b/src/app/modules/stores/views/list.component.html new file mode 100644 index 0000000..71e4440 --- /dev/null +++ b/src/app/modules/stores/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/stores/views/list.component.ts b/src/app/modules/stores/views/list.component.ts new file mode 100644 index 0000000..81ce25a --- /dev/null +++ b/src/app/modules/stores/views/list.component.ts @@ -0,0 +1,39 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { IStoreResponse } from '../models'; +import { StoresService } from '../services/main.service'; + +@Component({ + selector: 'app-stores', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class StoresComponent { + constructor(private storeService: StoresService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'location', header: 'موقعیت' }, + { field: 'isActive', header: 'فعال' }, + { field: 'createdAt', header: 'تاریخ ایجاد' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.storeService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/stores/views/single.component.html b/src/app/modules/stores/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/stores/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/stores/views/single.component.ts b/src/app/modules/stores/views/single.component.ts new file mode 100644 index 0000000..95581fd --- /dev/null +++ b/src/app/modules/stores/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-store', + templateUrl: './single.component.html', +}) +export class StoreComponent { + constructor() {} +} diff --git a/src/app/modules/suppliers/components/form.component.html b/src/app/modules/suppliers/components/form.component.html new file mode 100644 index 0000000..33213c7 --- /dev/null +++ b/src/app/modules/suppliers/components/form.component.html @@ -0,0 +1,14 @@ + +
+ + + + + + + + + + + +
diff --git a/src/app/modules/suppliers/components/form.component.ts b/src/app/modules/suppliers/components/form.component.ts new file mode 100644 index 0000000..aa2ee49 --- /dev/null +++ b/src/app/modules/suppliers/components/form.component.ts @@ -0,0 +1,74 @@ +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { ISupplierRequest, ISupplierResponse } from '../models'; +import { SuppliersService } from '../services/main.service'; + +@Component({ + selector: 'supplier-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], +}) +export class SupplierFormComponent { + @Input() initialValues?: ISupplierResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: SuppliersService) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + firstName: [this.initialValues?.firstName || null, [Validators.required]], + lastName: [this.initialValues?.lastName || null, [Validators.required]], + email: [this.initialValues?.email || null, [Validators.required, Validators.email]], + mobileNumber: [this.initialValues?.mobileNumber || null, [Validators.required]], + address: [this.initialValues?.address || null, [Validators.required]], + city: [this.initialValues?.city || null, [Validators.required]], + state: [this.initialValues?.state || null, [Validators.required]], + country: [this.initialValues?.country || null, [Validators.required]], + isActive: [this.initialValues?.isActive || false, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as ISupplierRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/suppliers/constants/apiRoutes/index.ts b/src/app/modules/suppliers/constants/apiRoutes/index.ts new file mode 100644 index 0000000..f4a129f --- /dev/null +++ b/src/app/modules/suppliers/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/suppliers'; + +export const SUPPLIERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (supplierId: string) => `${baseUrl}/${supplierId}`, +}; diff --git a/src/app/modules/suppliers/constants/index.ts b/src/app/modules/suppliers/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/suppliers/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/suppliers/constants/routes/index.ts b/src/app/modules/suppliers/constants/routes/index.ts new file mode 100644 index 0000000..3ec6d6a --- /dev/null +++ b/src/app/modules/suppliers/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TSuppliersRouteNames = 'suppliers' | 'supplier'; + +export const suppliersNamedRoutes: NamedRoutes = { + suppliers: { + path: 'suppliers', + loadComponent: () => import('../../views/list.component').then((m) => m.SuppliersComponent), + meta: { + title: 'تامین‌کنندگان', + pagePath: () => '/suppliers', + }, + }, + supplier: { + path: 'suppliers/:supplierId', + loadComponent: () => import('../../views/single.component').then((m) => m.SupplierComponent), + meta: { + title: 'تامین‌کننده', + pagePath: () => '/suppliers/:supplierId', + }, + }, +}; + +export const SUPPLIERS_ROUTES: Routes = Object.values(suppliersNamedRoutes); diff --git a/src/app/modules/suppliers/models/index.ts b/src/app/modules/suppliers/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/suppliers/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/suppliers/models/io.d.ts b/src/app/modules/suppliers/models/io.d.ts new file mode 100644 index 0000000..4d155c9 --- /dev/null +++ b/src/app/modules/suppliers/models/io.d.ts @@ -0,0 +1,29 @@ +export interface ISupplierRawResponse { + id: number; + firstName: string; + lastName: string; + email: string; + mobileNumber: string; + address: string; + city: string; + state: string; + country: string; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + deletedAt: Date; +} + +export interface ISupplierResponse extends ISupplierRawResponse {} + +export interface ISupplierRequest { + firstName: string; + lastName: string; + email: string; + mobileNumber: string; + address: string; + city: string; + state: string; + country: string; + isActive: boolean; +} diff --git a/src/app/modules/suppliers/services/main.service.ts b/src/app/modules/suppliers/services/main.service.ts new file mode 100644 index 0000000..ddbf516 --- /dev/null +++ b/src/app/modules/suppliers/services/main.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { SUPPLIERS_API_ROUTES } from '../constants'; +import { ISupplierRawResponse, ISupplierRequest, ISupplierResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class SuppliersService { + constructor(private http: HttpClient) {} + + private apiRoutes = SUPPLIERS_API_ROUTES; + + getAll(): Observable { + return this.http.get(this.apiRoutes.list()); + } + + getSingle(supplierId: string): Observable { + return this.http.get(this.apiRoutes.single(supplierId)); + } + + create(data: ISupplierRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/suppliers/views/index.ts b/src/app/modules/suppliers/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/suppliers/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/suppliers/views/list.component.html b/src/app/modules/suppliers/views/list.component.html new file mode 100644 index 0000000..f853845 --- /dev/null +++ b/src/app/modules/suppliers/views/list.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/modules/suppliers/views/list.component.ts b/src/app/modules/suppliers/views/list.component.ts new file mode 100644 index 0000000..57e2779 --- /dev/null +++ b/src/app/modules/suppliers/views/list.component.ts @@ -0,0 +1,45 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { ISupplierResponse } from '../models'; +import { SuppliersService } from '../services/main.service'; + +@Component({ + selector: 'app-suppliers', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class SuppliersComponent { + constructor(private supplierService: SuppliersService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'firstName', header: 'نام' }, + { field: 'lastName', header: 'نام خانوادگی' }, + { field: 'email', header: 'ایمیل' }, + { field: 'mobileNumber', header: 'شماره موبایل' }, + { field: 'address', header: 'آدرس' }, + { field: 'city', header: 'شهر' }, + { field: 'state', header: 'استان' }, + { field: 'country', header: 'کشور' }, + { field: 'isActive', header: 'فعال' }, + { field: 'createdAt', header: 'تاریخ ایجاد' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی' }, + { field: 'actions' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + + getData() { + this.loading.set(true); + this.supplierService.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res); + }); + } +} diff --git a/src/app/modules/suppliers/views/single.component.html b/src/app/modules/suppliers/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/suppliers/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/suppliers/views/single.component.ts b/src/app/modules/suppliers/views/single.component.ts new file mode 100644 index 0000000..4500ce7 --- /dev/null +++ b/src/app/modules/suppliers/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-supplier', + templateUrl: './single.component.html', +}) +export class SupplierComponent { + constructor() {} +} diff --git a/src/app/modules/users/components/form.component.html b/src/app/modules/users/components/form.component.html new file mode 100644 index 0000000..759f6b1 --- /dev/null +++ b/src/app/modules/users/components/form.component.html @@ -0,0 +1,9 @@ + +
+ + + + + + +
diff --git a/src/app/modules/users/components/form.component.ts b/src/app/modules/users/components/form.component.ts new file mode 100644 index 0000000..f40c8c9 --- /dev/null +++ b/src/app/modules/users/components/form.component.ts @@ -0,0 +1,81 @@ +import { mobileValidator } from '@/core/validators'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IUserRequest, IUserResponse } from '../models'; +import { UsersService } from '../services/main.service'; + +@Component({ + selector: 'user-form', + templateUrl: './form.component.html', + imports: [ + ReactiveFormsModule, + Dialog, + UikitFieldComponent, + InputComponent, + FormFooterActionsComponent, + ], +}) +export class UserFormComponent { + @Input() initialValues?: IUserResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor(private service: UsersService) { + effect(() => { + const v = this.visibleSignal(); + // this.visibleChange.emit(v); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + firstName: [this.initialValues?.firstName || null, [Validators.required]], + lastName: [this.initialValues?.lastName || null, [Validators.required]], + mobileNumber: [ + this.initialValues?.mobileNumber || null, + [Validators.required, mobileValidator()], + ], + roleId: [this.initialValues?.roleId || null, [Validators.required]], + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IUserRequest).subscribe({ + next: (res) => { + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/users/constants/apiRoutes/index.ts b/src/app/modules/users/constants/apiRoutes/index.ts new file mode 100644 index 0000000..91dda97 --- /dev/null +++ b/src/app/modules/users/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/users'; + +export const USERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (userId: string) => `${baseUrl}/${userId}`, +}; diff --git a/src/app/modules/users/constants/index.ts b/src/app/modules/users/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/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/modules/users/constants/routes/index.ts new file mode 100644 index 0000000..14e9d75 --- /dev/null +++ b/src/app/modules/users/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TUsersRouteNames = 'users' | 'user'; + +export const usersNamedRoutes: NamedRoutes = { + users: { + path: 'users', + loadComponent: () => import('../../views/list.component').then((m) => m.UsersComponent), + meta: { + title: 'کاربران', + pagePath: () => '/users', + }, + }, + user: { + path: 'users/:userId', + loadComponent: () => import('../../views/single.component').then((m) => m.UserComponent), + meta: { + title: 'کاربر', + pagePath: () => '/users/:userId', + }, + }, +}; + +export const USERS_ROUTES: Routes = Object.values(usersNamedRoutes); diff --git a/src/app/modules/users/models/index.ts b/src/app/modules/users/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/users/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/users/models/io.d.ts b/src/app/modules/users/models/io.d.ts new file mode 100644 index 0000000..dad19ee --- /dev/null +++ b/src/app/modules/users/models/io.d.ts @@ -0,0 +1,15 @@ +export interface IUserRawResponse { + mobileNumber: string; + firstName: string; + lastName: string; + id: number; + roleId: number; +} +export interface IUserResponse extends IUserRawResponse {} + +export interface IUserRequest { + firstName: string; + lastName: string; + mobileNumber: string; + roleId: number; +} diff --git a/src/app/modules/users/services/main.service.ts b/src/app/modules/users/services/main.service.ts new file mode 100644 index 0000000..ac7a81f --- /dev/null +++ b/src/app/modules/users/services/main.service.ts @@ -0,0 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { USERS_API_ROUTES } from '../constants'; +import { IUserRawResponse, IUserRequest, IUserResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class UsersService { + constructor(private http: HttpClient) {} + + private apiRoutes = USERS_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: IUserRequest): Observable { + return this.http.post(this.apiRoutes.list(), userData); + } + + update(userId: string, userData: IUserRequest): Observable { + return this.http.patch(this.apiRoutes.single(userId), userData); + } +} diff --git a/src/app/modules/users/views/index.ts b/src/app/modules/users/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/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/modules/users/views/list.component.html new file mode 100644 index 0000000..439b495 --- /dev/null +++ b/src/app/modules/users/views/list.component.html @@ -0,0 +1,15 @@ + + + diff --git a/src/app/modules/users/views/list.component.ts b/src/app/modules/users/views/list.component.ts new file mode 100644 index 0000000..a762cc5 --- /dev/null +++ b/src/app/modules/users/views/list.component.ts @@ -0,0 +1,48 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } 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 { + constructor(private userService: UsersService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'firstName', header: 'نام' }, + { field: 'lastName', header: 'نام خانوادگی' }, + { field: 'mobileNumber', header: 'شماره موبایل' }, + { field: 'roleId', header: 'نقش' }, + { field: 'actions' }, + ] 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); + }); + } + + openAddForm() { + this.visibleForm.set(true); + } +} diff --git a/src/app/modules/users/views/single.component.html b/src/app/modules/users/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/users/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/users/views/single.component.ts b/src/app/modules/users/views/single.component.ts new file mode 100644 index 0000000..e9f64f3 --- /dev/null +++ b/src/app/modules/users/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-user', + templateUrl: './single.component.html', +}) +export class UserComponent { + constructor() {} +} diff --git a/src/app/pages/auth/access.ts b/src/app/pages/auth/access.ts index 4c62f2d..aba196c 100644 --- a/src/app/pages/auth/access.ts +++ b/src/app/pages/auth/access.ts @@ -2,31 +2,48 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; import { ButtonModule } from 'primeng/button'; import { RippleModule } from 'primeng/ripple'; -import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator'; @Component({ - selector: 'app-access', - standalone: true, - imports: [ButtonModule, RouterModule, RippleModule, AppFloatingConfigurator, ButtonModule], - template: ` -
-
-
-
-
-
- -
-

Access Denied

- You do not have the necessary permisions. Please contact admins. - Access denied -
- -
-
-
-
+ selector: 'app-access', + standalone: true, + imports: [ButtonModule, RouterModule, RippleModule, ButtonModule], + template: `
+
+
+
+
+
+
-
` +

+ Access Denied +

+ You do not have the necessary permisions. Please contact admins. + Access denied +
+ +
+
+
+
+
+
`, }) export class Access {} diff --git a/src/app/pages/auth/error.ts b/src/app/pages/auth/error.ts index d3fc6f2..a61019f 100644 --- a/src/app/pages/auth/error.ts +++ b/src/app/pages/auth/error.ts @@ -2,31 +2,46 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; import { ButtonModule } from 'primeng/button'; import { RippleModule } from 'primeng/ripple'; -import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator'; @Component({ - selector: 'app-error', - imports: [ButtonModule, RippleModule, RouterModule, AppFloatingConfigurator, ButtonModule], - standalone: true, - template: ` -
-
-
-
-
-
- -
-

Error Occured

- Requested resource is not available. - Error -
- -
-
-
-
+ selector: 'app-error', + imports: [ButtonModule, RippleModule, RouterModule, ButtonModule], + standalone: true, + template: `
+
+
+
+
+
+
-
` +

+ Error Occured +

+ Requested resource is not available. + Error +
+ +
+
+
+
+
+
`, }) export class Error {} diff --git a/src/app/pages/auth/login.ts b/src/app/pages/auth/login.ts index 0b48f0a..aa93066 100644 --- a/src/app/pages/auth/login.ts +++ b/src/app/pages/auth/login.ts @@ -6,66 +6,127 @@ import { CheckboxModule } from 'primeng/checkbox'; import { InputTextModule } from 'primeng/inputtext'; import { PasswordModule } from 'primeng/password'; import { RippleModule } from 'primeng/ripple'; -import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator'; @Component({ - selector: 'app-login', - standalone: true, - imports: [ButtonModule, CheckboxModule, InputTextModule, PasswordModule, FormsModule, RouterModule, RippleModule, AppFloatingConfigurator], - template: ` - -
-
-
-
-
- - - - - - - - - -
Welcome to PrimeLand!
- Sign in to continue -
- -
- - - - - - -
-
- - -
- Forgot password? -
- -
-
-
+ selector: 'app-login', + standalone: true, + imports: [ + ButtonModule, + CheckboxModule, + InputTextModule, + PasswordModule, + FormsModule, + RouterModule, + RippleModule, + ], + template: ` +
+
+
+
+
+ + + + + + + + + +
+ Welcome to PrimeLand! +
+ Sign in to continue
+ +
+ + + + + + +
+
+ + +
+ Forgot password? +
+ +
+
- ` +
+
+ `, }) export class Login { - email: string = ''; + email: string = ''; - password: string = ''; + password: string = ''; - checked: boolean = false; + checked: boolean = false; } diff --git a/src/app/pages/landing/components/featureswidget.ts b/src/app/pages/landing/components/featureswidget.ts deleted file mode 100644 index 68a459f..0000000 --- a/src/app/pages/landing/components/featureswidget.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'features-widget', - standalone: true, - imports: [CommonModule], - template: `
-
-
-
Marvelous Features
- Placerat in egestas erat... -
- -
-
-
-
- -
-
Easy to Use
- Posuere morbi leo urna molestie. -
-
-
- -
-
-
-
- -
-
Fresh Design
- Semper risus in hendrerit. -
-
-
- -
-
-
-
- -
-
Well Documented
- Non arcu risus quis varius quam quisque. -
-
-
- -
-
-
-
- -
-
Responsive Layout
- Nulla malesuada pellentesque elit. -
-
-
- -
-
-
-
- -
-
Clean Code
- Condimentum lacinia quis vel eros. -
-
-
- -
-
-
-
- -
-
Dark Mode
- Convallis tellus id interdum velit laoreet. -
-
-
- -
-
-
-
- -
-
Ready to Use
- Mauris sit amet massa vitae. -
-
-
- -
-
-
-
- -
-
Modern Practices
- Elementum nibh tellus molestie nunc non. -
-
-
- -
-
-
-
- -
-
Privacy
- Neque egestas congue quisque. -
-
-
- -
-
-
Joséphine Miller
- Peak Interactive -

- “Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.” -

- Company logo -
-
-
-
` -}) -export class FeaturesWidget {} diff --git a/src/app/pages/landing/components/footerwidget.ts b/src/app/pages/landing/components/footerwidget.ts deleted file mode 100644 index a8cbd8d..0000000 --- a/src/app/pages/landing/components/footerwidget.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Component } from '@angular/core'; -import { Router, RouterModule } from '@angular/router'; - -@Component({ - selector: 'footer-widget', - imports: [RouterModule], - template: ` -
-
- - -
-
- - -
-

Resources

- Get Started - Learn - Case Studies -
- -
-

Community

- Discord - Eventsbadge - FAQ - Blog -
- - -
-
-
-
- ` -}) -export class FooterWidget { - constructor(public router: Router) {} -} diff --git a/src/app/pages/landing/components/herowidget.ts b/src/app/pages/landing/components/herowidget.ts deleted file mode 100644 index fbbf017..0000000 --- a/src/app/pages/landing/components/herowidget.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from '@angular/core'; -import { ButtonModule } from 'primeng/button'; -import { RippleModule } from 'primeng/ripple'; - -@Component({ - selector: 'hero-widget', - imports: [ButtonModule, RippleModule], - template: ` -
-
-

Eu sem integereget magna fermentum

-

Sed blandit libero volutpat sed cras. Fames ac turpis egestas integer. Placerat in egestas erat...

- -
-
- Hero Image -
-
- ` -}) -export class HeroWidget {} diff --git a/src/app/pages/landing/components/highlightswidget.ts b/src/app/pages/landing/components/highlightswidget.ts deleted file mode 100644 index 8e80e2d..0000000 --- a/src/app/pages/landing/components/highlightswidget.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'highlights-widget', - template: ` -
-
-
Powerful Everywhere
- Amet consectetur adipiscing elit... -
- -
-
- mockup mobile -
- -
-
- -
-
Congue Quisque Egestas
- Lectus arcu bibendum at varius vel pharetra vel turpis nunc. Eget aliquet nibh praesent tristique magna sit amet purus gravida. Sit amet mattis vulputate enim nulla aliquet. -
-
- -
-
-
- -
-
Celerisque Eu Ultrices
- Adipiscing commodo elit at imperdiet dui. Viverra nibh cras pulvinar mattis nunc sed blandit libero. Suspendisse in est ante in. Mauris pharetra et ultrices neque ornare aenean euismod elementum nisi. -
- -
- mockup -
-
-
- ` -}) -export class HighlightsWidget {} diff --git a/src/app/pages/landing/components/pricingwidget.ts b/src/app/pages/landing/components/pricingwidget.ts deleted file mode 100644 index f091a2c..0000000 --- a/src/app/pages/landing/components/pricingwidget.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Component } from '@angular/core'; -import { ButtonModule } from 'primeng/button'; -import { DividerModule } from 'primeng/divider'; -import { RippleModule } from 'primeng/ripple'; - -@Component({ - selector: 'pricing-widget', - imports: [DividerModule, ButtonModule, RippleModule], - template: ` -
-
-
Matchless Pricing
- Amet consectetur adipiscing elit... -
- -
-
-
-
Free
- free -
-
- $0 - per month -
- -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
- -
-
-
Startup
- startup -
-
- $1 - per month -
- -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
- -
-
-
Enterprise
- enterprise -
-
- $5 - per month -
- -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
-
-
- ` -}) -export class PricingWidget {} diff --git a/src/app/pages/landing/components/topbarwidget.component.ts b/src/app/pages/landing/components/topbarwidget.component.ts deleted file mode 100644 index 879c0a0..0000000 --- a/src/app/pages/landing/components/topbarwidget.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Component } from '@angular/core'; -import { StyleClassModule } from 'primeng/styleclass'; -import { Router, RouterModule } from '@angular/router'; -import { RippleModule } from 'primeng/ripple'; -import { ButtonModule } from 'primeng/button'; -import {AppFloatingConfigurator} from "@/layout/component/app.floatingconfigurator"; - -@Component({ - selector: 'topbar-widget', - imports: [RouterModule, StyleClassModule, ButtonModule, RippleModule, AppFloatingConfigurator], - template: ` - - - - - - - - - - SAKAI - - - - - - - ` -}) -export class TopbarWidget { - constructor(public router: Router) {} -} diff --git a/src/app/pages/landing/landing.ts b/src/app/pages/landing/landing.ts deleted file mode 100644 index 56d3e46..0000000 --- a/src/app/pages/landing/landing.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { RippleModule } from 'primeng/ripple'; -import { StyleClassModule } from 'primeng/styleclass'; -import { ButtonModule } from 'primeng/button'; -import { DividerModule } from 'primeng/divider'; -import { TopbarWidget } from './components/topbarwidget.component'; -import { HeroWidget } from './components/herowidget'; -import { FeaturesWidget } from './components/featureswidget'; -import { HighlightsWidget } from './components/highlightswidget'; -import { PricingWidget } from './components/pricingwidget'; -import { FooterWidget } from './components/footerwidget'; - -@Component({ - selector: 'app-landing', - standalone: true, - imports: [RouterModule, TopbarWidget, HeroWidget, FeaturesWidget, HighlightsWidget, PricingWidget, FooterWidget, RippleModule, StyleClassModule, ButtonModule, DividerModule], - template: ` -
-
- - - - - - -
-
- ` -}) -export class Landing {} diff --git a/src/app/pages/notfound/notfound.component.html b/src/app/pages/notfound/notfound.component.html new file mode 100644 index 0000000..d388b20 --- /dev/null +++ b/src/app/pages/notfound/notfound.component.html @@ -0,0 +1,52 @@ +
+
+
+ + صندوق فروشگاهی +
+
+
+ 404 +

صفحه مورد نظر یافت نشد

+
+ متاسفانه صفحه ای که به دنبال آن هستید وجود ندارد یا ممکن است حذف شده باشد. +
+
+ @for (item of notFoundListItems; track item) { + + + + + + {{ + item.title + }} + {{ item.description }} + + + } +
+ +
+
+
+
diff --git a/src/app/pages/notfound/notfound.component.ts b/src/app/pages/notfound/notfound.component.ts new file mode 100644 index 0000000..56ce444 --- /dev/null +++ b/src/app/pages/notfound/notfound.component.ts @@ -0,0 +1,29 @@ +import { Component, inject } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { ButtonModule } from 'primeng/button'; +import images from 'src/assets/images'; + +@Component({ + selector: 'app-notfound', + standalone: true, + imports: [RouterModule, ButtonModule], + templateUrl: './notfound.component.html', +}) +export class Notfound { + readonly logo = images.logo; + + readonly notFoundListItems = [ + { + icon: 'pi-table', + title: 'سوالات متداول', + description: 'لیست سوالات متداول', + link: '/', + }, + { + icon: 'pi-question-circle', + title: 'تیکت پشتیبانی', + description: 'از این طریق می‌توانید با پشتیبانی در ارتباط باشید', + link: '/', + }, + ]; +} diff --git a/src/app/pages/notfound/notfound.ts b/src/app/pages/notfound/notfound.ts deleted file mode 100644 index 07ef44a..0000000 --- a/src/app/pages/notfound/notfound.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { ButtonModule } from 'primeng/button'; -import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator'; - -@Component({ - selector: 'app-notfound', - standalone: true, - imports: [RouterModule, AppFloatingConfigurator, ButtonModule], - template: ` - ` -}) -export class Notfound {} diff --git a/src/app/shared/components/input/input.component.html b/src/app/shared/components/input/input.component.html index e744cf1..a766364 100644 --- a/src/app/shared/components/input/input.component.html +++ b/src/app/shared/components/input/input.component.html @@ -1,17 +1,21 @@ - + @if (type === "switch") { + + } @else { + + } diff --git a/src/app/shared/components/input/input.component.ts b/src/app/shared/components/input/input.component.ts index f5c881e..a23193b 100644 --- a/src/app/shared/components/input/input.component.ts +++ b/src/app/shared/components/input/input.component.ts @@ -4,15 +4,17 @@ import { CommonModule } from '@angular/common'; import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { InputTextModule } from 'primeng/inputtext'; +import { ToggleSwitch } from 'primeng/toggleswitch'; @Component({ selector: 'app-input', standalone: true, - imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent], + imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent, ToggleSwitch], templateUrl: './input.component.html', }) export class InputComponent { - @Input() type: 'simple' | 'postalCode' | 'mobile' | 'phone' | 'email' = 'simple'; + @Input() type: 'simple' | 'postalCode' | 'mobile' | 'phone' | 'email' | 'checkbox' | 'switch' = + 'simple'; @Input() control!: FormControl>; @Input() name!: string; @Input() label: string = ''; 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 bacaa3a..dff5a06 100644 --- a/src/app/shared/components/pageDataList/page-data-list.component.html +++ b/src/app/shared/components/pageDataList/page-data-list.component.html @@ -83,7 +83,7 @@ - + + @for (i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; track i) { diff --git a/src/app/shared/components/pageDataList/page-data-list.component.ts b/src/app/shared/components/pageDataList/page-data-list.component.ts index fb1be86..2b6c01b 100644 --- a/src/app/shared/components/pageDataList/page-data-list.component.ts +++ b/src/app/shared/components/pageDataList/page-data-list.component.ts @@ -80,9 +80,9 @@ export class PageDataListComponent { filterDrawerVisible = signal(false); ngOnInit() { - if (this.fullHeight) { - this.renderer.setStyle(this.host.nativeElement, 'height', '100cqmin'); - } + // if (this.fullHeight) { + // this.renderer.setStyle(this.host.nativeElement, 'height', '100cqmin'); + // } } edit = (item: I) => { diff --git a/src/app/uikit/uikit-emptystate.component.html b/src/app/uikit/uikit-emptystate.component.html index b6ce4b5..6e86269 100644 --- a/src/app/uikit/uikit-emptystate.component.html +++ b/src/app/uikit/uikit-emptystate.component.html @@ -1,6 +1,6 @@
- + {{ title }} @if (description) { diff --git a/src/app/uikit/uikit-emptystate.component.ts b/src/app/uikit/uikit-emptystate.component.ts index ee70566..03384ea 100644 --- a/src/app/uikit/uikit-emptystate.component.ts +++ b/src/app/uikit/uikit-emptystate.component.ts @@ -1,36 +1,36 @@ import { - AfterContentInit, - Component, - ContentChild, - ElementRef, - EventEmitter, - Input, - Output, + AfterContentInit, + Component, + ContentChild, + ElementRef, + EventEmitter, + Input, + Output, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Button } from 'primeng/button'; @Component({ - selector: 'uikit-empty-state', - standalone: true, - imports: [CommonModule, Button], - templateUrl: './uikit-emptystate.component.html', + selector: 'uikit-empty-state', + standalone: true, + imports: [CommonModule, Button], + templateUrl: './uikit-emptystate.component.html', }) export class UikitEmptyStateComponent implements AfterContentInit { - @ContentChild('cta', { static: false, read: ElementRef }) - ctaContent?: ElementRef; - hasCtaContent = false; + @ContentChild('cta', { static: false, read: ElementRef }) + ctaContent?: ElementRef; + hasCtaContent = false; - @Input() title: string = 'متاسفانه موردی یافت نشد.'; - @Input() description?: string; - @Input() icon: string = 'pi pi-database'; - @Input() ctaLabel?: string; - @Input() showCTA?: boolean = true; + @Input() title: string = 'متاسفانه موردی یافت نشد.'; + @Input() description?: string; + @Input() icon: string = 'pi pi-database'; + @Input() ctaLabel?: string; + @Input() showCTA?: boolean = true; - @Output() ctaClick = new EventEmitter(); + @Output() ctaClick = new EventEmitter(); - ngAfterContentInit() { - this.hasCtaContent = !!this.ctaContent; - } + ngAfterContentInit() { + this.hasCtaContent = !!this.ctaContent; + } } diff --git a/src/assets/images/index.ts b/src/assets/images/index.ts index 1d6d6d2..638f57b 100644 --- a/src/assets/images/index.ts +++ b/src/assets/images/index.ts @@ -2,7 +2,7 @@ // The Angular CLI / build serves files in `src/assets` at `/assets`. import errors from './errors'; import login from './login.webp'; -import logo from './logo.webp'; +import logo from './logo.png'; export default { logo, diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png index d9b4cf5..f82b4c3 100644 Binary files a/src/assets/images/logo.png and b/src/assets/images/logo.png differ diff --git a/src/assets/images/logo.webp b/src/assets/images/logo.webp deleted file mode 100644 index 19f9483..0000000 Binary files a/src/assets/images/logo.webp and /dev/null differ diff --git a/src/assets/layout/_main.scss b/src/assets/layout/_main.scss index 75831da..85ffa8b 100644 --- a/src/assets/layout/_main.scss +++ b/src/assets/layout/_main.scss @@ -15,5 +15,5 @@ .layout-main { flex: 1 1 auto; - overflow: hidden; + overflow: auto; } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index e789966..15584fc 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,9 +1,9 @@ // Development environment configuration export const environment = { - production: false, - apiBaseUrl: 'https://localhost', - host: 'localhost', - port: 60749, - enableLogging: true, - enableDebug: true, + production: false, + apiBaseUrl: 'http://localhost:3000', + host: 'localhost', + port: 60749, + enableLogging: true, + enableDebug: true, }; diff --git a/src/index.html b/src/index.html index 82ca35a..ddfd142 100644 --- a/src/index.html +++ b/src/index.html @@ -5,9 +5,8 @@ pos - - - + + diff --git a/src/proxy.conf.js b/src/proxy.conf.js index 8d37aee..331f35e 100644 --- a/src/proxy.conf.js +++ b/src/proxy.conf.js @@ -1,16 +1,16 @@ -const { env } = require('process'); +// const { env } = require('process'); -const target = env.ASPNETCORE_HTTPS_PORT - ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` - : env.ASPNETCORE_URLS - ? env.ASPNETCORE_URLS.split(';')[0] - : 'https://localhost:7148'; +// const target = env.ASPNETCORE_HTTPS_PORT +// ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` +// : env.ASPNETCORE_URLS +// ? env.ASPNETCORE_URLS.split(';')[0] +// : 'https://localhost:7148'; -const PROXY_CONFIG = [ - { - target, - secure: false, - }, -]; +// const PROXY_CONFIG = [ +// { +// target, +// secure: false, +// }, +// ]; -module.exports = PROXY_CONFIG; +// module.exports = PROXY_CONFIG;