supa-tid/index.html
quantumtaskai 50c4384add
first commit
supabase
2025-06-15 22:28:57 +05:30

1578 lines
59 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Trade Intelligence - AI Trade Management</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
// Configure Tailwind for proper styling
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Plus Jakarta Sans', 'sans-serif']
}
}
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="./config.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
margin: 0;
padding: 0;
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
</style>
</head>
<body>
<!-- Navigation Header -->
<nav class="bg-white/10 backdrop-blur-md border-b border-white/20 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-blue-600 rounded-xl flex items-center justify-center shadow-lg">
<i class="fas fa-ship text-white text-lg"></i>
</div>
<div>
<h1 class="text-lg font-bold text-white">Trade Intelligence</h1>
<p class="text-sm text-white">Victor AI Agent</p>
</div>
</div>
<div class="hidden md:flex items-center gap-6">
<div class="text-right">
<p class="text-sm text-white">Last updated</p>
<p class="font-semibold text-white">6:08:40 AM</p>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span class="text-sm text-green-600 font-medium text-white">Online</span>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 min-h-screen">
<!-- Header Section -->
<div class="text-center mb-8">
<h1 class="text-3xl lg:text-4xl font-bold text-white mb-4">AI Trade Intelligence</h1>
</div>
<!-- Enhanced Search Command Center -->
<div class="bg-white rounded-2xl p-6 mb-8 shadow-lg border border-gray-200">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
<i class="fas fa-brain text-white"></i>
</div>
<h2 class="text-xl font-bold text-gray-800">Trade AI Command</h2>
</div>
<!-- Main Search Bar -->
<div class="flex flex-col sm:flex-row gap-3 mb-4">
<div class="flex-1">
<input
id="searchInput"
type="text"
placeholder="find buyer from uk"
class="w-full px-3 py-2.5 sm:px-4 sm:py-3 rounded-xl border border-gray-300 bg-white text-gray-800 placeholder-gray-500 focus:border-blue-400 focus:ring-2 focus:ring-blue-400/50 outline-none transition-all text-sm sm:text-base"
/>
</div>
<div class="flex gap-2 sm:gap-3">
<button
onclick="performSearch()"
class="flex-1 sm:w-auto bg-blue-600 hover:bg-blue-700 text-white px-3 py-2.5 sm:px-6 sm:py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-1 sm:gap-2 min-w-[90px] sm:min-w-[120px] text-sm sm:text-base"
>
<i class="fas fa-search text-xs sm:text-sm"></i>
<span>Search</span>
</button>
<button
onclick="openCreateModal()"
class="flex-shrink-0 bg-green-600 hover:bg-green-700 text-white px-3 py-2.5 sm:px-4 sm:py-2.5 rounded-xl font-medium transition-all flex items-center justify-center gap-1 text-sm sm:text-base"
>
<i class="fas fa-plus text-xs sm:text-sm"></i>
<span class="hidden xs:inline">Create</span>
</button>
</div>
</div>
<!-- Quick Actions -->
<div class="mb-4">
<div class="flex flex-wrap gap-2">
<span class="text-xs sm:text-sm text-gray-500 mr-1 sm:mr-2 mb-1">Quick actions:</span>
<button onclick="quickSearch('all buyers')" class="text-xs sm:text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 sm:px-3 sm:py-1.5 rounded-full transition-colors min-h-[28px] sm:min-h-[32px]">
All Buyers
</button>
<button onclick="quickSearch('all sellers')" class="text-xs sm:text-sm bg-green-100 hover:bg-green-200 text-green-700 px-2 py-1 sm:px-3 sm:py-1.5 rounded-full transition-colors min-h-[28px] sm:min-h-[32px]">
All Sellers
</button>
<button onclick="quickSearch('gulfood data')" class="text-xs sm:text-sm bg-orange-100 hover:bg-orange-200 text-orange-700 px-2 py-1 sm:px-3 sm:py-1.5 rounded-full transition-colors min-h-[28px] sm:min-h-[32px]">
Gulfood Data
</button>
<button onclick="clearResults()" class="text-xs sm:text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-1 sm:px-3 sm:py-1.5 rounded-full transition-colors min-h-[28px] sm:min-h-[32px]">
Clear Results
</button>
</div>
</div>
<!-- Advanced Search Panel -->
<div class="border-t border-gray-200 pt-4">
<div class="flex items-center gap-2 mb-4">
<i class="fas fa-sliders-h text-gray-600"></i>
<h3 class="text-sm font-semibold text-gray-700">Product Mapping</h3>
<button onclick="toggleAdvancedPanel()" class="sm:hidden ml-auto p-1 text-gray-500 hover:text-gray-700">
<i id="advancedToggleIcon" class="fas fa-chevron-down text-xs"></i>
</button>
</div>
<div id="advancedContent" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-4">
<!-- Search Type -->
<div>
<label class="block text-xs sm:text-sm font-medium text-gray-700 mb-1 sm:mb-2">Search Type</label>
<select id="searchType" class="w-full px-2 py-2 sm:px-3 sm:py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm sm:text-base">
<option value="all">All Companies</option>
<option value="buyer">Buyers Only</option>
<option value="seller">Sellers Only</option>
</select>
</div>
<!-- Product/Brand -->
<div>
<label class="block text-xs sm:text-sm font-medium text-gray-700 mb-1 sm:mb-2">Product/Brand</label>
<input
id="productSearch"
type="text"
placeholder="FMCG"
class="w-full px-2 py-2 sm:px-3 sm:py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm sm:text-base"
/>
</div>
<!-- Country -->
<div class="relative sm:col-span-2 lg:col-span-1">
<label class="block text-xs sm:text-sm font-medium text-gray-700 mb-1 sm:mb-2">Country</label>
<input
id="countryFilter"
type="text"
placeholder="Type country name..."
class="w-full px-2 py-2 sm:px-3 sm:py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm sm:text-base"
oninput="showCountrySuggestions()"
onfocus="showCountrySuggestions()"
onblur="hideCountrySuggestions()"
/>
<div id="countrySuggestions" class="absolute z-10 w-full bg-white border border-gray-300 rounded-lg mt-1 max-h-48 overflow-y-auto hidden shadow-lg">
<!-- Country suggestions will be populated here -->
</div>
</div>
</div>
<!-- Advanced Search Actions -->
<div id="advancedActions" class="flex gap-2 sm:gap-3">
<button
onclick="performProductMatching()"
class="flex-auto bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 sm:px-6 sm:py-2.5 rounded-lg font-semibold transition-all flex items-center justify-center gap-2 text-sm sm:text-base"
>
<i class="fas fa-link text-xs sm:text-sm"></i>
<span>Product Mapping</span>
</button>
<button
onclick="resetAdvancedFilters()"
class="flex-shrink-0 bg-gray-500 hover:bg-gray-600 text-white px-3 py-2 sm:px-4 sm:py-2.5 rounded-lg font-medium transition-all flex items-center justify-center gap-1 text-sm sm:text-base"
>
<i class="fas fa-undo text-xs sm:text-sm"></i>
<span class="hidden xs:inline">Reset</span>
</button>
</div>
</div>
</div>
<!-- Product Matching Results Section -->
<div id="productMatchingResults" class="hidden bg-white rounded-2xl p-6 shadow-lg border border-gray-200 mb-8">
<div class="flex items-center gap-3 mb-6">
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<i class="fas fa-link text-purple-600"></i>
</div>
<div class="flex-1">
<h3 class="text-base sm:text-lg font-bold text-gray-800">Product Mapping Results</h3>
<p class="text-sm sm:text-base text-gray-600" id="productMatchingSubtitle">Find buyers and sellers for your products</p>
</div>
<button onclick="closeProductMapping()" class="text-gray-400 hover:text-gray-600 p-2">
<i class="fas fa-times"></i>
</button>
</div>
<!-- Product Info Header -->
<div class="bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg p-4 mb-6" id="productInfoHeader">
<!-- Product details will be populated here -->
</div>
<!-- Buyers and Sellers Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Buyers Section -->
<div class="bg-blue-50 rounded-lg p-4">
<div class="flex items-center gap-2 mb-4">
<i class="fas fa-shopping-cart text-blue-600"></i>
<h4 class="text-base sm:text-lg font-semibold text-blue-800" id="buyersTitle">Buyers (0)</h4>
</div>
<div class="space-y-0 max-h-96 overflow-y-auto custom-scrollbar" id="buyersList">
<!-- Buyers will be populated here -->
</div>
</div>
<!-- Sellers Section -->
<div class="bg-green-50 rounded-lg p-4">
<div class="flex items-center gap-2 mb-4">
<i class="fas fa-store text-green-600"></i>
<h4 class="text-base sm:text-lg font-semibold text-green-800" id="sellersTitle">Sellers (0)</h4>
</div>
<div class="space-y-0 max-h-96 overflow-y-auto custom-scrollbar" id="sellersList">
<!-- Sellers will be populated here -->
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div class="mb-8">
<!-- Results will show here -->
<div id="resultsSection" class="hidden">
<div class="bg-white rounded-2xl p-4 shadow-lg border border-gray-200">
<div class="mb-4">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-bold text-gray-800">Results</h3>
<div class="flex items-center gap-2 flex-shrink-0">
<select id="sortBy" onchange="sortResults()" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:border-blue-500">
<option value="default">Default</option>
<option value="name">By Name</option>
<option value="country">By Country</option>
<option value="products">By Products</option>
</select>
<button onclick="exportResults()" class="text-xs bg-green-100 hover:bg-green-200 text-green-700 px-2 py-1 sm:px-3 sm:py-1 rounded-lg transition-colors flex items-center gap-1 flex-shrink-0">
<i class="fas fa-download text-xs"></i>
<span class="hidden sm:inline">Export</span>
</button>
</div>
</div>
<p id="resultsCount" class="text-sm text-gray-600"></p>
</div>
<div id="resultsContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 lg:gap-6 custom-scrollbar max-h-[600px] overflow-y-auto"></div>
</div>
</div>
<!-- Initial state -->
<div id="initialState" class="bg-white rounded-2xl p-6 shadow-lg border border-gray-200">
<div class="text-center py-12 text-gray-500">
<svg class="w-16 h-16 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<p class="text-lg">Enter a query above to search your database</p>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-2xl p-4 max-w-sm w-full mx-4 shadow-2xl max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-800">Edit Record</h3>
<button onclick="closeModal('editModal')" class="text-gray-400 hover:text-gray-600 p-1">
<i class="fas fa-times"></i>
</button>
</div>
<form id="modalEditForm" class="space-y-3"></form>
<div class="flex gap-2 mt-4">
<button onclick="closeModal('editModal')" class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 py-2 px-3 text-sm rounded-lg font-medium transition-colors">
Cancel
</button>
<button onclick="submitModalEdit()" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 px-3 text-sm rounded-lg font-medium transition-colors">
Save
</button>
</div>
</div>
</div>
<!-- Create Modal -->
<div id="createModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-2xl p-4 max-w-sm w-full mx-4 shadow-2xl max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-800">Create Company</h3>
<button onclick="closeCreateModal()" class="text-gray-400 hover:text-gray-600 p-1">
<i class="fas fa-times"></i>
</button>
</div>
<form id="createForm" onsubmit="handleCreateSubmit(event)">
<div class="space-y-3">
<div>
<label class="block text-xs font-medium text-gray-700 mb-1">Table</label>
<select id="createTable" onchange="updateCreateFormFields()" class="w-full px-2 py-2 text-sm border border-gray-300 rounded-lg focus:border-blue-500">
<option value="buyer">Buyers</option>
<option value="seller">Sellers</option>
<option value="gulfood">Gulfood</option>
</select>
</div>
<div id="createFormFields">
<!-- Dynamic fields will be populated here -->
</div>
</div>
<div class="flex gap-2 mt-4">
<button type="button" onclick="closeCreateModal()" class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 py-2 px-3 text-sm rounded-lg font-medium transition-colors">
Cancel
</button>
<button type="submit" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-3 text-sm rounded-lg font-medium transition-colors">
Create
</button>
</div>
</form>
</div>
</div>
<script>
// Check if required dependencies are loaded
if (typeof supabase === 'undefined') {
console.error('Supabase library not loaded');
alert('Failed to load Supabase. Please refresh the page.');
}
if (typeof APP_CONFIG === 'undefined') {
console.error('APP_CONFIG not loaded');
alert('Configuration not loaded. Please check config.js');
}
const client = supabase.createClient(APP_CONFIG.supabaseUrl, APP_CONFIG.supabaseKey);
const tableFields = {
buyer: ["buyer", "country", "category", "brands", "phone_number", "mail_id", "website"],
seller: ["seller", "country", "category", "brands", "phone_number", "mail_id", "website"],
gulfood: ["company_name", "address", "phone_number", "mail_id", "website"]
};
let editContext = { id: null, table: null };
// Simple cache implementation
const queryCache = new Map();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
function getCacheKey(table, filters, page, pageSize) {
return JSON.stringify({ table, filters, page, pageSize });
}
function getCachedResult(cacheKey) {
const cached = queryCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
return null;
}
function setCachedResult(cacheKey, data) {
queryCache.set(cacheKey, {
data,
timestamp: Date.now()
});
// Clean old cache entries if cache gets too large
if (queryCache.size > 100) {
const oldEntries = Array.from(queryCache.entries())
.filter(([_, entry]) => Date.now() - entry.timestamp > CACHE_DURATION);
oldEntries.forEach(([key, _]) => queryCache.delete(key));
}
}
function clearCache() {
queryCache.clear();
}
function openModal(id) {
document.getElementById(id).classList.remove("hidden");
document.body.style.overflow = 'hidden';
}
function closeModal(id) {
document.getElementById(id).classList.add("hidden");
document.body.style.overflow = 'auto';
}
function setSearchQuery(query) {
document.getElementById('searchInput').value = query;
performSearch();
}
function openCreateModal() {
updateCreateFields();
openModal("createModal");
}
function openEditModal(record, table) {
const fields = tableFields[table];
const form = document.getElementById("modalEditForm");
form.innerHTML = fields.map(f => `
<div>
<label class="block text-xs font-medium text-gray-700 mb-1">${f.replace('_', ' ').toUpperCase()}</label>
<input name='${f}' value='${record[f] || ''}'
class='w-full px-2 py-2 text-sm border border-gray-300 rounded-lg focus:border-blue-500' />
</div>
`).join('');
editContext = { id: record.id, table };
openModal("editModal");
}
async function submitModalEdit() {
try {
const inputs = document.querySelectorAll("#modalEditForm input");
const update = {};
inputs.forEach(i => update[i.name] = i.value);
const { error } = await client.from(editContext.table).update(update).eq("id", editContext.id);
if (error) {
console.error('Update error:', error);
alert("❌ Update failed: " + error.message);
return;
}
alert("✅ Updated!");
clearCache(); // Clear cache after data modification
closeModal("editModal");
// Refresh current search if one exists
if (searchState.query) {
performSearch();
}
} catch (error) {
console.error('Submit edit error:', error);
alert("❌ Update failed: " + error.message);
}
}
// Event listener moved to DOMContentLoaded
function renderRecord(r) {
const fields = tableFields[r.__table];
const tableColors = {
buyer: 'border-l-blue-500',
seller: 'border-l-green-500',
gulfood: 'border-l-purple-500'
};
const tableBadges = {
buyer: { color: 'bg-blue-100 text-blue-700', icon: 'fas fa-shopping-cart', label: 'BUYER' },
seller: { color: 'bg-green-100 text-green-700', icon: 'fas fa-store', label: 'SELLER' },
gulfood: { color: 'bg-purple-100 text-purple-700', icon: 'fas fa-globe', label: 'GULFOOD' }
};
const countryFlag = r.country === 'United Kingdom' ? '🇬🇧' :
r.country === 'India' ? '🇮🇳' :
r.country === 'United States' ? '🇺🇸' :
r.country === 'UAE' ? '🇦🇪' :
r.country === 'China' ? '🇨🇳' :
r.country === 'Germany' ? '🇩🇪' :
r.country === 'France' ? '🇫🇷' :
r.country === 'Italy' ? '🇮🇹' : '🌍';
const badge = tableBadges[r.__table];
return `
<div class='bg-white p-3 sm:p-4 border border-gray-200 rounded-xl hover:shadow-lg transition-all duration-200 hover:border-gray-300 ${tableColors[r.__table]} border-l-4 relative' data-id='${r.id}' data-table='${r.__table}'>
<!-- Action buttons in top right -->
<div class='absolute top-2 right-2 sm:top-3 sm:right-3 flex gap-1'>
<button onclick='openEditModal(${JSON.stringify(r)}, "${r.__table}")'
class='p-1 sm:p-1.5 text-blue-600 hover:bg-blue-50 rounded-lg text-xs transition-colors'>
<i class='fas fa-edit'></i>
</button>
<button onclick='deleteRecord(this)'
class='p-1 sm:p-1.5 text-red-600 hover:bg-red-50 rounded-lg text-xs transition-colors'>
<i class='fas fa-trash'></i>
</button>
</div>
<div class='flex items-center gap-2 sm:gap-3 mb-2 sm:mb-3 pr-12 sm:pr-16'>
<span class='text-base sm:text-lg'>${countryFlag}</span>
<div class='flex-1 min-w-0'>
<h4 class='text-xs sm:text-sm font-bold text-gray-900 truncate leading-tight'>
${r.buyer || r.seller || r.company_name || 'Unnamed Record'}
</h4>
<p class='text-xs text-gray-500'>${r.country || 'Unknown Location'}</p>
</div>
</div>
<!-- Badge below title -->
<div class='mb-2 sm:mb-3'>
<span class='inline-flex items-center gap-1 px-1.5 py-0.5 sm:px-2 sm:py-1 rounded-full text-xs font-medium ${badge.color}'>
<i class='${badge.icon} text-xs'></i>
<span class='hidden xs:inline'>${badge.label}</span>
<span class='xs:hidden'>${badge.label.slice(0,3)}</span>
</span>
</div>
<div class='space-y-1.5 sm:space-y-2 mb-2 sm:mb-3'>
${r.phone_number ? `
<div class='flex items-center gap-2'>
<i class='fas fa-phone text-green-600 text-xs w-3 sm:w-4'></i>
<span class='text-xs sm:text-sm text-gray-600 truncate'>${r.phone_number}</span>
</div>
` : ''}
${r.mail_id ? `
<div class='flex items-center gap-2'>
<i class='fas fa-envelope text-blue-600 text-xs w-3 sm:w-4'></i>
<a href='mailto:${r.mail_id}' class='text-xs sm:text-sm text-blue-600 hover:text-blue-800 truncate'>${r.mail_id}</a>
</div>
` : ''}
${r.website ? `
<div class='flex items-center gap-2'>
<i class='fas fa-globe text-purple-600 text-xs w-3 sm:w-4'></i>
<a href='${r.website}' target='_blank' class='text-xs sm:text-sm text-purple-600 hover:text-purple-800 truncate'>${r.website}</a>
</div>
` : ''}
${r.address ? `
<div class='flex items-start gap-2'>
<i class='fas fa-map-marker-alt text-red-600 text-xs w-3 sm:w-4 mt-0.5'></i>
<span class='text-xs sm:text-sm text-gray-600 line-clamp-2'>${r.address}</span>
</div>
` : ''}
</div>
${(r.brands || r.category) ? `
<div class='flex flex-wrap gap-1'>
${r.brands ? r.brands.split(',').slice(0, window.innerWidth < 640 ? 2 : 3).map(brand => `
<span class='bg-gray-100 text-gray-700 px-1.5 py-0.5 sm:px-2 sm:py-1 rounded-full text-xs truncate max-w-[80px] sm:max-w-none'>${brand.trim()}</span>
`).join('') : ''}
${r.category ? `
<span class='bg-blue-100 text-blue-700 px-1.5 py-0.5 sm:px-2 sm:py-1 rounded-full text-xs truncate max-w-[100px] sm:max-w-none'>${r.category}</span>
` : ''}
${r.brands && r.brands.split(',').length > (window.innerWidth < 640 ? 2 : 3) ? `
<span class='text-xs text-gray-400'>+${r.brands.split(',').length - (window.innerWidth < 640 ? 2 : 3)} more</span>
` : ''}
</div>
` : ''}
</div>
`;
}
async function parseQueryWithAI(naturalQuery) {
// Check if n8n is configured
if (!APP_CONFIG.useN8n || APP_CONFIG.n8nWebhookUrl === 'YOUR_N8N_WEBHOOK_URL_HERE') {
console.log('🔄 n8n not configured, using fallback parsing');
return parseQueryFallback(naturalQuery);
}
try {
console.log('🤖 Parsing query with n8n AI:', naturalQuery);
// Add timeout and better error handling
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 second timeout for n8n
const response = await fetch(APP_CONFIG.n8nWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ query: naturalQuery }),
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
let errorMessage = `HTTP ${response.status}`;
try {
const errorData = await response.json();
errorMessage = errorData.message || errorData.error || errorMessage;
} catch (e) {
errorMessage = response.statusText || errorMessage;
}
throw new Error(errorMessage);
}
const result = await response.json();
// Handle n8n response format
if (result.success) {
console.log('✅ n8n AI parsing successful:', result);
if (result.fallback) {
console.log(' Used fallback logic in n8n');
}
return {
table: result.table,
filters: result.filters
};
} else {
throw new Error(result.error || 'n8n workflow failed');
}
} catch (error) {
if (error.name === 'AbortError') {
console.warn('⚠️ n8n AI parsing timeout, using local fallback');
} else if (error.message.includes('Failed to fetch')) {
console.warn('⚠️ Cannot reach n8n webhook, using local fallback. Check your n8n workflow is active.');
} else {
console.warn('⚠️ n8n AI parsing failed, using local fallback:', error.message);
}
// Fallback to simple keyword-based parsing
const fallbackResult = parseQueryFallback(naturalQuery);
console.log('🔄 Local fallback parsing result:', fallbackResult);
return fallbackResult;
}
}
function parseQueryFallback(query) {
const lowerQuery = query.toLowerCase();
// Determine table based on keywords
let table = "all";
if (lowerQuery.includes("buyer") && !lowerQuery.includes("seller")) {
table = "buyer";
} else if (lowerQuery.includes("seller") && !lowerQuery.includes("buyer")) {
table = "seller";
} else if (lowerQuery.includes("gulfood")) {
table = "gulfood";
}
// Extract potential filters
const filters = [];
// Country filters
const countries = ["india", "uk", "united kingdom", "usa", "united states", "uae", "china", "germany", "france", "italy"];
for (const country of countries) {
if (lowerQuery.includes(country)) {
const countryValue = country === "uk" ? "United Kingdom" :
country === "usa" ? "United States" :
country.charAt(0).toUpperCase() + country.slice(1);
filters.push({ field: "country", operator: "ilike", value: `%${countryValue}%` });
break;
}
}
// Product/brand filters
const products = ["pepsi", "cosmetic", "fmcg", "food", "beverage"];
for (const product of products) {
if (lowerQuery.includes(product)) {
filters.push({ field: "brands", operator: "ilike", value: `%${product}%` });
filters.push({ field: "category", operator: "ilike", value: `%${product}%` });
break;
}
}
return { table, filters };
}
async function fetchFrom(table, filters, page = 0, pageSize = 100) {
const cacheKey = getCacheKey(table, filters, page, pageSize);
// Check cache first
const cachedResult = getCachedResult(cacheKey);
if (cachedResult) {
console.log('Using cached result for:', table);
return cachedResult;
}
try {
let q = client.from(table).select("*", { count: 'exact' });
const allowed = tableFields[table];
if (filters && filters.length > 0) {
for (const f of filters) {
if (allowed.includes(f.field)) {
if (f.operator === 'ilike') {
q = q.ilike(f.field, f.value);
} else if (f.operator === 'eq') {
q = q.eq(f.field, f.value);
}
}
}
}
// Add pagination with safety check
const start = page * pageSize;
const end = start + pageSize - 1;
// Only add range if start is reasonable
if (start >= 0) {
q = q.range(start, end);
}
const { data, error, count } = await q;
if (error) {
console.error('Supabase error:', error);
throw new Error(`Database query failed: ${error.message}`);
}
const result = {
data: (data || []).map(row => ({ ...row, __table: table })),
count: count || 0,
hasMore: (data || []).length === pageSize && count > (page + 1) * pageSize
};
// Cache the result
setCachedResult(cacheKey, result);
return result;
} catch (error) {
console.error('Error in fetchFrom:', error);
throw new Error(`Failed to fetch ${table} data: ${error.message}`);
}
}
let searchState = { query: '', page: 0, hasMore: false, results: [] };
async function performSearch(loadMore = false) {
const query = document.getElementById("searchInput").value.trim();
const resultsContainer = document.getElementById("resultsContainer");
const resultStats = document.getElementById("resultsCount");
const resultsSection = document.getElementById("resultsSection");
const initialState = document.getElementById("initialState");
if (!query) {
showErrorMessage("Please enter a search query");
return;
}
// Reset pagination if new search
if (!loadMore || searchState.query !== query) {
searchState = { query, page: 0, hasMore: false, results: [] };
}
// Show results section, hide initial state and product mapping
resultsSection.classList.remove("hidden");
initialState.style.display = "none";
document.getElementById("productMatchingResults").classList.add("hidden");
if (!loadMore) {
resultsContainer.innerHTML = `
<div class="col-span-full flex flex-col items-center py-8 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-4 border-blue-600 border-t-transparent mb-3 sm:mb-4"></div>
<p class="text-sm sm:text-base text-gray-600">Searching with AI...</p>
</div>
`;
resultStats.innerHTML = "";
}
try {
const ai = await parseQueryWithAI(query);
let newResults = [];
let totalCount = 0;
if (ai.table === "all") {
const [buyerRes, sellerRes, gulfoodRes] = await Promise.all([
fetchFrom("buyer", ai.filters, searchState.page),
fetchFrom("seller", ai.filters, searchState.page),
fetchFrom("gulfood", ai.filters, searchState.page)
]);
newResults = [...buyerRes.data, ...sellerRes.data, ...gulfoodRes.data];
totalCount = buyerRes.count + sellerRes.count + gulfoodRes.count;
searchState.hasMore = buyerRes.hasMore || sellerRes.hasMore || gulfoodRes.hasMore;
} else {
const result = await fetchFrom(ai.table, ai.filters, searchState.page);
newResults = result.data;
totalCount = result.count;
searchState.hasMore = result.hasMore;
}
// If no new results on load more, mark as no more available
if (loadMore && newResults.length === 0) {
searchState.hasMore = false;
}
if (loadMore) {
searchState.results = [...searchState.results, ...newResults];
} else {
searchState.results = newResults;
}
if (searchState.results.length === 0) {
resultsContainer.innerHTML = `
<div class="col-span-full flex flex-col items-center py-12">
<svg class="w-16 h-16 mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<p class="text-lg text-gray-500">No results found</p>
<p class="text-gray-400">Try a different search query</p>
</div>
`;
} else {
const resultsHTML = searchState.results.map(r => renderRecord(r)).join('');
const loadMoreButton = searchState.hasMore ? `
<div class="col-span-full flex justify-center mt-4 sm:mt-6">
<button onclick="loadMoreResults()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 sm:px-6 sm:py-3 rounded-lg font-medium transition-colors text-sm sm:text-base w-full sm:w-auto max-w-xs">
Load More Results
</button>
</div>
` : '';
resultsContainer.innerHTML = resultsHTML + loadMoreButton;
}
resultStats.innerHTML = `Found ${totalCount} companies matching "${query}" (showing ${searchState.results.length})`;
searchState.page++;
} catch (e) {
console.error('Search error:', e);
showErrorMessage(`Search failed: ${e.message}`);
}
}
function loadMoreResults() {
// Disable button to prevent multiple clicks
const loadMoreBtn = document.querySelector('button[onclick="loadMoreResults()"]');
if (loadMoreBtn) {
loadMoreBtn.disabled = true;
loadMoreBtn.innerHTML = '<i class="fas fa-spinner animate-spin mr-2"></i>Loading...';
}
performSearch(true).finally(() => {
// Re-enable button if it still exists (might be removed if no more results)
if (loadMoreBtn && !loadMoreBtn.closest('.col-span-full').removed) {
loadMoreBtn.disabled = false;
loadMoreBtn.innerHTML = 'Load More Results';
}
});
}
function showErrorMessage(message) {
const resultsContainer = document.getElementById("resultsContainer");
const resultsSection = document.getElementById("resultsSection");
const initialState = document.getElementById("initialState");
resultsSection.classList.remove("hidden");
initialState.style.display = "none";
resultsContainer.innerHTML = `
<div class='col-span-full'>
<div class='bg-red-50 border border-red-200 rounded-xl p-6 text-center'>
<svg class="w-12 h-12 mx-auto mb-4 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<h3 class="text-lg font-semibold text-red-900 mb-2">Error</h3>
<p class="text-red-700">${message}</p>
<button onclick="clearResults()" class="mt-4 bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg">
Try Again
</button>
</div>
</div>
`;
}
async function performAdvancedSearch() {
const searchType = document.getElementById("searchType").value;
const productSearch = document.getElementById("productSearch").value;
const countryFilter = document.getElementById("countryFilter").value;
let query = "";
if (searchType === "buyer") {
query = "all buyers";
} else if (searchType === "seller") {
query = "all sellers";
} else {
query = "all companies";
}
if (productSearch) {
query += ` with ${productSearch}`;
}
if (countryFilter) {
query += ` from ${countryFilter}`;
}
document.getElementById("searchInput").value = query;
performSearch();
}
// Country suggestions functionality
let allCountries = [];
let countrySuggestionsTimeout;
async function loadAllCountries() {
try {
// Fetch unique countries from all tables
const [buyerCountries, sellerCountries, gulfoodCountries] = await Promise.all([
client.from('buyer').select('country').not('country', 'is', null),
client.from('seller').select('country').not('country', 'is', null),
client.from('gulfood').select('country').not('country', 'is', null)
]);
const countrySet = new Set();
// Add countries from all tables
[...(buyerCountries.data || []), ...(sellerCountries.data || []), ...(gulfoodCountries.data || [])]
.forEach(item => {
if (item.country && item.country.trim()) {
countrySet.add(item.country.trim());
}
});
allCountries = Array.from(countrySet).sort();
console.log('Loaded countries:', allCountries);
} catch (error) {
console.error('Error loading countries:', error);
// Fallback to common countries
allCountries = [
'India', 'United Kingdom', 'United States', 'UAE', 'China',
'Germany', 'France', 'Italy', 'Canada', 'Australia', 'Japan',
'Singapore', 'Malaysia', 'Thailand', 'South Korea', 'Brazil'
];
}
}
function showCountrySuggestions() {
const input = document.getElementById('countryFilter');
const suggestions = document.getElementById('countrySuggestions');
const query = input.value.toLowerCase().trim();
// Clear previous timeout
clearTimeout(countrySuggestionsTimeout);
countrySuggestionsTimeout = setTimeout(() => {
if (allCountries.length === 0) {
loadAllCountries().then(() => showCountrySuggestions());
return;
}
// Filter countries based on input
const filteredCountries = allCountries.filter(country =>
country.toLowerCase().includes(query)
).slice(0, 10); // Limit to 10 suggestions
if (filteredCountries.length > 0) {
suggestions.innerHTML = filteredCountries.map(country => `
<div class="px-3 py-2 hover:bg-blue-50 cursor-pointer border-b border-gray-100 last:border-b-0"
onclick="selectCountry('${country}')">
${country}
</div>
`).join('');
suggestions.classList.remove('hidden');
} else {
suggestions.classList.add('hidden');
}
}, 150); // Debounce
}
function hideCountrySuggestions() {
setTimeout(() => {
document.getElementById('countrySuggestions').classList.add('hidden');
}, 200); // Delay to allow click events
}
function selectCountry(country) {
document.getElementById('countryFilter').value = country;
document.getElementById('countrySuggestions').classList.add('hidden');
}
async function performProductMatching() {
const productSearch = document.getElementById("productSearch").value.trim();
const countryFilter = document.getElementById("countryFilter").value;
if (!productSearch) {
showErrorMessage("Please enter a product/brand to find mapping");
return;
}
// Show product matching results section
document.getElementById("productMatchingResults").classList.remove("hidden");
document.getElementById("resultsSection").classList.add("hidden");
document.getElementById("initialState").style.display = "none";
// Update header info
const productHeader = document.getElementById("productInfoHeader");
productHeader.innerHTML = `
<div class="flex items-center justify-between">
<div>
<h3 class="text-base sm:text-lg font-bold text-gray-800">Product: ${productSearch}</h3>
${countryFilter ? `<p class="text-sm sm:text-base text-gray-600">Filtered by: ${countryFilter}</p>` : ''}
</div>
<div class="flex items-center gap-4">
<span class="text-sm sm:text-base text-blue-600 font-semibold" id="buyersCount">0 Buyers</span>
<span class="text-sm sm:text-base text-green-600 font-semibold" id="sellersCount">0 Sellers</span>
</div>
</div>
`;
// Show loading state
document.getElementById("buyersList").innerHTML = `
<div class="text-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-2 border-blue-600 border-t-transparent mx-auto mb-2"></div>
<p class="text-blue-600 text-sm">Loading buyers...</p>
</div>
`;
document.getElementById("sellersList").innerHTML = `
<div class="text-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-2 border-green-600 border-t-transparent mx-auto mb-2"></div>
<p class="text-green-600 text-sm">Loading sellers...</p>
</div>
`;
try {
// Create database-side filters for product search
const productFilters = [
{ field: 'brands', operator: 'ilike', value: `%${productSearch}%` },
{ field: 'category', operator: 'ilike', value: `%${productSearch}%` }
];
// Add country filter if specified
const filters = [...productFilters];
if (countryFilter && countryFilter !== "") {
filters.push({ field: 'country', operator: 'eq', value: countryFilter });
}
// Optimized database queries with proper filtering
const fetchOptimizedBuyers = async () => {
try {
let query = client.from("buyer").select("*", { count: 'exact' });
// Apply product search using OR condition for brands and category
query = query.or(`brands.ilike.%${productSearch}%,category.ilike.%${productSearch}%,buyer.ilike.%${productSearch}%`);
// Apply country filter if specified
if (countryFilter && countryFilter !== "") {
query = query.eq("country", countryFilter);
}
// Limit results for performance
query = query.range(0, 199); // First 200 results
const { data, error, count } = await query;
if (error) throw error;
return {
data: (data || []).map(row => ({ ...row, __table: 'buyer' })),
count: count || 0
};
} catch (error) {
console.error('Buyer fetch error:', error);
throw new Error(`Failed to fetch buyers: ${error.message}`);
}
};
const fetchOptimizedSellers = async () => {
try {
let query = client.from("seller").select("*", { count: 'exact' });
// Apply product search using OR condition for brands and category
query = query.or(`brands.ilike.%${productSearch}%,category.ilike.%${productSearch}%,seller.ilike.%${productSearch}%`);
// Apply country filter if specified
if (countryFilter && countryFilter !== "") {
query = query.eq("country", countryFilter);
}
// Limit results for performance
query = query.range(0, 199); // First 200 results
const { data, error, count } = await query;
if (error) throw error;
return {
data: (data || []).map(row => ({ ...row, __table: 'seller' })),
count: count || 0
};
} catch (error) {
console.error('Seller fetch error:', error);
throw new Error(`Failed to fetch sellers: ${error.message}`);
}
};
// Fetch buyers and sellers in parallel with optimized queries
const [buyerResult, sellerResult] = await Promise.all([
fetchOptimizedBuyers(),
fetchOptimizedSellers()
]);
const buyers = buyerResult.data;
const sellers = sellerResult.data;
// Update counts with actual database counts
document.getElementById("buyersTitle").textContent = `Buyers (${buyerResult.count})`;
document.getElementById("sellersTitle").textContent = `Sellers (${sellerResult.count})`;
document.getElementById("buyersCount").textContent = `${buyerResult.count} Buyers`;
document.getElementById("sellersCount").textContent = `${sellerResult.count} Sellers`;
// Render buyers
if (buyers.length === 0) {
document.getElementById("buyersList").innerHTML = `
<div class="text-center py-8 text-gray-500">
<i class="fas fa-search text-2xl mb-2"></i>
<p>No buyers found for "${productSearch}"</p>
</div>
`;
} else {
document.getElementById("buyersList").innerHTML = buyers.map(buyer => renderMappingCard(buyer)).join('');
}
// Render sellers
if (sellers.length === 0) {
document.getElementById("sellersList").innerHTML = `
<div class="text-center py-8 text-gray-500">
<i class="fas fa-search text-2xl mb-2"></i>
<p>No sellers found for "${productSearch}"</p>
</div>
`;
} else {
document.getElementById("sellersList").innerHTML = sellers.map(seller => renderMappingCard(seller)).join('');
}
} catch (error) {
console.error('Product mapping error:', error);
const errorMessage = error.message || 'Unknown error occurred';
document.getElementById("buyersList").innerHTML = `
<div class="bg-red-50 border border-red-200 rounded-lg p-4 text-center">
<i class="fas fa-exclamation-triangle text-red-600 mb-2"></i>
<p class="text-red-700 text-sm">Error loading buyers: ${errorMessage}</p>
</div>
`;
document.getElementById("sellersList").innerHTML = `
<div class="bg-red-50 border border-red-200 rounded-lg p-4 text-center">
<i class="fas fa-exclamation-triangle text-red-600 mb-2"></i>
<p class="text-red-700 text-sm">Error loading sellers: ${errorMessage}</p>
</div>
`;
}
}
function renderMappingCard(record) {
const countryFlag = record.country === 'United Kingdom' ? '🇬🇧' :
record.country === 'India' ? '🇮🇳' :
record.country === 'United States' ? '🇺🇸' :
record.country === 'UAE' ? '🇦🇪' :
record.country === 'China' ? '🇨🇳' :
record.country === 'Germany' ? '🇩🇪' :
record.country === 'France' ? '🇫🇷' :
record.country === 'Italy' ? '🇮🇹' : '🌍';
const companyName = record.buyer || record.seller || record.company_name || 'Unnamed Company';
return `
<div class="bg-white rounded-lg p-4 border border-gray-200 hover:shadow-md transition-all mb-3">
<div class="flex items-center gap-2 mb-3">
<span class="text-lg">${countryFlag}</span>
<h5 class="font-bold text-gray-900 text-sm flex-1">${companyName}</h5>
</div>
<div class="space-y-2">
<div class="text-xs text-gray-600">
<i class="fas fa-map-marker-alt mr-1"></i>
${record.country || 'Unknown Location'}
</div>
${record.mail_id ? `
<div class="text-xs text-blue-600">
<i class="fas fa-envelope mr-1"></i>
<a href="mailto:${record.mail_id}" class="hover:underline">${record.mail_id}</a>
</div>
` : ''}
${record.phone_number ? `
<div class="text-xs text-green-600">
<i class="fas fa-phone mr-1"></i>
${record.phone_number}
</div>
` : ''}
${record.website ? `
<div class="text-xs text-purple-600">
<i class="fas fa-globe mr-1"></i>
<a href="${record.website}" target="_blank" class="hover:underline">${record.website}</a>
</div>
` : ''}
${record.brands ? `
<div class="text-xs text-gray-700 mt-2 pt-2 border-t border-gray-100">
<span class="font-medium">Products:</span>
<span class="text-gray-600">${record.brands}</span>
</div>
` : ''}
${record.category ? `
<div class="text-xs text-gray-700">
<span class="font-medium">Category:</span>
<span class="text-gray-600">${record.category}</span>
</div>
` : ''}
</div>
</div>
`;
}
function closeProductMapping() {
document.getElementById("productMatchingResults").classList.add("hidden");
document.getElementById("resultsSection").classList.add("hidden");
document.getElementById("initialState").style.display = "block";
}
function resetAdvancedFilters() {
document.getElementById("searchType").value = "all";
document.getElementById("productSearch").value = "";
document.getElementById("countryFilter").value = "";
}
function sortResults() {
const sortBy = document.getElementById('sortBy')?.value;
if (!sortBy || sortBy === 'default') {
return;
}
if (!searchState.results || searchState.results.length === 0) {
return;
}
// Simple sorting implementation
const sorted = [...searchState.results].sort((a, b) => {
let aVal = '';
let bVal = '';
switch(sortBy) {
case 'name':
aVal = a.buyer || a.seller || a.company_name || '';
bVal = b.buyer || b.seller || b.company_name || '';
break;
case 'country':
aVal = a.country || '';
bVal = b.country || '';
break;
case 'products':
aVal = a.brands || a.category || '';
bVal = b.brands || b.category || '';
break;
default:
return 0;
}
return aVal.localeCompare(bVal);
});
searchState.results = sorted;
// Re-render results
const resultsContainer = document.getElementById("resultsContainer");
if (resultsContainer) {
const resultsHTML = searchState.results.map(r => renderRecord(r)).join('');
resultsContainer.innerHTML = resultsHTML;
}
}
function exportResults() {
if (!searchState.results || searchState.results.length === 0) {
alert("No results to export");
return;
}
try {
// Convert to CSV
const headers = ['Name', 'Country', 'Type', 'Products/Category', 'Phone', 'Email', 'Website'];
const rows = searchState.results.map(r => [
r.buyer || r.seller || r.company_name || '',
r.country || '',
r.__table || '',
r.brands || r.category || '',
r.phone_number || '',
r.mail_id || '',
r.website || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(field => `"${field}"`).join(','))
.join('\n');
// Download CSV
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`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Export error:', error);
alert('Export failed: ' + error.message);
}
}
function quickSearch(type) {
const searchInput = document.getElementById("searchInput");
if (searchInput) {
searchInput.value = type;
performSearch();
} else {
console.error('Search input not found');
}
}
function clearResults() {
const resultsSection = document.getElementById("resultsSection");
const initialState = document.getElementById("initialState");
const searchInput = document.getElementById("searchInput");
if (resultsSection) {
resultsSection.classList.add("hidden");
}
if (initialState) {
initialState.style.display = "block";
}
if (searchInput) {
searchInput.value = "";
}
// Hide product mapping results
document.getElementById("productMatchingResults").classList.add("hidden");
// Reset search state
searchState = { query: '', page: 0, hasMore: false, results: [] };
}
async function deleteRecord(btn) {
try {
const card = btn.closest("[data-id]");
if (!card) {
console.error('Could not find card element');
return;
}
const table = card.dataset.table;
const id = card.dataset.id;
if (!table || !id) {
console.error('Missing table or id data');
alert("❌ Could not delete: missing record information");
return;
}
if (!confirm("Delete this record? This action cannot be undone.")) return;
const { error } = await client.from(table).delete().eq("id", id);
if (error) {
console.error('Delete error:', error);
alert("❌ Delete failed: " + error.message);
return;
}
alert("🗑️ Deleted");
clearCache(); // Clear cache after data modification
// Refresh current search if one exists
if (searchState.query) {
performSearch();
}
} catch (error) {
console.error('Delete record error:', error);
alert("❌ Delete failed: " + error.message);
}
}
// Table field definitions based on CSV structure
const tableFieldsDefinition = {
buyer: [
{ name: 'buyer', label: 'Company Name', type: 'text', required: true },
{ name: 'country', label: 'Country', type: 'text', required: true },
{ name: 'phone_number', label: 'Phone Number', type: 'tel', required: false },
{ name: 'mail_id', label: 'Email', type: 'email', required: false },
{ name: 'website', label: 'Website', type: 'url', required: false },
{ name: 'category', label: 'Category', type: 'text', required: false },
{ name: 'brands', label: 'Brands', type: 'text', required: false }
],
seller: [
{ name: 'seller', label: 'Company Name', type: 'text', required: true },
{ name: 'country', label: 'Country', type: 'text', required: true },
{ name: 'phone_number', label: 'Phone Number', type: 'tel', required: false },
{ name: 'mail_id', label: 'Email', type: 'email', required: false },
{ name: 'website', label: 'Website', type: 'url', required: false },
{ name: 'category', label: 'Category', type: 'text', required: false },
{ name: 'brands', label: 'Brands', type: 'text', required: false }
],
gulfood: [
{ name: 'company_name', label: 'Company Name', type: 'text', required: true },
{ name: 'address', label: 'Address', type: 'text', required: false },
{ name: 'phone_number', label: 'Phone Number', type: 'tel', required: false },
{ name: 'mail_id', label: 'Email', type: 'email', required: false },
{ name: 'website', label: 'Website', type: 'url', required: false }
]
};
function updateCreateFormFields() {
const table = document.getElementById('createTable').value;
const fields = tableFieldsDefinition[table];
const container = document.getElementById('createFormFields');
container.innerHTML = fields.map(field => `
<div>
<label class="block text-xs font-medium text-gray-700 mb-1">
${field.label} ${field.required ? '*' : ''}
</label>
<input
type="${field.type}"
name="${field.name}"
${field.required ? 'required' : ''}
class="w-full px-2 py-2 text-sm border border-gray-300 rounded-lg focus:border-blue-500"
placeholder="Enter ${field.label.toLowerCase()}"
>
</div>
`).join('');
}
// Create Modal Functions
function openCreateModal() {
document.getElementById('createModal').classList.remove('hidden');
updateCreateFormFields(); // Initialize with default fields
}
function closeCreateModal() {
document.getElementById('createModal').classList.add('hidden');
document.getElementById('createForm').reset();
}
async function handleCreateSubmit(event) {
event.preventDefault();
try {
const table = document.getElementById('createTable').value;
const formData = new FormData(event.target);
const data = {};
// Collect all form data
for (let [key, value] of formData.entries()) {
if (value.trim()) { // Only include non-empty values
data[key] = value.trim();
}
}
console.log('Attempting to insert into table:', table);
console.log('Data to insert:', data);
const { data: result, error } = await client.from(table).insert([data]);
if (error) {
console.error('Create error details:', error);
alert("❌ Create failed: " + (error.message || JSON.stringify(error)));
return;
}
console.log('Insert successful:', result);
alert("✅ Record created successfully!");
clearCache();
closeCreateModal();
// Refresh current search if one exists
if (searchState.query) {
performSearch();
}
} catch (error) {
console.error('Create record error:', error);
alert("❌ Create failed: " + (error.message || JSON.stringify(error)));
}
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
try {
console.log('Initializing dashboard...');
// Check if required elements exist
const searchInput = document.getElementById('searchInput');
if (!searchInput) {
console.error('Search input not found');
return;
}
// Search on Enter
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
performSearch();
}
});
// Load countries for suggestions
loadAllCountries();
console.log('Dashboard initialized successfully');
} catch (error) {
console.error('Initialization error:', error);
alert('Failed to initialize dashboard. Please refresh the page.');
}
});
</script>
<style>
/* Custom animations */
@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* Smooth transitions */
* {
transition: all 0.2s ease;
}
/* Focus states */
input:focus, select:focus, button:focus {
outline: none;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
</style>
</body>
</html>