Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Real Estate Content Calendar Pro</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"> | |
<script> | |
tailwind.config = { | |
theme: { | |
extend: { | |
colors: { | |
primary: '#4F46E5', | |
secondary: '#10B981', | |
accent: '#F59E0B', | |
dark: '#1F2937', | |
light: '#F3F4F6' | |
} | |
} | |
} | |
} | |
</script> | |
<style> | |
.timeline-connector { | |
position: absolute; | |
left: 24px; | |
top: 36px; | |
bottom: -12px; | |
width: 2px; | |
background-color: #E5E7EB; | |
} | |
.draggable { | |
cursor: move; | |
} | |
.draggable.dragging { | |
opacity: 0.5; | |
background-color: #E5E7EB; | |
} | |
.platform-tag { | |
font-size: 0.7rem; | |
padding: 0.2rem 0.4rem; | |
border-radius: 0.25rem; | |
} | |
.animate-pulse { | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
@keyframes pulse { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.5; } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<!-- Header --> | |
<header class="mb-8"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<h1 class="text-3xl font-bold text-dark">Content Calendar Pro</h1> | |
<p class="text-gray-600">30-Day Real Estate Content Planner</p> | |
</div> | |
<div class="flex items-center space-x-4"> | |
<button id="aiSuggestBtn" class="bg-primary hover:bg-primary-dark text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-magic mr-2"></i> AI Suggestions | |
</button> | |
<button id="exportBtn" class="border border-primary text-primary hover:bg-primary hover:text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-file-export mr-2"></i> Export | |
</button> | |
</div> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
<!-- Left Sidebar --> | |
<div class="lg:col-span-1 bg-white rounded-xl shadow p-6 h-fit"> | |
<h2 class="text-xl font-semibold mb-4 text-dark">Content Library</h2> | |
<div class="mb-6"> | |
<h3 class="font-medium text-gray-700 mb-2">Content Types</h3> | |
<div class="space-y-2"> | |
<div class="draggable bg-light p-3 rounded-lg border border-gray-200" draggable="true" data-type="property-showcase"> | |
<div class="flex items-center"> | |
<i class="fas fa-home text-accent mr-3"></i> | |
<span>Property Showcase</span> | |
</div> | |
</div> | |
<div class="draggable bg-light p-3 rounded-lg border border-gray-200" draggable="true" data-type="market-update"> | |
<div class="flex items-center"> | |
<i class="fas fa-chart-line text-secondary mr-3"></i> | |
<span>Market Update</span> | |
</div> | |
</div> | |
<div class="draggable bg-light p-3 rounded-lg border border-gray-200" draggable="true" data-type="client-testimonial"> | |
<div class="flex items-center"> | |
<i class="fas fa-quote-left text-primary mr-3"></i> | |
<span>Client Testimonial</span> | |
</div> | |
</div> | |
<div class="draggable bg-light p-3 rounded-lg border border-gray-200" draggable="true" data-type="neighborhood-guide"> | |
<div class="flex items-center"> | |
<i class="fas fa-map-marked-alt text-green-500 mr-3"></i> | |
<span>Neighborhood Guide</span> | |
</div> | |
</div> | |
<div class="draggable bg-light p-3 rounded-lg border border-gray-200" draggable="true" data-type="home-tips"> | |
<div class="flex items-center"> | |
<i class="fas fa-lightbulb text-yellow-500 mr-3"></i> | |
<span>Home Tips</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h3 class="font-medium text-gray-700 mb-2">Time Blocks</h3> | |
<div class="grid grid-cols-2 gap-2"> | |
<div class="draggable bg-light p-2 rounded-lg border border-gray-200 text-center" draggable="true" data-time="15"> | |
<span>15 min</span> | |
</div> | |
<div class="draggable bg-light p-2 rounded-lg border border-gray-200 text-center" draggable="true" data-time="30"> | |
<span>30 min</span> | |
</div> | |
<div class="draggable bg-light p-2 rounded-lg border border-gray-200 text-center" draggable="true" data-time="45"> | |
<span>45 min</span> | |
</div> | |
<div class="draggable bg-light p-2 rounded-lg border border-gray-200 text-center" draggable="true" data-time="60"> | |
<span>1 hour</span> | |
</div> | |
</div> | |
</div> | |
<div> | |
<h3 class="font-medium text-gray-700 mb-2">Platforms</h3> | |
<div class="flex flex-wrap gap-2"> | |
<span class="platform-tag bg-blue-100 text-blue-800 border border-blue-200">Instagram</span> | |
<span class="platform-tag bg-blue-600 text-white">Facebook</span> | |
<span class="platform-tag bg-black text-white">TikTok</span> | |
<span class="platform-tag bg-blue-700 text-white">LinkedIn</span> | |
</div> | |
</div> | |
</div> | |
<!-- Calendar View --> | |
<div class="lg:col-span-3"> | |
<div class="bg-white rounded-xl shadow overflow-hidden"> | |
<!-- Calendar Header --> | |
<div class="border-b border-gray-200 p-4"> | |
<div class="flex justify-between items-center"> | |
<h2 class="text-xl font-semibold text-dark">30-Day Content Plan</h2> | |
<div class="flex items-center space-x-2"> | |
<button id="prevMonth" class="p-2 rounded-full hover:bg-gray-100"> | |
<i class="fas fa-chevron-left"></i> | |
</button> | |
<span class="font-medium">June 2023</span> | |
<button id="nextMonth" class="p-2 rounded-full hover:bg-gray-100"> | |
<i class="fas fa-chevron-right"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Days of Week --> | |
<div class="grid grid-cols-7 bg-gray-50 text-gray-500 text-sm font-medium"> | |
<div class="py-2 text-center">Sun</div> | |
<div class="py-2 text-center">Mon</div> | |
<div class="py-2 text-center">Tue</div> | |
<div class="py-2 text-center">Wed</div> | |
<div class="py-2 text-center">Thu</div> | |
<div class="py-2 text-center">Fri</div> | |
<div class="py-2 text-center">Sat</div> | |
</div> | |
<!-- Calendar Grid --> | |
<div id="calendarGrid" class="grid grid-cols-7 gap-px bg-gray-200"> | |
<!-- Days will be populated by JavaScript --> | |
</div> | |
</div> | |
<!-- Timeline View --> | |
<div class="mt-6 bg-white rounded-xl shadow p-6"> | |
<h2 class="text-xl font-semibold mb-4 text-dark">Content Workflow Timeline</h2> | |
<div id="timelineContainer" class="space-y-4"> | |
<!-- Timeline items will be added here --> | |
<div class="text-center py-8 text-gray-400"> | |
<i class="fas fa-plus-circle text-3xl mb-2"></i> | |
<p>Drag content items from the library to begin planning</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- AI Suggestions Modal --> | |
<div id="aiModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-xl p-6 w-full max-w-2xl"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-semibold">AI Content Suggestions</h3> | |
<button id="closeAiModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Focus Area</label> | |
<select id="aiFocus" class="w-full p-2 border border-gray-300 rounded-lg"> | |
<option value="listings">Property Listings</option> | |
<option value="market">Market Updates</option> | |
<option value="testimonials">Client Success Stories</option> | |
<option value="neighborhood">Neighborhood Highlights</option> | |
<option value="tips">Home Buying/Selling Tips</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Platforms</label> | |
<div class="flex flex-wrap gap-2"> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="form-checkbox" value="instagram" checked> | |
<span class="ml-2">Instagram</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="form-checkbox" value="facebook" checked> | |
<span class="ml-2">Facebook</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="form-checkbox" value="tiktok"> | |
<span class="ml-2">TikTok</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="form-checkbox" value="linkedin"> | |
<span class="ml-2">LinkedIn</span> | |
</label> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Content Style</label> | |
<select id="aiStyle" class="w-full p-2 border border-gray-300 rounded-lg"> | |
<option value="professional">Professional</option> | |
<option value="casual">Casual/Friendly</option> | |
<option value="humorous">Humorous</option> | |
<option value="educational">Educational</option> | |
</select> | |
</div> | |
<button id="generateSuggestions" class="w-full bg-primary hover:bg-primary-dark text-white py-2 px-4 rounded-lg flex items-center justify-center"> | |
<i class="fas fa-magic mr-2"></i> Generate 30-Day Content Plan | |
</button> | |
<div id="aiLoading" class="hidden mt-4 text-center py-8"> | |
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary mx-auto"></div> | |
<p class="mt-4 text-gray-600">Generating your personalized content plan...</p> | |
</div> | |
<div id="aiResults" class="hidden mt-4 space-y-4 max-h-96 overflow-y-auto p-2"></div> | |
</div> | |
</div> | |
<!-- Task Detail Modal --> | |
<div id="taskModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-xl p-6 w-full max-w-md"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-semibold">Content Task Details</h3> | |
<button id="closeTaskModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Content Type</label> | |
<input type="text" id="taskType" class="w-full p-2 border border-gray-300 rounded-lg" readonly> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Title/Description</label> | |
<textarea id="taskDescription" class="w-full p-2 border border-gray-300 rounded-lg" rows="3"></textarea> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Platforms</label> | |
<div class="flex flex-wrap gap-2"> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="platform-checkbox" value="instagram"> | |
<span class="ml-2">Instagram</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="platform-checkbox" value="facebook"> | |
<span class="ml-2">Facebook</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="platform-checkbox" value="tiktok"> | |
<span class="ml-2">TikTok</span> | |
</label> | |
<label class="inline-flex items-center"> | |
<input type="checkbox" class="platform-checkbox" value="linkedin"> | |
<span class="ml-2">LinkedIn</span> | |
</label> | |
</div> | |
</div> | |
<div class="grid grid-cols-2 gap-4 mb-4"> | |
<div> | |
<label class="block text-gray-700 mb-2">Start Date</label> | |
<input type="date" id="taskStartDate" class="w-full p-2 border border-gray-300 rounded-lg"> | |
</div> | |
<div> | |
<label class="block text-gray-700 mb-2">Time Required</label> | |
<select id="taskTime" class="w-full p-2 border border-gray-300 rounded-lg"> | |
<option value="15">15 minutes</option> | |
<option value="30">30 minutes</option> | |
<option value="45">45 minutes</option> | |
<option value="60">1 hour</option> | |
<option value="90">1.5 hours</option> | |
<option value="120">2 hours</option> | |
</select> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 mb-2">Dependencies</label> | |
<select id="taskDependencies" class="w-full p-2 border border-gray-300 rounded-lg" multiple> | |
<!-- Will be populated with other tasks --> | |
</select> | |
</div> | |
<div class="flex justify-end space-x-2"> | |
<button id="saveTask" class="bg-primary hover:bg-primary-dark text-white py-2 px-4 rounded-lg"> | |
Save Changes | |
</button> | |
<button id="deleteTask" class="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-lg"> | |
Delete Task | |
</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Sample data | |
let currentDate = new Date(); | |
let tasks = []; | |
let currentTaskId = null; | |
// DOM Elements | |
const calendarGrid = document.getElementById('calendarGrid'); | |
const timelineContainer = document.getElementById('timelineContainer'); | |
const aiModal = document.getElementById('aiModal'); | |
const aiSuggestBtn = document.getElementById('aiSuggestBtn'); | |
const closeAiModal = document.getElementById('closeAiModal'); | |
const generateSuggestions = document.getElementById('generateSuggestions'); | |
const aiResults = document.getElementById('aiResults'); | |
const aiLoading = document.getElementById('aiLoading'); | |
const taskModal = document.getElementById('taskModal'); | |
const closeTaskModal = document.getElementById('closeTaskModal'); | |
const saveTask = document.getElementById('saveTask'); | |
const deleteTask = document.getElementById('deleteTask'); | |
// Initialize the app | |
document.addEventListener('DOMContentLoaded', function() { | |
renderCalendar(); | |
setupDragAndDrop(); | |
setupEventListeners(); | |
}); | |
// Render the calendar | |
function renderCalendar() { | |
calendarGrid.innerHTML = ''; | |
// Get the first day of the month and total days | |
const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getDay(); | |
const daysInMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate(); | |
// Add empty cells for days before the first day of the month | |
for (let i = 0; i < firstDay; i++) { | |
const dayCell = document.createElement('div'); | |
dayCell.className = 'bg-gray-50 h-32 p-2'; | |
calendarGrid.appendChild(dayCell); | |
} | |
// Add cells for each day of the month | |
for (let i = 1; i <= daysInMonth; i++) { | |
const dayCell = document.createElement('div'); | |
dayCell.className = 'bg-white h-32 p-2 relative'; | |
const dayNumber = document.createElement('div'); | |
dayNumber.className = 'font-medium text-gray-700'; | |
dayNumber.textContent = i; | |
dayCell.appendChild(dayNumber); | |
// Add tasks for this day | |
const dayTasks = tasks.filter(task => { | |
const taskDate = new Date(task.startDate); | |
return taskDate.getDate() === i && | |
taskDate.getMonth() === currentDate.getMonth() && | |
taskDate.getFullYear() === currentDate.getFullYear(); | |
}); | |
const tasksContainer = document.createElement('div'); | |
tasksContainer.className = 'mt-1 space-y-1 overflow-y-auto max-h-24'; | |
dayTasks.forEach(task => { | |
const taskElement = document.createElement('div'); | |
taskElement.className = 'text-xs p-1 rounded truncate cursor-pointer'; | |
// Set color based on content type | |
if (task.type === 'property-showcase') { | |
taskElement.className += ' bg-yellow-100 text-yellow-800'; | |
} else if (task.type === 'market-update') { | |
taskElement.className += ' bg-green-100 text-green-800'; | |
} else if (task.type === 'client-testimonial') { | |
taskElement.className += ' bg-blue-100 text-blue-800'; | |
} else if (task.type === 'neighborhood-guide') { | |
taskElement.className += ' bg-purple-100 text-purple-800'; | |
} else { | |
taskElement.className += ' bg-gray-100 text-gray-800'; | |
} | |
taskElement.textContent = task.title || task.type; | |
taskElement.addEventListener('click', () => openTaskModal(task.id)); | |
tasksContainer.appendChild(taskElement); | |
}); | |
dayCell.appendChild(tasksContainer); | |
calendarGrid.appendChild(dayCell); | |
} | |
// Render timeline | |
renderTimeline(); | |
} | |
// Render the timeline view | |
function renderTimeline() { | |
if (tasks.length === 0) { | |
timelineContainer.innerHTML = ` | |
<div class="text-center py-8 text-gray-400"> | |
<i class="fas fa-plus-circle text-3xl mb-2"></i> | |
<p>Drag content items from the library to begin planning</p> | |
</div> | |
`; | |
return; | |
} | |
timelineContainer.innerHTML = ''; | |
// Sort tasks by date | |
const sortedTasks = [...tasks].sort((a, b) => new Date(a.startDate) - new Date(b.startDate)); | |
sortedTasks.forEach((task, index) => { | |
const taskElement = document.createElement('div'); | |
taskElement.className = 'relative pl-12'; | |
taskElement.dataset.taskId = task.id; | |
// Add connector line except for last item | |
if (index < sortedTasks.length - 1) { | |
const connector = document.createElement('div'); | |
connector.className = 'timeline-connector'; | |
taskElement.appendChild(connector); | |
} | |
// Task card | |
const card = document.createElement('div'); | |
card.className = 'bg-white border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow cursor-pointer'; | |
card.addEventListener('click', () => openTaskModal(task.id)); | |
// Task header | |
const header = document.createElement('div'); | |
header.className = 'flex justify-between items-start mb-2'; | |
const title = document.createElement('h3'); | |
title.className = 'font-medium text-dark'; | |
title.textContent = task.title || formatTaskType(task.type); | |
const platforms = document.createElement('div'); | |
platforms.className = 'flex space-x-1'; | |
task.platforms.forEach(platform => { | |
const platformTag = document.createElement('span'); | |
platformTag.className = 'platform-tag text-xs'; | |
if (platform === 'instagram') { | |
platformTag.className += ' bg-blue-100 text-blue-800 border border-blue-200'; | |
} else if (platform === 'facebook') { | |
platformTag.className += ' bg-blue-600 text-white'; | |
} else if (platform === 'tiktok') { | |
platformTag.className += ' bg-black text-white'; | |
} else if (platform === 'linkedin') { | |
platformTag.className += ' bg-blue-700 text-white'; | |
} | |
platformTag.textContent = platform.charAt(0).toUpperCase() + platform.slice(1); | |
platforms.appendChild(platformTag); | |
}); | |
header.appendChild(title); | |
header.appendChild(platforms); | |
card.appendChild(header); | |
// Task details | |
const details = document.createElement('div'); | |
details.className = 'text-sm text-gray-600 mb-2'; | |
details.textContent = task.description || 'No description provided'; | |
card.appendChild(details); | |
// Task footer | |
const footer = document.createElement('div'); | |
footer.className = 'flex justify-between items-center text-xs text-gray-500'; | |
const date = document.createElement('div'); | |
date.textContent = formatDate(new Date(task.startDate)); | |
const time = document.createElement('div'); | |
time.className = 'font-medium'; | |
time.textContent = `${task.time} min`; | |
footer.appendChild(date); | |
footer.appendChild(time); | |
card.appendChild(footer); | |
// Icon | |
const icon = document.createElement('div'); | |
icon.className = 'absolute left-0 top-4 flex items-center justify-center w-10 h-10 rounded-full bg-primary text-white'; | |
const iconElement = document.createElement('i'); | |
if (task.type === 'property-showcase') { | |
iconElement.className = 'fas fa-home'; | |
} else if (task.type === 'market-update') { | |
iconElement.className = 'fas fa-chart-line'; | |
} else if (task.type === 'client-testimonial') { | |
iconElement.className = 'fas fa-quote-left'; | |
} else if (task.type === 'neighborhood-guide') { | |
iconElement.className = 'fas fa-map-marked-alt'; | |
} else { | |
iconElement.className = 'fas fa-lightbulb'; | |
} | |
icon.appendChild(iconElement); | |
taskElement.appendChild(icon); | |
taskElement.appendChild(card); | |
timelineContainer.appendChild(taskElement); | |
}); | |
} | |
// Setup drag and drop functionality | |
function setupDragAndDrop() { | |
const draggables = document.querySelectorAll('.draggable'); | |
const calendarCells = document.querySelectorAll('#calendarGrid > div'); | |
draggables.forEach(draggable => { | |
draggable.addEventListener('dragstart', () => { | |
draggable.classList.add('dragging'); | |
}); | |
draggable.addEventListener('dragend', () => { | |
draggable.classList.remove('dragging'); | |
}); | |
}); | |
calendarCells.forEach(cell => { | |
cell.addEventListener('dragover', e => { | |
e.preventDefault(); | |
const dragging = document.querySelector('.dragging'); | |
if (dragging) { | |
cell.classList.add('bg-blue-50'); | |
} | |
}); | |
cell.addEventListener('dragleave', () => { | |
cell.classList.remove('bg-blue-50'); | |
}); | |
cell.addEventListener('drop', e => { | |
e.preventDefault(); | |
cell.classList.remove('bg-blue-50'); | |
const dragging = document.querySelector('.dragging'); | |
if (dragging) { | |
const day = parseInt(cell.firstChild.textContent); | |
const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); | |
if (dragging.dataset.type) { | |
// It's a content type | |
const newTask = { | |
id: Date.now().toString(), | |
type: dragging.dataset.type, | |
title: '', | |
description: '', | |
platforms: ['instagram', 'facebook'], // Default platforms | |
startDate: date.toISOString().split('T')[0], | |
time: 30, // Default time | |
dependencies: [] | |
}; | |
tasks.push(newTask); | |
renderCalendar(); | |
openTaskModal(newTask.id); | |
} else if (dragging.dataset.time) { | |
// It's a time block - find if there's a task for this day | |
const dayTasks = tasks.filter(task => { | |
const taskDate = new Date(task.startDate); | |
return taskDate.getDate() === day && | |
taskDate.getMonth() === currentDate.getMonth() && | |
taskDate.getFullYear() === currentDate.getFullYear(); | |
}); | |
if (dayTasks.length > 0) { | |
const task = dayTasks[0]; | |
task.time = parseInt(dragging.dataset.time); | |
renderCalendar(); | |
renderTimeline(); | |
} | |
} | |
} | |
}); | |
}); | |
// Also allow dropping on timeline | |
timelineContainer.addEventListener('dragover', e => { | |
e.preventDefault(); | |
const dragging = document.querySelector('.dragging'); | |
if (dragging && dragging.dataset.time) { | |
timelineContainer.classList.add('bg-blue-50'); | |
} | |
}); | |
timelineContainer.addEventListener('dragleave', () => { | |
timelineContainer.classList.remove('bg-blue-50'); | |
}); | |
timelineContainer.addEventListener('drop', e => { | |
e.preventDefault(); | |
timelineContainer.classList.remove('bg-blue-50'); | |
const dragging = document.querySelector('.dragging'); | |
if (dragging && dragging.dataset.time) { | |
// Find the task that was hovered over | |
const taskElement = document.elementFromPoint(e.clientX, e.clientY); | |
const taskId = taskElement.closest('[data-task-id]')?.dataset.taskId; | |
if (taskId) { | |
const task = tasks.find(t => t.id === taskId); | |
if (task) { | |
task.time = parseInt(dragging.dataset.time); | |
renderTimeline(); | |
} | |
} | |
} | |
}); | |
} | |
// Setup event listeners | |
function setupEventListeners() { | |
// AI Modal | |
aiSuggestBtn.addEventListener('click', () => { | |
aiModal.classList.remove('hidden'); | |
}); | |
closeAiModal.addEventListener('click', () => { | |
aiModal.classList.add('hidden'); | |
aiResults.innerHTML = ''; | |
aiResults.classList.add('hidden'); | |
aiLoading.classList.add('hidden'); | |
}); | |
generateSuggestions.addEventListener('click', generateAiSuggestions); | |
// Task Modal | |
closeTaskModal.addEventListener('click', () => { | |
taskModal.classList.add('hidden'); | |
}); | |
saveTask.addEventListener('click', saveCurrentTask); | |
deleteTask.addEventListener('click', () => { | |
if (currentTaskId && confirm('Are you sure you want to delete this task?')) { | |
tasks = tasks.filter(task => task.id !== currentTaskId); | |
renderCalendar(); | |
renderTimeline(); | |
taskModal.classList.add('hidden'); | |
} | |
}); | |
// Navigation | |
document.getElementById('prevMonth').addEventListener('click', () => { | |
currentDate.setMonth(currentDate.getMonth() - 1); | |
renderCalendar(); | |
}); | |
document.getElementById('nextMonth').addEventListener('click', () => { | |
currentDate.setMonth(currentDate.getMonth() + 1); | |
renderCalendar(); | |
}); | |
// Export | |
document.getElementById('exportBtn').addEventListener('click', exportCalendar); | |
} | |
// Open task modal for editing | |
function openTaskModal(taskId) { | |
const task = tasks.find(t => t.id === taskId); | |
if (!task) return; | |
currentTaskId = taskId; | |
// Populate modal fields | |
document.getElementById('taskType').value = formatTaskType(task.type); | |
document.getElementById('taskDescription').value = task.description || ''; | |
document.getElementById('taskStartDate').value = task.startDate; | |
document.getElementById('taskTime').value = task.time; | |
// Clear and set platforms | |
document.querySelectorAll('.platform-checkbox').forEach(checkbox => { | |
checkbox.checked = task.platforms.includes(checkbox.value); | |
}); | |
// Populate dependencies | |
const dependenciesSelect = document.getElementById('taskDependencies'); | |
dependenciesSelect.innerHTML = ''; | |
tasks.filter(t => t.id !== taskId).forEach(t => { | |
const option = document.createElement('option'); | |
option.value = t.id; | |
option.textContent = `${formatTaskType(t.type)} - ${formatDate(new Date(t.startDate))}`; | |
option.selected = task.dependencies.includes(t.id); | |
dependenciesSelect.appendChild(option); | |
}); | |
taskModal.classList.remove('hidden'); | |
} | |
// Save current task changes | |
function saveCurrentTask() { | |
if (!currentTaskId) return; | |
const taskIndex = tasks.findIndex(t => t.id === currentTaskId); | |
if (taskIndex === -1) return; | |
// Get platform selections | |
const platforms = []; | |
document.querySelectorAll('.platform-checkbox:checked').forEach(checkbox => { | |
platforms.push(checkbox.value); | |
}); | |
// Get dependencies | |
const dependencies = []; | |
const options = document.getElementById('taskDependencies').options; | |
for (let i = 0; i < options.length; i++) { | |
if (options[i].selected) { | |
dependencies.push(options[i].value); | |
} | |
} | |
// Update task | |
tasks[taskIndex] = { | |
...tasks[taskIndex], | |
title: document.getElementById('taskDescription').value.split('\n')[0] || '', | |
description: document.getElementById('taskDescription').value, | |
platforms, | |
startDate: document.getElementById('taskStartDate').value, | |
time: parseInt(document.getElementById('taskTime').value), | |
dependencies | |
}; | |
renderCalendar(); | |
renderTimeline(); | |
taskModal.classList.add('hidden'); | |
} | |
// Generate AI suggestions | |
function generateAiSuggestions() { | |
const focus = document.getElementById('aiFocus').value; | |
const style = document.getElementById('aiStyle').value; | |
// Get selected platforms | |
const platforms = []; | |
document.querySelectorAll('#aiModal input[type="checkbox"]:checked').forEach(checkbox => { | |
platforms.push(checkbox.value); | |
}); | |
if (platforms.length === 0) { | |
alert('Please select at least one platform'); | |
return; | |
} | |
aiResults.innerHTML = ''; | |
aiResults.classList.add('hidden'); | |
aiLoading.classList.remove('hidden'); | |
// Simulate API call (in a real app, you would call the DeepSeek API here) | |
setTimeout(() => { | |
aiLoading.classList.add('hidden'); | |
aiResults.classList.remove('hidden'); | |
// Generate sample suggestions based on focus | |
const suggestions = generateSampleSuggestions(focus, platforms, style); | |
suggestions.forEach((suggestion, index) => { | |
const suggestionElement = document.createElement('div'); | |
suggestionElement.className = 'border border-gray-200 rounded-lg p-4'; | |
const header = document.createElement('div'); | |
header.className = 'flex justify-between items-center mb-2'; | |
const day = document.createElement('span'); | |
day.className = 'font-medium text-primary'; | |
day.textContent = `Day ${index + 1}`; | |
const platforms = document.createElement('div'); | |
platforms.className = 'flex space-x-1'; | |
suggestion.platforms.forEach(platform => { | |
const platformTag = document.createElement('span'); | |
platformTag.className = 'platform-tag text-xs'; | |
if (platform === 'instagram') { | |
platformTag.className += ' bg-blue-100 text-blue-800 border border-blue-200'; | |
} else if (platform === 'facebook') { | |
platformTag.className += ' bg-blue-600 text-white'; | |
} else if (platform === 'tiktok') { | |
platformTag.className += ' bg-black text-white'; | |
} else if (platform === 'linkedin') { | |
platformTag.className += ' bg-blue-700 text-white'; | |
} | |
platformTag.textContent = platform.charAt(0).toUpperCase() + platform.slice(1); | |
platforms.appendChild(platformTag); | |
}); | |
header.appendChild(day); | |
header.appendChild(platforms); | |
suggestionElement.appendChild(header); | |
const title = document.createElement('h4'); | |
title.className = 'font-medium text-lg mb-1'; | |
title.textContent = suggestion.title; | |
suggestionElement.appendChild(title); | |
const content = document.createElement('p'); | |
content.className = 'text-gray-600 mb-2'; | |
content.textContent = suggestion.content; | |
suggestionElement.appendChild(content); | |
const hashtags = document.createElement('div'); | |
hashtags.className = 'text-sm text-blue-500'; | |
hashtags.textContent = suggestion.hashtags; | |
suggestionElement.appendChild(hashtags); | |
const addButton = document.createElement('button'); | |
addButton.className = 'mt-2 w-full bg-secondary hover:bg-green-700 text-white py-1 px-3 rounded text-sm'; | |
addButton.textContent = 'Add to Calendar'; | |
addButton.addEventListener('click', () => addAiSuggestionToCalendar(suggestion, index + 1)); | |
suggestionElement.appendChild(addButton); | |
aiResults.appendChild(suggestionElement); | |
}); | |
}, 1500); | |
} | |
// Add AI suggestion to calendar | |
function addAiSuggestionToCalendar(suggestion, dayOffset) { | |
const date = new Date(); | |
date.setDate(date.getDate() + dayOffset); | |
const newTask = { | |
id: Date.now().toString(), | |
type: suggestion.type || 'custom', | |
title: suggestion.title, | |
description: suggestion.content + '\n\n' + suggestion.hashtags, | |
platforms: suggestion.platforms, | |
startDate: date.toISOString().split('T')[0], | |
time: 45, // Default time for AI suggestions | |
dependencies: [] | |
}; | |
tasks.push(newTask); | |
renderCalendar(); | |
renderTimeline(); | |
// Scroll to the new task in timeline | |
setTimeout(() => { | |
const taskElement = document.querySelector(`[data-task-id="${newTask.id}"]`); | |
if (taskElement) { | |
taskElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
} | |
}, 100); | |
} | |
// Export calendar | |
function exportCalendar() { | |
if (tasks.length === 0) { | |
alert('No content to export'); | |
return; | |
} | |
// Sort tasks by date | |
const sortedTasks = [...tasks].sort((a, b) => new Date(a.startDate) - new Date(b.startDate)); | |
// Create CSV content | |
let csvContent = "Date,Content Type,Title,Platforms,Time (min),Description\n"; | |
sortedTasks.forEach(task => { | |
const date = formatDate(new Date(task.startDate)); | |
const type = formatTaskType(task.type); | |
const title = task.title || ''; | |
const platforms = task.platforms.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(', '); | |
const time = task.time; | |
const description = task.description.replace(/"/g, '""').replace(/\n/g, ' '); | |
csvContent += `"${date}","${type}","${title}","${platforms}","${time}","${description}"\n`; | |
}); | |
// Create download link | |
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); | |
const url = URL.createObjectURL(blob); | |
const link = document.createElement('a'); | |
link.setAttribute('href', url); | |
link.setAttribute('download', `content_calendar_${formatDate(new Date())}.csv`); | |
link.style.visibility = 'hidden'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
// Helper functions | |
function formatDate(date) { | |
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); | |
} | |
function formatTaskType(type) { | |
return type.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); | |
} | |
function generateSampleSuggestions(focus, platforms, style) { | |
const suggestions = []; | |
const contentTypes = { | |
listings: 'property-showcase', | |
market: 'market-update', | |
testimonials: 'client-testimonial', | |
neighborhood: 'neighborhood-guide', | |
tips: 'home-tips' | |
}; | |
const type = contentTypes[focus] || 'custom'; | |
for (let i = 1; i <= 30; i++) { | |
let title, content, hashtags; | |
switch (focus) { | |
case 'listings': | |
title = `New Listing Alert: Beautiful ${i % 2 === 0 ? '3BR Home' : 'Downtown Condo'}`; | |
content = `Just listed! This stunning property features ${i % 2 === 0 ? 'a spacious backyard and modern kitchen' : 'floor-to-ceiling windows and luxury finishes'}. DM me for a private showing!`; | |
hashtags = '#RealEstate #NewListing #DreamHome #HouseHunting'; | |
break; | |
case 'market': | |
title = `${['June', 'July', 'August'][i % 3]} Market Update`; | |
content = `The local real estate market is ${['heating up', 'staying steady', 'seeing more inventory'][i % 3]}. Here are the latest stats and what they mean for ${['buyers', 'sellers', 'investors'][i % 3]}.`; | |
hashtags = '#MarketUpdate #RealEstateTrends #LocalMarket'; | |
break; | |
case 'testimonials': | |
title = `Success Story: ${['First-Time', 'Move-Up', 'Downsizing'][i % 3]} Buyers`; | |
content = `"Working with me made the ${['home buying', 'selling', 'investment'][i % 3]} process so smooth!" Hear from my recent clients about their experience.`; | |
hashtags = '#ClientSuccess #Testimonial #HappyClients'; | |
break; | |
case 'neighborhood': | |
title = `Neighborhood Spotlight: ${['Downtown', 'Suburban', 'Waterfront'][i % 3]} Area`; | |
content = `What makes ${['the downtown core', 'this family-friendly suburb', 'this waterfront community'][i % 3]} so special? Here are the top ${['5', '7', '3'][i % 3]} reasons to consider living here.`; | |
hashtags = '#NeighborhoodGuide #LocalLife #Community'; | |
break; | |
case 'tips': | |
title = `Tip #${i}: ${['Pricing', 'Staging', 'Negotiation'][i % 3]} Strategies`; | |
content = `My top ${['3', '5', '7'][i % 3]} ${['pricing', 'staging', 'negotiation'][i % 3]} tips for ${['sellers', 'buyers', 'investors'][i % 3]} in today's market.`; | |
hashtags = '#RealEstateTips #HomeBuying #SellingSmart'; | |
break; | |
default: | |
title = `Content Idea ${i}`; | |
content = `This is a sample content idea for your real estate business. Customize it with your specific details and expertise.`; | |
hashtags = '#RealEstate #ContentMarketing'; | |
} | |
// Adjust content based on style | |
if (style === 'casual') { | |
content = content.replace(/Here are/g, "Check out these").replace(/This is/g, "Here's"); | |
if (!content.includes("!")) content += "!"; | |
} else if (style === 'humorous') { | |
content = content.replace(/The local/g, "So, the local").replace(/Just listed/g, "Hot off the presses"); | |
if (!content.includes("π")) content += " π"; | |
} else if (style === 'educational') { | |
content = content.replace(/Here are/g, "In this post, we'll examine").replace(/This stunning/g, "From an investment perspective, this"); | |
} | |
suggestions.push({ | |
type, | |
title, | |
content, | |
hashtags, | |
platforms: platforms.length > 2 ? [platforms[i % platforms.length]] : platforms | |
}); | |
} | |
return suggestions; | |
} | |
</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=JayStormX8/content-calendar-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |