develop #43

Merged
maranda merged 4 commits from develop into main 2025-09-09 13:25:28 +02:00
6 changed files with 145 additions and 17 deletions

View File

@ -1,4 +1,13 @@
# Changelog
## [0.23.1] - 2025-09-09
### Fixed
- Se ha corregido el formato de la fecha a la hora de cambiar las tareas programadas
---
## [0.23.0] - 2025-09-08
### Added
- Se ha añadido la funcionalidad para crear backups de repositorios GIT.
## [0.22.2] - 2025-09-05
### Improved
- Se ha mejorado la UX en el asistente de ejecurcion de scripts.

View File

@ -28,10 +28,21 @@
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let task">
<ng-container *ngIf="column.columnDef !== 'management'">
<ng-container *ngIf="column.columnDef !== 'management' && column.columnDef !== 'scope' && column.columnDef !== 'organizationalUnit'">
{{ column.cell(task) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'scope'">
<mat-chip [color]="getScopeColor(task.scope)" selected>
{{ getScopeTranslation(task.scope) }}
</mat-chip>
</ng-container>
<ng-container *ngIf="column.columnDef === 'organizationalUnit'">
<span *ngIf="task.scope !== 'clients'">{{ task.organizationalUnit?.name || '' }}</span>
<span *ngIf="task.scope === 'clients'">-</span>
</ng-container>
<ng-container *ngIf="column.columnDef === 'management'">
<button class="action-button schedule-btn" (click)="openShowScheduleDialog(task)">
<mat-icon>schedule</mat-icon>

View File

@ -28,13 +28,14 @@ export class CommandsTaskComponent implements OnInit {
columns = [
{ columnDef: 'id', header: 'ID', cell: (task: any) => task.id },
{ columnDef: 'name', header: 'Nombre de tarea', cell: (task: any) => task.name },
{ columnDef: 'organizationalUnit', header: 'Ámbito', cell: (task: any) => task.scope },
{ columnDef: 'scope', header: 'Ámbito', cell: (task: any) => task.scope },
{ columnDef: 'organizationalUnit', header: 'Unidad Organizativa', cell: (task: any) => task.organizationalUnit?.name || '' },
{ columnDef: 'management', header: 'Gestiones', cell: (task: any) => task.schedules },
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss') },
{ columnDef: 'createdBy', header: 'Creado por', cell: (task: any) => task.createdBy },
];
displayedColumns: string[] = ['id', 'name', 'organizationalUnit', 'management', 'nextExecution', 'createdBy', 'actions'];
displayedColumns: string[] = ['id', 'name', 'scope', 'management', 'nextExecution', 'createdBy', 'actions'];
loading: boolean = false;
private apiUrl: string;
@ -60,6 +61,7 @@ export class CommandsTaskComponent implements OnInit {
(data) => {
this.tasks = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.updateDisplayedColumns();
this.loading = false;
},
(error) => {
@ -69,6 +71,38 @@ export class CommandsTaskComponent implements OnInit {
);
}
updateDisplayedColumns(): void {
const hasNonClientTasks = this.tasks.some(task => task.scope !== 'clients');
if (hasNonClientTasks) {
this.displayedColumns = ['id', 'name', 'scope', 'organizationalUnit', 'management', 'nextExecution', 'createdBy', 'actions'];
} else {
this.displayedColumns = ['id', 'name', 'scope', 'management', 'nextExecution', 'createdBy', 'actions'];
}
}
getScopeTranslation(scope: string): string {
const translations: { [key: string]: string } = {
'organizational-unit': 'Unidad Organizativa',
'classrooms-group': 'Grupo de aulas',
'classroom': 'Aula',
'clients-group': 'Grupo de clientes',
'clients': 'Clientes'
};
return translations[scope] || scope;
}
getScopeColor(scope: string): string {
const colors: { [key: string]: string } = {
'organizational-unit': 'primary',
'classrooms-group': 'accent',
'classroom': 'warn',
'clients-group': 'accent',
'clients': 'primary'
};
return colors[scope] || 'primary';
}
openCreateTaskModal(): void {
this.dialog.open(CreateTaskComponent, {
width: '800px',

View File

@ -116,10 +116,17 @@ export class CreateTaskScheduleComponent implements OnInit{
if (hhmmMatch) {
return hhmmMatch[0];
}
const parsed = new Date(time);
if (!isNaN(parsed.getTime())) {
const hours = String(parsed.getHours()).padStart(2, '0');
const minutes = String(parsed.getMinutes()).padStart(2, '0');
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 '';
@ -140,6 +147,23 @@ export class CreateTaskScheduleComponent implements OnInit{
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;
@ -147,7 +171,7 @@ export class CreateTaskScheduleComponent implements OnInit{
const payload: any = {
commandTask: this.data.task['@id'],
executionDate: formData.recurrenceType === 'none' ? this.convertDateToLocalISO(formData.executionDate) : null,
executionTime: formData.executionTime,
executionTime: this.convertTimeToUTC(formData.executionTime),
recurrenceType: formData.recurrenceType,
recurrenceDetails: {
...formData.recurrenceDetails,
@ -196,7 +220,6 @@ export class CreateTaskScheduleComponent implements OnInit{
const days = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
const months = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
// Calcular próxima ejecución y conteo
this.calculateNextExecutionAndCount();
if (recurrence === 'none') {
@ -239,10 +262,8 @@ export class CreateTaskScheduleComponent implements OnInit{
const selectedDayIndices = new Set<number>(selectedDayNames.map(name => this.getDayIndexFromName(name)));
const selectedMonthIndices = new Set<number>(selectedMonthNames.map(name => this.getMonthIndexFromName(name)));
// Conteo exacto de ejecuciones en el rango [startDate, endDate]
this.executionCount = this.calculateExecutionCountExact(startDate, endDate, selectedDayIndices, selectedMonthIndices);
// Próxima ejecución >= ahora dentro del rango y criterios
this.nextExecutionDate = this.findNextExecutionDate(startDate, endDate, time, selectedDayIndices, selectedMonthIndices);
}

View File

@ -58,7 +58,7 @@
<ng-container *ngIf="column.columnDef === 'executionTime'">
<div class="time-display">
<mat-icon class="time-icon">schedule</mat-icon>
<span class="time-value">{{ schedule.executionTime | date: 'HH:mm' }}</span>
<span class="time-value">{{ column.cell(schedule) }}</span>
</div>
</ng-container>

View File

@ -28,7 +28,7 @@ export class ShowTaskScheduleComponent implements OnInit{
columns = [
{ columnDef: 'id', header: 'ID', cell: (schedule: any) => schedule.id },
{ columnDef: 'recurrenceType', header: 'Tipo de Programación', cell: (schedule: any) => this.getRecurrenceTypeDisplay(schedule) },
{ columnDef: 'executionTime', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm') },
{ columnDef: 'executionTime', header: 'Hora de ejecución', cell: (schedule: any) => this.formatExecutionTimeForDisplay(schedule.executionTime) },
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (schedule: any) => this.calculateNextExecution(schedule) },
{ columnDef: 'daysOfWeek', header: 'Días de la semana', cell: (schedule: any) => this.formatDaysOfWeek(schedule.recurrenceDetails?.daysOfWeek) },
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => this.formatMonths(schedule.recurrenceDetails?.months) },
@ -123,13 +123,22 @@ export class ShowTaskScheduleComponent implements OnInit{
if (schedule.recurrenceType === 'none') {
if (schedule.executionDate && schedule.executionTime) {
const executionDate = new Date(schedule.executionDate);
const [hours, minutes] = this.parseExecutionTime(schedule.executionTime);
const executionTimeDate = new Date(schedule.executionTime);
const utcHours = executionTimeDate.getUTCHours();
const utcMinutes = executionTimeDate.getUTCMinutes();
const tempDate = new Date();
tempDate.setUTCHours(utcHours, utcMinutes, 0, 0);
const localHours = tempDate.getHours();
const localMinutes = tempDate.getMinutes();
const fullExecutionDateTime = new Date(
executionDate.getFullYear(),
executionDate.getMonth(),
executionDate.getDate(),
hours,
minutes,
localHours,
localMinutes,
0,
0
);
@ -144,7 +153,6 @@ export class ShowTaskScheduleComponent implements OnInit{
return 'Sin fecha/hora definida';
}
// Personalizada (u otros tipos con detalles): calcular siguiente ejecución real
if (schedule.recurrenceDetails) {
const startDate = schedule.recurrenceDetails.initDate ? new Date(schedule.recurrenceDetails.initDate) : null;
const endDate = schedule.recurrenceDetails.endDate ? new Date(schedule.recurrenceDetails.endDate) : null;
@ -157,7 +165,15 @@ export class ShowTaskScheduleComponent implements OnInit{
const selectedDayIndices = new Set<number>(days.map((d: string) => this.getDayIndexFromName(d)));
const selectedMonthIndices = new Set<number>(months.map((m: string) => this.getMonthIndexFromName(m)));
const [hours, minutes] = this.parseExecutionTime(schedule.executionTime);
const executionTimeDate = new Date(schedule.executionTime);
const utcHours = executionTimeDate.getUTCHours();
const utcMinutes = executionTimeDate.getUTCMinutes();
const tempDate = new Date();
tempDate.setUTCHours(utcHours, utcMinutes, 0, 0);
const hours = tempDate.getHours();
const minutes = tempDate.getMinutes();
const next = this.findNextExecutionDate(startDate, endDate, hours, minutes, selectedDayIndices, selectedMonthIndices);
if (next) {
@ -172,6 +188,37 @@ export class ShowTaskScheduleComponent implements OnInit{
}
}
formatExecutionTimeForDisplay(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 '';
}
private parseExecutionTime(executionTime: any): [number, number] {
if (typeof executionTime === 'string') {
const match = executionTime.match(/^(\d{2}):(\d{2})/);
@ -180,7 +227,13 @@ export class ShowTaskScheduleComponent implements OnInit{
}
const parsed = new Date(executionTime);
if (!isNaN(parsed.getTime())) {
return [parsed.getHours(), parsed.getMinutes()];
const utcHours = parsed.getUTCHours();
const utcMinutes = parsed.getUTCMinutes();
const currentDate = new Date();
currentDate.setUTCHours(utcHours, utcMinutes, 0, 0);
return [currentDate.getHours(), currentDate.getMinutes()];
}
return [0, 0];
}