testhtml2 / index.html
openfree's picture
Update index.html
b5d652f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Calendar with LocalStorage</title>
<style>
/* ์ด์ „ ์Šคํƒ€์ผ ์ฝ”๋“œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
background: #f5f5f5;
padding: 20px;
}
.calendar-container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
padding: 20px;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.month-nav {
display: flex;
align-items: center;
gap: 20px;
}
.nav-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #333;
padding: 5px 10px;
}
.current-month {
font-size: 24px;
font-weight: bold;
}
.filter-controls {
display: flex;
gap: 10px;
}
.filter-btn {
padding: 8px 15px;
border: none;
border-radius: 20px;
cursor: pointer;
background: #eee;
transition: 0.3s;
}
.filter-btn.active {
background: #007bff;
color: white;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 10px;
}
.day-header {
text-align: center;
font-weight: bold;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
.day-cell {
min-height: 120px;
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 5px;
transition: 0.3s;
}
.day-cell:hover {
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.day-cell.different-month {
background: #f8f9fa;
color: #999;
}
.event {
margin: 5px 0;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
cursor: move;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateY(-10px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.event.work { background: #ffcdd2; }
.event.personal { background: #c8e6c9; }
.event.meeting { background: #bbdefb; }
.event.dragging {
opacity: 0.5;
transform: scale(0.95);
}
.add-event-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
border-radius: 50%;
background: #007bff;
color: white;
border: none;
font-size: 24px;
cursor: pointer;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
transition: 0.3s;
}
.add-event-btn:hover {
transform: scale(1.1);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 10px;
width: 90%;
max-width: 400px;
}
.form-group {
margin: 15px 0;
}
input, select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
}
.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.modal-btn {
padding: 8px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.save-btn { background: #007bff; color: white; }
.cancel-btn { background: #eee; }
</style>
</head>
<body>
<div class="calendar-container">
<div class="calendar-header">
<div class="month-nav">
<button class="nav-btn" onclick="changeMonth(-1)">โ†</button>
<div class="current-month"></div>
<button class="nav-btn" onclick="changeMonth(1)">โ†’</button>
</div>
<div class="filter-controls">
<button class="filter-btn active" data-type="all">All</button>
<button class="filter-btn" data-type="work">Work</button>
<button class="filter-btn" data-type="personal">Personal</button>
<button class="filter-btn" data-type="meeting">Meeting</button>
</div>
</div>
<div class="calendar-grid"></div>
</div>
<button class="add-event-btn" onclick="openModal()">+</button>
<div class="modal">
<div class="modal-content">
<h2>Add Event</h2>
<div class="form-group">
<input type="text" id="eventTitle" placeholder="Event Title">
</div>
<div class="form-group">
<select id="eventType">
<option value="work">Work</option>
<option value="personal">Personal</option>
<option value="meeting">Meeting</option>
</select>
</div>
<div class="modal-buttons">
<button class="modal-btn cancel-btn" onclick="closeModal()">Cancel</button>
<button class="modal-btn save-btn" onclick="saveEvent()">Save</button>
</div>
</div>
</div>
<script>
let currentDate = new Date();
let events = [];
let selectedDate = null;
let draggedEvent = null;
// LocalStorage์—์„œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
function loadEvents() {
const savedEvents = localStorage.getItem('calendarEvents');
if (savedEvents) {
events = JSON.parse(savedEvents).map(event => ({
...event,
date: new Date(event.date)
}));
}
}
// LocalStorage์— ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์ €์žฅ
function saveEvents() {
localStorage.setItem('calendarEvents', JSON.stringify(events));
}
function initCalendar() {
loadEvents(); // ์ดˆ๊ธฐํ™” ์‹œ ์ €์žฅ๋œ ์ด๋ฒคํŠธ ๋กœ๋“œ
updateMonthDisplay();
renderCalendar();
initEventListeners();
}
function updateMonthDisplay() {
const months = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
document.querySelector('.current-month').textContent =
`${months[currentDate.getMonth()]} ${currentDate.getFullYear()}`;
}
function renderCalendar() {
const grid = document.querySelector('.calendar-grid');
grid.innerHTML = '';
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
days.forEach(day => {
const dayHeader = document.createElement('div');
dayHeader.className = 'day-header';
dayHeader.textContent = day;
grid.appendChild(dayHeader);
});
const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
const paddingDays = firstDay.getDay();
const prevLastDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0);
for (let i = paddingDays - 1; i >= 0; i--) {
const dayCell = createDayCell(prevLastDay.getDate() - i, true);
grid.appendChild(dayCell);
}
for (let day = 1; day <= lastDay.getDate(); day++) {
const dayCell = createDayCell(day, false);
grid.appendChild(dayCell);
}
const remainingDays = 42 - (paddingDays + lastDay.getDate());
for (let i = 1; i <= remainingDays; i++) {
const dayCell = createDayCell(i, true);
grid.appendChild(dayCell);
}
}
function createDayCell(day, isDifferentMonth) {
const cell = document.createElement('div');
cell.className = `day-cell${isDifferentMonth ? ' different-month' : ''}`;
cell.innerHTML = `<div class="day-number">${day}</div>`;
cell.addEventListener('dragover', e => e.preventDefault());
cell.addEventListener('drop', handleDrop);
if (!isDifferentMonth) {
const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day);
const dayEvents = events.filter(event => {
const eventDate = new Date(event.date);
return eventDate.toDateString() === date.toDateString();
});
dayEvents.forEach(event => {
const eventElement = createEventElement(event);
cell.appendChild(eventElement);
});
}
return cell;
}
function createEventElement(event) {
const eventDiv = document.createElement('div');
eventDiv.className = `event ${event.type}`;
eventDiv.textContent = event.title;
eventDiv.draggable = true;
eventDiv.dataset.eventId = event.id;
eventDiv.addEventListener('dragstart', e => {
draggedEvent = event;
e.target.classList.add('dragging');
});
eventDiv.addEventListener('dragend', e => {
e.target.classList.remove('dragging');
});
return eventDiv;
}
function handleDrop(e) {
e.preventDefault();
if (!draggedEvent) return;
const dayCell = e.target.closest('.day-cell');
if (!dayCell) return;
const dayNumber = dayCell.querySelector('.day-number').textContent;
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), dayNumber);
const eventIndex = events.findIndex(e => e.id === draggedEvent.id);
events[eventIndex].date = newDate;
saveEvents(); // ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ํ›„ ์ €์žฅ
draggedEvent = null;
renderCalendar();
}
function changeMonth(delta) {
currentDate.setMonth(currentDate.getMonth() + delta);
updateMonthDisplay();
renderCalendar();
}
function openModal() {
document.querySelector('.modal').style.display = 'flex';
}
function closeModal() {
document.querySelector('.modal').style.display = 'none';
}
function saveEvent() {
const title = document.getElementById('eventTitle').value;
const type = document.getElementById('eventType').value;
if (!title) return;
const newEvent = {
id: Date.now(),
title,
type,
date: new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
};
events.push(newEvent);
saveEvents(); // ์ƒˆ ์ด๋ฒคํŠธ ์ถ”๊ฐ€ ํ›„ ์ €์žฅ
renderCalendar();
closeModal();
document.getElementById('eventTitle').value = '';
}
function initEventListeners() {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
const type = e.target.dataset.type;
document.querySelectorAll('.event').forEach(event => {
if (type === 'all' || event.classList.contains(type)) {
event.style.display = 'block';
} else {
event.style.display = 'none';
}
});
});
});
}
// ์ดˆ๊ธฐํ™”
initCalendar();
</script>
</body>
</html>