Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bcb917d3b | |||
| 88adb566eb |
@@ -162,6 +162,10 @@
|
||||
],
|
||||
"extractLicenses": false,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/config.ts",
|
||||
"with": "src/tenants/tis/config.ts"
|
||||
},
|
||||
{
|
||||
"replace": "src/assets/images/brandingAssets.ts",
|
||||
"with": "src/tenants/tis/brandingAssets.ts"
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||
"appData": {
|
||||
"appVersion": "0.0.0",
|
||||
"buildDate": "2026-05-11T10:08:38.776Z"
|
||||
"appVersion": "0.0.3",
|
||||
"buildDate": "2026-05-11T13:52:49.314Z",
|
||||
"buildNumber": 3
|
||||
},
|
||||
"assetGroups": [
|
||||
{
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"purpose": "any",
|
||||
"sizes": "192x192",
|
||||
"src": "/favicon/web-app-manifest-192x192.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "maskable",
|
||||
"sizes": "192x192",
|
||||
"src": "/favicon/web-app-manifest-192x192.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "any",
|
||||
"sizes": "512x512",
|
||||
"src": "/favicon/web-app-manifest-512x512.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "maskable",
|
||||
"sizes": "512x512",
|
||||
|
||||
@@ -2,16 +2,28 @@
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"purpose": "any",
|
||||
"sizes": "192x192",
|
||||
"src": "/favicon/web-app-manifest-192x192.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "maskable",
|
||||
"sizes": "192x192",
|
||||
"src": "/web-app-manifest-192x192.png",
|
||||
"src": "/favicon/web-app-manifest-192x192.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "any",
|
||||
"sizes": "512x512",
|
||||
"src": "/favicon/web-app-manifest-512x512.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "maskable",
|
||||
"sizes": "512x512",
|
||||
"src": "/web-app-manifest-512x512.png",
|
||||
"src": "/favicon/web-app-manifest-512x512.png",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { PwaInstallService } from '@/core/services/pwa-install.service';
|
||||
import { Component, HostListener } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ConfirmDialog } from 'primeng/confirmdialog';
|
||||
import { ToastModule } from 'primeng/toast';
|
||||
import { brandingConfig } from './app/branding/branding.config';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -16,8 +18,11 @@ import { ToastModule } from 'primeng/toast';
|
||||
export class AppComponent {
|
||||
toastPosition: 'top-center' | 'bottom-right' = 'bottom-right';
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly pwaInstallService: PwaInstallService) {
|
||||
this.updateToastPosition();
|
||||
if (brandingConfig.enableInstallPrompt) {
|
||||
this.pwaInstallService.init();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
|
||||
+7
-7
@@ -10,13 +10,13 @@ export const appRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('@/layout/default/app.layout.component').then((m) => m.AppLayout),
|
||||
children: [
|
||||
SUPER_ADMIN_ROUTES,
|
||||
CONSUMER_ROUTES,
|
||||
PROVIDER_ROUTES,
|
||||
PARTNER_ROUTES,
|
||||
...POS_ROUTES.children!,
|
||||
],
|
||||
children: [SUPER_ADMIN_ROUTES, CONSUMER_ROUTES, PROVIDER_ROUTES, PARTNER_ROUTES],
|
||||
},
|
||||
{
|
||||
path: 'pos',
|
||||
loadComponent: () =>
|
||||
import('@/domains/pos/layouts/layout.component').then((m) => m.PosLayoutComponent),
|
||||
children: [POS_ROUTES],
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Router } from '@angular/router';
|
||||
import { CookieService } from 'ngx-cookie-service';
|
||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { catchError, finalize, tap } from 'rxjs/operators';
|
||||
import config from 'src/config';
|
||||
import { COOKIE_KEYS, LOCAL_STORAGE_KEYS } from '../../../assets/constants';
|
||||
import {
|
||||
IAuthAccountResponse,
|
||||
@@ -75,7 +76,7 @@ export class AuthService {
|
||||
return this.http
|
||||
.post<IAuthResponse>(
|
||||
'/api/v1/auth/login',
|
||||
{ username, password },
|
||||
{ username, password, isPos: config.isPosApplication },
|
||||
{
|
||||
headers: baseHeaders,
|
||||
},
|
||||
|
||||
@@ -4,3 +4,4 @@ export { ErrorHandlerService } from './error-handler.service';
|
||||
export { ConfigService } from './config.service';
|
||||
export * from './breadcrumb.service';
|
||||
export * from './native-bridge.service';
|
||||
export * from './pwa-install.service';
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { ConfirmationService } from 'primeng/api';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PwaInstallService {
|
||||
private deferredInstallPrompt: any = null;
|
||||
readonly isInstalled = signal(false);
|
||||
readonly canInstall = signal(false);
|
||||
|
||||
constructor(private readonly confirmationService: ConfirmationService) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const standalone =
|
||||
window.matchMedia('(display-mode: standalone)').matches ||
|
||||
(window.navigator as any).standalone === true;
|
||||
|
||||
console.log(window.matchMedia('(display-mode: standalone)').matches);
|
||||
console.log(window.navigator);
|
||||
|
||||
this.isInstalled.set(standalone);
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (event: any) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.deferredInstallPrompt = event;
|
||||
this.canInstall.set(true);
|
||||
console.log(this.deferredInstallPrompt);
|
||||
});
|
||||
|
||||
window.addEventListener('appinstalled', () => {
|
||||
this.deferredInstallPrompt = null;
|
||||
this.canInstall.set(false);
|
||||
this.isInstalled.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
openInstallConfirm() {
|
||||
if (this.isInstalled() || !this.canInstall() || !this.deferredInstallPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('openInstallConfirm');
|
||||
|
||||
this.confirmationService.confirm({
|
||||
header: 'نصب اپلیکیشن',
|
||||
message: 'برای ادامه میبایست نیازمندیهای نرمافزار را نصب کنید',
|
||||
acceptLabel: 'نصب',
|
||||
rejectButtonProps: {
|
||||
outlined: true,
|
||||
},
|
||||
accept: () => {
|
||||
this.deferredInstallPrompt.prompt();
|
||||
this.deferredInstallPrompt.userChoice.finally(() => {
|
||||
this.deferredInstallPrompt = null;
|
||||
this.canInstall.set(false);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,18 @@
|
||||
<div class="h-svh w-svw flex flex-col items-center justify-center">
|
||||
{{ requireInstall }}
|
||||
{{ pwaInstalled() }}
|
||||
<img [src]="logo" class="w-32 animate-pulse" />
|
||||
<span class="text-xl font-medium text-center text-muted-color mt-4"> در حال بارگذاری ... </span>
|
||||
@if (requireInstall && !pwaInstalled()) {
|
||||
<div class="mt-6 flex flex-col items-center gap-3">
|
||||
<span class="text-base font-medium text-center text-muted-color">برای ادامه، نصب برنامه الزامی است.</span>
|
||||
@if (canInstall()) {
|
||||
<button pButton type="button" size="small" (click)="installPwa()">نصب برنامه</button>
|
||||
} @else {
|
||||
<span class="text-sm text-center text-muted-color">منتظر فعال شدن امکان نصب در مرورگر...</span>
|
||||
}
|
||||
<button pButton type="button" outlined size="small" (click)="continueAfterInstall()">ادامه</button>
|
||||
</div>
|
||||
} @else {
|
||||
<span class="text-xl font-medium text-center text-muted-color mt-4"> در حال بارگذاری ... </span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { PwaInstallService } from '@/core/services';
|
||||
import { Component, EventEmitter, inject, Output } from '@angular/core';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { finalize, forkJoin } from 'rxjs';
|
||||
import images from 'src/assets/images';
|
||||
import { PosInfoStore, PosProfileStore } from '../store';
|
||||
@@ -7,6 +9,7 @@ import { DeviceInfoStore } from '../store/device.store';
|
||||
@Component({
|
||||
selector: 'pos-splash',
|
||||
templateUrl: 'splash.component.html',
|
||||
imports: [ButtonDirective],
|
||||
})
|
||||
export class PosSplashComponent {
|
||||
@Output() onComplete = new EventEmitter<void>();
|
||||
@@ -14,8 +17,27 @@ export class PosSplashComponent {
|
||||
private readonly posProfileStore = inject(PosProfileStore);
|
||||
private readonly posInfoStore = inject(PosInfoStore);
|
||||
private readonly deviceInfoStore = inject(DeviceInfoStore);
|
||||
private readonly pwaInstallService = inject(PwaInstallService);
|
||||
|
||||
logo = images.logo;
|
||||
requireInstall = false;
|
||||
// requireInstall = brandingConfig.enableInstallPrompt;
|
||||
pwaInstalled = this.pwaInstallService.isInstalled;
|
||||
canInstall = this.pwaInstallService.canInstall;
|
||||
private installPromptShown = false;
|
||||
|
||||
// constructor() {
|
||||
// effect(() => {
|
||||
// if (!this.requireInstall || this.pwaInstalled() || this.installPromptShown) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (this.canInstall()) {
|
||||
// this.installPromptShown = true;
|
||||
// this.pwaInstallService.openInstallConfirm();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
async ngOnInit() {
|
||||
const data = await this.deviceInfoStore.getData();
|
||||
@@ -25,9 +47,27 @@ export class PosSplashComponent {
|
||||
forkJoin([profileObs, infoObs])
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.onComplete.emit();
|
||||
if (!this.requireInstall || this.pwaInstalled()) {
|
||||
this.onComplete.emit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canInstall()) {
|
||||
this.pwaInstallService.openInstallConfirm();
|
||||
this.installPromptShown = true;
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe(([profileResult, infoResult]) => {});
|
||||
}
|
||||
|
||||
installPwa() {
|
||||
this.pwaInstallService.openInstallConfirm();
|
||||
}
|
||||
|
||||
continueAfterInstall() {
|
||||
if (this.pwaInstalled()) {
|
||||
this.onComplete.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<!-- [ngStyle]="{ transform: 'scale(1.' + pullDistance() * 10 + ')' }" -->
|
||||
</div>
|
||||
}
|
||||
{{ isAuth() }}
|
||||
@if (isAuth()) {
|
||||
@defer (when isAuth()) {
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -19,18 +19,18 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/sale_invoices"
|
||||
routerLink="/sale_invoices"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
<i class="pi pi-home me-2"></i>
|
||||
<span class="font-medium">مشاهدهی فاکتورها</span>
|
||||
<span class="font-medium">فاکتورها</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos"
|
||||
routerLink="/"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
@@ -41,7 +41,7 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/about"
|
||||
routerLink="/about"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
@@ -52,7 +52,7 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/support"
|
||||
routerLink="/support"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
@@ -16,6 +17,7 @@ import { PosInfoStore } from '../../store';
|
||||
export class PosMainMenuSidebarComponent extends AbstractDialog {
|
||||
private readonly posInfoStore = inject(PosInfoStore);
|
||||
private readonly swUpdate = inject(SwUpdate);
|
||||
private readonly toastService = inject(ToastService);
|
||||
|
||||
readonly posInfo = computed(() => this.posInfoStore.entity());
|
||||
appVersion = '0.0.0';
|
||||
@@ -31,12 +33,15 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
|
||||
|
||||
ngOnInit() {
|
||||
this.isPwaBuild = this.swUpdate.isEnabled;
|
||||
console.log(this.swUpdate);
|
||||
|
||||
if (!this.isPwaBuild) return;
|
||||
|
||||
fetch('/ngsw.json')
|
||||
.then((res) => res.json())
|
||||
.then((data: { appData?: { appVersion?: string } }) => {
|
||||
this.appVersion = data?.appData?.appVersion || this.appVersion;
|
||||
this.toastService.info({ text: this.appVersion });
|
||||
console.log('appVersion:', data?.appData);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -116,6 +116,10 @@ export class PosPagesLayoutComponent implements AfterViewInit {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.layoutService.changeIsFullPage(true);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.layoutService.setTopbarStartSlot(this.topbarStart);
|
||||
this.layoutService.setTopbarCenterSlot(this.topbarCenter);
|
||||
|
||||
@@ -61,10 +61,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@defer (when isVisibleCustomerForm()) {
|
||||
<pos-order-customer-dialog [(visible)]="isVisibleCustomerForm" (onSubmit)="submitCustomer()" />
|
||||
}
|
||||
<pos-order-customer-dialog [(visible)]="isVisibleCustomerForm" (onSubmit)="submitCustomer()" />
|
||||
<!-- <pos-pre-invoice-dialog [(visible)]="isVisiblePreInvoiceForm" /> -->
|
||||
@defer (when isVisiblePaymentForm()) {
|
||||
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment($event)" />
|
||||
}
|
||||
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment($event)" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { POS_ROUTES } from '@/domains/pos/routes';
|
||||
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
@@ -70,7 +71,7 @@ export class PosSaleInvoiceListComponent {
|
||||
});
|
||||
});
|
||||
activeFilterCount = computed(() => this.activeFilters().length);
|
||||
backRoute = '/pos';
|
||||
backRoute = POS_ROUTES.path || '/';
|
||||
|
||||
ngOnInit() {
|
||||
this.getData();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import images from 'src/assets/images';
|
||||
import config from 'src/config';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notfound',
|
||||
@@ -12,6 +13,8 @@ import images from 'src/assets/images';
|
||||
export class Notfound {
|
||||
readonly logo = images.logo;
|
||||
|
||||
isPosApplication = config.isPosApplication;
|
||||
|
||||
readonly notFoundListItems = [
|
||||
{
|
||||
icon: 'pi-table',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// TIS tenant environment configuration
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiBaseUrl: 'http://194.59.214.243:5002',
|
||||
// apiBaseUrl: 'http://192.168.128.73:5002',
|
||||
// apiBaseUrl: 'http://194.59.214.243:5002',
|
||||
apiBaseUrl: 'http://192.168.128.73:5002',
|
||||
host: 'localhost',
|
||||
port: 5000,
|
||||
enableLogging: false,
|
||||
|
||||
@@ -2,10 +2,12 @@ export interface BrandingConfig {
|
||||
appTitle: string;
|
||||
manifestPath: string;
|
||||
themeColor: string;
|
||||
enableInstallPrompt?: boolean;
|
||||
}
|
||||
|
||||
export const brandingConfig: BrandingConfig = {
|
||||
appTitle: 'پنل مدیریت صورتحسابهای مالیاتی',
|
||||
manifestPath: '/favicon/site.webmanifest',
|
||||
themeColor: '#ffffff',
|
||||
enableInstallPrompt: false,
|
||||
};
|
||||
|
||||
@@ -4,4 +4,5 @@ export const brandingConfig: BrandingConfig = {
|
||||
appTitle: 'تیس - مدیریت صورتحسابهای مالیاتی',
|
||||
manifestPath: '/favicon/site.webmanifest',
|
||||
themeColor: '#ffffff',
|
||||
enableInstallPrompt: true,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user