feat: Complete platform enhancement with multi-tenant architecture
Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
323
src/pages/territory-manager/apply.astro
Normal file
323
src/pages/territory-manager/apply.astro
Normal file
@@ -0,0 +1,323 @@
|
||||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
---
|
||||
|
||||
<Layout title="Apply to be a Territory Manager - Black Canyon Tickets">
|
||||
<div class="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
|
||||
<!-- Hero Section -->
|
||||
<div class="relative overflow-hidden">
|
||||
<div class="absolute inset-0 bg-black/20"></div>
|
||||
<div class="relative container mx-auto px-4 py-24">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<h1 class="text-5xl md:text-6xl font-bold text-white mb-6 animate-fade-in">
|
||||
Get Paid to Help Events in Your Community
|
||||
</h1>
|
||||
<p class="text-xl md:text-2xl text-blue-100 mb-8 animate-fade-in-delay">
|
||||
Join our nationwide network of Territory Managers and earn commissions by connecting local events with our premium ticketing platform.
|
||||
</p>
|
||||
|
||||
<!-- Development Disclaimer -->
|
||||
<div class="mb-8 max-w-2xl mx-auto bg-orange-500/10 border border-orange-500/20 rounded-lg p-4">
|
||||
<div class="text-orange-300 text-sm">
|
||||
<strong>⚠️ Development Feature:</strong> This Territory Manager system is currently in development. Applications submitted may not be processed immediately.
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-6 justify-center items-center">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-3xl font-bold text-white">$0.10</div>
|
||||
<div class="text-blue-100">per ticket sold</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-3xl font-bold text-white">$75</div>
|
||||
<div class="text-blue-100">per onsite event</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-3xl font-bold text-white">Unlimited</div>
|
||||
<div class="text-blue-100">earning potential</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benefits Section -->
|
||||
<div class="container mx-auto px-4 py-16">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-bold text-white mb-4">Why Join Our Team?</h2>
|
||||
<p class="text-xl text-blue-100">Flexible work, competitive pay, and the chance to help your community</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-8 mb-16">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">💰</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Earnings Potential</h3>
|
||||
<p class="text-blue-100">Earn $0.10 per ticket sold plus $75 per onsite event. Top performers have potential to earn $800+ monthly.*</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">📅</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Flexible Schedule</h3>
|
||||
<p class="text-blue-100">Work on your own schedule. Perfect for students, retirees, or anyone seeking extra income.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">🎯</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Exclusive Territory</h3>
|
||||
<p class="text-blue-100">Get your own protected territory with no competition from other Territory Managers.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">🎓</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Full Training</h3>
|
||||
<p class="text-blue-100">Complete training program with marketing materials, sales scripts, and ongoing support.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">🏆</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Recognition & Rewards</h3>
|
||||
<p class="text-blue-100">Achievement badges, leaderboards, and bonus rewards for top performers.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<div class="text-4xl mb-4">🌟</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Community Impact</h3>
|
||||
<p class="text-blue-100">Help local events succeed by connecting them with professional ticketing solutions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Territory Map Section -->
|
||||
<div class="container mx-auto px-4 py-16">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-bold text-white mb-4">Available Territories</h2>
|
||||
<p class="text-xl text-blue-100">Choose your preferred territory from available regions</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20 mb-8">
|
||||
<div id="territory-map" class="h-96 bg-gray-800 rounded-lg flex items-center justify-center">
|
||||
<p class="text-white text-lg">Interactive territory map will be loaded here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Earnings Calculator -->
|
||||
<div class="container mx-auto px-4 py-16">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-bold text-white mb-4">Earnings Calculator</h2>
|
||||
<p class="text-xl text-blue-100">Estimate your potential monthly earnings*</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20 max-w-2xl mx-auto">
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Events referred per month</label>
|
||||
<input type="range" id="events-slider" min="1" max="20" value="5" class="w-full">
|
||||
<div class="flex justify-between text-sm text-blue-100 mt-1">
|
||||
<span>1</span>
|
||||
<span id="events-value">5</span>
|
||||
<span>20</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Average tickets per event</label>
|
||||
<input type="range" id="tickets-slider" min="10" max="500" value="100" class="w-full">
|
||||
<div class="flex justify-between text-sm text-blue-100 mt-1">
|
||||
<span>10</span>
|
||||
<span id="tickets-value">100</span>
|
||||
<span>500</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Onsite events per month</label>
|
||||
<input type="range" id="onsite-slider" min="0" max="10" value="2" class="w-full">
|
||||
<div class="flex justify-between text-sm text-blue-100 mt-1">
|
||||
<span>0</span>
|
||||
<span id="onsite-value">2</span>
|
||||
<span>10</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-white/20 pt-6">
|
||||
<div class="text-center">
|
||||
<div class="text-4xl font-bold text-white mb-2" id="total-earnings">$200</div>
|
||||
<div class="text-lg text-blue-100">Estimated monthly earnings potential</div>
|
||||
<div class="text-sm text-blue-200 mt-2" id="earnings-breakdown">
|
||||
$50 from ticket commissions + $150 from onsite events
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Earnings Disclaimer -->
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-4xl mx-auto mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-blue-100">
|
||||
<span class="text-sm">*Earnings are potential and may vary based on individual performance and market conditions.</span>
|
||||
</div>
|
||||
<button id="disclaimer-toggle" class="text-blue-400 hover:text-blue-300 text-sm underline">
|
||||
View Full Disclaimer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="disclaimer-content" class="hidden mt-4 pt-4 border-t border-white/20">
|
||||
<div class="text-blue-100 space-y-3 text-sm">
|
||||
<p><strong>Earnings are not guaranteed and results may vary.</strong> The earnings examples and calculator above represent potential earnings based on successful event referrals and ticket sales. Your actual earnings will depend on various factors including:</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-4">
|
||||
<li>Your ability to identify and successfully refer events in your territory</li>
|
||||
<li>The number and size of events you refer</li>
|
||||
<li>Event organizer adoption of our ticketing platform</li>
|
||||
<li>Ticket sales performance for referred events</li>
|
||||
<li>Market conditions and seasonal variations</li>
|
||||
<li>Your level of effort and marketing effectiveness</li>
|
||||
</ul>
|
||||
<p><strong>No income claims are being made.</strong> Some Territory Managers may earn more, some may earn less, and some may earn nothing at all. Success as a Territory Manager requires dedication, networking skills, and active participation in your local event community.</p>
|
||||
<p><strong>Independent contractor status:</strong> Territory Managers are independent contractors, not employees. You will be responsible for your own taxes, expenses, and business operations.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Call to Action -->
|
||||
<div class="container mx-auto px-4 py-16 text-center">
|
||||
<h2 class="text-4xl font-bold text-white mb-6">Ready to Get Started?</h2>
|
||||
<p class="text-xl text-blue-100 mb-8">Join Territory Managers who are building their event referral business with BCT</p>
|
||||
<a href="/territory-manager/apply/form" class="inline-block bg-gradient-to-r from-blue-500 to-purple-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:from-blue-600 hover:to-purple-700 transition-all duration-300 shadow-lg hover:shadow-xl">
|
||||
Apply Now - It's Free
|
||||
</a>
|
||||
<p class="text-sm text-blue-200 mt-4">Application takes 5 minutes • Background check required • Independent contractor opportunity</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Earnings calculator functionality
|
||||
const eventsSlider = document.getElementById('events-slider');
|
||||
const ticketsSlider = document.getElementById('tickets-slider');
|
||||
const onsiteSlider = document.getElementById('onsite-slider');
|
||||
const eventsValue = document.getElementById('events-value');
|
||||
const ticketsValue = document.getElementById('tickets-value');
|
||||
const onsiteValue = document.getElementById('onsite-value');
|
||||
const totalEarnings = document.getElementById('total-earnings');
|
||||
const earningsBreakdown = document.getElementById('earnings-breakdown');
|
||||
|
||||
function updateCalculator() {
|
||||
const events = parseInt(eventsSlider.value);
|
||||
const tickets = parseInt(ticketsSlider.value);
|
||||
const onsite = parseInt(onsiteSlider.value);
|
||||
|
||||
eventsValue.textContent = events;
|
||||
ticketsValue.textContent = tickets;
|
||||
onsiteValue.textContent = onsite;
|
||||
|
||||
const ticketCommission = events * tickets * 0.10;
|
||||
const onsiteCommission = onsite * 75;
|
||||
const total = ticketCommission + onsiteCommission;
|
||||
|
||||
totalEarnings.textContent = `$${total.toLocaleString()}`;
|
||||
earningsBreakdown.textContent = `$${ticketCommission.toLocaleString()} from ticket commissions + $${onsiteCommission.toLocaleString()} from onsite events (potential earnings*)`;
|
||||
}
|
||||
|
||||
eventsSlider.addEventListener('input', updateCalculator);
|
||||
ticketsSlider.addEventListener('input', updateCalculator);
|
||||
onsiteSlider.addEventListener('input', updateCalculator);
|
||||
|
||||
// Initialize calculator
|
||||
updateCalculator();
|
||||
|
||||
// Disclaimer toggle functionality
|
||||
const disclaimerToggle = document.getElementById('disclaimer-toggle');
|
||||
const disclaimerContent = document.getElementById('disclaimer-content');
|
||||
|
||||
disclaimerToggle.addEventListener('click', () => {
|
||||
disclaimerContent.classList.toggle('hidden');
|
||||
disclaimerToggle.textContent = disclaimerContent.classList.contains('hidden') ?
|
||||
'View Full Disclaimer' : 'Hide Disclaimer';
|
||||
});
|
||||
|
||||
// Animate elements on scroll
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animate-fade-in');
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
document.querySelectorAll('.bg-white\\/10').forEach(el => {
|
||||
observer.observe(el);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-delay {
|
||||
animation: fadeIn 0.8s ease-out 0.2s forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Custom slider styles */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-track {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: linear-gradient(to right, #3b82f6, #8b5cf6);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-track {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
background: linear-gradient(to right, #3b82f6, #8b5cf6);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</Layout>
|
||||
519
src/pages/territory-manager/apply/form.astro
Normal file
519
src/pages/territory-manager/apply/form.astro
Normal file
@@ -0,0 +1,519 @@
|
||||
---
|
||||
import Layout from '../../../layouts/Layout.astro';
|
||||
---
|
||||
|
||||
<Layout title="Territory Manager Application - Black Canyon Tickets">
|
||||
<div class="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-white mb-4">Territory Manager Application</h1>
|
||||
<p class="text-xl text-blue-100">Join our team and start building your event referral business</p>
|
||||
<div class="bg-yellow-500/20 border border-yellow-500/50 rounded-lg p-4 mt-6 max-w-2xl mx-auto">
|
||||
<p class="text-yellow-200 text-sm">
|
||||
<strong>Important:</strong> This is an independent contractor opportunity. Earnings are not guaranteed and depend on your ability to successfully refer events in your territory. Results may vary.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-white text-sm">Step <span id="current-step">1</span> of 4</span>
|
||||
<span class="text-white text-sm"><span id="progress-percent">25</span>% Complete</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-700 rounded-full h-2">
|
||||
<div id="progress-bar" class="bg-gradient-to-r from-blue-500 to-purple-600 h-2 rounded-full transition-all duration-300" style="width: 25%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Form -->
|
||||
<form id="application-form" class="space-y-8">
|
||||
<!-- Step 1: Personal Information -->
|
||||
<div id="step-1" class="form-step">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Personal Information</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Full Name *</label>
|
||||
<input type="text" id="full-name" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Email Address *</label>
|
||||
<input type="email" id="email" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Phone Number *</label>
|
||||
<input type="tel" id="phone" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Date of Birth *</label>
|
||||
<input type="date" id="date-of-birth" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="block text-white text-sm font-medium mb-2">Address *</label>
|
||||
<input type="text" id="street" placeholder="Street Address" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 mb-3">
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-3">
|
||||
<input type="text" id="city" placeholder="City" required class="px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<input type="text" id="state" placeholder="State" required class="px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<input type="text" id="zip" placeholder="ZIP Code" required class="px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Territory and Preferences -->
|
||||
<div id="step-2" class="form-step hidden">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Territory Preferences</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Desired Territory *</label>
|
||||
<select id="desired-territory" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">Select a territory</option>
|
||||
<option value="denver-metro">Denver Metro Area</option>
|
||||
<option value="colorado-springs">Colorado Springs</option>
|
||||
<option value="boulder-county">Boulder County</option>
|
||||
<option value="fort-collins">Fort Collins</option>
|
||||
<option value="grand-junction">Grand Junction</option>
|
||||
<option value="other">Other (please specify)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Do you have reliable transportation? *</label>
|
||||
<div class="flex space-x-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="transportation" value="yes" class="mr-2">
|
||||
<span class="text-white">Yes</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="transportation" value="no" class="mr-2">
|
||||
<span class="text-white">No</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Do you have experience with events or sales? *</label>
|
||||
<div class="flex space-x-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="experience" value="yes" class="mr-2">
|
||||
<span class="text-white">Yes</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="experience" value="no" class="mr-2">
|
||||
<span class="text-white">No</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Why do you want to be a Territory Manager? *</label>
|
||||
<textarea id="motivation" rows="4" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Tell us about your motivation and goals..."></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Availability</label>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Days Available</label>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="monday" class="mr-2">
|
||||
<span class="text-white">Monday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="tuesday" class="mr-2">
|
||||
<span class="text-white">Tuesday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="wednesday" class="mr-2">
|
||||
<span class="text-white">Wednesday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="thursday" class="mr-2">
|
||||
<span class="text-white">Thursday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="friday" class="mr-2">
|
||||
<span class="text-white">Friday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="saturday" class="mr-2">
|
||||
<span class="text-white">Saturday</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="days" value="sunday" class="mr-2">
|
||||
<span class="text-white">Sunday</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Hours per week</label>
|
||||
<input type="range" id="hours-per-week" min="5" max="40" value="20" class="w-full">
|
||||
<div class="flex justify-between text-sm text-blue-100 mt-1">
|
||||
<span>5</span>
|
||||
<span id="hours-value">20</span>
|
||||
<span>40</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Documents -->
|
||||
<div id="step-3" class="form-step hidden">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Required Documents</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Government-issued ID *</label>
|
||||
<div class="border-2 border-dashed border-white/20 rounded-lg p-6 text-center">
|
||||
<input type="file" id="id-upload" accept=".pdf,.jpg,.jpeg,.png" required class="hidden">
|
||||
<div id="id-upload-area" class="cursor-pointer">
|
||||
<div class="text-4xl mb-4">📄</div>
|
||||
<p class="text-white mb-2">Click to upload your ID</p>
|
||||
<p class="text-sm text-blue-100">PDF, JPG, or PNG (max 10MB)</p>
|
||||
</div>
|
||||
<div id="id-upload-success" class="hidden">
|
||||
<div class="text-4xl mb-4">✅</div>
|
||||
<p class="text-green-400">ID uploaded successfully</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Resume (Optional)</label>
|
||||
<div class="border-2 border-dashed border-white/20 rounded-lg p-6 text-center">
|
||||
<input type="file" id="resume-upload" accept=".pdf,.doc,.docx" class="hidden">
|
||||
<div id="resume-upload-area" class="cursor-pointer">
|
||||
<div class="text-4xl mb-4">📋</div>
|
||||
<p class="text-white mb-2">Click to upload your resume</p>
|
||||
<p class="text-sm text-blue-100">PDF, DOC, or DOCX (max 10MB)</p>
|
||||
</div>
|
||||
<div id="resume-upload-success" class="hidden">
|
||||
<div class="text-4xl mb-4">✅</div>
|
||||
<p class="text-green-400">Resume uploaded successfully</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-500/20 border border-yellow-500/50 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="text-yellow-400 mr-3">⚠️</div>
|
||||
<div>
|
||||
<p class="text-yellow-100 font-medium">Background Check Required</p>
|
||||
<p class="text-yellow-200 text-sm mt-1">A background check will be conducted after your application is approved. This is for security purposes and to protect our clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Consent and Submit -->
|
||||
<div id="step-4" class="form-step hidden">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Consent and Agreement</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-blue-500/20 border border-blue-500/50 rounded-lg p-6">
|
||||
<h3 class="text-lg font-medium text-white mb-3">What happens next?</h3>
|
||||
<div class="space-y-2 text-blue-100">
|
||||
<p>1. We'll review your application within 2-3 business days</p>
|
||||
<p>2. If approved, you'll receive an email with next steps</p>
|
||||
<p>3. Complete the background check process</p>
|
||||
<p>4. Access your Territory Manager portal and start training</p>
|
||||
<p>5. Begin earning commissions on your first referral</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<label class="flex items-start">
|
||||
<input type="checkbox" id="consent-background" required class="mr-3 mt-1">
|
||||
<span class="text-white">I consent to a background check being conducted as part of the application process. *</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input type="checkbox" id="consent-data" required class="mr-3 mt-1">
|
||||
<span class="text-white">I agree to the processing of my personal data as described in the <a href="/privacy" class="text-blue-400 hover:text-blue-300">Privacy Policy</a>. *</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input type="checkbox" id="consent-terms" required class="mr-3 mt-1">
|
||||
<span class="text-white">I agree to the <a href="/terms" class="text-blue-400 hover:text-blue-300">Terms of Service</a> and Territory Manager Agreement. *</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input type="checkbox" id="consent-communications" class="mr-3 mt-1">
|
||||
<span class="text-white">I would like to receive updates about new features and opportunities (optional).</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-500/20 border border-green-500/50 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="text-green-400 mr-3">✨</div>
|
||||
<div>
|
||||
<p class="text-green-100 font-medium">Ready to join our team?</p>
|
||||
<p class="text-green-200 text-sm mt-1">Submit your application and we'll get back to you within 2-3 business days.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
<div class="flex justify-between items-center">
|
||||
<button type="button" id="prev-btn" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-3 rounded-lg transition-colors duration-200 hidden">
|
||||
Previous
|
||||
</button>
|
||||
<button type="button" id="next-btn" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg transition-all duration-200 ml-auto">
|
||||
Next
|
||||
</button>
|
||||
<button type="submit" id="submit-btn" class="bg-gradient-to-r from-green-500 to-blue-600 hover:from-green-600 hover:to-blue-700 text-white px-8 py-3 rounded-lg transition-all duration-200 hidden">
|
||||
Submit Application
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Modal -->
|
||||
<div id="success-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center hidden z-50">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20 max-w-md mx-4">
|
||||
<div class="text-center">
|
||||
<div class="text-6xl mb-4">🎉</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-4">Application Submitted!</h2>
|
||||
<p class="text-blue-100 mb-6">Thank you for your interest in becoming a Territory Manager. We'll review your application and get back to you within 2-3 business days.</p>
|
||||
<button id="close-modal" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg transition-all duration-200">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentStep = 1;
|
||||
const totalSteps = 4;
|
||||
|
||||
// Elements
|
||||
const currentStepEl = document.getElementById('current-step');
|
||||
const progressPercentEl = document.getElementById('progress-percent');
|
||||
const progressBarEl = document.getElementById('progress-bar');
|
||||
const prevBtn = document.getElementById('prev-btn');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const form = document.getElementById('application-form');
|
||||
const successModal = document.getElementById('success-modal');
|
||||
const closeModal = document.getElementById('close-modal');
|
||||
|
||||
// File upload handling
|
||||
const idUpload = document.getElementById('id-upload');
|
||||
const idUploadArea = document.getElementById('id-upload-area');
|
||||
const idUploadSuccess = document.getElementById('id-upload-success');
|
||||
const resumeUpload = document.getElementById('resume-upload');
|
||||
const resumeUploadArea = document.getElementById('resume-upload-area');
|
||||
const resumeUploadSuccess = document.getElementById('resume-upload-success');
|
||||
|
||||
// Hours slider
|
||||
const hoursSlider = document.getElementById('hours-per-week');
|
||||
const hoursValue = document.getElementById('hours-value');
|
||||
|
||||
// Update progress
|
||||
function updateProgress() {
|
||||
const percent = (currentStep / totalSteps) * 100;
|
||||
currentStepEl.textContent = currentStep;
|
||||
progressPercentEl.textContent = Math.round(percent);
|
||||
progressBarEl.style.width = percent + '%';
|
||||
}
|
||||
|
||||
// Show/hide steps
|
||||
function showStep(step) {
|
||||
document.querySelectorAll('.form-step').forEach(el => el.classList.add('hidden'));
|
||||
document.getElementById(`step-${step}`).classList.remove('hidden');
|
||||
|
||||
prevBtn.classList.toggle('hidden', step === 1);
|
||||
nextBtn.classList.toggle('hidden', step === totalSteps);
|
||||
submitBtn.classList.toggle('hidden', step !== totalSteps);
|
||||
}
|
||||
|
||||
// Validate current step
|
||||
function validateStep(step) {
|
||||
const stepEl = document.getElementById(`step-${step}`);
|
||||
const requiredFields = stepEl.querySelectorAll('input[required], select[required], textarea[required]');
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!field.value.trim()) {
|
||||
field.focus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2 specific validation
|
||||
if (step === 2) {
|
||||
const transportation = document.querySelector('input[name="transportation"]:checked');
|
||||
const experience = document.querySelector('input[name="experience"]:checked');
|
||||
|
||||
if (!transportation || !experience) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3 specific validation
|
||||
if (step === 3) {
|
||||
if (!idUpload.files.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4 specific validation
|
||||
if (step === 4) {
|
||||
const requiredConsent = ['consent-background', 'consent-data', 'consent-terms'];
|
||||
for (const id of requiredConsent) {
|
||||
if (!document.getElementById(id).checked) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Next button handler
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (validateStep(currentStep)) {
|
||||
currentStep++;
|
||||
showStep(currentStep);
|
||||
updateProgress();
|
||||
}
|
||||
});
|
||||
|
||||
// Previous button handler
|
||||
prevBtn.addEventListener('click', () => {
|
||||
currentStep--;
|
||||
showStep(currentStep);
|
||||
updateProgress();
|
||||
});
|
||||
|
||||
// File upload handlers
|
||||
idUploadArea.addEventListener('click', () => idUpload.click());
|
||||
idUpload.addEventListener('change', (e) => {
|
||||
if (e.target.files.length) {
|
||||
idUploadArea.classList.add('hidden');
|
||||
idUploadSuccess.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
resumeUploadArea.addEventListener('click', () => resumeUpload.click());
|
||||
resumeUpload.addEventListener('change', (e) => {
|
||||
if (e.target.files.length) {
|
||||
resumeUploadArea.classList.add('hidden');
|
||||
resumeUploadSuccess.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Hours slider
|
||||
hoursSlider.addEventListener('input', (e) => {
|
||||
hoursValue.textContent = e.target.value;
|
||||
});
|
||||
|
||||
// Form submission
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateStep(currentStep)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Submitting...';
|
||||
|
||||
try {
|
||||
// Collect form data
|
||||
const formData = new FormData();
|
||||
|
||||
// Personal info
|
||||
formData.append('fullName', document.getElementById('full-name').value);
|
||||
formData.append('email', document.getElementById('email').value);
|
||||
formData.append('phone', document.getElementById('phone').value);
|
||||
formData.append('dateOfBirth', document.getElementById('date-of-birth').value);
|
||||
formData.append('street', document.getElementById('street').value);
|
||||
formData.append('city', document.getElementById('city').value);
|
||||
formData.append('state', document.getElementById('state').value);
|
||||
formData.append('zip', document.getElementById('zip').value);
|
||||
|
||||
// Territory preferences
|
||||
formData.append('desiredTerritory', document.getElementById('desired-territory').value);
|
||||
formData.append('hasTransportation', document.querySelector('input[name="transportation"]:checked').value);
|
||||
formData.append('hasExperience', document.querySelector('input[name="experience"]:checked').value);
|
||||
formData.append('motivation', document.getElementById('motivation').value);
|
||||
formData.append('hoursPerWeek', hoursSlider.value);
|
||||
|
||||
// Days available
|
||||
const selectedDays = Array.from(document.querySelectorAll('input[name="days"]:checked')).map(cb => cb.value);
|
||||
formData.append('daysAvailable', JSON.stringify(selectedDays));
|
||||
|
||||
// Documents
|
||||
if (idUpload.files.length) {
|
||||
formData.append('idUpload', idUpload.files[0]);
|
||||
}
|
||||
if (resumeUpload.files.length) {
|
||||
formData.append('resumeUpload', resumeUpload.files[0]);
|
||||
}
|
||||
|
||||
// Consent
|
||||
formData.append('consentBackground', document.getElementById('consent-background').checked);
|
||||
formData.append('consentData', document.getElementById('consent-data').checked);
|
||||
formData.append('consentTerms', document.getElementById('consent-terms').checked);
|
||||
formData.append('consentCommunications', document.getElementById('consent-communications').checked);
|
||||
|
||||
// Submit to API
|
||||
const response = await fetch('/api/territory-manager/apply', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
successModal.classList.remove('hidden');
|
||||
} else {
|
||||
throw new Error('Failed to submit application');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submission error:', error);
|
||||
alert('There was an error submitting your application. Please try again.');
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Submit Application';
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal handler
|
||||
closeModal.addEventListener('click', () => {
|
||||
successModal.classList.add('hidden');
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
// Initialize
|
||||
showStep(1);
|
||||
updateProgress();
|
||||
</script>
|
||||
</Layout>
|
||||
469
src/pages/territory-manager/dashboard.astro
Normal file
469
src/pages/territory-manager/dashboard.astro
Normal file
@@ -0,0 +1,469 @@
|
||||
---
|
||||
import SecureLayout from '../../layouts/SecureLayout.astro';
|
||||
import ProtectedRoute from '../../components/ProtectedRoute.astro';
|
||||
---
|
||||
|
||||
<ProtectedRoute>
|
||||
<SecureLayout title="Territory Manager Dashboard - Black Canyon Tickets">
|
||||
<div class="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
|
||||
<!-- Header -->
|
||||
<div class="bg-white/10 backdrop-blur-sm border-b border-white/20">
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white">Territory Manager Dashboard</h1>
|
||||
<p class="text-blue-100" id="welcome-message">Welcome back, Territory Manager!</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg px-4 py-2 border border-white/20">
|
||||
<div class="text-sm text-blue-100">Territory</div>
|
||||
<div class="text-white font-medium" id="territory-name">Loading...</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg px-4 py-2 border border-white/20">
|
||||
<div class="text-sm text-blue-100">Rank</div>
|
||||
<div class="text-white font-medium">#<span id="current-rank">-</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Development Disclaimer -->
|
||||
<div class="mb-8 max-w-4xl mx-auto bg-orange-500/10 border border-orange-500/20 rounded-lg p-4">
|
||||
<div class="text-orange-300 text-sm">
|
||||
<strong>⚠️ Development Feature:</strong> This Territory Manager system is currently in development and may not be fully functional. Some features may not work as expected.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white" id="total-earnings">$0</div>
|
||||
<div class="text-blue-100">Total Earnings</div>
|
||||
</div>
|
||||
<div class="text-4xl">💰</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-green-400" id="earnings-change">+$0 this month</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white" id="events-referred">0</div>
|
||||
<div class="text-blue-100">Events Referred</div>
|
||||
</div>
|
||||
<div class="text-4xl">🎫</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-blue-400" id="events-change">+0 this month</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white" id="success-rate">0%</div>
|
||||
<div class="text-blue-100">Success Rate</div>
|
||||
</div>
|
||||
<div class="text-4xl">📈</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-purple-400" id="rate-change">+0% this month</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-white" id="pending-payouts">0</div>
|
||||
<div class="text-blue-100">Pending Payouts</div>
|
||||
</div>
|
||||
<div class="text-4xl">⏳</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-yellow-400" id="payout-amount">$0 pending</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="grid md:grid-cols-3 gap-6 mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-4xl mb-4">📝</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">Submit a Lead</h3>
|
||||
<p class="text-blue-100 mb-4">Add a new event lead to your pipeline</p>
|
||||
<button onclick="window.location.href='/territory-manager/leads/new'" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg transition-all duration-200">
|
||||
Add Lead
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-4xl mb-4">🔗</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">Generate Referral Link</h3>
|
||||
<p class="text-blue-100 mb-4">Create a custom referral link for events</p>
|
||||
<button onclick="generateReferralLink()" class="bg-gradient-to-r from-green-500 to-blue-600 hover:from-green-600 hover:to-blue-700 text-white px-6 py-3 rounded-lg transition-all duration-200">
|
||||
Generate Link
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-4xl mb-4">📊</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">View Reports</h3>
|
||||
<p class="text-blue-100 mb-4">Check your earnings and performance</p>
|
||||
<button onclick="window.location.href='/territory-manager/reports'" class="bg-gradient-to-r from-purple-500 to-pink-600 hover:from-purple-600 hover:to-pink-700 text-white px-6 py-3 rounded-lg transition-all duration-200">
|
||||
View Reports
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<div class="grid lg:grid-cols-2 gap-8">
|
||||
<!-- Recent Activity -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<h3 class="text-xl font-bold text-white mb-4">Recent Activity</h3>
|
||||
<div class="space-y-4" id="recent-activity">
|
||||
<!-- Activity items will be loaded here -->
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="/territory-manager/activity" class="text-blue-400 hover:text-blue-300 text-sm">View All Activity</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Earnings Chart -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<h3 class="text-xl font-bold text-white mb-4">Earnings Overview</h3>
|
||||
<canvas id="earnings-chart" class="w-full h-64"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Active Leads -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white">Active Leads</h3>
|
||||
<a href="/territory-manager/leads" class="text-blue-400 hover:text-blue-300 text-sm">View All</a>
|
||||
</div>
|
||||
<div class="space-y-3" id="active-leads">
|
||||
<!-- Lead items will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Achievements -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<h3 class="text-xl font-bold text-white mb-4">Recent Achievements</h3>
|
||||
<div class="space-y-3" id="achievements">
|
||||
<!-- Achievement items will be loaded here -->
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="/territory-manager/achievements" class="text-blue-400 hover:text-blue-300 text-sm">View All Achievements</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="mt-8 bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white">Notifications</h3>
|
||||
<button onclick="markAllAsRead()" class="text-blue-400 hover:text-blue-300 text-sm">Mark All Read</button>
|
||||
</div>
|
||||
<div class="space-y-3" id="notifications">
|
||||
<!-- Notification items will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Referral Link Modal -->
|
||||
<div id="referral-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center hidden z-50">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20 max-w-md w-full mx-4">
|
||||
<h2 class="text-2xl font-bold text-white mb-4">Referral Link Generated</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Your Referral Link</label>
|
||||
<div class="flex">
|
||||
<input type="text" id="referral-link" readonly class="flex-1 px-4 py-2 bg-white/10 border border-white/20 rounded-l-lg text-white">
|
||||
<button onclick="copyReferralLink()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-r-lg transition-colors duration-200">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">QR Code</label>
|
||||
<div class="bg-white p-4 rounded-lg">
|
||||
<div id="qr-code" class="w-32 h-32 bg-gray-200 mx-auto flex items-center justify-center">
|
||||
QR Code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-6">
|
||||
<button onclick="closeReferralModal()" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors duration-200">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let territoryManagerData = null;
|
||||
let chartInstance = null;
|
||||
|
||||
// Load dashboard data using the centralized router
|
||||
async function loadDashboardData() {
|
||||
try {
|
||||
const { territoryManagerRouter } = await import('../../lib/territory-manager-router');
|
||||
const data = await territoryManagerRouter.loadDashboard();
|
||||
|
||||
if (data.error) {
|
||||
console.error('Dashboard error:', data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
territoryManagerData = data;
|
||||
updateDashboard(data);
|
||||
} catch (error) {
|
||||
console.error('Error loading dashboard data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update dashboard with data
|
||||
function updateDashboard(data) {
|
||||
// Update welcome message
|
||||
document.getElementById('welcome-message').textContent = `Welcome back, ${data.profile?.full_name || 'Territory Manager'}!`;
|
||||
|
||||
// Update territory name
|
||||
document.getElementById('territory-name').textContent = data.territory?.name || 'Unassigned';
|
||||
|
||||
// Update stats
|
||||
document.getElementById('total-earnings').textContent = `$${data.stats.total_commission.toLocaleString()}`;
|
||||
document.getElementById('events-referred').textContent = data.stats.total_events_referred;
|
||||
document.getElementById('success-rate').textContent = `${Math.round(data.stats.success_rate * 100)}%`;
|
||||
document.getElementById('pending-payouts').textContent = data.stats.pending_payouts;
|
||||
document.getElementById('current-rank').textContent = data.stats.rank;
|
||||
|
||||
// Update changes
|
||||
document.getElementById('earnings-change').textContent = `+$${data.stats.current_month_commission.toLocaleString()} this month`;
|
||||
document.getElementById('payout-amount').textContent = `$${data.stats.pending_payouts * 0.40} pending`;
|
||||
|
||||
// Load recent activity
|
||||
loadRecentActivity(data.recent_activity || []);
|
||||
|
||||
// Load active leads
|
||||
loadActiveLeads(data.active_leads || []);
|
||||
|
||||
// Load achievements
|
||||
loadAchievements(data.achievements || []);
|
||||
|
||||
// Load notifications
|
||||
loadNotifications(data.notifications || []);
|
||||
|
||||
// Update earnings chart
|
||||
updateEarningsChart(data.earnings_history || []);
|
||||
}
|
||||
|
||||
// Load recent activity
|
||||
function loadRecentActivity(activities) {
|
||||
const container = document.getElementById('recent-activity');
|
||||
container.innerHTML = activities.length ? activities.map(activity => `
|
||||
<div class="flex items-center space-x-3 p-3 bg-white/5 rounded-lg">
|
||||
<div class="text-2xl">${getActivityIcon(activity.type)}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">${activity.message}</div>
|
||||
<div class="text-blue-100 text-xs">${new Date(activity.created_at).toLocaleDateString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No recent activity</div>';
|
||||
}
|
||||
|
||||
// Load active leads
|
||||
function loadActiveLeads(leads) {
|
||||
const container = document.getElementById('active-leads');
|
||||
container.innerHTML = leads.length ? leads.map(lead => `
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div>
|
||||
<div class="text-white font-medium">${lead.event_name}</div>
|
||||
<div class="text-blue-100 text-sm">${lead.organizer_contact.name}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-sm px-2 py-1 rounded-full ${getStatusColor(lead.status)}">${lead.status}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No active leads</div>';
|
||||
}
|
||||
|
||||
// Load achievements
|
||||
function loadAchievements(achievements) {
|
||||
const container = document.getElementById('achievements');
|
||||
container.innerHTML = achievements.length ? achievements.map(achievement => `
|
||||
<div class="flex items-center space-x-3 p-3 bg-white/5 rounded-lg">
|
||||
<div class="text-2xl">🏆</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white font-medium">${achievement.name}</div>
|
||||
<div class="text-blue-100 text-sm">${achievement.description}</div>
|
||||
</div>
|
||||
<div class="text-green-400 text-sm">+$${achievement.reward_amount}</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No achievements yet</div>';
|
||||
}
|
||||
|
||||
// Load notifications
|
||||
function loadNotifications(notifications) {
|
||||
const container = document.getElementById('notifications');
|
||||
container.innerHTML = notifications.length ? notifications.map(notification => `
|
||||
<div class="flex items-center space-x-3 p-3 bg-white/5 rounded-lg ${notification.read ? 'opacity-60' : ''}">
|
||||
<div class="text-2xl">${getNotificationIcon(notification.type)}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white font-medium">${notification.title}</div>
|
||||
<div class="text-blue-100 text-sm">${notification.message}</div>
|
||||
</div>
|
||||
<div class="text-blue-200 text-xs">${new Date(notification.created_at).toLocaleDateString()}</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No notifications</div>';
|
||||
}
|
||||
|
||||
// Update earnings chart
|
||||
function updateEarningsChart(data) {
|
||||
const ctx = document.getElementById('earnings-chart').getContext('2d');
|
||||
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.map(d => new Date(d.date).toLocaleDateString()),
|
||||
datasets: [{
|
||||
label: 'Monthly Earnings',
|
||||
data: data.map(d => d.amount),
|
||||
borderColor: 'rgb(59, 130, 246)',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: 'white'
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: 'rgba(255, 255, 255, 0.7)'
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
callback: function(value) {
|
||||
return '$' + value.toLocaleString();
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate referral link using the centralized router
|
||||
async function generateReferralLink() {
|
||||
try {
|
||||
const { territoryManagerRouter } = await import('../../lib/territory-manager-router');
|
||||
const result = await territoryManagerRouter.generateReferralLink();
|
||||
|
||||
if (result.success && result.referralLink) {
|
||||
document.getElementById('referral-link').value = result.referralLink.url;
|
||||
document.getElementById('referral-modal').classList.remove('hidden');
|
||||
} else {
|
||||
console.error('Failed to generate referral link:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating referral link:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy referral link
|
||||
function copyReferralLink() {
|
||||
const linkInput = document.getElementById('referral-link');
|
||||
linkInput.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
// Show feedback
|
||||
const copyBtn = event.target;
|
||||
const originalText = copyBtn.textContent;
|
||||
copyBtn.textContent = 'Copied!';
|
||||
copyBtn.classList.add('bg-green-600');
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtn.textContent = originalText;
|
||||
copyBtn.classList.remove('bg-green-600');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Close referral modal
|
||||
function closeReferralModal() {
|
||||
document.getElementById('referral-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Mark all notifications as read using the centralized router
|
||||
async function markAllAsRead() {
|
||||
try {
|
||||
const { territoryManagerRouter } = await import('../../lib/territory-manager-router');
|
||||
const result = await territoryManagerRouter.markAllNotificationsAsRead();
|
||||
|
||||
if (result.success) {
|
||||
loadDashboardData();
|
||||
} else {
|
||||
console.error('Failed to mark notifications as read:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking notifications as read:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function getActivityIcon(type) {
|
||||
const icons = {
|
||||
'lead_created': '📝',
|
||||
'event_converted': '🎉',
|
||||
'payout_received': '💰',
|
||||
'achievement_earned': '🏆'
|
||||
};
|
||||
return icons[type] || '📋';
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
'cold': 'bg-gray-600 text-white',
|
||||
'contacted': 'bg-blue-600 text-white',
|
||||
'confirmed': 'bg-green-600 text-white',
|
||||
'converted': 'bg-purple-600 text-white',
|
||||
'dead': 'bg-red-600 text-white'
|
||||
};
|
||||
return colors[status] || 'bg-gray-600 text-white';
|
||||
}
|
||||
|
||||
function getNotificationIcon(type) {
|
||||
const icons = {
|
||||
'payout': '💰',
|
||||
'achievement': '🏆',
|
||||
'lead_update': '📝',
|
||||
'system': '🔔'
|
||||
};
|
||||
return icons[type] || '📋';
|
||||
}
|
||||
|
||||
// Initialize dashboard
|
||||
loadDashboardData();
|
||||
</script>
|
||||
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</SecureLayout>
|
||||
</ProtectedRoute>
|
||||
169
src/pages/territory-manager/leads/DEVELOPMENT_GUIDE.md
Normal file
169
src/pages/territory-manager/leads/DEVELOPMENT_GUIDE.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Territory Manager Lead Management System - Development Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide documents the Territory Manager Lead Management system scaffolding and provides guidance for completing the implementation.
|
||||
|
||||
## Current Status: SCAFFOLDED
|
||||
|
||||
The lead management system has been scaffolded with basic structure but is **not fully functional**. This was intentionally left as a skeleton to be completed at a future date.
|
||||
|
||||
## What's Been Implemented
|
||||
|
||||
### 1. Database Schema (COMPLETE)
|
||||
- **Location**: `/supabase/migrations/20250109_territory_manager_schema.sql`
|
||||
- **Table**: `leads`
|
||||
- **Fields**:
|
||||
- `id` (UUID, primary key)
|
||||
- `territory_manager_id` (UUID, references territory_managers)
|
||||
- `event_name` (VARCHAR(255))
|
||||
- `organizer_contact` (JSONB) - Contact information
|
||||
- `event_details` (JSONB) - Event details like date, type, etc.
|
||||
- `status` (VARCHAR(20)) - cold, contacted, confirmed, converted, dead
|
||||
- `notes` (TEXT[]) - Array of notes
|
||||
- `follow_up_date` (TIMESTAMP)
|
||||
- `created_at`, `updated_at` (TIMESTAMP)
|
||||
|
||||
### 2. Sample Data (COMPLETE)
|
||||
- **Location**: `/supabase/migrations/20250109_add_sample_territories.sql`
|
||||
- **Sample leads**: 4 leads with different statuses and realistic data
|
||||
- **Sample territory managers**: 3 TMs with assigned territories
|
||||
|
||||
### 3. UI Components (SCAFFOLDED)
|
||||
- **Location**: `/src/pages/territory-manager/leads/`
|
||||
- **Files**:
|
||||
- `index.astro` - Lead listing page with filters and stats
|
||||
- `new.astro` - New lead creation form
|
||||
- `DEVELOPMENT_GUIDE.md` - This file
|
||||
|
||||
### 4. API Router Integration (SCAFFOLDED)
|
||||
- **Location**: `/src/lib/territory-manager-router.ts`
|
||||
- **Methods**:
|
||||
- `loadLeads()` - Load leads for current territory manager
|
||||
- `createLead()` - Create new lead
|
||||
- `updateLead()` - Update existing lead
|
||||
- `deleteLead()` - Delete lead
|
||||
|
||||
## What Needs to be Completed
|
||||
|
||||
### 1. API Endpoints (MISSING)
|
||||
Create the following API endpoints:
|
||||
|
||||
```
|
||||
/src/pages/api/territory-manager/leads/
|
||||
├── index.ts # GET: List leads, POST: Create lead
|
||||
├── [id].ts # GET: Get lead, PUT: Update lead, DELETE: Delete lead
|
||||
└── stats.ts # GET: Lead statistics
|
||||
```
|
||||
|
||||
### 2. Frontend Functionality (PARTIALLY IMPLEMENTED)
|
||||
- **Lead Creation**: Form exists but doesn't submit to API
|
||||
- **Lead Editing**: No edit page exists
|
||||
- **Lead Viewing**: No detailed view modal/page
|
||||
- **Filters**: UI exists but filtering logic not implemented
|
||||
- **Search**: UI exists but search not implemented
|
||||
- **Stats**: Basic stats calculation implemented
|
||||
|
||||
### 3. Real-time Updates (MISSING)
|
||||
- Supabase real-time subscriptions for live updates
|
||||
- Auto-refresh when leads are updated
|
||||
|
||||
### 4. Lead Status Management (MISSING)
|
||||
- Status change workflow
|
||||
- Follow-up date automation
|
||||
- Note adding functionality
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### Database Structure
|
||||
```sql
|
||||
-- Lead organizer_contact JSONB structure
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"phone": "+1-555-0123",
|
||||
"title": "Event Coordinator",
|
||||
"organization": "ABC Events"
|
||||
}
|
||||
|
||||
-- Lead event_details JSONB structure
|
||||
{
|
||||
"event_type": "Wedding",
|
||||
"event_date": "2025-08-15",
|
||||
"venue": "Downtown Hotel",
|
||||
"expected_attendance": 150,
|
||||
"budget_range": "$5,000-$10,000",
|
||||
"description": "Elegant wedding reception"
|
||||
}
|
||||
```
|
||||
|
||||
### API Client Integration
|
||||
The Territory Manager API client (`territory-manager-api.ts`) needs these methods:
|
||||
- `getLeads(territoryManagerId: string): Promise<Lead[]>`
|
||||
- `createLead(data: LeadFormData): Promise<Lead>`
|
||||
- `updateLead(id: string, data: Partial<Lead>): Promise<Lead>`
|
||||
- `deleteLead(id: string): Promise<void>`
|
||||
|
||||
### Frontend State Management
|
||||
- Use Supabase real-time subscriptions for live updates
|
||||
- Implement optimistic UI updates
|
||||
- Add loading states and error handling
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Row Level Security (RLS)
|
||||
Already implemented in schema:
|
||||
- Territory managers can only see their own leads
|
||||
- Admin can see all leads
|
||||
- Proper filtering by territory_manager_id
|
||||
|
||||
### Validation
|
||||
- Client-side validation exists in forms
|
||||
- Server-side validation needed in API endpoints
|
||||
- Sanitize all JSONB inputs
|
||||
|
||||
## Testing Data
|
||||
|
||||
Sample leads are available in the database:
|
||||
- Wedding lead (confirmed status)
|
||||
- Music festival lead (contacted status)
|
||||
- Charity gala lead (cold status)
|
||||
- Corporate conference lead (converted status)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
When completing this system, consider:
|
||||
|
||||
1. **Lead Scoring**: Implement scoring based on event size, budget, etc.
|
||||
2. **Email Integration**: Send follow-up emails automatically
|
||||
3. **Calendar Integration**: Sync follow-up dates with calendar
|
||||
4. **Lead Import**: Bulk import leads from CSV/Excel
|
||||
5. **Analytics**: Advanced reporting and conversion tracking
|
||||
6. **Mobile Optimization**: Ensure mobile-friendly lead management
|
||||
|
||||
## Development Priority
|
||||
|
||||
1. **High Priority**: Complete API endpoints and basic CRUD operations
|
||||
2. **Medium Priority**: Add real-time updates and advanced filtering
|
||||
3. **Low Priority**: Add advanced features like lead scoring and analytics
|
||||
|
||||
## Files to Modify
|
||||
|
||||
When completing implementation:
|
||||
- `/src/lib/territory-manager-api.ts` - Add lead management methods
|
||||
- `/src/pages/api/territory-manager/leads/` - Create API endpoints
|
||||
- `/src/pages/territory-manager/leads/index.astro` - Complete functionality
|
||||
- `/src/pages/territory-manager/leads/new.astro` - Connect to API
|
||||
- Create `/src/pages/territory-manager/leads/[id]/edit.astro` - Edit page
|
||||
- Create `/src/pages/territory-manager/leads/[id]/view.astro` - View page
|
||||
|
||||
## Commission Integration
|
||||
|
||||
Remember to integrate with the commission system:
|
||||
- When a lead is converted, create a commission record
|
||||
- Track commission rate (currently $0.10 per ticket)
|
||||
- Link converted leads to actual events for payout calculation
|
||||
|
||||
---
|
||||
|
||||
**Note**: This is a scaffolded system. All UI components and database schema are ready, but the API layer and complete functionality need to be implemented.
|
||||
162
src/pages/territory-manager/leads/index.astro
Normal file
162
src/pages/territory-manager/leads/index.astro
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
import SecureLayout from '../../../layouts/SecureLayout.astro';
|
||||
---
|
||||
|
||||
<SecureLayout title="Lead Management - Black Canyon Tickets">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Lead Management</h1>
|
||||
<p class="text-blue-100">Track and manage your event leads</p>
|
||||
</div>
|
||||
<a href="/territory-manager/leads/new" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg font-semibold transition-all duration-300">
|
||||
+ New Lead
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Lead Stats -->
|
||||
<div class="grid md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-2xl font-bold text-white mb-1" id="total-leads">0</div>
|
||||
<div class="text-blue-100">Total Leads</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-2xl font-bold text-green-400 mb-1" id="converted-leads">0</div>
|
||||
<div class="text-blue-100">Converted</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-2xl font-bold text-yellow-400 mb-1" id="active-leads">0</div>
|
||||
<div class="text-blue-100">Active</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="text-2xl font-bold text-blue-400 mb-1" id="conversion-rate">0%</div>
|
||||
<div class="text-blue-100">Conversion Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lead Filters -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 mb-8">
|
||||
<div class="flex flex-wrap gap-4 items-center">
|
||||
<div>
|
||||
<label class="text-white text-sm font-medium mb-1 block">Status</label>
|
||||
<select id="status-filter" class="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="cold">Cold</option>
|
||||
<option value="contacted">Contacted</option>
|
||||
<option value="confirmed">Confirmed</option>
|
||||
<option value="converted">Converted</option>
|
||||
<option value="dead">Dead</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-white text-sm font-medium mb-1 block">Follow-up</label>
|
||||
<select id="followup-filter" class="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white">
|
||||
<option value="">All</option>
|
||||
<option value="overdue">Overdue</option>
|
||||
<option value="today">Due Today</option>
|
||||
<option value="week">This Week</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-white text-sm font-medium mb-1 block">Search</label>
|
||||
<input type="text" id="search-filter" placeholder="Search leads..." class="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-400">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lead Table -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-white/20">
|
||||
<th class="text-left text-white font-medium py-3">Event Name</th>
|
||||
<th class="text-left text-white font-medium py-3">Organizer</th>
|
||||
<th class="text-left text-white font-medium py-3">Status</th>
|
||||
<th class="text-left text-white font-medium py-3">Follow-up</th>
|
||||
<th class="text-left text-white font-medium py-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="leads-table-body">
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-blue-100 py-8">Loading leads...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// TODO: Implement full lead management functionality
|
||||
// This is a scaffold - complete implementation needed
|
||||
|
||||
async function loadLeads() {
|
||||
try {
|
||||
const { territoryManagerRouter } = await import('../../../lib/territory-manager-router');
|
||||
const data = await territoryManagerRouter.loadLeads();
|
||||
|
||||
// Update stats
|
||||
document.getElementById('total-leads').textContent = data.totalLeads || 0;
|
||||
document.getElementById('converted-leads').textContent = data.convertedLeads || 0;
|
||||
document.getElementById('active-leads').textContent = data.activeLeads || 0;
|
||||
document.getElementById('conversion-rate').textContent = (data.conversionRate || 0) + '%';
|
||||
|
||||
// Render leads table
|
||||
renderLeadsTable(data.leads || []);
|
||||
} catch (error) {
|
||||
console.error('Error loading leads:', error);
|
||||
document.getElementById('leads-table-body').innerHTML =
|
||||
'<tr><td colspan="5" class="text-center text-red-400 py-8">Error loading leads</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderLeadsTable(leads) {
|
||||
const tbody = document.getElementById('leads-table-body');
|
||||
|
||||
if (!leads.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-blue-100 py-8">No leads found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = leads.map(lead => `
|
||||
<tr class="border-b border-white/10">
|
||||
<td class="py-3 text-white">${lead.event_name}</td>
|
||||
<td class="py-3 text-blue-100">${lead.organizer_contact?.name || 'N/A'}</td>
|
||||
<td class="py-3">
|
||||
<span class="px-2 py-1 rounded-full text-xs ${getStatusColor(lead.status)}">${lead.status}</span>
|
||||
</td>
|
||||
<td class="py-3 text-blue-100">${lead.follow_up_date ? new Date(lead.follow_up_date).toLocaleDateString() : 'N/A'}</td>
|
||||
<td class="py-3">
|
||||
<button onclick="viewLead('${lead.id}')" class="text-blue-400 hover:text-blue-300 mr-2">View</button>
|
||||
<button onclick="editLead('${lead.id}')" class="text-yellow-400 hover:text-yellow-300">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
cold: 'bg-gray-600 text-white',
|
||||
contacted: 'bg-blue-600 text-white',
|
||||
confirmed: 'bg-yellow-600 text-white',
|
||||
converted: 'bg-green-600 text-white',
|
||||
dead: 'bg-red-600 text-white'
|
||||
};
|
||||
return colors[status] || 'bg-gray-600 text-white';
|
||||
}
|
||||
|
||||
function viewLead(leadId) {
|
||||
// TODO: Implement lead view modal
|
||||
alert('Lead view functionality to be implemented');
|
||||
}
|
||||
|
||||
function editLead(leadId) {
|
||||
// TODO: Implement lead edit functionality
|
||||
window.location.href = `/territory-manager/leads/${leadId}/edit`;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadLeads();
|
||||
</script>
|
||||
</SecureLayout>
|
||||
172
src/pages/territory-manager/leads/new.astro
Normal file
172
src/pages/territory-manager/leads/new.astro
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
import SecureLayout from '../../../layouts/SecureLayout.astro';
|
||||
---
|
||||
|
||||
<SecureLayout title="New Lead - Black Canyon Tickets">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Add New Lead</h1>
|
||||
<p class="text-blue-100">Submit a new event lead to track and manage</p>
|
||||
</div>
|
||||
|
||||
<form id="lead-form" class="space-y-8">
|
||||
<!-- Event Information -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-xl font-bold text-white mb-6">Event Information</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Event Name *</label>
|
||||
<input type="text" id="event-name" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Event Type</label>
|
||||
<select id="event-type" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">Select event type</option>
|
||||
<option value="wedding">Wedding</option>
|
||||
<option value="corporate">Corporate Event</option>
|
||||
<option value="festival">Festival</option>
|
||||
<option value="concert">Concert</option>
|
||||
<option value="gala">Gala</option>
|
||||
<option value="conference">Conference</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Event Date</label>
|
||||
<input type="date" id="event-date" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Expected Attendance</label>
|
||||
<input type="number" id="expected-attendance" placeholder="e.g., 150" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="block text-white text-sm font-medium mb-2">Venue</label>
|
||||
<input type="text" id="venue" placeholder="Event venue name" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="block text-white text-sm font-medium mb-2">Event Description</label>
|
||||
<textarea id="event-description" rows="3" placeholder="Brief description of the event..." class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Organizer Contact -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-xl font-bold text-white mb-6">Organizer Contact</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Full Name *</label>
|
||||
<input type="text" id="organizer-name" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Email *</label>
|
||||
<input type="email" id="organizer-email" required class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Phone</label>
|
||||
<input type="tel" id="organizer-phone" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Title/Position</label>
|
||||
<input type="text" id="organizer-title" placeholder="e.g., Event Coordinator" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="block text-white text-sm font-medium mb-2">Organization</label>
|
||||
<input type="text" id="organizer-organization" placeholder="Company or organization name" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lead Status -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20">
|
||||
<h2 class="text-xl font-bold text-white mb-6">Lead Status</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Current Status</label>
|
||||
<select id="lead-status" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="cold">Cold Lead</option>
|
||||
<option value="contacted">Contacted</option>
|
||||
<option value="confirmed">Confirmed Interest</option>
|
||||
<option value="converted">Converted</option>
|
||||
<option value="dead">Dead Lead</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white text-sm font-medium mb-2">Follow-up Date</label>
|
||||
<input type="date" id="follow-up-date" class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="block text-white text-sm font-medium mb-2">Notes</label>
|
||||
<textarea id="lead-notes" rows="4" placeholder="Add any relevant notes about this lead..." class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="flex justify-between">
|
||||
<a href="/territory-manager/leads" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-3 rounded-lg transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-8 py-3 rounded-lg font-semibold transition-all duration-300">
|
||||
Save Lead
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// TODO: Implement lead creation functionality
|
||||
// This is a scaffold - complete implementation needed
|
||||
|
||||
document.getElementById('lead-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {
|
||||
event_name: document.getElementById('event-name').value,
|
||||
event_type: document.getElementById('event-type').value,
|
||||
event_date: document.getElementById('event-date').value,
|
||||
expected_attendance: document.getElementById('expected-attendance').value,
|
||||
venue: document.getElementById('venue').value,
|
||||
event_description: document.getElementById('event-description').value,
|
||||
organizer_contact: {
|
||||
name: document.getElementById('organizer-name').value,
|
||||
email: document.getElementById('organizer-email').value,
|
||||
phone: document.getElementById('organizer-phone').value,
|
||||
title: document.getElementById('organizer-title').value,
|
||||
organization: document.getElementById('organizer-organization').value
|
||||
},
|
||||
status: document.getElementById('lead-status').value,
|
||||
follow_up_date: document.getElementById('follow-up-date').value,
|
||||
notes: document.getElementById('lead-notes').value
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO: Implement API call to create lead
|
||||
console.log('Creating lead:', formData);
|
||||
|
||||
// Placeholder success
|
||||
alert('Lead created successfully!');
|
||||
window.location.href = '/territory-manager/leads';
|
||||
} catch (error) {
|
||||
console.error('Error creating lead:', error);
|
||||
alert('Error creating lead. Please try again.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</SecureLayout>
|
||||
219
src/pages/territory-manager/territories.astro
Normal file
219
src/pages/territory-manager/territories.astro
Normal file
@@ -0,0 +1,219 @@
|
||||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
---
|
||||
|
||||
<Layout title="Territory Manager Territories - Black Canyon Tickets">
|
||||
<div class="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-white mb-4">Territory Manager Territories</h1>
|
||||
<p class="text-xl text-blue-100">Explore available territories and their current status</p>
|
||||
</div>
|
||||
|
||||
<!-- Territory Map -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-8 border border-white/20 mb-8">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Colorado Territory Map</h2>
|
||||
<div id="territory-map" class="w-full h-96 bg-gray-800 rounded-lg relative overflow-hidden">
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<div class="text-white text-lg">Loading territory map...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Territory List -->
|
||||
<div class="grid lg:grid-cols-2 gap-8">
|
||||
<!-- Available Territories -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<h3 class="text-xl font-bold text-white mb-4">Available Territories</h3>
|
||||
<div class="space-y-4" id="available-territories">
|
||||
<!-- Available territories will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assigned Territories -->
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20">
|
||||
<h3 class="text-xl font-bold text-white mb-4">Assigned Territories</h3>
|
||||
<div class="space-y-4" id="assigned-territories">
|
||||
<!-- Assigned territories will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Territory Stats -->
|
||||
<div class="grid md:grid-cols-4 gap-6 mt-8">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-3xl font-bold text-white" id="total-territories">0</div>
|
||||
<div class="text-blue-100">Total Territories</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-3xl font-bold text-green-400" id="assigned-count">0</div>
|
||||
<div class="text-blue-100">Assigned</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-3xl font-bold text-yellow-400" id="available-count">0</div>
|
||||
<div class="text-blue-100">Available</div>
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-lg p-6 border border-white/20 text-center">
|
||||
<div class="text-3xl font-bold text-purple-400" id="total-population">0</div>
|
||||
<div class="text-blue-100">Total Population</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="text-center mt-8">
|
||||
<a href="/territory-manager/apply" class="inline-block bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-all duration-300 shadow-lg hover:shadow-xl mr-4">
|
||||
Apply to Manage a Territory
|
||||
</a>
|
||||
<a href="/territory-manager/dashboard" class="inline-block bg-gradient-to-r from-green-500 to-blue-600 hover:from-green-600 hover:to-blue-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-all duration-300 shadow-lg hover:shadow-xl">
|
||||
Territory Manager Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let territories = [];
|
||||
|
||||
// Load territories
|
||||
async function loadTerritories() {
|
||||
try {
|
||||
const response = await fetch('/api/territory-manager/territories');
|
||||
const data = await response.json();
|
||||
territories = data.territories || [];
|
||||
|
||||
renderTerritories();
|
||||
renderMap();
|
||||
updateStats();
|
||||
} catch (error) {
|
||||
console.error('Error loading territories:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Render territories
|
||||
function renderTerritories() {
|
||||
const availableContainer = document.getElementById('available-territories');
|
||||
const assignedContainer = document.getElementById('assigned-territories');
|
||||
|
||||
const available = territories.filter(t => !t.assigned_to);
|
||||
const assigned = territories.filter(t => t.assigned_to);
|
||||
|
||||
availableContainer.innerHTML = available.length ? available.map(territory => `
|
||||
<div class="bg-white/5 rounded-lg p-4 border border-white/10">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="text-white font-medium">${territory.name}</h4>
|
||||
<p class="text-blue-100 text-sm">Population: ${territory.population?.toLocaleString() || 'N/A'}</p>
|
||||
<p class="text-blue-100 text-sm">Market Size: ${territory.market_size || 'N/A'}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="inline-block px-2 py-1 bg-green-600 text-white text-xs rounded-full">Available</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No available territories</div>';
|
||||
|
||||
assignedContainer.innerHTML = assigned.length ? assigned.map(territory => `
|
||||
<div class="bg-white/5 rounded-lg p-4 border border-white/10">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 class="text-white font-medium">${territory.name}</h4>
|
||||
<p class="text-blue-100 text-sm">Population: ${territory.population?.toLocaleString() || 'N/A'}</p>
|
||||
<p class="text-blue-100 text-sm">Market Size: ${territory.market_size || 'N/A'}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="inline-block px-2 py-1 bg-red-600 text-white text-xs rounded-full">Assigned</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') : '<div class="text-blue-100 text-center py-4">No assigned territories</div>';
|
||||
}
|
||||
|
||||
// Render interactive map
|
||||
function renderMap() {
|
||||
const mapContainer = document.getElementById('territory-map');
|
||||
|
||||
// Create a simple visual map representation
|
||||
const mapHtml = `
|
||||
<div class="relative w-full h-full bg-gradient-to-br from-green-800 to-green-900 rounded-lg overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-20">
|
||||
<svg viewBox="0 0 400 300" class="w-full h-full">
|
||||
<!-- Colorado outline -->
|
||||
<path d="M50 50 L350 50 L350 250 L50 250 Z" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="2"/>
|
||||
|
||||
<!-- Territory boundaries -->
|
||||
${territories.map((territory, index) => {
|
||||
const x = 60 + (index % 4) * 70;
|
||||
const y = 70 + Math.floor(index / 4) * 60;
|
||||
const color = territory.assigned_to ? '#ef4444' : '#22c55e';
|
||||
|
||||
return `
|
||||
<rect x="${x}" y="${y}" width="60" height="40"
|
||||
fill="${color}" opacity="0.6"
|
||||
stroke="white" stroke-width="1"
|
||||
class="territory-rect cursor-pointer hover:opacity-80"
|
||||
data-territory-id="${territory.id}"
|
||||
data-territory-name="${territory.name}"
|
||||
data-assigned="${territory.assigned_to ? 'true' : 'false'}">
|
||||
</rect>
|
||||
<text x="${x + 30}" y="${y + 25}"
|
||||
fill="white" text-anchor="middle"
|
||||
font-size="8" font-weight="bold">
|
||||
${territory.name.split(' ')[0]}
|
||||
</text>
|
||||
`;
|
||||
}).join('')}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Map Legend -->
|
||||
<div class="absolute bottom-4 left-4 bg-black/50 rounded-lg p-3">
|
||||
<div class="text-white text-sm font-medium mb-2">Legend</div>
|
||||
<div class="flex items-center mb-1">
|
||||
<div class="w-4 h-4 bg-green-500 rounded mr-2"></div>
|
||||
<span class="text-white text-xs">Available</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-4 h-4 bg-red-500 rounded mr-2"></div>
|
||||
<span class="text-white text-xs">Assigned</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Title -->
|
||||
<div class="absolute top-4 left-4 text-white">
|
||||
<h3 class="text-lg font-bold">Colorado Territory Map</h3>
|
||||
<p class="text-sm opacity-75">Click territories for details</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
mapContainer.innerHTML = mapHtml;
|
||||
|
||||
// Add click handlers for territories
|
||||
mapContainer.querySelectorAll('.territory-rect').forEach(rect => {
|
||||
rect.addEventListener('click', (e) => {
|
||||
const territoryId = e.target.getAttribute('data-territory-id');
|
||||
const territoryName = e.target.getAttribute('data-territory-name');
|
||||
const isAssigned = e.target.getAttribute('data-assigned') === 'true';
|
||||
|
||||
alert(`Territory: ${territoryName}\nStatus: ${isAssigned ? 'Assigned' : 'Available'}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update stats
|
||||
function updateStats() {
|
||||
const totalTerritories = territories.length;
|
||||
const assignedCount = territories.filter(t => t.assigned_to).length;
|
||||
const availableCount = totalTerritories - assignedCount;
|
||||
const totalPopulation = territories.reduce((sum, t) => sum + (t.population || 0), 0);
|
||||
|
||||
document.getElementById('total-territories').textContent = totalTerritories;
|
||||
document.getElementById('assigned-count').textContent = assignedCount;
|
||||
document.getElementById('available-count').textContent = availableCount;
|
||||
document.getElementById('total-population').textContent = totalPopulation.toLocaleString();
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadTerritories();
|
||||
</script>
|
||||
</Layout>
|
||||
Reference in New Issue
Block a user