diff --git a/package-lock.json b/package-lock.json
index 9add9672..7e2a2b97 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,13 @@
"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==",
+ "license": "MIT",
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/date-fns"
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/date-format": {
@@ -10060,14 +10056,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 +12117,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/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..2e51714d 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
@@ -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'; // 'string' | 'js-date' | 'time' | 'object'
public borderLimit: boolean = false;
diff --git a/src/app/util/calendar-shim.ts b/src/app/util/calendar-shim.ts
new file mode 100644
index 00000000..96d435e4
--- /dev/null
+++ b/src/app/util/calendar-shim.ts
@@ -0,0 +1,180 @@
+import { CommonModule } from '@angular/common';
+import { Component, Input, NgModule } from '@angular/core';
+import { IonicModule, ModalController } from '@ionic/angular';
+import { format } from 'date-fns';
+
+const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
+
+export interface CalendarResult {
+ string: string;
+ unix: number;
+ date: Date;
+}
+
+export interface CalendarModalOptions {
+ canBackwardsSelected?: boolean;
+ defaultDate?: Date | string;
+ defaultDateRange?: {
+ from?: Date | string;
+ to?: Date | string;
+ };
+ defaultScrollTo?: Date | string;
+ pickMode?: 'single' | 'range';
+ title?: string;
+}
+
+export interface CalendarComponentOptions extends CalendarModalOptions {}
+
+@Component({
+ selector: 'ion-calendar-modal',
+ template: `
+
+
+ {{ options?.title || 'Select Date' }}
+
+ Cancel
+
+
+
+
+
+
+
+ From
+
+
+
+
+ To
+
+
+
+
+
+
+
+
+
+
+
+
+ Done
+
+
+
+ `,
+})
+export class CalendarModal {
+ @Input() options: CalendarModalOptions = {};
+
+ singleValue = this.toInputValue(new Date());
+ fromValue = this.toInputValue(new Date());
+ toValue = this.toInputValue(new Date());
+ minDate: string | null = null;
+
+ constructor(private modalCtrl: ModalController) {}
+
+ ngOnInit() {
+ const today = new Date();
+ const range = this.options?.defaultDateRange;
+
+ this.singleValue = this.toInputValue(this.options?.defaultDate || this.options?.defaultScrollTo || today);
+ this.fromValue = this.toInputValue(range?.from || this.options?.defaultScrollTo || today);
+ this.toValue = this.toInputValue(range?.to || this.options?.defaultScrollTo || today);
+
+ if (this.options?.canBackwardsSelected === false) {
+ this.minDate = this.toInputValue(today);
+ }
+ }
+
+ get isRange() {
+ return this.options?.pickMode === 'range';
+ }
+
+ normalizeValue(value: string | string[] | null | undefined) {
+ if (Array.isArray(value)) {
+ return value[0] || this.toInputValue(new Date());
+ }
+
+ return value || this.toInputValue(new Date());
+ }
+
+ updateFrom(event: Event) {
+ this.fromValue = this.normalizeValue((event as CustomEvent).detail?.value);
+ }
+
+ updateTo(event: Event) {
+ this.toValue = this.normalizeValue((event as CustomEvent).detail?.value);
+ }
+
+ updateSingle(event: Event) {
+ this.singleValue = this.normalizeValue((event as CustomEvent).detail?.value);
+ }
+
+ close() {
+ this.modalCtrl.dismiss();
+ }
+
+ confirm() {
+ if (this.isRange) {
+ this.modalCtrl.dismiss({
+ from: this.toResult(this.fromValue),
+ to: this.toResult(this.toValue),
+ });
+ return;
+ }
+
+ this.modalCtrl.dismiss(this.toResult(this.singleValue));
+ }
+
+ private toInputValue(value: Date | string) {
+ return format(this.toDate(value), 'yyyy-MM-dd');
+ }
+
+ private toResult(value: Date | string): CalendarResult {
+ const date = this.toDate(value);
+
+ return {
+ string: format(date, 'yyyy-MM-dd'),
+ unix: Math.floor(date.getTime() / 1000),
+ date,
+ };
+ }
+
+ private toDate(value: Date | string) {
+ if (value instanceof Date) {
+ return value;
+ }
+
+ const dateOnly = DATE_ONLY_PATTERN.exec(value);
+
+ if (!dateOnly) {
+ return new Date(value);
+ }
+
+ const [, year, month, day] = dateOnly;
+ return new Date(Number(year), Number(month) - 1, Number(day));
+ }
+}
+
+@NgModule({
+ declarations: [CalendarModal],
+ imports: [CommonModule, IonicModule],
+ exports: [CalendarModal],
+})
+export class CalendarModule {}
diff --git a/src/app/util/date-fns-shim.ts b/src/app/util/date-fns-shim.ts
new file mode 100644
index 00000000..25899f00
--- /dev/null
+++ b/src/app/util/date-fns-shim.ts
@@ -0,0 +1,97 @@
+const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
+
+export function parseISO(value: string): Date {
+ return parseDate(value);
+}
+
+export function format(value: Date | string | number, pattern: string): string {
+ const date = value instanceof Date ? value : new Date(value);
+ const hours12 = date.getHours() % 12 || 12;
+ const replacements: Record = {
+ yyyy: String(date.getFullYear()),
+ MM: pad(date.getMonth() + 1),
+ MMM: MONTHS[date.getMonth()],
+ dd: pad(date.getDate()),
+ d: String(date.getDate()),
+ HH: pad(date.getHours()),
+ hh: pad(hours12),
+ mm: pad(date.getMinutes()),
+ ss: pad(date.getSeconds()),
+ a: date.getHours() >= 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): Date {
+ const date = toDate(value);
+ return new Date(date.getFullYear(), date.getMonth(), 1);
+}
+
+export function endOfMonth(value: Date | number): 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 = new Date(interval.start);
+
+ while (current <= interval.end) {
+ days.push(new Date(current));
+ current.setDate(current.getDate() + 1);
+ }
+
+ return days;
+}
+
+export function getDate(value: Date | number): number {
+ return toDate(value).getDate();
+}
+
+export function getMonth(value: Date | number): number {
+ return toDate(value).getMonth();
+}
+
+export function getYear(value: Date | number): number {
+ return toDate(value).getFullYear();
+}
+
+export function isSameDay(left: Date | number, right: Date | number): boolean {
+ return format(toDate(left), 'yyyy-MM-dd') === format(toDate(right), 'yyyy-MM-dd');
+}
+
+export function isSameMonth(left: Date | number, right: Date | number): boolean {
+ const leftDate = toDate(left);
+ const rightDate = toDate(right);
+
+ return leftDate.getFullYear() === rightDate.getFullYear() && leftDate.getMonth() === rightDate.getMonth();
+}
+
+export function isSameYear(left: Date | number, right: Date | number): boolean {
+ return toDate(left).getFullYear() === toDate(right).getFullYear();
+}
+
+export function isToday(value: Date | number): boolean {
+ return isSameDay(toDate(value), new Date());
+}
+
+function toDate(value: Date | number): Date {
+ return value instanceof Date ? value : new Date(value);
+}
+
+function parseDate(value: string): Date {
+ const dateOnly = DATE_ONLY_PATTERN.exec(value);
+
+ if (!dateOnly) {
+ return new Date(value);
+ }
+
+ const [, year, month, day] = dateOnly;
+ return new Date(Number(year), Number(month) - 1, Number(day));
+}
+
+function pad(value: number): string {
+ return String(value).padStart(2, '0');
+}
diff --git a/tsconfig.json b/tsconfig.json
index 26160473..8d27fea3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -15,6 +15,14 @@
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
+ "paths": {
+ "date-fns": [
+ "src/app/util/date-fns-shim"
+ ],
+ "ion2-calendar": [
+ "src/app/util/calendar-shim"
+ ]
+ },
"importHelpers": true,
"target": "ES2022",
"module": "es2020",