From 6ad1a73c16f945fb968c25b6d7e1c296bd7d3279 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Sat, 23 May 2026 18:09:44 +0330 Subject: [PATCH] feat(pos): add shop and statistics modules with components, services, and routing - Implemented shop module with root component, loading state, and invoice handling. - Created statistics module with invoice type card component, API routes, and data models. - Added season picker component for selecting year and season. - Integrated services for fetching statistics data and managing state. - Established routing for statistics and shop views. --- angular.json | 4 + pnpm-lock.yaml | 6 +- .../models/salesInvoices_io.d.ts | 2 +- .../main-menu-sidebar.component.ts | 12 +- .../pagesLayout/choose-pos.component.ts | 4 +- .../modules/configs/views/root.component.html | 4 +- .../modules/configs/views/root.component.ts | 3 +- .../order/held-order-item.component.html | 28 ----- .../order/held-order-item.component.ts | 57 --------- .../order/held-orders.component.html | 19 --- .../components/order/held-orders.component.ts | 39 ------ .../components/filter-drawer.component.ts | 4 +- .../saleInvoices/components/list.component.ts | 85 +++++++++++-- .../modules/saleInvoices/models/filter.dto.ts | 2 +- .../components/categories.component.html | 0 .../components/categories.component.ts | 0 .../customers/customer-dialog.component.html | 0 .../customers/customer-dialog.component.ts | 0 .../customers/individual/form.component.html | 0 .../customers/individual/form.component.ts | 0 .../customers/legal/form.component.html | 0 .../customers/legal/form.component.ts | 0 .../customers/unknown/form.component.html | 0 .../customers/unknown/form.component.ts | 0 .../components/favorite-CTA.component.html | 0 .../components/favorite-CTA.component.ts | 0 .../components/goods.component.html | 0 .../components/goods.component.ts | 0 .../components/grid-view.component.html | 0 .../components/grid-view.component.ts | 0 .../components/list-view.component.html | 0 .../components/list-view.component.ts | 0 .../order/order-card-template.component.html | 0 .../order/order-card-template.component.ts | 0 .../order/order-section.component.html | 0 .../order/order-section.component.ts | 0 .../order-submitted-dialog.component.html | 0 .../order/order-submitted-dialog.component.ts | 0 .../order/price-info-card.component.html | 0 .../order/price-info-card.component.ts | 0 .../components/payload-form.component.html | 0 .../components/payload-form.component.ts | 0 ...dialog-amount-card-template.component.html | 0 ...m-dialog-amount-card-template.component.ts | 0 .../payloads/gold/form.component.html | 0 .../payloads/gold/form.component.ts | 0 .../payloads/gold/order-card.component.html | 0 .../payloads/gold/order-card.component.ts | 0 .../components/payloads/index.ts | 0 .../payloads/standard/form.component.html | 0 .../payloads/standard/form.component.ts | 0 .../standard/order-card.component.html | 0 .../payloads/standard/order-card.component.ts | 0 .../payment/form-dialog.component.html | 0 .../payment/form-dialog.component.ts | 0 .../constants/apiRoutes/favorite.ts | 0 .../constants/apiRoutes/index.ts | 0 .../{landing => shop}/constants/index.ts | 0 .../modules/shop/constants/routes/index.ts | 20 +++ .../{landing => shop}/models/customer.ts | 0 .../modules/{landing => shop}/models/index.ts | 0 .../modules/{landing => shop}/models/io.d.ts | 0 .../{landing => shop}/models/payload.ts | 0 .../{landing => shop}/models/payment.ts | 0 .../models/posPaymentResult.ts | 0 .../modules/{landing => shop}/models/types.ts | 0 .../services/favorite.service.ts | 0 .../services/main.service.ts | 0 .../services/payment-bridge.abstract.ts | 0 .../services/payment-bridge.service.ts | 0 .../{landing => shop}/store/main.store.ts | 0 .../modules/{landing => shop}/views/index.ts | 0 .../views/root.component.html | 0 .../{landing => shop}/views/root.component.ts | 4 +- .../modules/statistics/components/index.ts | 1 + .../invoice-type-card.component.html | 33 +++++ .../components/invoice-type-card.component.ts | 77 ++++++++++++ .../statistics/constants/apiRoutes/index.ts | 5 + .../pos/modules/statistics/constants/index.ts | 2 + .../statistics/constants/routes/index.ts | 21 ++++ .../pos/modules/statistics/models/index.ts | 1 + .../pos/modules/statistics/models/io.d.ts | 15 +++ .../statistics/services/main.service.ts | 18 +++ .../modules/statistics/store/main.store.ts | 50 ++++++++ .../pos/modules/statistics/views/index.ts | 1 + .../statistics/views/root.component.html | 57 +++++++++ .../statistics/views/root.component.ts | 76 ++++++++++++ src/app/domains/pos/routes.ts | 9 +- src/app/domains/pos/store/me.store.ts | 4 +- src/app/domains/pos/store/pos.store.ts | 4 +- .../modules/saleInvoices/store/main.store.ts | 2 - .../season-picker-dialog.component.html | 33 +++++ .../season-picker-dialog.component.ts | 53 ++++++++ .../seasonPicker/season-picker.component.html | 18 +++ .../seasonPicker/season-picker.component.ts | 115 ++++++++++++++++++ .../datepicker/datepicker.component.html | 23 +--- .../uikit/datepicker/datepicker.component.ts | 30 ++++- src/app/utils/date-formatter.utils.ts | 26 ++-- src/assets/styles.scss | 2 +- src/main.ts | 30 +++-- 100 files changed, 768 insertions(+), 231 deletions(-) delete mode 100644 src/app/domains/pos/modules/landing/components/order/held-order-item.component.html delete mode 100644 src/app/domains/pos/modules/landing/components/order/held-order-item.component.ts delete mode 100644 src/app/domains/pos/modules/landing/components/order/held-orders.component.html delete mode 100644 src/app/domains/pos/modules/landing/components/order/held-orders.component.ts rename src/app/domains/pos/modules/{landing => shop}/components/categories.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/categories.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/customer-dialog.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/customer-dialog.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/individual/form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/individual/form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/legal/form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/legal/form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/unknown/form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/customers/unknown/form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/favorite-CTA.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/favorite-CTA.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/goods.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/goods.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/grid-view.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/grid-view.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/list-view.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/list-view.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-card-template.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-card-template.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-section.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-section.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-submitted-dialog.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/order-submitted-dialog.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/price-info-card.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/order/price-info-card.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payload-form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payload-form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/form-dialog-amount-card-template.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/form-dialog-amount-card-template.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/gold/form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/gold/form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/gold/order-card.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/gold/order-card.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/index.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/standard/form.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/standard/form.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/standard/order-card.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payloads/standard/order-card.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payment/form-dialog.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/components/payment/form-dialog.component.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/constants/apiRoutes/favorite.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/constants/apiRoutes/index.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/constants/index.ts (100%) create mode 100644 src/app/domains/pos/modules/shop/constants/routes/index.ts rename src/app/domains/pos/modules/{landing => shop}/models/customer.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/index.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/io.d.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/payload.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/payment.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/posPaymentResult.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/models/types.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/services/favorite.service.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/services/main.service.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/services/payment-bridge.abstract.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/services/payment-bridge.service.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/store/main.store.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/views/index.ts (100%) rename src/app/domains/pos/modules/{landing => shop}/views/root.component.html (100%) rename src/app/domains/pos/modules/{landing => shop}/views/root.component.ts (96%) create mode 100644 src/app/domains/pos/modules/statistics/components/index.ts create mode 100644 src/app/domains/pos/modules/statistics/components/invoice-type-card.component.html create mode 100644 src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts create mode 100644 src/app/domains/pos/modules/statistics/constants/apiRoutes/index.ts create mode 100644 src/app/domains/pos/modules/statistics/constants/index.ts create mode 100644 src/app/domains/pos/modules/statistics/constants/routes/index.ts create mode 100644 src/app/domains/pos/modules/statistics/models/index.ts create mode 100644 src/app/domains/pos/modules/statistics/models/io.d.ts create mode 100644 src/app/domains/pos/modules/statistics/services/main.service.ts create mode 100644 src/app/domains/pos/modules/statistics/store/main.store.ts create mode 100644 src/app/domains/pos/modules/statistics/views/index.ts create mode 100644 src/app/domains/pos/modules/statistics/views/root.component.html create mode 100644 src/app/domains/pos/modules/statistics/views/root.component.ts create mode 100644 src/app/shared/components/seasonPicker/season-picker-dialog.component.html create mode 100644 src/app/shared/components/seasonPicker/season-picker-dialog.component.ts create mode 100644 src/app/shared/components/seasonPicker/season-picker.component.html create mode 100644 src/app/shared/components/seasonPicker/season-picker.component.ts diff --git a/angular.json b/angular.json index 431e41c..7747279 100644 --- a/angular.json +++ b/angular.json @@ -209,6 +209,8 @@ ".webp": "file" }, "styles": [ + "node_modules/flatpickr-wrap/dist/flatpickr.css", + "node_modules/flatpickr-wrap/dist/themes/confetti.css", "src/assets/styles.scss" ], "tsConfig": "tsconfig.app.json" @@ -250,6 +252,8 @@ "inlineStyleLanguage": "scss", "karmaConfig": "karma.conf.js", "styles": [ + "node_modules/flatpickr-wrap/dist/flatpickr.css", + "node_modules/flatpickr-wrap/dist/themes/confetti.css", "src/assets/styles.scss" ], "tsConfig": "tsconfig.spec.json" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f31cc6..486ace2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3682,7 +3682,7 @@ packages: hasBin: true esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==, tarball: https://hub.megan.ir/repository/npm/esbuild/-/esbuild-0.28.0.tgz} engines: {node: '>=18'} hasBin: true @@ -4122,7 +4122,7 @@ packages: engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, tarball: https://hub.megan.ir/repository/npm/graceful-fs/-/graceful-fs-4.2.11.tgz} handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} @@ -6058,7 +6058,7 @@ packages: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://hub.megan.ir/repository/npm/source-map/-/source-map-0.6.1.tgz} engines: {node: '>=0.10.0'} source-map@0.7.6: diff --git a/src/app/domains/consumer/modules/businessActivities/models/salesInvoices_io.d.ts b/src/app/domains/consumer/modules/businessActivities/models/salesInvoices_io.d.ts index 8b0ad14..96eea20 100644 --- a/src/app/domains/consumer/modules/businessActivities/models/salesInvoices_io.d.ts +++ b/src/app/domains/consumer/modules/businessActivities/models/salesInvoices_io.d.ts @@ -1,5 +1,5 @@ import ISummary from '@/core/models/summary'; -import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/landing/models'; +import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/shop/models'; export interface ISalesInvoicesRawResponse { id: string; diff --git a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts index 317c9d7..589dc18 100644 --- a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts +++ b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts @@ -5,11 +5,12 @@ import { RouterLink } from '@angular/router'; import { Button } from 'primeng/button'; import { Drawer } from 'primeng/drawer'; import { Ripple } from 'primeng/ripple'; -import config from 'src/config'; import { posAboutNamedRoutes } from '../../modules/about/constants'; import { posConfigNamedRoutes } from '../../modules/configs/constants'; import { PosGoodsManagementRoutes } from '../../modules/goodsManagement/constants'; import { posSaleInvoicesNamedRoutes } from '../../modules/saleInvoices/constants'; +import { PosShopNamedRoutes } from '../../modules/shop/constants/routes'; +import { StatisticsRoutes } from '../../modules/statistics/constants'; import { posSupportNamedRoutes } from '../../modules/support/constants'; import { PosInfoStore } from '../../store'; @@ -37,10 +38,15 @@ export class PosMainMenuSidebarComponent extends AbstractDialog { readonly menuItems = [ { - label: 'فروش', - routerLink: config.isPosApplication ? '/' : '/pos', + label: 'صفحه اصلی', + routerLink: StatisticsRoutes.statistics.meta.pagePath!(), icon: 'pi pi-fw pi-home', }, + { + label: 'فروش', + routerLink: PosShopNamedRoutes.shop.meta.pagePath!(), + icon: 'pi pi-fw pi-cart-arrow-down', + }, { label: 'فاکتورها', routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(), diff --git a/src/app/domains/pos/layouts/pagesLayout/choose-pos.component.ts b/src/app/domains/pos/layouts/pagesLayout/choose-pos.component.ts index 27e958a..6a23708 100644 --- a/src/app/domains/pos/layouts/pagesLayout/choose-pos.component.ts +++ b/src/app/domains/pos/layouts/pagesLayout/choose-pos.component.ts @@ -9,7 +9,7 @@ import { Message } from 'primeng/message'; import { catchError, finalize } from 'rxjs'; import { COOKIE_KEYS } from 'src/assets/constants'; import { IPosAccessibleResponse } from '../../models/pos.io'; -import { PosService } from '../../modules/landing/services/main.service'; +import { PosService } from '../../modules/shop/services/main.service'; @Component({ selector: 'pos-choose-cards', @@ -35,7 +35,7 @@ export class PosChooseCardsComponent { catchError((err) => { this.error.set(err); throw err; - }), + }) ) .subscribe((res) => { this.items.set(res?.data || []); diff --git a/src/app/domains/pos/modules/configs/views/root.component.html b/src/app/domains/pos/modules/configs/views/root.component.html index 764a239..c76cbc6 100644 --- a/src/app/domains/pos/modules/configs/views/root.component.html +++ b/src/app/domains/pos/modules/configs/views/root.component.html @@ -1,8 +1,8 @@ -
+
هریک از موارد زیر را که می‌خواهید در چاپ فاکتور نمایش داده‌ شوند را انتخاب کنید - +
diff --git a/src/app/domains/pos/modules/configs/views/root.component.ts b/src/app/domains/pos/modules/configs/views/root.component.ts index 7b29dc8..8f6cbc0 100644 --- a/src/app/domains/pos/modules/configs/views/root.component.ts +++ b/src/app/domains/pos/modules/configs/views/root.component.ts @@ -1,4 +1,5 @@ import { AppCardComponent } from '@/shared/components'; +import { UikitFlatpickrJalaliComponent } from '@/uikit'; import { Component, inject } from '@angular/core'; import { Router } from '@angular/router'; import { Message } from 'primeng/message'; @@ -7,7 +8,7 @@ import { PosConfigPrintFormComponent } from '../components/print/form.component' @Component({ selector: 'pos-config-page', templateUrl: './root.component.html', - imports: [PosConfigPrintFormComponent, AppCardComponent, Message], + imports: [PosConfigPrintFormComponent, AppCardComponent, Message, UikitFlatpickrJalaliComponent], }) export class PosConfigPageComponent { private readonly router = inject(Router); diff --git a/src/app/domains/pos/modules/landing/components/order/held-order-item.component.html b/src/app/domains/pos/modules/landing/components/order/held-order-item.component.html deleted file mode 100644 index d8ca28f..0000000 --- a/src/app/domains/pos/modules/landing/components/order/held-order-item.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
- سفارش #{{ heldOrder.orderNumber }} - - {{ heldOrder.orderItems.length }} کالا - - - -
-
- - -
-
diff --git a/src/app/domains/pos/modules/landing/components/order/held-order-item.component.ts b/src/app/domains/pos/modules/landing/components/order/held-order-item.component.ts deleted file mode 100644 index cbff732..0000000 --- a/src/app/domains/pos/modules/landing/components/order/held-order-item.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ToastService } from '@/core/services/toast.service'; -import { IPosHeldOrderResponse } from '@/modules/pos/models'; -import { ConfirmationDialogService } from '@/shared/components/confirmationDialog/confirmation-dialog.service'; -import { PriceMaskDirective } from '@/shared/directives'; -import { Component, EventEmitter, Input, Output, signal } from '@angular/core'; -import { ButtonDirective } from 'primeng/button'; -import { PosService } from '../../services/main.service'; - -@Component({ - selector: 'app-held-order-item', - templateUrl: './held-order-item.component.html', - imports: [PriceMaskDirective, ButtonDirective], -}) -export class HeldOrderItemComponent { - @Input() heldOrder!: IPosHeldOrderResponse; - @Input() posId!: number; - - @Output() onCancel = new EventEmitter(); - @Output() onLoad = new EventEmitter(); - - constructor( - private readonly posService: PosService, - private readonly confirmationService: ConfirmationDialogService, - private readonly toastService: ToastService, - ) {} - - actionLoading = signal(false); - - cancelHeldOrder(heldOrderId: number) { - this.confirmationService.confirm({ - message: `آیا از لغو سفارش شماره‌ی #${this.heldOrder.orderNumber} اطمینان دارید؟`, - header: 'لغو سفارش موقت', - acceptLabel: 'بله', - rejectLabel: 'خیر', - accept: () => { - this.actionLoading.set(true); - // this.posService.cancelOrder(this.posId, heldOrderId).subscribe({ - // next: () => { - // this.onCancel.emit(heldOrderId); - // this.actionLoading.set(false); - // this.toastService.success({ - // text: `سفارش شماره‌ی #${this.heldOrder.orderNumber} با موفقیت لغو شد.`, - // }); - // }, - // error: () => { - // this.actionLoading.set(false); - // }, - // }); - }, - reject: () => {}, - }); - } - - loadHeldOrder(heldOrderId: number) { - this.onLoad.emit(heldOrderId); - } -} diff --git a/src/app/domains/pos/modules/landing/components/order/held-orders.component.html b/src/app/domains/pos/modules/landing/components/order/held-orders.component.html deleted file mode 100644 index 22e5ca6..0000000 --- a/src/app/domains/pos/modules/landing/components/order/held-orders.component.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
-
- - سفارش‌های نگه‌داشته شده -
-
- @if (!heldOrders()?.length) { -
- هیچ سفارش نگه‌داشته شده‌ای وجود ندارد. -
- } @else { -
- @for (heldOrder of heldOrders(); track heldOrder.id) { - - } -
- } -
diff --git a/src/app/domains/pos/modules/landing/components/order/held-orders.component.ts b/src/app/domains/pos/modules/landing/components/order/held-orders.component.ts deleted file mode 100644 index c09246a..0000000 --- a/src/app/domains/pos/modules/landing/components/order/held-orders.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Maybe } from '@/core'; -import { IPosHeldOrderResponse } from '@/modules/pos/models'; -import { Component, inject, signal } from '@angular/core'; -import { PosService } from '../../services/main.service'; -import { HeldOrderItemComponent } from './held-order-item.component'; - -@Component({ - selector: 'app-held-orders', - templateUrl: './held-orders.component.html', - imports: [HeldOrderItemComponent], -}) -export class HeldOrdersComponent { - private readonly service = inject(PosService); - - heldOrders = signal>(null); - loading = signal(false); - - ngOnInit() { - this.getHeldOrders(); - } - - getHeldOrders() { - this.loading.set(true); - // this.service.getHeldOrders().subscribe({ - // next: (res) => { - // this.heldOrders.set(res.data); - // this.loading.set(false); - // }, - // error: () => { - // this.loading.set(false); - // }, - // }); - } - - removeHeldOrder(orderId: number) { - const currentOrders = this.heldOrders() || []; - this.heldOrders.set(currentOrders.filter((order) => order.id !== orderId)); - } -} diff --git a/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts index aa1ec03..d99ed05 100644 --- a/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts +++ b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts @@ -50,7 +50,7 @@ export class PosSaleInvoicesFilterDrawerComponent implements OnChanges { customer_mobile: this.fb.control(''), customer_national_id: this.fb.control(''), customer_economic_code: this.fb.control(''), - status: this.fb.control(null), + status: this.fb.control<'NOT_SEND' | 'SUCCESS' | 'FAILURE' | 'QUEUED' | null>(null), total_amount_from: this.fb.control(null), total_amount_to: this.fb.control(null), }); @@ -74,7 +74,7 @@ export class PosSaleInvoicesFilterDrawerComponent implements OnChanges { total_amount_from: this.value.total_amount_from ?? null, total_amount_to: this.value.total_amount_to ?? null, }, - { emitEvent: false }, + { emitEvent: false } ); } } diff --git a/src/app/domains/pos/modules/saleInvoices/components/list.component.ts b/src/app/domains/pos/modules/saleInvoices/components/list.component.ts index e953ad2..4fdeeef 100644 --- a/src/app/domains/pos/modules/saleInvoices/components/list.component.ts +++ b/src/app/domains/pos/modules/saleInvoices/components/list.component.ts @@ -1,7 +1,8 @@ import { POS_ROUTES } from '@/domains/pos/routes'; import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; +import { gregorianToJalali } from '@/utils'; import { Component, computed, inject, signal } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Button, ButtonDirective } from 'primeng/button'; import { Chip } from 'primeng/chip'; import { Skeleton } from 'primeng/skeleton'; @@ -32,6 +33,7 @@ import { SaleInvoiceCardComponent } from './sale-invoice-card.component'; export class PosSaleInvoiceListComponent { private readonly service = inject(PosSaleInvoicesService); private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); loading = signal(true); items = signal([]); @@ -57,16 +59,28 @@ export class PosSaleInvoiceListComponent { .filter(([, value]) => value !== undefined && value !== null && value !== '') .map(([key, value]) => { const typedKey = key as keyof IPosSaleInvoicesFilterDto; - const formattedValue = - typedKey === 'status' && value === 'NOT_SEND' - ? 'ارسال نشده' - : typedKey === 'status' && value === 'SUCCESS' - ? 'موفق' - : typedKey === 'status' && value === 'FAILURE' - ? 'ناموفق' - : typedKey === 'status' && value === 'QUEUED' - ? 'در انتظار ارسال' - : String(value); + let formattedValue = ''; + if (typedKey === 'status') { + formattedValue = + value === 'NOT_SEND' + ? 'ارسال نشده' + : value === 'SUCCESS' + ? 'موفق' + : value === 'FAILURE' + ? 'ناموفق' + : value === 'QUEUED' + ? 'در انتظار ارسال' + : String(value); + } else if ( + key === 'invoice_date_from' || + key === 'invoice_date_to' || + key === 'created_at_from' || + key === 'created_at_to' + ) { + formattedValue = gregorianToJalali(value); + } else { + formattedValue = String(value); + } return { key: typedKey, label: labels[typedKey] ?? typedKey, value: formattedValue }; }); }); @@ -74,6 +88,7 @@ export class PosSaleInvoiceListComponent { backRoute = POS_ROUTES.path || '/'; ngOnInit() { + this.hydrateFiltersFromQueryParams(); this.getData(); } @@ -96,6 +111,7 @@ export class PosSaleInvoiceListComponent { onFilterApply(query: IPosSaleInvoicesFilterDto) { this.filters.set(query); + this.syncFiltersToQueryParams(); this.getData(); } @@ -103,10 +119,57 @@ export class PosSaleInvoiceListComponent { const next = { ...this.filters() }; delete next[key]; this.filters.set(next); + this.syncFiltersToQueryParams(); this.getData(); } toSinglePage(item: IPosSaleInvoicesResponse) { this.router.navigateByUrl(posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id)); } + + private hydrateFiltersFromQueryParams() { + const queryParams = this.route.snapshot.queryParams; + const keys: (keyof IPosSaleInvoicesFilterDto)[] = [ + 'invoice_date_from', + 'invoice_date_to', + 'created_at_from', + 'created_at_to', + 'code', + 'customer_name', + 'customer_mobile', + 'customer_national_id', + 'customer_economic_code', + 'status', + 'total_amount_from', + 'total_amount_to', + ]; + + const parsed = keys.reduce((acc, key) => { + const value = queryParams[key]; + if (value !== undefined && value !== null && value !== '') { + acc[key] = value; + } + return acc; + }, {}); + + this.filters.set(parsed); + } + + private syncFiltersToQueryParams() { + const queryParams = Object.entries(this.filters()).reduce>( + (acc, [key, value]) => { + if (value !== undefined && value !== null && value !== '') { + acc[key] = String(value); + } + return acc; + }, + {} + ); + + this.router.navigate([], { + relativeTo: this.route, + queryParams, + replaceUrl: true, + }); + } } diff --git a/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts b/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts index 245e791..738262a 100644 --- a/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts +++ b/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts @@ -10,7 +10,7 @@ export interface IPosSaleInvoicesFilterDto { customer_mobile?: string; customer_national_id?: string; customer_economic_code?: string; - status?: string; + status?: 'NOT_SEND' | 'SUCCESS' | 'FAILURE' | 'QUEUED'; total_amount?: number | null; total_amount_from?: number | null; total_amount_to?: number | null; diff --git a/src/app/domains/pos/modules/landing/components/categories.component.html b/src/app/domains/pos/modules/shop/components/categories.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/categories.component.html rename to src/app/domains/pos/modules/shop/components/categories.component.html diff --git a/src/app/domains/pos/modules/landing/components/categories.component.ts b/src/app/domains/pos/modules/shop/components/categories.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/categories.component.ts rename to src/app/domains/pos/modules/shop/components/categories.component.ts diff --git a/src/app/domains/pos/modules/landing/components/customers/customer-dialog.component.html b/src/app/domains/pos/modules/shop/components/customers/customer-dialog.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/customer-dialog.component.html rename to src/app/domains/pos/modules/shop/components/customers/customer-dialog.component.html diff --git a/src/app/domains/pos/modules/landing/components/customers/customer-dialog.component.ts b/src/app/domains/pos/modules/shop/components/customers/customer-dialog.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/customer-dialog.component.ts rename to src/app/domains/pos/modules/shop/components/customers/customer-dialog.component.ts diff --git a/src/app/domains/pos/modules/landing/components/customers/individual/form.component.html b/src/app/domains/pos/modules/shop/components/customers/individual/form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/individual/form.component.html rename to src/app/domains/pos/modules/shop/components/customers/individual/form.component.html diff --git a/src/app/domains/pos/modules/landing/components/customers/individual/form.component.ts b/src/app/domains/pos/modules/shop/components/customers/individual/form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/individual/form.component.ts rename to src/app/domains/pos/modules/shop/components/customers/individual/form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/customers/legal/form.component.html b/src/app/domains/pos/modules/shop/components/customers/legal/form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/legal/form.component.html rename to src/app/domains/pos/modules/shop/components/customers/legal/form.component.html diff --git a/src/app/domains/pos/modules/landing/components/customers/legal/form.component.ts b/src/app/domains/pos/modules/shop/components/customers/legal/form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/legal/form.component.ts rename to src/app/domains/pos/modules/shop/components/customers/legal/form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/customers/unknown/form.component.html b/src/app/domains/pos/modules/shop/components/customers/unknown/form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/unknown/form.component.html rename to src/app/domains/pos/modules/shop/components/customers/unknown/form.component.html diff --git a/src/app/domains/pos/modules/landing/components/customers/unknown/form.component.ts b/src/app/domains/pos/modules/shop/components/customers/unknown/form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/customers/unknown/form.component.ts rename to src/app/domains/pos/modules/shop/components/customers/unknown/form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/favorite-CTA.component.html b/src/app/domains/pos/modules/shop/components/favorite-CTA.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/favorite-CTA.component.html rename to src/app/domains/pos/modules/shop/components/favorite-CTA.component.html diff --git a/src/app/domains/pos/modules/landing/components/favorite-CTA.component.ts b/src/app/domains/pos/modules/shop/components/favorite-CTA.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/favorite-CTA.component.ts rename to src/app/domains/pos/modules/shop/components/favorite-CTA.component.ts diff --git a/src/app/domains/pos/modules/landing/components/goods.component.html b/src/app/domains/pos/modules/shop/components/goods.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/goods.component.html rename to src/app/domains/pos/modules/shop/components/goods.component.html diff --git a/src/app/domains/pos/modules/landing/components/goods.component.ts b/src/app/domains/pos/modules/shop/components/goods.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/goods.component.ts rename to src/app/domains/pos/modules/shop/components/goods.component.ts diff --git a/src/app/domains/pos/modules/landing/components/grid-view.component.html b/src/app/domains/pos/modules/shop/components/grid-view.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/grid-view.component.html rename to src/app/domains/pos/modules/shop/components/grid-view.component.html diff --git a/src/app/domains/pos/modules/landing/components/grid-view.component.ts b/src/app/domains/pos/modules/shop/components/grid-view.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/grid-view.component.ts rename to src/app/domains/pos/modules/shop/components/grid-view.component.ts diff --git a/src/app/domains/pos/modules/landing/components/list-view.component.html b/src/app/domains/pos/modules/shop/components/list-view.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/list-view.component.html rename to src/app/domains/pos/modules/shop/components/list-view.component.html diff --git a/src/app/domains/pos/modules/landing/components/list-view.component.ts b/src/app/domains/pos/modules/shop/components/list-view.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/list-view.component.ts rename to src/app/domains/pos/modules/shop/components/list-view.component.ts diff --git a/src/app/domains/pos/modules/landing/components/order/order-card-template.component.html b/src/app/domains/pos/modules/shop/components/order/order-card-template.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-card-template.component.html rename to src/app/domains/pos/modules/shop/components/order/order-card-template.component.html diff --git a/src/app/domains/pos/modules/landing/components/order/order-card-template.component.ts b/src/app/domains/pos/modules/shop/components/order/order-card-template.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-card-template.component.ts rename to src/app/domains/pos/modules/shop/components/order/order-card-template.component.ts diff --git a/src/app/domains/pos/modules/landing/components/order/order-section.component.html b/src/app/domains/pos/modules/shop/components/order/order-section.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-section.component.html rename to src/app/domains/pos/modules/shop/components/order/order-section.component.html diff --git a/src/app/domains/pos/modules/landing/components/order/order-section.component.ts b/src/app/domains/pos/modules/shop/components/order/order-section.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-section.component.ts rename to src/app/domains/pos/modules/shop/components/order/order-section.component.ts diff --git a/src/app/domains/pos/modules/landing/components/order/order-submitted-dialog.component.html b/src/app/domains/pos/modules/shop/components/order/order-submitted-dialog.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-submitted-dialog.component.html rename to src/app/domains/pos/modules/shop/components/order/order-submitted-dialog.component.html diff --git a/src/app/domains/pos/modules/landing/components/order/order-submitted-dialog.component.ts b/src/app/domains/pos/modules/shop/components/order/order-submitted-dialog.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/order-submitted-dialog.component.ts rename to src/app/domains/pos/modules/shop/components/order/order-submitted-dialog.component.ts diff --git a/src/app/domains/pos/modules/landing/components/order/price-info-card.component.html b/src/app/domains/pos/modules/shop/components/order/price-info-card.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/price-info-card.component.html rename to src/app/domains/pos/modules/shop/components/order/price-info-card.component.html diff --git a/src/app/domains/pos/modules/landing/components/order/price-info-card.component.ts b/src/app/domains/pos/modules/shop/components/order/price-info-card.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/order/price-info-card.component.ts rename to src/app/domains/pos/modules/shop/components/order/price-info-card.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payload-form.component.html b/src/app/domains/pos/modules/shop/components/payload-form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payload-form.component.html rename to src/app/domains/pos/modules/shop/components/payload-form.component.html diff --git a/src/app/domains/pos/modules/landing/components/payload-form.component.ts b/src/app/domains/pos/modules/shop/components/payload-form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payload-form.component.ts rename to src/app/domains/pos/modules/shop/components/payload-form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/form-dialog-amount-card-template.component.html b/src/app/domains/pos/modules/shop/components/payloads/form-dialog-amount-card-template.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/form-dialog-amount-card-template.component.html rename to src/app/domains/pos/modules/shop/components/payloads/form-dialog-amount-card-template.component.html diff --git a/src/app/domains/pos/modules/landing/components/payloads/form-dialog-amount-card-template.component.ts b/src/app/domains/pos/modules/shop/components/payloads/form-dialog-amount-card-template.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/form-dialog-amount-card-template.component.ts rename to src/app/domains/pos/modules/shop/components/payloads/form-dialog-amount-card-template.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html b/src/app/domains/pos/modules/shop/components/payloads/gold/form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html rename to src/app/domains/pos/modules/shop/components/payloads/gold/form.component.html diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts b/src/app/domains/pos/modules/shop/components/payloads/gold/form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts rename to src/app/domains/pos/modules/shop/components/payloads/gold/form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/order-card.component.html b/src/app/domains/pos/modules/shop/components/payloads/gold/order-card.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/gold/order-card.component.html rename to src/app/domains/pos/modules/shop/components/payloads/gold/order-card.component.html diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/order-card.component.ts b/src/app/domains/pos/modules/shop/components/payloads/gold/order-card.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/gold/order-card.component.ts rename to src/app/domains/pos/modules/shop/components/payloads/gold/order-card.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/index.ts b/src/app/domains/pos/modules/shop/components/payloads/index.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/index.ts rename to src/app/domains/pos/modules/shop/components/payloads/index.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/standard/form.component.html b/src/app/domains/pos/modules/shop/components/payloads/standard/form.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/standard/form.component.html rename to src/app/domains/pos/modules/shop/components/payloads/standard/form.component.html diff --git a/src/app/domains/pos/modules/landing/components/payloads/standard/form.component.ts b/src/app/domains/pos/modules/shop/components/payloads/standard/form.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/standard/form.component.ts rename to src/app/domains/pos/modules/shop/components/payloads/standard/form.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payloads/standard/order-card.component.html b/src/app/domains/pos/modules/shop/components/payloads/standard/order-card.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/standard/order-card.component.html rename to src/app/domains/pos/modules/shop/components/payloads/standard/order-card.component.html diff --git a/src/app/domains/pos/modules/landing/components/payloads/standard/order-card.component.ts b/src/app/domains/pos/modules/shop/components/payloads/standard/order-card.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payloads/standard/order-card.component.ts rename to src/app/domains/pos/modules/shop/components/payloads/standard/order-card.component.ts diff --git a/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.html b/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/components/payment/form-dialog.component.html rename to src/app/domains/pos/modules/shop/components/payment/form-dialog.component.html diff --git a/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts b/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.ts similarity index 100% rename from src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts rename to src/app/domains/pos/modules/shop/components/payment/form-dialog.component.ts diff --git a/src/app/domains/pos/modules/landing/constants/apiRoutes/favorite.ts b/src/app/domains/pos/modules/shop/constants/apiRoutes/favorite.ts similarity index 100% rename from src/app/domains/pos/modules/landing/constants/apiRoutes/favorite.ts rename to src/app/domains/pos/modules/shop/constants/apiRoutes/favorite.ts diff --git a/src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts b/src/app/domains/pos/modules/shop/constants/apiRoutes/index.ts similarity index 100% rename from src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts rename to src/app/domains/pos/modules/shop/constants/apiRoutes/index.ts diff --git a/src/app/domains/pos/modules/landing/constants/index.ts b/src/app/domains/pos/modules/shop/constants/index.ts similarity index 100% rename from src/app/domains/pos/modules/landing/constants/index.ts rename to src/app/domains/pos/modules/shop/constants/index.ts diff --git a/src/app/domains/pos/modules/shop/constants/routes/index.ts b/src/app/domains/pos/modules/shop/constants/routes/index.ts new file mode 100644 index 0000000..bb07aea --- /dev/null +++ b/src/app/domains/pos/modules/shop/constants/routes/index.ts @@ -0,0 +1,20 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; +import config from 'src/config'; + +export type TShopRouteNames = 'shop'; + +const baseRoute = `${config.isPosApplication ? '' : '/pos'}/shop`; + +export const PosShopNamedRoutes: NamedRoutes = { + shop: { + path: 'shop', + loadComponent: () => import('../../views/root.component').then((m) => m.PosShopComponent), + meta: { + title: 'فروشگاه', + pagePath: () => baseRoute, + }, + }, +}; + +export const POS_SHOP_ROUTES: Routes = [PosShopNamedRoutes.shop]; diff --git a/src/app/domains/pos/modules/landing/models/customer.ts b/src/app/domains/pos/modules/shop/models/customer.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/customer.ts rename to src/app/domains/pos/modules/shop/models/customer.ts diff --git a/src/app/domains/pos/modules/landing/models/index.ts b/src/app/domains/pos/modules/shop/models/index.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/index.ts rename to src/app/domains/pos/modules/shop/models/index.ts diff --git a/src/app/domains/pos/modules/landing/models/io.d.ts b/src/app/domains/pos/modules/shop/models/io.d.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/io.d.ts rename to src/app/domains/pos/modules/shop/models/io.d.ts diff --git a/src/app/domains/pos/modules/landing/models/payload.ts b/src/app/domains/pos/modules/shop/models/payload.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/payload.ts rename to src/app/domains/pos/modules/shop/models/payload.ts diff --git a/src/app/domains/pos/modules/landing/models/payment.ts b/src/app/domains/pos/modules/shop/models/payment.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/payment.ts rename to src/app/domains/pos/modules/shop/models/payment.ts diff --git a/src/app/domains/pos/modules/landing/models/posPaymentResult.ts b/src/app/domains/pos/modules/shop/models/posPaymentResult.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/posPaymentResult.ts rename to src/app/domains/pos/modules/shop/models/posPaymentResult.ts diff --git a/src/app/domains/pos/modules/landing/models/types.ts b/src/app/domains/pos/modules/shop/models/types.ts similarity index 100% rename from src/app/domains/pos/modules/landing/models/types.ts rename to src/app/domains/pos/modules/shop/models/types.ts diff --git a/src/app/domains/pos/modules/landing/services/favorite.service.ts b/src/app/domains/pos/modules/shop/services/favorite.service.ts similarity index 100% rename from src/app/domains/pos/modules/landing/services/favorite.service.ts rename to src/app/domains/pos/modules/shop/services/favorite.service.ts diff --git a/src/app/domains/pos/modules/landing/services/main.service.ts b/src/app/domains/pos/modules/shop/services/main.service.ts similarity index 100% rename from src/app/domains/pos/modules/landing/services/main.service.ts rename to src/app/domains/pos/modules/shop/services/main.service.ts diff --git a/src/app/domains/pos/modules/landing/services/payment-bridge.abstract.ts b/src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts similarity index 100% rename from src/app/domains/pos/modules/landing/services/payment-bridge.abstract.ts rename to src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts diff --git a/src/app/domains/pos/modules/landing/services/payment-bridge.service.ts b/src/app/domains/pos/modules/shop/services/payment-bridge.service.ts similarity index 100% rename from src/app/domains/pos/modules/landing/services/payment-bridge.service.ts rename to src/app/domains/pos/modules/shop/services/payment-bridge.service.ts diff --git a/src/app/domains/pos/modules/landing/store/main.store.ts b/src/app/domains/pos/modules/shop/store/main.store.ts similarity index 100% rename from src/app/domains/pos/modules/landing/store/main.store.ts rename to src/app/domains/pos/modules/shop/store/main.store.ts diff --git a/src/app/domains/pos/modules/landing/views/index.ts b/src/app/domains/pos/modules/shop/views/index.ts similarity index 100% rename from src/app/domains/pos/modules/landing/views/index.ts rename to src/app/domains/pos/modules/shop/views/index.ts diff --git a/src/app/domains/pos/modules/landing/views/root.component.html b/src/app/domains/pos/modules/shop/views/root.component.html similarity index 100% rename from src/app/domains/pos/modules/landing/views/root.component.html rename to src/app/domains/pos/modules/shop/views/root.component.html diff --git a/src/app/domains/pos/modules/landing/views/root.component.ts b/src/app/domains/pos/modules/shop/views/root.component.ts similarity index 96% rename from src/app/domains/pos/modules/landing/views/root.component.ts rename to src/app/domains/pos/modules/shop/views/root.component.ts index 5f6f248..a264040 100644 --- a/src/app/domains/pos/modules/landing/views/root.component.ts +++ b/src/app/domains/pos/modules/shop/views/root.component.ts @@ -14,7 +14,7 @@ import { IPosOrderResponse } from '../models'; import { PosLandingStore } from '../store/main.store'; @Component({ - selector: 'pos-landing', + selector: 'pos-shop', templateUrl: './root.component.html', host: { class: 'grow overflow-hidden' }, changeDetection: ChangeDetectionStrategy.OnPush, @@ -29,7 +29,7 @@ import { PosLandingStore } from '../store/main.store'; PosOrderSubmittedDialogComponent, ], }) -export class PosLandingComponent extends AbstractIsMobileComponent { +export class PosShopComponent extends AbstractIsMobileComponent { private readonly infoStore = inject(PosInfoStore); private readonly landingStore = inject(PosLandingStore); diff --git a/src/app/domains/pos/modules/statistics/components/index.ts b/src/app/domains/pos/modules/statistics/components/index.ts new file mode 100644 index 0000000..65ce7f7 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/components/index.ts @@ -0,0 +1 @@ +export * from './invoice-type-card.component'; diff --git a/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.html b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.html new file mode 100644 index 0000000..1fed8b9 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.html @@ -0,0 +1,33 @@ +
+
+
+
+ + + + {{ preparedInfo().title }} +
+ + {{ count || 0 }} عدد + +
+
+
+
مبلغ کل:
+
{{ formattedTotalAmount }}
+
+
+
مالیات کل:
+
{{ formattedTotalTax }}
+
+
+
diff --git a/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts new file mode 100644 index 0000000..5a5cfee --- /dev/null +++ b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts @@ -0,0 +1,77 @@ +import { formatWithCurrency } from '@/utils'; +import { CommonModule } from '@angular/common'; +import { Component, computed, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'pos-statistics-invoice-type-card', + templateUrl: 'invoice-type-card.component.html', + imports: [CommonModule], +}) +export class PosStatisticsInvoiceTypeCardComponent { + @Input() type: 'all' | 'success' | 'failure' | 'notSended' | 'pending' | 'credit' = 'all'; + @Input() count?: number = 0; + @Input() totalAmount?: number = 0; + @Input() totalTaxAmount?: number = 0; + @Input() loading = false; + @Input() invoicesRoute = ''; + + @Output() onClick = new EventEmitter(); + + get formattedTotalAmount() { + return formatWithCurrency(this.totalAmount || 0); + } + get formattedTotalTax() { + return formatWithCurrency(this.totalTaxAmount || 0); + } + + readonly preparedInfo = computed(() => { + switch (this.type) { + case 'all': + return { + title: 'همه', + icon: 'close', + bgColor: '#6B72801A', + borderColor: '#6B7280', + }; + case 'success': + return { + title: 'تایید شده', + icon: 'check', + bgColor: '#22C55E1A', + borderColor: '#22C55E', + }; + case 'failure': + return { + title: 'خطادار', + icon: 'times', + bgColor: '#EF44441A', + borderColor: '#EF4444', + }; + case 'notSended': + return { + title: 'ارسال نشده', + icon: 'clock', + bgColor: '#F59E0B1A', + borderColor: '#F59E0B', + }; + case 'pending': + return { + title: 'انتظار ارسال', + icon: 'clock', + bgColor: '#3B82F61A', + borderColor: '#3B82F6', + }; + case 'credit': + return { + title: 'اعتبار', + icon: 'credit-card', + bgColor: '#8B5CF61A', + borderColor: '#8B5CF6', + }; + } + }); + + showDetails() { + this.onClick.emit(); + } +} diff --git a/src/app/domains/pos/modules/statistics/constants/apiRoutes/index.ts b/src/app/domains/pos/modules/statistics/constants/apiRoutes/index.ts new file mode 100644 index 0000000..6353334 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/constants/apiRoutes/index.ts @@ -0,0 +1,5 @@ +const baseUrl = () => `/api/v1/pos/statistics`; + +export const POS_STATISTICS_API_ROUTES = { + getAll: () => `${baseUrl()}/sale-invoices`, +}; diff --git a/src/app/domains/pos/modules/statistics/constants/index.ts b/src/app/domains/pos/modules/statistics/constants/index.ts new file mode 100644 index 0000000..8d8c878 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes/index'; +export * from './routes/index'; diff --git a/src/app/domains/pos/modules/statistics/constants/routes/index.ts b/src/app/domains/pos/modules/statistics/constants/routes/index.ts new file mode 100644 index 0000000..3d2e61b --- /dev/null +++ b/src/app/domains/pos/modules/statistics/constants/routes/index.ts @@ -0,0 +1,21 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; +import config from 'src/config'; + +export type TStatisticsRouteNames = 'statistics'; + +const baseRoute = `${config.isPosApplication ? '' : '/pos'}`; + +export const StatisticsRoutes: NamedRoutes = { + statistics: { + path: '', + loadComponent: () => + import('../../views/root.component').then((m) => m.PosStatisticsRootComponent), + meta: { + title: 'داشبودر', + pagePath: () => baseRoute, + }, + }, +}; + +export const POS_STATISTICS_ROUTES: Routes = [StatisticsRoutes.statistics]; diff --git a/src/app/domains/pos/modules/statistics/models/index.ts b/src/app/domains/pos/modules/statistics/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/pos/modules/statistics/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/domains/pos/modules/statistics/models/io.d.ts b/src/app/domains/pos/modules/statistics/models/io.d.ts new file mode 100644 index 0000000..c7ba6e1 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/models/io.d.ts @@ -0,0 +1,15 @@ +export interface IPosStatisticsRawResponse { + all: IPosStatisticsItem; + success: IPosStatisticsItem; + failure: IPosStatisticsItem; + pending: IPosStatisticsItem; + notSended: IPosStatisticsItem; + credit: IPosStatisticsItem; +} +export interface IPosStatisticsResponse extends IPosStatisticsRawResponse {} + +interface IPosStatisticsItem { + count: number; + total_amount: number; + total_tax: number; +} diff --git a/src/app/domains/pos/modules/statistics/services/main.service.ts b/src/app/domains/pos/modules/statistics/services/main.service.ts new file mode 100644 index 0000000..6902cc8 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/services/main.service.ts @@ -0,0 +1,18 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { POS_STATISTICS_API_ROUTES } from '../constants'; +import { IPosStatisticsRawResponse, IPosStatisticsResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class PosStatisticsService { + constructor(private http: HttpClient) {} + + private apiRoutes = POS_STATISTICS_API_ROUTES; + + getAll(fromDate?: Date): Observable { + return this.http.get(this.apiRoutes.getAll(), { + params: { from_date: fromDate?.toISOString() || '' }, + }); + } +} diff --git a/src/app/domains/pos/modules/statistics/store/main.store.ts b/src/app/domains/pos/modules/statistics/store/main.store.ts new file mode 100644 index 0000000..2cc962b --- /dev/null +++ b/src/app/domains/pos/modules/statistics/store/main.store.ts @@ -0,0 +1,50 @@ +import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state'; +import { inject, Injectable } from '@angular/core'; +import { catchError, finalize } from 'rxjs'; +import { IPosStatisticsResponse } from '../models'; +import { PosStatisticsService } from '../services/main.service'; + +interface PosStatisticsState extends EntityState { + lastFromDate: Date; +} + +@Injectable({ + providedIn: 'root', +}) +export class PosStatisticsStore extends EntityStore { + private readonly service = inject(PosStatisticsService); + constructor() { + super({ + ...defaultBaseStateData, + lastFromDate: new Date(), + }); + } + + getData(fromDate?: Date) { + if (fromDate && fromDate.getTime() === this._state().lastFromDate.getTime()) { + return; + } + this.patchState({ loading: true, lastFromDate: fromDate || new Date() }); + this.service + .getAll(fromDate) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError((error) => { + this.setError(error); + throw error; + }) + ) + .subscribe((entity) => { + this.patchState({ entity }); + }); + } + + override reset(): void { + this.setState({ + ...defaultBaseStateData, + lastFromDate: new Date(), + }); + } +} diff --git a/src/app/domains/pos/modules/statistics/views/index.ts b/src/app/domains/pos/modules/statistics/views/index.ts new file mode 100644 index 0000000..8ca0c48 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/views/index.ts @@ -0,0 +1 @@ +export * from './root.component'; diff --git a/src/app/domains/pos/modules/statistics/views/root.component.html b/src/app/domains/pos/modules/statistics/views/root.component.html new file mode 100644 index 0000000..d1b6228 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/views/root.component.html @@ -0,0 +1,57 @@ +
+ +
+ @if (loading()) { + @for (_ of [0, 1, 2, 3, 4, 5]; track $index) { +
+ +
+ } + } @else { + + + + + + + } +
+
+ +
+
diff --git a/src/app/domains/pos/modules/statistics/views/root.component.ts b/src/app/domains/pos/modules/statistics/views/root.component.ts new file mode 100644 index 0000000..d175593 --- /dev/null +++ b/src/app/domains/pos/modules/statistics/views/root.component.ts @@ -0,0 +1,76 @@ +import { SeasonPickerComponent } from '@/shared/components/seasonPicker/season-picker.component'; +import { formatGregorian, gregorianToJalali, parseJalali } from '@/utils'; +import { Component, computed, inject, signal } from '@angular/core'; +import { Router, RouterLink } from '@angular/router'; +import { ButtonDirective } from 'primeng/button'; +import { Skeleton } from 'primeng/skeleton'; +import { posSaleInvoicesNamedRoutes } from '../../saleInvoices/constants'; +import { IPosSaleInvoicesFilterDto } from '../../saleInvoices/models'; +import { PosShopNamedRoutes } from '../../shop/constants/routes'; +import { PosStatisticsInvoiceTypeCardComponent } from '../components'; +import { PosStatisticsStore } from '../store/main.store'; + +@Component({ + selector: 'pos-statistics-root', + templateUrl: './root.component.html', + imports: [ + PosStatisticsInvoiceTypeCardComponent, + Skeleton, + SeasonPickerComponent, + ButtonDirective, + RouterLink, + ], +}) +export class PosStatisticsRootComponent { + private readonly store = inject(PosStatisticsStore); + private readonly router = inject(Router); + + readonly item = computed(() => this.store.entity()); + readonly loading = computed(() => this.store.loading()); + readonly shopRoute = PosShopNamedRoutes.shop.meta.pagePath!(); + + selectedDate = signal(new Date()); + + ngOnInit() { + this.getData(this.selectedDate()); + } + + onDateChanged(date: Date) { + this.selectedDate.set(date); + this.getData(date); + } + + getData(date?: Date) { + this.store.getData(date); + } + + toInvoicesPage(type: string) { + const mainRoute = posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(); + const queryParams: IPosSaleInvoicesFilterDto = { + invoice_date_from: this.selectedDate().toISOString(), + invoice_date_to: new Date( + formatGregorian( + parseJalali(gregorianToJalali(this.selectedDate())).add(3, 'months').set('day', 1) + ) + ).toISOString(), + }; + + switch (type) { + case 'success': + queryParams.status = 'SUCCESS'; + break; + case 'failure': + queryParams.status = 'FAILURE'; + break; + case 'notSended': + queryParams.status = 'NOT_SEND'; + break; + case 'pending': + queryParams.status = 'QUEUED'; + break; + } + const queryString = new URLSearchParams(queryParams as Record).toString(); + + this.router.navigateByUrl(`${mainRoute}?${queryString}`); + } +} diff --git a/src/app/domains/pos/routes.ts b/src/app/domains/pos/routes.ts index e2ab395..494f735 100644 --- a/src/app/domains/pos/routes.ts +++ b/src/app/domains/pos/routes.ts @@ -3,21 +3,20 @@ import { POS_ABOUT_ROUTES } from './modules/about/constants'; import { POS_CONFIG_ROUTES } from './modules/configs/constants'; import { POS_GOODS_MANAGEMENT_ROUTES } from './modules/goodsManagement/constants'; import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants'; +import { POS_SHOP_ROUTES } from './modules/shop/constants/routes'; +import { POS_STATISTICS_ROUTES } from './modules/statistics/constants'; import { POS_SUPPORT_ROUTES } from './modules/support/constants'; export const POS_ROUTES = { path: '', loadComponent: () => import('@/layout/default/app.layout.component').then((m) => m.AppLayout), children: [ - { - path: '', - loadComponent: () => - import('./modules/landing/views/root.component').then((m) => m.PosLandingComponent), - }, + ...POS_SHOP_ROUTES, ...POS_SALE_INVOICES_ROUTES, ...POS_ABOUT_ROUTES, ...POS_SUPPORT_ROUTES, ...POS_CONFIG_ROUTES, ...POS_GOODS_MANAGEMENT_ROUTES, + ...POS_STATISTICS_ROUTES, ], } as Route; diff --git a/src/app/domains/pos/store/me.store.ts b/src/app/domains/pos/store/me.store.ts index a4bf285..a5c3377 100644 --- a/src/app/domains/pos/store/me.store.ts +++ b/src/app/domains/pos/store/me.store.ts @@ -3,7 +3,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { catchError, finalize, map } from 'rxjs'; import { IPosProfileResponse } from '../models'; -import { PosService } from '../modules/landing/services/main.service'; +import { PosService } from '../modules/shop/services/main.service'; interface PosProfileState extends EntityState {} @@ -37,7 +37,7 @@ export class PosProfileStore extends EntityStore { posId: string; @@ -44,7 +44,7 @@ export class PosInfoStore extends EntityStore { this.setEntity(entity); } return entity; - }), + }) ); } diff --git a/src/app/modules/saleInvoices/store/main.store.ts b/src/app/modules/saleInvoices/store/main.store.ts index 8e10dd6..1510af1 100644 --- a/src/app/modules/saleInvoices/store/main.store.ts +++ b/src/app/modules/saleInvoices/store/main.store.ts @@ -35,8 +35,6 @@ export class PublicSaleInvoiceStore extends EntityStore< }) ) .subscribe((entity) => { - console.log('entity', entity); - this.patchState({ entity }); }); } diff --git a/src/app/shared/components/seasonPicker/season-picker-dialog.component.html b/src/app/shared/components/seasonPicker/season-picker-dialog.component.html new file mode 100644 index 0000000..6c393cb --- /dev/null +++ b/src/app/shared/components/seasonPicker/season-picker-dialog.component.html @@ -0,0 +1,33 @@ + +
+ + +
+ +
+ @for (seasonItem of seasons; track seasonItem.key) { + + } + +
+ +
+ +
+
diff --git a/src/app/shared/components/seasonPicker/season-picker-dialog.component.ts b/src/app/shared/components/seasonPicker/season-picker-dialog.component.ts new file mode 100644 index 0000000..adcd788 --- /dev/null +++ b/src/app/shared/components/seasonPicker/season-picker-dialog.component.ts @@ -0,0 +1,53 @@ +import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; +import { CommonModule } from '@angular/common'; +import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ButtonDirective, ButtonSeverity } from 'primeng/button'; +import { SharedLightBottomsheetComponent } from '../dialog/light-bottomsheet.component'; +import { FormFooterActionsComponent } from '../formFooterActions/form-footer-actions.component'; + +type SeasonKey = 0 | 1 | 2 | 3; + +interface SeasonItem { + key: SeasonKey; + label: string; + severity: ButtonSeverity; +} + +@Component({ + selector: 'season-picker-dialog', + templateUrl: './season-picker-dialog.component.html', + imports: [ + CommonModule, + FormsModule, + SharedLightBottomsheetComponent, + FormFooterActionsComponent, + ButtonDirective, + ], +}) +export class SeasonPickerDialogComponent extends AbstractDialog { + @Input() years: { year: number; value: number }[] = []; + @Input() selectedYear!: number; + @Input() selectedSeason!: SeasonKey; + @Input() seasons: SeasonItem[] = []; + + @Output() submit = new EventEmitter<{ year: number; season: number }>(); + + selectedYearDraft = signal(this.selectedYear); + selectedSeasonDraft = signal(this.selectedSeason); + + isSeasonDisabled = computed(() => this.selectedYear >= this.selectedYearDraft()); + + onSubmit(): void { + this.submit.emit({ + year: this.selectedYearDraft(), + season: this.selectedSeasonDraft(), + }); + this.close(); + } + + ngOnChanges() { + this.selectedYearDraft.set(this.selectedYear); + this.selectedSeasonDraft.set(this.selectedSeason); + } +} diff --git a/src/app/shared/components/seasonPicker/season-picker.component.html b/src/app/shared/components/seasonPicker/season-picker.component.html new file mode 100644 index 0000000..81514b1 --- /dev/null +++ b/src/app/shared/components/seasonPicker/season-picker.component.html @@ -0,0 +1,18 @@ +
+ +
+ {{ currentSeason() }} +
+ +
+ +@if (isOpen()) { + +} diff --git a/src/app/shared/components/seasonPicker/season-picker.component.ts b/src/app/shared/components/seasonPicker/season-picker.component.ts new file mode 100644 index 0000000..bbab297 --- /dev/null +++ b/src/app/shared/components/seasonPicker/season-picker.component.ts @@ -0,0 +1,115 @@ +import { gregorianToJalali, JALALI_DATE_FORMATS, jalaliToGregorian, nowJalali } from '@/utils'; +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Input, OnInit, Output, signal } from '@angular/core'; +import { Button, ButtonSeverity } from 'primeng/button'; +import { SeasonPickerDialogComponent } from './season-picker-dialog.component'; + +type SeasonKey = 0 | 1 | 2 | 3; + +interface SeasonItem { + key: SeasonKey; + label: string; + severity: ButtonSeverity; +} + +@Component({ + selector: 'season-picker', + templateUrl: 'season-picker.component.html', + imports: [CommonModule, Button, SeasonPickerDialogComponent], +}) +export class SeasonPickerComponent implements OnInit { + @Input() minYear = 1390; + @Input() maxYear = Number(nowJalali(JALALI_DATE_FORMATS.YEAR)); + @Input() value!: Date; + + @Output() onChange = new EventEmitter(); + + readonly isOpen = signal(false); + readonly selectedYear = signal(Number(nowJalali(JALALI_DATE_FORMATS.YEAR))); + readonly selectedSeason = signal(0); + + readonly seasons: SeasonItem[] = [ + { key: 0, label: 'بهار', severity: 'success' }, + { key: 1, label: 'تابستان', severity: 'danger' }, + { key: 2, label: 'پاییز', severity: 'warn' }, + { key: 3, label: 'زمستان', severity: 'info' }, + ]; + + readonly years = signal<{ year: number; value: number }[]>([]); + + ngOnInit(): void { + const year = Number(gregorianToJalali(this.value, JALALI_DATE_FORMATS.YEAR)); + const monthIndex = Number(gregorianToJalali(this.value, JALALI_DATE_FORMATS.MONTH)) - 1; + const season = Math.floor(monthIndex / 3) as SeasonKey; + + this.selectedYear.set(year); + this.selectedSeason.set(season); + this.years.set( + Array.from({ length: this.maxYear - this.minYear + 1 }, (_, i) => ({ + year: this.minYear + i, + value: this.minYear + i, + })) + ); + this.emitValue(); + } + + currentSeason(): string { + const season = this.seasons.find((item) => item.key === this.selectedSeason()); + return `${season?.label ?? ''} ${this.selectedYear()}`; + } + + openPicker(): void { + this.isOpen.set(true); + } + + closePicker(): void { + this.isOpen.set(false); + } + + prevSeason(): void { + const index = this.selectedSeason(); + const currentYear = this.selectedYear(); + const nextSeason = index === 0 ? 3 : ((index - 1) as SeasonKey); + const nextYear = index === 0 ? currentYear - 1 : currentYear; + + // if (!this.isWithinBounds(nextYear, nextSeason)) return; + + this.selectedYear.set(nextYear); + this.selectedSeason.set(nextSeason); + this.emitValue(); + } + + nextSeason(): void { + const index = this.selectedSeason(); + const currentYear = this.selectedYear(); + const nextSeason = index === 3 ? 0 : ((index + 1) as SeasonKey); + const nextYear = index === 3 ? currentYear + 1 : currentYear; + + this.selectedYear.set(nextYear); + this.selectedSeason.set(nextSeason); + this.emitValue(); + } + + submitPicker(value: { year: number; season: number }): void { + this.selectedYear.set(value.year); + this.selectedSeason.set(value.season as SeasonKey); + this.emitValue(); + } + + private isWithinBounds(year: number, season: SeasonKey): boolean { + if (year < this.minYear || year > this.maxYear) return false; + // if (year === this.minYear && season < this.minSeason) return false; + // if (year === this.maxYear && season > this.maxSeason) return false; + return true; + } + + private emitValue(): void { + this.onChange.emit( + new Date( + jalaliToGregorian( + `${this.selectedYear()}/${(this.selectedSeason() * 3 + 1).toString().padStart(2, '0')}/01` + ) + ) + ); + } +} diff --git a/src/app/uikit/datepicker/datepicker.component.html b/src/app/uikit/datepicker/datepicker.component.html index 8631d2c..4e71e6c 100644 --- a/src/app/uikit/datepicker/datepicker.component.html +++ b/src/app/uikit/datepicker/datepicker.component.html @@ -1,18 +1,7 @@ - -
-
- +
+ - @if (hint) { - {{ hint }} - } -
-
-
- + @if (hint) { + {{ hint }} + } +
diff --git a/src/app/uikit/datepicker/datepicker.component.ts b/src/app/uikit/datepicker/datepicker.component.ts index b161687..3afa57c 100644 --- a/src/app/uikit/datepicker/datepicker.component.ts +++ b/src/app/uikit/datepicker/datepicker.component.ts @@ -1,17 +1,20 @@ import { Maybe } from '@/core'; +import { jalaliToGregorian, nowJalali } from '@/utils'; import { CommonModule } from '@angular/common'; import { + AfterViewInit, Component, ElementRef, EventEmitter, Input, - OnInit, + OnDestroy, Output, ViewChild, } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import dayjs from 'dayjs'; import flatpickr from 'flatpickr-wrap'; +import { Persian } from 'flatpickr-wrap/dist/l10n/fa'; import { BaseOptions, DateOption } from 'flatpickr-wrap/dist/types/options'; import { InputTextModule } from 'primeng/inputtext'; import { UikitFieldComponent } from '../uikit-field.component'; @@ -22,7 +25,7 @@ import { UikitFieldComponent } from '../uikit-field.component'; imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent], templateUrl: './datepicker.component.html', }) -export class UikitFlatpickrJalaliComponent implements OnInit { +export class UikitFlatpickrJalaliComponent implements AfterViewInit, OnDestroy { @Input() control?: FormControl>; @Input() placeholder = 'انتخاب تاریخ'; @Input() label = 'تاریخ'; @@ -41,19 +44,34 @@ export class UikitFlatpickrJalaliComponent implements OnInit { private fpInstance: any | null = null; - ngOnInit(): void { + ngAfterViewInit(): void { this.fpInstance = flatpickr(this.wrapperRef.nativeElement, { + wrap: true, ...this.options, - locale: 'fa', + locale: Persian, + disableMobile: true, + clickOpens: !this.disabled, minDate: this.minDate, maxDate: this.maxDate, - altInputClass: 'p-inputtext w-full', + now: nowJalali(), + altInputClass: 'flatpicker-field w-full', + altInput: true, dateFormat: 'Y-m-d', - altFormat: 'Y-m-d', + altFormat: 'Y/m/d', + onChange: (selectedDates: Date[], dateStr: string) => { + console.log('dateStr', jalaliToGregorian(dateStr)); + this.valueChange.emit(dateStr); if (this.control) this.control.setValue(dayjs(dateStr).toISOString()); }, }); } + + ngOnDestroy(): void { + if (this.fpInstance?.destroy) { + this.fpInstance.destroy(); + this.fpInstance = null; + } + } } diff --git a/src/app/utils/date-formatter.utils.ts b/src/app/utils/date-formatter.utils.ts index fa0f357..d89719c 100644 --- a/src/app/utils/date-formatter.utils.ts +++ b/src/app/utils/date-formatter.utils.ts @@ -29,6 +29,10 @@ export const JALALI_DATE_FORMATS = { TIME_WITH_SECONDS: 'HH:mm:ss', /** Month and year: "شهریور ۱۳۹۷" */ MONTH_YEAR: 'MMMM YYYY', + /** Year: "۱۳۹۷" */ + YEAR: 'YYYY', + /** Month: "۰۶" */ + MONTH: 'MM', /** Day and month: "۱۳ شهریور" */ DAY_MONTH: 'DD MMMM', } as const; @@ -55,6 +59,10 @@ export const GREGORIAN_DATE_FORMATS = { TIME_WITH_SECONDS: 'HH:mm:ss', /** Month and year: "September 2018" */ MONTH_YEAR: 'MMMM YYYY', + /** Year: "2018" */ + YEAR: 'YYYY', + /** Month: "06" */ + MONTH: 'MM', /** Day and month: "September 04" */ DAY_MONTH: 'MMMM DD', } as const; @@ -73,7 +81,7 @@ export const GREGORIAN_DATE_FORMATS = { */ export function formatJalali( date: string | Date | Dayjs | number, - format: string = JALALI_DATE_FORMATS.NUMERIC, + format: string = JALALI_DATE_FORMATS.NUMERIC ): string { return dayjs(date).calendar('jalali').locale('fa').format(format); } @@ -91,7 +99,7 @@ export function formatJalali( */ export function formatGregorian( date: string | Date | Dayjs | number, - format: string = GREGORIAN_DATE_FORMATS.NUMERIC, + format: string = GREGORIAN_DATE_FORMATS.NUMERIC ): string { return dayjs(date).calendar('gregory').locale('en').format(format); } @@ -107,7 +115,7 @@ export function formatGregorian( * parseJalali('1398-10-17') // Dayjs instance * parseJalali('1398/10/17') // Dayjs instance */ -export function parseJalali(jalaliDate: string, format?: string): Dayjs { +export function parseJalali(jalaliDate: string | Date | Dayjs | number, format?: string): Dayjs { return dayjs(jalaliDate, { jalali: true, ...(format && { format }) }); } @@ -124,7 +132,7 @@ export function parseJalali(jalaliDate: string, format?: string): Dayjs { */ export function gregorianToJalali( gregorianDate: string | Date | Dayjs, - format: string = JALALI_DATE_FORMATS.NUMERIC, + format: string = JALALI_DATE_FORMATS.NUMERIC ): string { return dayjs(gregorianDate).calendar('jalali').locale('fa').format(format); } @@ -142,7 +150,7 @@ export function gregorianToJalali( */ export function jalaliToGregorian( jalaliDate: string, - format: string = GREGORIAN_DATE_FORMATS.NUMERIC, + format: string = GREGORIAN_DATE_FORMATS.NUMERIC ): string { return parseJalali(jalaliDate).calendar('gregory').locale('en').format(format); } @@ -245,7 +253,7 @@ export function formatDate( date: string | Date | Dayjs | number, calendar: 'jalali' | 'gregory', locale: 'fa' | 'en', - format: string, + format: string ): string { return dayjs(date).calendar(calendar).locale(locale).format(format); } @@ -296,21 +304,21 @@ export function getJalaliWeekdayName(dayNumber: number): string { export function jalaliDiff( date1: string | number | Date | Dayjs, date2: string | number | Date | Dayjs, - unit: dayjs.OpUnitType = 'day', + unit: dayjs.OpUnitType = 'day' ): number { return toJalali(date1).diff(toJalali(date2), unit); } export function isJalaliBefore( date1: string | number | Date | Dayjs, - date2: string | number | Date | Dayjs, + date2: string | number | Date | Dayjs ): boolean { return toJalali(date1).isBefore(toJalali(date2)); } export function isJalaliAfter( date1: string | number | Date | Dayjs, - date2: string | number | Date | Dayjs, + date2: string | number | Date | Dayjs ): boolean { return toJalali(date1).isAfter(toJalali(date2)); } diff --git a/src/assets/styles.scss b/src/assets/styles.scss index 41ea47c..4c3e44f 100644 --- a/src/assets/styles.scss +++ b/src/assets/styles.scss @@ -1,6 +1,6 @@ /* You can add global styles to this file, and also import other style files */ @use "./tailwind.css"; -// @use "flatpickr-wrap/dist/themes/confetti.css"; +@use "flatpickr-wrap/dist/themes/confetti.css"; @use "flatpickr-wrap/dist/flatpickr.css"; @use "./flatpicker.css"; @use "./layout/layout.scss"; diff --git a/src/main.ts b/src/main.ts index 0d97ce0..dafc4cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,25 +1,23 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import flatpickr from 'flatpickr-wrap'; -import faLocales from 'flatpickr-wrap/dist/l10n/fa.js'; -import { brandingConfig } from './app/branding/branding.config'; import { AppComponent } from './app.component'; import { appConfig } from './app.config'; +import { brandingConfig } from './app/branding/branding.config'; import config from './config'; -flatpickr.localize(faLocales.fa!); +// flatpickr.localize(faLocales.fa!); -// Apply default configuration requested by the project -if (flatpickr.defaultConfig) { - Object.assign(flatpickr.defaultConfig, { - altInput: true, - altFormat: 'F j, Y H:i', - dateFormat: 'Y-m-d', - time_24hr: true, - time_format: 'H:i', - wrap: true, - clickOpens: true, - }); -} +// // Apply default configuration requested by the project +// if (flatpickr.defaultConfig) { +// Object.assign(flatpickr.defaultConfig, { +// altInput: true, +// altFormat: 'F j, Y H:i', +// dateFormat: 'Y-m-d', +// time_24hr: true, +// time_format: 'H:i', +// wrap: true, +// clickOpens: true, +// }); +// } document.title = brandingConfig.appTitle;