Crear un calendario con Angular 18

Publicado el 13.10.2024 a las 22:41

Crear un calendario con Angular 18

  1. Código HTML

  2. Código CSS

  3. Código TypeScript

Crear un calendario con Angular 18

Si quieres que algo salga bien, hazlo tú mismo.

En varias de mis aplicaciones uso calendarios.


Unas veces grandes, otras veces desplegables, otras con colorines...


Para ello usaba librerías de terceros.


Algunas de estas libererías tienen muchísimas más características de las que necesito, otras no son todo lo personalizable que quiero y otras no cuentan con alguna funcionalidad que necesito.


Por eso, siempre que puedo me gusta programarme todo desde cero.


No me mal interpretes, no soy de reinventar la rueda, pero cuando lo programo desde cero tengo todo el control.


Si te soy sincero... creo que he perdido más tiempo buscando un calendario que hiciera lo que necesito que lo que me ha llevado programarlo desde cero.


A continuación puedes probar el componente que he programado con Angular 18.


Octubre - 2024
L
M
X
J
V
S
D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Es posible que no lo veas ni atractivo ni funcional, pero lo tengo integrado en aplicaciones alimentando al calendario desde una hoja de cálcula de Google Sheet de manera que puedo marcar días con colores que signifiquen disponible, alta ocupación...


En fin, el límite es tu imaginación.


No me enrrollo más y te dejo el código.

Código HTML de mi calendario para Angular 18

  <div class="calendar">
  @if(googleSheetData.length>0){
  <div class="header">
    <button
      (click)="previousMonth()"
      [ngClass]="{
        active: !isCurrentMonthYear(),
        inactive: isCurrentMonthYear()
      }"
    >
      &lt;
    </button>
    <span>{{ monthsName[myCalendarMonth] }} - {{ myCalendarYear }}</span>
    <button (click)="nextMonth()">&gt;</button>
  </div>
  <div class="days-of-week">
    @for (day of daysOfWeek; track $index) {
    <div class="day empty">
      {{ day }}
    </div>
    }
  </div>
  <div class="weeks">
    @for (week of weeks; track $index) { @for ( day of week; track $index) {
    <div
      class="day"
      [ngClass]="{
        empty: !day,
        yellow: day?.status === 'yellow',
        red: day?.status === 'red',
      }"
    >
      {{ day?.day || "" }}
    </div>
    } }
  </div>
  }@else{
  <app-loading></app-loading>
  }
</div>

Código CSS de mi calendario para Angular 18

button{
  width: 48px;
}
.calendar {
  border: 1px solid rgb(204, 204, 204);
  padding: 16px;
  font-family: Arial, sans-serif;
}

.active{
  background-color: black;
}

.inactive{
  background-color: lightgray;
  border: lightgray;
}


.header {
  font-size: 1.2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.days-of-week,
.weeks {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin-bottom: 4px;
  gap: 4px;
}

.days-of-week div,
.day {
  text-align: center;
  padding: 8px;
  border: 1px solid rgb(208, 208, 208);
}

.day.empty {
  background-color: #f9f9f9;
}

.day.yellow {
  color: #856404;
  background-color: #fff3cd;
  border: solid 1px #f3be1f;
}

.day.red {
  color: #721c24;
  background-color: #f8d7da;
  border: solid 1px #f5b5bb;
}

Código TypeScript de mi calendario para Angular 18

 myCalendarYear: number;
  myCalendarMonth: number;
  daysOfWeek: string[] = ['L', 'M', 'X', 'J', 'V', 'S', 'D'];
  monthsName: string[] = [
    'Enero',
    'Febrero',
    'Marzo',
    'Abril',
    'Mayo',
    'Junio',
    'Julio',
    'Agosto',
    'Septiembre',
    'Octubre',
    'Noviembre',
    'Diciembre',
  ];
  weeks: Array<Array<CalendarDay | null>> = [];

  private sheetService = inject(GoogleSheetDataService);
  public dataSubscription!: Subscription;
  public googleSheetData: SheetCalendar[] = [];

  constructor() {
    const today = new Date();
    this.myCalendarYear = today.getFullYear();
    this.myCalendarMonth = today.getMonth();
  }

  ngOnInit(): void {
    this.dataSubscription = this.sheetService
      .getCalendar()
      .subscribe((data) => {
        this.googleSheetData = data;
        this.generateCalendar(this.myCalendarYear, this.myCalendarMonth);
      });
  }

  generateCalendar(year: number, month: number): void {
    this.weeks = [];
    const firstDay = (new Date(year, month, 1).getDay() + 6) % 7; // Ajustar el primer día de la semana a lunes
    const daysInMonth = new Date(year, month + 1, 0).getDate();

    let day = 1;
    for (let i = 0; i < 6; i++) {
      const week: Array<CalendarDay | null> = [];
      for (let j = 0; j < 7; j++) {
        if ((i === 0 && j < firstDay) || day > daysInMonth) {
          week.push(null);
        } else {
          week.push({ day, status: this.getColorDay(day) });
          day++;
        }
      }
      this.weeks.push(week);
    }
  }

  previousMonth(): void {
    //si está en el mes y año actuales, no hace nada
    if (this.isCurrentMonthYear()) return;    
    if (this.myCalendarMonth === 0) {
      this.myCalendarYear--;
      this.myCalendarMonth = 11;
    } else {
      this.myCalendarMonth--;
    }
    this.generateCalendar(this.myCalendarYear, this.myCalendarMonth);
  }

  nextMonth(): void {
    if (this.myCalendarMonth === 11) {
      this.myCalendarYear++;
      this.myCalendarMonth = 0;
    } else {
      this.myCalendarMonth++;
    }
    this.generateCalendar(this.myCalendarYear, this.myCalendarMonth);
  }

  getColorDay(day: number): 'red' | 'yellow' | 'none' {
    const searchDay = day.toString().padStart(2, '0');
    const searchMonth = String(this.myCalendarMonth + 1).padStart(2, '0');
    const searchYear = this.myCalendarYear.toString().slice(2, 4);
    const searhDate = '$searchDay}.$searchMonth}.$searchYear}';
    const dateFound = this.googleSheetData.find(
      (data) => data.date === searhDate
    );
    if (!dateFound) return 'none';

    switch (dateFound.status) {
      case 'red':
        return 'red';
      case 'yellow':
        return 'yellow';
      default:
        return 'none';
    }
  }

  ngOnDestroy() {
    if (this.dataSubscription) this.dataSubscription.unsubscribe();
  }

  public isCurrentMonthYear(): boolean {
    const today = new Date();
    return (
      this.myCalendarMonth === today.getMonth() &&
      this.myCalendarYear === today.getFullYear()
    );
  }
}

interface CalendarDay {
  day: number;
  status: 'red' | 'yellow' | 'none';
}

export interface SheetCalendar {
  date: string;
  status: string;
}

Hasta luego 🖖