Consolidated commit message to v22 signals

This commit is contained in:
2026-08-25 14:45:09 +00:00
parent 6403d25d3e
commit df289b20b8
443 changed files with 16058 additions and 17332 deletions
@@ -1,38 +1,50 @@
import { AsyncPipe } from '@angular/common';
import { AfterViewInit, Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import {
computed,
Component,
HostListener,
inject,
debounced,
input,
linkedSignal,
afterNextRender,
} from '@angular/core';
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatOptionModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialog } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import moment from 'moment';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { AttendanceType } from '../attendance/attendance-type';
import { AuthService } from '../auth/auth.service';
import { AttendanceTypeService } from '../attendance/attendance-type.service';
import { Employee } from '../employee/employee';
import { EmployeeService } from '../employee/employee.service';
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
import { EmployeeAttendance } from './employee-attendance';
import { EmployeeAttendanceDataSource } from './employee-attendance-datasource';
import { EmployeeAttendanceItem } from './employee-attendance-item';
import { EmployeeAttendanceService } from './employee-attendance.service';
export interface EmployeeAttendanceFormData {
startDate: Date;
finishDate: Date;
employee: string | Employee | null;
attendances: { attendanceType: number | null }[];
}
@Component({
selector: 'app-employee-attendance',
templateUrl: './employee-attendance.component.html',
styleUrls: ['./employee-attendance.component.css'],
imports: [
ReactiveFormsModule,
FormField,
FormRoot,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule,
@@ -43,115 +55,89 @@ import { EmployeeAttendanceService } from './employee-attendance.service';
MatSelectModule,
MatIconModule,
MatChipsModule,
AsyncPipe,
SkeletonLoaderComponent,
ErrorStateComponent,
],
})
export class EmployeeAttendanceComponent implements OnInit, AfterViewInit {
private route = inject(ActivatedRoute);
export class EmployeeAttendanceComponent {
private router = inject(Router);
private dialog = inject(MatDialog);
private snackBar = inject(MatSnackBar);
private auth = inject(AuthService);
private ser = inject(EmployeeAttendanceService);
private employeeSer = inject(EmployeeService);
private attendanceTypeSer = inject(AttendanceTypeService);
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
startDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
finishDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
infoResource = this.ser.get(this.id, this.startDate, this.finishDate);
info = computed(() => this.infoResource.value() ?? new EmployeeAttendance());
attendanceTypesResource = this.attendanceTypeSer.list();
attendanceTypes = computed(() => this.attendanceTypesResource.value() ?? []);
@ViewChild('employeeElement', { static: true }) employeeElement!: ElementRef<HTMLInputElement>;
@ViewChild('startDateElement', { static: true }) startDate!: ElementRef<HTMLInputElement>;
public employeeAttendanceObservable = new BehaviorSubject<EmployeeAttendanceItem[]>([]);
dataSource: EmployeeAttendanceDataSource = new EmployeeAttendanceDataSource(this.employeeAttendanceObservable);
model = linkedSignal({
source: this.info,
computation: (info): EmployeeAttendanceFormData => ({
startDate: info.startDate ? moment(info.startDate, 'DD-MMM-YYYY').toDate() : new Date(),
finishDate: info.finishDate ? moment(info.finishDate, 'DD-MMM-YYYY').toDate() : new Date(),
employee: info.employee ?? null,
attendances: info.body.map((x) => ({
attendanceType: x.attendanceType.id,
})),
}),
});
form: FormGroup<{
startDate: FormControl<moment.Moment>;
finishDate: FormControl<moment.Moment>;
employee: FormControl<string | null>;
attendances: FormArray<
FormGroup<{
attendanceType: FormControl<number>;
}>
>;
}>;
form = createForm(this.model);
info: EmployeeAttendance = new EmployeeAttendance();
attendanceTypes: AttendanceType[] = [];
dataSource = computed(() => this.info().body);
displayedColumns = ['date', 'status', 'prints'];
employees: Observable<Employee[]>;
employeesSearch = computed(() => {
const x = this.model().employee;
return typeof x === 'string' ? x : null;
});
debouncedEmployeeSearch = debounced(this.employeesSearch, 150);
employeesResource = this.employeeSer.autocomplete(computed(() => this.debouncedEmployeeSearch.value() ?? ''));
employees = computed(() => this.employeesResource.value() ?? []);
@HostListener('window:keydown.f2', ['$event'])
focusDate(event: Event) {
event.preventDefault();
this.startDate.nativeElement.focus();
this.startDate.nativeElement.select();
this.form().focusBoundControl();
}
constructor() {
this.form = new FormGroup({
startDate: new FormControl(moment(new Date()), { nonNullable: true }),
finishDate: new FormControl(moment(new Date()), { nonNullable: true }),
employee: new FormControl<string | null>(null),
attendances: new FormArray<FormGroup<{ attendanceType: FormControl<number> }>>([]),
afterNextRender(() => {
this.form().focusBoundControl();
});
// Listen to Employee Value Changes
this.employees = this.form.controls.employee.valueChanges.pipe(
debounceTime(150),
distinctUntilChanged(),
switchMap((x) => (x === null ? observableOf([]) : this.employeeSer.autocomplete(x))),
);
}
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as { info: EmployeeAttendance; attendanceTypes: AttendanceType[] };
this.info = data.info;
this.attendanceTypes = data.attendanceTypes;
this.form.controls.startDate.setValue(moment(this.info.startDate, 'DD-MMM-YYYY'));
this.form.controls.finishDate.setValue(moment(this.info.finishDate, 'DD-MMM-YYYY'));
this.form.controls.employee.setValue(this.info.employee?.name ?? '');
this.form.controls.attendances.clear();
this.info.body.forEach((x) =>
this.form.controls.attendances.push(
new FormGroup({
attendanceType: new FormControl(x.attendanceType.id, { nonNullable: true }),
}),
),
);
this.dataSource = new EmployeeAttendanceDataSource(this.employeeAttendanceObservable);
this.employeeAttendanceObservable.next(this.info.body);
});
}
ngAfterViewInit() {
setTimeout(() => {
this.employeeElement.nativeElement.focus();
}, 0);
}
displayFn(employee?: Employee | string): string {
return !employee ? '' : typeof employee === 'string' ? employee : employee.name;
}
selected(event: MatAutocompleteSelectedEvent): void {
this.info.employee = event.option.value as Employee;
}
getClass(index: number) {
const array = this.form.controls.attendances;
const id = array.controls[index].value.attendanceType;
const { name } = this.attendanceTypes.filter((x) => x.id === id)[0];
return name.toLowerCase().replace(/(\s+\+\s+)|(\s+)/g, '-');
const array = this.model().attendances;
if (!array || !array[index]) return '';
const id = array[index].attendanceType;
const typeObj = this.attendanceTypes().filter((x) => x.id === id)[0];
if (!typeObj) return '';
return typeObj.name.toLowerCase().replace(/(\s+\+\s+)|(\s+)/g, '-');
}
show() {
const formValue = this.form.value;
this.info.startDate = moment(formValue.startDate).format('DD-MMM-YYYY');
this.info.finishDate = moment(formValue.finishDate).format('DD-MMM-YYYY');
if (this.info.employee) {
this.router.navigate(['/employee-attendance', this.info.employee.id], {
const formValue = this.model();
const currentInfo = this.info();
currentInfo.startDate = moment(formValue.startDate).format('DD-MMM-YYYY');
currentInfo.finishDate = moment(formValue.finishDate).format('DD-MMM-YYYY');
currentInfo.employee =
typeof formValue.employee !== 'string' && formValue.employee ? formValue.employee : undefined;
if (currentInfo.employee) {
this.router.navigate(['/employee-attendance', currentInfo.employee.id], {
queryParams: {
startDate: this.info.startDate,
finishDate: this.info.finishDate,
startDate: currentInfo.startDate,
finishDate: currentInfo.finishDate,
},
});
}
@@ -163,21 +149,26 @@ export class EmployeeAttendanceComponent implements OnInit, AfterViewInit {
this.snackBar.open('', 'Success');
},
error: (error) => {
this.snackBar.open(error, 'Danger');
this.snackBar.open(error as string, 'Danger');
},
});
}
getEmployeeAttendance(): EmployeeAttendance {
const formValue = this.form.value;
this.info.startDate = moment(formValue.startDate).format('DD-MMM-YYYY');
this.info.finishDate = moment(formValue.finishDate).format('DD-MMM-YYYY');
const array = this.form.value.attendances;
const formValue = this.model();
const currentInfo = this.info();
currentInfo.startDate = moment(formValue.startDate).format('DD-MMM-YYYY');
currentInfo.finishDate = moment(formValue.finishDate).format('DD-MMM-YYYY');
currentInfo.employee =
typeof formValue.employee !== 'string' && formValue.employee ? formValue.employee : undefined;
const array = formValue.attendances;
if (array) {
this.info.body.forEach((item, index) => {
item.attendanceType.id = array[index].attendanceType ?? 0;
currentInfo.body.forEach((item, index) => {
if (array[index]) {
item.attendanceType.id = array[index].attendanceType ?? 0;
}
});
}
return this.info;
return currentInfo;
}
}