projetos / index.html
sidneibarreto's picture
Add 3 files
6f70433 verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agenda Integrada | Gestão de Compromissos</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
}
}
}
}
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles */
:root {
--primary-color: #0ea5e9;
--secondary-color: #7dd3fc;
}
/* Smooth transitions */
* {
transition: background-color 0.2s ease, color 0.2s ease;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Color picker */
.color-picker {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 30px;
height: 30px;
border: none;
cursor: pointer;
border-radius: 50%;
padding: 0;
background: transparent;
}
.color-picker::-webkit-color-swatch {
border-radius: 50%;
border: 2px solid #fff;
box-shadow: 0 0 0 1px rgba(0,0,0,0.1);
}
/* Custom checkbox */
.custom-checkbox {
position: relative;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 4px;
appearance: none;
cursor: pointer;
}
.custom-checkbox:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.custom-checkbox:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* Tooltip */
.tooltip {
position: relative;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
.tooltip-text {
visibility: hidden;
opacity: 0;
position: absolute;
z-index: 10;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background-color: #334155;
color: white;
text-align: center;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
transition: all 0.2s ease;
}
.tooltip-text::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #334155 transparent transparent transparent;
}
/* Loading spinner */
.spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-slate-50 font-sans antialiased">
<div class="min-h-screen">
<!-- Header -->
<header class="bg-gradient-to-r from-primary-600 to-primary-800 text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl md:text-3xl font-bold">Agenda Integrada</h1>
<p class="text-primary-200">Gestão completa de seus compromissos</p>
</div>
<div class="flex items-center space-x-4">
<button id="syncBtn" class="p-2 rounded-full hover:bg-primary-700 transition-colors tooltip">
<i class="fas fa-sync-alt"></i>
<span class="tooltip-text">Sincronizar dados</span>
</button>
<div class="relative">
<button id="userMenuBtn" class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center hover:bg-primary-400 transition-colors">
<i class="fas fa-user"></i>
</button>
<div id="userMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Meu perfil</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Configurações</a>
<div class="border-t border-gray-200 my-1"></div>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Sair</a>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- Main content -->
<main class="container mx-auto px-4 py-8 max-w-7xl">
<!-- Quick stats -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<div class="bg-white rounded-lg shadow p-4 flex items-center">
<div class="p-3 rounded-full bg-primary-100 text-primary-600 mr-4">
<i class="fas fa-calendar-day text-lg"></i>
</div>
<div>
<p class="text-sm text-gray-500">Hoje</p>
<p id="todayDate" class="text-xl font-semibold">-</p>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4 flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600 mr-4">
<i class="fas fa-calendar-check text-lg"></i>
</div>
<div>
<p class="text-sm text-gray-500">Compromissos</p>
<p id="appointmentsCount" class="text-xl font-semibold">0</p>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4 flex items-center">
<div class="p-3 rounded-full bg-red-100 text-red-600 mr-4">
<i class="fas fa-calendar-times text-lg"></i>
</div>
<div>
<p class="text-sm text-gray-500">Datas Bloqueadas</p>
<p id="blocksCount" class="text-xl font-semibold">0</p>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4 flex items-center">
<div class="p-3 rounded-full bg-amber-100 text-amber-600 mr-4">
<i class="fas fa-dollar-sign text-lg"></i>
</div>
<div>
<p class="text-sm text-gray-500">Total em Cache</p>
<p id="totalFee" class="text-xl font-semibold">R$ 0,00</p>
</div>
</div>
</div>
<div class="flex flex-col lg:flex-row gap-6">
<!-- Left sidebar - Add new event -->
<div class="w-full lg:w-1/3 bg-white rounded-lg shadow-md p-6 h-fit sticky top-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-primary-800">
<i class="fas fa-plus-circle mr-2"></i>Novo Evento
</h2>
<button id="clearFormBtn" class="text-sm text-primary-600 hover:text-primary-800">
<i class="fas fa-times mr-1"></i>Limpar
</button>
</div>
<form id="eventForm" class="space-y-4">
<div>
<label class="block text-gray-700 mb-2 font-medium">Tipo de Evento</label>
<div class="flex space-x-4">
<label class="inline-flex items-center">
<input type="radio" name="eventType" value="appointment" checked
class="form-radio text-primary-600 focus:ring-primary-500">
<span class="ml-2">Compromisso</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="eventType" value="block"
class="form-radio text-primary-600 focus:ring-primary-500">
<span class="ml-2">Bloquear Data</span>
</label>
</div>
</div>
<div>
<label for="eventDate" class="block text-gray-700 mb-2 font-medium">Data</label>
<input type="date" id="eventDate" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
</div>
<div id="companySection">
<label for="companySelect" class="block text-gray-700 mb-2 font-medium">Empresa</label>
<div class="flex space-x-2">
<select id="companySelect"
class="flex-grow px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
<option value="">Selecione uma empresa</option>
<!-- Options will be populated by JS -->
</select>
<button type="button" id="addCompanyBtn"
class="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors tooltip">
<i class="fas fa-plus"></i>
<span class="tooltip-text">Adicionar empresa</span>
</button>
</div>
</div>
<div id="newCompanySection" class="hidden space-y-4">
<div>
<label for="newCompanyName" class="block text-gray-700 mb-2 font-medium">Nova Empresa</label>
<input type="text" id="newCompanyName" placeholder="Nome da empresa"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
</div>
<div class="flex items-center">
<label for="companyColor" class="block text-gray-700 mr-3 font-medium">Cor:</label>
<input type="color" id="companyColor" value="#0ea5e9" class="color-picker">
<button type="button" id="cancelAddCompany"
class="ml-4 px-3 py-1 text-sm bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors">
Cancelar
</button>
</div>
</div>
<div id="descriptionSection">
<label for="eventDescription" class="block text-gray-700 mb-2 font-medium">Descrição</label>
<textarea id="eventDescription" rows="3" placeholder="Detalhes do compromisso"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"></textarea>
</div>
<div id="feeSection">
<label for="eventFee" class="block text-gray-700 mb-2 font-medium">Valor do Cache (R$)</label>
<div class="relative">
<span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">R$</span>
<input type="number" id="eventFee" min="0" step="0.01" placeholder="0,00"
class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
</div>
</div>
<div id="reminderSection" class="hidden">
<label class="flex items-center space-x-2">
<input type="checkbox" id="setReminder" class="custom-checkbox">
<span class="text-gray-700">Definir lembrete</span>
</label>
<div id="reminderOptions" class="hidden mt-2 pl-6 space-y-2">
<div class="flex items-center space-x-2">
<input type="radio" name="reminderTime" id="reminder15min" value="15" class="form-radio text-primary-600">
<label for="reminder15min" class="text-gray-700">15 minutos antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="reminderTime" id="reminder30min" value="30" class="form-radio text-primary-600">
<label for="reminder30min" class="text-gray-700">30 minutos antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="reminderTime" id="reminder1h" value="60" class="form-radio text-primary-600">
<label for="reminder1h" class="text-gray-700">1 hora antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="reminderTime" id="reminder1d" value="1440" class="form-radio text-primary-600">
<label for="reminder1d" class="text-gray-700">1 dia antes</label>
</div>
</div>
</div>
<button type="submit" id="submitBtn"
class="w-full bg-primary-600 text-white py-3 px-4 rounded-lg hover:bg-primary-700 transition-colors flex items-center justify-center">
<i class="fas fa-save mr-2"></i>Salvar Evento
</button>
</form>
</div>
<!-- Main content - Agenda -->
<div class="w-full lg:w-2/3">
<!-- Month selector and filters -->
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div class="flex items-center space-x-4">
<button id="prevMonth" class="p-2 rounded-full hover:bg-gray-100 transition-colors tooltip">
<i class="fas fa-chevron-left"></i>
<span class="tooltip-text">Mês anterior</span>
</button>
<h2 id="currentMonth" class="text-xl font-semibold text-primary-700">Junho 2023</h2>
<button id="nextMonth" class="p-2 rounded-full hover:bg-gray-100 transition-colors tooltip">
<i class="fas fa-chevron-right"></i>
<span class="tooltip-text">Próximo mês</span>
</button>
</div>
<div class="flex items-center space-x-2">
<button id="todayBtn" class="px-3 py-1 text-sm bg-primary-100 text-primary-700 rounded-lg hover:bg-primary-200 transition-colors">
Hoje
</button>
<div class="relative">
<button id="filterBtn" class="px-3 py-1 text-sm bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors flex items-center">
<i class="fas fa-filter mr-2"></i>Filtrar
</button>
<div id="filterDropdown" class="hidden absolute right-0 mt-1 w-48 bg-white rounded-md shadow-lg py-1 z-10 border border-gray-200">
<div class="px-3 py-2">
<label class="flex items-center space-x-2">
<input type="checkbox" id="filterAppointments" checked class="custom-checkbox">
<span class="text-gray-700">Compromissos</span>
</label>
</div>
<div class="px-3 py-2">
<label class="flex items-center space-x-2">
<input type="checkbox" id="filterBlocks" checked class="custom-checkbox">
<span class="text-gray-700">Datas bloqueadas</span>
</label>
</div>
<div class="border-t border-gray-200 my-1"></div>
<div class="px-3 py-2">
<label class="block text-gray-700 text-sm mb-1">Empresas</label>
<select id="companyFilter" class="w-full px-2 py-1 text-sm border border-gray-300 rounded">
<option value="">Todas</option>
<!-- Options will be populated by JS -->
</select>
</div>
</div>
</div>
<div class="relative">
<button id="viewBtn" class="px-3 py-1 text-sm bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors flex items-center">
<i class="fas fa-calendar-alt mr-2"></i>Mês
</button>
<div id="viewDropdown" class="hidden absolute right-0 mt-1 w-32 bg-white rounded-md shadow-lg py-1 z-10 border border-gray-200">
<a href="#" class="block px-3 py-1 text-sm text-gray-700 hover:bg-gray-100">Dia</a>
<a href="#" class="block px-3 py-1 text-sm text-gray-700 hover:bg-gray-100">Semana</a>
<a href="#" class="block px-3 py-1 text-sm text-gray-700 hover:bg-gray-100">Mês</a>
<a href="#" class="block px-3 py-1 text-sm text-gray-700 hover:bg-gray-100">Ano</a>
</div>
</div>
</div>
</div>
</div>
<!-- Agenda list -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div id="agendaList" class="divide-y divide-gray-200 max-h-[600px] overflow-y-auto">
<!-- Events will be loaded here -->
<div class="p-6 text-center text-gray-500">
<div class="inline-block p-4">
<i class="fas fa-spinner fa-spin text-2xl text-primary-500 mb-2"></i>
<p>Carregando agenda...</p>
</div>
</div>
</div>
</div>
<!-- Empty state -->
<div id="emptyState" class="hidden bg-white rounded-lg shadow-md p-8 text-center">
<div class="max-w-md mx-auto">
<div class="p-4 inline-block rounded-full bg-primary-100 text-primary-600 mb-4">
<i class="fas fa-calendar-plus text-3xl"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-2">Nenhum evento encontrado</h3>
<p class="text-gray-600 mb-6">Adicione seu primeiro evento usando o formulário ao lado ou altere os filtros de busca.</p>
<button id="addFirstEventBtn" class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
<i class="fas fa-plus mr-2"></i>Adicionar Evento
</button>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Edit Event Modal -->
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50 px-4">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="flex justify-between items-center border-b px-6 py-4">
<h3 class="text-lg font-semibold text-primary-700">Editar Evento</h3>
<button id="closeEditModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<form id="editForm" class="space-y-4">
<input type="hidden" id="editEventId">
<div>
<label class="block text-gray-700 mb-2 font-medium">Tipo de Evento</label>
<div class="flex space-x-4">
<label class="inline-flex items-center">
<input type="radio" name="editEventType" value="appointment"
class="form-radio text-primary-600 focus:ring-primary-500">
<span class="ml-2">Compromisso</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="editEventType" value="block"
class="form-radio text-primary-600 focus:ring-primary-500">
<span class="ml-2">Bloquear Data</span>
</label>
</div>
</div>
<div>
<label for="editEventDate" class="block text-gray-700 mb-2 font-medium">Data</label>
<input type="date" id="editEventDate" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
</div>
<div id="editCompanySection">
<label for="editCompanySelect" class="block text-gray-700 mb-2 font-medium">Empresa</label>
<select id="editCompanySelect"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
<!-- Options will be populated by JS -->
</select>
</div>
<div>
<label for="editEventDescription" class="block text-gray-700 mb-2 font-medium">Descrição</label>
<textarea id="editEventDescription" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"></textarea>
</div>
<div id="editFeeSection">
<label for="editEventFee" class="block text-gray-700 mb-2 font-medium">Valor do Cache (R$)</label>
<div class="relative">
<span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">R$</span>
<input type="number" id="editEventFee" min="0" step="0.01"
class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
</div>
</div>
<div id="editReminderSection" class="hidden">
<label class="flex items-center space-x-2">
<input type="checkbox" id="editSetReminder" class="custom-checkbox">
<span class="text-gray-700">Definir lembrete</span>
</label>
<div id="editReminderOptions" class="hidden mt-2 pl-6 space-y-2">
<div class="flex items-center space-x-2">
<input type="radio" name="editReminderTime" id="editReminder15min" value="15" class="form-radio text-primary-600">
<label for="editReminder15min" class="text-gray-700">15 minutos antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="editReminderTime" id="editReminder30min" value="30" class="form-radio text-primary-600">
<label for="editReminder30min" class="text-gray-700">30 minutos antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="editReminderTime" id="editReminder1h" value="60" class="form-radio text-primary-600">
<label for="editReminder1h" class="text-gray-700">1 hora antes</label>
</div>
<div class="flex items-center space-x-2">
<input type="radio" name="editReminderTime" id="editReminder1d" value="1440" class="form-radio text-primary-600">
<label for="editReminder1d" class="text-gray-700">1 dia antes</label>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" id="deleteEventBtn"
class="px-4 py-2 bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition-colors flex items-center">
<i class="fas fa-trash mr-2"></i>Excluir
</button>
<button type="submit"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors flex items-center">
<i class="fas fa-save mr-2"></i>Salvar
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Notification Toast -->
<div id="toast" class="fixed bottom-4 right-4 hidden">
<div class="bg-gray-800 text-white px-4 py-3 rounded-lg shadow-lg flex items-start max-w-xs">
<div id="toastIcon" class="mr-3 mt-0.5">
<i class="fas fa-check-circle text-green-400"></i>
</div>
<div>
<h4 id="toastTitle" class="font-semibold">Sucesso</h4>
<p id="toastMessage" class="text-sm text-gray-300">Evento adicionado com sucesso</p>
</div>
<button id="closeToast" class="ml-4 text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<script>
// Sample data - in a real app, this would come from an API or database
let companies = [
{ id: 1, name: "Empresa A", color: "#0ea5e9" },
{ id: 2, name: "Empresa B", color: "#10b981" },
{ id: 3, name: "Empresa C", color: "#f59e0b" },
{ id: 4, name: "Empresa D", color: "#8b5cf6" },
{ id: 5, name: "Empresa E", color: "#ec4899" }
];
let events = [
{
id: 1,
date: getFormattedDate(new Date()),
type: "appointment",
companyId: 1,
description: "Reunião de planejamento estratégico",
fee: 2500.00,
reminder: null
},
{
id: 2,
date: getFormattedDate(addDays(new Date(), 2)),
type: "appointment",
companyId: 2,
description: "Apresentação de resultados trimestrais",
fee: 1800.00,
reminder: 30
},
{
id: 3,
date: getFormattedDate(addDays(new Date(), 5)),
type: "block",
companyId: null,
description: "Férias - Viagem familiar",
fee: 0,
reminder: null
},
{
id: 4,
date: getFormattedDate(addDays(new Date(), 7)),
type: "appointment",
companyId: 3,
description: "Workshop de treinamento",
fee: 3200.00,
reminder: 1440
},
{
id: 5,
date: getFormattedDate(addDays(new Date(), 10)),
type: "appointment",
companyId: 4,
description: "Negociação de contrato",
fee: 1500.00,
reminder: 60
}
];
// Current month for the agenda view
let currentDate = new Date();
let showAppointments = true;
let showBlocks = true;
let companyFilter = null;
// DOM elements
const eventForm = document.getElementById('eventForm');
const eventTypeRadios = document.querySelectorAll('input[name="eventType"]');
const companySection = document.getElementById('companySection');
const newCompanySection = document.getElementById('newCompanySection');
const descriptionSection = document.getElementById('descriptionSection');
const feeSection = document.getElementById('feeSection');
const reminderSection = document.getElementById('reminderSection');
const setReminder = document.getElementById('setReminder');
const reminderOptions = document.getElementById('reminderOptions');
const companySelect = document.getElementById('companySelect');
const addCompanyBtn = document.getElementById('addCompanyBtn');
const cancelAddCompany = document.getElementById('cancelAddCompany');
const newCompanyName = document.getElementById('newCompanyName');
const companyColor = document.getElementById('companyColor');
const eventDate = document.getElementById('eventDate');
const eventDescription = document.getElementById('eventDescription');
const eventFee = document.getElementById('eventFee');
const clearFormBtn = document.getElementById('clearFormBtn');
const agendaList = document.getElementById('agendaList');
const emptyState = document.getElementById('emptyState');
const addFirstEventBtn = document.getElementById('addFirstEventBtn');
const currentMonth = document.getElementById('currentMonth');
const prevMonth = document.getElementById('prevMonth');
const nextMonth = document.getElementById('nextMonth');
const todayBtn = document.getElementById('todayBtn');
const filterBtn = document.getElementById('filterBtn');
const filterDropdown = document.getElementById('filterDropdown');
const filterAppointments = document.getElementById('filterAppointments');
const filterBlocks = document.getElementById('filterBlocks');
const companyFilterSelect = document.getElementById('companyFilter');
const viewBtn = document.getElementById('viewBtn');
const viewDropdown = document.getElementById('viewDropdown');
const editModal = document.getElementById('editModal');
const closeEditModal = document.getElementById('closeEditModal');
const editForm = document.getElementById('editForm');
const editEventId = document.getElementById('editEventId');
const editEventTypeRadios = document.querySelectorAll('input[name="editEventType"]');
const editCompanySection = document.getElementById('editCompanySection');
const editFeeSection = document.getElementById('editFeeSection');
const editReminderSection = document.getElementById('editReminderSection');
const editSetReminder = document.getElementById('editSetReminder');
const editReminderOptions = document.getElementById('editReminderOptions');
const editEventDate = document.getElementById('editEventDate');
const editCompanySelect = document.getElementById('editCompanySelect');
const editEventDescription = document.getElementById('editEventDescription');
const editEventFee = document.getElementById('editEventFee');
const deleteEventBtn = document.getElementById('deleteEventBtn');
const appointmentsCount = document.getElementById('appointmentsCount');
const blocksCount = document.getElementById('blocksCount');
const totalFee = document.getElementById('totalFee');
const todayDate = document.getElementById('todayDate');
const syncBtn = document.getElementById('syncBtn');
const userMenuBtn = document.getElementById('userMenuBtn');
const userMenu = document.getElementById('userMenu');
const toast = document.getElementById('toast');
const toastTitle = document.getElementById('toastTitle');
const toastMessage = document.getElementById('toastMessage');
const toastIcon = document.getElementById('toastIcon');
const closeToast = document.getElementById('closeToast');
// Initialize the app
function init() {
// Set today's date as default
const today = new Date();
eventDate.value = getFormattedDate(today);
// Display today's date
const options = { weekday: 'long', day: 'numeric', month: 'long' };
todayDate.textContent = today.toLocaleDateString('pt-BR', options);
// Populate company dropdowns
populateCompanyDropdowns();
// Load events for current month
loadAgenda();
// Update statistics
updateStatistics();
// Set up event listeners
setupEventListeners();
}
// Set up all event listeners
function setupEventListeners() {
// Event type radio buttons
eventTypeRadios.forEach(radio => {
radio.addEventListener('change', toggleEventFields);
});
// Add company button
addCompanyBtn.addEventListener('click', () => {
companySection.classList.add('hidden');
newCompanySection.classList.remove('hidden');
});
// Cancel adding new company
cancelAddCompany.addEventListener('click', () => {
companySection.classList.remove('hidden');
newCompanySection.classList.add('hidden');
newCompanyName.value = '';
});
// Clear form button
clearFormBtn.addEventListener('click', resetForm);
// Form submission
eventForm.addEventListener('submit', addNewEvent);
// Reminder checkbox
setReminder.addEventListener('change', () => {
reminderOptions.classList.toggle('hidden', !setReminder.checked);
});
// Month navigation
prevMonth.addEventListener('click', () => {
currentDate.setMonth(currentDate.getMonth() - 1);
loadAgenda();
});
nextMonth.addEventListener('click', () => {
currentDate.setMonth(currentDate.getMonth() + 1);
loadAgenda();
});
// Today button
todayBtn.addEventListener('click', () => {
currentDate = new Date();
loadAgenda();
});
// Filter button
filterBtn.addEventListener('click', () => {
filterDropdown.classList.toggle('hidden');
viewDropdown.classList.add('hidden');
});
// View button
viewBtn.addEventListener('click', () => {
viewDropdown.classList.toggle('hidden');
filterDropdown.classList.add('hidden');
});
// Filter checkboxes
filterAppointments.addEventListener('change', () => {
showAppointments = filterAppointments.checked;
loadAgenda();
});
filterBlocks.addEventListener('change', () => {
showBlocks = filterBlocks.checked;
loadAgenda();
});
// Company filter
companyFilterSelect.addEventListener('change', () => {
companyFilter = companyFilterSelect.value ? parseInt(companyFilterSelect.value) : null;
loadAgenda();
});
// Edit modal
closeEditModal.addEventListener('click', () => {
editModal.classList.add('hidden');
});
// Edit form submission
editForm.addEventListener('submit', saveEditedEvent);
// Edit reminder checkbox
editSetReminder.addEventListener('change', () => {
editReminderOptions.classList.toggle('hidden', !editSetReminder.checked);
});
// Delete event button
deleteEventBtn.addEventListener('click', deleteEvent);
// Sync button
syncBtn.addEventListener('click', syncData);
// User menu
userMenuBtn.addEventListener('click', () => {
userMenu.classList.toggle('hidden');
});
// Close toast
closeToast.addEventListener('click', () => {
toast.classList.add('hidden');
});
// Close dropdowns when clicking outside
document.addEventListener('click', (e) => {
if (!filterBtn.contains(e.target)) {
filterDropdown.classList.add('hidden');
}
if (!viewBtn.contains(e.target)) {
viewDropdown.classList.add('hidden');
}
if (!userMenuBtn.contains(e.target)) {
userMenu.classList.add('hidden');
}
});
// Add first event button
addFirstEventBtn.addEventListener('click', () => {
// Scroll to form
document.querySelector('.lg\\:w-1\\/3').scrollIntoView({ behavior: 'smooth' });
});
}
// Helper functions
function getFormattedDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function formatDateForDisplay(dateString) {
const [year, month, day] = dateString.split('-');
return `${day}/${month}/${year}`;
}
function formatCurrency(value) {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(value);
}
function showNotification(title, message, type = 'success') {
// Set icon based on type
let iconClass;
switch(type) {
case 'success':
iconClass = 'fas fa-check-circle text-green-400';
break;
case 'error':
iconClass = 'fas fa-exclamation-circle text-red-400';
break;
case 'warning':
iconClass = 'fas fa-exclamation-triangle text-amber-400';
break;
case 'info':
iconClass = 'fas fa-info-circle text-blue-400';
break;
default:
iconClass = 'fas fa-check-circle text-green-400';
}
toastIcon.innerHTML = `<i class="${iconClass}"></i>`;
toastTitle.textContent = title;
toastMessage.textContent = message;
toast.classList.remove('hidden');
// Auto-hide after 5 seconds
setTimeout(() => {
toast.classList.add('hidden');
}, 5000);
}
// Toggle form fields based on event type
function toggleEventFields() {
const selectedType = document.querySelector('input[name="eventType"]:checked').value;
if (selectedType === 'block') {
companySection.classList.add('hidden');
descriptionSection.classList.remove('hidden');
feeSection.classList.add('hidden');
reminderSection.classList.add('hidden');
} else {
companySection.classList.remove('hidden');
descriptionSection.classList.remove('hidden');
feeSection.classList.remove('hidden');
reminderSection.classList.remove('hidden');
}
}
// Reset form to default state
function resetForm() {
eventForm.reset();
eventDate.value = getFormattedDate(new Date());
companySection.classList.remove('hidden');
newCompanySection.classList.add('hidden');
newCompanyName.value = '';
reminderOptions.classList.add('hidden');
showNotification('Formulário limpo', 'O formulário foi resetado para o estado inicial', 'info');
}
// Populate company dropdowns
function populateCompanyDropdowns() {
// Clear existing options
companySelect.innerHTML = '<option value="">Selecione uma empresa</option>';
editCompanySelect.innerHTML = '<option value="">Selecione uma empresa</option>';
companyFilterSelect.innerHTML = '<option value="">Todas</option>';
// Add companies
companies.forEach(company => {
const option = document.createElement('option');
option.value = company.id;
option.textContent = company.name;
companySelect.appendChild(option.cloneNode(true));
editCompanySelect.appendChild(option.cloneNode(true));
// Add to filter dropdown
const filterOption = option.cloneNode(true);
companyFilterSelect.appendChild(filterOption);
});
}
// Add new event
function addNewEvent(e) {
e.preventDefault();
const submitBtn = document.getElementById('submitBtn');
const originalBtnContent = submitBtn.innerHTML;
// Show loading state
submitBtn.disabled = true;
submitBtn.innerHTML = '<div class="spinner mr-2"></div> Salvando...';
const eventType = document.querySelector('input[name="eventType"]:checked').value;
const date = eventDate.value;
let companyId = null;
let description = eventDescription.value;
let fee = 0;
let reminder = null;
if (eventType === 'appointment') {
// Check if adding a new company
if (!newCompanySection.classList.contains('hidden')) {
const companyName = newCompanyName.value.trim();
const color = companyColor.value;
if (!companyName) {
showNotification('Erro', 'Por favor, insira um nome para a empresa', 'error');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnContent;
return;
}
// Add new company (in a real app, this would be an API call)
const newCompany = {
id: companies.length > 0 ? Math.max(...companies.map(c => c.id)) + 1 : 1,
name: companyName,
color: color
};
companies.push(newCompany);
companyId = newCompany.id;
// Reset new company form
companySection.classList.remove('hidden');
newCompanySection.classList.add('hidden');
newCompanyName.value = '';
// Update dropdowns
populateCompanyDropdowns();
} else {
companyId = parseInt(companySelect.value);
if (!companyId) {
showNotification('Erro', 'Por favor, selecione uma empresa', 'error');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnContent;
return;
}
}
fee = parseFloat(eventFee.value) || 0;
// Set reminder if enabled
if (setReminder.checked) {
const reminderTime = document.querySelector('input[name="reminderTime"]:checked');
if (reminderTime) {
reminder = parseInt(reminderTime.value);
}
}
} else {
description = description || 'Data bloqueada';
}
// Add new event (in a real app, this would be an API call)
const newEvent = {
id: events.length > 0 ? Math.max(...events.map(e => e.id)) + 1 : 1,
date: date,
type: eventType,
companyId: companyId,
description: description,
fee: fee,
reminder: reminder
};
events.push(newEvent);
// Reset form
eventDescription.value = '';
eventFee.value = '';
setReminder.checked = false;
reminderOptions.classList.add('hidden');
// Reload agenda
loadAgenda();
updateStatistics();
// Show success message
showNotification('Sucesso', 'Evento adicionado com sucesso!');
// Restore button state
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnContent;
}
// Load agenda for current month with filters
function loadAgenda() {
// Update month display
const monthNames = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho",
"Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"];
currentMonth.textContent = `${monthNames[currentDate.getMonth()]} ${currentDate.getFullYear()}`;
// Get first and last day of current month
const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
// Filter events for current month with applied filters
let monthEvents = events.filter(event => {
const eventDate = new Date(event.date);
const dateInRange = eventDate >= firstDay && eventDate <= lastDay;
const typeMatches = (event.type === 'appointment' && showAppointments) ||
(event.type === 'block' && showBlocks);
const companyMatches = !companyFilter || event.companyId === companyFilter;
return dateInRange && typeMatches && companyMatches;
}).sort((a, b) => new Date(a.date) - new Date(b.date));
// Display events or empty state
if (monthEvents.length === 0) {
agendaList.classList.add('hidden');
emptyState.classList.remove('hidden');
return;
}
agendaList.classList.remove('hidden');
emptyState.classList.add('hidden');
let agendaHTML = '';
let currentDay = null;
monthEvents.forEach((event, index) => {
const eventDate = new Date(event.date);
const displayDate = formatDateForDisplay(event.date);
// Add day header if it's a new day
if (!currentDay || currentDay.getDate() !== eventDate.getDate()) {
currentDay = eventDate;
const dayName = currentDay.toLocaleDateString('pt-BR', { weekday: 'long' });
const isToday = currentDay.toDateString() === new Date().toDateString();
agendaHTML += `
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200 ${isToday ? 'bg-primary-50' : ''}">
<h3 class="font-medium text-gray-700 ${isToday ? 'text-primary-700' : ''}">
${dayName.charAt(0).toUpperCase() + dayName.slice(1)}, ${displayDate}
${isToday ? '<span class="ml-2 text-xs bg-primary-600 text-white px-2 py-0.5 rounded-full">Hoje</span>' : ''}
</h3>
</div>
`;
}
// Get company info if it's an appointment
let company = null;
let color = '#94a3b8'; // Default gray for blocked dates
let typeIcon = 'fas fa-calendar-day';
let typeClass = 'text-primary-600';
if (event.type === 'appointment' && event.companyId) {
company = companies.find(c => c.id === event.companyId);
if (company) color = company.color;
typeIcon = 'fas fa-handshake';
typeClass = 'text-primary-600';
} else {
typeIcon = 'fas fa-ban';
typeClass = 'text-red-600';
}
// Create event card
agendaHTML += `
<div class="fade-in px-4 py-3 hover:bg-gray-50 ${event.type === 'block' ? 'bg-red-50 hover:bg-red-100' : ''}" data-event-id="${event.id}">
<div class="flex items-start">
<div class="w-3 h-3 rounded-full mt-1.5 mr-3 flex-shrink-0" style="background-color: ${color};"></div>
<div class="flex-grow">
<div class="flex justify-between items-start">
<div class="flex items-center">
<i class="${typeIcon} ${typeClass} mr-2 text-sm"></i>
<h4 class="font-medium ${event.type === 'block' ? 'text-red-700' : 'text-primary-700'}">
${event.type === 'block' ? 'Data Bloqueada' : (company ? company.name : 'Empresa desconhecida')}
</h4>
</div>
<div class="flex space-x-2">
${event.reminder ? `
<span class="text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full flex items-center">
<i class="fas fa-bell text-xs mr-1 text-amber-500"></i>
${getReminderText(event.reminder)}
</span>
` : ''}
<button class="edit-event text-gray-400 hover:text-primary-600 transition-colors tooltip" data-event-id="${event.id}">
<i class="fas fa-edit"></i>
<span class="tooltip-text">Editar</span>
</button>
<button class="delete-event text-gray-400 hover:text-red-600 transition-colors tooltip" data-event-id="${event.id}">
<i class="fas fa-trash"></i>
<span class="tooltip-text">Excluir</span>
</button>
</div>
</div>
<p class="text-sm text-gray-600 mt-1">${event.description}</p>
${event.type === 'appointment' ? `
<p class="text-sm font-medium text-green-600 mt-1">
${formatCurrency(event.fee)}
</p>
` : ''}
<p class="text-xs text-gray-400 mt-2">
<i class="fas fa-clock mr-1"></i>
Criado em ${new Date().toLocaleDateString('pt-BR')}
</p>
</div>
</div>
</div>
`;
});
agendaList.innerHTML = agendaHTML;
// Add event listeners to edit and delete buttons
document.querySelectorAll('.edit-event').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
const eventId = parseInt(button.dataset.eventId);
openEditModal(eventId);
});
});
document.querySelectorAll('.delete-event').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
const eventId = parseInt(button.dataset.eventId);
if (confirm('Tem certeza que deseja excluir este evento?')) {
deleteEvent(eventId);
}
});
});
}
// Helper function to get reminder text
function getReminderText(minutes) {
if (minutes === 15) return '15 min antes';
if (minutes === 30) return '30 min antes';
if (minutes === 60) return '1 hora antes';
if (minutes === 1440) return '1 dia antes';
return 'Lembrete';
}
// Open edit modal with event data
function openEditModal(eventId) {
const event = events.find(e => e.id === eventId);
if (!event) return;
// Set form values
editEventId.value = event.id;
// Set event type
editEventTypeRadios.forEach(radio => {
radio.checked = radio.value === event.type;
});
editEventDate.value = event.date;
editCompanySelect.value = event.companyId || '';
editEventDescription.value = event.description;
editEventFee.value = event.fee;
// Set reminder if exists
if (event.reminder) {
editSetReminder.checked = true;
editReminderOptions.classList.remove('hidden');
document.querySelector(`input[name="editReminderTime"][value="${event.reminder}"]`).checked = true;
} else {
editSetReminder.checked = false;
editReminderOptions.classList.add('hidden');
}
// Toggle fields based on event type
if (event.type === 'block') {
editCompanySection.classList.add('hidden');
editFeeSection.classList.add('hidden');
editReminderSection.classList.add('hidden');
} else {
editCompanySection.classList.remove('hidden');
editFeeSection.classList.remove('hidden');
editReminderSection.classList.remove('hidden');
}
// Show modal
editModal.classList.remove('hidden');
}
// Save edited event
function saveEditedEvent(e) {
e.preventDefault();
const eventId = parseInt(editEventId.value);
const eventIndex = events.findIndex(e => e.id === eventId);
if (eventIndex === -1) return;
const eventType = document.querySelector('input[name="editEventType"]:checked').value;
const date = editEventDate.value;
const companyId = eventType === 'appointment' ? parseInt(editCompanySelect.value) : null;
const description = editEventDescription.value;
const fee = eventType === 'appointment' ? parseFloat(editEventFee.value) || 0 : 0;
let reminder = null;
if (eventType === 'appointment' && editSetReminder.checked) {
const reminderTime = document.querySelector('input[name="editReminderTime"]:checked');
if (reminderTime) {
reminder = parseInt(reminderTime.value);
}
}
// Update event (in a real app, this would be an API call)
events[eventIndex] = {
...events[eventIndex],
date: date,
type: eventType,
companyId: companyId,
description: description,
fee: fee,
reminder: reminder
};
// Close modal
editModal.classList.add('hidden');
// Reload agenda
loadAgenda();
updateStatistics();
// Show success message
showNotification('Sucesso', 'Evento atualizado com sucesso!');
}
// Delete event
function deleteEvent(eventId = null) {
if (!eventId) {
eventId = parseInt(editEventId.value);
}
// Delete event (in a real app, this would be an API call)
events = events.filter(e => e.id !== eventId);
// Close modal if open
editModal.classList.add('hidden');
// Reload agenda
loadAgenda();
updateStatistics();
// Show success message
showNotification('Sucesso', 'Evento excluído com sucesso!');
}
// Update statistics
function updateStatistics() {
const appointments = events.filter(e => e.type === 'appointment').length;
const blocks = events.filter(e => e.type === 'block').length;
const total = events.reduce((sum, event) => sum + (event.fee || 0), 0);
appointmentsCount.textContent = appointments;
blocksCount.textContent = blocks;
totalFee.textContent = formatCurrency(total);
}
// Sync data (simulated)
function syncData() {
const originalBtnContent = syncBtn.innerHTML;
// Show loading state
syncBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Sincronizando';
syncBtn.disabled = true;
// Simulate API call
setTimeout(() => {
// In a real app, this would fetch data from server
showNotification('Sincronizado', 'Dados sincronizados com sucesso!');
// Restore button state
syncBtn.innerHTML = originalBtnContent;
syncBtn.disabled = false;
}, 1500);
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
</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=sidneibarreto/projetos" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>