2026-05-31 13:54:34 +03:30
|
|
|
import { Component, EventEmitter, Input, Output, ViewChild, computed, signal } from '@angular/core';
|
|
|
|
|
|
|
|
|
|
import { FormControl } from '@angular/forms';
|
|
|
|
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
import jalaliday from 'jalaliday';
|
|
|
|
|
|
2025-12-04 21:07:18 +03:30
|
|
|
import { Maybe } from '@/core';
|
2026-05-31 13:54:34 +03:30
|
|
|
import { formatJalali } from '@/utils';
|
2025-12-04 21:07:18 +03:30
|
|
|
import { CommonModule } from '@angular/common';
|
2026-05-31 13:54:34 +03:30
|
|
|
import { InputText } from 'primeng/inputtext';
|
|
|
|
|
import { Popover } from 'primeng/popover';
|
2025-12-04 21:07:18 +03:30
|
|
|
import { UikitFieldComponent } from '../uikit-field.component';
|
2026-05-31 13:54:34 +03:30
|
|
|
import {
|
|
|
|
|
MonthInfo,
|
|
|
|
|
MonthlyWeekDate,
|
|
|
|
|
daysName,
|
|
|
|
|
prepareMonthInfo,
|
|
|
|
|
prepareWeekDayDates,
|
|
|
|
|
} from './monthly-calendar.helper.util';
|
|
|
|
|
|
|
|
|
|
dayjs.extend(jalaliday);
|
2025-12-04 21:07:18 +03:30
|
|
|
|
|
|
|
|
@Component({
|
2026-05-31 13:54:34 +03:30
|
|
|
selector: 'app-datepicker',
|
2025-12-04 21:07:18 +03:30
|
|
|
standalone: true,
|
|
|
|
|
templateUrl: './datepicker.component.html',
|
2026-05-31 13:54:34 +03:30
|
|
|
imports: [Popover, UikitFieldComponent, InputText, CommonModule],
|
2025-12-04 21:07:18 +03:30
|
|
|
})
|
2026-05-31 13:54:34 +03:30
|
|
|
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[] = [];
|
2025-12-04 21:07:18 +03:30
|
|
|
|
2026-05-31 13:54:34 +03:30
|
|
|
@Output() clickOnDayEvent = new EventEmitter<MonthlyWeekDate>();
|
2025-12-04 21:07:18 +03:30
|
|
|
@Output() valueChange = new EventEmitter<string>();
|
|
|
|
|
|
2026-05-31 13:54:34 +03:30
|
|
|
@ViewChild('op') op!: Popover;
|
|
|
|
|
|
|
|
|
|
readonly daysName = daysName;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Current displayed month
|
|
|
|
|
* Gregorian YYYY-MM-DD
|
|
|
|
|
*/
|
|
|
|
|
private readonly activeDateSignal = signal(dayjs().format('YYYY-MM-DD'));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2025-12-04 21:07:18 +03:30
|
|
|
}
|
2026-05-23 18:09:44 +03:30
|
|
|
|
2026-05-31 13:54:34 +03:30
|
|
|
// -----------------------
|
|
|
|
|
// 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);
|
2026-05-23 18:09:44 +03:30
|
|
|
}
|
2026-05-31 13:54:34 +03:30
|
|
|
|
|
|
|
|
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;
|
2026-05-23 18:09:44 +03:30
|
|
|
}
|
2025-12-04 21:07:18 +03:30
|
|
|
}
|