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> | |