vue.js를 공부한 다음 인공지능 chatGPT 4o를 사용해서 만들어보았다. 역시 공부하기 전에는 기본 구조부터 프로그램의 돌아가는 원리를 잘 이해하지 못해 gpt를 사용해도 만들지 못했을 것이다. 공부한 후에는 구조와 원리를 파악한 후 수정할 부분을 파악할 수 있었고 익스플로어나 크롬에 있는 개발자 도구를 사용해 버그도 찾아낼 수 있었다.
우선 재사용 가능한 컴포넌트를 만드는데 집중하였고, 그 컴포넌트를 여기저기 쓸 수 있게 되었다. 스케쥴 등록, 수정, 삭제가능한 컴포넌트를 만들었고 그 다음 캘린더 컴포넌트를 붙이고 emit 명령어를 사용하여 연결하였다. 이 과정에서 개발자 도구를 사용하여 에러를 찾아서 수정하였다. 특별히 데이터베이스를 사용하지 않고 로컬 저장소를 사용하여 아주 간단한 로직을 구현하였다.
Calendar.vue
<template>
<div class="calendar">
<div class="header">
<button @click="prevMonth">Prev</button>
<h2>{{ monthNames[currentMonth] }} {{ currentYear }}</h2>
<button @click="nextMonth">Next</button>
</div>
<div class="weekdays">
<div v-for="day in weekDays" :key="day">{{ day }}</div>
</div>
<div class="days">
<div
v-for="day in daysInMonth"
:key="day.date ? day.date.toDateString() : day.index"
:class="{'today': isToday(day.date), 'has-event': hasEvent(day.date)}"
>
<span v-if="day.date">{{ day.date.getDate() }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
events: Array
},
data() {
return {
currentDate: new Date(),
currentMonth: new Date().getMonth(),
currentYear: new Date().getFullYear(),
weekDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
monthNames: [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
]
};
},
computed: {
daysInMonth() {
const days = [];
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
const lastDate = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
for (let i = 0; i < firstDay; i++) {
days.push({ date: null, index: `empty-${i}` });
}
for (let date = 1; date <= lastDate; date++) {
days.push({ date: new Date(this.currentYear, this.currentMonth, date) });
}
return days;
}
},
methods: {
prevMonth() {
if (this.currentMonth === 0) {
this.currentMonth = 11;
this.currentYear--;
} else {
this.currentMonth--;
}
},
nextMonth() {
if (this.currentMonth === 11) {
this.currentMonth = 0;
this.currentYear++;
} else {
this.currentMonth++;
}
},
isToday(date) {
if (!date) return false;
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
},
hasEvent(date) {
if (!date) return false;
return this.events.some(event => {
const eventDate = new Date(event.date);
return eventDate.getDate() === date.getDate() &&
eventDate.getMonth() === date.getMonth() &&
eventDate.getFullYear() === date.getFullYear();
});
}
}
};
</script>
<style scoped>
.calendar {
max-width: 400px;
margin: auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.weekdays {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.days {
display: flex;
flex-wrap: wrap;
}
.days div {
width: calc(100% / 7);
height: 40px;
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
}
.today {
background-color: #42b983;
color: white;
border-radius: 50%;
}
.has-event {
background-color: purple;
color: white;
border-radius: 50%;
}
</style>
Schedule.vue
<template>
<div class="schedule">
<h2>My Schedule</h2>
<ul>
<li v-for="event in events" :key="event.id">
<span @click="editEvent(event)">{{ event.date }} {{ event.time }}: {{ event.title }}</span>
<button @click="deleteEvent(event.id)">Delete</button>
</li>
</ul>
<input v-model="newEventTitle" placeholder="Event Title" />
<input v-model="newEventDate" type="date" />
<input v-model="newEventTime" type="time" />
<button @click="addEvent">Add Event</button>
<button v-if="isEditing" @click="updateEvent">Update Event</button>
</div>
</template>
<script>
export default {
data() {
return {
events: JSON.parse(localStorage.getItem('events') || '[]'),
newEventTitle: '',
newEventDate: '',
newEventTime: '',
isEditing: false,
currentEvent: null
};
},
methods: {
addEvent() {
if (this.newEventTitle && this.newEventDate && this.newEventTime) {
const newEvent = {
id: Date.now(),
title: this.newEventTitle,
date: this.newEventDate,
time: this.newEventTime
};
this.events.push(newEvent);
this.saveEvents();
this.resetForm();
this.$emit('update-events', this.events);
}
},
editEvent(event) {
this.isEditing = true;
this.currentEvent = event;
this.newEventTitle = event.title;
this.newEventDate = event.date;
this.newEventTime = event.time;
},
updateEvent() {
if (this.currentEvent) {
this.currentEvent.title = this.newEventTitle;
this.currentEvent.date = this.newEventDate;
this.currentEvent.time = this.newEventTime;
this.saveEvents();
this.resetForm();
this.$emit('update-events', this.events);
}
},
deleteEvent(id) {
this.events = this.events.filter(event => event.id !== id);
this.saveEvents();
this.$emit('update-events', this.events);
},
saveEvents() {
localStorage.setItem('events', JSON.stringify(this.events));
},
resetForm() {
this.newEventTitle = '';
this.newEventDate = '';
this.newEventTime = '';
this.isEditing = false;
this.currentEvent = null;
}
}
};
</script>
<style scoped>
.schedule {
max-width: 400px;
margin: auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
input {
display: block;
width: 100%;
padding: 8px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px 15px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #38a373;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 10px 0;
}
span {
cursor: pointer;
}
</style>
마지막으로 홈페이지 구실을 하기위해 header, menu, article, side, footer를 구성하고 side에 캘린더와 일정 관리를 재사용하였다.
SideBar.vue
<template>
<div>
<Calendar :events="events" />
<Schedule @update-events="updateEvents" />
</div>
</template>
<script>
import Calendar from './Calendar.vue';
import Schedule from './Schedule.vue';
export default {
name: 'SideBar',
components: {
Calendar,
Schedule
},
data() {
return {
events: JSON.parse(localStorage.getItem('events') || '[]')
};
},
methods: {
updateEvents(events) {
this.events = events;
}
}
};
</script>
결과물과 만드는 과정은 아래 링크를 통해서 확인할 수 있다. 단 이 페이지는 최종 완성물이 아니라서 최적화 과정을 거쳐서 자신만의 웹페이지를 만들 수 있으면 한다.
My Personal Website (jocular-boba-1b5d4b.netlify.app)