import React, { useState, useEffect, createContext, useContext } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from 'firebase/auth'; import { getFirestore, doc, getDoc, setDoc, collection, query, where, onSnapshot, addDoc, updateDoc, deleteDoc, getDocs } from 'firebase/firestore'; import { Home, Users, Receipt, History, ListTodo, Menu, LogOut, Plus, Search, Edit, Trash2, Save, Calendar, DollarSign, Image as ImageIcon, X, Lightbulb, Globe } from 'lucide-react'; // Added Globe icon for language selection // jsPDF and jspdf-autotable are assumed to be loaded globally via CDN in the HTML context. // This is done to resolve "Could not resolve" errors in the compilation environment. // For example, by including // and // in the HTML file that renders this React app. // Your web app's Firebase configuration - PROVIDED BY USER const firebaseConfig = { apiKey: "AIzaSyANmX9hOkVLVLF_lgzJvP3xI2COS3XUqYs", authDomain: "app-de-gestion-de-negocios.firebaseapp.com", projectId: "app-de-gestion-de-negocios", storageBucket: "app-de-gestion-de-negocios.firebasestorage.app", messagingSenderId: "70853304594", appId: "1:70853304594:web:a36c0ee73f3808173413cb", measurementId: "G-ZVXV4N4CWB" }; // Extract appId from the provided firebaseConfig const appId = firebaseConfig.projectId; // Using projectId as appId for consistent collection paths // Initialize Firebase const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); // Context for user and authentication state const AuthContext = createContext(null); // Utility function to generate a random UUID if userId is not available const generateUUID = () => crypto.randomUUID(); // Translations object const translations = { es: { appNamePro: "Pro", appNameApp: "App", loadingApp: "Cargando aplicación...", error: "Error", close: "Cerrar", confirmation: "Confirmación", confirm: "Confirmar", cancel: "Cancelar", authPageTitle: "ProApp", authPageSubtitleRegister: "Crea tu cuenta y adapta la app a tu negocio", authPageSubtitleLogin: "Inicia sesión en tu cuenta", email: "Correo Electrónico", password: "Contraseña", registerNewAccount: "Registrar Nueva Cuenta", login: "Iniciar Sesión", alreadyHaveAccount: "¿Ya tienes una cuenta? Inicia Sesión", noAccount: "¿No tienes una cuenta? Regístrate", userId: "ID de Usuario:", notAuthenticated: "No autenticado", selectBusinessType: "Selecciona tu tipo de negocio", gardener: "Jardinería y Paisajismo", plumber: "Plomería", painter: "Pintura", cleaner: "Limpieza de Casas", other: "Otro (General)", pleaseSelectBusinessType: "Por favor, selecciona un tipo de negocio.", authenticationError: "Error de autenticación:", errorAuthSetup: "Error de configuración de autenticación inicial:", errorFetchingProfile: "Error al cargar perfil de usuario:", errorLoggingOut: "Error al cerrar sesión:", dashboardWelcome: "Bienvenido a tu Dashboard,", businessType: "Tipo de Negocio:", totalClients: "Clientes Totales", pendingInvoices: "Facturas Pendientes", upcomingTasks: "Tareas Próximas", clientManagement: "Gestión de Clientes", clientManagementDesc: "Administra la información de tus clientes.", invoicing: "Facturación", invoicingDesc: "Crea y gestiona tus facturas profesionales.", taskManagement: "Gestión de Tareas", taskManagementDesc: "Organiza y sigue tus tareas pendientes.", history: "Historial", historyDesc: "Consulta el registro de actividades y transacciones.", gardenerTipTitle: "Consejo de Jardinería", gardenerTipContent: "Recuerda programar riegos profundos para céspedes en verano y fertilizar las plantas de temporada para un crecimiento óptimo.", plumberTipTitle: "Consejo de Plomería", plumberTipContent: "Inspecciona regularmente las tuberías en busca de fugas y aconseja a tus clientes sobre el mantenimiento preventivo de calentadores de agua.", generalBusinessTipTitle: "Consejo General de Negocio", generalBusinessTipContent: "Mantén tus registros al día y comunícate proactivamente con tus clientes para asegurar su satisfacción.", searchClient: "Buscar cliente por nombre o dirección...", addNewClient: "Agregar Nuevo Cliente", noClientsRegistered: "No hay clientes registrados.", editClient: "Editar Cliente", addClient: "Agregar Nuevo Cliente", fullName: "Nombre Completo", address: "Dirección", phone: "Teléfono", photoUrl: "URL de Foto (Opcional)", maintenanceFrequency: "Frecuencia de Mantenimiento", weekly: "Semanal", biWeekly: "Quincenal", monthly: "Una vez al mes", monthlyMaintenanceCost: "Costo Mensual de Mantenimiento ($)", paymentsReceived: "Pagos Recibidos (Marcar Meses Pagados)", totalPaid: "Total Pagado:", totalRemaining: "Total Restante:", lastServiceDate: "Fecha Último Servicio", commonIssue: "Problema Común", saveChanges: "Guardar Cambios", clientUpdatedSuccess: "Cliente actualizado con éxito.", clientAddedSuccess: "Cliente agregado con éxito.", errorSavingClient: "Error al guardar cliente:", deleteClientConfirm: "¿Estás seguro de que quieres eliminar este cliente? Esta acción no se puede deshacer.", clientDeletedSuccess: "Cliente eliminado con éxito.", errorDeletingClient: "Error al eliminar cliente:", invoiceDesignSection: "Diseño de Factura (Plantilla Básica)", invoiceDesignInfo: "Actualmente, la aplicación utiliza una plantilla de factura básica. En futuras actualizaciones, se podría implementar un editor visual para personalizar el diseño.", invoiceIncludes: "La factura incluirá:", yourBusinessInfo: "Tu información de negocio (placeholder)", clientInfo: "Información del cliente", maintenanceDetails: "Detalles de mantenimiento (si aplica)", extraServicesAdded: "Servicios extra agregados", totalCalculation: "Cálculo total", createNewInvoice: "Crear Nueva Factura", selectClient: "Seleccionar Cliente", searchClientInvoice: "Buscar cliente...", clientSelected: "Cliente seleccionado:", includeMonthlyMaintenance: "Incluir Mantenimiento Mensual:", selectMonth: "Selecciona Mes", extraServices: "Servicios Extra", serviceName: "Nombre del servicio", cost: "Costo ($)", addService: "Agregar Servicio", enterValidServiceCost: "Por favor, ingresa un nombre y un costo válido para el servicio.", addedServices: "Servicios Agregados:", totalExtraServices: "Total de Servicios Extra:", invoiceTotal: "Total de la Factura:", generateInvoicePDF: "Generar Factura (PDF)", saveInvoice: "Guardar Factura", selectClientToGenerateInvoice: "Por favor, selecciona un cliente para generar la factura.", jsPDFError: "Error: jsPDF no está cargado. Asegúrate de que las librerías jsPDF y jspdf-autotable estén incluidas en el HTML.", invoiceSavedSuccess: "Factura guardada con éxito.", errorSavingInvoice: "Error al guardar la factura:", savedInvoices: "Facturas Guardadas", noSavedInvoices: "No hay facturas guardadas aún.", date: "Fecha:", total: "Total:", status: "Estado:", pending: "Pendiente", paid: "Pagada", historyModuleTitle: "Historial de Actividad", historyModuleContent: "Aquí se mostrará el historial de todas las actividades importantes, como la creación de clientes, generación de facturas, etc. (En desarrollo)", taskManagementTitle: "Gestión de Tareas", addNewTask: "Agregar Nueva Tarea", taskDescription: "Descripción de la tarea...", addTask: "Agregar Tarea", taskCannotBeEmpty: "La descripción de la tarea no puede estar vacía.", taskAddedSuccess: "Tarea agregada con éxito.", errorAddingTask: "Error al agregar tarea:", taskList: "Lista de Tareas", noPendingTasks: "No hay tareas pendientes.", taskStatusUpdated: "Estado de tarea actualizado.", errorUpdatingTask: "Error al actualizar tarea:", deleteTaskConfirm: "¿Estás seguro de que quieres eliminar este cliente? Esta acción no se puede deshacer.", taskBreakdown: "Desglose de Tarea ✨", originalTask: "Tarea original:", generatingBreakdown: "Generando desglose...", noBreakdownGenerated: "No se pudo generar un desglose. Intenta con una descripción de tarea diferente.", errorCallingAI: "Error al conectar con la IA:", months: [ // Added months array for iteration { value: 'jan', label: 'Enero' }, { value: 'feb', label: 'Febrero' }, { value: 'mar', label: 'Marzo' }, { value: 'apr', label: 'Abril' }, { value: 'may', label: 'Mayo' }, { value: 'jun', label: 'Junio' }, { value: 'jul', label: 'Julio' }, { value: 'aug', label: 'Agosto' }, { value: 'sep', label: 'Septiembre' }, { value: 'oct', label: 'Octubre' }, { value: 'nov', label: 'Noviembre' }, { value: 'dec', label: 'Diciembre' }, ], deleteService: "Eliminar servicio", taskBreakdownAI: "Desglosar Tarea con IA", errorFirebaseNotInitialized: "Error: Firebase no está completamente inicializado. Inténtalo de nuevo.", invoiceProApp: "ProApp Factura", invoiceId: "Factura ID:", billTo: "Facturar a:", thanksForBusiness: "¡Gracias por tu negocio!", description: "Descripción", costTable: "Costo", maintenanceGardening: "Mantenimiento de Jardinería", monthNotSpecified: "Mes no especificado", totalServices: "Total de Servicios Extra:", selectService: "Selecciona un servicio", logout: "Cerrar Sesión", gardenNotes: "Notas del Jardín", // New translation }, en: { appNamePro: "Pro", appNameApp: "App", loadingApp: "Loading application...", error: "Error", close: "Close", confirmation: "Confirmation", confirm: "Confirm", cancel: "Cancel", authPageTitle: "ProApp", authPageSubtitleRegister: "Create your account and adapt the app to your business", authPageSubtitleLogin: "Log in to your account", email: "Email", password: "Password", registerNewAccount: "Register New Account", login: "Log In", alreadyHaveAccount: "Already have an account? Log In", noAccount: "Don't have an account? Register", userId: "User ID:", notAuthenticated: "Not Authenticated", selectBusinessType: "Select your business type", gardener: "Gardening and Landscaping", plumber: "Plumbing", painter: "Painting", cleaner: "House Cleaning", other: "Other (General)", pleaseSelectBusinessType: "Please select a business type.", authenticationError: "Authentication error:", errorAuthSetup: "Initial authentication setup error:", errorFetchingProfile: "Error fetching user profile:", errorLoggingOut: "Error logging out:", dashboardWelcome: "Welcome to your Dashboard,", businessType: "Business Type:", totalClients: "Total Clients", pendingInvoices: "Pending Invoices", upcomingTasks: "Upcoming Tasks", clientManagement: "Client Management", clientManagementDesc: "Manage your client information.", invoicing: "Invoicing", invoicingDesc: "Create and manage your professional invoices.", taskManagement: "Task Management", taskManagementDesc: "Organize and track your pending tasks.", history: "History", historyDesc: "View the activity and transaction log.", gardenerTipTitle: "Gardening Tip", gardenerTipContent: "Remember to schedule deep watering for lawns in summer and fertilize seasonal plants for optimal growth.", plumberTipTitle: "Plumbing Tip", plumberTipContent: "Regularly inspect pipes for leaks and advise your clients on preventive maintenance for water heaters.", generalBusinessTipTitle: "General Business Tip", generalBusinessTipContent: "Keep your records up to date and proactively communicate with your clients to ensure their satisfaction.", searchClient: "Search client by name or address...", addNewClient: "Add New Client", noClientsRegistered: "No clients registered.", editClient: "Edit Client", addClient: "Add New Client", fullName: "Full Name", address: "Address", phone: "Phone", photoUrl: "Photo URL (Optional)", maintenanceFrequency: "Maintenance Frequency", weekly: "Weekly", biWeekly: "Bi-Weekly", monthly: "Once a Month", monthlyMaintenanceCost: "Monthly Maintenance Cost ($)", paymentsReceived: "Payments Received (Mark Paid Months)", totalPaid: "Total Paid:", totalRemaining: "Total Remaining:", lastServiceDate: "Last Service Date", commonIssue: "Common Issue", saveChanges: "Save Changes", clientUpdatedSuccess: "Client updated successfully.", clientAddedSuccess: "Client added successfully.", errorSavingClient: "Error saving client:", deleteClientConfirm: "Are you sure you want to delete this client? This action cannot be undone.", taskBreakdown: "Task Breakdown ✨", originalTask: "Original task:", generatingBreakdown: "Generating breakdown...", noBreakdownGenerated: "Could not generate a breakdown. Try a different task description.", errorCallingAI: "Error connecting with AI:", months: [ // Added months array for iteration { value: 'jan', label: 'January' }, { value: 'feb', label: 'February' }, { value: 'mar', label: 'March' }, { value: 'apr', label: 'April' }, { value: 'may', label: 'May' }, { value: 'jun', label: 'June' }, { value: 'jul', label: 'July' }, { value: 'aug', label: 'August' }, { value: 'sep', label: 'September' }, { value: 'oct', label: 'October' }, { value: 'nov', label: 'November' }, { value: 'dec', label: 'December' }, ], deleteService: "Delete service", taskBreakdownAI: "Breakdown Task with AI", errorFirebaseNotInitialized: "Error: Firebase is not fully initialized. Please try again.", invoiceProApp: "ProApp Invoice", invoiceId: "Invoice ID:", billTo: "Bill To:", thanksForBusiness: "Thank you for your business!", description: "Description", costTable: "Cost", maintenanceGardening: "Gardening Maintenance", monthNotSpecified: "Month not specified", totalServices: "Total Extra Services:", selectService: "Select a service", logout: "Log Out", gardenNotes: "Garden Notes", // New translation }, }; // Confirmation Modal Component function ConfirmationModal({ message, onConfirm, onCancel }) { const { language } = useContext(AuthContext); const t = translations[language]; return (

{t.confirmation}

{message}

); } // LLM Task Breakdown Modal Component function TaskBreakdownModal({ taskDescription, breakdownContent, onClose, isLoading }) { const { language } = useContext(AuthContext); const t = translations[language]; return (

{t.taskBreakdown}

{t.originalTask} "{taskDescription}"

{isLoading ? (

{t.generatingBreakdown}

) : (
{breakdownContent || t.noBreakdownGenerated}
)}
); } // Main App Component function App() { const [user, setUser] = useState(null); const [businessType, setBusinessType] = useState(null); const [loadingAuth, setLoadingAuth] = useState(true); const [showAuthModal, setShowAuthModal] = useState(false); const [modalMessage, setModalMessage] = useState(''); const [showConfirmModal, setShowConfirmModal] = useState(false); const [confirmModalMessage, setConfirmModalMessage] = useState(''); const [confirmAction, setConfirmAction] = useState(null); // Function to execute on confirm const [language, setLanguage] = useState('es'); // Default language is Spanish const t = translations[language]; useEffect(() => { // Authenticate with custom token or anonymously const authenticate = async () => { try { // We no longer rely on __initial_auth_token from the environment // For a deployed app, you'd handle initial auth (e.g., check session, sign in anonymously if no user) // For this environment, we'll just proceed with onAuthStateChanged // If no user is signed in, AuthPage will handle it. } catch (error) { console.error("Error during initial authentication setup:", error); setModalMessage(`${t.errorAuthSetup} ${error.message}`); setShowAuthModal(true); } finally { // setLoadingAuth(false); // Let onAuthStateChanged handle this } }; authenticate(); // Listen for auth state changes const unsubscribe = onAuthStateChanged(auth, async (currentUser) => { setUser(currentUser); if (currentUser) { const userId = currentUser.uid; // The path for user profiles now directly uses the projectId as appId const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data'); try { const userDocSnap = await getDoc(userDocRef); if (userDocSnap.exists()) { setBusinessType(userDocSnap.data().businessType); } else { // If user exists but no profile, prompt for business type or redirect to registration console.log("User profile not found, prompting for registration details."); setBusinessType(null); // Force user to register business type } } catch (error) { console.error("Error fetching user profile:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); } } else { setBusinessType(null); } setLoadingAuth(false); // Set loading to false once auth state is determined }); return () => unsubscribe(); }, [t]); // Added t to dependency array to re-run effects if translations change const handleLogout = async () => { try { await signOut(auth); setBusinessType(null); } catch (error) { console.error("Error logging out:", error); setModalMessage(`${t.errorLoggingOut} ${error.message}`); setShowAuthModal(true); } }; const showConfirmation = (message, action) => { setConfirmModalMessage(message); setConfirmAction(() => action); // Store the action to be executed setShowConfirmModal(true); }; const handleConfirm = () => { if (confirmAction) { confirmAction(); } setShowConfirmModal(false); setConfirmAction(null); }; const handleCancelConfirm = () => { setShowConfirmModal(false); setConfirmAction(null); }; if (loadingAuth) { return (
{t.loadingApp}
); } return (
{user && businessType ? : } {showAuthModal && (

{t.error}

{modalMessage}

)} {showConfirmModal && ( )}
); } // AuthPage Component function AuthPage() { const { setBusinessType, setShowAuthModal, setModalMessage, auth, db, appId, language } = useContext(AuthContext); const t = translations[language]; const [isRegistering, setIsRegistering] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [selectedBusinessType, setSelectedBusinessType] = useState(''); const handleAuth = async (e) => { e.preventDefault(); try { if (isRegistering) { if (!selectedBusinessType) { setModalMessage(t.pleaseSelectBusinessType); setShowAuthModal(true); return; } const userCredential = await createUserWithEmailAndPassword(auth, email, password); const userId = userCredential.user.uid; const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data'); await setDoc(userDocRef, { email: email, businessType: selectedBusinessType, createdAt: new Date(), }); setBusinessType(selectedBusinessType); } else { await signInWithEmailAndPassword(auth, email, password); } } catch (error) { console.error("Authentication error:", error); setModalMessage(`${t.authenticationError} ${error.message}`); setShowAuthModal(true); } }; return (

{t.appNamePro} {t.appNameApp}

{isRegistering ? t.authPageSubtitleRegister : t.authPageSubtitleLogin}

setEmail(e.target.value)} required />
setPassword(e.target.value)} required />
{isRegistering && (
)}

{auth.currentUser?.uid ? `${t.userId} ${auth.currentUser.uid}` : t.notAuthenticated}

); } // Dashboard Component function Dashboard() { const { businessType, handleLogout, language, setLanguage } = useContext(AuthContext); const t = translations[language]; const [activeModule, setActiveModule] = useState('dashboard'); // 'dashboard', 'clients', 'invoicing', 'history', 'tasks' const [isMenuOpen, setIsMenuOpen] = useState(false); const getAppColor = () => { switch (businessType) { case 'gardener': return 'text-green-600'; case 'plumber': return 'text-blue-500'; case 'painter': return 'text-red-500'; case 'cleaner': return 'text-purple-500'; default: return 'text-gray-500'; } }; const menuItems = [ { id: 'dashboard', name: t.dashboard, icon: Home }, { id: 'clients', name: t.clientManagement, icon: Users }, { id: 'invoicing', name: t.invoicing, icon: Receipt }, { id: 'history', name: t.history, icon: History }, { id: 'tasks', name: t.taskManagement, icon: ListTodo }, ]; const renderModule = () => { switch (activeModule) { case 'dashboard': return ; case 'clients': return ; case 'invoicing': return ; case 'history': return ; case 'tasks': return ; default: return ; } }; return (
{/* Header */}
{t.appNamePro} {t.appNameApp}
{/* Language Selector */}
{/* Mobile Menu Overlay */} {isMenuOpen && (
setIsMenuOpen(false)} >
)} {/* Mobile Menu */} {/* Main Content Area */}
{/* Desktop Sidebar */} {/* Module Content */}
{renderModule()}
); } // Dashboard Content Module function DashboardContent({ businessType, setActiveModule }) { const { user, db, appId, setShowAuthModal, setModalMessage, language } = useContext(AuthContext); const t = translations[language]; const userId = user?.uid || generateUUID(); const [summaryData, setSummaryData] = useState({ totalClients: 0, pendingInvoices: 0, upcomingTasks: 0, }); useEffect(() => { if (!user) return; const fetchSummary = async () => { try { // Fetch total clients const clientsColRef = collection(db, `artifacts/${appId}/users/${userId}/clients`); const clientsSnapshot = await getDocs(clientsColRef); setSummaryData(prev => ({ ...prev, totalClients: clientsSnapshot.size })); // Fetch pending invoices (example: invoices not marked as paid) const invoicesColRef = collection(db, `artifacts/${appId}/users/${userId}/invoices`); const qInvoices = query(invoicesColRef, where('status', '==', 'pending')); // Assuming a 'status' field const invoicesSnapshot = await getDocs(qInvoices); setSummaryData(prev => ({ ...prev, pendingInvoices: invoicesSnapshot.size })); // Fetch upcoming tasks (example: tasks not completed) const tasksColRef = collection(db, `artifacts/${appId}/users/${userId}/tasks`); const qTasks = query(tasksColRef, where('completed', '==', false)); // Assuming a 'completed' field const tasksSnapshot = await getDocs(qTasks); setSummaryData(prev => ({ ...prev, upcomingTasks: tasksSnapshot.size })); } catch (error) { console.error("Error fetching dashboard summary:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); } }; fetchSummary(); }, [user, db, appId, userId, t]); // Added t to dependency array const moduleCards = [ { id: 'clients', name: t.clientManagement, icon: Users, description: t.clientManagementDesc }, { id: 'invoicing', name: t.invoicing, icon: Receipt, description: t.invoicingDesc }, { id: 'tasks', name: t.taskManagement, icon: ListTodo, description: t.taskManagementDesc }, { id: 'history', name: t.history, icon: History, description: t.historyDesc }, ]; const getBusinessSpecificContent = () => { switch (businessType) { case 'gardener': return (

{t.gardenerTipTitle}

{t.gardenerTipContent}

); case 'plumber': return (

{t.plumberTipTitle}

{t.plumberTipContent}

); // Add more business types as needed default: return (

{t.generalBusinessTipTitle}

{t.generalBusinessTipContent}

); } }; return (

{t.dashboardWelcome} {user?.email}!

{t.businessType} {businessType}

{/* Summary Cards */}

{t.totalClients}

{summaryData.totalClients}

{t.pendingInvoices}

{summaryData.pendingInvoices}

{t.upcomingTasks}

{summaryData.upcomingTasks}

{/* Business Specific Content */}
{getBusinessSpecificContent()}
{/* Module Access Cards */}
{moduleCards.map((card) => ( ))}
); } // Clients Module function ClientsModule({ businessType }) { const { user, db, appId, setShowAuthModal, setModalMessage, showConfirmation, language } = useContext(AuthContext); const t = translations[language]; const userId = user?.uid || generateUUID(); const [clients, setClients] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [showAddClientModal, setShowAddClientModal] = useState(false); const [editingClient, setEditingClient] = useState(null); // null for add, object for edit useEffect(() => { if (!user) return; const clientsColRef = collection(db, `artifacts/${appId}/users/${userId}/clients`); const q = query(clientsColRef); const unsubscribe = onSnapshot(q, (snapshot) => { const clientsData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setClients(clientsData); }, (error) => { console.error("Error fetching clients:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); }); return () => unsubscribe(); }, [user, db, appId, userId, t]); const filteredClients = clients.filter(client => client.name.toLowerCase().includes(searchTerm.toLowerCase()) || client.address.toLowerCase().includes(searchTerm.toLowerCase()) ); const handleAddClient = () => { setEditingClient(null); setShowAddClientModal(true); }; const handleEditClient = (client) => { setEditingClient(client); setShowAddClientModal(true); }; const handleDeleteClient = (clientId) => { showConfirmation(t.deleteClientConfirm, async () => { try { await deleteDoc(doc(db, `artifacts/${appId}/users/${userId}/clients`, clientId)); setModalMessage(t.clientDeletedSuccess); setShowAuthModal(true); } catch (error) { console.error("Error deleting client:", error); setModalMessage(`${t.errorDeletingClient} ${error.message}`); setShowAuthModal(true); } }); }; return (

{t.clientManagement}

setSearchTerm(e.target.value)} />
{filteredClients.length > 0 ? ( filteredClients.map(client => ( )) ) : (

{t.noClientsRegistered}

)}
{showAddClientModal && ( setShowAddClientModal(false)} /> )}
); } // Client Card Component function ClientCard({ client, businessType, onEdit, onDelete }) { const { language, db, appId, user, setShowAuthModal, setModalMessage } = useContext(AuthContext); const t = translations[language]; // Get monthly fee and payments data from client prop const clientMonthlyFee = parseFloat(client.businessTypeSpecificData?.gardener?.monthlyFee || 0); const clientPaymentsFromFirestore = client.businessTypeSpecificData?.gardener?.payments || { jan: false, feb: false, mar: false, apr: false, may: false, jun: false, jul: false, aug: false, sep: false, oct: false, nov: false, dec: false, }; // State for managing payments and calculated totals locally const [currentPayments, setCurrentPayments] = useState(clientPaymentsFromFirestore); const [currentTotalPaid, setCurrentTotalPaid] = useState(0); const [currentTotalRemaining, setCurrentTotalRemaining] = useState(0); const [hasChanges, setHasChanges] = useState(false); // Initialize state and calculate totals when client prop changes useEffect(() => { setCurrentPayments(clientPaymentsFromFirestore); calculateAndSetTotals(clientPaymentsFromFirestore, clientMonthlyFee); setHasChanges(false); // Reset changes when client prop updates console.log("ClientCard useEffect triggered for client:", client.name); console.log("Monthly Fee from client prop:", clientMonthlyFee); console.log("Payments from client prop (Firestore):", clientPaymentsFromFirestore); }, [client, clientMonthlyFee, clientPaymentsFromFirestore]); // Dependencies for re-initialization // Helper to calculate totals based on current payments and monthly fee const calculateAndSetTotals = (payments, monthlyFeeValue) => { const numPaidMonths = Object.values(payments).filter(p => p).length; const newTotalPaid = numPaidMonths * monthlyFeeValue; const newTotalRemaining = (12 - numPaidMonths) * monthlyFeeValue; setCurrentTotalPaid(newTotalPaid); setCurrentTotalRemaining(newTotalRemaining); console.log("ClientCard: calculateAndSetTotals called"); console.log(" Monthly Fee Value used in calculation:", monthlyFeeValue); console.log(" Number of Paid Months (from currentPayments state):", numPaidMonths); console.log(" New Total Paid (calculated):", newTotalPaid); console.log(" New Total Remaining (calculated):", newTotalRemaining); }; // Handle toggling of payment checkboxes const handlePaymentToggle = (monthKey) => { const updatedPayments = { ...currentPayments, [monthKey]: !currentPayments[monthKey], }; setCurrentPayments(updatedPayments); calculateAndSetTotals(updatedPayments, clientMonthlyFee); // Pass updated payments and current monthly fee setHasChanges(true); // Mark that there are unsaved changes }; // Handle saving payment changes to Firestore const handleSavePayments = async () => { try { if (!user) { setModalMessage(t.errorFirebaseNotInitialized); setShowAuthModal(true); return; } const userId = user.uid; const clientDocRef = doc(db, `artifacts/${appId}/users/${userId}/clients`, client.id); await updateDoc(clientDocRef, { 'businessTypeSpecificData.gardener.payments': currentPayments, // These fields are now redundant if UI always calculates, but good for persistence/reporting 'businessTypeSpecificData.gardener.totalPaid': currentTotalPaid, 'businessTypeSpecificData.gardener.totalRemaining': currentTotalRemaining, }); setHasChanges(false); setModalMessage(t.clientUpdatedSuccess); setShowAuthModal(true); } catch (error) { console.error("Error saving payments:", error); setModalMessage(`${t.errorSavingClient} ${error.message}`); setShowAuthModal(true); } }; const renderBusinessSpecificDetails = () => { // Use the translated months array directly from translations const months = t.months; switch (businessType) { case 'gardener': return (

Detalles de Mantenimiento

{t.maintenanceFrequency}: {t[client.businessTypeSpecificData?.gardener?.maintenanceFrequency.replace('-', '')] || client.businessTypeSpecificData?.gardener?.maintenanceFrequency || 'N/A'}

{t.monthlyMaintenanceCost}: ${clientMonthlyFee.toFixed(2)}

{t.lastServiceDate}: {client.businessTypeSpecificData?.gardener?.lastServiceDate || 'N/A'}

{t.gardenNotes}: {client.businessTypeSpecificData?.gardener?.gardenNotes || 'N/A'}

{t.totalPaid} ${currentTotalPaid.toFixed(2)}

{t.totalRemaining} ${currentTotalRemaining.toFixed(2)}

{t.paymentsReceived}

{/* Changed grid layout for months */}
{months.map((month) => (
handlePaymentToggle(month.value)} className="mr-2 rounded text-blue-600 focus:ring-blue-500" />
))}
); case 'plumber': return (

{t.lastServiceDate}: {client.businessTypeSpecificData?.plumber?.lastServiceDate || 'N/A'}

{t.commonIssue}: {client.businessTypeSpecificData?.plumber?.commonIssue || 'N/A'}

); default: return null; } }; return (
{businessType === 'gardener' && (
{client.name} { e.target.onerror = null; e.target.src = `https://placehold.co/400x200/cccccc/333333?text=Sin+Imagen`; }} /> {/* Edit Photo Icon/Menu */}
)}

{client.name}

{/* Contact Info */}

{t.address}: {client.address}

{t.phone}: {client.phone || 'N/A'}

{t.email}: {client.email || 'N/A'}

{renderBusinessSpecificDetails()}
{businessType === 'gardener' && hasChanges && ( )}
); } // Add/Edit Client Modal function AddEditClientModal({ client, businessType, onClose }) { const { user, db, appId, setShowAuthModal, setModalMessage, language } = useContext(AuthContext); const t = translations[language]; const userId = user?.uid || generateUUID(); const isEditing = !!client; const [formData, setFormData] = useState({ name: client?.name || '', address: client?.address || '', phone: client?.phone || '', email: client?.email || '', businessTypeSpecificData: client?.businessTypeSpecificData || {}, }); // Gardener specific state const [gardenerSpecificData, setGardenerSpecificData] = useState({ photoUrl: formData.businessTypeSpecificData?.gardener?.photoUrl || '', maintenanceFrequency: formData.businessTypeSpecificData?.gardener?.maintenanceFrequency || 'monthly', monthlyFee: formData.businessTypeSpecificData?.gardener?.monthlyFee || 0, payments: formData.businessTypeSpecificData?.gardener?.payments || { jan: false, feb: false, mar: false, apr: false, may: false, jun: false, jul: false, aug: false, sep: false, oct: false, nov: false, dec: false, }, lastServiceDate: formData.businessTypeSpecificData?.gardener?.lastServiceDate || '', // New field gardenNotes: formData.businessTypeSpecificData?.gardener?.gardenNotes || '', // New field }); useEffect(() => { if (businessType === 'gardener') { const numPaidMonths = Object.values(gardenerSpecificData.payments).filter(p => p).length; const totalPaid = numPaidMonths * (parseFloat(gardenerSpecificData.monthlyFee) || 0); const totalRemaining = (12 - numPaidMonths) * (parseFloat(gardenerSpecificData.monthlyFee) || 0); setFormData(prev => ({ ...prev, businessTypeSpecificData: { ...prev.businessTypeSpecificData, gardener: { ...gardenerSpecificData, totalPaid: totalPaid, totalRemaining: totalRemaining, }, }, })); } }, [gardenerSpecificData, businessType]); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleGardenerSpecificChange = (e) => { const { name, value, type, checked } = e.target; if (type === 'checkbox') { setGardenerSpecificData(prev => ({ ...prev, payments: { ...prev.payments, [name]: checked, }, })); } else if (name === 'monthlyFee') { // Ensure monthlyFee is stored as a number setGardenerSpecificData(prev => ({ ...prev, [name]: parseFloat(value) || 0 })); } else { setGardenerSpecificData(prev => ({ ...prev, [name]: value })); } }; // Modified handleFileChange to accept URL directly const handlePhotoUrlChange = (e) => { setGardenerSpecificData(prev => ({ ...prev, photoUrl: e.target.value })); }; const handleSubmit = async (e) => { e.preventDefault(); try { let clientDataToSave = { ...formData }; if (businessType === 'gardener') { clientDataToSave.businessTypeSpecificData = { ...clientDataToSave.businessTypeSpecificData, gardener: gardenerSpecificData, }; } // Add logic for other business types here if (isEditing) { await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/clients`, client.id), clientDataToSave); setModalMessage(t.clientUpdatedSuccess); } else { await addDoc(collection(db, `artifacts/${appId}/users/${userId}/clients`), clientDataToSave); setModalMessage(t.clientAddedSuccess); } setShowAuthModal(true); onClose(); } catch (error) { console.error("Error saving client:", error); setModalMessage(`${t.errorSavingClient} ${error.message}`); setShowAuthModal(true); } }; const renderBusinessSpecificFormFields = () => { const months = t.months; // Use the translated months array switch (businessType) { case 'gardener': return ( <>
{gardenerSpecificData.photoUrl && ( Client Preview { e.target.onerror = null; e.target.src = `https://placehold.co/24x24/cccccc/333333?text=Error`; }} /> )}

(Nota: Para muchas imágenes o imágenes grandes, considere usar Firebase Storage y guardar aquí solo la URL pública.)

{months.map((month) => (
))}

{t.totalPaid} ${formData.businessTypeSpecificData?.gardener?.totalPaid?.toFixed(2) || '0.00'}

{t.totalRemaining} ${formData.businessTypeSpecificData?.gardener?.totalRemaining?.toFixed(2) || '0.00'}

); case 'plumber': return ( <>
setFormData(prev => ({ ...prev, businessTypeSpecificData: { ...prev.businessTypeSpecificData, plumber: { ...prev.businessTypeSpecificData?.plumber, lastServiceDate: e.target.value } } }))} />
setFormData(prev => ({ ...prev, businessTypeSpecificData: { ...prev.businessTypeSpecificData, plumber: { ...prev.businessTypeSpecificData?.plumber, commonIssue: e.target.value } } }))} placeholder="Ej: Fugas en grifos" />
); default: return null; } }; return (

{isEditing ? t.editClient : t.addClient}

{/* General Client Fields */}
{/* Business Specific Fields */} {renderBusinessSpecificFormFields()}
); } // Invoicing Module function InvoicingModule({ businessType }) { const { user, db, appId, setShowAuthModal, setModalMessage, language } = useContext(AuthContext); const t = translations[language]; const userId = user?.uid || generateUUID(); const [clients, setClients] = useState([]); const [selectedClient, setSelectedClient] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [includeMaintenance, setIncludeMaintenance] = useState(false); const [selectedMonth, setSelectedMonth] = useState(''); const [extraService, setExtraService] = useState({ name: '', cost: '' }); const [addedServices, setAddedServices] = useState([]); const [invoices, setInvoices] = useState([]); // To display saved invoices const months = t.months; // Use the translated months array const gardenerServices = [ 'Poda de árboles y arbustos', 'Corte de césped', 'Fertilización', 'Control de plagas', 'Diseño de paisajismo', 'Instalación de riego', 'Siembra de flores', 'Mantenimiento de jardines' ]; useEffect(() => { if (!user) return; // Fetch clients for selection const clientsColRef = collection(db, `artifacts/${appId}/users/${userId}/clients`); const unsubscribeClients = onSnapshot(clientsColRef, (snapshot) => { const clientsData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setClients(clientsData); }, (error) => { console.error("Error fetching clients for invoicing:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); }); // Fetch saved invoices const invoicesColRef = collection(db, `artifacts/${appId}/users/${userId}/invoices`); const unsubscribeInvoices = onSnapshot(invoicesColRef, (snapshot) => { const invoicesData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setInvoices(invoicesData); }, (error) => { console.error("Error fetching invoices:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); }); return () => { unsubscribeClients(); unsubscribeInvoices(); }; }, [user, db, appId, userId, t]); const filteredClients = clients.filter(client => client.name.toLowerCase().includes(searchTerm.toLowerCase()) ); const handleSelectClient = (client) => { setSelectedClient(client); setSearchTerm(client.name); // Set search term to selected client's name setAddedServices([]); // Clear services when a new client is selected setIncludeMaintenance(false); // Reset maintenance checkbox setSelectedMonth(''); // Reset month }; const handleAddService = () => { if (extraService.name && parseFloat(extraService.cost) > 0) { setAddedServices([...addedServices, { ...extraService, cost: parseFloat(extraService.cost) }]); setExtraService({ name: '', cost: '' }); } else { setModalMessage(t.enterValidServiceCost); setShowAuthModal(true); } }; const calculateTotal = () => { let total = 0; if (selectedClient && includeMaintenance && businessType === 'gardener') { const monthlyFee = selectedClient.businessTypeSpecificData?.gardener?.monthlyFee || 0; total += parseFloat(monthlyFee); } addedServices.forEach(service => { total += service.cost; }); return total; }; const generatePDF = () => { if (!selectedClient) { setModalMessage(t.selectClientToGenerateInvoice); setShowAuthModal(true); return; } // Ensure jsPDF is available globally if (typeof window.jsPDF === 'undefined') { setModalMessage(t.jsPDFError); setShowAuthModal(true); return; } const doc = new window.jsPDF.jsPDF(); // Access jsPDF from window object const invoiceDate = new Date().toLocaleDateString('es-ES'); // Keep date format consistent for now const totalAmount = calculateTotal(); // Header doc.setFontSize(24); doc.setTextColor(50, 50, 50); doc.text(t.invoiceProApp, 105, 20, null, null, 'center'); doc.setFontSize(10); doc.setTextColor(100, 100, 100); doc.text(`${t.date} ${invoiceDate}`, 170, 30); doc.text(`${t.invoiceId} ${generateUUID().substring(0, 8).toUpperCase()}`, 170, 35); // Simple ID // Business Info (Placeholder) doc.setFontSize(12); doc.setTextColor(50, 50, 50); doc.text("Tu Negocio S.A. de C.V.", 20, 45); // Placeholder, could be translated or user-configurable doc.text("Dirección de tu Negocio, Ciudad, País", 20, 50); // Placeholder doc.text("Teléfono: (123) 456-7890", 20, 55); // Placeholder doc.text("Email: info@tunegocio.com", 20, 60); // Placeholder // Client Info doc.setFontSize(12); doc.setTextColor(50, 50, 50); doc.text(`${t.billTo}:`, 20, 75); doc.setFontSize(14); doc.text(selectedClient.name, 20, 82); doc.setFontSize(12); doc.text(selectedClient.address, 20, 88); if (selectedClient.phone) doc.text(`${t.phone}: ${selectedClient.phone}`, 20, 94); if (selectedClient.email) doc.text(`${t.email}: ${selectedClient.email}`, 20, 100); // Invoice Items Table const tableColumn = [t.description, t.costTable]; const tableRows = []; if (includeMaintenance && businessType === 'gardener') { const maintenanceFee = selectedClient.businessTypeSpecificData?.gardener?.monthlyFee || 0; const monthLabel = months.find(m => m.value === selectedMonth)?.label || t.monthNotSpecified; tableRows.push([`${t.maintenanceGardening} (${monthLabel})`, `$${maintenanceFee.toFixed(2)}`]); } addedServices.forEach(service => { tableRows.push([service.name, `$${service.cost.toFixed(2)}`]); }); doc.autoTable(tableColumn, tableRows, { startY: 115, headStyles: { fillColor: [60, 140, 200], textColor: 255 }, styles: { cellPadding: 3, fontSize: 10, overflow: 'linebreak' }, columnStyles: { 0: { cellWidth: 140 }, 1: { cellWidth: 40, halign: 'right' }, }, margin: { left: 20, right: 20 }, }); // Total const finalY = doc.autoTable.previous.finalY; doc.setFontSize(14); doc.setTextColor(50, 50, 50); doc.text(`${t.total}: $${totalAmount.toFixed(2)}`, 190, finalY + 15, null, null, 'right'); // Footer doc.setFontSize(10); doc.setTextColor(150, 150, 150); doc.text(t.thanksForBusiness, 105, doc.internal.pageSize.height - 20, null, null, 'center'); doc.save(`Factura_${selectedClient.name.replace(/\s/g, '_')}_${invoiceDate.replace(/\//g, '-')}.pdf`); }; const handleSaveInvoice = async () => { if (!selectedClient) { setModalMessage(t.selectClientToGenerateInvoice); setShowAuthModal(true); return; } try { const invoiceData = { clientId: selectedClient.id, clientName: selectedClient.name, date: new Date().toISOString(), month: selectedMonth, includeMaintenance: includeMaintenance, maintenanceFee: includeMaintenance && businessType === 'gardener' ? (selectedClient.businessTypeSpecificData?.gardener?.monthlyFee || 0) : 0, extraServices: addedServices, totalAmount: calculateTotal(), status: 'pending', // You might add 'paid', 'sent', etc. }; await addDoc(collection(db, `artifacts/${appId}/users/${userId}/invoices`), invoiceData); setModalMessage(t.invoiceSavedSuccess); setShowAuthModal(true); // Clear form after saving setSelectedClient(null); setSearchTerm(''); setIncludeMaintenance(false); setSelectedMonth(''); setExtraService({ name: '', cost: '' }); setAddedServices([]); } catch (error) { console.error("Error saving invoice:", error); setModalMessage(`${t.errorSavingInvoice} ${error.message}`); setShowAuthModal(true); } }; return (

{t.invoicing}

{/* Invoice Design Section (Simplified) */}

{t.invoiceDesignSection}

{t.invoiceDesignInfo}

{t.invoiceIncludes}

  • {t.yourBusinessInfo}
  • {t.clientInfo}
  • {t.maintenanceDetails}
  • {t.extraServicesAdded}
  • {t.totalCalculation}
{/* Create Invoice Section */}

{t.createNewInvoice}

{/* Client Selector */}
{ setSearchTerm(e.target.value); setSelectedClient(null); // Deselect client if typing }} /> {searchTerm && !selectedClient && filteredClients.length > 0 && (
    {filteredClients.map(client => (
  • handleSelectClient(client)} className="px-4 py-2 cursor-pointer hover:bg-gray-100" > {client.name} ({client.address})
  • ))}
)} {searchTerm && !selectedClient && filteredClients.length === 0 && (

{t.noClientsRegistered}

)}
{selectedClient && (

{t.clientSelected} {selectedClient.name}

)}
{/* Gardener Specific: Maintenance Fee */} {selectedClient && businessType === 'gardener' && selectedClient.businessTypeSpecificData?.gardener?.monthlyFee > 0 && (
setIncludeMaintenance(e.target.checked)} className="rounded text-blue-600 focus:ring-blue-500" /> {includeMaintenance && ( )}
)} {/* Extra Services */}
{businessType === 'gardener' ? ( ) : ( setExtraService(prev => ({ ...prev, name: e.target.value }))} /> )} setExtraService(prev => ({ ...prev, cost: e.target.value }))} min="0" step="0.01" />
{addedServices.length > 0 && (

{t.addedServices}

    {addedServices.map((service, index) => (
  • {service.name} ${service.cost.toFixed(2)}
  • ))}
{t.totalExtraServices} ${addedServices.reduce((sum, s) => sum + s.cost, 0).toFixed(2)}
)}
{t.invoiceTotal} ${calculateTotal().toFixed(2)}
{/* Saved Invoices Section */}

{t.savedInvoices}

{invoices.length === 0 ? (

{t.noSavedInvoices}

) : (
{invoices.map(invoice => (

{invoice.clientName}

{t.date} {new Date(invoice.date).toLocaleDateString('es-ES')}

{t.total} ${invoice.totalAmount.toFixed(2)}

{t.status}: {invoice.status === 'pending' ? t.pending : t.paid}

{/* You could add buttons here to view PDF again or mark as paid */}
))}
)}
); } // History Module (Placeholder) function HistoryModule() { const { language } = useContext(AuthContext); const t = translations[language]; return (

{t.historyModuleTitle}

{t.historyModuleContent}

); } // Tasks Module (Placeholder) function TasksModule() { const { user, db, appId, setShowAuthModal, setModalMessage, showConfirmation, businessType, language } = useContext(AuthContext); const t = translations[language]; const userId = user?.uid || generateUUID(); const [tasks, setTasks] = useState([]); const [newTask, setNewTask] = useState(''); const [showBreakdownModal, setShowBreakdownModal] = useState(false); const [currentTaskForBreakdown, setCurrentTaskForBreakdown] = useState(''); const [llmBreakdownContent, setLlmBreakdownContent] = useState(''); const [isGeneratingBreakdown, setIsGeneratingBreakdown] = useState(false); useEffect(() => { if (!user) return; const tasksColRef = collection(db, `artifacts/${appId}/users/${userId}/tasks`); const q = query(tasksColRef); const unsubscribe = onSnapshot(q, (snapshot) => { const tasksData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setTasks(tasksData); }, (error) => { console.error("Error fetching tasks:", error); setModalMessage(`${t.errorFetchingProfile} ${error.message}`); setShowAuthModal(true); }); return () => unsubscribe(); }, [user, db, appId, userId, t]); const handleAddTask = async () => { if (newTask.trim() === '') { setModalMessage(t.taskCannotBeEmpty); setShowAuthModal(true); return; } try { await addDoc(collection(db, `artifacts/${appId}/users/${userId}/tasks`), { description: newTask, completed: false, createdAt: new Date().toISOString(), }); setNewTask(''); setModalMessage(t.taskAddedSuccess); setShowAuthModal(true); } catch (error) { console.error("Error adding task:", error); setModalMessage(`${t.errorAddingTask} ${error.message}`); setShowAuthModal(true); } }; const handleToggleComplete = async (taskId, currentStatus) => { try { await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, taskId), { completed: !currentStatus, }); setModalMessage(t.taskStatusUpdated); setShowAuthModal(true); } catch (error) { console.error("Error updating task status:", error); setModalMessage(`${t.errorUpdatingTask} ${error.message}`); setShowAuthModal(true); } }; const handleDeleteTask = (taskId) => { showConfirmation(t.deleteTaskConfirm, async () => { try { await deleteDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, taskId)); setModalMessage(t.taskDeletedSuccess); setShowAuthModal(true); } catch (error) { console.error("Error deleting task:", error); setModalMessage(`${t.errorDeletingTask} ${error.message}`); setShowAuthModal(true); } }); }; const handleGenerateTaskBreakdown = async (taskDescription) => { setCurrentTaskForBreakdown(taskDescription); setLlmBreakdownContent(''); // Clear previous content setShowBreakdownModal(true); setIsGeneratingBreakdown(true); try { let chatHistory = []; const prompt = `Desglosa la siguiente tarea para un negocio de ${businessType} en pasos detallados, incluyendo materiales y herramientas necesarias. Formatea la respuesta como una lista de pasos y luego una lista de materiales/herramientas. Tarea: "${taskDescription}"`; chatHistory.push({ role: "user", parts: [{ text: prompt }] }); const payload = { contents: chatHistory }; const apiKey = ""; // Canvas will inject the API key const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const result = await response.json(); if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0) { const text = result.candidates[0].content.parts[0].text; setLlmBreakdownContent(text); } else { setLlmBreakdownContent(t.noBreakdownGenerated); console.error("Gemini API response was empty or malformed:", result); } } catch (error) { console.error("Error calling Gemini API:", error); setLlmBreakdownContent(`${t.errorCallingAI} ${error.message}`); } finally { setIsGeneratingBreakdown(false); } }; return (

{t.taskManagementTitle}

{t.addNewTask}

setNewTask(e.target.value)} />

{t.taskList}

{tasks.length === 0 ? (

{t.noPendingTasks}

) : (
    {tasks.map(task => (
  • handleToggleComplete(task.id, task.completed)} className="rounded text-green-600 focus:ring-green-500 mr-3" /> {task.description}
  • ))}
)}
{showBreakdownModal && ( setShowBreakdownModal(false)} /> )}
); } export default App;