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, 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(`${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(`${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(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(selectedDayNames.map(name => this.getDayIndexFromName(name))); const selectedMonthIndices = new Set(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, selectedMonths: Set): 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, selectedMonths: Set): 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); } }