diff --git a/README.md b/README.md index 8df8962..3ada55c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ `#html` `#css` `#js` `#dom` `#localstorage` `#JSON` `#master-in-software-engineering` +# CALENDAR JS + ## Introduction
@@ -10,15 +12,6 @@ In this project you will have to create a calendar using the main web fundamenta The calendar must be interactive for the users allowing them to create events without refreshing the whole page. -## What are the main goals in this project? - -- Improve your knowledge of JavaScript -- Learn to work with the HTML DOM -- Learn to work with localStorage -- Learn and improve your knowledge in logic processes -- Learn to validate forms using Javascript -- Learn to work with JSON format - ## Requirements - You must develop this project using a single HTML page @@ -26,13 +19,9 @@ The calendar must be interactive for the users allowing them to create events wi - You must use localStorage to store all the events, this way, if you reload the page, the events will remain stored in the browser - You must use semantic HTML5 elements for all the contents of the application -## Repository +## Project -Check with your tech lead the project requirements for this pill. +Project developed: -## Resources + -- [JavaScript HTML DOM](https://www.w3schools.com/js/js_htmldom.asp) -- [JavaScript Dates](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Date) -- [LocalStorage](https://developer.mozilla.org/es/docs/Web/API/Window/localStorage) -- [JavaScript Timeout](https://www.w3schools.com/jsref/met_win_settimeout.asp) diff --git a/events.js b/events.js new file mode 100644 index 0000000..d3a698d --- /dev/null +++ b/events.js @@ -0,0 +1,191 @@ +let reminders = []; +let allEvents = []; + +function loadEvents() { + allEvents = getStorage("events") ? getStorage("events") : []; + allEvents.forEach((event) => { + event.remind ? reminders.push({ + id: event.id, + title: event.title, + initDate: event.initDate, + endDate: event.endDate, + time: event.time, + description: event.description, + type: event.type, + remind: event.remind, + finished: event.finished, + }) : null; + chooseDateEventAndPaint(event); + }); +} + +const addEvent = (e) => { + e.preventDefault(); + const monthEvent = new Date(initDate.value).getMonth(); + if (monthEvent === navigator) { + const startDate = setDateFormValues(initDate.value, extractTime(initTime.value)); + const finalDate = setDateFormValues(endDate.value, extractTime(endTime.value)); + if (finalDate.getMonth() - startDate.getMonth() <= 1) { + if (startDate < finalDate) { + if (startDate.getTime() + 600000 > new Date().getTime()) { + const event = createEvent(startDate, finalDate); + time.value && reloadReminderEvents(event); + reloadEvents(event); + saveStorage("events", allEvents); + chooseDateEventAndPaint(event); + initModalEvent(); + form.reset(); + document.querySelector("#addModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = "auto"; + } else openError(1); + } else openError(2); + } else openError(3); + } else openError(4); +} + +function createEvent(startDate, finalDate) { + const event = { + id: `id_${Date.now()}`, + title: title.value, + initDate: toIsoString(startDate), + endDate: toIsoString(finalDate), + time: time.value ? time.value : "", + description: description.value ? description.value : "", + type: type.value ? type.value : "", + remind: time.value ? true : undefined, + finished: false + } + return event; +} + +function chooseDateEventAndPaint(event) { + const month = new Date(event.initDate).getMonth(); + const monthEnd = new Date(event.endDate).getMonth(); + const { totalDays, daysInMonth1 } = getDaysOfEvent(event.initDate, event.endDate); + totalDays.forEach((day, i) => { + let daySquare; + if (monthEnd - month > 0) { + if (i <= daysInMonth1) { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${month}"]`); + } else { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${monthEnd}"]`); + } + } else { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${month}"]`); + } + if (totalDays.length === 1) paintEvent(event, daySquare, true, true); + else { + if (i === 0) paintEvent(event, daySquare, true, false); + else if (i === totalDays.length - 1) paintEvent(event, daySquare, false, true); + else paintEvent(event, daySquare); + } + }); +} + +function paintEvent(event, daySquare, paintInit, paintEnd) { + const newDomEvent = document.createElement("div"); + const strTimeInit = getFullTimeFromString(event.initDate); + const strTimeEnd = getFullTimeFromString(event.endDate); + newDomEvent.innerHTML = `
${event.title}
`; + newDomEvent.innerHTML += `${paintInit ? strTimeInit : ''}${paintInit && paintEnd ? ' - ' : ''}${paintEnd ? strTimeEnd : ''}
`; + newDomEvent.innerHTML += `${paintInit || paintEnd ? event.type : ''}
`; + newDomEvent.innerHTML += `${paintInit ? event.description : ''}
`; + newDomEvent.classList.add("day-event"); + newDomEvent.setAttribute('event-id', event.id); + newDomEvent.addEventListener("click", (e) => openEvent(e)); + newDomEvent.setAttribute('data-open', 'eventModal'); + + if (event.remind === false) newDomEvent.style.backgroundColor = 'orange'; + if (event.finished) newDomEvent.style.backgroundColor = 'rgb(203 55 55)'; + + daySquare.append(newDomEvent); +} + +const removeEvent = (e) => { + e.preventDefault(); + allEvents = arrayRemove(allEvents, idEvent.value); + removeStorage("events"); + saveStorage("events", allEvents); + + const month = new Date(initEventDate.value).getMonth(); + const monthEnd = new Date(endEventDate.value).getMonth(); + const { totalDays, daysInMonth1 } = getDaysOfEvent(initEventDate.value, endEventDate.value); + + totalDays.forEach((day, i) => { + let daySquare; + if (monthEnd - month > 0) { + if (i <= daysInMonth1) { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${month}"]`); + } else { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${monthEnd}"]`); + } + } else { + daySquare = document.querySelector(`div[data-day="${day}"][data-month="${month}"]`); + } + Array.from(daySquare.children).forEach(el => { + if (idEvent.value === el.getAttribute("event-id")) el.remove(); + }); + }); + document.querySelector("#eventModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = 'auto'; +} + +function getDaysOfEvent(initDate, endDate) { + const dateStart = new Date(initDate); + const dateFinal = new Date(endDate);; + const year = dateStart.getFullYear(); + const dayInit = dateStart.getDate(); + const dayFinal = dateFinal.getDate(); + const monthInit = dateStart.getMonth(); + const monthEnd = dateFinal.getMonth(); + let totalDays = []; + + if (monthEnd - monthInit > 0) { + const daysInMonth = new Date(year, monthInit, 0).getDate(); + const daysInMonth1 = (daysInMonth - dayInit); + const daysInMonth2 = dayFinal; + const numberDays = daysInMonth1 + daysInMonth2; + for (let i = 0; i <= numberDays; i++) { + if (i <= daysInMonth1) totalDays.push(i + dayInit); + } + for (let x = 1; x <= (numberDays - daysInMonth1); x++) { + totalDays.push(x); + } + const objectToReturn = { + totalDays: totalDays, + daysInMonth1: daysInMonth1, + } + return objectToReturn; + + } else { + const numberDays = dayFinal - dayInit; + for (let i = 0; i <= numberDays; i++) { + totalDays.push(i + dayInit); + } + return { totalDays: totalDays }; + } +} + +function disableEvent(task) { + openAlert(task, "end"); + removeStorage("events"); + allEvents.find(event => event.id === task.id).finished = true; + saveStorage("events", allEvents); + setTimeout(() => { + if (document.querySelector("#endAlert.is-visible")) { + document.querySelector("#endAlert.is-visible").classList.remove(isVisible); + } + }, 5000); +} + +function remindEvent(task) { + openAlert(task, "remind"); + removeStorage("events"); + allEvents.find(event => event.id === task.id).remind = false; + saveStorage("events", allEvents); + setTimeout(() => { + if (document.querySelector("#remindAlert.is-visible")) { + document.querySelector("#remindAlert.is-visible").classList.remove(isVisible); + } + }, 5000); +} \ No newline at end of file diff --git a/forms.js b/forms.js new file mode 100644 index 0000000..554b6c0 --- /dev/null +++ b/forms.js @@ -0,0 +1,84 @@ +const checkboxEndDate = document.querySelector("#existEndDate"); +const checkboxExpiration = document.querySelector("#expiration"); +const containerPreviousTime = document.querySelector("#previousTime"); +const containerEndDate = document.querySelector("#showEndDate"); +const initDate = document.querySelector("#initDate"); +const initTime = document.querySelector("#initTime"); +const title = document.querySelector("#title"); +const description = document.querySelector("#description"); +const type = document.querySelector("#type"); +const endDate = document.querySelector("#endDate"); +const endTime = document.querySelector("#endTime"); +const time = document.querySelector("#time"); +const containerExpiration = document.querySelector("#showExpiration"); + +function initForm() { + checkboxEndDate.addEventListener("change", showEndDate); + checkboxExpiration.addEventListener("change", showPreviousTime); + form.addEventListener("submit", addEvent); + formEvent.addEventListener("submit", removeEvent); +} + +const showEndDate = () => { + if (checkboxEndDate.checked) { + containerEndDate.classList.add(isVisible); + containerExpiration.classList.add(isVisible); + checkboxExpiration.checked = true; + containerPreviousTime.classList.add(isVisible); + endDate.required = true; + endTime.required = true; + time.required = true; + + } else { + containerEndDate.classList.remove(isVisible); + containerExpiration.classList.remove(isVisible); + endDate.required = false; + endTime.required = false; + time.required = false; + } +}; + +function hideEndDateAndRemind() { + containerEndDate.classList.remove(isVisible); + containerExpiration.classList.remove(isVisible); + checkboxEndDate.checked = false; + checkboxExpiration.checked = false; + endDate.required = false; + endTime.required = false; + time.required = false; +} + +function setDatesInForm(date, timeInit, timeEnd) { + initDate.value = date; + initTime.value = timeInit; + endDate.value = date; + endTime.value = timeEnd; + title.value = ''; + description.value = ''; + type.value = ''; +} + +function setEventData(event) { + document.querySelector("#titleEvent").textContent = event.title; + document.querySelector("#startDate").textContent = getStrDisplayDateTime(new Date(event.initDate), userLang); + document.querySelector("#finalDate").textContent = getStrDisplayDateTime(new Date(event.endDate), userLang); + document.querySelector("#descriptionEvent").textContent = event.description ? event.description : "Not Selected"; + document.querySelector("#timeEvent").textContent = event.time ? event.time + " minutes" : "Not Selected"; + document.querySelector("#typeEvent").textContent = event.type ? event.type : "Not Selected"; + document.querySelector("#typeEvent").style.textTransform = "capitalize"; + document.querySelector("#initEventDate").value = toIsoString(new Date(event.initDate)); + document.querySelector("#endEventDate").value = toIsoString(new Date(event.endDate)); + document.querySelector("#idEvent").value = event.id; +} + +const showPreviousTime = () => { + if (checkboxExpiration.checked) { + containerPreviousTime.classList.add(isVisible); + time.required = true; + + } else { + containerPreviousTime.classList.remove(isVisible); + time.required = false; + time.value = ''; + } +}; \ No newline at end of file diff --git a/index.css b/index.css new file mode 100644 index 0000000..917976b --- /dev/null +++ b/index.css @@ -0,0 +1,724 @@ +/* MAIN */ +:root { + --blue: steelblue; + --white: #fff; + --black: rgba(0, 0, 0, 0.668); + + --main-color: #101b3d; + --main-color-tr: #101b3d7e; + --main-dark-color: #010626; + --main-dark-color-tr: #010626a4; +} +* { + color: var(--white); + background-color: var(--main-color); + padding: 0; + margin: 0; + -ms-box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande", + "Lucida Sans", Arial, sans-serif; +} +h1 { + margin: 0; +} + +/* HEADER */ +.header { + padding: 20px 0; + display: flex; + flex-flow: row-reverse wrap; + justify-content: center; + background-color: transparent; + color: white; + margin: 20px 20px; +} +.header .h1 { + width: 100%; + font-size: 5em; + letter-spacing: 20px; + font-weight: bolder; + text-align: center; +} +.header .btn-add { + display: flex; + justify-content: center; + height: auto; + width: fit-content; +} +.header .btn-add .open-modal { + height: 40px; + cursor: pointer; + border: none; + outline: none; + font-size: 6em; + color: rgb(255, 255, 255); +} +.header .btn-add .open-modal:hover { + opacity: 0.7; +} + +/* CALENDAR */ +.skeleton { + width: 100%; +} + +.calendar { + margin: 0 auto; + margin-top: 50px; + width: 1470px; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 20px; +} +.calendar .display { + min-width: 700px; + display: flex; + text-align: center; + justify-content: center; + font-size: 1.6em; + margin-bottom: 30px; + text-transform: capitalize; +} +.calendar .display .previous-btn, +.calendar .display .next-btn { + width: 30px; + cursor: pointer; + border: none; + outline: none; + font-size: 1em; + font-weight: bolder; + color: rgb(255, 255, 255); +} +.calendar .display .previous-btn:hover, +.calendar .display .next-btn:hover { + transform: scale(1.6); +} +.calendar .display .month-display { + width: 80%; + font-size: 1.2em; +} + +.calendar .weekdays { + width: 100%; + font-size: 1.2em; + display: flex; + color: #247ba0; + text-align: center; +} +.calendar .weekdays .weekday { + width: 200px; + padding: 10px; + font-weight: 600; + letter-spacing: 1px; +} + +.calendar .months { + width: 100%; + margin: auto; + display: flex; + flex-wrap: wrap; +} +.calendar .month { + width: 100%; + margin: auto; + display: flex; + flex-wrap: wrap; + justify-content: start; +} + +.calendar .months .month .day { + width: 200px; + padding: 15px; + height: 200px; + box-sizing: border-box; + background-color: var(--main-dark-color-tr); + border-radius: 30px; + margin: 5px; + display: flex; + flex-direction: column; + justify-content: space-between; + cursor: pointer; + font-size: 1.3em; + overflow-y: auto; + overflow-x: hidden; +} +.calendar .months .month .day:hover { + background-color: var(--main-dark-color); +} +.calendar .months .month .day .number-day { + background-color: transparent; + width: fit-content; + margin-bottom: 10px; + margin-left: 5px; + margin-top: 5px; +} +.calendar .months .month .day .number-day span { + background-color: transparent; +} + +.calendar .months .month .day.empty { + background-color: transparent; +} +.calendar .months .month .day.current { + background-color: rgba(0, 94, 225, 0.46); +} +.calendar .months .month .day.current:hover { + background-color: rgba(0, 94, 225, 0.702); +} +.day::-webkit-scrollbar { + display: block; + width: 3px; +} +.day::-webkit-scrollbar-track { + background: transparent; +} +.day::-webkit-scrollbar-track-piece:end { + background: transparent; + margin-bottom: 30px; +} +.day::-webkit-scrollbar-track-piece:start { + background: transparent; + margin-top: 30px; +} +.day::-webkit-scrollbar-thumb { + background-color: var(--white); + border-right: none; + border-left: none; +} + +.calendar .months .month .day .day-event { + height: auto; + padding: 10px; + color: var(--black); + font-size: 0.7em; + margin-top: 5px; + border-radius: 10px; + line-height: 23px; + font-weight: bold; + word-wrap: break-word; + background-color: rgb(0 181 91); + overflow: visible; +} +.day-event::-webkit-scrollbar { + display: block; + width: 4px; +} +.day-event::-webkit-scrollbar-track { + background: transparent; +} +.day-event::-webkit-scrollbar-track-piece:end { + background: transparent; + margin-bottom: 10px; +} +.day-event::-webkit-scrollbar-track-piece:start { + background: transparent; + margin-top: 10px; +} +.day-event::-webkit-scrollbar-thumb { + background-color: var(--black); + border-right: none; + border-left: none; +} +.calendar .months .month .day .day-event:hover { +} +.calendar .months .month .day .day-event > * { + background-color: transparent; + text-transform: capitalize; +} + +/* MODAL */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + background: rgba(0, 0, 0, 0.885); + visibility: hidden; + opacity: 0; + transition: all 0.35s ease-in; + z-index: 10; +} +.modal-dialog { + position: relative; + width: 500px; + height: fit-content; + padding: 40px; + border-radius: 5px; + overflow: auto; + cursor: default; + background-color: var(--main-color); +} +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + font-weight: bolder; + font-size: 2em; + text-align: center; + height: fit-content; +} +.modal-header h3 { + text-align: center; + width: 90%; + word-wrap: break-word; + margin-right: 20px; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + height: fit-content; +} +.modal-header .modal-close { + width: 15px; + cursor: pointer; + border: none; + outline: none; + padding: 5px; + border-radius: 5px; + color: var(--white); + background-color: var(--main-color); +} +.modal-header .modal-close:hover { + transform: scale(1.5); +} +.modal p + p { + margin-top: 1rem; + text-transform: capitalize; +} +.modal { + visibility: hidden; + opacity: 0; + transition: all 0.35s ease-in; +} +.modal.is-visible { + visibility: visible; + opacity: 1; +} +[data-animation="slideInOutLeft"] .modal-dialog { + opacity: 0; + transform: translateX(-100%); + transition: all 0.5s var(--bounceEasing); +} +[data-animation="slideInOutLeft"].is-visible .modal-dialog { + opacity: 1; + transform: none; + transition-delay: 0.2s; +} +.modal-content { + display: flex; + flex-direction: column; + align-items: center; + margin: 30px 0; + min-height: 300px; +} +.modal-content > * { + background-color: var(--main-color); +} +.form { + width: 100%; + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.modal-content .form .form-data { + margin-bottom: 20px; + width: 100%; + display: flex; + background-color: var(--main-color); +} +.modal-content .form .form-data .div-label { + width: 50%; + background-color: var(--main-color); +} +label { + width: 90%; + background-color: var(--main-color); + font-size: 1.2em; +} +div.form-data > p { + width: 90%; + background-color: var(--main-color); + font-size: 1.2em; + margin-left: 50px; + margin-top: 20px; +} +.modal-content .form .form-data .div-input { + width: 50%; +} +.modal-content .form .form-data .div-input input, +.modal-content .form .form-data .div-input select, +.modal-content .form .form-data .div-input textarea { + width: 90%; + font-size: 1.2em; + color: black; + padding: 5px 10px; + background-color: white; + border-radius: 5px; +} +.modal-content .form .form-data .div-input input:focus, +.modal-content .form .form-data .div-input select:focus, +.modal-content .form .form-data .div-input textarea:focus { + outline: 3px solid rgb(83, 83, 204); +} + +input[type="checkbox"] { + width: 1.2em; + height: 1.2em; + margin: 3px 8px 0 0; + cursor: pointer; +} +input[type="checkbox"] + label { + cursor: pointer; +} +.modal-footer .btn-save, +.modal-footer .btn-cancel, +.modal-footer .btn-del { + background-color: rgba(0, 255, 0, 0.652); + color: white; + border: none; + padding: 12px 35px; + cursor: pointer; + border-radius: 5px; + font-size: 1.1em; + font-weight: bold; + margin-left: 20px; +} +.modal-footer .btn-cancel { + background-color: rgb(255, 0, 0); +} +.modal-footer .btn-del { + background-color: orange; +} + +/* ERRORS */ +.error-dialog { + position: relative; + width: 500px; + height: 130px; + padding: 40px; + border-radius: 5px; + overflow: hidden; + cursor: default; + background-color: rgb(230, 87, 68); +} +.error-header { + display: flex; + align-items: center; + justify-content: space-between; + font-weight: bolder; + font-size: 2em; + text-align: center; + height: fit-content; + background-color: rgb(230, 87, 68); +} +.error-header svg { + width: 1.3em; + height: 1.3em; + background-color: rgb(230, 87, 68); + margin-right: 20px; +} +.error-header h3 { + text-align: start; + width: 100%; + background-color: rgb(230, 87, 68); +} +.error-header .error-close { + width: 15px; + cursor: pointer; + border: none; + outline: none; + padding: 5px; + font-size: 0.8em; + border-radius: 5px; + color: var(--white); + background-color: rgb(230, 87, 68); +} +.error-header .error-close:hover { + transform: scale(1.5); +} +.error-content { + display: flex; + flex-direction: column; + align-items: center; + margin: 30px 0; + background-color: rgba(230, 87, 68, 0.922); +} +.error-content span { + background-color: rgba(230, 87, 68, 0.922); + height: auto; + font-weight: bold; + width: 100%; +} +.error-content span > * { + width: 100%; + margin-top: 10px; + background-color: rgba(230, 87, 68, 0.922); +} + +/* ALERT */ +.alert-dialog { + position: relative; + width: 500px; + height: 100px; + padding: 40px; + border-radius: 5px; + overflow: hidden; + cursor: default; + background-color: rgb(226, 171, 21); +} +.alert-header { + display: flex; + align-items: center; + justify-content: space-between; + font-weight: bolder; + font-size: 2em; + text-align: center; + height: fit-content; + background-color: rgb(226, 171, 21); +} +.alert-header svg { + width: 1.3em; + height: 1.3em; + background-color: rgb(226, 171, 21); + margin-right: 20px; +} +.alert-header h3 { + text-align: start; + width: 100%; + background-color: rgb(226, 171, 21); +} +.alert-header .alert-close { + width: 15px; + cursor: pointer; + border: none; + outline: none; + padding: 5px; + font-size: 0.8em; + border-radius: 5px; + color: var(--white); + background-color: rgb(226, 171, 21); +} +.alert-header .alert-close:hover { + transform: scale(1.5); +} +.alert-content { + display: flex; + flex-direction: column; + align-items: center; + margin: 30px 0; + background-color: rgb(226, 171, 21); +} +.alert-content span { + background-color: rgb(226, 171, 21); + height: auto; + font-weight: bold; + width: 100%; +} +.alert-content span > * { + width: 100%; + margin-top: 10px; + background-color: rgb(226, 171, 21); +} + +/* FORM */ +#showEndDate { + display: none !important; +} +#showEndDate.is-visible { + display: flex !important; +} +#showExpiration { + display: none !important; +} +#showExpiration.is-visible { + display: block !important; +} +#previousTime { + display: none !important; +} +#previousTime.is-visible { + display: flex !important; +} +.spanWeekday { + display: none; +} +#showEndDate { + display: flex; + flex-direction: column; + margin: 0; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@media screen and (max-width: 1510px) { + .calendar { + width: 980px; + } + .calendar .weekdays .weekday { + width: 130px; + font-size: 0.8em; + margin-bottom: 4px; + } + .calendar .months .month .day { + width: 130px; + height: 130px; + } +} + +@media screen and (max-width: 1000px) { + body { + width: 100vw; + flex-direction: column; + } + .header { + max-width: 100%; + width: 100vw; + display: flex; + flex-direction: column; + } + .header .h1 { + font-size: 2.9em; + transform: rotate(360deg); + margin: 10px 0; + text-align: center; + letter-spacing: 20px; + width: 100%; + } + .header .btn-add { + width: 100%; + } + .header .btn-add .open-modal { + height: auto; + } + .header .btn-add .open-modal:hover { + transform: none; + } + .calendar .months .month .day { + width: 100%; + height: fit-content; + min-height: 150px; + font-size: 1.1em; + display: flex; + flex-flow: row wrap; + border-radius: 5px; + justify-content: flex-start; + } + .calendar { + max-width: 100%; + margin-top: 10px; + } + .calendar .weekdays { + display: none; + } + .calendar .months .month .day.empty { + display: none; + } + .calendar .display { + font-size: 1.2em; + width: 60%; + min-width: 100% !important; + } + .calendar .display #monthDisplay { + width: 50%; + margin: 0 60px; + } + .calendar .display .previous-btn, + .calendar .display .next-btn { + font-size: 1.6em; + } + .calendar .display .previous-btn:hover, + .calendar .display .next-btn:hover { + font-size: 1.6em; + transform: none; + } + .calendar .months .month .day .day-event { + min-width: 220px; + margin-right: 5px; + margin-bottom: 5px; + flex-grow: 1; + } + .calendar .months .month .day .number-day { + width: 100%; + height: fit-content; + display: inline-flex; + grid-column-start: 1; + grid-column-end: 5; + } + .spanWeekday { + display: flex; + } + .modal .modal-dialog { + margin-left: -30px; + width: 60vw; + } + .modal-header h3 { + font-size: 0.7em; + } + div.form-data > p { + margin-left: 30px; + font-size: 0.7em; + } + .modal-footer { + flex-direction: column; + align-items: center; + } + .modal-content { + min-height: fit-content; + } + .modal-footer .btn-save, + .modal-footer .btn-cancel, + .modal-footer .btn-del { + padding: 4px 10px; + border-radius: 5px; + font-size: 0.7em; + margin-top: 5px; + } + .modal-content .form .form-data .div-input input, + .modal-content .form .form-data .div-input select, + .modal-content .form .form-data .div-input textarea { + font-size: 0.7em; + padding: 2px 4px; + } + label { + font-size: 0.7em !important; + } + input[type="checkbox"] { + width: 1em; + height: 1em; + margin-top: 0; + } +} + +@media screen and (max-width: 480px) { + .header .h1 { + font-size: 1.4em; + } +} + +@media screen and (max-width: 320px) { + .header .h1 { + font-size: 1.2em; + letter-spacing: 10px; + } + .calendar .display .month-display { + font-size: 1em; + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..bad1387 --- /dev/null +++ b/index.html @@ -0,0 +1,320 @@ + + + + + + +${getStrDisplayDate(currentDate, userLang)}
`; + loadEvents(); + initMonthButtons(); +} + +function addDay(element, day, month, year, emptyDays) { + const dayOfWeek = new Date(year, month, day).getDay(); + element.innerHTML = `${day - emptyDays} - ${weekDays[dayOfWeek]}
`; + element.setAttribute('data-day', day - emptyDays); + element.setAttribute('data-month', month); + element.setAttribute('data-year', year); + element.setAttribute('data-empty', emptyDays); + element.setAttribute('data-open', "addModal"); +} + + +loadMonths(); +initializeModals(); +threadPendingTasks(); +threadRemindTasks(); + + + diff --git a/modals.js b/modals.js new file mode 100644 index 0000000..2772915 --- /dev/null +++ b/modals.js @@ -0,0 +1,224 @@ +const isVisible = "is-visible"; + +// MODALS +function initModalCreation() { + + const openAddModal = document.querySelectorAll('[data-open="addModal"]'); + const closeAddModal = document.querySelectorAll('[data-close="addModal"]'); + + + for (const el of openAddModal) { + el.addEventListener("click", function (e) { + const { hours, minutes } = getTimeNow(); + let date; + + if (e.target.classList.contains("day") || e.target.classList.contains("open-modal")) { + + if (el.hasAttribute('data-day')) { + const year = el.getAttribute('data-year'); + const day = el.getAttribute('data-day'); + const month = el.getAttribute('data-month'); + const emptyDays = el.getAttribute('data-empty'); + date = new Date(year, month, day, 09, 00, 00); + if (day == currentDay) { + date = new Date(year, month, day, hours, minutes); + } + } else { + date = new Date(currentYear, navigator, 1, 09, 00, 00); + if (navigator == currentMonth) { + date = new Date(currentYear, navigator, currentDay, hours, minutes); + } + } + + const { strDate, strTimeInit, strTimeEnd } = getFormValues(date); + setDatesInForm(strDate, strTimeInit, strTimeEnd); + hideEndDateAndRemind(); + document.getElementById("addModal").classList.add(isVisible); + document.body.style.overflow = 'hidden'; + } + }); + } + + for (const el of closeAddModal) { + el.addEventListener("click", function () { + this.parentElement.parentElement.parentElement.classList.remove(isVisible); + document.body.style.overflow = 'auto'; + }); + } + document.addEventListener("click", e => { + if (e.target == document.querySelector("#addModal.is-visible")) { + document.querySelector("#addModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = 'auto'; + } + }); + document.addEventListener("keyup", e => { + if (e.key == "Escape" && document.querySelector("#addModal.is-visible")) { + document.querySelector("#addModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = 'auto'; + } + }); +} +function initModalEvent() { + + const openEventModal = document.querySelectorAll('[data-open="eventModal"]'); + const closeEventModal = document.querySelectorAll('[data-close="eventModal"]'); + + for (const el of openEventModal) { + el.addEventListener("click", function () { + document.querySelector("#eventModal").classList.add(isVisible); + document.body.style.overflow = 'hidden'; + }); + } + + for (const el of closeEventModal) { + el.addEventListener("click", function () { + this.parentElement.parentElement.parentElement.classList.remove(isVisible); + document.body.style.overflow = 'auto'; + }); + } + + document.addEventListener("click", e => { + if (e.target == document.querySelector("#eventModal.is-visible")) { + document.querySelector("#eventModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = 'auto'; + } + }); + document.addEventListener("keyup", e => { + if (e.key == "Escape" && document.querySelector("#eventModal.is-visible")) { + document.querySelector("#eventModal.is-visible").classList.remove(isVisible); + document.body.style.overflow = 'auto'; + } + }); +} +function initRemindAlert() { + + const closeRemindAlert = document.querySelectorAll('[data-close="remindAlert"]'); + + for (const el of closeRemindAlert) { + el.addEventListener("click", function () { + this.parentElement.parentElement.parentElement.classList.remove(isVisible); + }); + } + + document.addEventListener("click", e => { + if (e.target == document.querySelector("#remindAlert.is-visible")) { + document.querySelector("#remindAlert.is-visible").classList.remove(isVisible); + } + }); + document.addEventListener("keyup", e => { + if (e.key == "Escape" && document.querySelector("#remindAlert.is-visible")) { + document.querySelector("#remindAlert.is-visible").classList.remove(isVisible); + } + }); +} +function initEndAlert() { + + const closeEndAlert = document.querySelectorAll('[data-close="endAlert"]'); + + for (const el of closeEndAlert) { + el.addEventListener("click", function () { + this.parentElement.parentElement.parentElement.classList.remove(isVisible); + }); + } + + document.addEventListener("click", e => { + if (e.target == document.querySelector("#endAlert.is-visible")) { + document.querySelector("#endAlert.is-visible").classList.remove(isVisible); + } + }); + document.addEventListener("keyup", e => { + if (e.key == "Escape" && document.querySelector("#endAlert.is-visible")) { + document.querySelector("#endAlert.is-visible").classList.remove(isVisible); + } + }); +} +function initErrors() { + + const closeEndAlert = document.querySelectorAll('[data-close="error"]'); + + for (const el of closeEndAlert) { + el.addEventListener("click", function () { + this.parentElement.parentElement.parentElement.classList.remove(isVisible); + }); + } + + document.addEventListener("click", e => { + if (e.target == document.querySelector("#error.is-visible")) { + document.querySelector("#error.is-visible").classList.remove(isVisible); + } + }); + document.addEventListener("keyup", e => { + if (e.key == "Escape" && document.querySelector("#error.is-visible")) { + document.querySelector("#error.is-visible").classList.remove(isVisible); + } + }); +} +function initializeModals() { + initModalCreation(); + initModalEvent(); + initRemindAlert(); + initEndAlert(); + initErrors(); + initForm(); +} + +// OPENERS +function openEvent(e) { + let tempEvent; + const events = getStorage("events"); + events && + events.forEach((event) => { + if (event.id === e.target.getAttribute('event-id')) { + tempEvent = event; + } + }); + setEventData(tempEvent); +} +function openError(type) { + const content = document.querySelector("#errorContent"); + content.innerHTML = ''; + const text = document.createElement('span'); + + switch (type) { + case 1: + text.textContent = 'Error . . . Cannot create an event on a date before the current date. You have 10 minutes to setting up the event'; + break; + case 2: + text.textContent = 'Error . . . The end date of the event must be later than the start date.'; + break; + case 3: + text.textContent = 'Error . . . The duration of the event cannot exceed two months.'; + break; + case 4: + text.textContent = 'Error . . . The month from which you are trying to create the event is incorrect. Please select the correct month before creating the event.'; + break; + } + content.append(text); + document.querySelector("#error").classList.add(isVisible); +} +function openAlert(task, type) { + if (type === "end") { + const content = document.querySelector("#endAlertContent"); + content.innerHTML = ''; + const text = document.createElement('span'); + text.innerHTML = `La tarea ${task.title} ha finalizado`; + content.append(text); + document.querySelector("#endAlert").classList.add(isVisible); + const disabledTasks = document.querySelectorAll(`[event-id=${task.id}]`); + for (const task of disabledTasks) { + task.style.backgroundColor = "rgb(203 55 55)"; + } + } else { + const content = document.querySelector("#remindAlertContent"); + content.innerHTML = ''; + const text = document.createElement('span'); + text.innerHTML = `Quedan ${task.time} minutos para terminar la tarea ${task.title}`; + content.append(text); + document.querySelector("#remindAlert").classList.add(isVisible); + const remindedTasks = document.querySelectorAll(`[event-id=${task.id}]`); + for (const task of remindedTasks) { + task.style.backgroundColor = "orange"; + } + + } +} \ No newline at end of file diff --git a/nav.js b/nav.js new file mode 100644 index 0000000..29f9294 --- /dev/null +++ b/nav.js @@ -0,0 +1,31 @@ +let navigator; +const previousBtn = document.querySelector('#previousMonth'); +const nextBtn = document.querySelector('#nextMonth'); +const monthDisplay = document.querySelector("#monthDisplay"); + +function changeMonth(action) { + const domMonth = document.getElementById(navigator); + changeStyles("off", domMonth); + let newDate; + + if (action === 'up') { + navigator++; + setVisibility('visible', previousBtn); + if (navigator === 11) setVisibility("hidden", nextBtn); + + } else { + navigator--; + setVisibility('visible', nextBtn); + if (navigator === 0) newDate = new Date(currentYear, navigator, currentMonth); + if (navigator === 0) setVisibility("hidden", previousBtn); + } + newDate = new Date(currentYear, navigator, 1, 12, 00, 00); + monthDisplay.innerHTML = `${getStrDisplayDate(newDate, userLang)}
`; + changeStyles("on", domMonth, navigator); +} + +function initMonthButtons() { + setVisibility("hidden", previousBtn); + previousBtn.addEventListener('click', () => changeMonth('down')); + nextBtn.addEventListener('click', () => changeMonth('up')); +} diff --git a/thread.js b/thread.js new file mode 100644 index 0000000..6b8fd3b --- /dev/null +++ b/thread.js @@ -0,0 +1,33 @@ +function threadRemindTasks() { + setInterval(() => { + reminders.forEach(task => { + if (task.remind) { + const now = new Date(); + const end = new Date(task.endDate); + const timeRemind = end.getTime() - (task.time * 60 * 1000); + const reminded = now > new Date(timeRemind); + + if (reminded) { + remindEvent(task); + task.remind = false; + } + } + }) + }, 1000) +} + +function threadPendingTasks() { + setInterval(() => { + allEvents.forEach((task) => { + if (!task.finished) { + const init = new Date(task.initDate); + const end = new Date(task.endDate); + const now = new Date(Date.now()); + if (now > end) { + disableEvent(task); + task.finished = true; + } + } + }) + }, 1000); +} \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..7c64c28 --- /dev/null +++ b/utils.js @@ -0,0 +1,127 @@ +addCero = function (num) { + return (num < 10 ? '0' : '') + num; +}; + +// STORAGE +function saveStorage(key, el) { + localStorage.setItem(key, JSON.stringify(el)); +} +function getStorage(key) { + return JSON.parse(localStorage.getItem(key)); +} +function removeStorage(key) { + localStorage.removeItem(key); +} +function reloadEvents(event) { + allEvents.push(event); +} +function reloadReminderEvents(event) { + reminders.push(event); +} + +// UTIL +function arrayRemove(arr, id) { + return arr.filter(event => { + return event.id !== id; + }); +} +function getEmptyDaysInMonth(year, month, day, lang) { + const date = new Date(year, month, day); + const dateStr = date.toLocaleDateString(lang, { + weekday: "long", + }); + return weekDays.indexOf(dateStr); +} +function changeStyles(action, element, nav) { + if (action === 'on') { + document.getElementById(nav).style.opacity = '1'; + document.getElementById(nav).style.height = 'auto'; + document.getElementById(nav).style.lineHeight = 'inherit'; + document.getElementById(nav).style.overflow = 'visible'; + } else { + element.style.opacity = '0'; + element.style.height = '0'; + element.style.lineHeight = '0'; + element.style.overflow = 'hidden'; + } +} +function setVisibility(action, element) { + element.style.visibility = action; +} + +// DATETIME + +// impresión de fecha en pantalla +function getStrDisplayDate(date, lang) { + return Intl.DateTimeFormat(lang, { dateStyle: 'full' }).format(date); +} +// impresión en pantalla +function getStrDisplayDateTime(date, lang) { + options = { + weekday: 'long', + year: 'numeric', month: 'long', day: 'numeric', + hour: 'numeric', minute: 'numeric', + hour12: false, +}; + return Intl.DateTimeFormat(lang, options).format(date); +} +// Extraer horas y minutos a partir inputs "time" +function extractTime(time) { + if (time) { + const data = time.split(':'); + const hours = data[0]; + const minutes = data[1]; + return { hours, minutes }; + } else { + return { hours: "21", minutes: "30" } + } +} +// 14:56 extraer horas y minutos ahora +function getTimeNow() { + const date = new Date(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + return { hours, minutes }; +} +// Configurar valores de formulario al abrir modal -> initModalCreation +function getFormValues(date) { + + const strDate = date.getFullYear() + + '-' + addCero(date.getMonth() + 1) + + '-' + addCero(date.getDate()); + const strTimeInit = addCero(date.getHours()) + + ':' + addCero(date.getMinutes()); + const strTimeEnd = addCero(date.getHours() + 2) + + ':' + addCero(date.getMinutes()); + + return { strDate, strTimeInit, strTimeEnd }; +} +// Crear fecha a partir de los valores del formulario -> addEvent +function setDateFormValues(date, { hours, minutes }) { + const dateOnly = new Date(date); + return new Date(dateOnly.getFullYear(), dateOnly.getMonth(), dateOnly.getDate(), hours, minutes); +} +// Crear string de fecha para guardar en JSON +function toIsoString(date) { + var tzo = -date.getTimezoneOffset(), + dif = tzo >= 0 ? '+' : '-', + pad = function (num) { + return (num < 10 ? '0' : '') + num; + }; + + return date.getFullYear() + + '-' + pad(date.getMonth() + 1) + + '-' + pad(date.getDate()) + + 'T' + pad(date.getHours()) + + ':' + pad(date.getMinutes()) + + ':' + pad(date.getSeconds()) + + dif + pad(Math.floor(Math.abs(tzo) / 60)) + + ':' + pad(Math.abs(tzo) % 60); +} +// Crear string de hora con zeros a partir de un string de fecha +function getFullTimeFromString(str) { + const date = new Date(str); + const hour = date.getHours(); + const minute = date.getMinutes(); + return `${addCero(hour)}:${addCero(minute)}`; +}