calendar-lucas / index.html
gallabs's picture
Add 2 files
7cbaf8f verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>General Labs - Calendário</title>
<script src="https://cdn.tailwindcss.com"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<style>
.event-planning {
background-color: #166534;
}
.event-meeting {
background-color: #164e63;
}
.event-reports {
background-color: #854d0e;
}
.event-theme {
background-color: #991b1b;
}
.event-birthday {
background-color: #0d9488;
}
.sidebar-item:hover .sidebar-tooltip {
display: block;
}
.calendar-day:hover {
background-color: #1e293b;
}
.event-item:hover {
transform: translateX(5px);
}
.dropdown-notifications {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.dropdown-notifications.show {
max-height: 500px;
}
.modal {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.modal.hidden {
opacity: 0;
pointer-events: none;
transform: translateY(-20px);
}
.week-view .hour-slot {
height: 60px;
border-bottom: 1px solid #374151;
}
.day-view .hour-slot {
height: 80px;
border-bottom: 1px solid #374151;
}
.list-view .event-item {
border-left: 4px solid;
}
@media (max-width: 1024px) {
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 40;
}
.sidebar-overlay.open {
display: block;
}
}
</style>
</head>
<body class="bg-gray-900 text-gray-200">
<!-- Layout Container -->
<div class="flex h-screen overflow-hidden">
<!-- Sidebar Overlay (Mobile) -->
<div id="sidebarOverlay" class="sidebar-overlay"></div>
<!-- Sidebar -->
<aside
id="sidebar"
class="sidebar w-64 bg-gray-800 border-r border-gray-700 flex-shrink-0 fixed lg:static h-full z-50"
>
<div class="p-4 border-b border-gray-700">
<h1 class="text-xl font-bold text-white flex items-center">
<span
class="bg-blue-600 w-8 h-8 rounded flex items-center justify-center mr-2"
>
<i class="fas fa-flask text-white"></i>
</span>
General Labs
</h1>
</div>
<div class="p-4 overflow-y-auto h-[calc(100%-65px)]">
<ul class="space-y-2">
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-tachometer-alt w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Dashboards</span>
<span
class="bg-blue-600 text-xs font-medium px-2 py-0.5 rounded-full ml-auto"
>New</span
>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg bg-gray-700 text-white group"
>
<i class="fas fa-th w-6 text-white"></i>
<span class="ml-3">Apps</span>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-chart-pie w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Consolidados</span>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-cubes w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Aplicativos</span>
<span
class="bg-red-600 text-xs font-medium px-2 py-0.5 rounded-full ml-auto"
>Hot</span
>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-user-plus w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Cadastros</span>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-tasks w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Processos</span>
</a>
</li>
<li>
<a
href="#"
class="flex items-center p-2 text-base font-normal rounded-lg text-white hover:bg-gray-700 group"
>
<i
class="fas fa-layer-group w-6 text-gray-400 group-hover:text-white"
></i>
<span class="ml-3">Layouts</span>
</a>
</li>
</ul>
</div>
</aside>
<!-- Main Content -->
<div class="flex-1 overflow-auto">
<!-- Top Navigation -->
<nav
class="bg-gray-800 border-b border-gray-700 px-4 py-3 flex items-center justify-between"
>
<!-- Mobile menu button -->
<button
id="sidebarToggle"
class="lg:hidden text-gray-400 hover:text-white"
>
<i class="fas fa-bars text-xl"></i>
</button>
<!-- Search Bar -->
<div class="flex-1 mx-4 lg:mx-8">
<div class="relative max-w-md">
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
>
<i class="fas fa-search text-gray-400"></i>
</div>
<input
type="text"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full pl-10 p-2.5 focus:ring-blue-500 focus:border-blue-500"
placeholder="Pesquisar..."
/>
</div>
</div>
<!-- Right Icons -->
<div class="flex items-center space-x-4">
<!-- Language Selector -->
<div class="flex items-center space-x-1">
<button class="text-gray-400 hover:text-white">
<span class="text-xl">🇧🇷</span>
</button>
<button class="text-gray-400 hover:text-white">
<span class="text-xl">🇺🇸</span>
</button>
</div>
<!-- Notifications -->
<div class="relative">
<button
id="notificationsButton"
class="text-gray-400 hover:text-white relative"
>
<i class="fas fa-bell text-xl"></i>
<span
class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center"
>3</span
>
</button>
<!-- Notifications Dropdown -->
<div
id="notificationsDropdown"
class="dropdown-notifications absolute right-0 mt-2 w-72 bg-gray-800 rounded-md shadow-lg z-50 border border-gray-700"
>
<div class="p-3 border-b border-gray-700">
<h3 class="text-white font-medium">Notificações</h3>
</div>
<div class="divide-y divide-gray-700">
<a href="#" class="flex items-start p-3 hover:bg-gray-700">
<div class="bg-blue-600 rounded-full p-2 mr-3">
<i class="fas fa-envelope text-white text-sm"></i>
</div>
<div>
<p class="text-white">Nova mensagem de Juliana</p>
<p class="text-gray-400 text-sm">2 minutos atrás</p>
</div>
</a>
<a href="#" class="flex items-start p-3 hover:bg-gray-700">
<div class="bg-yellow-600 rounded-full p-2 mr-3">
<i
class="fas fa-exclamation-triangle text-white text-sm"
></i>
</div>
<div>
<p class="text-white">
Evento 'Client Meeting' começa em 1h
</p>
<p class="text-gray-400 text-sm">30 minutos atrás</p>
</div>
</a>
<a href="#" class="flex items-start p-3 hover:bg-gray-700">
<div class="bg-green-600 rounded-full p-2 mr-3">
<i class="fas fa-calendar-plus text-white text-sm"></i>
</div>
<div>
<p class="text-white">Novo evento adicionado por Anna</p>
<p class="text-gray-400 text-sm">1 hora atrás</p>
</div>
</a>
</div>
<div class="p-3 border-t border-gray-700 text-center">
<a href="#" class="text-blue-400 text-sm hover:underline"
>Ver todas</a
>
</div>
</div>
</div>
<!-- Settings -->
<button class="text-gray-400 hover:text-white">
<i class="fas fa-cog text-xl"></i>
</button>
<!-- Profile -->
<div class="flex items-center">
<img
class="w-8 h-8 rounded-full"
src="https://randomuser.me/api/portraits/women/44.jpg"
alt="User photo"
/>
</div>
</div>
</nav>
<!-- Breadcrumb and Page Title -->
<div class="px-6 py-4 border-b border-gray-700">
<nav class="flex" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-3">
<li class="inline-flex items-center">
<a
href="#"
class="inline-flex items-center text-sm font-medium text-gray-400 hover:text-white"
>
<i class="fas fa-home mr-2"></i>
Home
</a>
</li>
<li>
<div class="flex items-center">
<i class="fas fa-chevron-right text-gray-600 text-xs"></i>
<a
href="#"
class="ml-1 text-sm font-medium text-gray-400 hover:text-white md:ml-2"
>Apps</a
>
</div>
</li>
<li aria-current="page">
<div class="flex items-center">
<i class="fas fa-chevron-right text-gray-600 text-xs"></i>
<span class="ml-1 text-sm font-medium text-white md:ml-2"
>Calendar</span
>
</div>
</li>
</ol>
</nav>
<h1 class="text-2xl font-bold text-white mt-2">CALENDAR</h1>
</div>
<!-- Main Content Area -->
<div class="p-6 flex flex-col lg:flex-row">
<!-- Left Column - Upcoming Events -->
<div class="w-full lg:w-1/4 lg:pr-4 mb-6 lg:mb-0">
<div class="bg-gray-800 rounded-lg p-4 h-full">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-white">
Eventos Agendados
</h2>
<button
id="createEventButton"
class="bg-blue-700 hover:bg-blue-800 text-white px-3 py-1 rounded text-sm"
>
+ Criar Novo Evento
</button>
</div>
<p class="text-gray-400 text-sm mb-4">
Arraste e solte seu evento ou clique no calendário
</p>
<div class="mb-6">
<h3 class="text-sm font-medium text-gray-300 mb-2">Legenda</h3>
<div class="space-y-2">
<div class="flex items-center">
<span class="w-3 h-3 rounded-full bg-green-800 mr-2"></span>
<span class="text-gray-300 text-sm"
>Planejamento de Novo Evento</span
>
</div>
<div class="flex items-center">
<span class="w-3 h-3 rounded-full bg-blue-900 mr-2"></span>
<span class="text-gray-300 text-sm">Reunião</span>
</div>
<div class="flex items-center">
<span
class="w-3 h-3 rounded-full bg-yellow-800 mr-2"
></span>
<span class="text-gray-300 text-sm"
>Geração de Relatórios</span
>
</div>
<div class="flex items-center">
<span class="w-3 h-3 rounded-full bg-red-800 mr-2"></span>
<span class="text-gray-300 text-sm">Criar Novo Tema</span>
</div>
</div>
</div>
<div>
<h3 class="text-sm font-medium text-gray-300 mb-2">
Próximos Eventos
</h3>
<div id="upcomingEventsList" class="space-y-3">
<!-- Events will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<!-- Right Column - Calendar -->
<div class="w-full lg:w-3/4">
<div class="bg-gray-800 rounded-lg p-4 h-full">
<div
class="flex flex-col md:flex-row md:items-center md:justify-between mb-6"
>
<div class="flex items-center space-x-2 mb-4 md:mb-0">
<button
id="prevPeriodButton"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded"
>
<i class="fas fa-chevron-left"></i>
</button>
<button
id="todayButton"
class="bg-blue-700 hover:bg-blue-800 text-white px-4 py-1 rounded"
>
Hoje
</button>
<button
id="nextPeriodButton"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded"
>
<i class="fas fa-chevron-right"></i>
</button>
<h3
id="currentPeriodTitle"
class="text-lg font-semibold text-white ml-2"
>
Abril 2025
</h3>
</div>
<div class="flex space-x-1 bg-gray-700 p-1 rounded">
<button
data-view="month"
class="view-button px-3 py-1 text-sm rounded text-white bg-gray-800"
>
Mês
</button>
<button
data-view="week"
class="view-button px-3 py-1 text-sm rounded text-gray-300 hover:text-white"
>
Semana
</button>
<button
data-view="day"
class="view-button px-3 py-1 text-sm rounded text-gray-300 hover:text-white"
>
Dia
</button>
<button
data-view="list"
class="view-button px-3 py-1 text-sm rounded text-gray-300 hover:text-white"
>
Lista
</button>
</div>
</div>
<!-- Calendar Views -->
<div id="calendarViews">
<!-- Month View (default) -->
<div id="monthView">
<!-- Calendar Header -->
<div class="grid grid-cols-7 gap-1 mb-2">
<div class="text-center text-gray-400 text-sm font-medium">
Dom
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Seg
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Ter
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Qua
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Qui
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Sex
</div>
<div class="text-center text-gray-400 text-sm font-medium">
Sáb
</div>
</div>
<!-- Calendar Grid -->
<div id="monthGrid" class="grid grid-cols-7 gap-1">
<!-- Days will be populated by JavaScript -->
</div>
</div>
<!-- Week View -->
<div id="weekView" class="hidden week-view">
<div id="weekHeader" class="grid grid-cols-8 gap-1 mb-2">
<div class="text-center text-gray-400 text-sm font-medium">
Hora
</div>
<!-- Days will be populated by JavaScript -->
</div>
<div id="weekGrid" class="grid grid-cols-8 gap-1">
<!-- Time slots will be populated by JavaScript -->
</div>
</div>
<!-- Day View -->
<div id="dayView" class="hidden day-view">
<div
id="dayHeader"
class="text-center text-gray-400 text-sm font-medium mb-2"
>
<!-- Day will be populated by JavaScript -->
</div>
<div id="dayGrid">
<!-- Time slots will be populated by JavaScript -->
</div>
</div>
<!-- List View -->
<div id="listView" class="hidden list-view">
<div
id="listHeader"
class="text-center text-gray-400 text-sm font-medium mb-2"
>
<!-- Period will be populated by JavaScript -->
</div>
<div id="listGrid" class="space-y-2">
<!-- Events will be populated by JavaScript -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Event Modal -->
<div
id="eventModal"
class="modal fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 hidden"
>
<div class="bg-gray-800 rounded-lg w-full max-w-md mx-4">
<div
class="p-4 border-b border-gray-700 flex justify-between items-center"
>
<h3 id="modalTitle" class="text-lg font-semibold text-white">
Novo Evento
</h3>
<button id="closeModalButton" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<form id="eventForm">
<input type="hidden" id="eventId" />
<div class="mb-4">
<label
for="eventTitle"
class="block text-sm font-medium text-gray-300 mb-1"
>Título do Evento</label
>
<input
type="text"
id="eventTitle"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div class="mb-4">
<label
for="eventCategory"
class="block text-sm font-medium text-gray-300 mb-1"
>Categoria</label
>
<select
id="eventCategory"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
>
<option value="event-planning">
Planejamento de Novo Evento
</option>
<option value="event-meeting">Reunião</option>
<option value="event-reports">Geração de Relatórios</option>
<option value="event-theme">Criar Novo Tema</option>
<option value="event-birthday">Festa de Aniversário</option>
</select>
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label
for="eventStartDate"
class="block text-sm font-medium text-gray-300 mb-1"
>Data Início</label
>
<input
type="date"
id="eventStartDate"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<label
for="eventStartTime"
class="block text-sm font-medium text-gray-300 mb-1"
>Hora Início</label
>
<input
type="time"
id="eventStartTime"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label
for="eventEndDate"
class="block text-sm font-medium text-gray-300 mb-1"
>Data Fim</label
>
<input
type="date"
id="eventEndDate"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label
for="eventEndTime"
class="block text-sm font-medium text-gray-300 mb-1"
>Hora Fim</label
>
<input
type="time"
id="eventEndTime"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div class="mb-4">
<label
for="eventDescription"
class="block text-sm font-medium text-gray-300 mb-1"
>Descrição</label
>
<textarea
id="eventDescription"
rows="3"
class="bg-gray-700 border border-gray-600 text-white text-sm rounded-lg block w-full p-2.5 focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<div class="flex justify-end space-x-2">
<button
type="button"
id="deleteEventButton"
class="text-white bg-red-700 hover:bg-red-800 px-4 py-2 rounded-lg hidden"
>
Excluir
</button>
<button
type="button"
id="cancelEventButton"
class="text-white bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg"
>
Cancelar
</button>
<button
type="submit"
id="saveEventButton"
class="text-white bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded-lg"
>
Salvar
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Sample events data
let events = [
{
id: "1",
title: "Evento de dia inteiro",
category: "event-meeting",
start: "2025-04-01",
end: "2025-04-01",
allDay: true,
description: "Evento que dura o dia todo",
},
{
id: "2",
title: "Visitar Curso Online",
category: "event-reports",
start: "2025-04-08T09:00:00",
end: "2025-04-09T17:00:00",
allDay: false,
description: "Participar do curso online sobre desenvolvimento web",
},
{
id: "3",
title: "Reunião com equipe",
category: "event-planning",
start: "2025-04-11T14:00:00",
end: "2025-04-11T15:30:00",
allDay: false,
description: "Reunião semanal com a equipe de desenvolvimento",
},
{
id: "4",
title: "Festa de Aniversário",
category: "event-birthday",
start: "2025-04-13T19:00:00",
end: "2025-04-14T01:00:00",
allDay: false,
description: "Festa de aniversário do colega de trabalho",
},
{
id: "5",
title: "Evento Repetitivo",
category: "event-theme",
start: "2025-04-16T10:00:00",
end: "2025-04-18T16:00:00",
allDay: false,
description: "Evento que se repete por vários dias",
},
{
id: "6",
title: "Planejamento de Estratégia Semanal",
category: "event-planning",
start: "2025-04-21T09:00:00",
end: "2025-04-21T11:00:00",
allDay: false,
description: "Planejamento da estratégia para a semana",
},
{
id: "7",
title: "Reunião com o Cliente Alexis",
category: "event-theme",
start: "2025-05-04T13:00:00",
end: "2025-05-04T15:00:00",
allDay: false,
description: "Reunião importante com o cliente principal",
},
{
id: "8",
title: "Projeto Velzon",
category: "event-meeting",
start: "2025-05-05T08:00:00",
end: "2025-05-05T17:00:00",
allDay: true,
description: "Início do novo projeto Velzon",
},
{
id: "9",
title: "Dia Mundial do Braille",
category: "event-planning",
start: "2022-01-03T21:00:00",
end: "2022-01-04T00:00:00",
allDay: false,
description: "Evento comemorativo",
},
{
id: "10",
title: "Dia Mundial da Hanseníase",
category: "event-reports",
start: "2022-01-29T21:00:00",
end: "2022-01-30T00:00:00",
allDay: false,
description: "Evento comemorativo",
},
{
id: "11",
title: "Dia Internacional da Língua Materna",
category: "event-birthday",
start: "2022-02-20T21:00:00",
end: "2022-02-21T00:00:00",
allDay: false,
description: "Evento comemorativo",
},
{
id: "12",
title: "Reunião de Equipe",
category: "event-meeting",
start: "2022-02-21T21:00:00",
end: "2022-02-22T00:00:00",
allDay: false,
description: "Reunião mensal da equipe",
},
];
// Calendar state
let currentView = "month";
let currentDate = new Date(2025, 3, 1); // April 2025
// DOM Elements
const sidebarToggle = document.getElementById("sidebarToggle");
const sidebar = document.getElementById("sidebar");
const sidebarOverlay = document.getElementById("sidebarOverlay");
const notificationsButton = document.getElementById(
"notificationsButton"
);
const notificationsDropdown = document.getElementById(
"notificationsDropdown"
);
const createEventButton = document.getElementById("createEventButton");
const upcomingEventsList = document.getElementById("upcomingEventsList");
const currentPeriodTitle = document.getElementById("currentPeriodTitle");
const prevPeriodButton = document.getElementById("prevPeriodButton");
const nextPeriodButton = document.getElementById("nextPeriodButton");
const todayButton = document.getElementById("todayButton");
const viewButtons = document.querySelectorAll(".view-button");
const monthView = document.getElementById("monthView");
const monthGrid = document.getElementById("monthGrid");
const weekView = document.getElementById("weekView");
const weekHeader = document.getElementById("weekHeader");
const weekGrid = document.getElementById("weekGrid");
const dayView = document.getElementById("dayView");
const dayHeader = document.getElementById("dayHeader");
const dayGrid = document.getElementById("dayGrid");
const listView = document.getElementById("listView");
const listHeader = document.getElementById("listHeader");
const listGrid = document.getElementById("listGrid");
const eventModal = document.getElementById("eventModal");
const modalTitle = document.getElementById("modalTitle");
const closeModalButton = document.getElementById("closeModalButton");
const eventForm = document.getElementById("eventForm");
const eventIdInput = document.getElementById("eventId");
const eventTitleInput = document.getElementById("eventTitle");
const eventCategoryInput = document.getElementById("eventCategory");
const eventStartDateInput = document.getElementById("eventStartDate");
const eventStartTimeInput = document.getElementById("eventStartTime");
const eventEndDateInput = document.getElementById("eventEndDate");
const eventEndTimeInput = document.getElementById("eventEndTime");
const eventDescriptionInput = document.getElementById("eventDescription");
const deleteEventButton = document.getElementById("deleteEventButton");
const cancelEventButton = document.getElementById("cancelEventButton");
const saveEventButton = document.getElementById("saveEventButton");
// Initialize the calendar
document.addEventListener("DOMContentLoaded", () => {
renderCalendar();
renderUpcomingEvents();
// Set today's date in the date inputs
const today = new Date();
const todayStr = today.toISOString().split("T")[0];
eventStartDateInput.value = todayStr;
eventEndDateInput.value = todayStr;
});
// Toggle sidebar on mobile
sidebarToggle.addEventListener("click", () => {
sidebar.classList.toggle("open");
sidebarOverlay.classList.toggle("open");
});
sidebarOverlay.addEventListener("click", () => {
sidebar.classList.remove("open");
sidebarOverlay.classList.remove("open");
});
// Toggle notifications dropdown
notificationsButton.addEventListener("click", () => {
notificationsDropdown.classList.toggle("show");
});
// Close dropdown when clicking outside
document.addEventListener("click", (event) => {
if (
!notificationsButton.contains(event.target) &&
!notificationsDropdown.contains(event.target)
) {
notificationsDropdown.classList.remove("show");
}
});
// View buttons
viewButtons.forEach((button) => {
button.addEventListener("click", () => {
currentView = button.dataset.view;
updateActiveViewButton();
renderCalendar();
});
});
// Navigation buttons
prevPeriodButton.addEventListener("click", () => {
navigatePeriod(-1);
});
nextPeriodButton.addEventListener("click", () => {
navigatePeriod(1);
});
todayButton.addEventListener("click", () => {
currentDate = new Date();
renderCalendar();
});
// Event modal
createEventButton.addEventListener("click", () => {
openEventModal();
});
closeModalButton.addEventListener("click", () => {
closeEventModal();
});
cancelEventButton.addEventListener("click", () => {
closeEventModal();
});
// Form submission
eventForm.addEventListener("submit", (e) => {
e.preventDefault();
saveEvent();
});
deleteEventButton.addEventListener("click", () => {
deleteEvent();
});
// Calendar functions
function renderCalendar() {
updatePeriodTitle();
switch (currentView) {
case "month":
renderMonthView();
break;
case "week":
renderWeekView();
break;
case "day":
renderDayView();
break;
case "list":
renderListView();
break;
}
}
function renderMonthView() {
// Hide other views and show month view
hideAllViews();
monthView.classList.remove("hidden");
// Clear the grid
monthGrid.innerHTML = "";
// Add day headers
const daysOfWeek = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
daysOfWeek.forEach((day) => {
const dayElement = document.createElement("div");
dayElement.className =
"text-center text-gray-400 text-sm font-medium";
dayElement.textContent = day;
monthGrid.appendChild(dayElement);
});
// Get first day of month and how many days to show from previous month
const firstDay = new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
1
);
const startingDayOfWeek = firstDay.getDay();
// Get last day of month and how many days to show from next month
const lastDay = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
0
);
const daysInMonth = lastDay.getDate();
// Calculate how many rows we need
const totalDaysToShow = startingDayOfWeek + daysInMonth;
const rowsNeeded = Math.ceil(totalDaysToShow / 7);
// Get previous month's days if needed
const prevMonthLastDay = new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
0
).getDate();
// Add days from previous month
for (let i = 0; i < startingDayOfWeek; i++) {
const dayElement = createDayElement(
prevMonthLastDay - startingDayOfWeek + i + 1,
true
);
monthGrid.appendChild(dayElement);
}
// Add days of current month
for (let i = 1; i <= daysInMonth; i++) {
const dayElement = createDayElement(i, false);
// Add events for this day
const dateStr = `${currentDate.getFullYear()}-${(
currentDate.getMonth() + 1
)
.toString()
.padStart(2, "0")}-${i.toString().padStart(2, "0")}`;
const dayEvents = getEventsForDate(dateStr);
if (dayEvents.length > 0) {
const eventsContainer = document.createElement("div");
eventsContainer.className = "text-xs mt-1 space-y-1";
dayEvents.forEach((event) => {
const eventElement = document.createElement("span");
eventElement.className = `inline-block ${event.category} text-white rounded px-1 truncate`;
eventElement.textContent = event.title;
eventElement.dataset.eventId = event.id;
eventElement.addEventListener("click", (e) => {
e.stopPropagation();
openEventModal(event.id);
});
eventsContainer.appendChild(eventElement);
});
dayElement.appendChild(eventsContainer);
}
monthGrid.appendChild(dayElement);
}
// Add days from next month if needed
const daysToAdd = rowsNeeded * 7 - (startingDayOfWeek + daysInMonth);
for (let i = 1; i <= daysToAdd; i++) {
const dayElement = createDayElement(i, true);
monthGrid.appendChild(dayElement);
}
}
function createDayElement(day, isOtherMonth) {
const dayElement = document.createElement("div");
dayElement.className = `calendar-day bg-gray-900 h-24 p-1 rounded cursor-pointer ${
isOtherMonth ? "text-gray-600" : "text-gray-300"
}`;
const dayNumber = document.createElement("div");
dayNumber.className = "text-right";
dayNumber.textContent = day;
dayElement.appendChild(dayNumber);
// Highlight today
const today = new Date();
if (
!isOtherMonth &&
currentDate.getFullYear() === today.getFullYear() &&
currentDate.getMonth() === today.getMonth() &&
day === today.getDate()
) {
dayNumber.className += " font-bold text-blue-400";
}
// Add click event to create new event
dayElement.addEventListener("click", () => {
const dateStr = `${currentDate.getFullYear()}-${(
currentDate.getMonth() + 1
)
.toString()
.padStart(2, "0")}-${day.toString().padStart(2, "0")}`;
openEventModal(null, dateStr);
});
return dayElement;
}
function renderWeekView() {
// Hide other views and show week view
hideAllViews();
weekView.classList.remove("hidden");
// Clear the grid
weekHeader.innerHTML = "";
weekGrid.innerHTML = "";
// Add time column header
const timeHeader = document.createElement("div");
timeHeader.className = "text-center text-gray-400 text-sm font-medium";
timeHeader.textContent = "Hora";
weekHeader.appendChild(timeHeader);
// Get start and end of week
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
// Add day headers
for (let i = 0; i < 7; i++) {
const day = new Date(weekStart);
day.setDate(weekStart.getDate() + i);
const dayHeader = document.createElement("div");
dayHeader.className = "text-center text-gray-400 text-sm font-medium";
const dayName = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"][
day.getDay()
];
dayHeader.textContent = `${dayName} ${day.getDate()}/${
day.getMonth() + 1
}`;
weekHeader.appendChild(dayHeader);
}
// Add time slots
for (let hour = 8; hour < 20; hour++) {
// Time label
const timeLabel = document.createElement("div");
timeLabel.className = "text-xs text-gray-400 text-right pr-2";
timeLabel.textContent = `${hour}:00`;
timeLabel.style.gridColumn = "1";
timeLabel.style.gridRow = `${hour - 7}`;
weekGrid.appendChild(timeLabel);
// Day columns
for (let day = 0; day < 7; day++) {
const dayDate = new Date(weekStart);
dayDate.setDate(weekStart.getDate() + day);
dayDate.setHours(hour);
const dayStr = dayDate.toISOString().split("T")[0];
const hourEvents = getEventsForDateAndHour(dayStr, hour);
const timeSlot = document.createElement("div");
timeSlot.className = "hour-slot relative border-r border-gray-700";
timeSlot.style.gridColumn = `${day + 2}`;
timeSlot.style.gridRow = `${hour - 7}`;
if (hourEvents.length > 0) {
hourEvents.forEach((event) => {
const eventElement = document.createElement("div");
eventElement.className = `absolute inset-1 ${event.category} text-white text-xs p-1 rounded overflow-hidden`;
eventElement.textContent = event.title;
eventElement.dataset.eventId = event.id;
eventElement.addEventListener("click", (e) => {
e.stopPropagation();
openEventModal(event.id);
});
timeSlot.appendChild(eventElement);
});
} else {
timeSlot.addEventListener("click", () => {
const dateStr = dayDate.toISOString().split("T")[0];
const timeStr = `${hour.toString().padStart(2, "0")}:00`;
openEventModal(null, dateStr, timeStr);
});
}
weekGrid.appendChild(timeSlot);
}
}
}
function renderDayView() {
// Hide other views and show day view
hideAllViews();
dayView.classList.remove("hidden");
// Clear the grid
dayHeader.innerHTML = "";
dayGrid.innerHTML = "";
// Set day header
const dayName = [
"Domingo",
"Segunda",
"Terça",
"Quarta",
"Quinta",
"Sexta",
"Sábado",
][currentDate.getDay()];
dayHeader.textContent = `${dayName}, ${currentDate.getDate()} de ${
[
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
][currentDate.getMonth()]
} de ${currentDate.getFullYear()}`;
// Add time slots
for (let hour = 8; hour < 20; hour++) {
const timeSlot = document.createElement("div");
timeSlot.className = "hour-slot flex";
// Time label
const timeLabel = document.createElement("div");
timeLabel.className = "text-xs text-gray-400 w-16 text-right pr-2";
timeLabel.textContent = `${hour}:00`;
timeSlot.appendChild(timeLabel);
// Event area
const eventArea = document.createElement("div");
eventArea.className = "flex-1 relative";
const dayStr = currentDate.toISOString().split("T")[0];
const hourEvents = getEventsForDateAndHour(dayStr, hour);
if (hourEvents.length > 0) {
hourEvents.forEach((event) => {
const eventElement = document.createElement("div");
eventElement.className = `absolute inset-1 ${event.category} text-white text-xs p-1 rounded overflow-hidden`;
eventElement.textContent = event.title;
eventElement.dataset.eventId = event.id;
eventElement.addEventListener("click", (e) => {
e.stopPropagation();
openEventModal(event.id);
});
eventArea.appendChild(eventElement);
});
} else {
eventArea.addEventListener("click", () => {
const dateStr = currentDate.toISOString().split("T")[0];
const timeStr = `${hour.toString().padStart(2, "0")}:00`;
openEventModal(null, dateStr, timeStr);
});
}
timeSlot.appendChild(eventArea);
dayGrid.appendChild(timeSlot);
}
}
function renderListView() {
// Hide other views and show list view
hideAllViews();
listView.classList.remove("hidden");
// Clear the grid
listHeader.innerHTML = "";
listGrid.innerHTML = "";
// Set list header
if (currentView === "month") {
const monthName = [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
][currentDate.getMonth()];
listHeader.textContent = `Eventos de ${monthName} ${currentDate.getFullYear()}`;
// Get events for the month
const monthEvents = getEventsForMonth(
currentDate.getFullYear(),
currentDate.getMonth() + 1
);
renderEventList(monthEvents);
} else if (currentView === "week") {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
listHeader.textContent = `Eventos de ${weekStart.getDate()}/${
weekStart.getMonth() + 1
} a ${weekEnd.getDate()}/${weekEnd.getMonth() + 1}`;
// Get events for the week
const weekEvents = getEventsForWeek(weekStart, weekEnd);
renderEventList(weekEvents);
} else if (currentView === "day") {
listHeader.textContent = `Eventos de ${currentDate.getDate()}/${
currentDate.getMonth() + 1
}/${currentDate.getFullYear()}`;
// Get events for the day
const dayEvents = getEventsForDate(
currentDate.toISOString().split("T")[0]
);
renderEventList(dayEvents);
}
}
function renderEventList(events) {
if (events.length === 0) {
const noEvents = document.createElement("div");
noEvents.className = "text-center text-gray-400 py-4";
noEvents.textContent = "Nenhum evento encontrado";
listGrid.appendChild(noEvents);
return;
}
events.sort((a, b) => new Date(a.start) - new Date(b.start));
events.forEach((event) => {
const eventElement = document.createElement("div");
eventElement.className =
"event-item bg-gray-700 rounded p-3 transition-transform duration-200 cursor-pointer mb-2";
eventElement.dataset.eventId = event.id;
const startDate = new Date(event.start);
const endDate = new Date(event.end);
let dateStr, timeStr;
if (event.allDay) {
dateStr = startDate.toLocaleDateString("pt-BR");
timeStr = "Dia inteiro";
} else {
dateStr = startDate.toLocaleDateString("pt-BR");
const startTime = startDate.toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
});
const endTime = endDate.toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
});
timeStr = `${startTime} - ${endTime}`;
}
eventElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h4 class="text-white font-medium">${event.title}</h4>
<p class="text-gray-400 text-sm">${dateStr}</p>
</div>
<span class="bg-blue-600 text-white text-xs px-2 py-1 rounded">${timeStr}</span>
</div>
`;
eventElement.addEventListener("click", () => {
openEventModal(event.id);
});
listGrid.appendChild(eventElement);
});
}
function renderUpcomingEvents() {
upcomingEventsList.innerHTML = "";
// Get upcoming events (next 30 days)
const today = new Date();
today.setHours(0, 0, 0, 0);
const nextMonth = new Date(today);
nextMonth.setMonth(today.getMonth() + 1);
const upcomingEvents = events.filter((event) => {
const eventDate = new Date(event.start);
return eventDate >= today && eventDate <= nextMonth;
});
if (upcomingEvents.length === 0) {
const noEvents = document.createElement("div");
noEvents.className = "text-center text-gray-400 py-4";
noEvents.textContent = "Nenhum evento próximo";
upcomingEventsList.appendChild(noEvents);
return;
}
upcomingEvents.sort((a, b) => new Date(a.start) - new Date(b.start));
upcomingEvents.slice(0, 5).forEach((event) => {
const eventElement = document.createElement("div");
eventElement.className =
"event-item bg-gray-700 rounded p-3 transition-transform duration-200 cursor-pointer mb-2";
eventElement.dataset.eventId = event.id;
const startDate = new Date(event.start);
const endDate = new Date(event.end);
let dateStr, timeStr;
if (event.allDay) {
dateStr = startDate.toLocaleDateString("pt-BR");
timeStr = "Dia inteiro";
} else {
dateStr = startDate.toLocaleDateString("pt-BR");
const startTime = startDate.toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
});
const endTime = endDate.toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
});
timeStr = `${startTime} - ${endTime}`;
}
eventElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h4 class="text-white font-medium">${event.title}</h4>
<p class="text-gray-400 text-sm">${dateStr}</p>
</div>
<span class="bg-blue-600 text-white text-xs px-2 py-1 rounded">${timeStr}</span>
</div>
`;
eventElement.addEventListener("click", () => {
openEventModal(event.id);
});
upcomingEventsList.appendChild(eventElement);
});
}
function hideAllViews() {
monthView.classList.add("hidden");
weekView.classList.add("hidden");
dayView.classList.add("hidden");
listView.classList.add("hidden");
}
function updateActiveViewButton() {
viewButtons.forEach((button) => {
if (button.dataset.view === currentView) {
button.classList.add("bg-gray-800", "text-white");
button.classList.remove("text-gray-300", "hover:text-white");
} else {
button.classList.remove("bg-gray-800", "text-white");
button.classList.add("text-gray-300", "hover:text-white");
}
});
}
function updatePeriodTitle() {
if (currentView === "month") {
const monthName = [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
][currentDate.getMonth()];
currentPeriodTitle.textContent = `${monthName} ${currentDate.getFullYear()}`;
} else if (currentView === "week") {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
currentPeriodTitle.textContent = `${weekStart.getDate()}/${
weekStart.getMonth() + 1
} - ${weekEnd.getDate()}/${
weekEnd.getMonth() + 1
} ${weekEnd.getFullYear()}`;
} else if (currentView === "day") {
const dayName = [
"Domingo",
"Segunda",
"Terça",
"Quarta",
"Quinta",
"Sexta",
"Sábado",
][currentDate.getDay()];
currentPeriodTitle.textContent = `${dayName}, ${currentDate.getDate()} de ${
[
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
][currentDate.getMonth()]
} de ${currentDate.getFullYear()}`;
} else if (currentView === "list") {
if (currentView === "month") {
const monthName = [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
][currentDate.getMonth()];
currentPeriodTitle.textContent = `Lista: ${monthName} ${currentDate.getFullYear()}`;
} else if (currentView === "week") {
const weekStart = new Date(currentDate);
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
currentPeriodTitle.textContent = `Lista: ${weekStart.getDate()}/${
weekStart.getMonth() + 1
} - ${weekEnd.getDate()}/${weekEnd.getMonth() + 1}`;
} else if (currentView === "day") {
currentPeriodTitle.textContent = `Lista: ${currentDate.getDate()}/${
currentDate.getMonth() + 1
}/${currentDate.getFullYear()}`;
}
}
}
function navigatePeriod(direction) {
if (currentView === "month") {
currentDate.setMonth(currentDate.getMonth() + direction);
} else if (currentView === "week") {
currentDate.setDate(currentDate.getDate() + direction * 7);
} else if (currentView === "day") {
currentDate.setDate(currentDate.getDate() + direction);
}
renderCalendar();
}
// Event modal functions
function openEventModal(eventId = null, dateStr = null, timeStr = null) {
if (eventId) {
// Edit existing event
const event = events.find((e) => e.id === eventId);
if (event) {
modalTitle.textContent = "Editar Evento";
eventIdInput.value = event.id;
eventTitleInput.value = event.title;
eventCategoryInput.value = event.category;
eventDescriptionInput.value = event.description || "";
// Set start date/time
const startDate = new Date(event.start);
eventStartDateInput.value = startDate.toISOString().split("T")[0];
if (!event.allDay) {
eventStartTimeInput.value = startDate
.toTimeString()
.substring(0, 5);
} else {
eventStartTimeInput.value = "";
}
// Set end date/time
const endDate = new Date(event.end);
eventEndDateInput.value = endDate.toISOString().split("T")[0];
if (!event.allDay) {
eventEndTimeInput.value = endDate.toTimeString().substring(0, 5);
} else {
eventEndTimeInput.value = "";
}
// Show delete button
deleteEventButton.classList.remove("hidden");
}
} else {
// Create new event
modalTitle.textContent = "Novo Evento";
eventIdInput.value = "";
eventTitleInput.value = "";
eventCategoryInput.value = "event-planning";
eventDescriptionInput.value = "";
// Set default dates/times
if (dateStr) {
eventStartDateInput.value = dateStr;
eventEndDateInput.value = dateStr;
} else {
const today = new Date();
const todayStr = today.toISOString().split("T")[0];
eventStartDateInput.value = todayStr;
eventEndDateInput.value = todayStr;
}
if (timeStr) {
eventStartTimeInput.value = timeStr;
// Set end time to 1 hour later
const [hours, minutes] = timeStr.split(":");
const endTime = new Date();
endTime.setHours(parseInt(hours) + 1, parseInt(minutes), 0, 0);
eventEndTimeInput.value = endTime.toTimeString().substring(0, 5);
} else {
eventStartTimeInput.value = "";
eventEndTimeInput.value = "";
}
// Hide delete button
deleteEventButton.classList.add("hidden");
}
eventModal.classList.remove("hidden");
}
function closeEventModal() {
eventModal.classList.add("hidden");
}
function saveEvent() {
const id = eventIdInput.value || generateId();
const title = eventTitleInput.value.trim();
const category = eventCategoryInput.value;
const description = eventDescriptionInput.value.trim();
const startDate = eventStartDateInput.value;
const startTime = eventStartTimeInput.value;
const endDate = eventEndDateInput.value;
const endTime = eventEndTimeInput.value;
if (!title) {
alert("Por favor, insira um título para o evento");
return;
}
// Determine if it's an all-day event
const allDay = !startTime || !endTime;
// Format start and end dates
let start, end;
if (allDay) {
start = startDate;
end = endDate;
} else {
start = `${startDate}T${startTime}:00`;
end = `${endDate}T${endTime}:00`;
}
// Check if end is before start
if (new Date(end) < new Date(start)) {
alert(
"A data/hora de término não pode ser anterior à data/hora de início"
);
return;
}
// Create or update event
const eventIndex = events.findIndex((e) => e.id === id);
const event = {
id,
title,
category,
description,
start,
end,
allDay,
};
if (eventIndex >= 0) {
// Update existing event
events[eventIndex] = event;
} else {
// Add new event
events.push(event);
}
// Close modal and refresh calendar
closeEventModal();
renderCalendar();
renderUpcomingEvents();
}
function deleteEvent() {
const id = eventIdInput.value;
if (confirm("Tem certeza que deseja excluir este evento?")) {
events = events.filter((e) => e.id !== id);
closeEventModal();
renderCalendar();
renderUpcomingEvents();
}
}
function generateId() {
return Math.random().toString(36).substring(2, 9);
}
// Event filtering functions
function getEventsForDate(dateStr) {
return events.filter((event) => {
const eventStart = new Date(event.start);
const eventEnd = new Date(event.end);
const targetDate = new Date(dateStr);
// Check if target date is between event start and end (inclusive)
targetDate.setHours(0, 0, 0, 0);
eventStart.setHours(0, 0, 0, 0);
eventEnd.setHours(0, 0, 0, 0);
return targetDate >= eventStart && targetDate <= eventEnd;
});
}
function getEventsForDateAndHour(dateStr, hour) {
return events.filter((event) => {
if (event.allDay) return false;
const eventStart = new Date(event.start);
const eventEnd = new Date(event.end);
const targetDate = new Date(dateStr);
targetDate.setHours(hour, 0, 0, 0);
// Check if target hour is between event start and end (inclusive)
return targetDate >= eventStart && targetDate < eventEnd;
});
}
function getEventsForMonth(year, month) {
return events.filter((event) => {
const eventStart = new Date(event.start);
return (
eventStart.getFullYear() === year &&
eventStart.getMonth() + 1 === month
);
});
}
function getEventsForWeek(startDate, endDate) {
return events.filter((event) => {
const eventStart = new Date(event.start);
const eventEnd = new Date(event.end);
// Check if event overlaps with the week
return (
(eventStart >= startDate && eventStart <= endDate) ||
(eventEnd >= startDate && eventEnd <= endDate) ||
(eventStart <= startDate && eventEnd >= endDate)
);
});
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gallabs/calendar-lucas" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>