Initial commit - Black Canyon Tickets whitelabel platform
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
610
src/pages/dashboard.astro
Normal file
610
src/pages/dashboard.astro
Normal file
@@ -0,0 +1,610 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Navigation from '../components/Navigation.astro';
|
||||
---
|
||||
|
||||
<Layout title="Dashboard - Black Canyon Tickets">
|
||||
<style>
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeInUp {
|
||||
animation: fadeInUp 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-slideIn {
|
||||
animation: slideIn 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.hover-float:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.bg-grid-pattern {
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-slate-900">
|
||||
<!-- Animated background elements -->
|
||||
<div class="fixed inset-0 overflow-hidden pointer-events-none">
|
||||
<div class="absolute -top-40 -right-40 w-80 h-80 bg-gradient-to-br from-purple-600/20 to-pink-600/20 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute -bottom-40 -left-40 w-80 h-80 bg-gradient-to-br from-blue-600/20 to-cyan-600/20 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-gradient-to-br from-indigo-600/10 to-purple-600/10 rounded-full blur-3xl animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
<!-- Grid pattern overlay -->
|
||||
<div class="absolute inset-0 bg-grid-pattern opacity-5"></div>
|
||||
|
||||
<Navigation title="Dashboard" />
|
||||
|
||||
<main class="relative max-w-7xl mx-auto py-8 sm:px-6 lg:px-8">
|
||||
<div class="px-4 sm:px-0">
|
||||
<!-- Dashboard Header -->
|
||||
<div class="mb-12 animate-slideIn">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-6">
|
||||
<div>
|
||||
<h1 class="text-4xl md:text-5xl font-light text-white tracking-wide">Dashboard</h1>
|
||||
<p class="text-white/80 mt-2 text-lg font-light">Manage your events and track performance</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<button
|
||||
id="toggle-view-btn"
|
||||
class="bg-white/10 backdrop-blur-lg border border-white/20 hover:bg-white/20 text-white px-6 py-3 rounded-xl text-sm font-medium transition-all duration-200 shadow-lg shadow-black/10 flex items-center gap-2 hover:shadow-xl hover:scale-105"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span id="view-text">Calendar View</span>
|
||||
</button>
|
||||
<a
|
||||
href="/settings/fees"
|
||||
class="bg-white/10 backdrop-blur-lg border border-white/20 hover:bg-white/20 text-white px-6 py-3 rounded-xl text-sm font-medium transition-all duration-200 shadow-lg shadow-black/10 flex items-center gap-2 hover:shadow-xl hover:scale-105"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Fee Settings
|
||||
</a>
|
||||
<a
|
||||
href="/events/new"
|
||||
class="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-6 py-3 rounded-xl text-sm font-medium transition-all duration-200 shadow-lg shadow-blue-500/30 flex items-center gap-2 hover:shadow-xl hover:scale-105"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Create Event
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats Cards -->
|
||||
<div id="stats-cards" class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
||||
<!-- Stats will be populated here -->
|
||||
</div>
|
||||
|
||||
<!-- Calendar View -->
|
||||
<div id="calendar-view" class="hidden mb-8">
|
||||
<div id="calendar-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- List View -->
|
||||
<div id="list-view">
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg overflow-hidden">
|
||||
<div class="px-8 py-6 border-b border-white/20 bg-gradient-to-r from-white/10 to-white/5">
|
||||
<h3 class="text-xl font-light text-white tracking-wide">Your Events</h3>
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<div id="events-container" class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Events will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="text-center py-16">
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-12 max-w-md mx-auto">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-2 border-blue-400 border-t-transparent mx-auto mb-6"></div>
|
||||
<p class="text-white/80 font-light text-lg">Loading your events...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="no-events" class="text-center py-16 hidden">
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-16 max-w-lg mx-auto">
|
||||
<div class="w-20 h-20 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-2xl flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="h-10 w-10 text-white/60" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-light text-white mb-3 tracking-wide">No events yet</h3>
|
||||
<p class="text-white/80 mb-8 text-lg font-light leading-relaxed">Get started by creating your first event and start selling tickets.</p>
|
||||
<a
|
||||
href="/events/new"
|
||||
class="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-medium rounded-xl transition-all duration-200 shadow-lg shadow-blue-500/30 hover:shadow-xl hover:scale-105"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Create Your First Event
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
const eventsContainer = document.getElementById('events-container');
|
||||
const loading = document.getElementById('loading');
|
||||
const noEvents = document.getElementById('no-events');
|
||||
const toggleViewBtn = document.getElementById('toggle-view-btn');
|
||||
const viewText = document.getElementById('view-text');
|
||||
const calendarView = document.getElementById('calendar-view');
|
||||
const listView = document.getElementById('list-view');
|
||||
const calendarContainer = document.getElementById('calendar-container');
|
||||
const statsCards = document.getElementById('stats-cards');
|
||||
|
||||
let currentView = 'list';
|
||||
let allEvents = [];
|
||||
|
||||
// Check authentication (simplified since Navigation component handles user display)
|
||||
async function checkAuth() {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
window.location.href = '/';
|
||||
return null;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
// Load events
|
||||
async function loadEvents() {
|
||||
try {
|
||||
// Check if user has organization_id or is admin
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
const { data: userProfile, error: userError } = await supabase
|
||||
.from('users')
|
||||
.select('organization_id, role')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (userError) {
|
||||
console.error('Error loading user profile:', userError);
|
||||
console.error('User ID:', user?.id);
|
||||
loading.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-6 max-w-md mx-auto">
|
||||
<p class="text-red-600 font-medium">Error loading user profile</p>
|
||||
<p class="text-red-500 text-sm mt-2">${userError.message || userError}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('User profile loaded:', userProfile);
|
||||
|
||||
// Check if user is admin or has organization_id
|
||||
const isAdmin = userProfile?.role === 'admin';
|
||||
if (!isAdmin && !userProfile?.organization_id) {
|
||||
console.log('User has no organization_id and is not admin, showing no events');
|
||||
loading.classList.add('hidden');
|
||||
noEvents.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load events based on user permissions
|
||||
let query = supabase.from('events').select('*');
|
||||
|
||||
if (!isAdmin) {
|
||||
// Regular users see only their organization's events
|
||||
query = query.eq('organization_id', userProfile.organization_id);
|
||||
}
|
||||
// Admins see all events (no filter)
|
||||
|
||||
const { data: events, error } = await query.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error loading events from database:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('Successfully loaded events:', events?.length || 0, 'events');
|
||||
allEvents = events || [];
|
||||
loading.classList.add('hidden');
|
||||
|
||||
if (allEvents.length === 0) {
|
||||
noEvents.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
renderStatsCards();
|
||||
renderListView();
|
||||
if (currentView === 'calendar') {
|
||||
renderCalendarView();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading events:', error);
|
||||
loading.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-6 max-w-md mx-auto">
|
||||
<p class="text-red-600 font-medium">Error loading events</p>
|
||||
<p class="text-red-500 text-sm mt-2">${error.message || error}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Render stats cards
|
||||
function renderStatsCards() {
|
||||
const totalEvents = allEvents.length;
|
||||
const upcomingEvents = allEvents.filter(event => new Date(event.start_time) > new Date()).length;
|
||||
const pastEvents = totalEvents - upcomingEvents;
|
||||
|
||||
statsCards.innerHTML = `
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-8 hover:shadow-xl hover:-translate-y-1 hover:scale-105 transition-all duration-200 ease-out cursor-pointer group">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wider">Total Events</p>
|
||||
<p class="text-3xl font-light text-white mt-2 group-hover:text-blue-400 transition-colors">${totalEvents}</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-lg transition-all duration-200 ease-out">
|
||||
<svg class="w-7 h-7 text-blue-400 group-hover:text-blue-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-8 hover:shadow-xl hover:-translate-y-1 hover:scale-105 transition-all duration-200 ease-out cursor-pointer group">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wider">Upcoming Events</p>
|
||||
<p class="text-3xl font-light text-white mt-2 group-hover:text-emerald-400 transition-colors">${upcomingEvents}</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-emerald-500/20 to-teal-500/20 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-lg transition-all duration-200 ease-out">
|
||||
<svg class="w-7 h-7 text-emerald-400 group-hover:text-emerald-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-8 hover:shadow-xl hover:-translate-y-1 hover:scale-105 transition-all duration-200 ease-out cursor-pointer group">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wider">Past Events</p>
|
||||
<p class="text-3xl font-light text-white mt-2 group-hover:text-slate-400 transition-colors">${pastEvents}</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-slate-500/20 to-gray-500/20 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-lg transition-all duration-200 ease-out">
|
||||
<svg class="w-7 h-7 text-slate-400 group-hover:text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render list view
|
||||
function renderListView() {
|
||||
eventsContainer.innerHTML = allEvents.map((event, index) => {
|
||||
const eventDate = new Date(event.start_time);
|
||||
const isUpcoming = eventDate > new Date();
|
||||
const formattedDate = eventDate.toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
const formattedTime = eventDate.toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
const animationDelay = index * 0.1;
|
||||
return `
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg hover:shadow-xl hover:-translate-y-1 hover:scale-105 transition-all duration-200 ease-out overflow-hidden group">
|
||||
<div class="p-8">
|
||||
<div class="flex items-start justify-between mb-6">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl font-light text-white mb-3 tracking-wide group-hover:text-white/90 transition-colors duration-200">${event.title}</h3>
|
||||
<div class="flex items-center text-sm text-white/80 mb-3 group-hover:text-white/90 transition-colors">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
${event.venue}
|
||||
</div>
|
||||
<div class="flex items-center text-sm text-white/80 group-hover:text-white/90 transition-colors">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
${formattedDate} at ${formattedTime}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="px-3 py-1 text-xs font-semibold rounded-full ${isUpcoming ? 'bg-gradient-to-r from-emerald-500/20 to-teal-500/20 text-emerald-400' : 'bg-gradient-to-r from-slate-500/20 to-gray-500/20 text-slate-400'} transition-all duration-200 ease-out">
|
||||
${isUpcoming ? 'Upcoming' : 'Past'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${event.description ? `<p class="text-sm text-white/70 mb-6 leading-relaxed group-hover:text-white/80 transition-colors">${event.description}</p>` : ''}
|
||||
|
||||
<div class="flex items-center justify-between pt-6 border-t border-white/20">
|
||||
<div class="flex space-x-3">
|
||||
<a
|
||||
href="/events/${event.id}/manage"
|
||||
class="inline-flex items-center gap-2 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 shadow-lg shadow-blue-500/25 hover:shadow-lg hover:-translate-y-0.5 hover:scale-105"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Manage
|
||||
</a>
|
||||
<a
|
||||
href="/e/${event.slug}"
|
||||
class="inline-flex items-center gap-2 border border-white/20 hover:bg-white/10 text-white px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 shadow-lg shadow-black/10 hover:shadow-lg hover:-translate-y-0.5 hover:scale-105"
|
||||
target="_blank"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
Preview
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-xs text-white/60 font-medium">
|
||||
Created ${new Date(event.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Render calendar view
|
||||
function renderCalendarView() {
|
||||
// Initialize calendar with current date or previously selected date
|
||||
calendarContainer.innerHTML = createSimpleCalendar(allEvents, currentCalendarYear, currentCalendarMonth);
|
||||
}
|
||||
|
||||
// Enhanced calendar HTML generator with mobile responsiveness
|
||||
function createSimpleCalendar(events, year, month) {
|
||||
const monthNames = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
|
||||
let html = `
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg overflow-hidden">
|
||||
<!-- Calendar Header -->
|
||||
<div class="px-8 py-6 bg-gradient-to-r from-white/10 to-white/5 border-b border-white/20 backdrop-blur-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-2xl font-light text-white tracking-wide">${monthNames[month]} ${year}</h3>
|
||||
<div class="flex gap-3">
|
||||
<button onclick="navigateMonth(-1)" class="p-3 hover:bg-white/10 rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl hover:scale-105">
|
||||
<svg class="w-5 h-5 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="navigateMonth(1)" class="p-3 hover:bg-white/10 rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl hover:scale-105">
|
||||
<svg class="w-5 h-5 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Body -->
|
||||
<div class="p-4 sm:p-6">
|
||||
<!-- Day Headers -->
|
||||
<div class="grid grid-cols-7 gap-1 mb-4">
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Sun</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Mon</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Tue</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Wed</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Thu</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Fri</div>
|
||||
<div class="text-center text-xs sm:text-sm font-semibold text-white/80 py-2">Sat</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Grid -->
|
||||
<div class="grid grid-cols-7 gap-1 sm:gap-2">
|
||||
`;
|
||||
|
||||
// Empty cells for days before month starts
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
html += '<div class="aspect-square"></div>';
|
||||
}
|
||||
|
||||
// Days of the month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayEvents = events.filter(event => {
|
||||
const eventDate = new Date(event.start_time);
|
||||
return eventDate.getDate() === day &&
|
||||
eventDate.getMonth() === month &&
|
||||
eventDate.getFullYear() === year;
|
||||
});
|
||||
|
||||
const isToday = new Date().toDateString() === new Date(year, month, day).toDateString();
|
||||
const hasEvents = dayEvents.length > 0;
|
||||
|
||||
html += `
|
||||
<div class="aspect-square border rounded-lg p-1 sm:p-2 hover:bg-white/10 transition-colors cursor-pointer ${
|
||||
isToday ? 'bg-blue-500/20 border-blue-400 ring-2 ring-blue-400/20' :
|
||||
hasEvents ? 'border-white/30 bg-white/5' : 'border-white/20'
|
||||
}" onclick="showDayEvents(${year}, ${month}, ${day})">
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="text-xs sm:text-sm font-semibold mb-1 ${
|
||||
isToday ? 'text-blue-400' : 'text-white'
|
||||
}">${day}</div>
|
||||
|
||||
<!-- Mobile: Show event dots -->
|
||||
<div class="flex-1 sm:hidden">
|
||||
${dayEvents.length > 0 ? `
|
||||
<div class="flex flex-wrap gap-0.5">
|
||||
${dayEvents.slice(0, 3).map(() => `
|
||||
<div class="w-1.5 h-1.5 bg-blue-400 rounded-full"></div>
|
||||
`).join('')}
|
||||
${dayEvents.length > 3 ? '<div class="text-xs text-white/60">+</div>' : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<!-- Desktop: Show event titles -->
|
||||
<div class="hidden sm:block flex-1 space-y-1 overflow-hidden">
|
||||
${dayEvents.slice(0, 2).map(event => `
|
||||
<div class="text-xs bg-blue-500/20 text-blue-400 rounded px-1.5 py-0.5 truncate font-medium"
|
||||
title="${event.title} at ${event.venue}">
|
||||
${event.title}
|
||||
</div>
|
||||
`).join('')}
|
||||
${dayEvents.length > 2 ? `<div class="text-xs text-white/60 font-medium">+${dayEvents.length - 2} more</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Event List (shows when day is clicked) -->
|
||||
<div id="mobile-day-events" class="hidden sm:hidden mt-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl shadow-lg p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h4 id="selected-day-title" class="font-semibold text-white"></h4>
|
||||
<button onclick="hideMobileDayEvents()" class="p-1 hover:bg-white/10 rounded">
|
||||
<svg class="w-4 h-4 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="selected-day-events" class="space-y-2">
|
||||
<!-- Events will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Calendar navigation
|
||||
let currentCalendarYear = new Date().getFullYear();
|
||||
let currentCalendarMonth = new Date().getMonth();
|
||||
|
||||
window.navigateMonth = function(direction) {
|
||||
currentCalendarMonth += direction;
|
||||
if (currentCalendarMonth < 0) {
|
||||
currentCalendarMonth = 11;
|
||||
currentCalendarYear--;
|
||||
} else if (currentCalendarMonth > 11) {
|
||||
currentCalendarMonth = 0;
|
||||
currentCalendarYear++;
|
||||
}
|
||||
calendarContainer.innerHTML = createSimpleCalendar(allEvents, currentCalendarYear, currentCalendarMonth);
|
||||
};
|
||||
|
||||
// Show day events on mobile
|
||||
window.showDayEvents = function(year, month, day) {
|
||||
const dayEvents = allEvents.filter(event => {
|
||||
const eventDate = new Date(event.start_time);
|
||||
return eventDate.getDate() === day &&
|
||||
eventDate.getMonth() === month &&
|
||||
eventDate.getFullYear() === year;
|
||||
});
|
||||
|
||||
if (dayEvents.length === 0) return;
|
||||
|
||||
const monthNames = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
|
||||
const mobileDayEvents = document.getElementById('mobile-day-events');
|
||||
const selectedDayTitle = document.getElementById('selected-day-title');
|
||||
const selectedDayEventsContainer = document.getElementById('selected-day-events');
|
||||
|
||||
selectedDayTitle.textContent = `${monthNames[month]} ${day}, ${year}`;
|
||||
selectedDayEventsContainer.innerHTML = dayEvents.map(event => `
|
||||
<div class="flex items-start gap-3 p-3 bg-white/10 backdrop-blur-lg rounded-lg">
|
||||
<div class="w-2 h-2 bg-blue-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h5 class="font-medium text-white text-sm">${event.title}</h5>
|
||||
<p class="text-xs text-white/80 mt-1">${event.venue}</p>
|
||||
<p class="text-xs text-white/60 mt-1">${new Date(event.start_time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
mobileDayEvents.classList.remove('hidden');
|
||||
};
|
||||
|
||||
window.hideMobileDayEvents = function() {
|
||||
document.getElementById('mobile-day-events').classList.add('hidden');
|
||||
};
|
||||
|
||||
// Toggle between views
|
||||
function toggleView() {
|
||||
if (currentView === 'list') {
|
||||
currentView = 'calendar';
|
||||
viewText.textContent = 'List View';
|
||||
calendarView.classList.remove('hidden');
|
||||
listView.classList.add('hidden');
|
||||
renderCalendarView();
|
||||
} else {
|
||||
currentView = 'list';
|
||||
viewText.textContent = 'Calendar View';
|
||||
calendarView.classList.add('hidden');
|
||||
listView.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
toggleViewBtn.addEventListener('click', toggleView);
|
||||
|
||||
// Initialize
|
||||
checkAuth().then(session => {
|
||||
if (session) {
|
||||
loadEvents();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user