깜놀하는 해므찌로

Angular input-date component 활용 예시 본문

IT

Angular input-date component 활용 예시

agnusdei1207 2023. 6. 24. 15:13
반응형
SMALL
<div
  class="relative flex w-full"
  [class]="class"
  (outside)="isOpen = false"
  appClickOutside
>
  <div
    class="flex w-full items-center shadow-sm border-2 px-3 py-2 border-gray-200 hover:border-gray-400 focus-within:border-gray-400 focus-within:border-2 rounded-md cursor-pointer"
    (click)="isOpen = !isOpen"
  >
    <input
      class="text-sm whitespace-nowrap"
      (keyup)="keyup($event)"
      [value]="(value | date : 'yyyy-MM-dd') ?? label"
      class="w-full text-sm transition-all outline-none"
      readonly
    />
    <app-icon name="material-symbols:calendar-today-outline" class="w-5 h-5" />
  </div>
  <app-date-picker
    *ngIf="isOpen"
    class="absolute left-0 z-50 top-[calc(100%+0.25rem)]"
    [ngModel]="value"
    (selected)="isOpen = false"
    (selectedDate)="writeValue($event)"
  ></app-date-picker>
</div>
import { CommonModule } from '@angular/common';
import {
  AfterViewInit,
  Component,
  ElementRef,
  EventEmitter,
  Input,
  Optional,
  Output,
  Self,
  ViewChild,
} from '@angular/core';
import { FormsModule, NgControl } from '@angular/forms';
import * as dayjs from 'dayjs';
import { debounceTime, fromEvent } from 'rxjs';
import { CustomValueAccessor } from 'src/app/components/custom-value-accessor';
import { DatePickerComponent } from 'src/app/components/date-picker/date-picker.component';
import { ClickOutsideDirective } from '../derectives/directives/click-outside.directive';
import { IconComponent } from '../icon/icon.component';

@Component({
  selector: 'app-input-date',
  templateUrl: './input-date.component.html',
  styleUrls: ['./input-date.component.scss'],
  standalone: true,
  imports: [
    DatePickerComponent,
    CommonModule,
    IconComponent,
    FormsModule,
    ClickOutsideDirective,
  ],
})
export class InputDateComponent
  extends CustomValueAccessor<Date>
  implements AfterViewInit
{
  @ViewChild('input') input: ElementRef<HTMLInputElement> | undefined;
  @Output() selectedDate: EventEmitter<Date> = new EventEmitter<Date>();
  @Input() label?: string;
  @Input() type?: 'text' | 'password' = 'text';
  @Input() maxlength?: string | number = 0;
  @Input() override required: boolean = false;
  @Input() class!: string;

  isOpen: boolean = false;

  constructor(
    @Self() @Optional() private ngControl: NgControl,
    private outArea: ElementRef<HTMLDivElement>
  ) {
    super();
    if (this.ngControl) this.ngControl.valueAccessor = this;
  }

  ngOnInit() {}

  ngAfterViewInit(): void {
    this.change();
  }

  change() {
    if (this.input) {
      this.input.nativeElement.value = dayjs(this.value).format('YYYY-MM-DD');
    }
  }

  keyup(ev: any) {
    if (this.input) {
      fromEvent(this.input.nativeElement, 'keyup')
        .pipe(debounceTime(1000))
        .subscribe({
          next: () => this.writeValue(dayjs(ev.target.value).toDate()),
        });
    }
  }
}

1. input-date 템플릿과 컴포넌트

 

 

 

 

<div
  id="date-range"
  class="fade-in z-[100] flex w-max flex-col gap-2.5 rounded border-2 border-primary-600 bg-white p-3 text-sm shadow-md"
>
  <div class="flex items-center justify-between text-gray-500 select-none">
    <div
      class="flex items-center p-2 rounded cursor-pointer hover:bg-gray-100"
      (click)="prevYear()"
    >
      <app-icon class="w-3 h-3" name="heroicons:chevron-double-left"></app-icon>
    </div>
    <div
      class="flex items-center p-2 rounded cursor-pointer hover:bg-gray-100"
      (click)="prevMonth()"
    >
      <app-icon class="w-3 h-3" name="heroicons:chevron-left"></app-icon>
    </div>
    <div class="text-gray-600">{{ selectedMonth.format("YYYY년 MM월") }}</div>
    <div
      class="flex items-center p-2 rounded cursor-pointer hover:bg-gray-100"
      (click)="nextMonth()"
    >
      <app-icon class="w-3 h-3" name="heroicons:chevron-right"></app-icon>
    </div>
    <div
      class="flex items-center p-2 rounded cursor-pointer hover:bg-gray-100"
      (click)="nextYear()"
    >
      <app-icon
        class="w-3 h-3"
        name="heroicons:chevron-double-right"
      ></app-icon>
    </div>
  </div>
  <div class="w-full h-px bg-gray-100"></div>
  <div class="flex items-center justify-between w-full px-3 py-1 pb-0">
    <div class="text-danger">일</div>
    <div>월</div>
    <div>화</div>
    <div>수</div>
    <div>목</div>
    <div>금</div>
    <div class="text-primary">토</div>
  </div>
  <div class="grid items-center grid-cols-7 pb-0">
    <ng-container *ngFor="let day of days">
      <div
        class="min-w-max cursor-pointer rounded p-2.5 py-2"
        [ngClass]="setStyle(day)"
        (click)="handleSelectDate(day)"
      >
        {{ day.format("DD") }}
      </div>
    </ng-container>
  </div>
</div>
import {
  Component,
  EventEmitter,
  Input,
  OnInit,
  Optional,
  Output,
  Self,
} from '@angular/core';
import { NgControl } from '@angular/forms';
import * as dayjs from 'dayjs';
import { BehaviorSubject } from 'rxjs';
import { CustomValueAccessor } from 'src/app/components/custom-value-accessor';
import { IconComponent } from '../icon/icon.component';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-date-picker',
  templateUrl: './date-picker.component.html',
  styleUrls: ['./date-picker.component.scss'],
  standalone: true,
  imports: [IconComponent, CommonModule],
})
export class DatePickerComponent
  extends CustomValueAccessor<Date>
  implements OnInit
{
  @Input() keyupDate: BehaviorSubject<Date> = new BehaviorSubject(new Date());
  @Output() selected: EventEmitter<void> = new EventEmitter();
  @Output() selectedDate: EventEmitter<Date> = new EventEmitter<Date>();

  selectedMonth: dayjs.Dayjs = dayjs();
  days: dayjs.Dayjs[] = [];

  constructor(@Self() @Optional() private ngControl: NgControl) {
    super();
    if (this.ngControl) this.ngControl.valueAccessor = this;
  }

  ngOnInit() {
    this.setDays();

    this.keyupDate.subscribe((res: Date) => {
      this.value = dayjs(res).toDate();
      this.selectedMonth = dayjs(res);
      this.setDays();
    });
  }

  prevYear() {
    this.selectedMonth = dayjs(this.selectedMonth).subtract(1, 'year');
    this.setDays();
  }

  prevMonth() {
    this.selectedMonth = dayjs(this.selectedMonth).subtract(1, 'month');
    this.setDays();
  }

  nextYear() {
    this.selectedMonth = dayjs(this.selectedMonth).add(1, 'year');
    this.setDays();
  }

  nextMonth() {
    this.selectedMonth = dayjs(this.selectedMonth).add(1, 'month');
    this.setDays();
  }

  setDays() {
    this.days = [];
    const firstDay = this.selectedMonth.startOf('month');
    const lastDay = dayjs(this.selectedMonth).daysInMonth();

    for (let i = 0; i < firstDay.day(); i++) {
      this.days.push(
        this.selectedMonth.startOf('month').subtract(i + 1, 'day')
      );
    }
    this.days.reverse();

    for (let i = 0; i < lastDay; i++) {
      this.days.push(this.selectedMonth.startOf('month').add(i, 'day'));
    }

    for (
      let i = 0;
      i < 6 - dayjs(this.selectedMonth).endOf('month').day();
      i++
    ) {
      this.days.push(this.selectedMonth.endOf('month').add(i + 1, 'day'));
    }
  }

  setStyle = (day: any) => {
    let style = '';

    if (day.format('YY-MM-DD') === dayjs(this.value).format('YY-MM-DD')) {
      style += 'bg-primary text-white';
    } else if (dayjs(this.selectedMonth).month() !== day.month()) {
      style += 'hover:bg-gray-100 text-gray-400';
    } else if (day.day() === 0) {
      style += 'hover:bg-gray-100 text-danger';
    } else if (day.day() === 6) {
      style += 'hover:bg-gray-100 text-primary';
    } else {
      style += 'bg-white hover:bg-gray-100 text-gray-500';
    }

    return style;
  };

  handleSelectDate = (day: any) => {
    if (dayjs(this.selectedMonth).month() > day.month()) {
      this.prevMonth();
    } else if (dayjs(this.selectedMonth).month() < day.month()) {
      this.nextMonth();
    }

    this.writeValue(dayjs(day).toDate());
    this.selectedDate.emit(dayjs(day).toDate());
    this.selected.emit();
  };
}

2. datepiker 템플릿과 컴포넌트

 

 

 

 

 

 

<app-input-date
    class="flex-1"
    label="시작일"
    [(ngModel)]="startAt"
    (ngModelChange)="searchCondition()"
  />

 

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
  import * as dayjs from 'dayjs';

  imports: [
    FormsModule,
    ReactiveFormsModule,
  ],
  
  
  startAt: Date = MONTH_BEFORE;
  endAt: Date = TODAY;
  


export const TODAY = new Date();
export const MONTH_BEFORE = dayjs().subtract(1, 'month').toDate();

3. input-date 컴포넌트를 가져와서 사용하는 예시

반응형
LIST