Update index.html

mobile view
This commit is contained in:
quantumtaskai 2025-06-16 00:03:26 +05:30 committed by GitHub
parent 50c4384add
commit c82904860b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -54,6 +54,24 @@
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); } 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); } 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
} }
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOutRight {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.toast-enter {
animation: slideInRight 0.3s ease-out;
}
.toast-exit {
animation: slideOutRight 0.3s ease-in;
}
</style> </style>
</head> </head>
<body> <body>
@ -114,11 +132,12 @@
</div> </div>
<div class="flex gap-2 sm:gap-3"> <div class="flex gap-2 sm:gap-3">
<button <button
id="searchButton"
onclick="performSearch()" 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" 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> <i id="searchIcon" class="fas fa-search text-xs sm:text-sm"></i>
<span>Search</span> <span id="searchText">Search</span>
</button> </button>
<button <button
onclick="openCreateModal()" onclick="openCreateModal()"
@ -155,11 +174,11 @@
<i class="fas fa-sliders-h text-gray-600"></i> <i class="fas fa-sliders-h text-gray-600"></i>
<h3 class="text-sm font-semibold text-gray-700">Product Mapping</h3> <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"> <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> <i id="advancedToggleIcon" class="fas fa-chevron-right text-xs"></i>
</button> </button>
</div> </div>
<div id="advancedContent" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-4"> <div id="advancedContent" class="hidden sm:grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-4">
<!-- Search Type --> <!-- Search Type -->
<div> <div>
<label class="block text-xs sm:text-sm font-medium text-gray-700 mb-1 sm:mb-2">Search Type</label> <label class="block text-xs sm:text-sm font-medium text-gray-700 mb-1 sm:mb-2">Search Type</label>
@ -200,7 +219,7 @@
</div> </div>
<!-- Advanced Search Actions --> <!-- Advanced Search Actions -->
<div id="advancedActions" class="flex gap-2 sm:gap-3"> <div id="advancedActions" class="hidden sm:flex gap-2 sm:gap-3">
<button <button
onclick="performProductMatching()" 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" 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"
@ -288,7 +307,7 @@
<p id="resultsCount" class="text-sm text-gray-600"></p> <p id="resultsCount" class="text-sm text-gray-600"></p>
</div> </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 id="resultsContainer" class="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-4 lg:gap-6 custom-scrollbar max-h-[600px] overflow-y-auto"></div>
</div> </div>
</div> </div>
@ -304,6 +323,11 @@
</div> </div>
</div> </div>
<!-- Toast Notification Container -->
<div id="toastContainer" class="fixed top-4 right-4 z-50 space-y-2">
<!-- Toast notifications will be dynamically added here -->
</div>
<!-- Edit Modal --> <!-- Edit Modal -->
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50"> <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="bg-white rounded-2xl p-4 max-w-sm w-full mx-4 shadow-2xl max-h-[90vh] overflow-y-auto">
@ -419,6 +443,93 @@ function clearCache() {
queryCache.clear(); queryCache.clear();
} }
function setSearchButtonLoading(isLoading) {
const button = document.getElementById('searchButton');
const icon = document.getElementById('searchIcon');
const text = document.getElementById('searchText');
if (isLoading) {
button.disabled = true;
button.classList.add('opacity-75', 'cursor-not-allowed');
button.classList.remove('hover:bg-blue-700');
icon.className = 'fas fa-spinner fa-spin text-xs sm:text-sm';
text.textContent = 'Searching...';
} else {
button.disabled = false;
button.classList.remove('opacity-75', 'cursor-not-allowed');
button.classList.add('hover:bg-blue-700');
icon.className = 'fas fa-search text-xs sm:text-sm';
text.textContent = 'Search';
}
}
function showToast(message, type = 'success', duration = 4000) {
const container = document.getElementById('toastContainer');
const toastId = 'toast-' + Date.now();
// Determine colors and icon based on type
const config = {
success: {
bgColor: 'bg-green-500',
icon: 'fas fa-check-circle',
borderColor: 'border-green-400'
},
error: {
bgColor: 'bg-red-500',
icon: 'fas fa-exclamation-circle',
borderColor: 'border-red-400'
},
warning: {
bgColor: 'bg-yellow-500',
icon: 'fas fa-exclamation-triangle',
borderColor: 'border-yellow-400'
},
info: {
bgColor: 'bg-blue-500',
icon: 'fas fa-info-circle',
borderColor: 'border-blue-400'
}
};
const { bgColor, icon, borderColor } = config[type] || config.success;
// Create toast element
const toast = document.createElement('div');
toast.id = toastId;
toast.className = `${bgColor} text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-[320px] max-w-md border-l-4 ${borderColor} toast-enter`;
toast.innerHTML = `
<i class="${icon} text-lg"></i>
<span class="flex-1 text-sm font-medium">${message}</span>
<button onclick="removeToast('${toastId}')" class="text-white hover:text-gray-200 transition-colors">
<i class="fas fa-times text-sm"></i>
</button>
`;
// Add to container
container.appendChild(toast);
// Auto remove after duration
setTimeout(() => {
removeToast(toastId);
}, duration);
}
function removeToast(toastId) {
const toast = document.getElementById(toastId);
if (toast) {
toast.classList.remove('toast-enter');
toast.classList.add('toast-exit');
// Remove from DOM after animation
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}
}
function openModal(id) { function openModal(id) {
document.getElementById(id).classList.remove("hidden"); document.getElementById(id).classList.remove("hidden");
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
@ -429,6 +540,31 @@ function closeModal(id) {
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
} }
function toggleAdvancedPanel() {
const content = document.getElementById('advancedContent');
const actions = document.getElementById('advancedActions');
const icon = document.getElementById('advancedToggleIcon');
// Check if currently hidden (using Tailwind's hidden class)
if (content.classList.contains('hidden')) {
// Show content
content.classList.remove('hidden');
content.classList.add('grid');
actions.classList.remove('hidden');
actions.classList.add('flex');
icon.classList.remove('fa-chevron-right');
icon.classList.add('fa-chevron-down');
} else {
// Hide content
content.classList.remove('grid');
content.classList.add('hidden');
actions.classList.remove('flex');
actions.classList.add('hidden');
icon.classList.remove('fa-chevron-down');
icon.classList.add('fa-chevron-right');
}
}
function setSearchQuery(query) { function setSearchQuery(query) {
document.getElementById('searchInput').value = query; document.getElementById('searchInput').value = query;
performSearch(); performSearch();
@ -462,11 +598,11 @@ async function submitModalEdit() {
const { error } = await client.from(editContext.table).update(update).eq("id", editContext.id); const { error } = await client.from(editContext.table).update(update).eq("id", editContext.id);
if (error) { if (error) {
console.error('Update error:', error); console.error('Update error:', error);
alert("❌ Update failed: " + error.message); showToast("Update failed: " + error.message, 'error');
return; return;
} }
alert("✅ Updated!"); showToast("Record updated successfully!", 'success');
clearCache(); // Clear cache after data modification clearCache(); // Clear cache after data modification
closeModal("editModal"); closeModal("editModal");
@ -476,7 +612,7 @@ async function submitModalEdit() {
} }
} catch (error) { } catch (error) {
console.error('Submit edit error:', error); console.error('Submit edit error:', error);
alert("❌ Update failed: " + error.message); showToast("Update failed: " + error.message, 'error');
} }
} }
@ -537,8 +673,8 @@ function renderRecord(r) {
<div class='mb-2 sm:mb-3'> <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}'> <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> <i class='${badge.icon} text-xs'></i>
<span class='hidden xs:inline'>${badge.label}</span> <span class='hidden sm:inline'>${badge.label}</span>
<span class='xs:hidden'>${badge.label.slice(0,3)}</span> <span class='sm:hidden'>${badge.label.slice(0,3).toUpperCase()}</span>
</span> </span>
</div> </div>
@ -768,6 +904,11 @@ async function performSearch(loadMore = false) {
return; return;
} }
// Show loading state on search button
if (!loadMore) {
setSearchButtonLoading(true);
}
// Reset pagination if new search // Reset pagination if new search
if (!loadMore || searchState.query !== query) { if (!loadMore || searchState.query !== query) {
searchState = { query, page: 0, hasMore: false, results: [] }; searchState = { query, page: 0, hasMore: false, results: [] };
@ -850,6 +991,11 @@ async function performSearch(loadMore = false) {
} catch (e) { } catch (e) {
console.error('Search error:', e); console.error('Search error:', e);
showErrorMessage(`Search failed: ${e.message}`); showErrorMessage(`Search failed: ${e.message}`);
} finally {
// Hide loading state on search button
if (!loadMore) {
setSearchButtonLoading(false);
}
} }
} }
@ -1295,7 +1441,7 @@ function sortResults() {
function exportResults() { function exportResults() {
if (!searchState.results || searchState.results.length === 0) { if (!searchState.results || searchState.results.length === 0) {
alert("No results to export"); showToast("No results to export", 'warning');
return; return;
} }
@ -1326,9 +1472,11 @@ function exportResults() {
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
showToast(`Exported ${searchState.results.length} records successfully`, 'success');
} catch (error) { } catch (error) {
console.error('Export error:', error); console.error('Export error:', error);
alert('Export failed: ' + error.message); showToast('Export failed: ' + error.message, 'error');
} }
} }
@ -1379,7 +1527,7 @@ async function deleteRecord(btn) {
if (!table || !id) { if (!table || !id) {
console.error('Missing table or id data'); console.error('Missing table or id data');
alert("❌ Could not delete: missing record information"); showToast("Could not delete: missing record information", 'error');
return; return;
} }
@ -1388,11 +1536,11 @@ async function deleteRecord(btn) {
const { error } = await client.from(table).delete().eq("id", id); const { error } = await client.from(table).delete().eq("id", id);
if (error) { if (error) {
console.error('Delete error:', error); console.error('Delete error:', error);
alert("❌ Delete failed: " + error.message); showToast("Delete failed: " + error.message, 'error');
return; return;
} }
alert("🗑️ Deleted"); showToast("Record deleted successfully", 'success');
clearCache(); // Clear cache after data modification clearCache(); // Clear cache after data modification
// Refresh current search if one exists // Refresh current search if one exists
@ -1401,7 +1549,7 @@ async function deleteRecord(btn) {
} }
} catch (error) { } catch (error) {
console.error('Delete record error:', error); console.error('Delete record error:', error);
alert("❌ Delete failed: " + error.message); showToast("Delete failed: " + error.message, 'error');
} }
} }
@ -1487,12 +1635,12 @@ async function handleCreateSubmit(event) {
const { data: result, error } = await client.from(table).insert([data]); const { data: result, error } = await client.from(table).insert([data]);
if (error) { if (error) {
console.error('Create error details:', error); console.error('Create error details:', error);
alert("❌ Create failed: " + (error.message || JSON.stringify(error))); showToast("Create failed: " + (error.message || JSON.stringify(error)), 'error');
return; return;
} }
console.log('Insert successful:', result); console.log('Insert successful:', result);
alert("✅ Record created successfully!"); showToast("Record created successfully!", 'success');
clearCache(); clearCache();
closeCreateModal(); closeCreateModal();
@ -1502,7 +1650,7 @@ async function handleCreateSubmit(event) {
} }
} catch (error) { } catch (error) {
console.error('Create record error:', error); console.error('Create record error:', error);
alert("❌ Create failed: " + (error.message || JSON.stringify(error))); showToast("Create failed: " + (error.message || JSON.stringify(error)), 'error');
} }
} }
@ -1575,4 +1723,4 @@ input:focus, select:focus, button:focus {
</body> </body>
</html> </html>