본문 바로가기
1인 프로젝트/나만의 달력

3차 백업

by kirope 2024. 8. 5.
반응형

1. 그림 밑에 있는 그림 선택 서랍은 삭제하고, 업로드 버튼만 만들기. 각 월별로 달력 이동했을 때 미리 업로드한 그림이 보이기만 하면 됨. 버튼의 위치는 달력 좌측 상단인 '월요일' 위에 만들기
2. 연도 월 선택 서랍은 달력의 우측 상단인 '토요일'과 '일요일' 사이쯤의 위치로 해서 위로 옮기고, 연도-월 순으로 순서 바꾸기
3. 사용자가 도시명을 입력할 수 있는 입력창 만들기. 위치는 연도-월 박스 왼쪽에 만들기
4. 각 일자를 클릭했을 때 나오는 창의 폰트도 segeoUI로 통일할 것

각각의 수정 사항은 지금까지의 코드에 누적하여 반영하고, 수정사항 외에는 기존 코드를 그대로 유지할 것. 수정사항 반영하여 전체 코드를 알려주기 바람.

 

## index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Calendar and To-Do List</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <div class="left-panel">
            <img id="photo-preview" src="" alt="Uploaded photo">
        </div>
        <div class="right-panel">
            <div class="calendar-header">
                <label for="photo-upload" class="upload-button">Upload</label>
                <input type="file" id="photo-upload" accept="image/*">
                <input type="text" id="city-input" placeholder="Enter city">
                <select id="year-select"></select>
                <select id="month-select">
                    <option value="0">1월(Jan.)</option>
                    <option value="1">2월(Feb.)</option>
                    <option value="2">3월(Mar.)</option>
                    <option value="3">4월(Apr.)</option>
                    <option value="4">5월(May.)</option>
                    <option value="5">6월(Jun.)</option>
                    <option value="6">7월(Jul.)</option>
                    <option value="7">8월(Aug.)</option>
                    <option value="8">9월(Sep.)</option>
                    <option value="9">10월(Oct.)</option>
                    <option value="10">11월(Nov.)</option>
                    <option value="11">12월(Dec.)</option>
                </select>
            </div>
            <div class="calendar">
                <div class="weekdays">
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div class="sunday"></div>
                </div>
                <div class="days" id="days-container"></div>
            </div>
            <div class="todo-list">
                <h3>TO DO LIST:</h3>
                <ul class="todo-lines">
                    <li class="todo-line">
                        <div class="checkbox"></div>
                        <input type="text" class="todo-input">
                    </li>
                    <li class="todo-line">
                        <div class="checkbox"></div>
                        <input type="text" class="todo-input">
                    </li>
                    <li class="todo-line">
                        <div class="checkbox"></div>
                        <input type="text" class="todo-input">
                    </li>
                    <li class="todo-line">
                        <div class="checkbox"></div>
                        <input type="text" class="todo-input">
                    </li>
                    <li class="todo-line">
                        <div class="checkbox"></div>
                        <input type="text" class="todo-input">
                    </li>
                </ul>
            </div>
        </div>
    </div>
    <div id="event-modal" class="modal">
        <div class="modal-content">
            <span class="close">&times;</span>
            <h2 id="event-date"></h2>
            <textarea id="event-details" placeholder="Enter event details"></textarea>
            <button id="save-event">Save</button>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

 

## script.js

document.getElementById('photo-upload').addEventListener('change', function(event) {
    const file = event.target.files[0];
    if (file) {
        const reader = new FileReader();
        reader.onload = function(e) {
            const preview = document.getElementById('photo-preview');
            preview.src = e.target.result;
            preview.style.display = 'block';
            localStorage.setItem('uploadedImage', e.target.result);
           
            const currentMonth = monthSelect.value;
            monthImages[currentMonth] = e.target.result;
            localStorage.setItem('monthImages', JSON.stringify(monthImages));
        }
        reader.readAsDataURL(file);
    }
});

const yearSelect = document.getElementById('year-select');
const monthSelect = document.getElementById('month-select');
const daysContainer = document.getElementById('days-container');
const todoInputs = document.querySelectorAll('.todo-input');
const checkboxes = document.querySelectorAll('.checkbox');
const cityInput = document.getElementById('city-input');
const modal = document.getElementById('event-modal');
const closeModal = document.getElementsByClassName('close')[0];
const eventDate = document.getElementById('event-date');
const eventDetails = document.getElementById('event-details');
const saveEvent = document.getElementById('save-event');

let monthImages = {};
let events = {};

const apiKey = 'YOUR_API_KEY'; // OpenWeatherMap API 키로 교체하세요
let city = '서울'; // 기본 도시

function populateYears() {
    const currentYear = new Date().getFullYear();
    for (let i = currentYear - 10; i <= currentYear + 10; i++) {
        const option = document.createElement('option');
        option.value = i;
        option.textContent = i;
        yearSelect.appendChild(option);
    }
}

function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

function getLastDateOfMonth(year, month) {
    const lastDate = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    return lastDate[month];
}

function populateDays(year, month) {
    daysContainer.innerHTML = '';
    const firstDay = new Date(year, month, 1).getDay();
    const lastDate = getLastDateOfMonth(year, month);

    for (let i = 0; i < firstDay; i++) {
        const emptyDiv = document.createElement('div');
        daysContainer.appendChild(emptyDiv);
    }

    for (let i = 1; i <= lastDate; i++) {
        const dayDiv = document.createElement('div');
        const dateSpan = document.createElement('span');
        dateSpan.className = 'date';
        dateSpan.textContent = i;
        const weatherSpan = document.createElement('span');
        weatherSpan.className = 'weather';
        const weatherIcon = document.createElement('img');
        weatherIcon.src = 'static/images/weather_icon_placeholder.svg';
        weatherSpan.appendChild(weatherIcon);
        dayDiv.appendChild(dateSpan);
        dayDiv.appendChild(weatherSpan);
        dayDiv.addEventListener('click', () => openEventModal(year, month, i));
        if ((firstDay + i - 1) % 7 === 6) {
            dayDiv.style.color = 'red';
        }
        daysContainer.appendChild(dayDiv);
    }

    updateWeatherIcons(year, month);
}

function updateWeatherIcons(year, month) {
    const weatherIcons = document.querySelectorAll('.weather img');
    weatherIcons.forEach((icon, index) => {
        const day = index + 1;
        const date = `${year}-${month + 1}-${day}`;
        fetchWeatherData(date, icon);
    });
}

function fetchWeatherData(date, icon) {
    const geocodingUrl = `http://api.openweathermap.org/geo/1.0/direct?q=${encodeURIComponent(city)}&limit=1&appid=${apiKey}`;
   
    fetch(geocodingUrl)
        .then(response => response.json())
        .then(data => {
            if (data.length > 0) {
                const { lat, lon } = data[0];
                const weatherUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&dt=${date}&appid=${apiKey}`;
                return fetch(weatherUrl);
            }
            throw new Error('City not found');
        })
        .then(response => response.json())
        .then(data => {
            const weather = data.weather[0].main.toLowerCase();
            switch (weather) {
                case 'clear':
                    icon.src = 'static/images/clear_icon.svg';
                    break;
                case 'clouds':
                    icon.src = 'static/images/clouds_icon.svg';
                    break;
                case 'rain':
                    icon.src = 'static/images/rain_icon.svg';
                    break;
                case 'snow':
                    icon.src = 'static/images/snow_icon.svg';
                    break;
                default:
                    icon.src = 'static/images/default_weather_icon.svg';
            }
        })
        .catch(error => console.error('Error fetching weather data:', error));
}

function saveTodos() {
    const todos = [];
    todoInputs.forEach((input, index) => {
        todos.push({
            text: input.value,
            checked: checkboxes[index].classList.contains('checked')
        });
    });
    localStorage.setItem('todos', JSON.stringify(todos));
}

function loadTodos() {
    const todos = JSON.parse(localStorage.getItem('todos')) || [];
    todoInputs.forEach((input, index) => {
        if (todos[index]) {
            input.value = todos[index].text || '';
            if (todos[index].checked) {
                checkboxes[index].classList.add('checked');
            } else {
                checkboxes[index].classList.remove('checked');
            }
        }
    });
}

todoInputs.forEach(input => {
    input.addEventListener('input', saveTodos);
});

checkboxes.forEach(checkbox => {
    checkbox.addEventListener('click', function() {
        this.classList.toggle('checked');
        saveTodos();
    });
});

function loadImage() {
    const uploadedImage = localStorage.getItem('uploadedImage');
    if (uploadedImage) {
        const preview = document.getElementById('photo-preview');
        preview.src = uploadedImage;
        preview.style.display = 'block';
    }

    const savedMonthImages = localStorage.getItem('monthImages');
    if (savedMonthImages) {
        monthImages = JSON.parse(savedMonthImages);
    }
}

function openEventModal(year, month, day) {
    const dateString = `${year}-${month + 1}-${day}`;
    eventDate.textContent = dateString;
    eventDetails.value = events[dateString] || '';
    modal.style.display = 'block';
}

closeModal.onclick = function() {
    modal.style.display = 'none';
}

window.onclick = function(event) {
    if (event.target == modal) {
        modal.style.display = 'none';
    }
}

saveEvent.onclick = function() {
    const dateString = eventDate.textContent;
    events[dateString] = eventDetails.value;
    localStorage.setItem('events', JSON.stringify(events));
    modal.style.display = 'none';
}

yearSelect.addEventListener('change', function() {
    populateDays(yearSelect.value, monthSelect.value);
    updateMonthImage();
});

monthSelect.addEventListener('change', function() {
    populateDays(yearSelect.value, monthSelect.value);
    updateMonthImage();
});

cityInput.addEventListener('change', function() {
    city = cityInput.value;
    populateDays(yearSelect.value, monthSelect.value);
});

function updateMonthImage() {
    const currentMonth = monthSelect.value;
    if (monthImages[currentMonth]) {
        document.getElementById('photo-preview').src = monthImages[currentMonth];
    } else {
        loadImage();
    }
}

populateYears();
populateDays(new Date().getFullYear(), new Date().getMonth());
loadTodos();
loadImage();
updateMonthImage();

 

## styles.css

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #B4A391;
    color: #2E292A;
}

.container {
    display: flex;
    width: 95%;
    max-width: 1400px;
    background-color: #B4A391;
}

.left-panel, .right-panel {
    padding: 20px;
}

.left-panel {
    flex: 0 0 600px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.right-panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    height: 600px; /* Match the height of the image */
}

#photo-upload {
    display: none;
}

.upload-button {
    background-color: white;
    color: #2E292A;
    padding: 10px 20px;
    cursor: pointer;
    font-size: 16px;
    display: inline-block;
    box-sizing: border-box;
    text-align: center;
}

#photo-preview {
    width: 600px;
    height: 600px;
    object-fit: cover;
    display: none;
}

.calendar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
    height: 40px;
    width: 100%; /* Ensure it takes the full width */
}

.calendar-header input,
.calendar-header select,
.calendar-header .upload-button {
    height: 100%;
    padding: 0 10px;
    font-size: 14px;
    border: 1px solid #ccc;
    box-sizing: border-box;
    flex: 1;
}

.calendar-header .upload-button {
    border-right: none;
}

.calendar-header #city-input {
    border-left: none;
    border-right: none;
}

.calendar-header #year-select {
    border-left: none;
    border-right: none;
}

.calendar-header #month-select {
    border-left: none;
}

.calendar {
    text-align: center;
    width: 100%;
    background-color: #B4A391;
    border-radius: 10px;
    padding: 10px;
    box-sizing: border-box;
    flex-grow: 1;
    display: flex;
    flex-direction: column;
}

.weekdays, .days {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 5px;
}

.weekdays div {
    font-weight: bold;
    padding: 10px 0;
}

.weekdays .sunday {
    color: red;
}

.days {
    grid-template-rows: repeat(6, 1fr);
    flex-grow: 1;
    gap: 10px; /* Add vertical gap between dates */
}

.days div {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 100%;
    cursor: pointer;
    padding: 5px;
}

.days div .date {
    font-size: 14px;
}

.days div .weather {
    font-size: 12px;
}

.weather img {
    width: 20px;
    height: 20px;
    display: block;
}

.todo-list {
    margin-top: 10px;
}

.todo-lines {
    list-style-type: none;
    padding: 0;
}

.todo-line {
    display: flex;
    align-items: center;
    margin-bottom: 10px;
    padding-bottom: 5px;
    border-bottom: 1px solid #fff;
}

.checkbox {
    width: 24px;
    height: 24px;
    margin-right: 10px;
    background-image: url('static/images/check_box_outline_blank_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.svg');
    background-size: cover;
    cursor: pointer;
}

.checkbox.checked {
    background-image: url('static/images/check_box_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.svg');
}

.todo-input {
    flex: 1;
    background: transparent;
    border: none;
    color: #2E292A;
    font-size: 16px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.todo-input:focus {
    outline: none;
}

.modal {
    display: none;
    position: fixed;
    z-index: 1;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.4);
}

.modal-content {
    background-color: #fefefe;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 500px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.close {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
    cursor: pointer;
}

.close:hover,
.close:focus {
    color: black;
    text-decoration: none;
    cursor: pointer;
}

#event-details {
    width: 100%;
    height: 100px;
    margin-top: 10px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#save-event {
    margin-top: 10px;
    padding: 5px 10px;
    background-color: #4CAF50;
    color: white;
    border: none;
    cursor: pointer;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
728x90
반응형

'1인 프로젝트 > 나만의 달력' 카테고리의 다른 글

백업...또 백업  (0) 2024.08.09
PostgreSQL  (0) 2024.08.07
Perplexity 사용하기 어렵다...  (0) 2024.08.06
2차 백업  (0) 2024.08.05
1차 백업  (0) 2024.08.05