mirror of
https://github.com/thecyberlearn/victor-dashboard.git
synced 2026-08-18 06:53:04 +00:00
1217 lines
50 KiB
HTML
1217 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Victor AI Agent Dashboard</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
body {
|
||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||
}
|
||
</style>
|
||
|
||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||
|
||
body {
|
||
font-family: 'Inter', sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.glass-effect {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.animate-pulse-soft {
|
||
animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||
}
|
||
|
||
@keyframes pulse-soft {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.7; }
|
||
}
|
||
|
||
.card-hover {
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.card-hover:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.search-input:focus {
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.mobile-stack {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.mobile-full {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.gradient-text {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.loading-skeleton {
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: loading 1.5s infinite;
|
||
}
|
||
|
||
@keyframes loading {
|
||
0% { background-position: 200% 0; }
|
||
100% { background-position: -200% 0; }
|
||
}
|
||
|
||
/* === Custom Enhancements === */
|
||
.glassy-card {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(12px);
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
html {
|
||
font-size: 15px;
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
html {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.text-shadow {
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
@keyframes fadeUp {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.fade-up {
|
||
animation: fadeUp 0.4s ease-out;
|
||
}
|
||
|
||
.tooltip:hover::after {
|
||
content: attr(data-tooltip);
|
||
position: absolute;
|
||
background: #333;
|
||
color: white;
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
border-radius: 4px;
|
||
top: -32px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
white-space: nowrap;
|
||
z-index: 100;
|
||
}
|
||
|
||
.tooltip {
|
||
position: relative;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Mobile-specific styles */
|
||
@media (max-width: 768px) {
|
||
.mobile-stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 0.5rem;
|
||
}
|
||
}
|
||
|
||
/* Collapsible styles */
|
||
.collapsible-content {
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
transition: max-height 0.3s ease;
|
||
}
|
||
|
||
.collapsible-content.open {
|
||
max-height: 200px;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from { opacity: 0; transform: translateY(-10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.slide-down {
|
||
animation: slideDown 0.3s ease-out;
|
||
}
|
||
|
||
</style>
|
||
|
||
<style>
|
||
@keyframes slideUp {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes fadeLine {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
.animate-slide-up {
|
||
animation: slideUp 0.4s ease-out;
|
||
}
|
||
.animate-fade {
|
||
animation: fadeLine 0.6s ease-in;
|
||
}
|
||
</style>
|
||
|
||
</head>
|
||
<body class="bg-gray-50">
|
||
<!-- Mobile Header -->
|
||
<div class="lg:hidden bg-white shadow-sm border-b sticky top-0 z-50">
|
||
<div class="px-4 py-3 flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-blue-600 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
||
<i class="fas fa-robot text-white text-lg"></i>
|
||
</div>
|
||
<div>
|
||
<h1 class="text-lg font-bold text-gray-800">Victor AI</h1>
|
||
<p class="text-xs text-gray-500">Trade Intelligence</p>
|
||
</div>
|
||
</div>
|
||
<button id="mobileMenuBtn" class="p-2 rounded-lg hover:bg-gray-100">
|
||
<i class="fas fa-bars text-gray-600"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="min-h-screen flex">
|
||
<!-- Sidebar -->
|
||
<div id="sidebar" class="hidden lg:flex fixed lg:relative z-40 w-64 bg-white shadow-xl border-r border-gray-200 flex-col">
|
||
<div class="p-6 border-b border-gray-100">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-12 h-12 bg-gradient-to-br from-blue-600 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
||
<i class="fas fa-robot text-white text-xl"></i>
|
||
</div>
|
||
<div>
|
||
<h1 class="text-xl font-bold gradient-text">Victor AI</h1>
|
||
<p class="text-sm text-gray-500">Trade Intelligence Dashboard</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="flex-1 p-4 space-y-2">
|
||
<a href="#" class="flex items-center gap-3 px-4 py-3 bg-blue-50 text-blue-700 rounded-xl font-medium">
|
||
<i class="fas fa-search w-4"></i>
|
||
<span>Search Database</span>
|
||
</a>
|
||
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
|
||
<i class="fas fa-chart-bar w-4"></i>
|
||
<span>Analytics</span>
|
||
</a>
|
||
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
|
||
<i class="fas fa-bookmark w-4"></i>
|
||
<span>Saved Searches</span>
|
||
</a>
|
||
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
|
||
<i class="fas fa-cog w-4"></i>
|
||
<span>Settings</span>
|
||
</a>
|
||
</nav>
|
||
|
||
<div class="p-4 border-t border-gray-100">
|
||
<div class="bg-gradient-to-r from-blue-50 to-purple-50 p-4 rounded-xl">
|
||
<h3 class="font-semibold text-gray-800 mb-2">Need Help?</h3>
|
||
<p class="text-sm text-gray-600 mb-3">Get support with your searches</p>
|
||
<button class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
|
||
Contact Support
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Sidebar Overlay -->
|
||
<div id="sidebarOverlay" class="hidden fixed inset-0 bg-black bg-opacity-50 z-30 lg:hidden"></div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="flex-1 lg:ml-0">
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 lg:py-8">
|
||
<!-- Desktop Header -->
|
||
<div class="hidden lg:block mb-8">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-3xl font-bold text-gray-800 mb-2">Trade Intelligence Search</h1>
|
||
<p class="text-white">Find exporters, importers, and trade data from around the world</p>
|
||
</div>
|
||
<div class="text-right">
|
||
<p class="text-sm text-white">Last updated</p>
|
||
<p class="font-semibold text-gray-800" id="lastUpdated">Just now</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Search Section -->
|
||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-4 lg:p-6 mb-6 lg:mb-8">
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<div class="flex-1">
|
||
<input
|
||
id="query"
|
||
type="text"
|
||
placeholder="find exporter from india, find importer from uk, find gulfood data"
|
||
value=""
|
||
class="w-full px-4 py-3 rounded-xl border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition-all search-input text-sm lg:text-base"
|
||
/>
|
||
</div>
|
||
<button
|
||
onclick="window.callWorkflow()"
|
||
class="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 min-w-[120px]"
|
||
id="searchBtn"
|
||
>
|
||
<i class="fas fa-search"></i>
|
||
<span>Search</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Quick Search Suggestions -->
|
||
<div class="mt-4 flex flex-wrap gap-2">
|
||
<span class="text-sm text-gray-500 mr-2">Quick searches:</span>
|
||
<button onclick="window.setQuickSearch('find exporter from india')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
|
||
Exporters from India
|
||
</button>
|
||
<button onclick="window.setQuickSearch('find importer from uk')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
|
||
UK Importers
|
||
</button>
|
||
<button onclick="window.setQuickSearch('find gulfood data')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
|
||
Gulfood Exhibition
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Icon Cards (visible only on mobile) -->
|
||
<div class="lg:hidden mb-6">
|
||
<div class="mobile-stats-grid">
|
||
<div class="bg-white rounded-xl p-3 shadow-sm border border-gray-200 text-center">
|
||
<div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||
<i class="fas fa-search text-blue-600"></i>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mb-1">Total Searches</p>
|
||
<p class="text-sm font-bold text-gray-800" id="mobileSearches">0</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-3 shadow-sm border border-gray-200 text-center">
|
||
<div class="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||
<i class="fas fa-chart-line text-green-600"></i>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mb-1">Results Found</p>
|
||
<p class="text-sm font-bold text-gray-800" id="mobileResults">0</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-3 shadow-sm border border-gray-200 text-center">
|
||
<div class="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||
<i class="fas fa-globe text-purple-600"></i>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mb-1">Countries</p>
|
||
<p class="text-sm font-bold text-gray-800">150+</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-3 shadow-sm border border-gray-200 text-center">
|
||
<div class="w-8 h-8 bg-orange-100 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||
<i class="fas fa-bolt text-orange-600"></i>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mb-1">Response Time</p>
|
||
<p class="text-sm font-bold text-gray-800" id="mobileResponseTime">0ms</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Collapsible Execute Section -->
|
||
<div class="mt-4 bg-white rounded-xl shadow-sm border border-gray-200">
|
||
<button
|
||
id="mobileExecuteToggle"
|
||
onclick="toggleMobileExecute()"
|
||
class="w-full p-4 flex items-center justify-between text-left"
|
||
>
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||
<i class="fas fa-terminal text-purple-600 text-lg"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="font-semibold text-gray-800">Create / Update / Delete</h3>
|
||
<p class="text-xs text-gray-500">Execute CRUD operations</p>
|
||
</div>
|
||
</div>
|
||
<i class="fas fa-chevron-down text-gray-400 transition-transform" id="executeChevron"></i>
|
||
</button>
|
||
|
||
<div id="mobileExecuteContent" class="collapsible-content">
|
||
<div class="px-4 pb-4 slide-down">
|
||
<div class="flex flex-col gap-3">
|
||
<input
|
||
id="mobileCrudCommand"
|
||
type="text"
|
||
placeholder="e.g., Create importer Indi Tech from India email info@inditech.in"
|
||
class="w-full px-4 py-3 rounded-xl border border-gray-300 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none transition-all search-input text-sm"
|
||
/>
|
||
<button
|
||
onclick="window.callMobileCrudOperation()"
|
||
class="w-full bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-2"
|
||
id="mobileCrudBtn"
|
||
>
|
||
<i class="fas fa-terminal"></i>
|
||
<span>Execute</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Desktop CRUD Command Section (hidden on mobile) -->
|
||
<div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-200 p-4 lg:p-6 mb-6 fade-up">
|
||
<h2 class="text-lg font-bold text-gray-800 mb-3">🔧 Create / Update / Delete</h2>
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<input
|
||
id="crudCommand"
|
||
type="text"
|
||
placeholder="e.g., Create importer Indi Tech from India email info@inditech.in"
|
||
class="w-full px-4 py-3 rounded-xl border border-gray-300 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none transition-all search-input text-sm lg:text-base"
|
||
/>
|
||
<button
|
||
onclick="window.callCrudOperation()"
|
||
class="w-full sm:w-auto bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 min-w-[120px]"
|
||
id="crudBtn"
|
||
>
|
||
<i class="fas fa-terminal"></i>
|
||
<span>Execute</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CRUD Result Section -->
|
||
<div id="crudResultSection" class="hidden mb-6 fade-up">
|
||
<div class="bg-white rounded-xl border border-gray-200 p-4">
|
||
<h3 class="text-sm font-semibold text-gray-700 mb-3">📝 Operation Result</h3>
|
||
<div id="crudResultHtml" class="text-sm text-gray-800 leading-relaxed"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Desktop Stats Cards (hidden on mobile) -->
|
||
<div class="hidden lg:grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6 mb-6 lg:mb-8">
|
||
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm text-gray-500">Total Searches</p>
|
||
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="totalSearches">0</p>
|
||
</div>
|
||
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||
<i class="fas fa-search text-blue-600"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm text-gray-500">Results Found</p>
|
||
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="totalResults">0</p>
|
||
</div>
|
||
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||
<i class="fas fa-chart-line text-green-600"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm text-gray-500">Countries</p>
|
||
<p class="text-xl lg:text-2xl font-bold text-gray-800">150+</p>
|
||
</div>
|
||
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||
<i class="fas fa-globe text-purple-600"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm text-gray-500">Response Time</p>
|
||
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="responseTime">0ms</p>
|
||
</div>
|
||
<div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
|
||
<i class="fas fa-bolt text-orange-600"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Results Section -->
|
||
<div id="resultsSection" class="hidden">
|
||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 lg:mb-6">
|
||
<h2 class="text-xl lg:text-2xl font-bold text-gray-800 mb-2 sm:mb-0">Search Results</h2>
|
||
<div class="flex items-center gap-4">
|
||
<p id="resultsCount" class="text-sm text-white"></p>
|
||
<div class="flex items-center gap-2">
|
||
<button onclick="window.exportResults()" class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-2 rounded-lg transition-colors flex items-center gap-2">
|
||
<i class="fas fa-download text-xs"></i>
|
||
Export
|
||
</button>
|
||
<button onclick="window.shareResults()" class="text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-2 rounded-lg transition-colors flex items-center gap-2">
|
||
<i class="fas fa-share text-xs"></i>
|
||
Share
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="results" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 lg:gap-6"></div>
|
||
|
||
<!-- Load More Button -->
|
||
<div id="loadMoreSection" class="hidden mt-8 text-center">
|
||
<button onclick="window.loadMoreResults()" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-6 py-3 rounded-xl font-medium transition-colors">
|
||
Load More Results
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Empty State -->
|
||
<div id="emptyState" class="text-center py-12 lg:py-16">
|
||
<div class="max-w-md mx-auto">
|
||
<div class="w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||
<i class="fas fa-search text-blue-600 text-2xl"></i>
|
||
</div>
|
||
<h3 class="text-xl font-semibold text-gray-800 mb-3">Start Your Search</h3>
|
||
<p class="text-white mb-6">Enter your search query above to find exporters, importers, and trade data from our comprehensive database.</p>
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
|
||
<div class="bg-white p-4 rounded-lg border border-gray-200">
|
||
<h4 class="font-medium text-gray-800 mb-2">Find Exporters</h4>
|
||
<p class="text-gray-600">Search by country, product, or company name</p>
|
||
</div>
|
||
<div class="bg-white p-4 rounded-lg border border-gray-200">
|
||
<h4 class="font-medium text-gray-800 mb-2">Find Importers</h4>
|
||
<p class="text-gray-600">Discover potential buyers worldwide</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Declare all variables in global scope first
|
||
let totalSearchCount = 0;
|
||
let totalResultCount = 0;
|
||
let currentSearchData = [];
|
||
|
||
// Mobile execute toggle function
|
||
function toggleMobileExecute() {
|
||
const content = document.getElementById('mobileExecuteContent');
|
||
const chevron = document.getElementById('executeChevron');
|
||
|
||
if (content.classList.contains('open')) {
|
||
content.classList.remove('open');
|
||
chevron.style.transform = 'rotate(0deg)';
|
||
} else {
|
||
content.classList.add('open');
|
||
chevron.style.transform = 'rotate(180deg)';
|
||
}
|
||
}
|
||
|
||
// Enhanced search function with better error handling
|
||
async function callWorkflow() {
|
||
const query = document.getElementById('query').value.trim();
|
||
if (!query) {
|
||
alert('Please enter a search query');
|
||
return;
|
||
}
|
||
|
||
const searchBtn = document.getElementById('searchBtn');
|
||
const results = document.getElementById('results');
|
||
const resultsSection = document.getElementById('resultsSection');
|
||
const resultsCount = document.getElementById('resultsCount');
|
||
const emptyState = document.getElementById('emptyState');
|
||
|
||
// Show loading state and clear previous results
|
||
searchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Searching...';
|
||
searchBtn.disabled = true;
|
||
resultsSection.classList.add('hidden');
|
||
emptyState.classList.add('hidden');
|
||
|
||
// Show loading skeleton
|
||
results.innerHTML = Array(6).fill().map(() => `
|
||
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
|
||
<div class="animate-pulse">
|
||
<div class="flex items-center gap-3 mb-4">
|
||
<div class="w-8 h-6 bg-gray-200 rounded"></div>
|
||
<div class="h-6 bg-gray-200 rounded flex-1"></div>
|
||
</div>
|
||
<div class="space-y-3">
|
||
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
||
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
|
||
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
resultsSection.classList.remove('hidden');
|
||
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
console.log('Making API request with query:', query);
|
||
|
||
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Accept': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('Response status:', response.status);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('Full API Response:', data);
|
||
|
||
const responseTime = Date.now() - startTime;
|
||
|
||
// Process the API response
|
||
let cards = '';
|
||
let resultCount = 0;
|
||
|
||
// Handle the API response format (your API returns an array)
|
||
if (data && Array.isArray(data) && data.length > 0) {
|
||
const apiData = data[0];
|
||
console.log('Processing API data:', apiData);
|
||
|
||
if (apiData.success && apiData.content) {
|
||
// Try originalMessage first, then fallback to message
|
||
const messageContent = apiData.content.originalMessage || apiData.content.message;
|
||
console.log('Message content:', messageContent);
|
||
|
||
if (messageContent) {
|
||
const companies = parseApiResponse(messageContent);
|
||
resultCount = companies.length;
|
||
currentSearchData = companies;
|
||
|
||
companies.forEach((company, index) => {
|
||
cards += generateCompanyCardFromData(company, index + 1);
|
||
});
|
||
} else {
|
||
console.log('No message content found in API response');
|
||
}
|
||
} else {
|
||
console.log('API response indicates failure or no content');
|
||
}
|
||
} else if (data && data.success && data.content) {
|
||
// Handle single object response (fallback)
|
||
const messageContent = data.content.originalMessage || data.content.message;
|
||
|
||
if (messageContent) {
|
||
const companies = parseApiResponse(messageContent);
|
||
resultCount = companies.length;
|
||
currentSearchData = companies;
|
||
|
||
companies.forEach((company, index) => {
|
||
cards += generateCompanyCardFromData(company, index + 1);
|
||
});
|
||
}
|
||
} else if (data && data.output) {
|
||
// Fallback for older API format
|
||
const companies = parseApiResponse(data.output);
|
||
resultCount = companies.length;
|
||
currentSearchData = companies;
|
||
|
||
companies.forEach((company, index) => {
|
||
cards += generateCompanyCardFromData(company, index + 1);
|
||
});
|
||
} else {
|
||
console.log('Unexpected API response format:', data);
|
||
}
|
||
|
||
// Update UI
|
||
updateStats(resultCount, responseTime);
|
||
resultsCount.textContent = `Found ${resultCount} ${data[0]?.tableType || 'company'}(s) matching "${query}"`;
|
||
|
||
if (cards) {
|
||
results.innerHTML = cards;
|
||
resultsSection.classList.remove('hidden');
|
||
|
||
// Show load more if there are many results
|
||
if (resultCount > 20) {
|
||
document.getElementById('loadMoreSection').classList.remove('hidden');
|
||
}
|
||
} else {
|
||
results.innerHTML = `
|
||
<div class="col-span-full bg-white rounded-xl p-8 text-center">
|
||
<div class="text-4xl mb-3">😔</div>
|
||
<p class="text-gray-600 font-semibold">No results found</p>
|
||
<p class="text-gray-500 text-sm mt-2">Try a different search term</p>
|
||
</div>`;
|
||
resultsSection.classList.remove('hidden');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Search error:', error);
|
||
|
||
let errorMessage = 'Search failed';
|
||
let errorDetail = 'Please try again or contact support';
|
||
|
||
if (error.message.includes('401')) {
|
||
errorMessage = 'Authentication Error';
|
||
errorDetail = 'The API key or endpoint URL needs to be updated. Please contact the administrator.';
|
||
} else if (error.message.includes('404')) {
|
||
errorMessage = 'Service Not Found';
|
||
errorDetail = 'The search service is currently unavailable.';
|
||
} else if (error.message.includes('Failed to fetch')) {
|
||
errorMessage = 'Connection Error';
|
||
errorDetail = 'Please check your internet connection and try again.';
|
||
}
|
||
|
||
results.innerHTML = `
|
||
<div class="col-span-full bg-red-50 border border-red-200 rounded-xl p-6 text-center">
|
||
<div class="text-red-600 text-2xl mb-3">⚠️</div>
|
||
<p class="text-red-700 font-semibold">${errorMessage}</p>
|
||
<p class="text-red-600 text-sm mt-2">${errorDetail}</p>
|
||
<div class="mt-4 p-3 bg-red-100 rounded-lg text-left">
|
||
<p class="text-xs text-red-700 font-mono">${error.message}</p>
|
||
</div>
|
||
<button onclick="location.reload()" class="mt-4 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-sm transition-colors">
|
||
Refresh Page
|
||
</button>
|
||
</div>`;
|
||
resultsSection.classList.remove('hidden');
|
||
} finally {
|
||
searchBtn.innerHTML = '<i class="fas fa-search"></i> <span>Search</span>';
|
||
searchBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Enhanced parsing function - handles your exact API format
|
||
function parseApiResponse(responseText) {
|
||
let companies = [];
|
||
|
||
try {
|
||
// Clean up the text and remove markdown/HTML formatting
|
||
let cleanText = responseText
|
||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||
.replace(/\*\*/g, '') // Remove bold markdown
|
||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert markdown links to plain text
|
||
.replace(/`([^`]+)`/g, '$1') // Remove code formatting
|
||
.replace(/🔍.*?🚢/g, '') // Remove search header with ship emoji
|
||
.replace(/🔍.*?📦/g, '') // Remove search header with package emoji
|
||
.replace(/📊.*?────+/g, '') // Remove stats line
|
||
.replace(/💡.*$/gm, '') // Remove suggestions
|
||
.trim();
|
||
|
||
console.log('Cleaned text:', cleanText.substring(0, 500)); // Debug log
|
||
|
||
// Split by numbered company entries (1. 2. 3. etc.)
|
||
const splitPattern = /\b(\d+)\.\s+/;
|
||
const parts = cleanText.split(splitPattern);
|
||
|
||
// Extract company blocks (every 3rd element after split)
|
||
const companyBlocks = [];
|
||
for (let i = 2; i < parts.length; i += 2) {
|
||
if (parts[i] && parts[i].trim()) {
|
||
companyBlocks.push(parts[i].trim());
|
||
}
|
||
}
|
||
|
||
console.log('Company blocks found:', companyBlocks.length); // Debug log
|
||
|
||
companyBlocks.forEach((block, index) => {
|
||
if (!block || block.trim().length === 0) return;
|
||
|
||
console.log(`Processing block ${index + 1}:`, block.substring(0, 200)); // Debug log
|
||
|
||
const lines = block.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
||
|
||
// Extract company name (first line)
|
||
const companyName = lines[0] || '';
|
||
|
||
// Extract phone numbers (look for ☎️ emoji followed by numbers)
|
||
const phoneMatches = block.match(/☎️\s*([\d\s\-\(\)\.E\+]+)/g) || [];
|
||
const phone = phoneMatches[0] ? phoneMatches[0].replace(/☎️\s*/, '').trim() : '';
|
||
|
||
// Extract emails (look for 📧 emoji followed by email)
|
||
const emailMatches = block.match(/📧\s*([^\s\n]+@[^\s\n]+)/g) || [];
|
||
const email = emailMatches[0] ? emailMatches[0].replace(/📧\s*/, '').trim() : '';
|
||
|
||
// Extract websites (look for 🔗 emoji followed by URL)
|
||
const websiteMatches = block.match(/🔗\s*(https?:\/\/[^\s\n]+)/g) || [];
|
||
const website = websiteMatches[0] ? websiteMatches[0].replace(/🔗\s*/, '').replace(/\/$/, '') : '';
|
||
|
||
// Extract location (look for 📍 emoji followed by location)
|
||
const locationMatches = block.match(/📍\s*([^\n🔗📧☎️📦🏷️]+)/g) || [];
|
||
let location = 'Unknown';
|
||
|
||
if (locationMatches.length > 0) {
|
||
location = locationMatches[0].replace(/📍\s*/, '').trim();
|
||
}
|
||
|
||
// Fallback location detection
|
||
if (location === 'Unknown' || location.length < 2) {
|
||
if (block.includes('United Kingdom')) location = 'United Kingdom';
|
||
else if (block.includes('India')) location = 'India';
|
||
else if (block.includes('USA') || block.includes('United States')) location = 'USA';
|
||
else if (block.includes('UAE') || block.includes('Dubai')) location = 'UAE';
|
||
else if (block.includes('China')) location = 'China';
|
||
}
|
||
|
||
// Extract products (look for 📦 emoji)
|
||
const productMatches = block.match(/📦\s*([^\n🏷️]+)/g) || [];
|
||
const products = [];
|
||
|
||
productMatches.forEach(match => {
|
||
const productText = match.replace(/📦\s*/, '').trim();
|
||
const productList = productText.split(/[,&]/).map(p => p.trim()).filter(p => p.length > 0);
|
||
products.push(...productList);
|
||
});
|
||
|
||
// Extract brands (look for 🏷️ emoji)
|
||
const brandMatches = block.match(/🏷️\s*([^\n]+)/g) || [];
|
||
const brands = [];
|
||
|
||
brandMatches.forEach(match => {
|
||
const brandText = match.replace(/🏷️\s*/, '').trim();
|
||
const brandList = brandText.split(/[,&]/).map(b => b.trim()).filter(b => b.length > 0);
|
||
brands.push(...brandList);
|
||
});
|
||
|
||
// Combine products and brands
|
||
const allProducts = [...products, ...brands].slice(0, 8); // Limit to 8 items
|
||
|
||
if (companyName && companyName.length > 0) {
|
||
companies.push({
|
||
name: companyName,
|
||
phone: phone,
|
||
email: email,
|
||
website: website,
|
||
address: location,
|
||
products: allProducts
|
||
});
|
||
}
|
||
});
|
||
|
||
console.log('Final parsed companies:', companies); // Debug log
|
||
|
||
} catch (error) {
|
||
console.error('Error parsing API response:', error);
|
||
console.log('Raw response text:', responseText);
|
||
}
|
||
|
||
return companies;
|
||
}
|
||
|
||
// Enhanced card generation
|
||
function generateCompanyCardFromData(company, index) {
|
||
const location = company.address?.toLowerCase() || '';
|
||
let flagHtml = '';
|
||
|
||
if (location.includes('india')) {
|
||
flagHtml = '<div class="w-8 h-6 bg-gradient-to-b from-orange-400 via-white to-green-500 rounded border border-gray-300 relative"><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-4 h-4 border-2 border-blue-800 rounded-full"></div></div>';
|
||
} else if (location.includes('united kingdom') || location.includes('uk')) {
|
||
flagHtml = '<div class="w-8 h-6 bg-blue-800 rounded border border-gray-300 relative"><div class="absolute inset-0 bg-gradient-to-br from-red-600 via-white to-red-600" style="clip-path: polygon(0 0, 100% 0, 0 100%)"></div><div class="absolute inset-0 bg-gradient-to-bl from-red-600 via-white to-red-600" style="clip-path: polygon(100% 0, 100% 100%, 0 100%)"></div><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-full bg-red-600"></div><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full h-1 bg-red-600"></div></div>';
|
||
} else if (location.includes('usa') || location.includes('united states')) {
|
||
flagHtml = '<div class="w-8 h-6 bg-red-600 rounded border border-gray-300 relative"><div class="absolute top-0 left-0 w-3 h-3 bg-blue-800"></div><div class="absolute top-1 left-0 w-full h-0.5 bg-white"></div><div class="absolute top-2 left-0 w-full h-0.5 bg-white"></div></div>';
|
||
} else if (location.includes('uae') || location.includes('dubai')) {
|
||
flagHtml = '<div class="w-8 h-6 bg-gradient-to-b from-green-600 via-white via-black to-red-600 rounded border border-gray-300"></div>';
|
||
} else if (location.includes('china')) {
|
||
flagHtml = '<div class="w-8 h-6 bg-red-600 rounded border border-gray-300 relative"><div class="absolute top-1 left-1 text-yellow-400 text-xs">⭐</div></div>';
|
||
} else {
|
||
flagHtml = '<div class="w-8 h-6 bg-gray-300 rounded border border-gray-300 flex items-center justify-center text-xs">🌍</div>';
|
||
}
|
||
|
||
return `
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 card-hover">
|
||
<div class="p-4 lg:p-6">
|
||
<div class="flex items-center gap-3 mb-4">
|
||
${flagHtml}
|
||
<h3 class="text-base lg:text-lg font-bold text-blue-700 hover:text-blue-800 cursor-pointer line-clamp-2">${company.name}</h3>
|
||
</div>
|
||
|
||
<div class="space-y-3 text-sm text-gray-700">
|
||
${company.phone ? `
|
||
<div class="flex items-center gap-2">
|
||
<i class="fas fa-phone text-green-600 w-4"></i>
|
||
<span class="break-all">${company.phone}</span>
|
||
</div>
|
||
` : ''}
|
||
|
||
${company.email ? `
|
||
<div class="flex items-center gap-2">
|
||
<i class="fas fa-envelope text-blue-600 w-4"></i>
|
||
<a href="mailto:${company.email}" class="text-blue-600 hover:text-blue-800 break-all">${company.email}</a>
|
||
</div>
|
||
` : ''}
|
||
|
||
${company.website ? `
|
||
<div class="flex items-center gap-2">
|
||
<i class="fas fa-globe text-purple-600 w-4"></i>
|
||
<a href="${company.website}" class="text-blue-600 hover:text-blue-800 underline break-all" target="_blank" rel="noopener">${company.website}</a>
|
||
</div>
|
||
` : ''}
|
||
|
||
${company.address ? `
|
||
<div class="flex items-start gap-2">
|
||
<i class="fas fa-map-marker-alt text-red-600 w-4 mt-0.5"></i>
|
||
<span class="text-gray-600">${company.address}</span>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
|
||
${company.products && company.products.length > 0 ? `
|
||
<div class="mt-4 flex flex-wrap gap-2">
|
||
${company.products.slice(0, 4).map(product => `
|
||
<span class="bg-blue-50 text-blue-700 px-2 py-1 rounded-full text-xs font-medium">${product}</span>
|
||
`).join('')}
|
||
${company.products.length > 4 ? `
|
||
<span class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs font-medium">+${company.products.length - 4} more</span>
|
||
` : ''}
|
||
</div>
|
||
` : ''}
|
||
|
||
<div class="mt-4 pt-4 border-t border-gray-100 flex gap-2">
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Update stats for both desktop and mobile
|
||
function updateStats(resultCount, responseTime) {
|
||
totalSearchCount++;
|
||
totalResultCount += resultCount;
|
||
|
||
// Desktop stats
|
||
document.getElementById('totalSearches').textContent = totalSearchCount;
|
||
document.getElementById('totalResults').textContent = totalResultCount;
|
||
document.getElementById('responseTime').textContent = responseTime + 'ms';
|
||
|
||
// Mobile stats
|
||
document.getElementById('mobileSearches').textContent = totalSearchCount;
|
||
document.getElementById('mobileResults').textContent = totalResultCount;
|
||
document.getElementById('mobileResponseTime').textContent = responseTime + 'ms';
|
||
|
||
updateLastUpdated();
|
||
}
|
||
|
||
// Update last updated time
|
||
function updateLastUpdated() {
|
||
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
|
||
}
|
||
|
||
// Quick search functionality
|
||
function setQuickSearch(query) {
|
||
document.getElementById('query').value = query;
|
||
window.callWorkflow();
|
||
}
|
||
|
||
// Export functionality
|
||
function exportResults() {
|
||
if (currentSearchData.length === 0) return;
|
||
|
||
const headers = ['Company Name', 'Phone', 'Email', 'Website', 'Address', 'Products'];
|
||
const csvContent = [
|
||
headers.join(','),
|
||
...currentSearchData.map(company => [
|
||
`"${company.name}"`,
|
||
`"${company.phone || ''}"`,
|
||
`"${company.email || ''}"`,
|
||
`"${company.website || ''}"`,
|
||
`"${company.address || ''}"`,
|
||
`"${(company.products || []).join('; ')}"`
|
||
].join(','))
|
||
].join('\n');
|
||
|
||
const blob = new Blob([csvContent], { type: 'text/csv' });
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `search_results_${new Date().toISOString().split('T')[0]}.csv`;
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
}
|
||
|
||
// Share functionality
|
||
function shareResults() {
|
||
if (navigator.share && currentSearchData.length > 0) {
|
||
navigator.share({
|
||
title: 'Trade Search Results',
|
||
text: `Found ${currentSearchData.length} companies in search results`,
|
||
url: window.location.href
|
||
});
|
||
} else {
|
||
navigator.clipboard.writeText(window.location.href).then(() => {
|
||
alert('Link copied to clipboard!');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Load more functionality
|
||
function loadMoreResults() {
|
||
document.getElementById('loadMoreSection').classList.add('hidden');
|
||
}
|
||
|
||
// Additional functionality
|
||
function viewCompanyDetails(companyName) {
|
||
alert(`Viewing details for: ${companyName}`);
|
||
}
|
||
|
||
function saveCompany(companyName) {
|
||
alert(`Saved: ${companyName}`);
|
||
}
|
||
|
||
function shareCompany(companyName) {
|
||
if (navigator.share) {
|
||
navigator.share({
|
||
title: companyName,
|
||
text: `Check out this company: ${companyName}`,
|
||
url: window.location.href
|
||
});
|
||
} else {
|
||
navigator.clipboard.writeText(`${companyName} - ${window.location.href}`).then(() => {
|
||
alert('Company info copied to clipboard!');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Desktop CRUD operation
|
||
async function callCrudOperation() {
|
||
const queryInput = document.getElementById('crudCommand');
|
||
const query = queryInput.value.trim();
|
||
if (!query) {
|
||
alert('Please enter a CRUD command');
|
||
return;
|
||
}
|
||
|
||
const crudBtn = document.getElementById('crudBtn');
|
||
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
|
||
crudBtn.disabled = true;
|
||
|
||
try {
|
||
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
|
||
method: 'GET',
|
||
headers: { 'Accept': 'application/json' }
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
|
||
const clean = raw
|
||
.replace(/<[^>]*>/g, '')
|
||
.replace(/\*\*/g, '')
|
||
.replace(/Complete dataset returned.*included/gi, '')
|
||
.trim();
|
||
|
||
const operation = result?.operation || 'info';
|
||
const type = result?.content?.messageType || 'success';
|
||
const success = result?.success;
|
||
|
||
// Determine toast style
|
||
let bg = 'bg-emerald-100', border = 'border-emerald-300', text = 'text-emerald-900', icon = 'fa-check-circle', title = 'Success!';
|
||
|
||
if (/cannot create/i.test(clean)) {
|
||
bg = 'bg-yellow-50'; border = 'border-yellow-200'; text = 'text-yellow-800'; icon = 'fa-clone'; title = 'Duplicate Found';
|
||
} else if (!success && (operation === 'delete' || type === 'error')) {
|
||
bg = 'bg-red-100'; border = 'border-red-300'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
|
||
} else if (operation === 'delete' && success) {
|
||
bg = 'bg-rose-100'; border = 'border-rose-300'; text = 'text-rose-800'; icon = 'fa-trash-alt'; title = 'Deleted!';
|
||
} else if (operation === 'update') {
|
||
bg = 'bg-amber-100'; border = 'border-amber-300'; text = 'text-amber-900'; icon = 'fa-pen'; title = 'Updated!';
|
||
} else if (operation === 'read') {
|
||
bg = 'bg-blue-100'; border = 'border-blue-300'; text = 'text-blue-900'; icon = 'fa-info-circle'; title = 'Fetched!';
|
||
}
|
||
|
||
// Build toast element
|
||
const toast = document.createElement('div');
|
||
toast.className = `fade-up fixed bottom-6 right-6 max-w-sm w-full z-50 shadow-xl rounded-xl border px-4 py-3 ${bg} ${border} ${text} animate-slide-up`;
|
||
toast.innerHTML = `
|
||
<div class="flex justify-between items-start gap-3">
|
||
<div class="flex items-start gap-2">
|
||
<i class="fas ${icon} mt-1 text-lg"></i>
|
||
<div>
|
||
<div class="font-semibold">${title}</div>
|
||
<div class="mt-1 space-y-1 text-sm leading-snug">${clean.split('\n').map(line => `<div class="animate-fade">${line}</div>`).join('')}</div>
|
||
</div>
|
||
</div>
|
||
<button onclick="this.parentElement.parentElement.remove()" class="text-xl font-bold hover:text-black/50 transition">×</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(toast);
|
||
queryInput.value = '';
|
||
setTimeout(() => toast.remove(), 8000);
|
||
} catch (error) {
|
||
console.error('CRUD error:', error);
|
||
alert('❌ ' + error.message);
|
||
} finally {
|
||
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
|
||
crudBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Mobile CRUD operation
|
||
async function callMobileCrudOperation() {
|
||
const queryInput = document.getElementById('mobileCrudCommand');
|
||
const query = queryInput.value.trim();
|
||
if (!query) {
|
||
alert('Please enter a CRUD command');
|
||
return;
|
||
}
|
||
|
||
const crudBtn = document.getElementById('mobileCrudBtn');
|
||
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
|
||
crudBtn.disabled = true;
|
||
|
||
try {
|
||
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
|
||
method: 'GET',
|
||
headers: { 'Accept': 'application/json' }
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
|
||
const clean = raw
|
||
.replace(/<[^>]*>/g, '')
|
||
.replace(/\*\*/g, '')
|
||
.replace(/Complete dataset returned.*included/gi, '')
|
||
.trim();
|
||
|
||
const operation = result?.operation || 'info';
|
||
const type = result?.content?.messageType || 'success';
|
||
const success = result?.success;
|
||
|
||
// Determine toast style
|
||
let bg = 'bg-emerald-100', border = 'border-emerald-300', text = 'text-emerald-900', icon = 'fa-check-circle', title = 'Success!';
|
||
|
||
if (/cannot create/i.test(clean)) {
|
||
bg = 'bg-yellow-50'; border = 'border-yellow-200'; text = 'text-yellow-800'; icon = 'fa-clone'; title = 'Duplicate Found';
|
||
} else if (!success && (operation === 'delete' || type === 'error')) {
|
||
bg = 'bg-red-100'; border = 'border-red-300'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
|
||
} else if (operation === 'delete' && success) {
|
||
bg = 'bg-rose-100'; border = 'border-rose-300'; text = 'text-rose-800'; icon = 'fa-trash-alt'; title = 'Deleted!';
|
||
} else if (operation === 'update') {
|
||
bg = 'bg-amber-100'; border = 'border-amber-300'; text = 'text-amber-900'; icon = 'fa-pen'; title = 'Updated!';
|
||
} else if (operation === 'read') {
|
||
bg = 'bg-blue-100'; border = 'border-blue-300'; text = 'text-blue-900'; icon = 'fa-info-circle'; title = 'Fetched!';
|
||
}
|
||
|
||
// Build toast element
|
||
const toast = document.createElement('div');
|
||
toast.className = `fade-up fixed bottom-6 left-4 right-4 max-w-sm mx-auto z-50 shadow-xl rounded-xl border px-4 py-3 ${bg} ${border} ${text} animate-slide-up`;
|
||
toast.innerHTML = `
|
||
<div class="flex justify-between items-start gap-3">
|
||
<div class="flex items-start gap-2">
|
||
<i class="fas ${icon} mt-1 text-lg"></i>
|
||
<div>
|
||
<div class="font-semibold">${title}</div>
|
||
<div class="mt-1 space-y-1 text-sm leading-snug">${clean.split('\n').map(line => `<div class="animate-fade">${line}</div>`).join('')}</div>
|
||
</div>
|
||
</div>
|
||
<button onclick="this.parentElement.parentElement.remove()" class="text-xl font-bold hover:text-black/50 transition">×</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(toast);
|
||
queryInput.value = '';
|
||
|
||
// Auto-collapse the mobile execute section after successful operation
|
||
const content = document.getElementById('mobileExecuteContent');
|
||
const chevron = document.getElementById('executeChevron');
|
||
content.classList.remove('open');
|
||
chevron.style.transform = 'rotate(0deg)';
|
||
|
||
setTimeout(() => toast.remove(), 8000);
|
||
} catch (error) {
|
||
console.error('Mobile CRUD error:', error);
|
||
alert('❌ ' + error.message);
|
||
} finally {
|
||
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
|
||
crudBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Make functions available globally
|
||
window.callWorkflow = callWorkflow;
|
||
window.setQuickSearch = setQuickSearch;
|
||
window.exportResults = exportResults;
|
||
window.shareResults = shareResults;
|
||
window.loadMoreResults = loadMoreResults;
|
||
window.saveCompany = saveCompany;
|
||
window.callCrudOperation = callCrudOperation;
|
||
window.callMobileCrudOperation = callMobileCrudOperation;
|
||
window.shareCompany = shareCompany;
|
||
window.toggleMobileExecute = toggleMobileExecute;
|
||
|
||
// Initialize when DOM is loaded
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Mobile menu functionality
|
||
document.getElementById('mobileMenuBtn').addEventListener('click', function() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
const overlay = document.getElementById('sidebarOverlay');
|
||
|
||
sidebar.classList.toggle('hidden');
|
||
overlay.classList.toggle('hidden');
|
||
});
|
||
|
||
document.getElementById('sidebarOverlay').addEventListener('click', function() {
|
||
document.getElementById('sidebar').classList.add('hidden');
|
||
document.getElementById('sidebarOverlay').classList.add('hidden');
|
||
});
|
||
|
||
// Enter key support for search
|
||
document.getElementById('query').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
window.callWorkflow();
|
||
}
|
||
});
|
||
|
||
// Enter key support for mobile CRUD
|
||
document.getElementById('mobileCrudCommand')?.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
window.callMobileCrudOperation();
|
||
}
|
||
});
|
||
|
||
// Enter key support for desktop CRUD
|
||
document.getElementById('crudCommand')?.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
window.callCrudOperation();
|
||
}
|
||
});
|
||
|
||
// Initialize
|
||
updateLastUpdated();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|