set own datepicker and change فاکتور to صورت‌حساب

This commit is contained in:
2026-05-31 13:54:34 +03:30
parent f50219a094
commit 4ec6143068
53 changed files with 557 additions and 13514 deletions
+1 -6
View File
@@ -190,8 +190,7 @@
"allowedCommonJsDependencies": [
"dayjs",
"dayjs/locale/fa",
"dayjs/plugin/relativeTime",
"flatpickr-wrap/dist/l10n/fa.js"
"dayjs/plugin/relativeTime"
],
"assets": [
{
@@ -209,8 +208,6 @@
".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"
@@ -252,8 +249,6 @@
"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"
-12993
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -12,12 +12,9 @@
"@primeng/themes": "^20.4.0",
"@primeuix/themes": "^1.2.5",
"@tailwindcss/postcss": "^4.2.3",
"@zoomit/dayjs-jalali-plugin": "^0.1.11",
"angularx-qrcode": "20.0.0",
"chart.js": "4.4.2",
"dayjs": "^1.11.20",
"flatpickr": "^4.6.13",
"flatpickr-wrap": "^1.0.0",
"jalaliday": "^3.1.1",
"jest-editor-support": "32.0.0-beta.1",
"ngx-cookie-service": "^21.3.1",
+9 -3
View File
@@ -12,7 +12,7 @@ import {
withInterceptors,
withInterceptorsFromDi,
} from '@angular/common/http';
import { ApplicationConfig, isDevMode, provideZonelessChangeDetection } from '@angular/core';
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import {
provideRouter,
@@ -34,7 +34,7 @@ export const appConfig: ApplicationConfig = {
anchorScrolling: 'enabled',
scrollPositionRestoration: 'enabled',
}),
withEnabledBlockingInitialNavigation(),
withEnabledBlockingInitialNavigation()
),
provideZonelessChangeDetection(),
// configure HttpClient once: enable fetch and register both functional
@@ -42,6 +42,12 @@ export const appConfig: ApplicationConfig = {
provideAnimationsAsync(),
// ensure PrimeNG uses our preset (applies css variables at app initialization)
providePrimeNG({
zIndex: {
tooltip: 2100,
menu: 2000,
overlay: 2000,
modal: 1100,
},
theme: {
preset: MyPreset,
options: { darkModeSelector: '.app-dark' },
@@ -72,7 +78,7 @@ export const appConfig: ApplicationConfig = {
provideHttpClient(
withFetch(),
withInterceptors([loggingInterceptor, authInterceptor, errorInterceptor, dedupInterceptor]),
withInterceptorsFromDi(),
withInterceptorsFromDi()
),
// provideServiceWorker('ngsw-worker.js', {
// enabled: !isDevMode(),
@@ -11,8 +11,6 @@ export function greaterThanValidator(minValue: number): ValidatorFn {
return null;
}
console.log('v', v);
const numericValue = typeof v === 'number' ? v : Number(v);
if (Number.isNaN(numericValue)) {
return { greaterThan: 'مقدار باید عددی باشد' };
@@ -18,7 +18,7 @@ export const CONSUMER_MENU_ITEMS = [
routerLink: ['/consumer/poses'],
},
{
label: 'فاکتورها',
label: 'صورت‌حساب‌ها',
icon: 'pi pi-fw pi-receipt',
routerLink: ['/consumer/sale_invoices'],
},
@@ -1,9 +1,8 @@
<app-page-data-list
pageTitle="لیست فاکتور‌ها"
pageTitle="لیست صورت‌حساب‌ها"
[columns]="columns"
emptyPlaceholderTitle="تا به حال فاکتور فروشی توسط این پایانه ایجاد نشده است."
emptyPlaceholderTitle="تا به حال صورت‌حسابی توسط این پایانه ایجاد نشده است."
[items]="items()"
[loading]="loading()"
(onRefresh)="refresh()"
>
(onRefresh)="refresh()">
</app-page-data-list>
@@ -1,10 +1,9 @@
<app-page-data-list
[pageTitle]="'لیست فاکتورها'"
[pageTitle]="'لیست صورت‌حساب‌'"
[columns]="columns"
emptyPlaceholderTitle="فاکتوری یافت نشد"
emptyPlaceholderTitle="صورت‌حسابی یافت نشد"
[items]="items()"
[loading]="loading()"
[showDetails]="true"
(onDetails)="toSinglePage($event)"
(onRefresh)="refresh()"
/>
(onRefresh)="refresh()" />
@@ -40,7 +40,7 @@ export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICust
},
{
field: 'invoice_date',
header: 'تاریخ فاکتور',
header: 'تاریخ صورت‌حساب‌',
type: 'date',
},
{
@@ -63,7 +63,7 @@ export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICust
toSinglePage(item: ICustomerSaleInvoicesResponse) {
this.router.navigateByUrl(
consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.customerId, item.id),
consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.customerId, item.id)
);
}
}
@@ -9,10 +9,10 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
path: 'saleInvoices',
loadComponent: () =>
import('../../views/saleInvoices/list.component').then(
(m) => m.ConsumerCustomerSaleInvoicesComponent,
(m) => m.ConsumerCustomerSaleInvoicesComponent
),
meta: {
title: 'فاکتورهای صادر شده',
title: 'صورت‌حساب‌های صادر شده',
pagePath: (customerId: string) => `consumer/customers/${customerId}`,
},
},
@@ -20,10 +20,10 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
path: 'saleInvoices/:invoiceId',
loadComponent: () =>
import('../../views/saleInvoices/single.component').then(
(m) => m.ConsumerCustomerSaleInvoiceComponent,
(m) => m.ConsumerCustomerSaleInvoiceComponent
),
meta: {
title: 'فاکتور صادر شده',
title: 'صورت‌حساب‌ صادر شده',
pagePath: (customerId: string, saleInvoiceId: string) =>
`consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`,
},
@@ -1,15 +1,14 @@
<app-page-data-list
pageTitle="آخرین فاکتورهای صادر شده امروز"
pageTitle="آخرین صورت‌حساب‌های صادر شده امروز"
[columns]="columns"
emptyPlaceholderTitle="تا به این لحظه فاکتوری صادر نشده است."
emptyPlaceholderTitle="تا به این لحظه صورت‌حساب‌ی صادر نشده است."
[items]="items()"
[showDetails]="true"
[loading]="loading()"
(onDetails)="toSinglePage($event)"
(onRefresh)="refresh()"
>
(onRefresh)="refresh()">
<ng-template #moreActions>
<a routerLink pButton [routerLink]="invoicesPageRoute" outlined>تمامی فاکتورها</a>
<a routerLink pButton [routerLink]="invoicesPageRoute" outlined>تمامی صورت‌حساب‌ها</a>
</ng-template>
<ng-template #status let-item>
@@ -45,7 +45,7 @@ export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<ISta
},
{
field: 'invoice_date',
header: 'تاریخ فاکتور',
header: 'تاریخ صورت‌حساب‌',
type: 'dateTime',
},
{ field: 'status', header: 'وضعیت صدور', customDataModel: this.status },
@@ -58,7 +58,7 @@ export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<ISta
toSinglePage(invoice: IStatisticsSaleInvoicesResponse) {
this.router.navigateByUrl(
consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoice.id),
consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoice.id)
);
}
}
@@ -1,7 +1,7 @@
<app-page-data-list
[pageTitle]="'لیست فاکتورها'"
[pageTitle]="'لیست صورت‌حساب‌ها'"
[columns]="columns"
emptyPlaceholderTitle="فاکتوری یافت نشد"
emptyPlaceholderTitle="صورت‌حساب‌ی یافت نشد"
[items]="items()"
[loading]="loading()"
[perPage]="responseMetaData()?.perPage"
@@ -9,7 +9,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
loadComponent: () =>
import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent),
meta: {
title: 'فاکتورهای صادر شده',
title: 'صورت‌حساب‌های صادر شده',
pagePath: () => `/consumer/sale_invoices`,
},
},
@@ -18,7 +18,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
loadComponent: () =>
import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent),
meta: {
title: 'فاکتور صادر شده',
title: 'صورت‌حساب‌ صادر شده',
pagePath: (saleInvoiceId: string) => `/consumer/sale_invoices/${saleInvoiceId}`,
},
},
@@ -4,15 +4,14 @@
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
(onHide)="close()">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<uikit-datepicker
<!-- <uikit-datepicker
label="تاریخ شروع لایسنس"
[control]="form.controls.starts_at"
name="starts_at"
hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
/>
/> -->
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
@@ -1,7 +1,7 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit';
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { Component, computed, inject, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { ILicenseRequest, ILicenseResponse } from '../../models';
@@ -10,12 +10,7 @@ import { LicensesService } from '../../services/licenses.service';
@Component({
selector: 'partner-consumer-license-form',
templateUrl: './form.component.html',
imports: [
ReactiveFormsModule,
SharedDialogComponent,
FormFooterActionsComponent,
UikitFlatpickrJalaliComponent,
],
imports: [ReactiveFormsModule, SharedDialogComponent, FormFooterActionsComponent],
})
export class ConsumerLicenseFormComponent extends AbstractFormDialog<
ILicenseRequest,
@@ -8,7 +8,7 @@ export const CONSUMER_MENU_ITEMS = [
routerLink: ['/'],
},
{
label: 'فاکتورها',
label: 'صورت‌حساب‌ها',
icon: 'pi pi-fw pi-home',
},
],
@@ -13,7 +13,7 @@
<pos-change-password-form class="w-full" />
<app-card-data cardTitle="تنظیمات پرینت صورت‌حساب" class="w-full">
<p-message variant="text" severity="info" icon="pi pi-info-circle">
هریک از موارد زیر را که می‌خواهید در چاپ فاکتور نمایش داده‌ شوند را انتخاب کنید
هریک از موارد زیر را که می‌خواهید در چاپ صورت‌حساب‌ نمایش داده‌ شوند را انتخاب کنید
</p-message>
<pos-config-print-form class="mt-6 block w-full" (onClose)="returnToMainPage()" />
</app-card-data>
@@ -4,7 +4,7 @@
[modal]="true"
[dismissible]="true"
[showCloseIcon]="true"
header="فیلتر فاکتورها"
header="فیلتر صورت‌حساب‌ها"
styleClass="w-full md:w-[28rem]"
(visibleChange)="visibleChange.emit($event)">
<div class="flex h-full flex-col overflow-y-auto">
@@ -24,8 +24,8 @@
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
<!-- <div class="grid grid-cols-2 gap-2">
<uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
<uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
<uikit-datepicker label="از تاریخ صورت‌حساب‌" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
<uikit-datepicker label="تا تاریخ صورت‌حساب‌" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
</div>
<div class="grid grid-cols-2 gap-2">
@@ -1,5 +1,5 @@
<div class="bg-surface-card">
<app-inner-pages-header pageTitle="لیست فاکتورهای صادر شده" [backRoute]="backRoute">
<app-inner-pages-header pageTitle="لیست صورت‌حساب‌های صادر شده" [backRoute]="backRoute">
<ng-template #actions>
<div class="flex gap-1">
<p-button
@@ -34,7 +34,7 @@
}
} @else if (!items() || items().length === 0) {
<div class="col-span-1">
<p class="text-muted-color text-center">فاکتوری یافت نشد.</p>
<p class="text-muted-color text-center">صورت‌حساب‌ی یافت نشد.</p>
</div>
} @else {
@for (item of items(); track item.id) {
@@ -48,8 +48,8 @@ export class PosSaleInvoiceListComponent {
filters = signal<IPosSaleInvoicesFilterDto>({});
activeFilters = computed(() => {
const labels: Partial<Record<keyof IPosSaleInvoicesFilterDto, string>> = {
invoice_date_from: 'از تاریخ فاکتور',
invoice_date_to: 'تا تاریخ فاکتور',
invoice_date_from: 'از تاریخ صورت‌حساب‌',
invoice_date_to: 'تا تاریخ صورت‌حساب‌',
created_at_from: 'از تاریخ ایجاد',
created_at_to: 'تا تاریخ ایجاد',
invoice_number: 'شماره صورت‌حساب',
@@ -9,10 +9,10 @@
{{ preparedInvoiceType() }}
</app-key-value>
<app-key-value alignment="end" label="مبلغ کل" [value]="saleInvoice.total_amount" type="price" />
<app-key-value alignment="end" label="نوع فاکتور">
<app-key-value alignment="end" label="نوع صورت‌حساب‌">
<catalog-invoice-type-tag [status]="saleInvoice.type.value" />
</app-key-value>
<app-key-value alignment="end" label="تاریخ فاکتور" [value]="saleInvoice.created_at" type="dateTime" />
<app-key-value alignment="end" label="تاریخ صورت‌حساب‌" [value]="saleInvoice.created_at" type="date" />
</div>
<hr />
<div class="flex items-center justify-center gap-4">
@@ -27,7 +27,7 @@
}
@if (saleInvoice.status.value.toLowerCase() === 'not_send') {
<p-button
label="ارسال فاکتور"
label="ارسال صورت‌حساب‌"
type="button"
(click)="sendInvoice()"
[loading]="sendingLoading()"
@@ -74,12 +74,12 @@ export class SaleInvoiceCardComponent {
.subscribe((res) => {
if (res.status === 'SEND_FAILURE') {
this.toastService.error({
text: res.message || 'خطا در ارسال فاکتور رخ داده است.',
text: res.message || 'خطا در ارسال صورت‌حساب‌ رخ داده است.',
});
} else {
this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
this.toastService.success({ text: 'ارسال صورت‌حساب‌ با موفقیت انجام شد.' });
}
// this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
// this.toastService.success({ text: 'ارسال صورت‌حساب‌ با موفقیت انجام شد.' });
this.refreshRequested.emit();
});
}
@@ -91,7 +91,7 @@ export class SaleInvoiceCardComponent {
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
.subscribe((res) => {
this.toastService.info({
text: `وضعیت فعلی فاکتور شماره‌ی ${this.saleInvoice.invoice_number}، ${taxProviderStatusTranslatorUtil(res.status)} می‌باشد.`,
text: `وضعیت فعلی صورت‌حساب‌ شماره‌ی ${this.saleInvoice.invoice_number}، ${taxProviderStatusTranslatorUtil(res.status)} می‌باشد.`,
});
this.refreshRequested.emit();
});
@@ -104,9 +104,11 @@ export class SaleInvoiceCardComponent {
.pipe(finalize(() => this.resendingLoading.set(false)))
.subscribe((res) => {
if (res.status === 'FAILURE') {
this.toastService.error({ text: res.message || 'خطا در ارسال مجدد فاکتور رخ داده است.' });
this.toastService.error({
text: res.message || 'خطا در ارسال مجدد صورت‌حساب‌ رخ داده است.',
});
} else {
this.toastService.success({ text: 'ارسال مجدد فاکتور با موفقیت انجام شد.' });
this.toastService.success({ text: 'ارسال مجدد صورت‌حساب‌ با موفقیت انجام شد.' });
}
this.refreshRequested.emit();
});
@@ -118,7 +120,7 @@ export class SaleInvoiceCardComponent {
.revoke(this.saleInvoice.id)
.pipe(finalize(() => this.revokingInvoiceLoading.set(false)))
.subscribe(() => {
this.toastService.success({ text: 'ابطال فاکتور با موفقیت انجام شد.' });
this.toastService.success({ text: 'ابطال صورت‌حساب‌ با موفقیت انجام شد.' });
this.refreshRequested.emit();
});
}
@@ -12,7 +12,7 @@ export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames>
loadComponent: () =>
import('../../views/list.component').then((m) => m.PosSaleInvoicesComponent),
meta: {
title: 'فاکتورها',
title: 'صورت‌حساب‌ها',
pagePath: () => baseRoute,
},
},
@@ -21,7 +21,7 @@ export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames>
loadComponent: () =>
import('../../views/single.component').then((m) => m.PosSaleInvoiceComponent),
meta: {
title: 'جزئیات فاکتور',
title: 'جزئیات صورت‌حساب‌',
pagePath: (invoiceId: string) => `${baseRoute}/${invoiceId}`,
},
},
@@ -41,10 +41,19 @@
<hr />
<div class="sticky bottom-0 flex flex-col gap-4 py-2">
<p-card class="border-surface-border border">
<div class="flex flex-col gap-4">
<app-datepicker
label="تاریخ صورت‌حساب‌"
alignment="horizontal"
[min]="invoiceMinDate"
[max]="invoiceMaxDate"
[control]="invoiceDate"
(valueChange)="changeDate($event)" />
<div class="flex shrink-0 items-center justify-between gap-2">
<app-key-value label="مشتری" [value]="customerNameToShow()" />
<button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button>
</div>
</div>
</p-card>
<pos-order-price-info-card />
<button
@@ -1,6 +1,8 @@
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
// import { ICustomerResponse } from '@/modules/customers/models';
import { KeyValueComponent } from '@/shared/components';
import { MiniMonthlyCalendarComponent } from '@/uikit';
import { formatGregorian, gregorianAddUnit } from '@/utils';
import {
ChangeDetectionStrategy,
Component,
@@ -10,7 +12,7 @@ import {
Output,
signal,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormControl, FormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
import images from 'src/assets/images';
@@ -33,6 +35,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
PosOrderCustomerDialogComponent,
KeyValueComponent,
Card,
MiniMonthlyCalendarComponent,
],
})
export class PosOrderSectionComponent {
@@ -43,6 +46,10 @@ export class PosOrderSectionComponent {
placeholderImage = images.placeholders.default;
isVisibleCustomerForm = signal(false);
invoiceMinDate = formatGregorian(gregorianAddUnit(new Date(), -7, 'days'));
invoiceMaxDate = formatGregorian(new Date());
invoiceDate = new FormControl(this.store.invoiceDate());
inOrderGoods = computed(() => this.store.inOrderGoods());
customer = computed(() => this.store.customer());
@@ -88,4 +95,8 @@ export class PosOrderSectionComponent {
}
submitCustomer() {}
changeDate(date: string) {
this.store.setInvoiceDate(date);
}
}
@@ -9,12 +9,14 @@
<qrcode [qrdata]="publicInvoiceRoute()" [width]="220" [errorCorrectionLevel]="'M'"></qrcode>
<div class="flex items-center gap-2">
<i class="" class="pi pi-check-circle text-xl! text-green-700"></i>
<p class="text-lg font-bold">فاکتور شما با موفقیت ایجاد شد.</p>
<p class="text-lg font-bold">صورت‌حساب‌ شما با موفقیت ایجاد شد.</p>
</div>
</div>
<div class="flex justify-center gap-2">
<button pButton type="button" outlined (click)="printInvoice()">چاپ</button>
<button pButton type="button" outlined (click)="sendInvoice()">ارسال به سامانه مودیان</button>
<button pButton type="button" outlined [loading]="sendingLoading()" (click)="sendInvoice()">
ارسال به سامانه مودیان
</button>
<button pButton type="button" outlined (click)="openOrderDetails()">جزئیات</button>
</div>
</shared-light-bottomsheet>
@@ -53,7 +53,7 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
.sendToTsp(this.invoice.id)
.pipe(finalize(() => this.sendingLoading.set(false)))
.subscribe(() => {
this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
this.toastService.success({ text: 'ارسال صورت‌حساب‌ با موفقیت انجام شد.' });
this.sended.set(true);
this.close();
});
@@ -65,7 +65,7 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
title: `فروشگاه ${this.posName()}`,
items: [
{
label: 'شماره فاکتور',
label: 'شماره صورت‌حساب‌',
value: this.invoice.code,
},
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
@@ -287,7 +287,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
if (this.selectedSettlementType() === 'CASH') {
if (this.remainedAmount() > 0) {
return this.toastServices.warn({
text: 'فاکتور تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
text: 'صورت‌حساب‌ تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
});
}
}
@@ -1,9 +1,14 @@
import { Maybe } from '@/core';
// import { ICustomerResponse } from '@/modules/customers/models';
import { ToastService } from '@/core/services/toast.service';
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
import {
createUUID,
formatDate,
GREGORIAN_DATE_FORMATS,
JALALI_DATE_FORMATS,
nowGregorian,
} from '@/utils';
import { computed, inject, Injectable, signal } from '@angular/core';
import { catchError, finalize, firstValueFrom, map, throwError } from 'rxjs';
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
@@ -47,7 +52,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
customerDetails: {
type: CustomerType.UNKNOWN,
},
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC_WITH_FULL_TIME),
invoiceDate: nowGregorian(JALALI_DATE_FORMATS.FULL),
payments: {
cash: 0,
set_off: 0,
@@ -59,7 +64,6 @@ export const INITIAL_POS_STATE: IPosLandingState = {
@Injectable({ providedIn: 'any' })
export class PosLandingStore {
private readonly service = inject(PosService);
private readonly toastServices = inject(ToastService);
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
@@ -249,6 +253,10 @@ export class PosLandingStore {
this.setState({ customerDetails: customer });
}
setInvoiceDate(invoiceDate: string) {
this.setState({ invoiceDate });
}
setPayment(payments: IPayment) {
this.setState({ payments });
}
@@ -259,6 +267,7 @@ export class PosLandingStore {
submitOrder() {
this.setState({ submitOrderLoading: true });
const orderPayload: IPosOrderRequest = {
customer_id: this.customer().customer_id,
customer_type: this.customer().type,
@@ -268,7 +277,9 @@ export class PosLandingStore {
good_id: good.id,
measure_unit: good.measure_unit,
})),
invoice_date: new Date().toISOString(),
invoice_date: new Date(
formatDate(this.invoiceDate(), 'gregory', 'en', GREGORIAN_DATE_FORMATS.FULL)
).toISOString(),
payments: this.payments(),
settlement_type: this.state$().settlement_type,
total_amount: this.orderPricingInfo().totalAmount,
@@ -295,7 +306,7 @@ export class PosLandingStore {
customerDetails: {
type: CustomerType.UNKNOWN,
},
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC),
invoiceDate: _res.invoice_date,
payments: {
cash: 0,
set_off: 0,
@@ -23,17 +23,17 @@
<!-- [style.paddingBottom]="'calc(0.75rem + {{env(safe-area-inset-bottom)}})'" -->
<div class="mx-auto flex w-full max-w-3xl items-center justify-between">
<div class="flex flex-col items-start gap-1">
<span class="text-base">مبلغ کل فاکتور</span>
<span class="text-base">مبلغ کل صورت‌حساب‌</span>
<span class="text-xl font-semibold" [appPriceMask]="priceInfo().totalAmount"></span>
</div>
<div class="flex items-center gap-2">
<span class="text-base font-medium">مشاهده فاکتور</span>
<span class="text-base font-medium">مشاهده صورت‌حساب‌</span>
<i class="pi pi-chevron-up"></i>
</div>
</div>
</button>
}
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [closable]="true" header="جزییات فاکتور">
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [closable]="true" header="جزییات صورت‌حساب‌">
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
</shared-light-bottomsheet>
@@ -4,15 +4,14 @@
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
(onHide)="close()">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<uikit-datepicker
<!-- <uikit-datepicker
label="تاریخ شروع لایسنس"
[control]="form.controls.starts_at"
name="starts_at"
hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
/>
/> -->
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
@@ -1,7 +1,7 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit';
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { Component, computed, inject, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { PartnerSelectComponent } from '../../../partners/shared/select.component';
@@ -15,7 +15,7 @@ import { LicensesService } from '../../services/licenses.service';
ReactiveFormsModule,
SharedDialogComponent,
FormFooterActionsComponent,
UikitFlatpickrJalaliComponent,
// UikitFlatpickrJalaliComponent,
PartnerSelectComponent,
],
})
@@ -4,16 +4,15 @@
[modal]="true"
[style]="{ width: '300px' }"
[closable]="true"
(onHide)="close()"
>
(onHide)="close()">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
<uikit-datepicker
<!-- <uikit-datepicker
label="انقضای فروش"
[control]="form.controls.activated_expires_at"
name="activated_expires_at"
[minDate]="minDate"
/>
/> -->
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</shared-dialog>
@@ -1,13 +1,13 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit';
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { nowJalali } from '@/utils';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { IPartnerChargeLicenseTransactionRequest } from '../models/chargeLicenseTransactions_io';
import { AdminPartnerChargeLicenseTransactionsService } from '../services/chargeLicenseTransactions.service';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({
selector: 'partner-charge-license-form-dialog',
@@ -17,7 +17,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
ReactiveFormsModule,
InputComponent,
FormFooterActionsComponent,
UikitFlatpickrJalaliComponent,
// UikitFlatpickrJalaliComponent,
],
})
export class PartnerChargeLicenseFormDialogComponent extends AbstractFormDialog<
@@ -4,16 +4,15 @@
[modal]="true"
[style]="{ width: '300px' }"
[closable]="true"
(onHide)="close()"
>
(onHide)="close()">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
<uikit-datepicker
<!-- <uikit-datepicker
label="انقضای فروش"
[control]="form.controls.activated_expires_at"
name="activated_expires_at"
[minDate]="minDate"
/>
/> -->
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</shared-dialog>
@@ -1,13 +1,13 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit';
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { nowJalali } from '@/utils';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { IAdminPartnerChargeAccountRequest } from '../../models/chargeAccount_io';
import { AdminPartnerChargeAccountService } from '../../services/chargeAccount.service';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({
selector: 'admin-partner-charge-account-form-dialog',
@@ -17,7 +17,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
ReactiveFormsModule,
InputComponent,
FormFooterActionsComponent,
UikitFlatpickrJalaliComponent,
// UikitFlatpickrJalaliComponent,
],
})
export class AdminPartnerChargeAccountFormDialogComponent extends AbstractFormDialog<
@@ -9,7 +9,7 @@ export const publicSaleInvoicesNamedRoutes: NamedRoutes<TPublicSaleInvoicesRoute
loadComponent: () =>
import('../../views/single.component').then((m) => m.PublicSaleInvoiceComponent),
meta: {
title: 'جزئیات فاکتور',
title: 'جزئیات صورت‌حساب‌',
pagePath: (invoiceId: string) => `sale-invoices/${invoiceId}`,
},
},
@@ -5,7 +5,7 @@ import { InputComponent } from '../input/input.component';
@Component({
selector: 'field-invoice-number-sequence',
template: `<app-input
label="شروع شماره فاکتور"
label="شروع شماره صورت‌حساب‌"
[control]="control"
[name]="name"
type="number"
@@ -15,5 +15,5 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
export class FieldInvoiceTemplatesComponent {
@Input({ required: true }) control = new FormControl<string>('');
@Input() name = 'invoice-template';
@Input() label = 'قالب فاکتور';
@Input() label = 'قالب صورت‌حساب‌';
}
@@ -1,16 +1,16 @@
import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { Component, Input } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'field-license-starts-at',
template: `<uikit-datepicker
label="تاریخ شروع لایسنس"
[control]="control"
[name]="name"
hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
/>`,
imports: [ReactiveFormsModule, UikitFlatpickrJalaliComponent],
// template: `<uikit-datepicker
// label="تاریخ شروع لایسنس"
// [control]="control"
// [name]="name"
// hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
// />`,
template: '',
imports: [ReactiveFormsModule],
})
export class LicenseStartsAtComponent {
@Input({ required: true }) control = new FormControl<string>('');
@@ -4,7 +4,7 @@
<uikit-empty-state> </uikit-empty-state>
} @else {
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false" [backRoute]="backRoute">
<app-card-data cardTitle="اطلاعات صورت‌حساب‌" [editable]="false" [backRoute]="backRoute">
<ng-template #moreActions>
<div class="">
<p-menu #menu [model]="moreActionMenuItems()" [popup]="true" appendTo="body" />
@@ -14,8 +14,8 @@
<div class="flex flex-col gap-4">
<div class="listKeyValue">
<app-key-value label="شماره صورت‌حساب" [value]="invoice.invoice_number" />
<app-key-value label="تاریخ فاکتور" [value]="invoice.invoice_date" type="dateTime" />
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice.created_at" type="dateTime" />
<app-key-value label="تاریخ صورت‌حساب‌" [value]="invoice.invoice_date" type="date" />
<app-key-value label="تاریخ ایجاد صورت‌حساب‌" [value]="invoice.created_at" type="dateTime" />
<app-key-value label="نوع صورت‌حساب">
<catalog-invoice-type-tag [status]="invoice.type.value" />
</app-key-value>
@@ -86,18 +86,15 @@ export class SharedSaleInvoiceSingleViewComponent {
showErrors() {}
showRevokeConfirmation() {
console.log('this.confirmationService', this.confirmationService);
this.confirmationService.ask({
header: `ابطال فاکتور شماره ${this.invoice!.invoice_number}`,
message: `در صورت تایید ابطال فاکتور شماره ${this.invoice!.invoice_number} بر روی دکمه‌ی ابطال کلیک کنید.`,
header: `ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number}`,
message: `در صورت تایید ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number} بر روی دکمه‌ی ابطال کلیک کنید.`,
acceptLabel: 'ابطال',
acceptButtonProps: {
severity: 'dangerdock',
},
accept: () => {
console.log('here');
// this.deferredInstallPrompt.prompt();
// this.deferredInstallPrompt.userChoice.finally(() => {
// this.deferredInstallPrompt = null;
@@ -26,8 +26,8 @@
// showConfirm() {
// this.confirmService.confirm({
// message: 'آیا پرداخت فاکتور انجام شده است؟',
// header: 'وضعیت پرداخت فاکتور',
// message: 'آیا پرداخت صورت‌حساب‌ انجام شده است؟',
// header: 'وضعیت پرداخت صورت‌حساب‌',
// acceptLabel: 'بله',
// rejectLabel: 'خیر',
// accept: () => {
@@ -35,8 +35,8 @@
// },
// reject: () => {
// this.toastService.info({
// text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.',
// title: 'پرداخت فاکتور انجام نشد',
// text: 'می‌توانید از طریق منوی پرداخت صورت‌حساب‌، در آینده اقدام به پرداخت نمایید.',
// title: 'پرداخت صورت‌حساب‌ انجام نشد',
// });
// },
// });
@@ -1,10 +1,10 @@
import { IListConfig } from './list-config.model';
export const saleInvoiceListConfig: IListConfig = {
pageTitle: 'مدیریت فاکتورهای فروش',
addNewCtaLabel: 'افزودن فاکتور',
emptyPlaceholderTitle: 'فاکتوری یافت نشد',
emptyPlaceholderDescription: 'برای افزودن فاکتور، روی دکمهٔ بالا کلیک کنید.',
pageTitle: 'مدیریت صورت‌حساب‌های فروش',
addNewCtaLabel: 'افزودن صورت‌حساب‌',
emptyPlaceholderTitle: 'صورت‌حساب‌ی یافت نشد',
emptyPlaceholderDescription: 'برای افزودن صورت‌حساب‌، روی دکمهٔ بالا کلیک کنید.',
columns: [
{ field: 'code', header: 'کد رهگیری' },
{
@@ -27,7 +27,7 @@ export const saleInvoiceListConfig: IListConfig = {
},
{
field: 'invoice_date',
header: 'تاریخ فاکتور',
header: 'تاریخ صورت‌حساب‌',
type: 'date',
},
{
@@ -1,7 +1,58 @@
<div #wrapper [attr.data-name]="name" class="flatWrapper w-full">
<input data-input [attr.placeholder]="placeholder" [name]="name" class="flatpicker-field w-full" />
<uikit-field [label]="label!" [name]="name!" [alignment]="alignment" class="w-full" (click)="op.toggle($event)">
<input pInputText readonly [name]="name" [value]="valueToShow" class="w-full" />
</uikit-field>
<p-popover #op appendTo="body">
<div #wrapperRef class="dir-rtl border-divider bg-background-paper w-full min-w-xs overflow-auto rounded-md">
<div class="bg-primary-main sticky top-0 flex justify-center px-3">
<div class="flex items-center gap-3">
<div class="flex h-8 items-center">
<div class="px-2">
<i class="pi pi-chevron-right cursor-pointer" (click)="changeMonth(-1)"></i>
</div>
@if (hint) {
<small [attr.id]="name + '-help'">{{ hint }}</small>
<div class="flex w-32 items-center justify-center px-3 font-bold select-none">
{{ monthInfo().monthTitle }}
</div>
<div class="px-2">
<i class="pi pi-chevron-left cursor-pointer" (click)="changeMonth(1)"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-7">
@for (dayName of daysName; track $index) {
<div class="text-muted-color flex items-center justify-center py-2">
{{ dayName[0] }}
</div>
}
</div>
</div>
<hr />
<div class="relative grow" style="direction: rtl">
@for (weekDays of weekDaysDates(); track $index) {
<div class="grid grid-cols-7">
@for (day of weekDays; track $index) {
<div
class="relative flex aspect-square items-center justify-center p-1"
[class.bg-background-default]="closedDays.includes($index)">
@if (!(day.isNextMonth || day.isPastMonth)) {
<span
class="flex aspect-square w-full cursor-pointer items-center justify-center rounded px-2 py-1 transition-colors select-none"
[ngClass]="{
'bg-surface-ground font-bold': day.isCurrentDay,
'bg-primary! text-primary-contrast!': isSelected(day),
'text-muted-color! font-normal': day.isDisabled,
}"
(click)="selectDate(day)">
{{ day.day }}
</span>
}
</div>
}
</div>
}
</div>
</div>
</p-popover>
+152 -57
View File
@@ -1,75 +1,170 @@
import { Maybe } from '@/core';
import { nowJalali } from '@/utils';
import { CommonModule } from '@angular/common';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
Output,
ViewChild,
} from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Component, EventEmitter, Input, Output, ViewChild, computed, signal } from '@angular/core';
import { FormControl } 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 jalaliday from 'jalaliday';
import { Maybe } from '@/core';
import { formatJalali } from '@/utils';
import { CommonModule } from '@angular/common';
import { InputText } from 'primeng/inputtext';
import { Popover } from 'primeng/popover';
import { UikitFieldComponent } from '../uikit-field.component';
import {
MonthInfo,
MonthlyWeekDate,
daysName,
prepareMonthInfo,
prepareWeekDayDates,
} from './monthly-calendar.helper.util';
dayjs.extend(jalaliday);
@Component({
selector: 'uikit-datepicker',
selector: 'app-datepicker',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent],
templateUrl: './datepicker.component.html',
imports: [Popover, UikitFieldComponent, InputText, CommonModule],
})
export class UikitFlatpickrJalaliComponent implements AfterViewInit, OnDestroy {
@Input() control?: FormControl<Maybe<string>>;
@Input() placeholder = 'انتخاب تاریخ';
@Input() label = 'تاریخ';
@Input() disabled = false;
@Input() name: string = 'datepicker';
@Input() options: Partial<BaseOptions> = {};
@Input() showLabel: boolean = true;
@Input() showErrors: boolean = true;
@Input() hint?: string;
@Input() minDate?: DateOption;
@Input() maxDate?: DateOption;
export class MiniMonthlyCalendarComponent {
@Input() control!: FormControl<Maybe<string>>;
@Input() alignment?: 'vertical' | 'horizontal' = 'vertical';
@Input() name?: string = 'date';
@Input() label?: string = 'تاریخ';
@Input() closeOnSelect?: boolean = true;
@Input() min?: string;
@Input() max?: string;
@Input() closedDays: number[] = [];
@Output() clickOnDayEvent = new EventEmitter<MonthlyWeekDate>();
@Output() valueChange = new EventEmitter<string>();
@ViewChild('wrapper', { static: true }) wrapperRef!: ElementRef<HTMLElement>;
@ViewChild('op') op!: Popover;
private fpInstance: any | null = null;
readonly daysName = daysName;
ngAfterViewInit(): void {
this.fpInstance = flatpickr(this.wrapperRef.nativeElement, {
wrap: true,
...this.options,
locale: Persian,
disableMobile: true,
clickOpens: !this.disabled,
minDate: this.minDate,
maxDate: this.maxDate,
now: nowJalali(),
altInputClass: 'flatpicker-field w-full',
altInput: true,
dateFormat: 'Y-m-d',
altFormat: 'Y/m/d',
/**
* Current displayed month
* Gregorian YYYY-MM-DD
*/
private readonly activeDateSignal = signal(dayjs().format('YYYY-MM-DD'));
onChange: (selectedDates: Date[], dateStr: string) => {
this.valueChange.emit(dateStr);
if (this.control) this.control.setValue(dayjs(dateStr).toISOString());
},
});
/**
* Selected value
* Gregorian YYYY-MM-DD
*/
private readonly selectedDateSignal = signal<Maybe<string>>(null);
disabled = false;
monthInfo = computed<MonthInfo>(() => prepareMonthInfo(this.activeDateSignal()));
weekDaysDates = computed(() => prepareWeekDayDates(this.monthInfo(), this.min, this.max));
// -----------------------
// Getters
// -----------------------
get value(): Maybe<string> {
return this.selectedDateSignal();
}
ngOnDestroy(): void {
if (this.fpInstance?.destroy) {
this.fpInstance.destroy();
this.fpInstance = null;
get valueToShow(): Maybe<string> {
return this.selectedDateSignal() && formatJalali(this.selectedDateSignal()!);
}
get activeDate(): string {
return this.activeDateSignal();
}
// -----------------------
// Setters
// -----------------------
set value(date: Maybe<string>) {
this.selectedDateSignal.set(date);
this.control.setValue(date);
if (date) {
this.activeDateSignal.set(date);
}
}
set activeDate(date: string) {
this.activeDateSignal.set(date);
}
// -----------------------
// Navigation
// -----------------------
changeMonth(months: number): void {
const newDate = dayjs(this.activeDateSignal()).add(months, 'month').format('YYYY-MM-DD');
this.activeDateSignal.set(newDate);
}
// -----------------------
// Selection
// -----------------------
selectDate(day: MonthlyWeekDate): void {
if (this.disabled) {
return;
}
if (day.isDisabled) {
return;
}
this.value = day.gregorianDate;
this.onChange(day.gregorianDate);
this.onTouched();
if (this.closeOnSelect) {
this.op.toggle(false);
}
this.clickOnDayEvent.emit(day);
this.valueChange.emit(day.gregorianDate);
}
isSelected(day: MonthlyWeekDate): boolean {
return !!this.value && this.value === day.gregorianDate;
}
// -----------------------
// CVA
// -----------------------
private onChange: (value: Maybe<string>) => void = () => {};
private onTouched: () => void = () => {};
writeValue(value: Maybe<string>): void {
this.value = value;
}
registerOnChange(fn: (value: Maybe<string>) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
// -----------------------
// Helpers
// -----------------------
trackByIndex(index: number): number {
return index;
}
}
@@ -0,0 +1,162 @@
import dayjs, { Dayjs } from 'dayjs';
import jalaliday from 'jalaliday';
dayjs.extend(jalaliday);
export interface MonthInfo {
isCurrentMonth: boolean;
firstDateOfMonth: dayjs.Dayjs;
lastDateOfMonth: dayjs.Dayjs;
monthDaysCount: number;
firstDayOfMonth: number;
jalaliFirstDayOfMonth: number;
monthTitle: string;
monthIndex: number;
prevMonthIsDisabled: boolean;
nextMonthIsDisabled: boolean;
isDisabled: boolean;
}
export interface MonthlyWeekDate {
gregorianDate: string;
date: string;
isNextMonth: boolean;
isPastMonth: boolean;
isCurrentDay: boolean;
isPastDate: boolean;
isFutureDate: boolean;
dayText: string;
day: number;
isHoliday: boolean;
isDisabled: boolean;
}
export const daysName = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
export function prepareMonthInfo(activeDate: string | Date, min?: string, max?: string): MonthInfo {
const date = dayjs(activeDate).calendar('jalali');
const firstDayOfMonth = date.startOf('month').day();
return {
isCurrentMonth: dayjs().calendar('jalali').isSame(date, 'month'),
firstDateOfMonth: date.startOf('month'),
lastDateOfMonth: date.endOf('month'),
monthDaysCount: date.daysInMonth(),
firstDayOfMonth,
jalaliFirstDayOfMonth: firstDayOfMonth === 6 ? 0 : firstDayOfMonth + 1,
monthTitle: date.locale('fa').format('MMMM YYYY'),
monthIndex: date.get('month'),
isDisabled: false,
nextMonthIsDisabled: false,
prevMonthIsDisabled: false,
};
}
export function prepareWeekDayDates(
monthInfo: MonthInfo,
min?: string,
max?: string
): MonthlyWeekDate[][] {
const weekDaysDates: MonthlyWeekDate[][] = [];
const { monthDaysCount, firstDateOfMonth } = monthInfo;
for (let i = 0; i < monthDaysCount; i++) {
const date = firstDateOfMonth.add(i, 'day');
const dateDay = date.day();
const persianDateDay = dateDay === 6 ? 0 : dateDay + 1;
if (i === 0 || persianDateDay === 0) {
weekDaysDates.push(new Array(7) as MonthlyWeekDate[]);
}
if (i === 0) {
for (let j = 0; j < persianDateDay; j++) {
weekDaysDates[0][j] = weekDateFactory(
date.add(-persianDateDay + j, 'day'),
monthInfo,
min,
max
);
}
}
weekDaysDates[weekDaysDates.length - 1][persianDateDay] = weekDateFactory(
date,
monthInfo,
min,
max
);
if (i === monthDaysCount - 1) {
for (let j = persianDateDay + 1; j < 7; j++) {
weekDaysDates[weekDaysDates.length - 1][j] = weekDateFactory(
date.add(j - persianDateDay, 'day'),
monthInfo,
min,
max
);
}
}
}
return weekDaysDates;
}
function weekDateFactory(
date: dayjs.Dayjs,
monthInfo: MonthInfo,
min?: string,
max?: string
): MonthlyWeekDate {
const now = dayjs().calendar('jalali');
const formattedDate = date.format('YYYY-MM-DD');
return {
gregorianDate: date.calendar('gregory').format('YYYY-MM-DD'),
date: formattedDate,
isNextMonth: date.month() > monthInfo.monthIndex,
isPastMonth: date.month() < monthInfo.monthIndex,
isCurrentDay: now.isSame(date, 'day'),
isPastDate: date.isBefore(now, 'day'),
isFutureDate: now.isBefore(date, 'day'),
dayText: date.locale('fa').format('D MMMM'),
day: Number(date.locale('fa').format('D')),
isDisabled: isDisabledDate(date, min, max),
// Replace with your own holiday service
isHoliday: false,
};
}
function isDisabledDate(date: Dayjs, min?: string, max?: string) {
if (min && date.isBefore(dayjs(min), 'day')) {
return true;
}
if (max && date.isAfter(dayjs(max), 'day')) {
return true;
}
return false;
}
+1 -1
View File
@@ -2,7 +2,7 @@
<ng-template #fieldTemplate>
@if (showLabel) {
<div [class]="`flex gap-1 ${alignment === 'horizontal' ? 'items-center' : 'flex-col'} ${className || ''}`">
<div [class]="`flex shrink-0 gap-1 ${alignment === 'horizontal' ? 'items-center' : 'flex-col'} ${className || ''}`">
@if (labelView) {
<ng-container [ngTemplateOutlet]="labelView"> </ng-container>
} @else {
+31 -19
View File
@@ -1,7 +1,9 @@
import jalaliPlugin from '@zoomit/dayjs-jalali-plugin';
import dayjs, { Dayjs } from 'dayjs';
import 'dayjs/locale/fa';
import relativeTime from 'dayjs/plugin/relativeTime';
import jalaliPlugin from 'jalaliday';
type dateInput = string | Date | Dayjs | number;
// Extend dayjs with plugins
dayjs.extend(jalaliPlugin);
@@ -80,7 +82,7 @@ export const GREGORIAN_DATE_FORMATS = {
* formatJalali(new Date(), JALALI_DATE_FORMATS.FULL_WITH_WEEKDAY) // 'پنج‌شنبه، ۱۳ شهریور ۱۳۹۷'
*/
export function formatJalali(
date: string | Date | Dayjs | number,
date: dateInput,
format: string = JALALI_DATE_FORMATS.NUMERIC
): string {
return dayjs(date).calendar('jalali').locale('fa').format(format);
@@ -98,7 +100,7 @@ export function formatJalali(
* formatGregorian(new Date(), GREGORIAN_DATE_FORMATS.NUMERIC_WITH_TIME) // '2018-09-04 16:30'
*/
export function formatGregorian(
date: string | Date | Dayjs | number,
date: dateInput,
format: string = GREGORIAN_DATE_FORMATS.NUMERIC
): string {
return dayjs(date).calendar('gregory').locale('en').format(format);
@@ -115,7 +117,7 @@ export function formatGregorian(
* parseJalali('1398-10-17') // Dayjs instance
* parseJalali('1398/10/17') // Dayjs instance
*/
export function parseJalali(jalaliDate: string | Date | Dayjs | number, format?: string): Dayjs {
export function parseJalali(jalaliDate: dateInput, format?: string): Dayjs {
return dayjs(jalaliDate, { jalali: true, ...(format && { format }) });
}
@@ -192,7 +194,7 @@ export function nowGregorian(format: string = GREGORIAN_DATE_FORMATS.NUMERIC_WIT
* @example
* relativeTimeJalali(dayjs().subtract(2, 'day')) // '۲ روز پیش'
*/
export function relativeTimeJalali(date: string | Date | Dayjs | number): string {
export function relativeTimeJalali(date: dateInput): string {
return dayjs(date).locale('fa').fromNow();
}
@@ -219,7 +221,7 @@ export function isValidJalaliDate(jalaliDate: string): boolean {
* @example
* startOfDayJalali('1398-10-17') // Dayjs instance at 00:00:00
*/
export function startOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
export function startOfDayJalali(date: dateInput): Dayjs {
return dayjs(date).calendar('jalali').startOf('day');
}
@@ -232,7 +234,7 @@ export function startOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
* @example
* endOfDayJalali('1398-10-17') // Dayjs instance at 23:59:59
*/
export function endOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
export function endOfDayJalali(date: dateInput): Dayjs {
return dayjs(date).calendar('jalali').endOf('day');
}
@@ -250,7 +252,7 @@ export function endOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
* formatDate('2018-09-04', 'gregory', 'en', 'MMMM DD YYYY') // 'September 04 2018'
*/
export function formatDate(
date: string | Date | Dayjs | number,
date: dateInput,
calendar: 'jalali' | 'gregory',
locale: 'fa' | 'en',
format: string
@@ -302,28 +304,38 @@ export function getJalaliWeekdayName(dayNumber: number): string {
}
export function jalaliDiff(
date1: string | number | Date | Dayjs,
date2: string | number | Date | Dayjs,
date1: dateInput,
date2: dateInput,
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
): boolean {
export function jalaliAddUnit(
date: dateInput,
count: number,
unit: dayjs.ManipulateType = 'days'
): Dayjs {
return dayjs(date, { jalali: true }).add(count, unit);
}
export function gregorianAddUnit(
date: dateInput,
count: number,
unit: dayjs.ManipulateType = 'days'
): Dayjs {
return dayjs(date).add(count, unit);
}
export function isJalaliBefore(date1: dateInput, date2: dateInput): boolean {
return toJalali(date1).isBefore(toJalali(date2));
}
export function isJalaliAfter(
date1: string | number | Date | Dayjs,
date2: string | number | Date | Dayjs
): boolean {
export function isJalaliAfter(date1: dateInput, date2: dateInput): boolean {
return toJalali(date1).isAfter(toJalali(date2));
}
export function toJalali(date: string | number | Date | Dayjs, format = 'YYYY/MM/DD'): Dayjs {
export function toJalali(date: dateInput, format = 'YYYY/MM/DD'): Dayjs {
return dayjs(date).calendar('jalali');
}
-282
View File
@@ -1,282 +0,0 @@
.flatpickr-calendar {
direction: rtl;
background: var(--color-surface-ground);
}
.flatpickr-time {
direction: ltr;
}
.flat {
display: flex;
}
.flatpickr-calendar .hasWeeks .dayContainer {
/*border-left: 0; */
}
.flatpickr-calendar:before,
.flatpickr-calendar:after {
/*left: 22px; */
}
.flatpickr-calendar.rightMost:before,
.flatpickr-calendar.arrowRight:before,
.flatpickr-calendar.rightMost:after,
.flatpickr-calendar.arrowRight:after {
/*left: auto;*/
/*right: 22px;*/
}
.flatpickr-calendar.arrowTop:before {
/*border-bottom-color: #e6e6e6;*/
}
.flatpickr-calendar.arrowTop:after {
/*border-bottom-color: #fff;*/
}
.flatpickr-calendar.arrowBottom:before {
/*border-top-color: #e6e6e6;*/
}
.flatpickr-calendar.arrowBottom:after {
/*border-top-color: #fff;*/
}
.flatpickr-months {
display: flex;
align-items: center;
}
.flatpickr-months .flatpickr-month {
color: var(--p-text-muted-color);
fill: var(--p-text-muted-color);
height: 40px;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
color: var(--p-text-muted-color);
fill: var(--p-text-muted-color);
position: relative !important;
rotate: 180deg;
}
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
right: 0;
left: auto;
}
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
left: 0;
right: auto;
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
/*color: #959ea9;*/
}
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
/*fill: #f64747;*/
}
.numInputWrapper span {
right: auto;
left: 0;
/*border: 1px solid rgba(57,57,57,0.15);*/
}
.numInputWrapper span:hover {
/*background: rgba(0,0,0,0.1);*/
}
.numInputWrapper span:active {
/*background: rgba(0,0,0,0.2);*/
}
.numInputWrapper span.arrowUp:after {
/*border-bottom: 4px solid rgba(57,57,57,0.6);*/
}
.numInputWrapper span.arrowDown:after {
/*border-top: 4px solid rgba(57,57,57,0.6);*/
}
.numInputWrapper span svg path {
/*fill: rgba(0,0,0,0.5);*/
}
.numInputWrapper:hover {
/*background: rgba(0,0,0,0.05);*/
}
.flatpickr-current-month {
padding: 0;
position: relative;
left: auto;
right: auto;
display: flex;
margin: auto;
gap: 8px;
height: 100%;
}
.flatpickr-current-month > * {
flex: 1;
}
.flatpickr-current-month span.cur-month {
/*margin-left: 0.5ch;*/
}
.flatpickr-current-month span.cur-month:hover {
/*background: rgba(0,0,0,0.05);*/
}
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
border-bottom-color: var(--p-text-color);
}
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
border-top-color: var(--p-text-color);
}
.flatpickr-current-month input.cur-year {
font-size: 16px;
display: flex;
align-items: center;
height: 100%;
padding: 0 0 0 20px;
}
.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
/*color: rgba(0,0,0,0.5);*/
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
font-size: 16px;
height: 100%;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
/*background: rgba(0,0,0,0.05);*/
}
span.flatpickr-weekday {
color: var(--p-text-muted-color);
}
.dayContainer {
text-align: left;
}
/*.dayContainer + .dayContainer {*/
/* -webkit-box-shadow: -1px 0 0 #e6e6e6;*/
/* box-shadow: -1px 0 0 #e6e6e6;*/
/*}*/
.flatpickr-day {
color: var(--p-text-color);
}
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
background: var(--p-blue-300);
border-color: var(--focus-ring-color);
}
.flatpickr-day.today {
/*border-color: #959ea9;*/
}
.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
/*border-color: #959ea9;*/
/*background: #959ea9;*/
/*color: #fff;*/
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: var(--color-primary);
color: #fff;
border-color: var(--focus-ring-color);
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
/*-webkit-box-shadow: -10px 0 0 #569ff7;*/
/*box-shadow: -10px 0 0 #569ff7;*/
}
.flatpickr-day.inRange {
/*-webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;*/
/* box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;*/
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
color: var(--p-text-color);
opacity: 0.4;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
/*color: rgba(57,57,57,0.1);*/
}
.flatpickr-day.week.selected {
/*-webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;*/
/* box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;*/
}
.flatpickr-weekwrapper {
/*float: left;*/
}
.flatpickr-weekwrapper .flatpickr-weeks {
/*-webkit-box-shadow: 1px 0 0 #e6e6e6;*/
/* box-shadow: 1px 0 0 #e6e6e6;*/
}
.flatpickr-weekwrapper span.flatpickr-day,
.flatpickr-weekwrapper span.flatpickr-day:hover {
/*color: rgba(57,57,57,0.3);*/
}
.flatpickr-time .numInputWrapper {
/*float: left;*/
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: var(--p-text-color);
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: var(--p-text-color);
}
.flatpickr-time input {
color: var(--p-text-color);
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
/*float: left;*/
color: var(--p-text-color);
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: var(--color-surface-hover);
}
-3
View File
@@ -1,8 +1,5 @@
/* 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/flatpickr.css";
@use "./flatpicker.css";
@use "./layout/layout.scss";
@use "primeicons/primeicons.css";
@use "./rtlSupport.scss";
-15
View File
@@ -4,21 +4,6 @@ import { appConfig } from './app.config';
import { brandingConfig } from './app/branding/branding.config';
import config from './config';
// 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,
// });
// }
document.title = brandingConfig.appTitle;
const manifestLink = document.querySelector('link[rel="manifest"]');