이벤트 추가하기
지난 게시글에서 이벤트 추가 Dialog를 생성하였다.
오늘은 Dialog를 통해 이벤트를 추가하면 해당 유저의 Event Table에 데이터가 삽입되고 캘린더에 추가된 이벤트가 보이도록 해볼 것이다.
Spring Boot
Event Entity
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Event extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "event_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
private LocalDate startDate;
private LocalDate endDate;
private LocalTime startTime;
private LocalTime endTime;
private String title;
private String content;
// 연관관계 편의 메서드
public void setMember(Member member){
this.member = member;
member.getEvents().add(this);
}
}
우선 이벤트 엔티티를 추가해준다.
member와 다대일 연관관계를 맺고 일정에 필요한 필드들을 가지고 있다.
Date와 Time을 분리한 이유는 일정에서 날짜만 정해지고 시간이 정해지지 않는 경우가 있기 때문이다.
Event Repository, Service
public interface EventRepository extends JpaRepository<Event, Long> {
}
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class EventService {
private final EventRepository eventRepository;
private final MemberRepository memberRepository;
@Transactional
public EventResponseDto save(EventSaveRequestDto dto, String email) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException("회원이 존재하지 않습니다."));
Event event = dto.toEntity(member);
eventRepository.save(event);
return new EventResponseDto(event);
}
}
EventRepository는 기본 Spring Data JPA가 지원해주는 메서드들을 사용할 것이다.
EventService의 save에서 EventSaveRequestDto, email 매개변수를 통해 event를 생성한 후 event를 저장한다.
그 후 EventResponseDto로 변환하여 return 해준다.
@NoArgsConstructor
@Getter
public class EventSaveRequestDto {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate startDate;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate endDate;
@DateTimeFormat(pattern = "HH:mm")
private LocalTime startTime;
@DateTimeFormat(pattern = "HH:mm")
private LocalTime endTime;
private String title;
private String content;
public Event toEntity(Member member){
Event event = Event.builder()
.startDate(startDate)
.endDate(endDate)
.startTime(startTime)
.endTime(endTime)
.title(title)
.content(content)
.build();
event.setMember(member);
return event;
}
}
EventSaveRequestDto를 보면 @DateTimeFormat으로 직렬화된 JSON 데이터들을 LocalDate, LocalTime으로 변환할 수 있다.
EventApiController
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/events")
public class EventApiController {
private final EventService eventService;
@PostMapping
public ResponseEntity saveEvent(@RequestBody EventSaveRequestDto dto,
@TokenMemberEmail String email){
EventResponseDto saveDto = eventService.save(dto, email);
return ResponseEntity.ok(saveDto);
}
}
EventApiController에서 클라이언트의 요청을 받아 이벤트를 저장해준다.
@ToeknMemberEmail은 HandlerMethodArgumentResolver를 이용하여 클라이언트에서 보낸 Token을 디코딩하여 회원의 email을 반환해준다.
클라이언트단에서 로그인 회원을 검증할 테지만 여기서 한번 더 검증을 통해 토큰이 없다면 이벤트는 추가되지 않을 것이다.
Vue.js
지난 게시글에서 구현한 일정 추가 Dialog 이후를 구현할 것이다.
Calendar Store
State
const state = {
event: initEvent(),
events: [],
dialog: false,
};
function initEvent(){
return {
startDate: '',
startTime: '',
endDate: '',
endTime: '',
content: '',
title: '',
}
}
Calendar의 state이다.
event는 일정 추가 Dialog의 각 속성의 값들이다.
events는 추가된 이벤트들을 받아오고 그 값을 캘린더에 보여주기 위한 state이다.
EventDialog.vue Methods
methods: {
submit() {
if (this.event.title === '' || this.event.endDate === '') {
store.commit('SET_SNACKBAR',
setSnackBarInfo('제목과 종료일자를 작성해주세요.', 'error', 'top'));
} else {
this.$store.dispatch('REQUEST_ADD_EVENT', this.event);
}
},
}
일정 추가 모달에서 추가 버튼을 누르게 되면 호출되는 submit() 메서드이다.
일정 제목과 종료일은 필수이므로 검증을 한 후 dispatch를 통해 actions를 호출한다.
Actions
const actions = {
async REQUEST_ADD_EVENT(context, calendar) {
try {
const response = await requestAddEvent(calendar);
const addedEvent = makeEvent(response.data);
context.commit('ADD_EVENT', addedEvent);
store.commit('SET_SNACKBAR', setSnackBarInfo('일정이 추가 되었습니다.', 'info', 'top'))
} catch (e) {
console.log('일정 추가 에러' + e);
}
},
}
const colors = ['blue', 'indigo', 'deep-purple', 'green', 'orange', 'red'];
const makeEvent = (event) => {
return {
name: event.title,
start: event.startDate + getTime(event.startTime),
end: event.endDate + getTime(event.endTime),
color: colors[Math.floor(Math.random() * 6)]
}
};
캘린더의 actions 부분이다.
우선 비동기 통신으로 requestAddEvent를 통해 서버에게 저장을 요청한다.
저장된 이벤트를 서버는 응답을 해줄 것이다.
그 후 makeEvent를 통해 이벤트를 따로 생성해줘야 한다.
name, start, end, color 프로퍼티를 가지는 event여야 vuetify 캘린더가 인식하여 캘린더에 보여주기 때문이다.
name은 일정 제목이 되고 start는 startDate와 Time을 합친 것이다.
color는 현재는 랜덤으로 배정하였다.
그렇게 만든 이벤트를 mutations에 넘겨준다.
Mutations
const mutations = {
ADD_EVENT(state, getEvent) {
state.events.push(getEvent);
state.dialog = false;
state.event = initEvent();
}
};
actions에서 받은 이벤트를 events state에 추가해준다.
이렇게 추가된 이벤트를 캘린더에서 받아 화면에 보이게 될 것이다.
Calendar.vue
<v-calendar
:event-color="getEventColor"
:events="events"
:start="start"
:type="type"
@click:date="open"
@click:event="showEvent"
@click:more="moreEvent"
@click:time="open"
dark
ref="calendar"
v-model="start"
></v-calendar>
캘린더 컴포넌트이다.
저번 게시글 이후에 추가된 속성들이 조금 있다.
순서대로 event-color는 말 그대로 event의 color에 맞게 캘린더에 일정이 표시된다.
events는 데이터 배열로 실제 일정들을 가지고 있다.
@click. date는 캘린더의 날짜를 클릭했을 때 발생하는 것으로 Event추가 Dialog를 띄워준다.
@click.event는 추후에 선택된 이벤트를 자세히 보여줄 때 사용한다.
@click.more은 같을 날에 이벤트가 많을 때 more 표시가 잇는데 그것을 누르면 캘린더의 type을 day로 변경하여 자세시 볼 수 있게 해 준다.
@click.time은 @click.date와 동일하게 시간을 클릭하면 이벤트 추가 Dialog를 띄어준다.
events computed
computed: {
events() {
return this.$store.state.calendar.events;
}
},
mutations에서 추가한 state의 events 받아와 위에서 설명한 events 넣어주었다.
결과 확인
이벤트 추가가 정상적으로 이루어지고 캘린더에 즉시 반영되는 것을 확인할 수 있다.
데이터베이스를 확인해봐도 정상적으로 데이터가 들어간 것을 알 수 있다.