import React, { useState, useEffect, createContext, useContext, useRef } from 'react'; // --- महत्त्वाचे / IMPORTANT --- // तुम्हाला तुमच्या Google Apps Script वेब ॲपची URL येथे पेस्ट करावी लागेल. // जर ही URL योग्यरित्या पेस्ट केली नाही, तर ॲप चालणार नाही आणि तुम्हाला 'Failed to parse URL' असा एरर दिसेल. // You MUST replace this placeholder with the actual Web App URL you obtained from Google Apps Script deployment. // If this URL is not correctly pasted, the app will fail to run with 'Failed to parse URL' errors. const APPS_SCRIPT_WEB_APP_URL = "YOUR_GOOGLE_APPS_SCRIPT_WEB_APP_URL_HERE"; // If you are running this outside of the Canvas environment, // you might need to manually define a dummy __app_id. // In Canvas, __app_id is provided automatically. const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Create a context for authentication and user data const AuthContext = createContext(null); // Auth Provider Component function AuthProvider({ children }) { const [currentUser, setCurrentUser] = useState(null); const [loadingAuth, setLoadingAuth] = useState(true); const [userRole, setUserRole] = useState(null); // 'admin', 'office', 'view-only' const [isAuthenticated, setIsAuthenticated] = useState(false); const [currentUserId, setCurrentUserId] = useState(null); const [sessionAlertData, setSessionAlertData] = useState(null); // State for session alert popup // Generate or retrieve a unique device ID for the current session const currentDeviceId = useRef(localStorage.getItem('deviceId') || crypto.randomUUID()).current; useEffect(() => { localStorage.setItem('deviceId', currentDeviceId); }, [currentDeviceId]); // Helper function to make requests to Apps Script // action आणि sheetName हे नेहमी पॅरामीटर्स म्हणून पाठवले जातील. const makeAppsScriptRequest = async (action, sheetName, data = {}, id = null, idColumn = null) => { const params = new URLSearchParams({ action: action, sheetName: sheetName }); if (id) params.append('id', id); if (idColumn) params.append('idColumn', idColumn); const url = `${APPS_SCRIPT_WEB_APP_URL}?${params.toString()}`; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8', }, body: JSON.stringify(data), }); const result = await response.json(); if (!result.success) { console.error("Apps Script Error:", result.error || result.message); throw new Error(result.error || result.message); } return result.data; } catch (error) { console.error("Fetch Error:", error); throw error; } }; // Helper function to fetch data (GET requests) // sheetName हे नेहमी queryParams मध्ये असणे आवश्यक आहे. const fetchAppsScriptData = async (sheetName, queryParams = {}) => { const params = new URLSearchParams({ sheetName: sheetName, ...queryParams }); const url = `${APPS_SCRIPT_WEB_APP_URL}?${params.toString()}`; try { const response = await fetch(url, { method: 'GET', }); const result = await response.json(); if (!result.success) { console.error("Apps Script Fetch Error:", result.error || result.message); throw new Error(result.error || result.message); } return result.data; } catch (error) { console.error("Fetch Error:", error); throw error; } }; // Function to capture and log activity to Google Sheet via Apps Script const logActivity = async (type, userId, status, details = {}) => { try { await makeAppsScriptRequest('authorize', 'ActivityLog', { // 'authorize' is the action for logging userId, status, type, enteredUsername: details.enteredUsername || '', passwordInfo: details.passwordInfo || '', deviceName: navigator.platform, browserOSDetails: navigator.userAgent, ipAddress: 'N/A (Client-side limitation)', // Still N/A for client-side JS approxLocation: 'N/A (Client-side limitation)', // Still N/A for client-side JS currentDeviceId: currentDeviceId, errorMessage: details.errorMessage || '', role: details.role || '', simulated: details.simulated || false }); } catch (error) { console.error("Error logging activity to Apps Script:", error); } }; // Effect for initial user state simulation (since no real Firebase Auth) // This will simulate an anonymous user and fetch their role from Users sheet useEffect(() => { const simulateInitialAuth = async () => { setLoadingAuth(true); try { // Simulate anonymous login by generating a random UUID as UserID if not already present const storedUserId = localStorage.getItem('currentLoggedInUserId'); let userId = storedUserId || crypto.randomUUID(); // Make a request to get/create user data const userData = await fetchAppsScriptData('Users', { userId: userId }); localStorage.setItem('currentLoggedInUserId', userData.UserID); // Persist ID setCurrentUser({ uid: userData.UserID }); setIsAuthenticated(true); setCurrentUserId(userData.UserID); setUserRole(userData.Role); // Log initial successful login logActivity('login', userData.UserID, 'success', { role: userData.Role, simulated: true }); // --- Session Monitoring --- const sessionInterval = setInterval(async () => { try { const allSessions = await fetchAppsScriptData('UserSessions'); const mySession = allSessions.find(s => s.UserID === userData.UserID); if (mySession && mySession.LastLoggedInDeviceId !== currentDeviceId) { // Another device logged in, trigger alert setSessionAlertData({ timestamp: mySession.LoginTimestamp, userId: mySession.UserID, deviceName: mySession.DeviceName, ipAddress: mySession.IPAddress, preciseLocation: mySession.ApproxLocation }); // Optionally, force logout the current session here // For this prototype, we'll just show the alert. console.warn("Session alert triggered: Another device logged in."); } // Update current device's session status await makeAppsScriptRequest('update', 'UserSessions', { UserID: userData.UserID, LastLoggedInDeviceId: currentDeviceId, LoginTimestamp: new Date().toISOString(), DeviceName: navigator.platform, BrowserOSDetails: navigator.userAgent, IPAddress: 'N/A (Client-side limitation)', ApproxLocation: 'N/A (Client-side limitation)' }, userData.UserID, 'UserID'); } catch (error) { console.error("Error in session monitoring interval:", error); } }, 5000); // Check every 5 seconds return () => clearInterval(sessionInterval); // Cleanup interval } catch (error) { console.error("Error during initial auth simulation:", error); setLoadingAuth(false); setIsAuthenticated(false); setUserRole(null); setCurrentUserId(null); logActivity('login', 'anonymous_initial_fail', 'failed', { errorMessage: error.message, simulated: true }); } finally { setLoadingAuth(false); } }; simulateInitialAuth(); }, []); // Run once on mount const logout = async () => { if (currentUser) { // Log logout activity await logActivity('logout', currentUser.uid, 'logout', { deviceName: navigator.platform, browserOSDetails: navigator.userAgent, currentDeviceId: currentDeviceId }); // Clear session data in Google Sheet await makeAppsScriptRequest('update', 'UserSessions', { LastLoggedInDeviceId: '', LoginTimestamp: '' }, currentUser.uid, 'UserID'); localStorage.removeItem('currentLoggedInUserId'); // Clear stored user ID } setCurrentUser(null); setIsAuthenticated(false); setUserRole(null); setCurrentUserId(null); setSessionAlertData(null); // Clear any pending session alerts on logout }; // For demonstration, a simple way to set role after anonymous login const simulateLogin = async (username, password, role) => { if (!currentUser) { console.error("No current user to set role for."); return; } try { // Send login request to Apps Script const result = await makeAppsScriptRequest('login', 'Users', { username, password, currentDeviceId, deviceName: navigator.platform, browserOSDetails: navigator.userAgent, ipAddress: 'N/A (Client-side limitation)', approxLocation: 'N/A (Client-side limitation)', simulated: true // Mark as simulated login }); if (result && result.user) { // Update current user state based on successful login setCurrentUser({ uid: result.user.UserID }); setIsAuthenticated(true); setCurrentUserId(result.user.UserID); setUserRole(result.user.Role); localStorage.setItem('currentLoggedInUserId', result.user.UserID); // Update stored user ID // Apps Script already logs the login activity, no need to log again here. return { success: true, user: result.user }; } else { console.error("Login failed: Invalid credentials or user not found."); // Apps Script already logs failed login, no need to log again here. return { success: false, message: "Invalid username or password." }; } } catch (error) { console.error("Error during simulated login:", error); // Log error if fetch itself fails logActivity('login', username, 'failed', { errorMessage: error.message, simulated: true }); return { success: false, message: "Login failed due to an error." }; } }; return ( {loadingAuth ? (

ॲप लोड होत आहे...

) : ( <> {children} {sessionAlertData && ( setSessionAlertData(null)} alertData={sessionAlertData} /> )} )}
); } // Custom hook to use auth context const useAuth = () => useContext(AuthContext); // --- Global UI Components --- // Modal Component const Modal = ({ isOpen, onClose, children, title }) => { if (!isOpen) return null; return (

{title}

{children}
); }; // Session Alert Modal (New Component) const SessionAlertModal = ({ isOpen, onClose, alertData }) => { if (!isOpen || !alertData) return null; return (

तुमचे खाते दुसऱ्या डिव्हाइसवर लॉग इन झाले आहे!

वेळ: {new Date(alertData.timestamp).toLocaleString()}

युझरनेम/आयडी: {alertData.userId}

डिव्हाइसचे नाव: {alertData.deviceName}

आयपी ॲड्रेस: {alertData.ipAddress}

स्थान: {alertData.preciseLocation}

); }; // Message Box (instead of alert) const MessageBox = ({ message, type = 'info', onClose }) => { const bgColor = type === 'error' ? 'bg-red-100 border-red-400 text-red-700' : 'bg-blue-100 border-blue-400 text-blue-700'; const borderColor = type === 'error' ? 'border-red-500' : 'border-blue-500'; return (
{message}
); }; // --- Main App Component --- function App() { const { isAuthenticated, userRole, loadingAuth, logout, currentUserId } = useAuth(); // Using string paths for currentPage to simulate URL routing const [currentPage, setCurrentPage] = useState('/'); // Default to Home page const [message, setMessage] = useState(''); const [messageType, setMessageType] = useState('info'); // Handle initial page redirection based on authentication and role useEffect(() => { if (!loadingAuth) { if (isAuthenticated) { // If userRole is 'view-only', automatically go to search. Otherwise, dashboard. // This logic ensures the correct landing page after authentication. setCurrentPage(userRole === 'view-only' ? '/search' : '/dashboard'); } else { // If not authenticated, ensure we are on the home or login page if (currentPage !== '/' && currentPage !== '/login') { setCurrentPage('/'); // Redirect to home/login if somehow navigated away } } } }, [isAuthenticated, loadingAuth, userRole]); // Added currentPage to dependency array to handle re-render logic correctly const showMessage = (msg, type = 'info') => { setMessage(msg); setMessageType(type); setTimeout(() => setMessage(''), 3000); // Hide after 3 seconds }; // Function to prevent copying, printing, and screenshotting for non-admins useEffect(() => { const handleKeyDown = (e) => { if (userRole !== 'admin' && (e.ctrlKey || e.metaKey) && (e.key === 'c' || e.key === 'p' || e.key === 's')) { e.preventDefault(); showMessage("कॉपी करणे, प्रिंट करणे किंवा स्क्रीनशॉट घेणे प्रतिबंधित आहे.", "error"); } }; const handleContextMenu = (e) => { if (userRole !== 'admin') { e.preventDefault(); showMessage("राईट-क्लिक अक्षम केले आहे.", "error"); } }; const handleVisibilityChange = () => { if (document.visibilityState === 'hidden' && userRole !== 'admin') { // This is a weak attempt to prevent screenshots, relies on browser behavior // A true prevention is hard without OS-level access. console.warn("टॅब/ॲप स्विच करण्याचा प्रयत्न, संभाव्य स्क्रीनशॉट प्रतिबंधक ट्रिगर."); } }; document.addEventListener('keydown', handleKeyDown); document.addEventListener('contextmenu', handleContextMenu); document.addEventListener('visibilitychange', handleVisibilityChange); return () => { document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('contextmenu', handleContextMenu); document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [userRole]); // Determine if the current page requires authentication const authRequiredPages = ['/dashboard', '/data-management', '/search', '/campaigns', '/reports', '/backup', '/activity-log']; if (authRequiredPages.includes(currentPage) && !isAuthenticated) { // Redirect to login if not authenticated and trying to access an auth-required page setCurrentPage('/login'); showMessage("कृपया या पृष्ठावर प्रवेश करण्यासाठी लॉग इन करा.", "error"); return ; } // Role-based access control for specific pages const checkAccess = (allowedRoles) => { if (!isAuthenticated) return false; // Should be caught by the above check, but good for redundancy return allowedRoles.includes(userRole); }; return (
{message && setMessage('')} />} {isAuthenticated && (currentPage !== '/' && currentPage !== '/login') && ( // Only show Navbar if authenticated and not on Home/Login )}
{(() => { switch (currentPage) { case '/': return ; case '/login': return ; case '/dashboard': return (userRole && userRole !== 'view-only') ? : ; case '/data-management': return checkAccess(['admin']) ? : ; case '/search': return isAuthenticated ? : ; case '/campaigns': return checkAccess(['admin', 'office']) ? : ; case '/reports': return checkAccess(['admin', 'office', 'view-only']) ? : ; case '/backup': return checkAccess(['admin']) ? : ; case '/activity-log': return checkAccess(['admin']) ? : ; default: return

पृष्ठ सापडले नाही

; } })()}
); } // --- Navigation Bar --- const Navbar = ({ setCurrentPage, logout, userRole, userId }) => { return ( ); }; const NavItem = ({ children, onClick }) => (
  • ); // --- Unauthorized Access Component --- const UnauthorizedAccess = ({ showMessage }) => { useEffect(() => { showMessage("तुम्हाला या पृष्ठावर प्रवेश करण्याची परवानगी नाही.", "error"); }, [showMessage]); return (

    प्रवेश नाकारला

    तुमच्याकडे ही सामग्री पाहण्यासाठी आवश्यक परवानग्या नाहीत.

    ); }; // --- Home Page --- const HomePage = ({ setCurrentPage }) => { return (

    वडगाव मावळ डिरेक्टरी मध्ये आपले स्वागत आहे

    निवासी संपर्क माहिती व्यवस्थापित करण्यासाठी आणि ॲक्सेस करण्यासाठी तुमचे सुरक्षित प्लॅटफॉर्म.

    ); }; // --- Login Page --- const LoginPage = ({ setCurrentPage, showMessage }) => { const { simulateLogin, isAuthenticated, logActivity, currentDeviceId } = useAuth(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [authorizeClicked, setAuthorizeClicked] = useState(false); const [loginEnabled, setLoginEnabled] = useState(false); // Controls 'Login' button state useEffect(() => { if (isAuthenticated) { setCurrentPage('/dashboard'); } }, [isAuthenticated, setCurrentPage]); const handleAuthorize = async () => { setAuthorizeClicked(true); // Indicate authorization attempt setLoginEnabled(false); // Disable login button until authorization is complete // Simulate IP address and geo-location capture for logging purposes // In a real application, this would be done server-side or via a dedicated Geo-IP API const ipAddress = 'नमुनेदार आयपी: 192.168.1.1'; // Placeholder const approxLocation = 'नमुनेदार स्थान: पुणे, भारत'; // Placeholder // Log the authorization attempt try { await logActivity('authorization_attempt', username || 'N/A', 'authorized', { enteredUsername: username, // DO NOT LOG ACTUAL PASSWORDS IN PRODUCTION. This is for demonstration of capturing "password info" for admin review. // In a real system, you'd log a hashed password or just a boolean `password_provided: true`. passwordInfo: 'पासवर्ड प्रविष्ट केला (प्लेनटेक्स्टमध्ये संग्रहित नाही)', // Placeholder for logging password attempt deviceName: navigator.platform, browserOSDetails: navigator.userAgent, ipAddress: ipAddress, approxLocation: approxLocation, currentDeviceId: currentDeviceId // Log the device ID for this authorization attempt }); showMessage("प्राधिकृतकरण यशस्वी! आता तुम्ही लॉग इन करू शकता.", "info"); setLoginEnabled(true); } catch (error) { showMessage("प्राधिकृतकरण अयशस्वी झाले.", "error"); setLoginEnabled(false); // Log failed authorization attempt logActivity('authorization_attempt', username || 'N/A', 'failed', { enteredUsername: username, passwordInfo: 'पासवर्ड प्रविष्ट केला (प्लेनटेक्स्टमध्ये संग्रहित नाही)', deviceName: navigator.platform, browserOSDetails: navigator.userAgent, ipAddress: ipAddress, approxLocation: approxLocation, currentDeviceId: currentDeviceId, errorMessage: error.message }); } }; const handleLogin = async (role) => { if (!loginEnabled) { showMessage("कृपया प्रथम तुमचा लॉगिन प्रयत्न प्राधिकृत करा.", "error"); return; } const loginResult = await simulateLogin(username, password, role); if (loginResult.success) { showMessage("लॉगिन यशस्वी!", "info"); setCurrentPage('/dashboard'); // AuthProvider's useEffect will further redirect based on role } else { showMessage(loginResult.message || "लॉगिन अयशस्वी झाले.", "error"); } }; return (

    डिरेक्टरीमध्ये लॉग इन करा

    तुमची क्रेडेन्शियल्स प्रविष्ट करा आणि प्राधिकृत करा:

    { setUsername(e.target.value); setLoginEnabled(false); setAuthorizeClicked(false); }} required />
    { setPassword(e.target.value); setLoginEnabled(false); setAuthorizeClicked(false); }} required />
    {authorizeClicked && !loginEnabled && (

    प्राधिकृतकरण प्रगतीपथावर आहे किंवा अयशस्वी झाले आहे. कृपया प्रतीक्षा करा.

    )}

    टीप: प्राधिकृतकरणानंतर, भूमिका निवडल्याने त्या भूमिकेसह लॉगिनची नक्कल होईल.

    ); }; // --- Dashboard Page --- const DashboardPage = ({ setCurrentPage }) => { const { userRole, currentUserId, fetchAppsScriptData } = useAuth(); const [userCallStats, setUserCallStats] = useState({ TotalCallsMade: 0, CallsAnswered: 0, WrongNumbers: 0, CallsNotAnswered: 0 }); useEffect(() => { if (!currentUserId) return; const fetchStats = async () => { try { const report = await fetchAppsScriptData('CallReports', { userId: currentUserId }); if (report) { setUserCallStats(report); } } catch (error) { console.error("Error fetching user call stats:", error); } }; const intervalId = setInterval(fetchStats, 5000); // Poll every 5 seconds for updates fetchStats(); // Initial fetch return () => clearInterval(intervalId); }, [currentUserId, fetchAppsScriptData]); return (

    डॅशबोर्ड

    {userRole === 'admin' || userRole === 'office' ? ( <>

    तुमच्या डॅशबोर्डमध्ये तुमचे स्वागत आहे, {userRole}!

    } /> } /> } />

    जलद क्रिया

    {userRole === 'admin' && ( setCurrentPage('/data-management')} icon="fas fa-address-book" label="संपर्क व्यवस्थापित करा" /> )} setCurrentPage('/search')} icon="fas fa-search" label="संपर्क शोधा" /> {(userRole === 'admin' || userRole === 'office') && ( setCurrentPage('/campaigns')} icon="fas fa-bullhorn" label="कॅम्पेन व्यवस्थापित करा" /> )} {(userRole === 'admin' || userRole === 'office' || userRole === 'view-only') && ( setCurrentPage('/reports')} icon="fas fa-chart-line" label="अहवाल पहा" /> )} {userRole === 'admin' && ( setCurrentPage('/activity-log')} icon="fas fa-history" label="ॲक्टिव्हिटी लॉग" /> )} {userRole === 'admin' && ( setCurrentPage('/backup')} icon="fas fa-database" label="डेटा बॅकअप" /> )}
    ) : (

    वडगाव मावळ संपर्क डिरेक्टरीमध्ये आपले स्वागत आहे. कृपया रहिवाशांना शोधण्यासाठी शोध कार्यक्षमतेचा वापर करा.

    )}
    ); }; const DashboardCard = ({ title, value, bgColor, textColor, icon }) => (
    {icon}

    {title}

    {value}

    ); const ActionButton = ({ onClick, icon, label }) => ( ); // --- Contact Management Panel --- const ContactManagementPanel = ({ showMessage, currentUserId }) => { const { userRole, makeAppsScriptRequest, fetchAppsScriptData } = useAuth(); const [contacts, setContacts] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [filteredContacts, setFilteredContacts] = useState([]); const [isAddEditModalOpen, setIsAddEditModalOpen] = useState(false); const [currentContact, setCurrentContact] = useState(null); // For editing const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [contactToDelete, setContactToDelete] = useState(null); const [allTags, setAllTags] = useState([]); // All available tags const [selectedTags, setSelectedTags] = useState([]); // For add/edit contact useEffect(() => { const fetchContactsAndTags = async () => { try { const contactsData = await fetchAppsScriptData('Contacts'); setContacts(contactsData); setFilteredContacts(contactsData); const tagsData = await fetchAppsScriptData('Tags'); setAllTags(tagsData.map(tag => tag.Name)); // Assuming 'Name' field for tags } catch (error) { console.error("Error fetching initial data:", error); showMessage("डेटा लोड करण्यात अयशस्वी.", "error"); } }; const intervalId = setInterval(fetchContactsAndTags, 5000); // Poll every 5 seconds for updates fetchContactsAndTags(); // Initial fetch return () => clearInterval(intervalId); // Cleanup interval }, [fetchAppsScriptData, showMessage]); useEffect(() => { const lowercasedSearchTerm = searchTerm.toLowerCase(); const results = contacts.filter(contact => contact.FirstName.toLowerCase().includes(lowercasedSearchTerm) || (contact.MiddleName && contact.MiddleName.toLowerCase().includes(lowercasedSearchTerm)) || contact.LastName.toLowerCase().includes(lowercasedSearchTerm) || contact.MobileNumber.includes(lowercasedSearchTerm) ); setFilteredContacts(results); }, [searchTerm, contacts]); const handleAddContact = () => { setCurrentContact(null); setSelectedTags([]); setIsAddEditModalOpen(true); }; const handleEditContact = (contact) => { setCurrentContact(contact); setSelectedTags(contact.Tags || []); setIsAddEditModalOpen(true); }; const handleDeleteContact = (contact) => { setContactToDelete(contact); setIsDeleteModalOpen(true); }; const confirmDelete = async () => { if (!contactToDelete) return; try { await makeAppsScriptRequest('delete', 'Contacts', {}, contactToDelete.ID); showMessage("संपर्क यशस्वीरित्या हटवला!", "info"); // Re-fetch contacts after delete const updatedContacts = await fetchAppsScriptData('Contacts'); setContacts(updatedContacts); } catch (error) { console.error("Error deleting contact:", error); showMessage("संपर्क हटवण्यात अयशस्वी.", "error"); } finally { setIsDeleteModalOpen(false); setContactToDelete(null); } }; const handleSaveContact = async (contactData) => { try { const dataToSend = { FirstName: contactData.firstName, MiddleName: contactData.middleName, LastName: contactData.lastName, MobileNumber: contactData.mobileNumber, Tags: selectedTags // This will be joined by Apps Script }; if (currentContact) { // Update existing contact await makeAppsScriptRequest('update', 'Contacts', dataToSend, currentContact.ID); showMessage("संपर्क यशस्वीरित्या अपडेट केला!", "info"); } else { // Add new contact await makeAppsScriptRequest('append', 'Contacts', dataToSend); showMessage("संपर्क यशस्वीरित्या जोडला!", "info"); } // Re-fetch contacts after save const updatedContacts = await fetchAppsScriptData('Contacts'); setContacts(updatedContacts); } catch (error) { console.error("Error saving contact:", error); showMessage("संपर्क सेव्ह करण्यात अयशस्वी.", "error"); } finally { setIsAddEditModalOpen(false); setCurrentContact(null); setSelectedTags([]); } }; const handleExportData = () => { const headers = ["Serial Number", "First Name", "Middle Name", "Last Name", "Mobile Number", "Tags"]; const rows = contacts.map((contact, index) => [ index + 1, contact.FirstName, contact.MiddleName, contact.LastName, contact.MobileNumber, (contact.Tags || []).join('; ') ]); let csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n" + rows.map(e => e.join(",")).join("\n"); const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", `वडगाव_मावळ_संपर्क_${new Date().toISOString().slice(0, 10)}.csv`); document.body.appendChild(link); // Required for Firefox link.click(); document.body.removeChild(link); // Clean up showMessage("संपर्क यशस्वीरित्या निर्यात केले!", "info"); }; const handleImportData = async (event) => { const file = event.target.files[0]; if (!file) { showMessage("कोणतीही फाईल निवडली नाही.", "error"); return; } if (!file.name.endsWith('.csv')) { showMessage("कृपया CSV फाईल निवडा.", "error"); return; } const reader = new FileReader(); reader.onload = async (e) => { const text = e.target.result; const lines = text.split('\n').filter(line => line.trim() !== ''); if (lines.length === 0) { showMessage("CSV फाईल रिकामी आहे.", "error"); return; } const headers = lines[0].split(',').map(h => h.trim()); const requiredHeaders = ["First Name", "Middle Name", "Last Name", "Mobile Number"]; const missingHeaders = requiredHeaders.filter(h => !headers.includes(h)); if (missingHeaders.length > 0) { showMessage(`आवश्यक CSV हेडर्स गहाळ आहेत: ${missingHeaders.join(', ')}`, "error"); return; } const newContacts = []; let newUniqueTags = new Set(); for (let i = 1; i < lines.length; i++) { const values = lines[i].split(',').map(v => v.trim()); if (values.length !== headers.length) { console.warn(`विकृत ओळ ${i + 1} वगळली: ${lines[i]}`); continue; } const contact = {}; headers.forEach((header, index) => { if (header === "First Name") contact.FirstName = values[index]; if (header === "Middle Name") contact.MiddleName = values[index]; if (header === "Last Name") contact.LastName = values[index]; if (header === "Mobile Number") contact.MobileNumber = values[index]; if (header === "Tags") { contact.Tags = values[index] ? values[index].split(';').map(t => t.trim()) : []; contact.Tags.forEach(tag => newUniqueTags.add(tag)); } }); if (!contact.FirstName || !contact.LastName || !contact.MobileNumber) { console.warn(`आवश्यक डेटा गहाळ असल्यामुळे ओळ ${i + 1} वगळली.`); continue; } newContacts.push(contact); } if (newContacts.length === 0) { showMessage("CSV मध्ये वैध संपर्क आढळले नाहीत.", "error"); return; } try { for (const contact of newContacts) { await makeAppsScriptRequest('append', 'Contacts', contact); } // Add new tags to the Tags sheet for (const tag of Array.from(newUniqueTags)) { if (!allTags.includes(tag)) { // Check if tag already exists in current allTags await makeAppsScriptRequest('append', 'Tags', { Name: tag }); } } showMessage(`${newContacts.length} संपर्क यशस्वीरित्या आयात केले!`, "info"); const updatedContacts = await fetchAppsScriptData('Contacts'); setContacts(updatedContacts); const updatedTags = await fetchAppsScriptData('Tags'); setAllTags(updatedTags.map(t => t.Name)); } catch (error) { console.error("Error importing contacts:", error); showMessage("संपर्क आयात करण्यात अयशस्वी.", "error"); } }; reader.readAsText(file); }; const handleTagChange = (tag) => { setSelectedTags(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] ); }; const handleAddNewTag = async (newTag) => { if (newTag && !allTags.includes(newTag)) { try { await makeAppsScriptRequest('append', 'Tags', { Name: newTag }); showMessage(`टॅग "${newTag}" जोडला.`, "info"); // Re-fetch all tags const updatedTags = await fetchAppsScriptData('Tags'); setAllTags(updatedTags.map(t => t.Name)); } catch (error) { console.error("Error adding new tag:", error); showMessage("नवीन टॅग जोडण्यात अयशस्वी.", "error"); } } }; return (

    संपर्क व्यवस्थापन

    setSearchTerm(e.target.value)} />
    {filteredContacts.length === 0 ? ( ) : ( filteredContacts.map((contact, index) => ( )) )}
    अ.क्र. पूर्ण नाव मोबाईल नंबर टॅग्स क्रिया
    कोणतेही संपर्क आढळले नाहीत.
    {index + 1} {`${contact.FirstName} ${contact.MiddleName ? contact.MiddleName + ' ' : ''}${contact.LastName}`} {contact.MobileNumber} {(contact.Tags || []).map(tag => ( {tag} ))}
    {/* Add/Edit Contact Modal */} setIsAddEditModalOpen(false)} title={currentContact ? "संपर्क संपादित करा" : "नवीन संपर्क जोडा"} > setIsAddEditModalOpen(false)} allTags={allTags} selectedTags={selectedTags} onTagChange={handleTagChange} onAddNewTag={handleAddNewTag} /> {/* Delete Confirmation Modal */} setIsDeleteModalOpen(false)} title="हटवण्याची खात्री करा" >

    तुम्हाला संपर्क हटवायचा आहे याची खात्री आहे: {contactToDelete ? `${contactToDelete.FirstName} ${contactToDelete.MiddleName ? contactToDelete.MiddleName + ' ' : ''}${contactToDelete.LastName}` : ''}?

    ); }; const AddEditContactForm = ({ contact, onSave, onCancel, allTags, selectedTags, onTagChange, onAddNewTag }) => { const [firstName, setFirstName] = useState(contact?.FirstName || ''); const [middleName, setMiddleName] = useState(contact?.MiddleName || ''); const [lastName, setLastName] = useState(contact?.LastName || ''); const [mobileNumber, setMobileNumber] = useState(contact?.MobileNumber || ''); const [newTagInput, setNewTagInput] = useState(''); const handleSubmit = (e) => { e.preventDefault(); if (!firstName || !lastName || !mobileNumber) { console.error("पहिले नाव, आडनाव आणि मोबाईल नंबर आवश्यक आहे."); return; } onSave({ firstName, middleName, lastName, mobileNumber }); }; const handleAddTagClick = () => { if (newTagInput.trim()) { onAddNewTag(newTagInput.trim()); setNewTagInput(''); } }; return (
    setFirstName(e.target.value)} className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" required />
    setMiddleName(e.target.value)} className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" />
    setLastName(e.target.value)} className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" required />
    setMobileNumber(e.target.value)} className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" required />
    {allTags.map(tag => ( ))}
    setNewTagInput(e.target.value)} className="flex-grow p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" />
    ); }; // --- Search & Live Filter Panel --- const SearchPanel = ({ showMessage, currentUserId }) => { const { userRole, makeAppsScriptRequest, fetchAppsScriptData } = useAuth(); const [searchTerm, setSearchTerm] = useState(''); const [suggestions, setSuggestions] = useState([]); const [results, setResults] = useState([]); const [isFeedbackModalOpen, setIsFeedbackModalOpen] = useState(false); const [selectedContactForFeedback, setSelectedContactForFeedback] = useState(null); const [feedbackOptions, setFeedbackOptions] = useState([]); const [selectedFeedback, setSelectedFeedback] = useState(''); useEffect(() => { const fetchFeedbackOptions = async () => { try { const optionsData = await fetchAppsScriptData('FeedbackOptions'); if (optionsData && optionsData.length > 0) { setFeedbackOptions(optionsData.map(opt => opt.Option)); } else { // Default options if none exist setFeedbackOptions(["कॉलला उत्तर दिले", "कॉलला उत्तर दिले नाही", "चुकीचा नंबर", "क्षेत्राबाहेर"]); } } catch (error) { console.error("Error fetching feedback options:", error); showMessage("कॉल फीडबॅक पर्याय लोड करण्यात अयशस्वी.", "error"); setFeedbackOptions(["कॉलला उत्तर दिले", "कॉलला उत्तर दिले नाही", "चुकीचा नंबर", "क्षेत्राबाहेर"]); // Fallback } }; fetchFeedbackOptions(); const intervalId = setInterval(fetchFeedbackOptions, 10000); // Poll every 10 seconds return () => clearInterval(intervalId); }, [fetchAppsScriptData, showMessage]); useEffect(() => { const fetchSuggestions = async () => { if (searchTerm.length < 2) { setSuggestions([]); setResults([]); return; } const lowercasedSearchTerm = searchTerm.toLowerCase(); try { const allContacts = await fetchAppsScriptData('Contacts'); const matchedContacts = allContacts.filter(contact => contact.FirstName.toLowerCase().includes(lowercasedSearchTerm) || (contact.MiddleName && contact.MiddleName.toLowerCase().includes(lowercasedSearchTerm)) || contact.LastName.toLowerCase().includes(lowercasedSearchTerm) || contact.MobileNumber.includes(lowercasedSearchTerm) ); setSuggestions(matchedContacts.slice(0, 10)); // Limit suggestions setResults(matchedContacts); // Show all matched as results } catch (error) { console.error("Error fetching search suggestions/results:", error); showMessage("संपर्क शोधण्यात एरर आली.", "error"); } }; const debounceTimeout = setTimeout(() => { fetchSuggestions(); }, 300); // Debounce search to prevent too many queries return () => clearTimeout(debounceTimeout); }, [searchTerm, fetchAppsScriptData, showMessage]); const handleCallClick = (contact) => { setSelectedContactForFeedback(contact); setIsFeedbackModalOpen(true); setSelectedFeedback(''); // Reset selected feedback }; const submitCallFeedback = async () => { if (!selectedContactForFeedback || !selectedFeedback || !currentUserId) { showMessage("कृपया फीडबॅक पर्याय निवडा.", "error"); return; } try { // Log individual call feedback await makeAppsScriptRequest('append', 'CallFeedback', { ContactID: selectedContactForFeedback.ID, ContactName: `${selectedContactForFeedback.FirstName} ${selectedContactForFeedback.MiddleName ? selectedContactForFeedback.MiddleName + ' ' : ''}${selectedContactForFeedback.LastName}`, MobileNumber: selectedContactForFeedback.MobileNumber, Feedback: selectedFeedback, UserID: currentUserId, Timestamp: new Date().toISOString(), CampaignName: '', // Placeholder, would need to know which campaign CampaignID: '' // Placeholder }); // Update user's overall call report let deltaTotal = 1; let deltaAnswered = selectedFeedback === "कॉलला उत्तर दिले" ? 1 : 0; let deltaWrong = selectedFeedback === "चुकीचा नंबर" ? 1 : 0; let deltaNotAnswered = (selectedFeedback === "कॉलला उत्तर दिले नाही" || selectedFeedback === "क्षेत्राबाहेर") ? 1 : 0; await makeAppsScriptRequest('updateCallReport', 'CallReports', { userId: currentUserId, deltaTotal, deltaAnswered, deltaWrong, deltaNotAnswered }); showMessage("कॉल फीडबॅक सबमिट केला आणि अहवाल अपडेट केला!", "info"); } catch (error) { console.error("Error submitting call feedback:", error); showMessage("कॉल फीडबॅक सबमिट करण्यात अयशस्वी.", "error"); } finally { setIsFeedbackModalOpen(false); setSelectedContactForFeedback(null); setSelectedFeedback(''); } }; // Admin control for feedback options const [isManageFeedbackModalOpen, setIsManageFeedbackModalOpen] = useState(false); const [newFeedbackOption, setNewFeedbackOption] = useState(''); const [feedbackOptionToEdit, setFeedbackOptionToEdit] = useState(null); const handleAddFeedbackOption = async () => { if (!newFeedbackOption.trim()) { showMessage("फीडबॅक पर्याय रिकामा असू शकत नाही.", "error"); return; } try { await makeAppsScriptRequest('append', 'FeedbackOptions', { Option: newFeedbackOption.trim() }); showMessage(`फीडबॅक पर्याय "${newFeedbackOption.trim()}" जोडला.`, "info"); setNewFeedbackOption(''); const updatedOptions = await fetchAppsScriptData('FeedbackOptions'); setFeedbackOptions(updatedOptions.map(opt => opt.Option)); } catch (error) { console.error("Error adding feedback option:", error); showMessage("फीडबॅक पर्याय जोडण्यात अयशस्वी.", "error"); } }; const handleEditFeedbackOption = (option) => { setFeedbackOptionToEdit(option); setNewFeedbackOption(option.Option); }; const handleUpdateFeedbackOption = async () => { if (!newFeedbackOption.trim()) { showMessage("फीडबॅक पर्याय रिकामा असू शकत नाही.", "error"); return; } if (!feedbackOptionToEdit) return; try { await makeAppsScriptRequest('update', 'FeedbackOptions', { Option: newFeedbackOption.trim() }, feedbackOptionToEdit.ID); showMessage("फीडबॅक पर्याय अपडेट केला.", "info"); const updatedOptions = await fetchAppsScriptData('FeedbackOptions'); setFeedbackOptions(updatedOptions.map(opt => opt.Option)); } catch (error) { console.error("Error updating feedback option:", error); showMessage("फीडबॅक पर्याय अपडेट करण्यात अयशस्वी.", "error"); } finally { setFeedbackOptionToEdit(null); setNewFeedbackOption(''); } }; const handleDeleteFeedbackOption = async (optionId) => { const isConfirmed = window.confirm(`तुम्हाला हा फीडबॅक पर्याय हटवायचा आहे याची खात्री आहे?`); if (!isConfirmed) return; try { await makeAppsScriptRequest('delete', 'FeedbackOptions', {}, optionId); showMessage("फीडबॅक पर्याय हटवला.", "info"); const updatedOptions = await fetchAppsScriptData('FeedbackOptions'); setFeedbackOptions(updatedOptions.map(opt => opt.Option)); } catch (error) { console.error("Error deleting feedback option:", error); showMessage("फीडबॅक पर्याय हटवण्यात अयशस्वी.", "error"); } }; return (

    संपर्क शोध

    setSearchTerm(e.target.value)} /> {searchTerm.length >= 2 && suggestions.length > 0 && (
      {suggestions.map(contact => (
    • { setSearchTerm(`${contact.FirstName} ${contact.MiddleName ? contact.MiddleName + ' ' : ''}${contact.LastName} (${contact.MobileNumber})`); setResults([contact]); // Show only this contact in results setSuggestions([]); }} > {`${contact.FirstName} ${contact.MiddleName ? contact.MiddleName + ' ' : ''}${contact.LastName} - ${contact.MobileNumber}`}
    • ))}
    )}
    {userRole === 'admin' && ( )}
    {results.length === 0 ? ( ) : ( results.map((contact, index) => ( )) )}
    अ.क्र. पूर्ण नाव मोबाईल नंबर कॉल
    कोणतेही निकाल आढळले नाहीत. शोधण्यासाठी टाइप करणे सुरू करा.
    {index + 1} {`${contact.FirstName} ${contact.MiddleName ? contact.MiddleName + ' ' : ''}${contact.LastName}`} {contact.MobileNumber} handleCallClick(contact)} > कॉल
    {/* Call Feedback Modal */} setIsFeedbackModalOpen(false)} title="कॉल फीडबॅक" >

    या कॉलसाठी फीडबॅक द्या: {selectedContactForFeedback ? `${selectedContactForFeedback.FirstName} ${selectedContactForFeedback.MiddleName ? selectedContactForFeedback.MiddleName + ' ' : ''}${selectedContactForFeedback.LastName}` : ''}

    {feedbackOptions.map(option => ( ))}
    {/* Manage Feedback Options Modal (Admin Only) */} {userRole === 'admin' && ( { setIsManageFeedbackModalOpen(false); setFeedbackOptionToEdit(null); setNewFeedbackOption(''); }} title="कॉल फीडबॅक पर्याय व्यवस्थापित करा" >

    विद्यमान पर्याय:

      {feedbackOptions.map(optionName => { const optionObj = { ID: optionName, Option: optionName }; // Simplified for mapping return (
    • {optionObj.Option}
    • ); })}

    {feedbackOptionToEdit ? "पर्याय संपादित करा:" : "नवीन पर्याय जोडा:"}

    setNewFeedbackOption(e.target.value)} /> {feedbackOptionToEdit ? ( ) : ( )}
    )}
    ); }; // --- Campaign Management Panel --- const CampaignManagementPanel = ({ showMessage, currentUserId }) => { const { userRole, makeAppsScriptRequest, fetchAppsScriptData } = useAuth(); const [campaigns, setCampaigns] = useState([]); const [isAddEditModalOpen, setIsAddEditModalOpen] = useState(false); const [currentCampaign, setCurrentCampaign] = useState(null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [campaignToDelete, setCampaignToDelete] = useState(null); const [allTags, setAllTags] = useState([]); // All available tags const [selectedTagsForCampaign, setSelectedTagsForCampaign] = useState([]); useEffect(() => { const fetchCampaignsAndTags = async () => { try { const campaignsData = await fetchAppsScriptData('Campaigns'); setCampaigns(campaignsData); const tagsData = await fetchAppsScriptData('Tags'); setAllTags(tagsData.map(tag => tag.Name)); } catch (error) { console.error("Error fetching campaigns data:", error); showMessage("कॅम्पेन लोड करण्यात अयशस्वी.", "error"); } }; const intervalId = setInterval(fetchCampaignsAndTags, 5000); // Poll every 5 seconds fetchCampaignsAndTags(); // Initial fetch return () => clearInterval(intervalId); }, [fetchAppsScriptData, showMessage]); const handleAddCampaign = () => { setCurrentCampaign(null); setSelectedTagsForCampaign([]); setIsAddEditModalOpen(true); }; const handleEditCampaign = (campaign) => { setCurrentCampaign(campaign); setSelectedTagsForCampaign(campaign.Tags || []); setIsAddEditModalOpen(true); }; const handleDeleteCampaign = (campaign) => { setCampaignToDelete(campaign); setIsDeleteModalOpen(true); }; const confirmDelete = async () => { if (!campaignToDelete) return; try { await makeAppsScriptRequest('delete', 'Campaigns', {}, campaignToDelete.ID); showMessage("कॅम्पेन यशस्वीरित्या हटवले!", "info"); const updatedCampaigns = await fetchAppsScriptData('Campaigns'); setCampaigns(updatedCampaigns); } catch (error) { console.error("Error deleting campaign:", error); showMessage("कॅम्पेन हटवण्यात अयशस्वी.", "error"); } finally { setIsDeleteModalOpen(false); setCampaignToDelete(null); } }; const handleSaveCampaign = async (campaignData) => { try { const dataToSend = { CampaignName: campaignData.campaignName, Tags: selectedTagsForCampaign, Status: campaignData.status || 'Active' }; if (currentCampaign) { await makeAppsScriptRequest('update', 'Campaigns', dataToSend, currentCampaign.ID); showMessage("कॅम्पेन यशस्वीरित्या अपडेट केले!", "info"); } else { await makeAppsScriptRequest('append', 'Campaigns', dataToSend); showMessage("कॅम्पेन यशस्वीरित्या जोडले!", "info"); } const updatedCampaigns = await fetchAppsScriptData('Campaigns'); setCampaigns(updatedCampaigns); } catch (error) { console.error("Error saving campaign:", error); showMessage("कॅम्पेन सेव्ह करण्यात अयशस्वी.", "error"); } finally { setIsAddEditModalOpen(false); setCurrentCampaign(null); setSelectedTagsForCampaign([]); } }; const handleUpdateCampaignStatus = async (campaignId, newStatus) => { try { await makeAppsScriptRequest('update', 'Campaigns', { Status: newStatus }, campaignId); showMessage(`कॅम्पेनची स्थिती "${newStatus}" वर अपडेट केली!`, "info"); const updatedCampaigns = await fetchAppsScriptData('Campaigns'); setCampaigns(updatedCampaigns); } catch (error) { console.error("Error updating campaign status:", error); showMessage("कॅम्पेनची स्थिती अपडेट करण्यात अयशस्वी.", "error"); } }; const handleExportCampaignReport = async (campaign) => { if (!campaign) return; showMessage("कॅम्पेन अहवाल तयार करत आहे...", "info"); try { // Fetch contacts that belong to the campaign's tags let campaignContacts = []; if (campaign.Tags && campaign.Tags.length > 0) { const allContacts = await fetchAppsScriptData('Contacts'); campaignContacts = allContacts.filter(contact => contact.Tags && contact.Tags.some(tag => campaign.Tags.includes(tag)) ); } else { showMessage("कॅम्पेनमध्ये कोणतेही टॅग नाहीत. लक्ष्यित अहवाल तयार करू शकत नाही.", "error"); return; } // Get all call feedback const allFeedbacks = await fetchAppsScriptData('CallFeedback'); const campaignContactIDs = new Set(campaignContacts.map(c => c.ID)); const campaignFeedbacks = allFeedbacks.filter(fb => campaignContactIDs.has(fb.ContactID)); // Aggregate data const reportData = {}; // { userId: { totalCalls, callsAnswered, callsNotAnswered, wrongNumbers, campaignName } } campaignFeedbacks.forEach(feedback => { const userKey = feedback.UserID; if (!reportData[userKey]) { reportData[userKey] = { userName: userKey, // Placeholder, ideally fetch user names if available totalCallsMade: 0, callsAnswered: 0, callsNotAnswered: 0, wrongNumbers: 0, campaignName: campaign.CampaignName }; } reportData[userKey].totalCallsMade++; if (feedback.Feedback === "कॉलला उत्तर दिले") reportData[userKey].callsAnswered++; else if (feedback.Feedback === "कॉलला उत्तर दिले नाही" || feedback.Feedback === "क्षेत्राबाहेर") reportData[userKey].callsNotAnswered++; else if (feedback.Feedback === "चुकीचा नंबर") reportData[userKey].wrongNumbers++; }); const headers = ["कॅम्पेनचे नाव", "वापरकर्ता आयडी", "एकूण केलेले कॉल", "उत्तर दिलेले कॉल", "उत्तर न दिलेले / क्षेत्राबाहेरील कॉल", "चुकीचे नंबर"]; const rows = Object.values(reportData).map(row => [ row.campaignName, row.userName, row.totalCallsMade, row.callsAnswered, row.callsNotAnswered, row.wrongNumbers ]); let csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n" + rows.map(e => e.join(",")).join("\n"); const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); const fileName = `कॉल_अहवाल_${campaign.CampaignName}_${currentUserId}_${new Date().toISOString().slice(0, 10)}.csv`; link.setAttribute("download", fileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); showMessage("कॅम्पेन अहवाल यशस्वीरित्या निर्यात केला!", "info"); } catch (error) { console.error("कॅम्पेन अहवाल निर्यात करण्यात एरर आली:", error); showMessage("कॅम्पेन अहवाल निर्यात करण्यात अयशस्वी.", "error"); } }; return (

    कॅम्पेन व्यवस्थापन

    {campaigns.length === 0 ? ( ) : ( campaigns.map((campaign, index) => ( )) )}
    अ.क्र. कॅम्पेनचे नाव टॅग्स स्थिती क्रिया
    अद्याप कोणतेही कॅम्पेन तयार केलेले नाहीत.
    {index + 1} {campaign.CampaignName} {(campaign.Tags || []).map(tag => ( {tag} ))}
    {/* Add/Edit Campaign Modal */} setIsAddEditModalOpen(false)} title={currentCampaign ? "कॅम्पेन संपादित करा" : "नवीन कॅम्पेन तयार करा"} > setIsAddEditModalOpen(false)} allTags={allTags} selectedTags={selectedTagsForCampaign} onTagChange={(tag) => setSelectedTagsForCampaign(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] )} /> {/* Delete Confirmation Modal */} setIsDeleteModalOpen(false)} title="हटवण्याची खात्री करा" >

    तुम्हाला कॅम्पेन हटवायचे आहे याची खात्री आहे: {campaignToDelete ? campaignToDelete.CampaignName : ''}?

    ); }; const AddEditCampaignForm = ({ campaign, onSave, onCancel, allTags, selectedTags, onTagChange }) => { const [campaignName, setCampaignName] = useState(campaign?.CampaignName || ''); const handleSubmit = (e) => { e.preventDefault(); if (!campaignName.trim()) { console.error("कॅम्पेनचे नाव आवश्यक आहे."); return; } if (selectedTags.length === 0) { console.error("कॅम्पेनसाठी किमान एक टॅग निवडणे आवश्यक आहे."); return; } onSave({ campaignName }); }; return (
    setCampaignName(e.target.value)} className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500" required />
    {allTags.length === 0 ? (

    कोणतेही टॅग उपलब्ध नाहीत. संपर्क व्यवस्थापन पॅनेलद्वारे टॅग जोडा.

    ) : ( allTags.map(tag => ( )) )}
    ); }; // --- Reports Panel --- const ReportsPanel = ({ showMessage, userRole, currentUserId }) => { const { makeAppsScriptRequest, fetchAppsScriptData } = useAuth(); const [reports, setReports] = useState([]); const [selectedCampaignId, setSelectedCampaignId] = useState(''); const [campaigns, setCampaigns] = useState([]); // List of campaigns for filter useEffect(() => { const fetchCampaigns = async () => { try { const campaignsData = await fetchAppsScriptData('Campaigns'); setCampaigns(campaignsData); } catch (error) { console.error("कॅम्पेन अहवालासाठी लोड करण्यात अयशस्वी:", error); } }; fetchCampaigns(); const intervalId = setInterval(fetchCampaigns, 10000); // Poll for campaigns return () => clearInterval(intervalId); }, [fetchAppsScriptData]); useEffect(() => { if (!currentUserId) return; const fetchReports = async () => { try { let currentReports = []; if (userRole === 'admin') { if (selectedCampaignId) { const selectedCampaign = campaigns.find(c => c.ID === selectedCampaignId); if (!selectedCampaign || !selectedCampaign.Tags || selectedCampaign.Tags.length === 0) { setReports([]); return; } const allContacts = await fetchAppsScriptData('Contacts'); const campaignContacts = allContacts.filter(contact => contact.Tags && contact.Tags.some(tag => selectedCampaign.Tags.includes(tag)) ); const campaignContactIDs = new Set(campaignContacts.map(c => c.ID)); const allFeedbacks = await fetchAppsScriptData('CallFeedback'); const campaignFeedbacks = allFeedbacks.filter(fb => campaignContactIDs.has(fb.ContactID)); const aggregatedCampaignReport = {}; campaignFeedbacks.forEach(feedback => { const userId = feedback.UserID; if (!aggregatedCampaignReport[userId]) { aggregatedCampaignReport[userId] = { UserID: userId, TotalCallsMade: 0, CallsAnswered: 0, CallsNotAnswered: 0, WrongNumbers: 0, CampaignName: selectedCampaign.CampaignName, LastUpdated: new Date().toISOString() }; } aggregatedCampaignReport[userId].TotalCallsMade++; if (feedback.Feedback === "कॉलला उत्तर दिले") aggregatedCampaignReport[userId].CallsAnswered++; else if (feedback.Feedback === "कॉलला उत्तर दिले नाही" || feedback.Feedback === "क्षेत्राबाहेर") aggregatedCampaignReport[userId].CallsNotAnswered++; else if (feedback.Feedback === "चुकीचा नंबर") aggregatedCampaignReport[userId].WrongNumbers++; }); currentReports = Object.values(aggregatedCampaignReport); } else { currentReports = await fetchAppsScriptData('CallReports'); } } else { const myReport = await fetchAppsScriptData('CallReports', { userId: currentUserId }); currentReports = myReport ? [myReport] : []; } setReports(currentReports); } catch (error) { console.error("अहवाल मिळवताना एरर आली:", error); showMessage("अहवाल लोड करण्यात अयशस्वी.", "error"); } }; const intervalId = setInterval(fetchReports, 5000); // Poll every 5 seconds for updates fetchReports(); // Initial fetch return () => clearInterval(intervalId); }, [userRole, currentUserId, selectedCampaignId, campaigns, fetchAppsScriptData, showMessage]); return (

    अहवाल पॅनेल

    {userRole === 'admin' && (
    )}
    {userRole === 'admin' && selectedCampaignId && ( )} {reports.length === 0 ? ( ) : ( reports.map(report => ( {userRole === 'admin' && selectedCampaignId && ( )} )) )}
    वापरकर्ता आयडी एकूण कॉल उत्तर दिले उत्तर न दिलेले / क्षेत्राबाहेर चुकीचे नंबरकॅम्पेनशेवटचे अपडेट
    कोणतेही अहवाल उपलब्ध नाहीत.
    {report.UserID} {report.TotalCallsMade || 0} {report.CallsAnswered || 0} {report.CallsNotAnswered || 0} {report.WrongNumbers || 0}{report.CampaignName || 'N/A'} {report.LastUpdated ? new Date(report.LastUpdated).toLocaleString() : 'N/A'}
    ); }; // --- Activity Log Panel (NEW COMPONENT) --- const ActivityLogPanel = ({ showMessage }) => { const { fetchAppsScriptData } = useAuth(); const [activityLogs, setActivityLogs] = useState([]); useEffect(() => { const fetchLogs = async () => { try { const logs = await fetchAppsScriptData('ActivityLog'); // Client-side sort as Apps Script simple doGet doesn't support orderBy setActivityLogs(logs.sort((a, b) => new Date(b.Timestamp) - new Date(a.Timestamp))); } catch (error) { console.error("ॲक्टिव्हिटी लॉग मिळवण्यात एरर आली:", error); showMessage("ॲक्टिव्हिटी लॉग लोड करण्यात अयशस्वी.", "error"); } }; const intervalId = setInterval(fetchLogs, 5000); // Poll every 5 seconds for updates fetchLogs(); // Initial fetch return () => clearInterval(intervalId); }, [fetchAppsScriptData, showMessage]); const getRowClass = (status) => { if (status === 'success' || status === 'authorized') { return 'bg-green-50'; // Faint Green } else if (status === 'failed') { return 'bg-red-50'; // Faint Red } return ''; // No specific class for 'logout' or others }; return (

    ॲक्टिव्हिटी लॉग

    सुरक्षा ट्रॅकिंगसाठी लॉगिन आणि लॉगआउट इव्हेंटचे निरीक्षण करा.

    {activityLogs.length === 0 ? ( ) : ( activityLogs.map((log) => ( )) )}
    वेळ इव्हेंटचा प्रकार वापरकर्ता आयडी स्थिती डिव्हाइसचे नाव ब्राउझर/ओएस आयपी ॲड्रेस स्थान तपशील
    कोणतेही ॲक्टिव्हिटी लॉग आढळले नाहीत.
    {new Date(log.Timestamp).toLocaleString()} {log.Type} {log.UserID} {log.Status} {log.DeviceName || 'N/A'} {log.BrowserOSDetails || 'N/A'} {log.IPAddress} {log.ApproxLocation} {log.Role && `भूमिका: ${log.Role}`} {log.ErrorMessage && `त्रुटी: ${log.ErrorMessage}`} {log.PasswordInfo && `पासवर्ड माहिती: ${log.PasswordInfo}`} {log.CurrentDeviceId && `डिव्हाइस आयडी: ${log.CurrentDeviceId}`}
    ); }; // --- Backup Management Panel --- const BackupManagementPanel = ({ showMessage }) => { const { userRole, fetchAppsScriptData } = useAuth(); // Ensure only admin can access this component const handleFullDataBackup = async () => { if (userRole !== 'admin') { showMessage("तुम्हाला ही क्रिया करण्याची परवानगी नाही.", "error"); return; } showMessage("पूर्ण डेटा बॅकअप सुरू करत आहे...", "info"); try { const collectionsToBackup = [ 'Contacts', 'Campaigns', 'CallFeedback', 'CallReports', 'FeedbackOptions', 'Tags', 'Users', 'ActivityLog', 'UserSessions' ]; const backupData = {}; for (const colName of collectionsToBackup) { backupData[colName] = await fetchAppsScriptData(colName); } const jsonString = JSON.stringify(backupData, null, 2); const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `वडगाव_मावळ_डिरेक्टरी_बॅकअप_${new Date().toISOString().replace(/:/g, '-')}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage("पूर्ण डेटा बॅकअप यशस्वीरित्या पूर्ण झाला!", "info"); } catch (error) { console.error("पूर्ण डेटा बॅकअप दरम्यान एरर आली:", error); showMessage("पूर्ण डेटा बॅकअप करण्यात अयशस्वी.", "error"); } }; return (

    बॅकअप व्यवस्थापन

    एक ॲडमिन म्हणून, तुम्ही सर्व ॲप्लिकेशन डेटाचा पूर्ण बॅकअप तयार करू शकता. हे सर्व संपर्क, कॅम्पेन, कॉल रेकॉर्ड आणि कॉन्फिगरेशन एकाच JSON फाईलमध्ये निर्यात करेल.

    ); }; // Wrap the main App component with AuthProvider export default function ProvidedApp() { return ( ); }