389 lines
13 KiB
TypeScript
389 lines
13 KiB
TypeScript
import { inject } from '@angular/core';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import {Component, Inject, OnInit } from '@angular/core';
|
|
import {FormBuilder, FormGroup} from "@angular/forms";
|
|
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
|
import {HttpClient} from "@angular/common/http";
|
|
import {ToastrService} from "ngx-toastr";
|
|
import {ConfigService} from "@services/config.service";
|
|
|
|
@Component({
|
|
selector: 'app-create-task-schedule',
|
|
templateUrl: './create-task-schedule.component.html',
|
|
styleUrl: './create-task-schedule.component.css'
|
|
})
|
|
export class CreateTaskScheduleComponent implements OnInit{
|
|
public translate = inject(TranslateService);
|
|
form: FormGroup;
|
|
baseUrl: string;
|
|
apiUrl: string;
|
|
recurrenceTypes = [
|
|
{ value: 'none', label : this.translate.instant('withoutRepetition') },
|
|
{ value: 'custom', label : this.translate.instant('customRepeat') }
|
|
];
|
|
weekDays: string[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
|
isSingleDateSelected: boolean = true;
|
|
monthsList: string[] = [
|
|
'january', 'february', 'march', 'april', 'may', 'june',
|
|
'july', 'august', 'september', 'october', 'november', 'december'
|
|
];
|
|
|
|
monthRows: string[][] = [];
|
|
editing: boolean = false;
|
|
selectedMonths: { [key: string]: boolean } = {};
|
|
selectedDays: { [key: string]: boolean } = {};
|
|
nextExecutionDate: Date | null = null;
|
|
executionCount: number = 0;
|
|
|
|
constructor(
|
|
private fb: FormBuilder,
|
|
public dialogRef: MatDialogRef<CreateTaskScheduleComponent>,
|
|
private http: HttpClient,
|
|
private toastr: ToastrService,
|
|
private configService: ConfigService,
|
|
@Inject(MAT_DIALOG_DATA) public data: any
|
|
) {
|
|
|
|
this.baseUrl = this.configService.apiUrl;
|
|
this.apiUrl = `${this.baseUrl}/command-task-schedules`;
|
|
this.form = this.fb.group({
|
|
executionDate: [new Date()],
|
|
executionTime: ['08:00'],
|
|
recurrenceType: ['none'],
|
|
recurrenceDetails: this.fb.group({
|
|
daysOfWeek: [[]],
|
|
months: this.fb.control([]),
|
|
initDate: [null],
|
|
endDate: [null]
|
|
}),
|
|
enabled: [true]
|
|
});
|
|
|
|
if (this.data.schedule) {
|
|
this.editing = true;
|
|
this.loadData();
|
|
}
|
|
|
|
this.form.get('recurrenceType')?.valueChanges.subscribe((value) => {
|
|
if (value === 'none') {
|
|
this.form.get('recurrenceDetails')?.disable();
|
|
} else {
|
|
this.form.get('recurrenceDetails')?.enable();
|
|
}
|
|
});
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.monthRows = [
|
|
this.monthsList.slice(0, 6),
|
|
this.monthsList.slice(6, 12)
|
|
];
|
|
}
|
|
|
|
loadData(): void {
|
|
this.http.get<any>(`${this.baseUrl}${this.data.schedule['@id']}`).subscribe(
|
|
(data) => {
|
|
const formattedExecutionTime = this.formatExecutionTime(data.executionTime);
|
|
|
|
this.form.patchValue({
|
|
executionDate: data.executionDate,
|
|
executionTime: formattedExecutionTime,
|
|
recurrenceType: data.recurrenceType,
|
|
recurrenceDetails: {
|
|
...data.recurrenceDetails,
|
|
initDate: data.recurrenceDetails.initDate || null,
|
|
endDate: data.recurrenceDetails.endDate || null,
|
|
daysOfWeek: data.recurrenceDetails.daysOfWeek || [],
|
|
months: data.recurrenceDetails.months || []
|
|
},
|
|
enabled: data.enabled
|
|
});
|
|
this.selectedDays = data.recurrenceDetails.daysOfWeek.reduce((acc: any, day: string) => {
|
|
acc[day] = true;
|
|
return acc;
|
|
}, {});
|
|
this.selectedMonths = data.recurrenceDetails.months.reduce((acc: any, month: string) => {
|
|
acc[month] = true;
|
|
return acc;
|
|
}, {});
|
|
},
|
|
(error) => {
|
|
console.error(this.translate.instant('errorLoadingScheduleData'), error);
|
|
}
|
|
);
|
|
}
|
|
|
|
formatExecutionTime(time: string | Date): string {
|
|
if (typeof time === 'string') {
|
|
const hhmmMatch = time.match(/^\d{2}:\d{2}/);
|
|
if (hhmmMatch) {
|
|
return hhmmMatch[0];
|
|
}
|
|
|
|
const parsed = new Date(time);
|
|
if (!isNaN(parsed.getTime())) {
|
|
const utcHours = parsed.getUTCHours();
|
|
const utcMinutes = parsed.getUTCMinutes();
|
|
|
|
const currentDate = new Date();
|
|
currentDate.setUTCHours(utcHours, utcMinutes, 0, 0);
|
|
|
|
const hours = String(currentDate.getHours()).padStart(2, '0');
|
|
const minutes = String(currentDate.getMinutes()).padStart(2, '0');
|
|
return `${hours}:${minutes}`;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
if (time instanceof Date && !isNaN(time.getTime())) {
|
|
const hours = String(time.getHours()).padStart(2, '0');
|
|
const minutes = String(time.getMinutes()).padStart(2, '0');
|
|
return `${hours}:${minutes}`;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
convertDateToLocalISO(date: Date): string {
|
|
date = new Date(date);
|
|
const adjustedDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
return adjustedDate.toISOString();
|
|
}
|
|
|
|
convertTimeToUTC(timeString: string): string {
|
|
const today = new Date();
|
|
const [hours, minutes] = timeString.split(':');
|
|
|
|
const localDateTime = new Date(
|
|
today.getFullYear(),
|
|
today.getMonth(),
|
|
today.getDate(),
|
|
parseInt(hours, 10),
|
|
parseInt(minutes, 10),
|
|
0,
|
|
0
|
|
);
|
|
|
|
return localDateTime.toISOString();
|
|
}
|
|
|
|
|
|
onSubmit() {
|
|
const formData = this.form.value;
|
|
|
|
if (formData.recurrenceType === 'custom') {
|
|
const selectedDayNames = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
|
|
const selectedMonthNames = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
|
|
|
|
if (selectedDayNames.length === 0) {
|
|
this.toastr.error(this.translate.instant('youMustSelectAtLeastOne1'));
|
|
return;
|
|
}
|
|
|
|
if (selectedMonthNames.length === 0) {
|
|
this.toastr.error(this.translate.instant('youMustSelectAtLeastOne2'));
|
|
return;
|
|
}
|
|
|
|
const initDate = formData.recurrenceDetails?.initDate;
|
|
const endDate = formData.recurrenceDetails?.endDate;
|
|
|
|
if (!initDate || !endDate) {
|
|
this.toastr.error(this.translate.instant('youMustSpecifyStartDate'));
|
|
return;
|
|
}
|
|
|
|
if (new Date(endDate) <= new Date(initDate)) {
|
|
this.toastr.error(this.translate.instant('theEndDateMust'));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const payload: any = {
|
|
commandTask: this.data.task['@id'],
|
|
executionDate: formData.recurrenceType === 'none' ? this.convertDateToLocalISO(formData.executionDate) : null,
|
|
executionTime: this.convertTimeToUTC(formData.executionTime),
|
|
recurrenceType: formData.recurrenceType,
|
|
recurrenceDetails: {
|
|
...formData.recurrenceDetails,
|
|
initDate: formData.recurrenceDetails?.initDate || null,
|
|
endDate: formData.recurrenceDetails?.endDate || null,
|
|
daysOfWeek: formData.recurrenceDetails?.daysOfWeek || [],
|
|
months: formData.recurrenceDetails?.months || []
|
|
},
|
|
enabled: formData.enabled
|
|
}
|
|
|
|
if (this.editing) {
|
|
const taskId = this.data.task.uuid;
|
|
this.http.patch<any>(`${this.baseUrl}${this.data.schedule['@id']}`, payload).subscribe({
|
|
next: () => {
|
|
this.toastr.success(this.translate.instant('updatedTaskScheduleWith'));
|
|
this.dialogRef.close(true);
|
|
},
|
|
error: () => {
|
|
this.toastr.error(this.translate.instant('errorUpdatingTask'));
|
|
}
|
|
});
|
|
} else {
|
|
this.http.post<any>(this.apiUrl, payload).subscribe({
|
|
next: () => {
|
|
this.toastr.success(this.translate.instant('taskScheduleCreatedWith'));
|
|
this.dialogRef.close(true);
|
|
},
|
|
error: () => {
|
|
this.toastr.error(this.translate.instant('errorCreatingTask'));
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
onCancel(): void {
|
|
this.dialogRef.close(false);
|
|
}
|
|
|
|
get summaryText(): string {
|
|
const recurrence = this.form.get('recurrenceType')?.value;
|
|
const start = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.initDate')?.value;
|
|
const end = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.endDate')?.value;
|
|
const time = this.form.get('executionTime')?.value;
|
|
const days = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
|
|
const months = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
|
|
|
|
this.calculateNextExecutionAndCount();
|
|
|
|
if (recurrence === 'none') {
|
|
return `Esta acción se ejecutará una sola vez el ${ this.formatDate(start)} a las ${time}.`;
|
|
}
|
|
|
|
return `Esta acción se ejecutará todos los ${days.join(', ')} de ${months.join(', ')}, desde el ${this.formatDate(start)} hasta el ${this.formatDate(end)} a las ${time}.`;
|
|
}
|
|
|
|
private calculateNextExecutionAndCount(): void {
|
|
const recurrence = this.form.get('recurrenceType')?.value;
|
|
const time = this.form.get('executionTime')?.value;
|
|
|
|
if (recurrence === 'none') {
|
|
const execDate = this.form.get('executionDate')?.value;
|
|
if (execDate && time) {
|
|
this.nextExecutionDate = new Date(execDate);
|
|
const [hours, minutes] = time.split(':');
|
|
this.nextExecutionDate.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
|
this.executionCount = 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const startDateValue = this.form.get('recurrenceDetails.initDate')?.value;
|
|
const endDateValue = this.form.get('recurrenceDetails.endDate')?.value;
|
|
|
|
const selectedDayNames = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
|
|
const selectedMonthNames = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
|
|
|
|
if (!startDateValue || !endDateValue || selectedDayNames.length === 0 || selectedMonthNames.length === 0) {
|
|
this.nextExecutionDate = null;
|
|
this.executionCount = 0;
|
|
return;
|
|
}
|
|
|
|
const startDate = new Date(startDateValue);
|
|
const endDate = new Date(endDateValue);
|
|
|
|
const selectedDayIndices = new Set<number>(selectedDayNames.map(name => this.getDayIndexFromName(name)));
|
|
const selectedMonthIndices = new Set<number>(selectedMonthNames.map(name => this.getMonthIndexFromName(name)));
|
|
|
|
this.executionCount = this.calculateExecutionCountExact(startDate, endDate, selectedDayIndices, selectedMonthIndices);
|
|
|
|
this.nextExecutionDate = this.findNextExecutionDate(startDate, endDate, time, selectedDayIndices, selectedMonthIndices);
|
|
}
|
|
|
|
private calculateExecutionCountExact(startDate: Date, endDate: Date, selectedDays: Set<number>, selectedMonths: Set<number>): number {
|
|
const cursor = new Date(startDate);
|
|
cursor.setHours(0, 0, 0, 0);
|
|
const end = new Date(endDate);
|
|
end.setHours(23, 59, 59, 999);
|
|
|
|
let count = 0;
|
|
while (cursor <= end) {
|
|
if (selectedMonths.has(cursor.getMonth()) && selectedDays.has(cursor.getDay())) {
|
|
count += 1;
|
|
}
|
|
cursor.setDate(cursor.getDate() + 1);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private findNextExecutionDate(startDate: Date, endDate: Date, time: string, selectedDays: Set<number>, selectedMonths: Set<number>): Date | null {
|
|
const now = new Date();
|
|
|
|
const startCandidate = new Date(Math.max(new Date(startDate).setHours(0, 0, 0, 0), new Date(now).setHours(0, 0, 0, 0)));
|
|
const endLimit = new Date(endDate);
|
|
endLimit.setHours(23, 59, 59, 999);
|
|
|
|
const [hours, minutes] = (time || '00:00').split(':');
|
|
|
|
const candidate = new Date(startCandidate);
|
|
while (candidate <= endLimit) {
|
|
if (selectedMonths.has(candidate.getMonth()) && selectedDays.has(candidate.getDay())) {
|
|
const candidateDateTime = new Date(candidate);
|
|
candidateDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
|
if (candidateDateTime >= now) {
|
|
return candidateDateTime;
|
|
}
|
|
}
|
|
candidate.setDate(candidate.getDate() + 1);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private getDayIndexFromName(dayName: string): number {
|
|
const mapping: { [key: string]: number } = {
|
|
sunday: 0,
|
|
monday: 1,
|
|
tuesday: 2,
|
|
wednesday: 3,
|
|
thursday: 4,
|
|
friday: 5,
|
|
saturday: 6
|
|
};
|
|
return mapping[dayName.toLowerCase()] ?? -1;
|
|
}
|
|
|
|
private getMonthIndexFromName(monthName: string): number {
|
|
const mapping: { [key: string]: number } = {
|
|
january: 0,
|
|
february: 1,
|
|
march: 2,
|
|
april: 3,
|
|
may: 4,
|
|
june: 5,
|
|
july: 6,
|
|
august: 7,
|
|
september: 8,
|
|
october: 9,
|
|
november: 10,
|
|
december: 11
|
|
};
|
|
return mapping[monthName.toLowerCase()] ?? -1;
|
|
}
|
|
|
|
formatDate(date: string | Date): string {
|
|
const realDate = (date instanceof Date) ? date : new Date(date);
|
|
return new Intl.DateTimeFormat('es-ES', { dateStyle: 'long' }).format(realDate);
|
|
}
|
|
|
|
toggleDay(day: string) {
|
|
this.selectedDays[day] = !this.selectedDays[day];
|
|
const days = Object.keys(this.selectedDays).filter(d => this.selectedDays[d]);
|
|
this.form.get('recurrenceDetails.daysOfWeek')?.setValue(days);
|
|
}
|
|
|
|
toggleMonth(month: string) {
|
|
this.selectedMonths[month] = !this.selectedMonths[month];
|
|
const months = Object.keys(this.selectedMonths).filter(m => this.selectedMonths[m]);
|
|
this.form.get('recurrenceDetails.months')?.setValue(months);
|
|
}
|
|
|
|
}
|