In Angular-15 application, I am using ngx-datepicker of ngx-bootstrap
Currently I have this:
- StartDate and EndDate it enables the current date back to the last one year, then the rest disabled
- Between the StartDate and EndDate, the user should not be able to select more than a month, even though it enables a year
- When the Search button clicked and the validation is violated, it displays this: Date range cannot exceed one month
All the three (3) above works, but I have this problem:
When the user clicks on search button, whenever the validation is violated. that is when user selects more than a month range between StartDate and EndDate, after the search button clicked, It disables all the other dates, and enables only the date selected on StartDate for both StartDate and EndDate
For instance, if the user selects
StartDate : 01-Jul-2024 EndDate : 01-NOV-2024
and clicks search
After showing the validation warning, it will only enable
01-Jul-2024
for both StartDate and EndDate
Kindly help resolve it.
MAIN CODE:
created-date-transactions:
import { Component, TemplateRef } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { PaginatePipe } from 'ngx-pagination';
import { ToastrService } from 'ngx-toastr';
import { TransactionService } from 'src/app/features/admin/services/transaction.service';
import {
ITransactionCreatedDateList,
ICreatedPageResult,
ICreatedResponse,
ICreatedPagingFilter,
} from 'src/app/features/admin/models/transaction/transaction-created-date-list.model';
import { DatePipe, CurrencyPipe } from '@angular/common';
import { OrderPipe } from 'ngx-order-pipe';
import { saveAs } from 'file-saver';
import { interval, Subscription } from 'rxjs';
import { startWith, switchMap } from 'rxjs/operators';
@Component({
selector: 'app-created-date-transactions',
templateUrl: './created-date-transactions.component.html',
styleUrls: ['./created-date-transactions.component.scss'],
})
export class CreatedDateTransactionsComponent {
bsModalRef?: BsModalRef;
search = 'Search';
transactionList: any;
columns: any[] = [];
columnsWithFeatures: any;
isLoading = false;
showModal!: boolean;
selectedSearchCriteria: any;
order: string = 'createDate';
reverse: boolean = false;
allTransactionList!: ITransactionCreatedDateList[];
pageResult!: ICreatedPageResult<ITransactionCreatedDateList[]>;
filter: ICreatedPagingFilter = {
searchQuery: '',
sortBy: '',
isSortAscending: true,
startDate: null,
endDate: null,
pageNumber: 1,
pageSize: 10,
exportToExcel: false,
};
minStartDate!: Date;
minEndDate!: Date;
maxStartDate!: Date;
maxEndDate!: Date;
startingIndex: number = 0; // Initialize with 0
searchButtonClicked = false;
private refreshSubscription!: Subscription;
constructor(
private transactionService: TransactionService,
private datePipe: DatePipe,
private currencyPipe: CurrencyPipe,
private toastr: ToastrService,
private modalService: BsModalService,
private orderPipe: OrderPipe,
private paginatePipe: PaginatePipe
) {
const today = new Date();
this.maxStartDate = today;
this.minStartDate = new Date();
this.minStartDate.setFullYear(today.getFullYear() - 1);
this.maxEndDate = today;
this.minEndDate = new Date();
this.minEndDate.setFullYear(today.getFullYear() - 1);
}
ngOnInit(): void {
this.isLoading = true;
}
validateDateRange(startDate: Date, endDate: Date): boolean {
const oneMonthInMilliseconds = 30 * 24 * 60 * 60 * 1000; // Approx. one month
return Math.abs(endDate.getTime() - startDate.getTime()) <= oneMonthInMilliseconds;
}
validateSearchDateRange() {
if (this.filter.startDate && this.filter.endDate) {
const startDate = new Date(this.filter.startDate);
const endDate = new Date(this.filter.endDate);
// Calculate the difference in months
const monthDifference = (endDate.getFullYear() - startDate.getFullYear()) * 12 +
(endDate.getMonth() - startDate.getMonth());
if (Math.abs(monthDifference) > 1) {
this.toastr.warning('Date range cannot exceed one month');
// Reset end date to exactly one month after start date
const adjustedEndDate = new Date(startDate);
adjustedEndDate.setMonth(startDate.getMonth() + 1);
this.filter.endDate = adjustedEndDate;
// Recalculate min and max dates for both inputs
this.minEndDate = startDate;
this.maxEndDate = adjustedEndDate;
this.minStartDate = new Date(startDate.getFullYear() - 1, startDate.getMonth(), startDate.getDate());
this.maxStartDate = new Date();
}
}
}
updateBsConfig(): void {
this.filter.startDate
? (this.maxEndDate = this.filter.startDate)
: (this.maxEndDate = new Date());
this.filter.endDate
? (this.maxStartDate = this.filter.endDate)
: (this.maxStartDate = new Date());
}
loadAllTransactions() {
this.updateBsConfig();
this.transactionService
.getAllTransactionsByCreatedDateFilter(this.filter)
.subscribe((result) => {
this.pageResult = result;
this.allTransactionList = result.pageItems;
this.isLoading = false;
});
}
onSearch() {
this.validateSearchDateRange();
this.searchButtonClicked = true;
this.filter.pageNumber = 1;
this.loadAllTransactions();
}
onDateRangeSelected(startDate: Date | null, endDate: Date | null) {
if (startDate && endDate) {
if (this.validateDateRange(startDate, endDate)) {
this.filter.startDate = startDate;
this.filter.endDate = endDate;
this.loadAllTransactions();
} else {
this.toastr.error('The selected date range cannot exceed one month.');
// Reset the filter dates to `null` to clear invalid selections
this.filter.startDate = null;
this.filter.endDate = null;
}
} else {
this.toastr.error('Invalid date selection. Please select valid start and end dates.');
// Reset the filter dates to avoid further issues
this.filter.startDate = null;
this.filter.endDate = null;
}
}
}
This is the html aspect:
created-date-transactions.html:
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0">Admin Dashboard: {{ pageTitle }}</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item">
<a [routerLink]="['/admin-dashboard']">Dashboard</a>
</li>
<li class="breadcrumb-item active">{{ pageTitle }}</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-xs-12 col-12">
<div class="card card-danger">
<div class="card-header">
<h3 class="card-title">{{ search }}</h3>
<div class="card-tools">
<button
type="button"
class="btn btn-tool"
data-card-widget="collapse"
>
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="startDate">Start Date:<span style="color: red">*</span></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"
><i class="far fa-calendar-alt"></i
></span>
</div>
<input
type="text"
placeholder="DD-MM-YYYY"
class="form-control"
bsDatepicker
[minDate]="minStartDate"
[maxDate]="maxStartDate"
[(ngModel)]="filter.startDate"
[bsConfig]="{
isAnimated: true,
dateInputFormat: 'DD-MM-YYYY',
returnFocusToInput: true,
showClearButton: true,
clearPosition: 'right',
maxDate: maxStartDate
}"
(change)="filter.startDate && filter.endDate && onDateRangeSelected(filter.startDate, filter.endDate)"
required
/>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="endDate">End Date:<span style="color: red">*</span></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"
><i class="far fa-calendar-alt"></i
></span>
</div>
<input
type="text"
placeholder="DD-MM-YYYY"
class="form-control"
bsDatepicker
[minDate]="minEndDate"
[maxDate]="maxEndDate"
[(ngModel)]="filter.endDate"
[bsConfig]="{
isAnimated: true,
dateInputFormat: 'DD-MM-YYYY',
returnFocusToInput: true,
showClearButton: true,
clearPosition: 'right',
maxDate: maxEndDate
}"
(change)="filter.startDate && filter.endDate && onDateRangeSelected(filter.startDate, filter.endDate)"
required
/>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<div class="input-group">
<input
type="text"
placeholder="Search By: Merchant, Description, Channel ..."
class="form-control"
id="searchInput"
[(ngModel)]="filter.searchQuery"
/>
<div class="input-group-append">
<button
class="btn btn-primary"
type="button"
(click)="onSearch()"
[disabled]="!filter.startDate || !filter.endDate"
>
Search
</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-success"
title="Export Excel Data"
(click)="onExportToExcel()"
[disabled]="!filter.startDate || !filter.endDate"
>
<i class="fa fa-file-excel-o" aria-hidden="true"></i> Export to
Excel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
