product management, training, support, analytics, login
This commit is contained in:
parent
fcc9942ea1
commit
27807fc9d6
115
src/App.tsx
115
src/App.tsx
@ -1,10 +1,9 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { CookiesProvider } from 'react-cookie';
|
||||
import { store } from './store';
|
||||
import { setTheme } from './store/slices/themeSlice';
|
||||
import { useAppSelector } from './store/hooks';
|
||||
|
||||
// Channel Partner Components
|
||||
import Layout from './components/Layout/Layout';
|
||||
@ -14,6 +13,11 @@ import PartnershipsPage from './pages/Partnerships';
|
||||
import DealsPage from './pages/Deals';
|
||||
import CommissionsPage from './pages/Commissions';
|
||||
import ProductManagement from './pages/ProductManagement';
|
||||
import Training from './pages/Training';
|
||||
import Support from './pages/Support';
|
||||
import Analytics from './pages/Analytics';
|
||||
import Reports from './pages/Reports';
|
||||
import Settings from './pages/Settings';
|
||||
import Login from './pages/Login';
|
||||
import Signup from './pages/Signup';
|
||||
|
||||
@ -26,6 +30,7 @@ import ResellerDashboardInstances from './pages/reseller/Instances';
|
||||
import ResellerBilling from './pages/reseller/Billing';
|
||||
import ResellerSupport from './pages/reseller/Support';
|
||||
import ResellerReports from './pages/reseller/Reports';
|
||||
import ResellerTraining from './pages/reseller/Training';
|
||||
import ResellerLayout from './components/reseller/layout/ResellerLayout';
|
||||
import CookieConsent from './components/CookieConsent';
|
||||
import './index.css';
|
||||
@ -117,54 +122,54 @@ function App() {
|
||||
} />
|
||||
<Route path="/training" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Training" description="Manage reseller training programs and certifications" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/support" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Support" description="Handle support requests and tickets" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/analytics" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Analytics" description="Advanced analytics and insights" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/reports" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Reports" description="Generate and view detailed reports" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/targets" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Targets" description="Set and track performance targets" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/performance" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Performance" description="Performance metrics and KPIs" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/marketplace" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Marketplace" description="Browse and manage marketplace offerings" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/certifications" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Certifications" description="Manage certifications and badges" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/knowledge-base" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Knowledge Base" description="Access documentation and resources" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/settings" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Settings" description="Configure your account and preferences" />
|
||||
<Training />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/support" element={
|
||||
<Layout>
|
||||
<Support />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/analytics" element={
|
||||
<Layout>
|
||||
<Analytics />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/reports" element={
|
||||
<Layout>
|
||||
<Reports />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/settings" element={
|
||||
<Layout>
|
||||
<Settings />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/targets" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Targets" description="Set and track performance targets" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/performance" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Performance" description="Performance metrics and KPIs" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/marketplace" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Marketplace" description="Browse and manage marketplace offerings" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/certifications" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Certifications" description="Manage certifications and badges" />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/knowledge-base" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Knowledge Base" description="Access documentation and resources" />
|
||||
</Layout>
|
||||
} />
|
||||
|
||||
{/* Reseller Routes */}
|
||||
<Route path="/reseller/login" element={<ResellerLogin />} />
|
||||
@ -221,11 +226,11 @@ function App() {
|
||||
<PlaceholderPage title="Billing & Payments" description="Manage your billing, invoices, and payment methods" />
|
||||
</ResellerLayout>
|
||||
} />
|
||||
<Route path="/reseller-dashboard/support" element={
|
||||
<ResellerLayout>
|
||||
<PlaceholderPage title="Support Center" description="Get help and submit support tickets" />
|
||||
</ResellerLayout>
|
||||
} />
|
||||
<Route path="/reseller-dashboard/support" element={
|
||||
<ResellerLayout>
|
||||
<Support />
|
||||
</ResellerLayout>
|
||||
} />
|
||||
<Route path="/reseller-dashboard/reports" element={
|
||||
<ResellerLayout>
|
||||
<PlaceholderPage title="Reports & Analytics" description="View detailed reports and analytics" />
|
||||
@ -238,7 +243,7 @@ function App() {
|
||||
} />
|
||||
<Route path="/reseller-dashboard/training" element={
|
||||
<ResellerLayout>
|
||||
<PlaceholderPage title="Training Center" description="Access training materials, courses, and certifications" />
|
||||
<ResellerTraining />
|
||||
</ResellerLayout>
|
||||
} />
|
||||
<Route path="/reseller-dashboard/marketplace" element={
|
||||
@ -268,7 +273,7 @@ function App() {
|
||||
} />
|
||||
<Route path="/reseller/training" element={
|
||||
<Layout>
|
||||
<PlaceholderPage title="Training Center" description="Access training materials, courses, and certifications" />
|
||||
<ResellerTraining />
|
||||
</Layout>
|
||||
} />
|
||||
<Route path="/reseller/marketplace" element={
|
||||
|
||||
660
src/components/SupportAdmin.tsx
Normal file
660
src/components/SupportAdmin.tsx
Normal file
@ -0,0 +1,660 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Headphones,
|
||||
MessageCircle,
|
||||
FileText,
|
||||
Search,
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Settings,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
Star,
|
||||
Filter,
|
||||
Download,
|
||||
Send,
|
||||
User,
|
||||
Calendar,
|
||||
Tag,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Award,
|
||||
X,
|
||||
Save,
|
||||
Upload
|
||||
} from 'lucide-react';
|
||||
|
||||
interface SupportTicket {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
status: 'open' | 'in_progress' | 'resolved' | 'closed';
|
||||
priority: 'low' | 'medium' | 'high' | 'urgent';
|
||||
category: 'technical' | 'billing' | 'account' | 'general';
|
||||
submittedBy: string;
|
||||
assignedTo: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: TicketMessage[];
|
||||
}
|
||||
|
||||
interface TicketMessage {
|
||||
id: string;
|
||||
content: string;
|
||||
sender: string;
|
||||
timestamp: string;
|
||||
isInternal: boolean;
|
||||
}
|
||||
|
||||
interface FAQ {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
helpful: number;
|
||||
notHelpful: number;
|
||||
}
|
||||
|
||||
interface SupportResource {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'documentation' | 'video' | 'guide' | 'tutorial';
|
||||
url: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface SupportAdminProps {
|
||||
tickets: SupportTicket[];
|
||||
faqs: FAQ[];
|
||||
resources: SupportResource[];
|
||||
onTicketUpdate: (id: string, updates: Partial<SupportTicket>) => void;
|
||||
onTicketDelete: (id: string) => void;
|
||||
onFAQAdd: (faq: Omit<FAQ, 'id'>) => void;
|
||||
onFAQUpdate: (id: string, updates: Partial<FAQ>) => void;
|
||||
onFAQDelete: (id: string) => void;
|
||||
onResourceAdd: (resource: Omit<SupportResource, 'id'>) => void;
|
||||
onResourceUpdate: (id: string, updates: Partial<SupportResource>) => void;
|
||||
onResourceDelete: (id: string) => void;
|
||||
onExportReport: (type: 'pdf' | 'excel' | 'csv') => void;
|
||||
}
|
||||
|
||||
const SupportAdmin: React.FC<SupportAdminProps> = ({
|
||||
tickets,
|
||||
faqs,
|
||||
resources,
|
||||
onTicketUpdate,
|
||||
onTicketDelete,
|
||||
onFAQAdd,
|
||||
onFAQUpdate,
|
||||
onFAQDelete,
|
||||
onResourceAdd,
|
||||
onResourceUpdate,
|
||||
onResourceDelete,
|
||||
onExportReport
|
||||
}) => {
|
||||
const [currentView, setCurrentView] = useState<'tickets' | 'faqs' | 'resources' | 'analytics'>('tickets');
|
||||
const [selectedTicket, setSelectedTicket] = useState<SupportTicket | null>(null);
|
||||
const [editingFAQ, setEditingFAQ] = useState<FAQ | null>(null);
|
||||
const [editingResource, setEditingResource] = useState<SupportResource | null>(null);
|
||||
const [showAddFAQ, setShowAddFAQ] = useState(false);
|
||||
const [showAddResource, setShowAddResource] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||||
const [filterPriority, setFilterPriority] = useState<string>('all');
|
||||
const [filterCategory, setFilterCategory] = useState<string>('all');
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'open': return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
|
||||
case 'in_progress': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
||||
case 'resolved': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
||||
case 'closed': return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'urgent': return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
|
||||
case 'high': return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300';
|
||||
case 'medium': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
||||
case 'low': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTickets = tickets.filter(ticket => {
|
||||
const matchesSearch = ticket.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
ticket.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesStatus = filterStatus === 'all' || ticket.status === filterStatus;
|
||||
const matchesPriority = filterPriority === 'all' || ticket.priority === filterPriority;
|
||||
const matchesCategory = filterCategory === 'all' || ticket.category === filterCategory;
|
||||
|
||||
return matchesSearch && matchesStatus && matchesPriority && matchesCategory;
|
||||
});
|
||||
|
||||
const handleFAQSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const newFAQ: Omit<FAQ, 'id'> = {
|
||||
question: formData.get('question') as string,
|
||||
answer: formData.get('answer') as string,
|
||||
category: formData.get('category') as string,
|
||||
tags: (formData.get('tags') as string).split(',').map(tag => tag.trim()),
|
||||
helpful: 0,
|
||||
notHelpful: 0
|
||||
};
|
||||
onFAQAdd(newFAQ);
|
||||
setShowAddFAQ(false);
|
||||
(e.target as HTMLFormElement).reset();
|
||||
};
|
||||
|
||||
const handleResourceSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const newResource: Omit<SupportResource, 'id'> = {
|
||||
title: formData.get('title') as string,
|
||||
description: formData.get('description') as string,
|
||||
type: formData.get('type') as 'documentation' | 'video' | 'guide' | 'tutorial',
|
||||
url: formData.get('url') as string,
|
||||
category: formData.get('category') as string,
|
||||
tags: (formData.get('tags') as string).split(',').map(tag => tag.trim())
|
||||
};
|
||||
onResourceAdd(newResource);
|
||||
setShowAddResource(false);
|
||||
(e.target as HTMLFormElement).reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<Settings className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Support Administration</h2>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Manage tickets, FAQs, and resources</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
{/* View Toggle Buttons */}
|
||||
<div className="flex items-center space-x-2 bg-secondary-100 dark:bg-secondary-800 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setCurrentView('tickets')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'tickets'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Tickets
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('faqs')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'faqs'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
FAQs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('resources')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'resources'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Resources
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('analytics')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'analytics'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Analytics
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Based on Current View */}
|
||||
{currentView === 'tickets' && (
|
||||
<div className="space-y-6">
|
||||
{/* Search and Filters */}
|
||||
<div className="card p-6">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-secondary-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tickets..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<select
|
||||
value={filterStatus}
|
||||
onChange={(e) => setFilterStatus(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Status</option>
|
||||
<option value="open">Open</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
<select
|
||||
value={filterPriority}
|
||||
onChange={(e) => setFilterPriority(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Priority</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
<option value="high">High</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
<select
|
||||
value={filterCategory}
|
||||
onChange={(e) => setFilterCategory(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Categories</option>
|
||||
<option value="technical">Technical</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="account">Account</option>
|
||||
<option value="general">General</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tickets List */}
|
||||
<div className="space-y-4">
|
||||
{filteredTickets.map((ticket) => (
|
||||
<div key={ticket.id} className="card p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-2">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">{ticket.title}</h3>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(ticket.status)}`}>
|
||||
{ticket.status.replace('_', ' ')}
|
||||
</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(ticket.priority)}`}>
|
||||
{ticket.priority}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{ticket.description}</p>
|
||||
<div className="flex items-center space-x-6 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="w-4 h-4" />
|
||||
<span>{ticket.submittedBy}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>{new Date(ticket.createdAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
<span>{ticket.messages.length} messages</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setSelectedTicket(ticket)}
|
||||
className="p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
<button className="p-2 text-secondary-600 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onTicketDelete(ticket.id)}
|
||||
className="p-2 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'faqs' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Manage FAQs</h2>
|
||||
<button
|
||||
onClick={() => setShowAddFAQ(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add FAQ
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq) => (
|
||||
<div key={faq.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium text-secondary-900 dark:text-white mb-2">{faq.question}</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{faq.answer}</p>
|
||||
<div className="flex items-center space-x-4 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<span>Category: {faq.category}</span>
|
||||
<span>Helpful: {faq.helpful}</span>
|
||||
<span>Not Helpful: {faq.notHelpful}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setEditingFAQ(faq)}
|
||||
className="p-2 text-secondary-600 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onFAQDelete(faq.id)}
|
||||
className="p-2 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'resources' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Manage Resources</h2>
|
||||
<button
|
||||
onClick={() => setShowAddResource(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Resource
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{resources.map((resource) => (
|
||||
<div key={resource.id} className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-secondary-900 dark:text-white">{resource.title}</h3>
|
||||
<p className="text-sm text-secondary-500 dark:text-secondary-400">{resource.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">{resource.description}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{resource.tags.slice(0, 2).map((tag) => (
|
||||
<span key={tag} className="px-2 py-1 bg-secondary-100 dark:bg-secondary-800 text-secondary-600 dark:text-secondary-400 text-xs rounded">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setEditingResource(resource)}
|
||||
className="text-secondary-600 hover:text-secondary-700 dark:text-secondary-400 dark:hover:text-secondary-300"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onResourceDelete(resource.id)}
|
||||
className="text-danger-600 hover:text-danger-700 dark:text-danger-400 dark:hover:text-danger-300"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'analytics' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white mb-6">Support Analytics</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="bg-primary-50 dark:bg-primary-900 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-800 rounded-lg flex items-center justify-center">
|
||||
<MessageCircle className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Total Tickets</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{tickets.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-yellow-100 dark:bg-yellow-800 rounded-lg flex items-center justify-center">
|
||||
<Clock className="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Open Tickets</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
{tickets.filter(t => t.status === 'open').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-green-50 dark:bg-green-900 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-green-100 dark:bg-green-800 rounded-lg flex items-center justify-center">
|
||||
<CheckCircle className="w-5 h-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Resolved</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
{tickets.filter(t => t.status === 'resolved').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-blue-50 dark:bg-blue-900 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-800 rounded-lg flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">FAQs</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{faqs.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Quick Actions</h3>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={() => onExportReport('pdf')}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export PDF
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onExportReport('excel')}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<Plus className="w-5 h-5 text-primary-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Create New Ticket</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<FileText className="w-5 h-5 text-success-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Add FAQ</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<Upload className="w-5 h-5 text-warning-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Upload Resource</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add FAQ Modal */}
|
||||
{showAddFAQ && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Add New FAQ</h3>
|
||||
<form onSubmit={handleFAQSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Question</label>
|
||||
<input type="text" name="question" className="input w-full" placeholder="Enter question" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Answer</label>
|
||||
<textarea name="answer" className="input w-full" rows={4} placeholder="Enter answer" required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Category</label>
|
||||
<select name="category" className="input w-full" required>
|
||||
<option value="">Select category</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="technical">Technical</option>
|
||||
<option value="account">Account</option>
|
||||
<option value="general">General</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Tags (comma-separated)</label>
|
||||
<input type="text" name="tags" className="input w-full" placeholder="tag1, tag2, tag3" />
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddFAQ(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-success-600 text-white rounded-lg hover:bg-success-700 transition-colors"
|
||||
>
|
||||
Add FAQ
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Resource Modal */}
|
||||
{showAddResource && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Add New Resource</h3>
|
||||
<form onSubmit={handleResourceSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Title</label>
|
||||
<input type="text" name="title" className="input w-full" placeholder="Resource title" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Description</label>
|
||||
<textarea name="description" className="input w-full" rows={3} placeholder="Resource description" required></textarea>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Type</label>
|
||||
<select name="type" className="input w-full" required>
|
||||
<option value="">Select type</option>
|
||||
<option value="documentation">Documentation</option>
|
||||
<option value="video">Video</option>
|
||||
<option value="guide">Guide</option>
|
||||
<option value="tutorial">Tutorial</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Category</label>
|
||||
<select name="category" className="input w-full" required>
|
||||
<option value="">Select category</option>
|
||||
<option value="onboarding">Onboarding</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="technical">Technical</option>
|
||||
<option value="general">General</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">URL</label>
|
||||
<input type="url" name="url" className="input w-full" placeholder="https://..." required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Tags (comma-separated)</label>
|
||||
<input type="text" name="tags" className="input w-full" placeholder="tag1, tag2, tag3" />
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddResource(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-success-600 text-white rounded-lg hover:bg-success-700 transition-colors"
|
||||
>
|
||||
Add Resource
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportAdmin;
|
||||
463
src/components/TrainingAdmin.tsx
Normal file
463
src/components/TrainingAdmin.tsx
Normal file
@ -0,0 +1,463 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Save,
|
||||
X,
|
||||
Upload,
|
||||
BookOpen,
|
||||
Video,
|
||||
FileText,
|
||||
Settings
|
||||
} from 'lucide-react';
|
||||
|
||||
interface TrainingModule {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
duration: string;
|
||||
level: 'Beginner' | 'Intermediate' | 'Advanced';
|
||||
category: string;
|
||||
videos: TrainingVideo[];
|
||||
materials: TrainingMaterial[];
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
interface TrainingVideo {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
youtubeUrl: string;
|
||||
duration: string;
|
||||
thumbnail: string;
|
||||
}
|
||||
|
||||
interface TrainingMaterial {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'PDF' | 'PPT' | 'DOC' | 'VIDEO';
|
||||
downloadUrl: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
interface TrainingAdminProps {
|
||||
modules: TrainingModule[];
|
||||
onModuleAdd: (module: Omit<TrainingModule, 'id'>) => void;
|
||||
onModuleUpdate: (id: string, module: Partial<TrainingModule>) => void;
|
||||
onModuleDelete: (id: string) => void;
|
||||
onVideoAdd: (moduleId: string, video: Omit<TrainingVideo, 'id'>) => void;
|
||||
onVideoUpdate: (moduleId: string, videoId: string, video: Partial<TrainingVideo>) => void;
|
||||
onVideoDelete: (moduleId: string, videoId: string) => void;
|
||||
onMaterialAdd: (moduleId: string, material: Omit<TrainingMaterial, 'id'>) => void;
|
||||
onMaterialUpdate: (moduleId: string, materialId: string, material: Partial<TrainingMaterial>) => void;
|
||||
onMaterialDelete: (moduleId: string, materialId: string) => void;
|
||||
}
|
||||
|
||||
const TrainingAdmin: React.FC<TrainingAdminProps> = ({
|
||||
modules,
|
||||
onModuleAdd,
|
||||
onModuleUpdate,
|
||||
onModuleDelete,
|
||||
onVideoAdd,
|
||||
onVideoUpdate,
|
||||
onVideoDelete,
|
||||
onMaterialAdd,
|
||||
onMaterialUpdate,
|
||||
onMaterialDelete
|
||||
}) => {
|
||||
const [showAddModule, setShowAddModule] = useState(false);
|
||||
const [showAddVideo, setShowAddVideo] = useState(false);
|
||||
const [showAddMaterial, setShowAddMaterial] = useState(false);
|
||||
const [editingModule, setEditingModule] = useState<TrainingModule | null>(null);
|
||||
const [editingVideo, setEditingVideo] = useState<{ moduleId: string; video: TrainingVideo } | null>(null);
|
||||
const [editingMaterial, setEditingMaterial] = useState<{ moduleId: string; material: TrainingMaterial } | null>(null);
|
||||
const [selectedModuleForAdd, setSelectedModuleForAdd] = useState<string>('');
|
||||
|
||||
const categories = ['Platform Basics', 'Sales & Marketing', 'Technical', 'Business'];
|
||||
const levels = ['Beginner', 'Intermediate', 'Advanced'];
|
||||
const materialTypes = ['PDF', 'PPT', 'DOC', 'VIDEO'];
|
||||
|
||||
const handleModuleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
const newModule: Omit<TrainingModule, 'id'> = {
|
||||
title: formData.get('title') as string,
|
||||
description: formData.get('description') as string,
|
||||
duration: formData.get('duration') as string,
|
||||
level: formData.get('level') as 'Beginner' | 'Intermediate' | 'Advanced',
|
||||
category: formData.get('category') as string,
|
||||
videos: [],
|
||||
materials: [],
|
||||
completed: false
|
||||
};
|
||||
|
||||
onModuleAdd(newModule);
|
||||
setShowAddModule(false);
|
||||
(e.target as HTMLFormElement).reset();
|
||||
};
|
||||
|
||||
const handleVideoSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
const newVideo: Omit<TrainingVideo, 'id'> = {
|
||||
title: formData.get('title') as string,
|
||||
description: formData.get('description') as string,
|
||||
youtubeUrl: formData.get('youtubeUrl') as string,
|
||||
duration: formData.get('duration') as string,
|
||||
thumbnail: `https://img.youtube.com/vi/${formData.get('youtubeUrl')?.toString().split('v=')[1]}/maxresdefault.jpg`
|
||||
};
|
||||
|
||||
onVideoAdd(selectedModuleForAdd, newVideo);
|
||||
setShowAddVideo(false);
|
||||
(e.target as HTMLFormElement).reset();
|
||||
};
|
||||
|
||||
const handleMaterialSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
const newMaterial: Omit<TrainingMaterial, 'id'> = {
|
||||
title: formData.get('title') as string,
|
||||
description: formData.get('description') as string,
|
||||
type: formData.get('type') as 'PDF' | 'PPT' | 'DOC' | 'VIDEO',
|
||||
downloadUrl: '#',
|
||||
size: formData.get('size') as string
|
||||
};
|
||||
|
||||
onMaterialAdd(selectedModuleForAdd, newMaterial);
|
||||
setShowAddMaterial(false);
|
||||
(e.target as HTMLFormElement).reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Admin Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<Settings className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Training Management</h2>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Manage training modules, videos, and materials</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddModule(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Module
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modules List */}
|
||||
<div className="space-y-4">
|
||||
{modules.map((module) => (
|
||||
<div key={module.id} className="card p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<BookOpen className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">{module.title}</h3>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">{module.category} • {module.level} • {module.duration}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setEditingModule(module)}
|
||||
className="p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded-lg transition-colors"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onModuleDelete(module.id)}
|
||||
className="p-2 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">{module.description}</p>
|
||||
|
||||
{/* Videos Section */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-md font-medium text-secondary-900 dark:text-white flex items-center">
|
||||
<Video className="w-4 h-4 mr-2" />
|
||||
Videos ({module.videos.length})
|
||||
</h4>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedModuleForAdd(module.id);
|
||||
setShowAddVideo(true);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-1 bg-success-600 text-white text-xs rounded-md hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
Add Video
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{module.videos.map((video) => (
|
||||
<div key={video.id} className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h5 className="font-medium text-secondary-900 dark:text-white text-sm">{video.title}</h5>
|
||||
<p className="text-xs text-secondary-600 dark:text-secondary-400">{video.duration}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<button
|
||||
onClick={() => setEditingVideo({ moduleId: module.id, video })}
|
||||
className="p-1 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded transition-colors"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onVideoDelete(module.id, video.id)}
|
||||
className="p-1 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded transition-colors"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Materials Section */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-md font-medium text-secondary-900 dark:text-white flex items-center">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Materials ({module.materials.length})
|
||||
</h4>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedModuleForAdd(module.id);
|
||||
setShowAddMaterial(true);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-1 bg-success-600 text-white text-xs rounded-md hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
Add Material
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{module.materials.map((material) => (
|
||||
<div key={material.id} className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h5 className="font-medium text-secondary-900 dark:text-white text-sm">{material.title}</h5>
|
||||
<p className="text-xs text-secondary-600 dark:text-secondary-400">{material.type} • {material.size}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<button
|
||||
onClick={() => setEditingMaterial({ moduleId: module.id, material })}
|
||||
className="p-1 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded transition-colors"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onMaterialDelete(module.id, material.id)}
|
||||
className="p-1 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded transition-colors"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Module Modal */}
|
||||
{showAddModule && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Add New Training Module</h3>
|
||||
<button
|
||||
onClick={() => setShowAddModule(false)}
|
||||
className="p-1 text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-300"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={handleModuleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Title</label>
|
||||
<input name="title" type="text" className="input w-full" placeholder="Module title" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Description</label>
|
||||
<textarea name="description" className="input w-full" rows={3} placeholder="Module description" required></textarea>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Duration</label>
|
||||
<input name="duration" type="text" className="input w-full" placeholder="e.g., 2 hours" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Level</label>
|
||||
<select name="level" className="input w-full" required>
|
||||
{levels.map(level => (
|
||||
<option key={level} value={level}>{level}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Category</label>
|
||||
<select name="category" className="input w-full" required>
|
||||
{categories.map(category => (
|
||||
<option key={category} value={category}>{category}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddModule(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Add Module
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Video Modal */}
|
||||
{showAddVideo && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Add New Video</h3>
|
||||
<button
|
||||
onClick={() => setShowAddVideo(false)}
|
||||
className="p-1 text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-300"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={handleVideoSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Title</label>
|
||||
<input name="title" type="text" className="input w-full" placeholder="Video title" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Description</label>
|
||||
<textarea name="description" className="input w-full" rows={3} placeholder="Video description" required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">YouTube URL</label>
|
||||
<input name="youtubeUrl" type="url" className="input w-full" placeholder="https://www.youtube.com/watch?v=..." required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Duration</label>
|
||||
<input name="duration" type="text" className="input w-full" placeholder="e.g., 15:30" required />
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddVideo(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Add Video
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Material Modal */}
|
||||
{showAddMaterial && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Add New Material</h3>
|
||||
<button
|
||||
onClick={() => setShowAddMaterial(false)}
|
||||
className="p-1 text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-300"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={handleMaterialSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Title</label>
|
||||
<input name="title" type="text" className="input w-full" placeholder="Material title" required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Description</label>
|
||||
<textarea name="description" className="input w-full" rows={3} placeholder="Material description" required></textarea>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Type</label>
|
||||
<select name="type" className="input w-full" required>
|
||||
{materialTypes.map(type => (
|
||||
<option key={type} value={type}>{type}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Size</label>
|
||||
<input name="size" type="text" className="input w-full" placeholder="e.g., 2.5 MB" required />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">File Upload</label>
|
||||
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-4 text-center">
|
||||
<Upload className="w-8 h-8 text-gray-400 mx-auto mb-2" />
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">Click to upload or drag and drop</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddMaterial(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Add Material
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrainingAdmin;
|
||||
386
src/components/TrainingAnalytics.tsx
Normal file
386
src/components/TrainingAnalytics.tsx
Normal file
@ -0,0 +1,386 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
TrendingUp,
|
||||
Users,
|
||||
BookOpen,
|
||||
Video,
|
||||
FileText,
|
||||
Award,
|
||||
Clock,
|
||||
BarChart3,
|
||||
Download,
|
||||
Calendar,
|
||||
Filter,
|
||||
Eye,
|
||||
EyeOff,
|
||||
RefreshCw,
|
||||
Target,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
AlertCircle,
|
||||
Play
|
||||
} from 'lucide-react';
|
||||
|
||||
interface TrainingAnalytics {
|
||||
totalModules: number;
|
||||
totalVideos: number;
|
||||
totalMaterials: number;
|
||||
totalResellers: number;
|
||||
activeResellers: number;
|
||||
completedModules: number;
|
||||
averageCompletionTime: string;
|
||||
popularCategories: Array<{ name: string; count: number; percentage: number }>;
|
||||
completionRates: Array<{ month: string; rate: number }>;
|
||||
topPerformingModules: Array<{ name: string; completionRate: number; avgTime: string }>;
|
||||
resellerProgress: Array<{ name: string; completed: number; total: number; percentage: number }>;
|
||||
recentActivity: Array<{ type: string; description: string; timestamp: string; user: string }>;
|
||||
}
|
||||
|
||||
interface TrainingAnalyticsProps {
|
||||
analytics: TrainingAnalytics;
|
||||
onExportReport: (type: 'pdf' | 'excel' | 'csv') => void;
|
||||
onDateRangeChange: (startDate: string, endDate: string) => void;
|
||||
}
|
||||
|
||||
const TrainingAnalytics: React.FC<TrainingAnalyticsProps> = ({
|
||||
analytics,
|
||||
onExportReport,
|
||||
onDateRangeChange
|
||||
}) => {
|
||||
const [dateRange, setDateRange] = useState('30d');
|
||||
const [showDetailedMetrics, setShowDetailedMetrics] = useState(false);
|
||||
|
||||
const getCompletionRateColor = (rate: number) => {
|
||||
if (rate >= 80) return 'text-success-600 dark:text-success-400';
|
||||
if (rate >= 60) return 'text-warning-600 dark:text-warning-400';
|
||||
return 'text-danger-600 dark:text-danger-400';
|
||||
};
|
||||
|
||||
const getProgressColor = (percentage: number) => {
|
||||
if (percentage >= 80) return 'bg-success-500';
|
||||
if (percentage >= 60) return 'bg-warning-500';
|
||||
return 'bg-danger-500';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Training Analytics</h2>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Comprehensive insights into training performance</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<select
|
||||
value={dateRange}
|
||||
onChange={(e) => setDateRange(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="7d">Last 7 days</option>
|
||||
<option value="30d">Last 30 days</option>
|
||||
<option value="90d">Last 90 days</option>
|
||||
<option value="1y">Last year</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={() => setShowDetailedMetrics(!showDetailedMetrics)}
|
||||
className="inline-flex items-center px-3 py-2 bg-secondary-100 dark:bg-secondary-800 text-secondary-700 dark:text-secondary-300 rounded-lg text-sm font-medium hover:bg-secondary-200 dark:hover:bg-secondary-700 transition-colors"
|
||||
>
|
||||
{showDetailedMetrics ? <EyeOff className="w-4 h-4 mr-2" /> : <Eye className="w-4 h-4 mr-2" />}
|
||||
{showDetailedMetrics ? 'Hide Details' : 'Show Details'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Key Metrics */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Total Modules</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{analytics.totalModules}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<BookOpen className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+12% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Active Resellers</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{analytics.activeResellers}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-success-100 dark:bg-success-900 rounded-lg">
|
||||
<Users className="w-6 h-6 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+8% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Completion Rate</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
{Math.round((analytics.completedModules / analytics.totalModules) * 100)}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-warning-100 dark:bg-warning-900 rounded-lg">
|
||||
<Target className="w-6 h-6 text-warning-600 dark:text-warning-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<Clock className="w-4 h-4 text-secondary-600 mr-1" />
|
||||
<span className="text-sm text-secondary-600">Avg: {analytics.averageCompletionTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Total Content</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
{analytics.totalVideos + analytics.totalMaterials}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-danger-100 dark:bg-danger-900 rounded-lg">
|
||||
<FileText className="w-6 h-6 text-danger-600 dark:text-danger-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<Video className="w-4 h-4 text-primary-600 mr-1" />
|
||||
<span className="text-sm text-primary-600">{analytics.totalVideos} videos</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Detailed Metrics */}
|
||||
{showDetailedMetrics && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Popular Categories */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Popular Categories</h3>
|
||||
<div className="space-y-4">
|
||||
{analytics.popularCategories.map((category, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-3 h-3 rounded-full bg-primary-500"></div>
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white">{category.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary-600 h-2 rounded-full"
|
||||
style={{ width: `${category.percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-sm text-secondary-600 dark:text-secondary-400 w-12 text-right">
|
||||
{category.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Completion Trends */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Completion Trends</h3>
|
||||
<div className="space-y-4">
|
||||
{analytics.completionRates.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white">{item.month}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getCompletionRateColor(item.rate).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${item.rate}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className={`text-sm w-12 text-right ${getCompletionRateColor(item.rate)}`}>
|
||||
{item.rate}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Top Performing Modules */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Top Performing Modules</h3>
|
||||
<button className="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Module</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Completion Rate</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Avg Time</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{analytics.topPerformingModules.map((module, index) => (
|
||||
<tr key={index} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
|
||||
<BookOpen className="w-4 h-4 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white">{module.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-16 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getCompletionRateColor(module.completionRate).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${module.completionRate}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className={`text-sm ${getCompletionRateColor(module.completionRate)}`}>
|
||||
{module.completionRate}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-secondary-600 dark:text-secondary-400">
|
||||
{module.avgTime}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
||||
module.completionRate >= 80
|
||||
? 'bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-300'
|
||||
: module.completionRate >= 60
|
||||
? 'bg-warning-100 text-warning-800 dark:bg-warning-900 dark:text-warning-300'
|
||||
: 'bg-danger-100 text-danger-800 dark:bg-danger-900 dark:text-danger-300'
|
||||
}`}>
|
||||
{module.completionRate >= 80 ? 'Excellent' : module.completionRate >= 60 ? 'Good' : 'Needs Attention'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reseller Progress */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Reseller Progress</h3>
|
||||
<button className="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{analytics.resellerProgress.map((reseller, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
|
||||
<Users className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-900 dark:text-white">{reseller.name}</p>
|
||||
<p className="text-xs text-secondary-600 dark:text-secondary-400">
|
||||
{reseller.completed} of {reseller.total} modules completed
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getProgressColor(reseller.percentage)}`}
|
||||
style={{ width: `${reseller.percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white w-12 text-right">
|
||||
{reseller.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Recent Activity</h3>
|
||||
<div className="space-y-4">
|
||||
{analytics.recentActivity.map((activity, index) => (
|
||||
<div key={index} className="flex items-start space-x-3">
|
||||
<div className="w-8 h-8 bg-secondary-100 dark:bg-secondary-800 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
{activity.type === 'completion' && <CheckCircle className="w-4 h-4 text-success-600" />}
|
||||
{activity.type === 'started' && <Play className="w-4 h-4 text-primary-600" />}
|
||||
{activity.type === 'error' && <XCircle className="w-4 h-4 text-danger-600" />}
|
||||
{activity.type === 'warning' && <AlertCircle className="w-4 h-4 text-warning-600" />}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-secondary-900 dark:text-white">{activity.description}</p>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<span className="text-xs text-secondary-600 dark:text-secondary-400">{activity.user}</span>
|
||||
<span className="text-xs text-secondary-500">•</span>
|
||||
<span className="text-xs text-secondary-600 dark:text-secondary-400">{activity.timestamp}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Export Options */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Export Reports</h3>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Download analytics data in various formats</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={() => onExportReport('pdf')}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export PDF
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onExportReport('excel')}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export Excel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onExportReport('csv')}
|
||||
className="inline-flex items-center px-4 py-2 bg-warning-600 text-white rounded-lg text-sm font-medium hover:bg-warning-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrainingAnalytics;
|
||||
359
src/pages/Analytics.tsx
Normal file
359
src/pages/Analytics.tsx
Normal file
@ -0,0 +1,359 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Users,
|
||||
DollarSign,
|
||||
ShoppingCart,
|
||||
Award,
|
||||
Target,
|
||||
Calendar,
|
||||
Filter,
|
||||
Download,
|
||||
Eye,
|
||||
EyeOff,
|
||||
RefreshCw,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
Activity,
|
||||
PieChart,
|
||||
LineChart,
|
||||
BarChart,
|
||||
AreaChart
|
||||
} from 'lucide-react';
|
||||
|
||||
const Analytics: React.FC = () => {
|
||||
const [dateRange, setDateRange] = useState('30d');
|
||||
const [showDetailedMetrics, setShowDetailedMetrics] = useState(false);
|
||||
|
||||
// Mock data
|
||||
const analyticsData = {
|
||||
totalRevenue: 1250000,
|
||||
totalResellers: 156,
|
||||
activeResellers: 142,
|
||||
totalDeals: 89,
|
||||
conversionRate: 23.5,
|
||||
averageDealSize: 14000,
|
||||
monthlyGrowth: 12.8,
|
||||
topPerformingResellers: [
|
||||
{ name: 'John Smith', revenue: 89000, deals: 12, growth: 15.2 },
|
||||
{ name: 'Sarah Johnson', revenue: 76000, deals: 9, growth: 8.7 },
|
||||
{ name: 'Mike Davis', revenue: 68000, deals: 11, growth: 22.1 },
|
||||
{ name: 'Lisa Wilson', revenue: 54000, deals: 7, growth: 5.3 },
|
||||
{ name: 'David Brown', revenue: 48000, deals: 6, growth: 18.9 }
|
||||
],
|
||||
revenueByMonth: [
|
||||
{ month: 'Jan', revenue: 85000, deals: 8 },
|
||||
{ month: 'Feb', revenue: 92000, deals: 9 },
|
||||
{ month: 'Mar', revenue: 78000, deals: 7 },
|
||||
{ month: 'Apr', revenue: 105000, deals: 10 },
|
||||
{ month: 'May', revenue: 118000, deals: 11 },
|
||||
{ month: 'Jun', revenue: 125000, deals: 12 }
|
||||
],
|
||||
categoryPerformance: [
|
||||
{ category: 'Cloud Services', revenue: 450000, percentage: 36 },
|
||||
{ category: 'Security Solutions', revenue: 320000, percentage: 25.6 },
|
||||
{ category: 'Data Analytics', revenue: 280000, percentage: 22.4 },
|
||||
{ category: 'Consulting', revenue: 200000, percentage: 16 }
|
||||
],
|
||||
regionalPerformance: [
|
||||
{ region: 'North America', revenue: 520000, growth: 15.2 },
|
||||
{ region: 'Europe', revenue: 380000, growth: 8.7 },
|
||||
{ region: 'Asia Pacific', revenue: 250000, growth: 22.1 },
|
||||
{ region: 'Latin America', revenue: 100000, growth: 5.3 }
|
||||
]
|
||||
};
|
||||
|
||||
const getGrowthColor = (growth: number) => {
|
||||
return growth >= 0 ? 'text-success-600 dark:text-success-400' : 'text-danger-600 dark:text-danger-400';
|
||||
};
|
||||
|
||||
const getGrowthIcon = (growth: number) => {
|
||||
return growth >= 0 ? <ArrowUp className="w-4 h-4" /> : <ArrowDown className="w-4 h-4" />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
Analytics Dashboard
|
||||
</h1>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mt-1">
|
||||
Comprehensive insights into your business performance
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<select
|
||||
value={dateRange}
|
||||
onChange={(e) => setDateRange(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="7d">Last 7 days</option>
|
||||
<option value="30d">Last 30 days</option>
|
||||
<option value="90d">Last 90 days</option>
|
||||
<option value="1y">Last year</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={() => setShowDetailedMetrics(!showDetailedMetrics)}
|
||||
className="inline-flex items-center px-3 py-2 bg-secondary-100 dark:bg-secondary-800 text-secondary-700 dark:text-secondary-300 rounded-lg text-sm font-medium hover:bg-secondary-200 dark:hover:bg-secondary-700 transition-colors"
|
||||
>
|
||||
{showDetailedMetrics ? <EyeOff className="w-4 h-4 mr-2" /> : <Eye className="w-4 h-4 mr-2" />}
|
||||
{showDetailedMetrics ? 'Hide Details' : 'Show Details'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Key Metrics */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Total Revenue</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
${(analyticsData.totalRevenue / 1000).toFixed(0)}K
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<DollarSign className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+{analyticsData.monthlyGrowth}% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Active Resellers</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{analyticsData.activeResellers}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-success-100 dark:bg-success-900 rounded-lg">
|
||||
<Users className="w-6 h-6 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+8% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Total Deals</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{analyticsData.totalDeals}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-warning-100 dark:bg-warning-900 rounded-lg">
|
||||
<ShoppingCart className="w-6 h-6 text-warning-600 dark:text-warning-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+15% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-secondary-600 dark:text-secondary-400">Conversion Rate</p>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">{analyticsData.conversionRate}%</p>
|
||||
</div>
|
||||
<div className="p-3 bg-danger-100 dark:bg-danger-900 rounded-lg">
|
||||
<Target className="w-6 h-6 text-danger-600 dark:text-danger-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-4">
|
||||
<TrendingUp className="w-4 h-4 text-success-600 mr-1" />
|
||||
<span className="text-sm text-success-600">+2.3% from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Charts Section */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Revenue Trend */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Revenue Trend</h3>
|
||||
<button className="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-64 bg-gray-50 dark:bg-gray-800 rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<LineChart className="w-16 h-16 text-secondary-400 mx-auto mb-4" />
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Revenue trend chart</p>
|
||||
<p className="text-sm text-secondary-500">Monthly revenue progression</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-2 gap-4">
|
||||
{analyticsData.revenueByMonth.slice(-4).map((item, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">{item.month}</p>
|
||||
<p className="font-semibold text-secondary-900 dark:text-white">${(item.revenue / 1000).toFixed(0)}K</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Performance */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Category Performance</h3>
|
||||
<button className="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-64 bg-gray-50 dark:bg-gray-800 rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<PieChart className="w-16 h-16 text-secondary-400 mx-auto mb-4" />
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Category distribution chart</p>
|
||||
<p className="text-sm text-secondary-500">Revenue by product category</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
{analyticsData.categoryPerformance.map((category, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<span className="text-sm text-secondary-600 dark:text-secondary-400">{category.category}</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-20 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary-600 h-2 rounded-full"
|
||||
style={{ width: `${category.percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white">{category.percentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Performing Resellers */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Top Performing Resellers</h3>
|
||||
<button className="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Reseller</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Revenue</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Deals</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Growth</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-secondary-600 dark:text-secondary-400">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{analyticsData.topPerformingResellers.map((reseller, index) => (
|
||||
<tr key={index} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
|
||||
<Users className="w-4 h-4 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-secondary-900 dark:text-white">{reseller.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-secondary-900 dark:text-white">
|
||||
${(reseller.revenue / 1000).toFixed(0)}K
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-secondary-600 dark:text-secondary-400">
|
||||
{reseller.deals}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
{getGrowthIcon(reseller.growth)}
|
||||
<span className={`text-sm ${getGrowthColor(reseller.growth)}`}>
|
||||
{reseller.growth}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-300">
|
||||
Active
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Regional Performance */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Regional Performance</h3>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{analyticsData.regionalPerformance.map((region, index) => (
|
||||
<div key={index} className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">{region.region}</h4>
|
||||
<div className="flex items-center space-x-1">
|
||||
{getGrowthIcon(region.growth)}
|
||||
<span className={`text-sm ${getGrowthColor(region.growth)}`}>
|
||||
{region.growth}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-secondary-900 dark:text-white">
|
||||
${(region.revenue / 1000).toFixed(0)}K
|
||||
</p>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400 mt-1">Revenue</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Export Options */}
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Export Analytics</h3>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Download analytics data in various formats</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={() => console.log('Exporting PDF...')}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export PDF
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('Exporting Excel...')}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export Excel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('Exporting CSV...')}
|
||||
className="inline-flex items-center px-4 py-2 bg-warning-600 text-white rounded-lg text-sm font-medium hover:bg-warning-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Export CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Analytics;
|
||||
359
src/pages/Reports.tsx
Normal file
359
src/pages/Reports.tsx
Normal file
@ -0,0 +1,359 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
FileText,
|
||||
Download,
|
||||
Calendar,
|
||||
Filter,
|
||||
Search,
|
||||
Eye,
|
||||
EyeOff,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Users,
|
||||
DollarSign,
|
||||
ShoppingCart,
|
||||
Award,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Star,
|
||||
RefreshCw,
|
||||
Settings,
|
||||
Plus
|
||||
} from 'lucide-react';
|
||||
|
||||
interface Report {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'revenue' | 'performance' | 'commission' | 'analytics' | 'custom';
|
||||
description: string;
|
||||
lastGenerated: string;
|
||||
status: 'ready' | 'generating' | 'failed';
|
||||
size: string;
|
||||
format: 'pdf' | 'excel' | 'csv';
|
||||
}
|
||||
|
||||
const Reports: React.FC = () => {
|
||||
const [selectedReportType, setSelectedReportType] = useState<string>('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showGenerateReport, setShowGenerateReport] = useState(false);
|
||||
|
||||
// Mock data
|
||||
const reports: Report[] = [
|
||||
{
|
||||
id: 'REP-001',
|
||||
name: 'Monthly Revenue Report',
|
||||
type: 'revenue',
|
||||
description: 'Comprehensive monthly revenue analysis with growth metrics',
|
||||
lastGenerated: '2024-01-15T10:30:00Z',
|
||||
status: 'ready',
|
||||
size: '2.4 MB',
|
||||
format: 'pdf'
|
||||
},
|
||||
{
|
||||
id: 'REP-002',
|
||||
name: 'Reseller Performance Report',
|
||||
type: 'performance',
|
||||
description: 'Detailed performance metrics for all resellers',
|
||||
lastGenerated: '2024-01-14T14:20:00Z',
|
||||
status: 'ready',
|
||||
size: '1.8 MB',
|
||||
format: 'excel'
|
||||
},
|
||||
{
|
||||
id: 'REP-003',
|
||||
name: 'Commission Statement',
|
||||
type: 'commission',
|
||||
description: 'Monthly commission calculations and payouts',
|
||||
lastGenerated: '2024-01-13T09:15:00Z',
|
||||
status: 'ready',
|
||||
size: '3.1 MB',
|
||||
format: 'pdf'
|
||||
},
|
||||
{
|
||||
id: 'REP-004',
|
||||
name: 'Sales Analytics Report',
|
||||
type: 'analytics',
|
||||
description: 'Advanced sales analytics and trend analysis',
|
||||
lastGenerated: '2024-01-12T16:45:00Z',
|
||||
status: 'generating',
|
||||
size: '4.2 MB',
|
||||
format: 'excel'
|
||||
},
|
||||
{
|
||||
id: 'REP-005',
|
||||
name: 'Custom Performance Report',
|
||||
type: 'custom',
|
||||
description: 'Custom report with specific metrics and filters',
|
||||
lastGenerated: '2024-01-11T11:30:00Z',
|
||||
status: 'failed',
|
||||
size: '0 MB',
|
||||
format: 'csv'
|
||||
}
|
||||
];
|
||||
|
||||
const reportTypes = [
|
||||
{ id: 'all', name: 'All Reports', icon: FileText },
|
||||
{ id: 'revenue', name: 'Revenue Reports', icon: DollarSign },
|
||||
{ id: 'performance', name: 'Performance Reports', icon: TrendingUp },
|
||||
{ id: 'commission', name: 'Commission Reports', icon: Award },
|
||||
{ id: 'analytics', name: 'Analytics Reports', icon: BarChart3 },
|
||||
{ id: 'custom', name: 'Custom Reports', icon: Settings }
|
||||
];
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'ready': return 'bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-300';
|
||||
case 'generating': return 'bg-warning-100 text-warning-800 dark:bg-warning-900 dark:text-warning-300';
|
||||
case 'failed': return 'bg-danger-100 text-danger-800 dark:bg-danger-900 dark:text-danger-300';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'ready': return <CheckCircle className="w-4 h-4" />;
|
||||
case 'generating': return <Clock className="w-4 h-4" />;
|
||||
case 'failed': return <AlertCircle className="w-4 h-4" />;
|
||||
default: return <Clock className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'revenue': return <DollarSign className="w-5 h-5" />;
|
||||
case 'performance': return <TrendingUp className="w-5 h-5" />;
|
||||
case 'commission': return <Award className="w-5 h-5" />;
|
||||
case 'analytics': return <BarChart3 className="w-5 h-5" />;
|
||||
case 'custom': return <Settings className="w-5 h-5" />;
|
||||
default: return <FileText className="w-5 h-5" />;
|
||||
}
|
||||
};
|
||||
|
||||
const filteredReports = reports.filter(report => {
|
||||
const matchesSearch = report.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
report.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesType = selectedReportType === 'all' || report.type === selectedReportType;
|
||||
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<FileText className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
Reports Center
|
||||
</h1>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mt-1">
|
||||
Generate and manage business reports
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowGenerateReport(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Generate Report
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Report Type Filter */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Report Types</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
{reportTypes.map((type) => (
|
||||
<button
|
||||
key={type.id}
|
||||
onClick={() => setSelectedReportType(type.id)}
|
||||
className={`p-4 rounded-lg border transition-colors ${
|
||||
selectedReportType === type.id
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-900 text-primary-700 dark:text-primary-300'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-secondary-700 dark:text-secondary-300 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-2">
|
||||
<type.icon className="w-6 h-6" />
|
||||
<span className="text-sm font-medium text-center">{type.name}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search and Filters */}
|
||||
<div className="card p-6">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-secondary-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search reports..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<select className="input text-sm">
|
||||
<option>All Formats</option>
|
||||
<option>PDF</option>
|
||||
<option>Excel</option>
|
||||
<option>CSV</option>
|
||||
</select>
|
||||
<select className="input text-sm">
|
||||
<option>All Status</option>
|
||||
<option>Ready</option>
|
||||
<option>Generating</option>
|
||||
<option>Failed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reports List */}
|
||||
<div className="space-y-4">
|
||||
{filteredReports.map((report) => (
|
||||
<div key={report.id} className="card p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
|
||||
{getTypeIcon(report.type)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-2">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">{report.name}</h3>
|
||||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(report.status)}`}>
|
||||
{getStatusIcon(report.status)}
|
||||
<span className="ml-1">{report.status}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{report.description}</p>
|
||||
<div className="flex items-center space-x-6 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Generated: {new Date(report.lastGenerated).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>Size: {report.size}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="uppercase font-medium">{report.format}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button className="p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded-lg transition-colors">
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
{report.status === 'ready' && (
|
||||
<button className="p-2 text-success-600 hover:bg-success-100 dark:hover:bg-success-900 rounded-lg transition-colors">
|
||||
<Download className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button className="p-2 text-secondary-600 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Quick Actions</h3>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<DollarSign className="w-5 h-5 text-primary-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Revenue Report</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<TrendingUp className="w-5 h-5 text-success-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Performance Report</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<Award className="w-5 h-5 text-warning-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Commission Report</span>
|
||||
</button>
|
||||
<button className="flex items-center space-x-3 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<BarChart3 className="w-5 h-5 text-danger-600" />
|
||||
<span className="text-secondary-900 dark:text-white">Analytics Report</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Generate Report Modal */}
|
||||
{showGenerateReport && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Generate New Report</h3>
|
||||
<form className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Report Name</label>
|
||||
<input type="text" className="input w-full" placeholder="Enter report name" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Report Type</label>
|
||||
<select className="input w-full">
|
||||
<option>Revenue Report</option>
|
||||
<option>Performance Report</option>
|
||||
<option>Commission Report</option>
|
||||
<option>Analytics Report</option>
|
||||
<option>Custom Report</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Date Range</label>
|
||||
<select className="input w-full">
|
||||
<option>Last 7 days</option>
|
||||
<option>Last 30 days</option>
|
||||
<option>Last 90 days</option>
|
||||
<option>Last year</option>
|
||||
<option>Custom range</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Format</label>
|
||||
<select className="input w-full">
|
||||
<option>PDF</option>
|
||||
<option>Excel</option>
|
||||
<option>CSV</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGenerateReport(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Generate Report
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Reports;
|
||||
471
src/pages/Settings.tsx
Normal file
471
src/pages/Settings.tsx
Normal file
@ -0,0 +1,471 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Settings as SettingsIcon,
|
||||
User,
|
||||
Shield,
|
||||
Bell,
|
||||
Palette,
|
||||
Globe,
|
||||
CreditCard,
|
||||
Database,
|
||||
Save,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Check,
|
||||
X,
|
||||
Plus,
|
||||
Trash2,
|
||||
Edit,
|
||||
Download,
|
||||
Upload,
|
||||
Key,
|
||||
Mail,
|
||||
Phone,
|
||||
MapPin
|
||||
} from 'lucide-react';
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState('profile');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [notifications, setNotifications] = useState({
|
||||
email: true,
|
||||
sms: false,
|
||||
push: true,
|
||||
marketing: false
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{ id: 'profile', name: 'Profile', icon: User },
|
||||
{ id: 'security', name: 'Security', icon: Shield },
|
||||
{ id: 'notifications', name: 'Notifications', icon: Bell },
|
||||
{ id: 'appearance', name: 'Appearance', icon: Palette },
|
||||
{ id: 'billing', name: 'Billing', icon: CreditCard },
|
||||
{ id: 'integrations', name: 'Integrations', icon: Database }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<SettingsIcon className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
Settings
|
||||
</h1>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mt-1">
|
||||
Manage your account preferences and settings
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{/* Sidebar Navigation */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="card p-4">
|
||||
<nav className="space-y-2">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-left transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-300'
|
||||
: 'text-secondary-700 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
<tab.icon className="w-5 h-5" />
|
||||
<span className="font-medium">{tab.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="lg:col-span-3">
|
||||
{/* Profile Settings */}
|
||||
{activeTab === 'profile' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Profile Information</h3>
|
||||
<form className="space-y-6">
|
||||
<div className="flex items-center space-x-6">
|
||||
<div className="w-20 h-20 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
|
||||
<User className="w-10 h-10 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Upload Photo
|
||||
</button>
|
||||
<button className="px-4 py-2 border border-gray-300 dark:border-gray-600 text-secondary-700 dark:text-secondary-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">First Name</label>
|
||||
<input type="text" className="input w-full" defaultValue="John" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Last Name</label>
|
||||
<input type="text" className="input w-full" defaultValue="Smith" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Email</label>
|
||||
<input type="email" className="input w-full" defaultValue="john.smith@example.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Phone</label>
|
||||
<input type="tel" className="input w-full" defaultValue="+1 (555) 123-4567" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Company</label>
|
||||
<input type="text" className="input w-full" defaultValue="Cloudtopiaa Solutions" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Job Title</label>
|
||||
<input type="text" className="input w-full" defaultValue="Channel Partner" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Bio</label>
|
||||
<textarea className="input w-full" rows={4} placeholder="Tell us about yourself..."></textarea>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button className="px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Security Settings */}
|
||||
{activeTab === 'security' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Password & Security</h3>
|
||||
<form className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Current Password</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
className="input w-full pr-10"
|
||||
placeholder="Enter current password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-secondary-400 hover:text-secondary-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">New Password</label>
|
||||
<input type="password" className="input w-full" placeholder="Enter new password" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">Confirm New Password</label>
|
||||
<input type="password" className="input w-full" placeholder="Confirm new password" />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button className="px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Update Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<h4 className="text-md font-semibold text-secondary-900 dark:text-white mb-4">Two-Factor Authentication</h4>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Add an extra layer of security to your account</p>
|
||||
<p className="text-sm text-secondary-500 dark:text-secondary-500 mt-1">Currently disabled</p>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Enable 2FA
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-6">
|
||||
<h4 className="text-md font-semibold text-secondary-900 dark:text-white mb-4">Active Sessions</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div>
|
||||
<p className="font-medium text-secondary-900 dark:text-white">Chrome on Windows</p>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Last active: 2 hours ago</p>
|
||||
</div>
|
||||
<button className="text-danger-600 hover:text-danger-700 text-sm font-medium">
|
||||
Revoke
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div>
|
||||
<p className="font-medium text-secondary-900 dark:text-white">Safari on iPhone</p>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Last active: 1 day ago</p>
|
||||
</div>
|
||||
<button className="text-danger-600 hover:text-danger-700 text-sm font-medium">
|
||||
Revoke
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notification Settings */}
|
||||
{activeTab === 'notifications' && (
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Notification Preferences</h3>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Email Notifications</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Receive notifications via email</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notifications.email}
|
||||
onChange={(e) => setNotifications({...notifications, email: e.target.checked})}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">SMS Notifications</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Receive notifications via SMS</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notifications.sms}
|
||||
onChange={(e) => setNotifications({...notifications, sms: e.target.checked})}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Push Notifications</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Receive push notifications in browser</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notifications.push}
|
||||
onChange={(e) => setNotifications({...notifications, push: e.target.checked})}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Marketing Emails</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Receive marketing and promotional emails</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notifications.marketing}
|
||||
onChange={(e) => setNotifications({...notifications, marketing: e.target.checked})}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button className="px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Save Preferences
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Appearance Settings */}
|
||||
{activeTab === 'appearance' && (
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Appearance & Theme</h3>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white mb-4">Theme</h4>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<button className="p-4 border-2 border-primary-500 bg-white dark:bg-gray-800 rounded-lg">
|
||||
<div className="w-full h-8 bg-gray-100 dark:bg-gray-700 rounded mb-2"></div>
|
||||
<p className="text-sm font-medium text-secondary-900 dark:text-white">Light</p>
|
||||
</button>
|
||||
<button className="p-4 border-2 border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 rounded-lg">
|
||||
<div className="w-full h-8 bg-gray-800 rounded mb-2"></div>
|
||||
<p className="text-sm font-medium text-secondary-900 dark:text-white">Dark</p>
|
||||
</button>
|
||||
<button className="p-4 border-2 border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 rounded-lg">
|
||||
<div className="w-full h-8 bg-gradient-to-r from-gray-100 to-gray-800 rounded mb-2"></div>
|
||||
<p className="text-sm font-medium text-secondary-900 dark:text-white">Auto</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white mb-4">Language</h4>
|
||||
<select className="input w-full max-w-xs">
|
||||
<option>English (US)</option>
|
||||
<option>Spanish</option>
|
||||
<option>French</option>
|
||||
<option>German</option>
|
||||
<option>Chinese</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white mb-4">Time Zone</h4>
|
||||
<select className="input w-full max-w-xs">
|
||||
<option>UTC-05:00 Eastern Time (US & Canada)</option>
|
||||
<option>UTC-08:00 Pacific Time (US & Canada)</option>
|
||||
<option>UTC+00:00 London</option>
|
||||
<option>UTC+01:00 Paris</option>
|
||||
<option>UTC+05:30 Mumbai</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button className="px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Billing Settings */}
|
||||
{activeTab === 'billing' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Billing Information</h3>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Current Plan</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Professional Plan - $99/month</p>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Change Plan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white mb-4">Payment Method</h4>
|
||||
<div className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<CreditCard className="w-6 h-6 text-secondary-600" />
|
||||
<div>
|
||||
<p className="font-medium text-secondary-900 dark:text-white">•••• •••• •••• 4242</p>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Expires 12/25</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="text-primary-600 hover:text-primary-700 text-sm font-medium">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white mb-4">Billing Address</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<input type="text" className="input" placeholder="Street Address" />
|
||||
<input type="text" className="input" placeholder="City" />
|
||||
<input type="text" className="input" placeholder="State" />
|
||||
<input type="text" className="input" placeholder="ZIP Code" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Integrations Settings */}
|
||||
{activeTab === 'integrations' && (
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-6">Integrations</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
|
||||
<Database className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Salesforce</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Sync your sales data</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-green-100 dark:bg-green-900 rounded-lg flex items-center justify-center">
|
||||
<Mail className="w-5 h-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Mailchimp</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Email marketing integration</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center">
|
||||
<Key className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-secondary-900 dark:text-white">Zapier</h4>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">Automate workflows</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
688
src/pages/Support.tsx
Normal file
688
src/pages/Support.tsx
Normal file
@ -0,0 +1,688 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Headphones,
|
||||
MessageCircle,
|
||||
FileText,
|
||||
Search,
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Settings,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
Star,
|
||||
Filter,
|
||||
Download,
|
||||
Send,
|
||||
User,
|
||||
Calendar,
|
||||
Tag,
|
||||
X
|
||||
} from 'lucide-react';
|
||||
import SupportAdmin from '../components/SupportAdmin';
|
||||
|
||||
interface SupportTicket {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
status: 'open' | 'in_progress' | 'resolved' | 'closed';
|
||||
priority: 'low' | 'medium' | 'high' | 'urgent';
|
||||
category: 'technical' | 'billing' | 'account' | 'general';
|
||||
submittedBy: string;
|
||||
assignedTo: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: TicketMessage[];
|
||||
}
|
||||
|
||||
interface TicketMessage {
|
||||
id: string;
|
||||
content: string;
|
||||
sender: string;
|
||||
timestamp: string;
|
||||
isInternal: boolean;
|
||||
}
|
||||
|
||||
interface FAQ {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
helpful: number;
|
||||
notHelpful: number;
|
||||
}
|
||||
|
||||
interface SupportResource {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'documentation' | 'video' | 'guide' | 'tutorial';
|
||||
url: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const Support: React.FC = () => {
|
||||
const [currentView, setCurrentView] = useState<'tickets' | 'faqs' | 'resources' | 'admin'>('tickets');
|
||||
const [isAdminMode, setIsAdminMode] = useState(false);
|
||||
const [selectedTicket, setSelectedTicket] = useState<SupportTicket | null>(null);
|
||||
const [showNewTicket, setShowNewTicket] = useState(false);
|
||||
const [showNewFAQ, setShowNewFAQ] = useState(false);
|
||||
const [showNewResource, setShowNewResource] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||||
const [filterPriority, setFilterPriority] = useState<string>('all');
|
||||
const [filterCategory, setFilterCategory] = useState<string>('all');
|
||||
|
||||
// Mock data
|
||||
const supportTickets: SupportTicket[] = [
|
||||
{
|
||||
id: 'TICKET-001',
|
||||
title: 'Commission calculation issue',
|
||||
description: 'The commission calculation seems incorrect for the last month. Need assistance to verify the calculations.',
|
||||
status: 'open',
|
||||
priority: 'high',
|
||||
category: 'billing',
|
||||
submittedBy: 'John Smith',
|
||||
assignedTo: 'Support Team',
|
||||
createdAt: '2024-01-15T10:30:00Z',
|
||||
updatedAt: '2024-01-15T14:20:00Z',
|
||||
messages: [
|
||||
{
|
||||
id: 'MSG-001',
|
||||
content: 'I noticed that my commission for December 2023 is showing $2,500 but I expected $3,200 based on my sales.',
|
||||
sender: 'John Smith',
|
||||
timestamp: '2024-01-15T10:30:00Z',
|
||||
isInternal: false
|
||||
},
|
||||
{
|
||||
id: 'MSG-002',
|
||||
content: 'Thank you for reporting this issue. I\'m investigating the commission calculation for your account.',
|
||||
sender: 'Support Team',
|
||||
timestamp: '2024-01-15T11:15:00Z',
|
||||
isInternal: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'TICKET-002',
|
||||
title: 'Dashboard not loading properly',
|
||||
description: 'The dashboard is taking too long to load and some widgets are not displaying correctly.',
|
||||
status: 'in_progress',
|
||||
priority: 'medium',
|
||||
category: 'technical',
|
||||
submittedBy: 'Sarah Johnson',
|
||||
assignedTo: 'Technical Team',
|
||||
createdAt: '2024-01-14T09:15:00Z',
|
||||
updatedAt: '2024-01-15T08:45:00Z',
|
||||
messages: [
|
||||
{
|
||||
id: 'MSG-003',
|
||||
content: 'Dashboard has been slow for the past 2 days. Sometimes it takes 30+ seconds to load.',
|
||||
sender: 'Sarah Johnson',
|
||||
timestamp: '2024-01-14T09:15:00Z',
|
||||
isInternal: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const faqs: FAQ[] = [
|
||||
{
|
||||
id: 'FAQ-001',
|
||||
question: 'How do I calculate my commission?',
|
||||
answer: 'Your commission is calculated based on your sales volume and the commission rate for your tier. You can view detailed calculations in the Commission section of your dashboard.',
|
||||
category: 'billing',
|
||||
tags: ['commission', 'billing', 'calculation'],
|
||||
helpful: 45,
|
||||
notHelpful: 2
|
||||
},
|
||||
{
|
||||
id: 'FAQ-002',
|
||||
question: 'How do I add new resellers to my network?',
|
||||
answer: 'To add new resellers, go to the Resellers section and click "Add New Reseller". Fill in the required information and submit the form.',
|
||||
category: 'account',
|
||||
tags: ['resellers', 'network', 'management'],
|
||||
helpful: 32,
|
||||
notHelpful: 1
|
||||
}
|
||||
];
|
||||
|
||||
const supportResources: SupportResource[] = [
|
||||
{
|
||||
id: 'RES-001',
|
||||
title: 'Getting Started Guide',
|
||||
description: 'Complete guide for new channel partners to get started with the platform.',
|
||||
type: 'guide',
|
||||
url: '/guides/getting-started',
|
||||
category: 'onboarding',
|
||||
tags: ['beginner', 'setup', 'guide']
|
||||
},
|
||||
{
|
||||
id: 'RES-002',
|
||||
title: 'Commission System Overview',
|
||||
description: 'Detailed explanation of how the commission system works.',
|
||||
type: 'documentation',
|
||||
url: '/docs/commission-system',
|
||||
category: 'billing',
|
||||
tags: ['commission', 'billing', 'system']
|
||||
}
|
||||
];
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'open': return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
|
||||
case 'in_progress': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
||||
case 'resolved': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
||||
case 'closed': return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'urgent': return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
|
||||
case 'high': return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300';
|
||||
case 'medium': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
||||
case 'low': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const filteredTickets = supportTickets.filter(ticket => {
|
||||
const matchesSearch = ticket.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
ticket.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesStatus = filterStatus === 'all' || ticket.status === filterStatus;
|
||||
const matchesPriority = filterPriority === 'all' || ticket.priority === filterPriority;
|
||||
const matchesCategory = filterCategory === 'all' || ticket.category === filterCategory;
|
||||
|
||||
return matchesSearch && matchesStatus && matchesPriority && matchesCategory;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<Headphones className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
Support Center
|
||||
</h1>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mt-1">
|
||||
Manage support tickets, FAQs, and resources
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
{/* View Toggle Buttons */}
|
||||
<div className="flex items-center space-x-2 bg-secondary-100 dark:bg-secondary-800 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setCurrentView('tickets')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'tickets'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Tickets
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('faqs')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'faqs'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
FAQs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('resources')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'resources'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Resources
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentView('admin')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
currentView === 'admin'
|
||||
? 'bg-white dark:bg-gray-700 text-secondary-900 dark:text-white shadow-sm'
|
||||
: 'text-secondary-600 dark:text-secondary-400 hover:text-secondary-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Admin
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Admin Actions */}
|
||||
{currentView === 'tickets' && (
|
||||
<button
|
||||
onClick={() => setShowNewTicket(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Ticket
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Based on Current View */}
|
||||
{currentView === 'tickets' && (
|
||||
<div className="space-y-6">
|
||||
{/* Search and Filters */}
|
||||
<div className="card p-6">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-secondary-400 w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tickets..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<select
|
||||
value={filterStatus}
|
||||
onChange={(e) => setFilterStatus(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Status</option>
|
||||
<option value="open">Open</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
<select
|
||||
value={filterPriority}
|
||||
onChange={(e) => setFilterPriority(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Priority</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
<option value="high">High</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
<select
|
||||
value={filterCategory}
|
||||
onChange={(e) => setFilterCategory(e.target.value)}
|
||||
className="input text-sm"
|
||||
>
|
||||
<option value="all">All Categories</option>
|
||||
<option value="technical">Technical</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="account">Account</option>
|
||||
<option value="general">General</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tickets List */}
|
||||
<div className="space-y-4">
|
||||
{filteredTickets.map((ticket) => (
|
||||
<div key={ticket.id} className="card p-6 hover:shadow-md transition-shadow cursor-pointer" onClick={() => setSelectedTicket(ticket)}>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-2">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">{ticket.title}</h3>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(ticket.status)}`}>
|
||||
{ticket.status.replace('_', ' ')}
|
||||
</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(ticket.priority)}`}>
|
||||
{ticket.priority}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{ticket.description}</p>
|
||||
<div className="flex items-center space-x-6 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="w-4 h-4" />
|
||||
<span>{ticket.submittedBy}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>{new Date(ticket.createdAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
<span>{ticket.messages.length} messages</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button className="p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900 rounded-lg transition-colors">
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
{isAdminMode && (
|
||||
<>
|
||||
<button className="p-2 text-secondary-600 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button className="p-2 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded-lg transition-colors">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'faqs' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Frequently Asked Questions</h2>
|
||||
{isAdminMode && (
|
||||
<button
|
||||
onClick={() => setShowNewFAQ(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add FAQ
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq) => (
|
||||
<div key={faq.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium text-secondary-900 dark:text-white mb-2">{faq.question}</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{faq.answer}</p>
|
||||
<div className="flex items-center space-x-4 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<span>Category: {faq.category}</span>
|
||||
<span>Helpful: {faq.helpful}</span>
|
||||
<span>Not Helpful: {faq.notHelpful}</span>
|
||||
</div>
|
||||
</div>
|
||||
{isAdminMode && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<button className="p-2 text-secondary-600 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button className="p-2 text-danger-600 hover:bg-danger-100 dark:hover:bg-danger-900 rounded-lg transition-colors">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'resources' && (
|
||||
<div className="space-y-6">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Support Resources</h2>
|
||||
{isAdminMode && (
|
||||
<button
|
||||
onClick={() => setShowNewResource(true)}
|
||||
className="inline-flex items-center px-4 py-2 bg-success-600 text-white rounded-lg text-sm font-medium hover:bg-success-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Resource
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{supportResources.map((resource) => (
|
||||
<div key={resource.id} className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-secondary-900 dark:text-white">{resource.title}</h3>
|
||||
<p className="text-sm text-secondary-500 dark:text-secondary-400">{resource.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">{resource.description}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{resource.tags.slice(0, 2).map((tag) => (
|
||||
<span key={tag} className="px-2 py-1 bg-secondary-100 dark:bg-secondary-800 text-secondary-600 dark:text-secondary-400 text-xs rounded">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<a
|
||||
href={resource.url}
|
||||
className="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 text-sm font-medium"
|
||||
>
|
||||
View
|
||||
</a>
|
||||
{isAdminMode && (
|
||||
<>
|
||||
<button className="text-secondary-600 hover:text-secondary-700 dark:text-secondary-400 dark:hover:text-secondary-300">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button className="text-danger-600 hover:text-danger-700 dark:text-danger-400 dark:hover:text-danger-300">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentView === 'admin' && (
|
||||
<SupportAdmin
|
||||
tickets={supportTickets}
|
||||
faqs={faqs}
|
||||
resources={supportResources}
|
||||
onTicketUpdate={(id, updates) => {
|
||||
console.log('Updating ticket:', id, updates);
|
||||
// Handle ticket update
|
||||
}}
|
||||
onTicketDelete={(id) => {
|
||||
console.log('Deleting ticket:', id);
|
||||
// Handle ticket deletion
|
||||
}}
|
||||
onFAQAdd={(faq) => {
|
||||
console.log('Adding FAQ:', faq);
|
||||
// Handle FAQ addition
|
||||
}}
|
||||
onFAQUpdate={(id, updates) => {
|
||||
console.log('Updating FAQ:', id, updates);
|
||||
// Handle FAQ update
|
||||
}}
|
||||
onFAQDelete={(id) => {
|
||||
console.log('Deleting FAQ:', id);
|
||||
// Handle FAQ deletion
|
||||
}}
|
||||
onResourceAdd={(resource) => {
|
||||
console.log('Adding resource:', resource);
|
||||
// Handle resource addition
|
||||
}}
|
||||
onResourceUpdate={(id, updates) => {
|
||||
console.log('Updating resource:', id, updates);
|
||||
// Handle resource update
|
||||
}}
|
||||
onResourceDelete={(id) => {
|
||||
console.log('Deleting resource:', id);
|
||||
// Handle resource deletion
|
||||
}}
|
||||
onExportReport={(type) => {
|
||||
console.log('Exporting report:', type);
|
||||
// Handle report export
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Ticket Detail Modal */}
|
||||
{selectedTicket && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">{selectedTicket.title}</h2>
|
||||
<button
|
||||
onClick={() => setSelectedTicket(null)}
|
||||
className="text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-300"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="grid md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<h3 className="font-medium text-secondary-900 dark:text-white mb-2">Ticket Details</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-secondary-600 dark:text-secondary-400">Status:</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(selectedTicket.status)}`}>
|
||||
{selectedTicket.status.replace('_', ' ')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-secondary-600 dark:text-secondary-400">Priority:</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(selectedTicket.priority)}`}>
|
||||
{selectedTicket.priority}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-secondary-600 dark:text-secondary-400">Category:</span>
|
||||
<span className="text-secondary-900 dark:text-white">{selectedTicket.category}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-secondary-600 dark:text-secondary-400">Submitted by:</span>
|
||||
<span className="text-secondary-900 dark:text-white">{selectedTicket.submittedBy}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-secondary-900 dark:text-white mb-2">Description</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">{selectedTicket.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h3 className="font-medium text-secondary-900 dark:text-white mb-4">Messages</h3>
|
||||
<div className="space-y-4">
|
||||
{selectedTicket.messages.map((message) => (
|
||||
<div key={message.id} className="flex space-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
|
||||
<User className="w-4 h-4 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<p className="text-secondary-900 dark:text-white">{message.content}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 mt-2 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<span>{message.sender}</span>
|
||||
<span>•</span>
|
||||
<span>{new Date(message.timestamp).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="flex space-x-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type your message..."
|
||||
className="input flex-1"
|
||||
/>
|
||||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* New Ticket Modal */}
|
||||
{showNewTicket && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4">Create New Ticket</h3>
|
||||
<form className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Title</label>
|
||||
<input type="text" className="input w-full" placeholder="Ticket title" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Description</label>
|
||||
<textarea className="input w-full" rows={4} placeholder="Describe your issue..."></textarea>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Priority</label>
|
||||
<select className="input w-full">
|
||||
<option>Low</option>
|
||||
<option>Medium</option>
|
||||
<option>High</option>
|
||||
<option>Urgent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Category</label>
|
||||
<select className="input w-full">
|
||||
<option>Technical</option>
|
||||
<option>Billing</option>
|
||||
<option>Account</option>
|
||||
<option>General</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewTicket(false)}
|
||||
className="px-4 py-2 text-secondary-600 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Create Ticket
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Support;
|
||||
1141
src/pages/Training.tsx
Normal file
1141
src/pages/Training.tsx
Normal file
File diff suppressed because it is too large
Load Diff
637
src/pages/reseller/Training.tsx
Normal file
637
src/pages/reseller/Training.tsx
Normal file
@ -0,0 +1,637 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Play,
|
||||
BookOpen,
|
||||
Download,
|
||||
ExternalLink,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Users,
|
||||
Award,
|
||||
Video,
|
||||
FileText,
|
||||
Headphones,
|
||||
Globe,
|
||||
Star,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Shield,
|
||||
Settings,
|
||||
EyeOff
|
||||
} from 'lucide-react';
|
||||
|
||||
interface TrainingModule {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
duration: string;
|
||||
level: 'Beginner' | 'Intermediate' | 'Advanced';
|
||||
category: string;
|
||||
videos: TrainingVideo[];
|
||||
materials: TrainingMaterial[];
|
||||
completed: boolean;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
interface TrainingVideo {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
youtubeUrl: string;
|
||||
duration: string;
|
||||
thumbnail: string;
|
||||
watched: boolean;
|
||||
}
|
||||
|
||||
interface TrainingMaterial {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: 'PDF' | 'PPT' | 'DOC' | 'VIDEO';
|
||||
downloadUrl: string;
|
||||
size: string;
|
||||
downloaded: boolean;
|
||||
}
|
||||
|
||||
const ResellerTraining: React.FC = () => {
|
||||
const [expandedModule, setExpandedModule] = useState<string | null>(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('all');
|
||||
const [isAdminMode, setIsAdminMode] = useState(false);
|
||||
|
||||
const trainingModules: TrainingModule[] = [
|
||||
{
|
||||
id: 'reseller-basics',
|
||||
title: 'Reseller Portal Mastery',
|
||||
description: 'Master the reseller dashboard, customer management, and basic operations.',
|
||||
duration: '3 hours',
|
||||
level: 'Beginner',
|
||||
category: 'Portal Basics',
|
||||
completed: false,
|
||||
progress: 65,
|
||||
videos: [
|
||||
{
|
||||
id: 'dashboard-overview',
|
||||
title: 'Reseller Dashboard Overview',
|
||||
description: 'Complete tour of your reseller dashboard and key features',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '18:45',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: true
|
||||
},
|
||||
{
|
||||
id: 'customer-management',
|
||||
title: 'Customer Management Best Practices',
|
||||
description: 'How to effectively manage your customer base and relationships',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '25:30',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
}
|
||||
],
|
||||
materials: [
|
||||
{
|
||||
id: 'reseller-handbook',
|
||||
title: 'Reseller Handbook',
|
||||
description: 'Complete guide to reseller operations',
|
||||
type: 'PDF',
|
||||
downloadUrl: '#',
|
||||
size: '4.2 MB',
|
||||
downloaded: true
|
||||
},
|
||||
{
|
||||
id: 'customer-templates',
|
||||
title: 'Customer Communication Templates',
|
||||
description: 'Ready-to-use email and message templates',
|
||||
type: 'DOC',
|
||||
downloadUrl: '#',
|
||||
size: '2.1 MB',
|
||||
downloaded: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sales-excellence',
|
||||
title: 'Sales Excellence for Resellers',
|
||||
description: 'Advanced sales techniques and strategies to boost your revenue.',
|
||||
duration: '5 hours',
|
||||
level: 'Intermediate',
|
||||
category: 'Sales',
|
||||
completed: false,
|
||||
progress: 30,
|
||||
videos: [
|
||||
{
|
||||
id: 'prospecting-techniques',
|
||||
title: 'Prospecting and Lead Generation',
|
||||
description: 'Effective techniques for finding and qualifying potential customers',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '32:15',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
},
|
||||
{
|
||||
id: 'closing-strategies',
|
||||
title: 'Closing Strategies and Negotiation',
|
||||
description: 'Master the art of closing deals and negotiating terms',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '28:40',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
}
|
||||
],
|
||||
materials: [
|
||||
{
|
||||
id: 'sales-playbook',
|
||||
title: 'Sales Playbook',
|
||||
description: 'Comprehensive sales strategy guide',
|
||||
type: 'PDF',
|
||||
downloadUrl: '#',
|
||||
size: '6.8 MB',
|
||||
downloaded: false
|
||||
},
|
||||
{
|
||||
id: 'pitch-deck',
|
||||
title: 'Sales Pitch Deck Templates',
|
||||
description: 'Professional presentation templates',
|
||||
type: 'PPT',
|
||||
downloadUrl: '#',
|
||||
size: '15.3 MB',
|
||||
downloaded: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'technical-expertise',
|
||||
title: 'Technical Expertise for Resellers',
|
||||
description: 'Deep dive into cloud technologies and technical sales.',
|
||||
duration: '8 hours',
|
||||
level: 'Advanced',
|
||||
category: 'Technical',
|
||||
completed: false,
|
||||
progress: 15,
|
||||
videos: [
|
||||
{
|
||||
id: 'cloud-architecture',
|
||||
title: 'Cloud Architecture Fundamentals',
|
||||
description: 'Understanding cloud infrastructure and deployment models',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '45:20',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
},
|
||||
{
|
||||
id: 'security-compliance',
|
||||
title: 'Security and Compliance',
|
||||
description: 'Security best practices and compliance requirements',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '38:55',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
}
|
||||
],
|
||||
materials: [
|
||||
{
|
||||
id: 'technical-whitepaper',
|
||||
title: 'Technical Solutions Guide',
|
||||
description: 'In-depth technical documentation',
|
||||
type: 'PDF',
|
||||
downloadUrl: '#',
|
||||
size: '18.7 MB',
|
||||
downloaded: false
|
||||
},
|
||||
{
|
||||
id: 'case-studies',
|
||||
title: 'Success Case Studies',
|
||||
description: 'Real-world implementation examples',
|
||||
type: 'PDF',
|
||||
downloadUrl: '#',
|
||||
size: '12.4 MB',
|
||||
downloaded: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'business-growth',
|
||||
title: 'Business Growth and Scaling',
|
||||
description: 'Strategies for scaling your reseller business and maximizing profits.',
|
||||
duration: '4 hours',
|
||||
level: 'Intermediate',
|
||||
category: 'Business',
|
||||
completed: false,
|
||||
progress: 0,
|
||||
videos: [
|
||||
{
|
||||
id: 'business-scaling',
|
||||
title: 'Scaling Your Reseller Business',
|
||||
description: 'Strategies for growing your customer base and revenue',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '35:10',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
},
|
||||
{
|
||||
id: 'profit-optimization',
|
||||
title: 'Profit Optimization Strategies',
|
||||
description: 'Maximizing margins and optimizing pricing strategies',
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
duration: '29:25',
|
||||
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
|
||||
watched: false
|
||||
}
|
||||
],
|
||||
materials: [
|
||||
{
|
||||
id: 'business-plan-template',
|
||||
title: 'Business Plan Template',
|
||||
description: 'Template for creating your business growth plan',
|
||||
type: 'DOC',
|
||||
downloadUrl: '#',
|
||||
size: '3.2 MB',
|
||||
downloaded: false
|
||||
},
|
||||
{
|
||||
id: 'financial-calculator',
|
||||
title: 'Financial Planning Calculator',
|
||||
description: 'Excel tool for financial planning and projections',
|
||||
type: 'DOC',
|
||||
downloadUrl: '#',
|
||||
size: '2.8 MB',
|
||||
downloaded: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const categories = ['all', 'Portal Basics', 'Sales', 'Technical', 'Business'];
|
||||
|
||||
const filteredModules = selectedCategory === 'all'
|
||||
? trainingModules
|
||||
: trainingModules.filter(module => module.category === selectedCategory);
|
||||
|
||||
const getLevelColor = (level: string) => {
|
||||
switch (level) {
|
||||
case 'Beginner': return 'bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-300';
|
||||
case 'Intermediate': return 'bg-warning-100 text-warning-800 dark:bg-warning-900 dark:text-warning-300';
|
||||
case 'Advanced': return 'bg-danger-100 text-danger-800 dark:bg-danger-900 dark:text-danger-300';
|
||||
default: return 'bg-secondary-100 text-secondary-800 dark:bg-secondary-900 dark:text-secondary-300';
|
||||
}
|
||||
};
|
||||
|
||||
const toggleModule = (moduleId: string) => {
|
||||
setExpandedModule(expandedModule === moduleId ? null : moduleId);
|
||||
};
|
||||
|
||||
const totalProgress = Math.round(
|
||||
trainingModules.reduce((acc, module) => acc + module.progress, 0) / trainingModules.length
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<BookOpen className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
Training Center
|
||||
</h1>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mt-1">
|
||||
Enhance your skills and grow your reseller business
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-primary-600 dark:text-primary-400">{totalProgress}%</div>
|
||||
<div className="text-sm text-secondary-500 dark:text-secondary-400">Overall Progress</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsAdminMode(!isAdminMode)}
|
||||
className={`inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
isAdminMode
|
||||
? 'bg-primary-600 text-white hover:bg-primary-700'
|
||||
: 'bg-secondary-100 text-secondary-700 hover:bg-secondary-200 dark:bg-secondary-800 dark:text-secondary-300 dark:hover:bg-secondary-700'
|
||||
}`}
|
||||
>
|
||||
{isAdminMode ? <EyeOff className="w-4 h-4 mr-2" /> : <Settings className="w-4 h-4 mr-2" />}
|
||||
{isAdminMode ? 'Exit Admin' : 'Admin Mode'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works Section */}
|
||||
<div className="card p-6">
|
||||
<h2 className="text-2xl font-bold text-secondary-900 dark:text-white mb-6">How Training Works</h2>
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Target className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-2">1. Set Goals</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Choose your learning path based on your current skills and business goals.</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-success-100 dark:bg-success-900 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Video className="w-8 h-8 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-2">2. Learn</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Watch expert-led videos and access comprehensive training materials.</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-warning-100 dark:bg-warning-900 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<TrendingUp className="w-8 h-8 text-warning-600 dark:text-warning-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-2">3. Apply</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Implement what you learn to grow your business and increase sales.</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-danger-100 dark:bg-danger-900 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Award className="w-8 h-8 text-danger-600 dark:text-danger-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white mb-2">4. Succeed</h3>
|
||||
<p className="text-secondary-600 dark:text-secondary-400">Earn certifications and achieve your business growth targets.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Overview */}
|
||||
<div className="grid md:grid-cols-4 gap-6 mb-8">
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Modules Completed</h3>
|
||||
<CheckCircle className="w-6 h-6 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-secondary-900 dark:text-white">0</div>
|
||||
<div className="text-sm text-secondary-500 dark:text-secondary-400">out of {trainingModules.length}</div>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Videos Watched</h3>
|
||||
<Video className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
{trainingModules.reduce((acc, module) =>
|
||||
acc + module.videos.filter(v => v.watched).length, 0
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-secondary-500 dark:text-secondary-400">videos completed</div>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Materials Downloaded</h3>
|
||||
<Download className="w-6 h-6 text-warning-600 dark:text-warning-400" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-secondary-900 dark:text-white">
|
||||
{trainingModules.reduce((acc, module) =>
|
||||
acc + module.materials.filter(m => m.downloaded).length, 0
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-secondary-500 dark:text-secondary-400">resources saved</div>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Certifications</h3>
|
||||
<Award className="w-6 h-6 text-danger-600 dark:text-danger-400" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-secondary-900 dark:text-white">0</div>
|
||||
<div className="text-sm text-secondary-500 dark:text-secondary-400">earned</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedCategory === category
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-white dark:bg-gray-800 text-secondary-700 dark:text-secondary-300 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{category === 'all' ? 'All Categories' : category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Training Modules */}
|
||||
<div className="space-y-6">
|
||||
{filteredModules.map((module) => (
|
||||
<div key={module.id} className="card overflow-hidden">
|
||||
<div
|
||||
className="p-6 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
onClick={() => toggleModule(module.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
{module.completed ? (
|
||||
<CheckCircle className="w-8 h-8 text-success-600 dark:text-success-400" />
|
||||
) : (
|
||||
<div className="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">{module.progress}%</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-2">
|
||||
<h3 className="text-xl font-semibold text-secondary-900 dark:text-white">{module.title}</h3>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getLevelColor(module.level)}`}>
|
||||
{module.level}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-3">{module.description}</p>
|
||||
<div className="flex items-center space-x-6 text-sm text-secondary-500 dark:text-secondary-400">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>{module.duration}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Video className="w-4 h-4" />
|
||||
<span>{module.videos.filter(v => v.watched).length}/{module.videos.length} watched</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>{module.materials.filter(m => m.downloaded).length}/{module.materials.length} downloaded</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Progress Bar */}
|
||||
<div className="mt-3">
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary-600 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${module.progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
{expandedModule === module.id ? (
|
||||
<ChevronUp className="w-5 h-5 text-secondary-400" />
|
||||
) : (
|
||||
<ChevronDown className="w-5 h-5 text-secondary-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expandedModule === module.id && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-6">
|
||||
{/* Videos Section */}
|
||||
<div className="mb-8">
|
||||
<h4 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4 flex items-center">
|
||||
<Video className="w-5 h-5 mr-2" />
|
||||
Training Videos
|
||||
</h4>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{module.videos.map((video) => (
|
||||
<div key={video.id} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div className="aspect-video bg-gray-200 dark:bg-gray-700 rounded-lg mb-3 relative overflow-hidden">
|
||||
<img
|
||||
src={video.thumbnail}
|
||||
alt={video.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
|
||||
<Play className="w-12 h-12 text-white" />
|
||||
</div>
|
||||
{video.watched && (
|
||||
<div className="absolute top-2 right-2 bg-success-500 text-white px-2 py-1 rounded-full text-xs">
|
||||
Watched
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h5 className="font-medium text-secondary-900 dark:text-white mb-1">{video.title}</h5>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400 mb-2">{video.description}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-secondary-500 dark:text-secondary-400">{video.duration}</span>
|
||||
<a
|
||||
href={video.youtubeUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center px-3 py-1 bg-danger-600 text-white text-sm rounded-md hover:bg-danger-700 transition-colors"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-1" />
|
||||
Watch on YouTube
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Materials Section */}
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold text-secondary-900 dark:text-white mb-4 flex items-center">
|
||||
<FileText className="w-5 h-5 mr-2" />
|
||||
Training Materials
|
||||
</h4>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{module.materials.map((material) => (
|
||||
<div key={material.id} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-primary-100 dark:bg-primary-900 rounded-lg">
|
||||
<FileText className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-medium text-secondary-900 dark:text-white">{material.title}</h5>
|
||||
<p className="text-sm text-secondary-600 dark:text-secondary-400">{material.description}</p>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<span className="text-xs text-secondary-500 dark:text-secondary-400">{material.type}</span>
|
||||
<span className="text-xs text-secondary-500 dark:text-secondary-400">•</span>
|
||||
<span className="text-xs text-secondary-500 dark:text-secondary-400">{material.size}</span>
|
||||
{material.downloaded && (
|
||||
<>
|
||||
<span className="text-xs text-secondary-500 dark:text-secondary-400">•</span>
|
||||
<span className="text-xs text-success-600 dark:text-success-400">Downloaded</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={material.downloadUrl}
|
||||
className="inline-flex items-center px-3 py-1 bg-primary-600 text-white text-sm rounded-md hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-1" />
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Additional Resources */}
|
||||
<div className="card p-6">
|
||||
<h2 className="text-2xl font-bold text-secondary-900 dark:text-white mb-6">Additional Resources</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="bg-gradient-to-br from-primary-50 to-primary-100 dark:from-primary-900 dark:to-primary-800 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Headphones className="w-8 h-8 text-primary-600 dark:text-primary-400" />
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Live Q&A Sessions</h3>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">Join our bi-weekly live Q&A sessions with product experts and get personalized guidance.</p>
|
||||
<a href="#" className="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium">View Schedule →</a>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900 dark:to-success-800 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Users className="w-8 h-8 text-success-600 dark:text-success-400" />
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Reseller Community</h3>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">Connect with fellow resellers, share success stories, and learn from peer experiences.</p>
|
||||
<a href="#" className="text-success-600 hover:text-success-700 dark:text-success-400 dark:hover:text-success-300 font-medium">Join Community →</a>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-warning-50 to-warning-100 dark:from-warning-900 dark:to-warning-800 rounded-lg p-6">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<Globe className="w-8 h-8 text-warning-600 dark:text-warning-400" />
|
||||
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">Knowledge Base</h3>
|
||||
</div>
|
||||
<p className="text-secondary-600 dark:text-secondary-400 mb-4">Access comprehensive documentation, troubleshooting guides, and best practices.</p>
|
||||
<a href="#" className="text-warning-600 hover:text-warning-700 dark:text-warning-400 dark:hover:text-warning-300 font-medium">Browse Articles →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Certification Path */}
|
||||
<div className="bg-gradient-to-r from-primary-600 to-warning-600 rounded-lg p-8 text-white">
|
||||
<div className="text-center">
|
||||
<Award className="w-16 h-16 mx-auto mb-4" />
|
||||
<h2 className="text-3xl font-bold mb-4">Earn Your Certifications</h2>
|
||||
<p className="text-xl mb-6 opacity-90">
|
||||
Complete our training modules and earn professional certifications to boost your credibility and sales performance.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
<div className="bg-white bg-opacity-20 rounded-lg px-4 py-2">
|
||||
<span className="font-medium">Reseller Certified</span>
|
||||
</div>
|
||||
<div className="bg-white bg-opacity-20 rounded-lg px-4 py-2">
|
||||
<span className="font-medium">Sales Expert</span>
|
||||
</div>
|
||||
<div className="bg-white bg-opacity-20 rounded-lg px-4 py-2">
|
||||
<span className="font-medium">Technical Specialist</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResellerTraining;
|
||||
Loading…
Reference in New Issue
Block a user