Spaces:
Running
Running
<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> | |