/**
* AGC Document Search - Main Application
* Handles application state management and UI interactions
*/
// Application State
const appState = {
isInitializing: true,
isOnline: navigator.onLine,
apiStatus: "connecting",
// Search State
searchQuery: "",
searchResults: null,
searchHistory: loadFromStorage("searchHistory") || [],
// Documents State
documents: [],
documentTypes: ["All Types"],
selectedDocument: null,
bookmarks: loadFromStorage("bookmarks") || [],
// UI State
activeTab: "all-docs",
viewMode: loadFromStorage("viewMode") || "grid",
// Loading States
loading: {
search: false,
documents: false,
},
// AI Chat
aiChat: {
messages: loadFromStorage("chatMessages") || [],
isTyping: false,
},
};
// DOM Elements
const elements = {
// Safely get elements with a fallback
getElement: function (id) {
const element = document.getElementById(id);
if (!element) {
console.warn(`Element with ID '${id}' not found in the DOM`);
}
return element;
},
get searchInput() {
return this.getElement("mainSearchInput");
},
get searchButton() {
return this.getElement("mainSearchButton");
},
get searchResultsSection() {
return this.getElement("resultsSection");
},
get searchResultsContainer() {
return this.getElement("searchResultsContainer");
},
get recentDocumentsContainer() {
return this.getElement("recentDocumentsContainer");
},
get chatMessages() {
return this.getElement("chatMessages");
},
get chatInput() {
return this.getElement("chatInput");
},
get sendMessageButton() {
return this.getElement("chatSendBtn");
},
get mainContent() {
return this.getElement("mainContent");
},
// For elements that use querySelectorAll
get tabButtons() {
return document.querySelectorAll(".tab") || [];
},
// Other elements
get mobileMenuButton() {
return this.getElement("mobileMenuToggle");
},
get mobileMenu() {
return this.getElement("mobileMenu");
},
// Will be created dynamically
documentViewerModal: null,
};
// Utility Functions
function formatDate(dateString) {
if (!dateString) return "N/A";
return new Date(dateString).toLocaleDateString("en-MY", {
year: "numeric",
month: "short",
day: "numeric",
});
}
function truncateText(text, maxLength = 100) {
if (!text || text.length <= maxLength) return text;
return text.substring(0, maxLength) + "...";
}
function debounce(func, wait = 300) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function loadFromStorage(key) {
try {
const data = localStorage.getItem(`agc_${key}`);
return data ? JSON.parse(data) : null;
} catch (error) {
console.error(`Error loading ${key} from storage:`, error);
return null;
}
}
function saveToStorage(key, data) {
try {
localStorage.setItem(`agc_${key}`, JSON.stringify(data));
return true;
} catch (error) {
console.error(`Error saving ${key} to storage:`, error);
return false;
}
}
// Search Functions
async function performSearch(query, docType = "All Types") {
if (!query.trim()) return;
// Update UI to loading state
appState.loading.search = true;
updateLoadingState();
try {
// Perform search via API
const results = await apiService.searchDocuments(query);
// Update application state
appState.searchResults = results;
// Add to search history
if (!appState.searchHistory.some((item) => item.query === query)) {
appState.searchHistory.unshift({
query,
timestamp: new Date().toISOString(),
count: results.documents.length,
});
// Keep last 20 searches
if (appState.searchHistory.length > 20) {
appState.searchHistory = appState.searchHistory.slice(0, 20);
}
saveToStorage("searchHistory", appState.searchHistory);
}
// Update UI with results
displaySearchResults(results);
} catch (error) {
console.error("Search failed:", error);
elements.searchResultsContainer.innerHTML = `
Sorry, the search failed. Please try again.
${error.message}
`;
} finally {
appState.loading.search = false;
updateLoadingState();
// Show results section if hidden
elements.searchResultsSection.classList.remove("hidden");
}
}
function displaySearchResults(results) {
const { documents, query, enhanced_query, answer } = results;
if (!documents || documents.length === 0) {
elements.searchResultsContainer.innerHTML = `
No results found for "${query}"
Try using different keywords or browse all documents.
`;
return;
}
let resultsHTML = "";
// Add AI answer if available
if (answer) {
resultsHTML += `
`;
}
// Enhanced query display
if (enhanced_query && enhanced_query !== query) {
resultsHTML += `
Showing results for: ${enhanced_query}
Original search: ${query}
`;
}
// Results count
resultsHTML += `
Found ${documents.length} result${
documents.length !== 1 ? "s" : ""
}
`;
// Document results
resultsHTML += '';
documents.forEach((doc) => {
const docTitle = doc.title || "Untitled Document";
const docContent = doc.content || "No content available";
const docType = doc.doc_type || "Unknown";
const docDate = formatDate(doc.created_at);
const isBookmarked = appState.bookmarks.includes(doc.id);
resultsHTML += `
${truncateText(docContent, 200)}
`;
});
resultsHTML += "
";
elements.searchResultsContainer.innerHTML = resultsHTML;
}
// Document Functions
async function loadDocuments(docType = "All Types") {
appState.loading.documents = true;
updateLoadingState();
try {
// Load documents based on document type
const filters = {
doc_type: docType !== "All Types" ? docType : undefined,
};
const documents = await apiService.getDocuments(filters);
appState.documents = documents;
// Display documents in recent documents section
displayRecentDocuments(documents.slice(0, 6)); // Show first 6 documents
} catch (error) {
console.error("Failed to load documents:", error);
// Check if the container exists before attempting to update it
const container = document.getElementById("recentDocumentsContainer");
if (container) {
container.innerHTML = `
Sorry, we couldn't load the documents. Please try again.
${error.message}
`;
}
// Show error notification
showNotification(
"error",
"Failed to load documents. Please check the console for details."
);
} finally {
appState.loading.documents = false;
updateLoadingState();
}
}
function displayRecentDocuments(documents) {
// Check if the container element exists
const recentDocumentsContainer = document.getElementById(
"recentDocumentsContainer"
);
if (!recentDocumentsContainer) {
console.warn("Recent documents container not found in the DOM");
return;
}
if (!documents || documents.length === 0) {
recentDocumentsContainer.innerHTML = `
No recent documents found.
`;
return;
}
let documentsHTML = '';
documents.forEach((doc) => {
const docTitle = doc.title || "Untitled Document";
const docContent = doc.content || "No content available";
const docType = doc.doc_type || "Unknown";
const docDate = formatDate(doc.created_at);
const isBookmarked = appState.bookmarks.includes(doc.id);
documentsHTML += `
${truncateText(docContent, 100)}
`;
});
documentsHTML += "
";
recentDocumentsContainer.innerHTML = documentsHTML;
}
async function loadDocumentTypes() {
try {
const response = await apiService.getDocumentTypes();
if (response && response.document_types) {
// Add "All Types" as the first option
appState.documentTypes = ["All Types", ...response.document_types];
// Update UI elements that use document types (e.g., filter dropdowns)
updateDocumentTypeSelectors();
}
} catch (error) {
console.error("Failed to load document types:", error);
}
}
function updateDocumentTypeSelectors() {
// For future implementation - update any dropdowns or filters with document types
}
// View document details
window.viewDocument = async function (documentId) {
try {
console.log(`Viewing document with ID: ${documentId}`);
showLoader();
// Convert any numeric IDs to our document format
if (typeof documentId === "number" || !isNaN(parseInt(documentId, 10))) {
console.log(`Converting numeric ID ${documentId} to string format`);
// If it's a number between 1-6, convert to our doc format
const numId = parseInt(documentId, 10);
if (numId >= 1 && numId <= 6) {
documentId = `doc${numId}`;
console.log(`Converted to: ${documentId}`);
}
}
// Fetch document from API
const document = await apiService.getDocument(documentId);
// Extract legal information from content
const extractedData = extractLegalInfo(document.content);
// Create modal if it doesn't exist
let modal = window.document.querySelector(".document-modal");
if (!modal) {
modal = window.document.createElement("div");
modal.className = "document-modal";
modal.innerHTML = `
`;
window.document.body.appendChild(modal);
// Add event listeners
const closeButtons = modal.querySelectorAll(".document-modal-close");
closeButtons.forEach((button) => {
button.addEventListener("click", () => {
modal.classList.add("hidden");
});
});
modal
.querySelector(".document-modal-overlay")
.addEventListener("click", () => {
modal.classList.add("hidden");
});
}
// Update modal title
modal.querySelector(".document-modal-title").textContent =
document.title || `Document ${documentId}`;
// Build the modal content with structured information
let modalContent = `
`;
// Add extracted information if available
modalContent += generateDocumentSummary(extractedData);
// Add full content section
modalContent += `
Full Document Content
${
document.content || "No content available"
}
`;
// Update modal body
modal.querySelector(".document-modal-body").innerHTML = modalContent;
// Add styles for new elements if not already present
if (!window.document.querySelector("style#document-details-styles")) {
const detailsStyles = window.document.createElement("style");
detailsStyles.id = "document-details-styles";
detailsStyles.textContent = `
.section-title {
margin: 0 0 0.75rem 0;
font-size: 1.1rem;
color: var(--primary-dark);
display: flex;
align-items: center;
gap: 0.5rem;
}
.full-content-section {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--gray-light);
}
.full-content-container {
background-color: #f8f9fa;
border-radius: var(--radius);
border: 1px solid var(--gray-light);
padding: 1rem;
max-height: 400px;
overflow-y: auto;
}
.full-content-text {
font-family: monospace;
font-size: 0.9rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
margin: 0;
}
.document-summary {
margin: 1rem 0;
}
.info-section {
margin-bottom: 1rem;
padding: 1rem;
background-color: #f8f9fa;
border-radius: var(--radius);
border-left: 3px solid var(--primary);
}
.info-section h4 {
margin: 0 0 0.75rem 0;
color: var(--primary-dark);
font-size: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.info-section p {
margin: 0.5rem 0;
font-size: 0.9rem;
}
.acts-list,
.persons-list {
margin: 0.5rem 0;
padding-left: 0;
list-style: none;
}
.act-item,
.person-item {
background-color: rgba(59, 130, 246, 0.1);
margin: 0.25rem 0;
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.9rem;
border-left: 3px solid var(--primary);
}
.person-item {
border-left-color: var(--warning);
background-color: rgba(245, 158, 11, 0.1);
}
.person-name {
font-weight: 600;
font-size: 0.95rem;
margin-bottom: 0.35rem;
}
.person-role,
.person-category {
font-size: 0.85rem;
margin-top: 0.25rem;
}
.role-label,
.category-label {
display: inline-block;
padding: 0.15rem 0.5rem;
border-radius: 1rem;
font-size: 0.75rem;
font-weight: 500;
}
.role-label {
background-color: var(--primary);
color: white;
}
.category-label {
background-color: var(--gray-light);
color: var(--gray-dark);
}
.allegation-item {
margin: 0.75rem 0;
padding: 0.75rem;
background-color: rgba(239, 68, 68, 0.05);
border-radius: 0.25rem;
border-left: 3px solid var(--danger);
}
.allegation-preview {
margin: 0.5rem 0 0 0;
font-size: 0.9rem;
color: var(--gray);
font-style: italic;
}
.status-ongoing {
color: var(--primary);
font-weight: 600;
}
.status-selesai,
.status-completed {
color: var(--success);
font-weight: 600;
}
.status-pending {
color: var(--warning);
font-weight: 600;
}
`;
window.document.head.appendChild(detailsStyles);
}
// Show modal
modal.classList.remove("hidden");
} catch (error) {
console.error("Error loading document:", error);
showNotification(
"error",
`Failed to load document details: ${error.message}`
);
} finally {
hideLoader();
}
};
function hideDocumentViewer() {
if (elements.documentViewerModal) {
elements.documentViewerModal.classList.add("hidden");
}
}
function toggleBookmark(documentId) {
if (!documentId) return;
const bookmarks = [...appState.bookmarks];
const index = bookmarks.indexOf(documentId);
if (index === -1) {
// Add bookmark
bookmarks.push(documentId);
} else {
// Remove bookmark
bookmarks.splice(index, 1);
}
// Update state and storage
appState.bookmarks = bookmarks;
saveToStorage("bookmarks", bookmarks);
// Update UI
document
.querySelectorAll(`.bookmark-button[onclick*="${documentId}"]`)
.forEach((button) => {
if (index === -1) {
button.classList.add("bookmarked");
} else {
button.classList.remove("bookmarked");
}
});
}
// Tab Navigation
function switchTab(tabId) {
// Update active tab state
appState.activeTab = tabId;
// Get all tab links and remove active class
const mainTabLinks = document.querySelectorAll(".main-tab-link");
mainTabLinks.forEach((t) => t.classList.remove("active"));
// Add active class to the corresponding tab
const activeTab = document.querySelector(`.main-tab-link[href="#${tabId}"]`);
if (activeTab) {
activeTab.classList.add("active");
}
// Hide all tab content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.remove("active");
});
// Show the selected tab content
const tabContent = document.getElementById(tabId);
if (tabContent) {
tabContent.classList.add("active");
}
// Load tab-specific content
if (tabId === "browse") {
loadDocuments("All Types");
} else if (tabId === "search") {
// Any search-specific initialization
} else if (tabId === "chat") {
// Any chat-specific initialization
}
}
// AI Chat Functions
function sendChatMessage() {
const message = elements.chatInput.value.trim();
if (!message) return;
// Add user message to UI
addChatMessage(message, "user");
// Clear input
elements.chatInput.value = "";
// Set typing indicator
appState.aiChat.isTyping = true;
updateChatTypingIndicator();
// Show API connection error message
setTimeout(() => {
const aiResponse =
"The AI assistant feature is currently unavailable. Please ensure the AI service is properly configured and connected.";
// Add error response to UI
addChatMessage(aiResponse, "system");
// Clear typing indicator
appState.aiChat.isTyping = false;
updateChatTypingIndicator();
}, 1000);
}
function addChatMessage(message, role) {
// Add to state
appState.aiChat.messages.push({
role,
content: message,
timestamp: new Date().toISOString(),
});
// Save to storage (limited to last 50 messages)
if (appState.aiChat.messages.length > 50) {
appState.aiChat.messages = appState.aiChat.messages.slice(-50);
}
saveToStorage("chatMessages", appState.aiChat.messages);
// Add to UI
const chatMessageElement = document.createElement("div");
if (role === "user") {
chatMessageElement.innerHTML = `
`;
} else if (role === "system") {
chatMessageElement.innerHTML = `
`;
} else {
chatMessageElement.innerHTML = `
`;
}
elements.chatMessages.appendChild(chatMessageElement);
// Scroll to bottom
elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight;
}
function updateChatTypingIndicator() {
// Remove existing typing indicator if any
const existingIndicator =
elements.chatMessages.querySelector(".typing-indicator");
if (existingIndicator) {
existingIndicator.remove();
}
if (appState.aiChat.isTyping) {
// Add typing indicator
const indicatorElement = document.createElement("div");
indicatorElement.className = "typing-indicator";
indicatorElement.innerHTML = `
`;
elements.chatMessages.appendChild(indicatorElement);
elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight;
}
}
// UI Helper Functions
function updateLoadingState() {
// Update loading indicators based on appState.loading
const searchButton = document.getElementById("mainSearchButton");
if (searchButton) {
if (appState.loading.search) {
searchButton.innerHTML = ' ';
searchButton.disabled = true;
} else {
searchButton.innerHTML = ' ';
searchButton.disabled = false;
}
}
// Other loading indicators can be updated here
}
function showLoadingOverlay() {
let overlay = document.querySelector(".loading-overlay");
if (!overlay) {
overlay = document.createElement("div");
overlay.className = "loading-overlay";
overlay.innerHTML = `
`;
document.body.appendChild(overlay);
}
overlay.classList.remove("hidden");
}
function hideLoadingOverlay() {
const overlay = document.querySelector(".loading-overlay");
if (overlay) {
overlay.classList.add("hidden");
}
}
// Initialize Application
async function initializeApp() {
try {
showLoadingOverlay();
// Check API connection (blocking)
const isConnected = await apiService.checkConnection();
appState.apiStatus = isConnected ? "connected" : "disconnected";
console.log(`API Status: ${appState.apiStatus}`);
if (!isConnected) {
hideLoadingOverlay();
showNotification(
"error",
"Cannot connect to API server. Please ensure the backend service is running."
);
// Check if mainContent element exists
const mainContent = document.getElementById("mainContent");
if (mainContent) {
mainContent.innerHTML = `
API Connection Error
The application could not connect to the backend API server.
Please make sure:
The backend server is running (uvicorn api:app --reload)
The API is accessible at ${apiService.baseUrl}
Try Again
`;
}
return;
}
try {
// Load document types
await loadDocumentTypes();
} catch (error) {
console.error("Failed to load document types:", error);
showNotification(
"warning",
"Could not load document types. Some filtering options may be unavailable."
);
}
try {
// Load initial documents
await loadDocuments();
} catch (error) {
console.error("Failed to load documents:", error);
showNotification(
"error",
"Failed to load documents. Please check the console for details."
);
}
// Setup event listeners
setupEventListeners();
// Load chat history
loadChatHistory();
} catch (error) {
console.error("Failed to initialize application:", error);
hideLoadingOverlay();
showNotification(
"error",
"Application initialization failed. Please check the console for details."
);
// Show error on main content
const mainContent = document.getElementById("mainContent");
if (mainContent) {
mainContent.innerHTML = `
Application Error
Something went wrong while initializing the application.
Error details:
${error.message || "Unknown error"}
Try Again
`;
}
} finally {
// Mark initialization as complete
appState.isInitializing = false;
hideLoadingOverlay();
}
}
function setupEventListeners() {
// Search
if (elements.searchButton) {
elements.searchButton.addEventListener("click", () => {
if (elements.searchInput) {
performSearch(elements.searchInput.value);
}
});
}
if (elements.searchInput) {
elements.searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
performSearch(elements.searchInput.value);
}
});
}
// Tab navigation - fix for the main tabs
const mainTabLinks = document.querySelectorAll(".main-tab-link");
mainTabLinks.forEach((tab) => {
tab.addEventListener("click", (e) => {
e.preventDefault(); // Prevent default anchor behavior
// Remove active class from all tabs
mainTabLinks.forEach((t) => t.classList.remove("active"));
// Add active class to clicked tab
tab.classList.add("active");
// Get the tab ID from the href attribute
const tabId = tab.getAttribute("href").substring(1); // Remove # from href
// Hide all tab content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.remove("active");
});
// Show the selected tab content
const tabContent = document.getElementById(tabId);
if (tabContent) {
tabContent.classList.add("active");
}
});
});
// Mobile menu tab navigation
const mobileMenuLinks = document.querySelectorAll(".mobile-menu a");
mobileMenuLinks.forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
const tabId = link.getAttribute("href").substring(1);
// Hide mobile menu after selection
const mobileMenu = document.getElementById("mobileMenu");
if (mobileMenu) mobileMenu.classList.remove("active");
// Remove active class from all main tabs
mainTabLinks.forEach((t) => t.classList.remove("active"));
// Add active to the corresponding main tab
const correspondingTab = document.querySelector(
`.main-tab-link[href="#${tabId}"]`
);
if (correspondingTab) correspondingTab.classList.add("active");
// Hide all tab content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.remove("active");
});
// Show the selected tab content
const tabContent = document.getElementById(tabId);
if (tabContent) {
tabContent.classList.add("active");
}
});
});
// Mobile menu toggle
if (elements.mobileMenuButton && elements.mobileMenu) {
elements.mobileMenuButton.addEventListener("click", () => {
elements.mobileMenu.classList.toggle("active");
});
}
// Chat
if (elements.sendMessageButton) {
elements.sendMessageButton.addEventListener("click", sendChatMessage);
}
if (elements.chatInput) {
elements.chatInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
sendChatMessage();
}
});
}
}
function loadChatHistory() {
// Display existing chat messages
appState.aiChat.messages.forEach((message) => {
addChatMessage(message.content, message.role);
});
}
// Export functions for global access (needed for onclick handlers)
window.toggleBookmark = toggleBookmark;
// Initialize the application when DOM is loaded
document.addEventListener("DOMContentLoaded", initializeApp);
// Utility functions for document extraction
function extractLegalInfo(content) {
if (!content) return {};
const info = {
fileNumber: extractField(content, "File Number"),
status: extractField(content, "Status"),
caseNumber: extractField(content, "Case Number"),
dppSuggestion: extractField(content, "DPP Suggestion"),
hodDecision: extractField(content, "HOD Decision"),
allegations: extractAllegations(content),
persons: extractPersons(content),
acts: extractActs(content),
};
return info;
}
// Helper function to extract field values
function extractField(content, fieldName) {
const regex = new RegExp(`${fieldName}:\\s*([^\\n]+)`, "i");
const match = content.match(regex);
return match ? match[1].trim() : null;
}
// Extract allegations information
function extractAllegations(content) {
const allegations = [];
const allegationPattern =
/ALLEGATION #(\d+):([\s\S]*?)(?=ALLEGATION #|\n--- |$)/g;
let match;
while ((match = allegationPattern.exec(content)) !== null) {
allegations.push({
number: match[1],
details: match[2].trim().substring(0, 200) + "...",
});
}
return allegations;
}
// Extract person information
function extractPersons(content) {
const persons = [];
// Try to find JSON-formatted person data
const jsonPersonPattern =
/\{"peranan":\s*"([^"]+)",\s*"category":\s*"([^"]+)",\s*"namaPihak":\s*"([^"]+)"\}/g;
let jsonMatch;
while ((jsonMatch = jsonPersonPattern.exec(content)) !== null) {
try {
persons.push({
name: jsonMatch[3],
role: jsonMatch[1],
category: jsonMatch[2],
});
} catch (e) {
console.error("Error processing person data:", e);
}
}
// If no JSON data found, try the old format as fallback
if (persons.length === 0) {
const personPattern =
/Person ID: (\d+)[\s\S]*?namaPerayuResponden['":]?\s*['":]([^'"]+)['"]/g;
let match;
while ((match = personPattern.exec(content)) !== null) {
persons.push({
id: match[1],
name: match[2].trim(),
role: "Unknown",
category: "Unknown",
});
}
}
return persons;
}
// Extract legal acts and sections
function extractActs(content) {
const acts = [];
const actPattern = /(Akta [^\\n]+)|(Section [^\\n]+)|(Seksyen [^\\n]+)/gi;
let match;
while ((match = actPattern.exec(content)) !== null) {
const act = match[0].trim();
if (act && !acts.includes(act)) {
acts.push(act);
}
}
return acts.slice(0, 3); // Limit to 3 acts
}
// Generate document summary based on extracted data
function generateDocumentSummary(data) {
let summary = '';
// Case information
if (data.caseNumber || data.fileNumber) {
summary += '
';
summary += '
Case Information:';
if (data.caseNumber)
summary += `
Case: ${data.caseNumber}
`;
if (data.fileNumber)
summary += `
File: ${data.fileNumber}
`;
if (data.status)
summary += `
Status: ${
data.status
}
`;
summary += "
";
}
// Legal acts
if (data.acts && data.acts.length > 0) {
summary += '
';
summary += '
Legal References:';
summary += '
';
data.acts.forEach((act) => {
summary += `${act} `;
});
summary += " ";
}
// Persons involved
if (data.persons && data.persons.length > 0) {
summary += '
';
summary += '
Persons Involved:';
summary += '
';
data.persons.forEach((person) => {
summary += `
${person.name}
${
person.role
? `${person.role}
`
: ""
}
${
person.category
? `${person.category}
`
: ""
}
`;
});
summary += " ";
}
// Allegations count
if (data.allegations && data.allegations.length > 0) {
summary += '
';
summary += `
Allegations: (${data.allegations.length})`;
data.allegations.forEach((allegation) => {
summary += `
Allegation ${allegation.number}:
${allegation.details}
`;
});
summary += "
";
}
summary += "
";
return summary;
}
// Show notification
function showNotification(type, message) {
// Create notification element if it doesn't exist
let notification = document.querySelector(".notification");
if (notification) {
// Remove existing notification first
notification.remove();
}
// Create new notification
notification = document.createElement("div");
notification.className = `notification ${type}`;
let icon;
switch (type) {
case "success":
icon = "check-circle";
break;
case "error":
icon = "exclamation-circle";
break;
case "warning":
icon = "exclamation-triangle";
break;
default:
icon = "info-circle";
}
notification.innerHTML = `
${message}
`;
document.body.appendChild(notification);
// Auto-remove after 5 seconds
setTimeout(() => {
notification.classList.add("notification-hide");
setTimeout(() => {
if (notification && notification.parentNode) {
notification.remove();
}
}, 300);
}, 5000);
}
// Show loading indicator
function showLoader() {
showLoadingOverlay();
}
// Hide loading indicator
function hideLoader() {
hideLoadingOverlay();
}