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:
2025-07-12 18:21:40 -06:00
parent a02d64a86c
commit 26a87d0d00
232 changed files with 33175 additions and 5365 deletions

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

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

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

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

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

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

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