diff --git a/package-lock.json b/package-lock.json index 9add9672..c3be3807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,12 +44,10 @@ "chart.js": "^4.1.1", "cordova-plugin-file": "^7.0.0", "cordova-plugin-nativestorage": "^2.3.2", - "date-fns": "^2.29.3", + "date-fns": "^3.6.0", "file-saver": "^2.0.5", - "ion2-calendar": "^3.5.0", "ionicons": "^6.0.3", "mixpanel-browser": "^2.45.0", - "moment": "^2.30.1", "ng5-slider": "^1.2.6", "rxjs": "~7.5.0", "tslib": "^2.3.0", @@ -7371,15 +7369,12 @@ "dev": true }, "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "engines": { - "node": ">=0.11" - }, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/date-format": { @@ -10060,14 +10055,6 @@ "node": ">= 0.4" } }, - "node_modules/ion2-calendar": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ion2-calendar/-/ion2-calendar-3.5.0.tgz", - "integrity": "sha512-LAd+fd+z95Ftvwa899bErre4ld9Cf/eF3iOd0NmLOGTqbdICi65GnyGAtTLQLdxNuj6arizyh8YC6rxt0m1y5Q==", - "peerDependencies": { - "moment": "^2.19.3" - } - }, "node_modules/ionicons": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.1.3.tgz", @@ -12129,14 +12116,6 @@ "node": "*" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index fd0473f0..3d1ee4da 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,10 @@ "chart.js": "^4.1.1", "cordova-plugin-file": "^7.0.0", "cordova-plugin-nativestorage": "^2.3.2", - "date-fns": "^2.29.3", + "date-fns": "^3.6.0", "file-saver": "^2.0.5", - "ion2-calendar": "^3.5.0", "ionicons": "^6.0.3", "mixpanel-browser": "^2.45.0", - "moment": "^2.30.1", "ng5-slider": "^1.2.6", "rxjs": "~7.5.0", "tslib": "^2.3.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f927ed05..06da47cd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -67,7 +67,6 @@ import {FulltimeLocationPageModule} from './pages/logged-in/fulltimer/fulltime-l import { FulltimerSearchPageModule } from './pages/logged-in/fulltimer/fulltimer-search/fulltimer-search.module'; import { StaffPageModule } from './pages/logged-in/pickers/staff/staff.module'; import { DateDropdownModule } from './components/date-dropdown/date-dropdown.module'; -import { CalendarModule } from 'ion2-calendar'; import {CompanyFilterPageModule} from './pages/logged-in/company/company-list/company-filter/company-filter.module'; import { StoreOptionPageModule } from './pages/logged-in/store/store-option/store-option.module'; import { StoreModule } from './components/store/store.module'; @@ -119,7 +118,6 @@ declare global { IonicModule.forRoot(), AppRoutingModule, HttpClientModule, - CalendarModule, BrowserTransferStateModule, IonicStorageModule.forRoot({ name: '__payroll_staff', diff --git a/src/app/components/date-range-refinement-list/date-range-refinement-list.component.ts b/src/app/components/date-range-refinement-list/date-range-refinement-list.component.ts index a88fe9b1..204904e3 100644 --- a/src/app/components/date-range-refinement-list/date-range-refinement-list.component.ts +++ b/src/app/components/date-range-refinement-list/date-range-refinement-list.component.ts @@ -2,9 +2,9 @@ import { Component, Input, Inject, forwardRef, EventEmitter, Output, OnInit, Opt import { connectRange } from 'instantsearch.js/es/connectors'; import { BaseWidget, NgAisIndex, NgAisInstantSearch } from 'angular-instantsearch'; import { parseNumberInput, noop } from 'angular-instantsearch/esm2015/utils'; -import {CalendarModal, CalendarModalOptions, CalendarResult} from 'ion2-calendar'; -import { ModalController } from '@ionic/angular'; +import { AlertController } from '@ionic/angular'; import {Options} from "ng5-slider"; +import { presentDateAlert } from 'src/app/util/date-alert'; @Component({ selector: 'date-range-refinement-list', @@ -51,7 +51,7 @@ export class DateRangeRefinementListComponent extends BaseWidget { public instantSearchInstance, @Optional() public parentIndex: NgAisIndex, - public modalCtrl: ModalController + public alertCtrl: AlertController ) { super('RangeRefinementComponent'); @@ -212,36 +212,17 @@ export class DateRangeRefinementListComponent extends BaseWidget { */ async openCalendarPopup(string) { - const options: CalendarModalOptions = { - canBackwardsSelected: true, - pickMode: 'single', - title: '', - //defaultScrollTo: new Date(this.value.upper), - defaultDateRange: { - from: new Date(this.value.lower), - to: new Date(this.value.upper) - } - }; - - const myCalendar = await this.modalCtrl.create({ - component: CalendarModal, - cssClass: 'modal-calender', - componentProps: { options } - }); - - myCalendar.present(); - - const eventCloseData: any = await myCalendar.onDidDismiss(); - const date = eventCloseData.data; + const defaultDate = string === 'l' ? this.value.lower : this.value.upper; + const date = await presentDateAlert(this.alertCtrl, new Date(defaultDate * 1000)); if (date) { - if (string =='l') { - this.lowerDate = date.string; - this.value.lower = date.unix; - } else if (string =='h') { - this.upperDate = date.string; - this.value.upper = date.unix; + if (string === 'l') { + this.lowerDate = date; + this.value.lower = Math.floor(new Date(date).getTime() / 1000); + } else if (string === 'h') { + this.upperDate = date; + this.value.upper = Math.floor(new Date(date).getTime() / 1000); } //this.value.upper = date.unix + (60*60*24); diff --git a/src/app/pages/logged-in/candidate/candidate-view/candidate-view.page.ts b/src/app/pages/logged-in/candidate/candidate-view/candidate-view.page.ts index 5dcc242f..16372c99 100644 --- a/src/app/pages/logged-in/candidate/candidate-view/candidate-view.page.ts +++ b/src/app/pages/logged-in/candidate/candidate-view/candidate-view.page.ts @@ -11,12 +11,8 @@ import { import { ActivatedRoute, Router } from '@angular/router'; import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { - CalendarModal, - CalendarModalOptions, - CalendarResult, - CalendarComponentOptions -} from 'ion2-calendar'; +import { format, parseISO } from 'date-fns'; +import { presentDateAlert } from 'src/app/util/date-alert'; // models import { Store } from 'src/app/models/store'; @@ -1721,28 +1717,21 @@ export class CandidateViewPage implements OnInit { { text: 'Ok', handler: async (data) => { - const options: CalendarModalOptions = { - title: 'Select Date', - canBackwardsSelected: false, - }; - - const myCalendar = await this.modalCtrl.create({ - component: CalendarModal, - componentProps: { options }, - cssClass: "popup-modal" - }); - - myCalendar.present(); - - const event: any = await myCalendar.onDidDismiss(); - const date: CalendarResult = event.data; + const defaultDate = this.candidate?.candidate_civil_expiry_date + ? new Date(this.candidate.candidate_civil_expiry_date) + : undefined; + const date = await presentDateAlert(this.alertCtrl, defaultDate); if (date) { + const formattedDate = format(parseISO(date), 'yyyy-MM-dd'); const loading = await this.loadingCtrl.create(); loading.present(); - this.candidateService.updateCivilExpiry(date.string, this.candidate_id).subscribe(res => { + this.candidateService.updateCivilExpiry(formattedDate, this.candidate_id).subscribe(res => { if (res.operation == 'success') { - this.candidate.candidate_civil_expiry_date = date.string; + this.candidate.candidate_civil_expiry_date = + res.candidate_civil_expiry_date + || res.data?.candidate_civil_expiry_date + || formattedDate; } this.toastCtrl.create({ message: this.authService.errorMessage(res.message), diff --git a/src/app/pages/logged-in/leave-request/leave-request.page.ts b/src/app/pages/logged-in/leave-request/leave-request.page.ts index 4a702639..4d29c2b6 100644 --- a/src/app/pages/logged-in/leave-request/leave-request.page.ts +++ b/src/app/pages/logged-in/leave-request/leave-request.page.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AlertController, ModalController } from '@ionic/angular'; -import { CalendarModal, CalendarModalOptions } from 'ion2-calendar'; import { AnalyticsService } from 'src/app/providers/analytics.service'; +import { presentDateRangeAlert } from 'src/app/util/date-alert'; //services import { AuthService } from 'src/app/providers/auth.service'; import { DailyStandupService } from 'src/app/providers/logged-in/daily-standup.service'; @@ -103,35 +103,17 @@ export class LeaveRequestPage implements OnInit { // Set it to one month ago fromDate.setMonth(fromDate.getMonth() - 1); - const options: CalendarModalOptions = { - canBackwardsSelected: true, - pickMode: 'range', - title: '', - defaultScrollTo: new Date(), - defaultDateRange: { - from: fromDate, - to: new Date() - } - }; - - const myCalendar = await this.modalCtrl.create({ - component: CalendarModal, - cssClass: 'modal-calender', - componentProps: { options } + const date = await presentDateRangeAlert(this.alertCtrl, { + from: fromDate, + to: new Date() }); - myCalendar.present(); - - const eventCloseData: any = await myCalendar.onDidDismiss(); - - const date = eventCloseData.data; - if (date) { // this.form.value.from_date - this.form.controls['from_date'].setValue(date.from.string); - this.form.controls['to_date'].setValue(date.to.string); + this.form.controls['from_date'].setValue(date.from); + this.form.controls['to_date'].setValue(date.to); - this.range = date.from.string + '-' + date.to.string; + this.range = date.from + '-' + date.to; } } diff --git a/src/app/pages/logged-in/leave/leave-request-form/leave-request-form.page.ts b/src/app/pages/logged-in/leave/leave-request-form/leave-request-form.page.ts index 00df2b86..afc5596d 100644 --- a/src/app/pages/logged-in/leave/leave-request-form/leave-request-form.page.ts +++ b/src/app/pages/logged-in/leave/leave-request-form/leave-request-form.page.ts @@ -3,12 +3,12 @@ import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {DailyStandupService} from "src/app/providers/logged-in/daily-standup.service"; import {AuthService} from "src/app/providers/auth.service"; import {AlertController, ModalController, NavController} from "@ionic/angular"; -import {CalendarModal, CalendarModalOptions} from "ion2-calendar"; import {StaffLeaveService} from "../../../../providers/logged-in/staff-leave.service"; import {AwsService} from "../../../../providers/aws.service"; import {Subscription} from "rxjs"; import {SentryErrorhandlerService} from "../../../../providers/sentry.errorhandler.service"; import { AnalyticsService } from 'src/app/providers/analytics.service'; +import { presentDateRangeAlert } from 'src/app/util/date-alert'; @Component({ selector: 'app-leave-request-form', @@ -140,35 +140,17 @@ export class LeaveRequestFormPage implements OnInit, OnDestroy { // Set it to one month ago fromDate.setMonth(fromDate.getMonth() - 1); - const options: CalendarModalOptions = { - canBackwardsSelected: true, - pickMode: 'range', - title: '', - defaultScrollTo: new Date(), - defaultDateRange: { - from: fromDate, - to: new Date() - } - }; - - const myCalendar = await this.modalCtrl.create({ - component: CalendarModal, - cssClass: 'modal-calender', - componentProps: { options } + const date = await presentDateRangeAlert(this.alertCtrl, { + from: fromDate, + to: new Date() }); - myCalendar.present(); - - const eventCloseData: any = await myCalendar.onDidDismiss(); - - const date = eventCloseData.data; - if (date) { // this.form.value.from_date - this.form.controls['from_date'].setValue(date.from.string); - this.form.controls['to_date'].setValue(date.to.string); + this.form.controls['from_date'].setValue(date.from); + this.form.controls['to_date'].setValue(date.to); - this.range = date.from.string + '-' + date.to.string; + this.range = date.from + '-' + date.to; } } diff --git a/src/app/pages/logged-in/transfer/transfer-form/transfer-form.page.ts b/src/app/pages/logged-in/transfer/transfer-form/transfer-form.page.ts index b2884500..bfeec153 100644 --- a/src/app/pages/logged-in/transfer/transfer-form/transfer-form.page.ts +++ b/src/app/pages/logged-in/transfer/transfer-form/transfer-form.page.ts @@ -11,7 +11,6 @@ import { import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { CustomValidator } from 'src/app/validators/custom.validator'; -import { CalendarModal, CalendarModalOptions } from 'ion2-calendar'; import { Subscription } from 'rxjs'; // models import { Transfer } from 'src/app/models/transfer'; @@ -27,6 +26,7 @@ import { EventService } from '../../../../providers/event.service'; import { TranslateLabelService } from 'src/app/providers/translate-label.service'; import { SentryErrorhandlerService } from 'src/app/providers/sentry.errorhandler.service'; import { AnalyticsService } from 'src/app/providers/analytics.service'; +import { presentDateRangeAlert } from 'src/app/util/date-alert'; @Component({ @@ -59,7 +59,7 @@ export class TransferFormPage implements OnInit { public endDate; // max date public selected; // max date dateRange: { from: string; to: string; }; - type: 'string'; // 'string' | 'js-date' | 'moment' | 'time' | 'object' + type: 'string'; public borderLimit: boolean = false; @@ -544,42 +544,24 @@ export class TransferFormPage implements OnInit { // Set it to one month ago fromDate.setMonth(fromDate.getMonth() - 1); - const options: CalendarModalOptions = { - canBackwardsSelected: true, - pickMode: 'range', - title: '', - defaultScrollTo: new Date(), - defaultDateRange: { - from: fromDate, - to: new Date() - } - }; - - const myCalendar = await this.modalCtrl.create({ - component: CalendarModal, - cssClass: 'modal-calender', - componentProps: { options } + const date = await presentDateRangeAlert(this._alertCtrl, { + from: fromDate, + to: new Date() }); - myCalendar.present(); - - const eventCloseData: any = await myCalendar.onDidDismiss(); - - const date = eventCloseData.data; - if (date) { // this.startDate console.log(this.form) if (this.form && this.form.controls && this.form.controls['start_date']) { - this.form.controls['start_date'].setValue(date.from.string); - this.form.controls['end_date'].setValue(date.to.string); + this.form.controls['start_date'].setValue(date.from); + this.form.controls['end_date'].setValue(date.to); } - this.startDate = date.from.string; - this.endDate = date.to.string; + this.startDate = date.from; + this.endDate = date.to; - this.range = date.from.string + '-' + date.to.string; + this.range = date.from + '-' + date.to; } } diff --git a/src/app/util/date-alert.ts b/src/app/util/date-alert.ts new file mode 100644 index 00000000..d7e037fb --- /dev/null +++ b/src/app/util/date-alert.ts @@ -0,0 +1,86 @@ +import { AlertController } from '@ionic/angular'; +import { format, parseISO } from 'date-fns'; + +export interface DateRangeSelection { + from: string; + to: string; +} + +function parseDateInput(value: Date | string | number): Date { + if (value instanceof Date) { + return value; + } + + return parseISO(value); +} + +function toInputDate(value?: Date | string | number): string { + if (!value) { + return format(new Date(), 'yyyy-MM-dd'); + } + + const date = parseDateInput(value); + + return Number.isNaN(date.getTime()) ? format(new Date(), 'yyyy-MM-dd') : format(date, 'yyyy-MM-dd'); +} + +export async function presentDateRangeAlert( + alertCtrl: AlertController, + defaultRange: { from?: Date | string | number; to?: Date | string | number } = {}, + header = 'Select Date Range' +): Promise { + const alert = await alertCtrl.create({ + header, + inputs: [ + { name: 'from', type: 'date', value: toInputDate(defaultRange.from) }, + { name: 'to', type: 'date', value: toInputDate(defaultRange.to) }, + ], + buttons: [ + { text: 'Cancel', role: 'cancel' }, + { text: 'Ok', role: 'confirm' }, + ], + }); + + await alert.present(); + const result = await alert.onDidDismiss(); + const values = result.data?.values; + + if (result.role !== 'confirm' || !values?.from || !values?.to) { + return null; + } + + const from = toInputDate(values.from); + const to = toInputDate(values.to); + + if (from > to) { + return { from: to, to: from }; + } + + return { + from, + to, + }; +} + +export async function presentDateAlert( + alertCtrl: AlertController, + defaultDate?: Date | string | number, + header = 'Select Date' +): Promise { + const alert = await alertCtrl.create({ + header, + inputs: [ + { name: 'date', type: 'date', value: toInputDate(defaultDate) }, + ], + buttons: [ + { text: 'Cancel', role: 'cancel' }, + { text: 'Ok', role: 'confirm' }, + ], + }); + + await alert.present(); + const result = await alert.onDidDismiss(); + const value = result.data?.values?.date; + + return result.role === 'confirm' && value ? value : null; +} diff --git a/src/app/util/date-fns-shim.ts b/src/app/util/date-fns-shim.ts new file mode 100644 index 00000000..d6de19dd --- /dev/null +++ b/src/app/util/date-fns-shim.ts @@ -0,0 +1,109 @@ +const DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/; + +function toDate(value: Date | number | string): Date { + if (value instanceof Date) { + return value; + } + + if (typeof value === 'string') { + const dateOnly = DATE_ONLY_RE.exec(value); + if (dateOnly) { + const [, year, month, day] = dateOnly; + return new Date(Number(year), Number(month) - 1, Number(day)); + } + } + + return new Date(value); +} + +function pad(value: number, length = 2): string { + return String(value).padStart(length, '0'); +} + +export function parseISO(value: Date | number | string): Date { + return toDate(value); +} + +export function format(value: Date | number | string, pattern: string): string { + const date = toDate(value); + const hours = date.getHours(); + const twelveHour = hours % 12 || 12; + const monthShort = date.toLocaleString('en-US', { month: 'short' }); + const replacements: { [token: string]: string } = { + yyyy: String(date.getFullYear()), + MMM: monthShort, + MM: pad(date.getMonth() + 1), + dd: pad(date.getDate()), + d: String(date.getDate()), + HH: pad(hours), + hh: pad(twelveHour), + mm: pad(date.getMinutes()), + ss: pad(date.getSeconds()), + a: hours >= 12 ? 'PM' : 'AM', + }; + + return pattern.replace(/yyyy|MMM|MM|dd|d|HH|hh|mm|ss|a/g, token => replacements[token]); +} + +export function startOfMonth(value: Date | number | string): Date { + const date = toDate(value); + return new Date(date.getFullYear(), date.getMonth(), 1); +} + +export function endOfMonth(value: Date | number | string): Date { + const date = toDate(value); + return new Date(date.getFullYear(), date.getMonth() + 1, 0); +} + +export function eachDayOfInterval(interval: { start: Date; end: Date }): Date[] { + const days: Date[] = []; + const current = startOfDay(interval.start); + const end = startOfDay(interval.end); + + while (current <= end) { + days.push(new Date(current)); + current.setDate(current.getDate() + 1); + } + + return days; +} + +export function getDate(value: Date | number | string): number { + return toDate(value).getDate(); +} + +export function getMonth(value: Date | number | string): number { + return toDate(value).getMonth(); +} + +export function getYear(value: Date | number | string): number { + return toDate(value).getFullYear(); +} + +export function isSameDay(left: Date | number | string, right: Date | number | string): boolean { + const leftDate = toDate(left); + const rightDate = toDate(right); + return leftDate.getFullYear() === rightDate.getFullYear() + && leftDate.getMonth() === rightDate.getMonth() + && leftDate.getDate() === rightDate.getDate(); +} + +export function isSameMonth(left: Date | number | string, right: Date | number | string): boolean { + const leftDate = toDate(left); + const rightDate = toDate(right); + return leftDate.getFullYear() === rightDate.getFullYear() + && leftDate.getMonth() === rightDate.getMonth(); +} + +export function isSameYear(left: Date | number | string, right: Date | number | string): boolean { + return toDate(left).getFullYear() === toDate(right).getFullYear(); +} + +export function isToday(value: Date | number | string): boolean { + return isSameDay(value, new Date()); +} + +function startOfDay(value: Date | number | string): Date { + const date = toDate(value); + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); +} diff --git a/tsconfig.json b/tsconfig.json index 26160473..702c13da 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,11 @@ "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "node", + "paths": { + "date-fns": [ + "src/app/util/date-fns-shim" + ] + }, "importHelpers": true, "target": "ES2022", "module": "es2020",