import React, { useState, useEffect } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { Task } from '../../entities/Task'; import { Project } from '../../entities/Project'; import { Note } from '../../entities/Note'; import { loadInboxItemsToStore, loadMoreInboxItemsToStore, processInboxItemWithStore, deleteInboxItemWithStore, updateInboxItemWithStore, } from '../../utils/inboxService'; import InboxItemDetail from './InboxItemDetail'; import { useToast } from '../Shared/ToastContext'; import { useTranslation } from 'react-i18next'; import { InboxIcon, InformationCircleIcon } from '@heroicons/react/24/outline'; import LoadingScreen from '../Shared/LoadingScreen'; import ProjectModal from '../Project/ProjectModal'; import NoteModal from '../Note/NoteModal'; import QuickCaptureInput from './QuickCaptureInput'; import { createTask } from '../../utils/tasksService'; import { createProject } from '../../utils/projectsService'; import { createNote } from '../../utils/notesService'; import { isUrl } from '../../utils/urlService'; import { fetchAreas } from '../../utils/areasService'; import { fetchProjects } from '../../utils/projectsService'; import { useStore } from '../../store/useStore'; const InboxItems: React.FC = () => { const { t } = useTranslation(); const { showSuccessToast, showErrorToast } = useToast(); const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const [hasInitialized, setHasInitialized] = useState(false); const { inboxItems, isLoading, pagination } = useStore( (state) => state.inboxStore ); const { areas, setAreas, setError: setAreasError, } = useStore((state) => state.areasStore); const { loadTags, hasLoaded: tagsHasLoaded, isLoading: tagsLoading, } = useStore((state) => state.tagsStore); const [projects, setProjects] = useState([]); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [isNoteModalOpen, setIsNoteModalOpen] = useState(false); const [isInfoExpanded, setIsInfoExpanded] = useState(false); const [projectToEdit, setProjectToEdit] = useState(null); const [noteToEdit, setNoteToEdit] = useState(null); const [currentConversionItemUid, setCurrentConversionItemUid] = useState< string | null >(null); useEffect(() => { const urlPageSize = searchParams.get('loaded'); const currentLoadedCount = urlPageSize ? parseInt(urlPageSize, 10) : 20; loadInboxItemsToStore(true, currentLoadedCount); setHasInitialized(true); const loadInitialProjects = async () => { try { const projectData = await fetchProjects(); setProjects(Array.isArray(projectData) ? projectData : []); } catch (error) { console.error('Failed to load initial projects:', error); setProjects([]); } }; loadInitialProjects(); const loadInitialAreas = async () => { try { const areasData = await fetchAreas(); setAreas(areasData); } catch (error) { console.error('Failed to load initial areas:', error); setAreasError(true); } }; loadInitialAreas(); const loadInitialTags = async () => { if (!tagsHasLoaded && !tagsLoading) { try { await loadTags(); } catch (error) { console.error('Failed to load initial tags:', error); } } }; loadInitialTags(); const handleForceReload = () => { setTimeout(() => { const currentInboxStore = useStore.getState().inboxStore; const currentCount = currentInboxStore.inboxItems.length; loadInboxItemsToStore(false, currentCount); }, 500); }; const handleInboxItemsUpdated = ( event: CustomEvent<{ count: number; firstItemContent: string }> ) => { if (event.detail.count > 0) { showSuccessToast( t( 'inbox.newTelegramItem', 'New item from Telegram: {{content}}', { content: event.detail.firstItemContent, } ) ); if (event.detail.count > 1) { showSuccessToast( t( 'inbox.multipleNewItems', '{{count}} more new items added', { count: event.detail.count - 1, } ) ); } } }; const pollInterval = setInterval(() => { const currentInboxStore = useStore.getState().inboxStore; const currentCount = currentInboxStore.inboxItems.length; loadInboxItemsToStore(false, currentCount); }, 15000); window.addEventListener('forceInboxReload', handleForceReload); window.addEventListener( 'inboxItemsUpdated', handleInboxItemsUpdated as EventListener ); return () => { clearInterval(pollInterval); window.removeEventListener('forceInboxReload', handleForceReload); window.removeEventListener( 'inboxItemsUpdated', handleInboxItemsUpdated as EventListener ); }; }, [t, showSuccessToast]); useEffect(() => { if (!hasInitialized) return; const urlPageSize = searchParams.get('loaded'); const urlLoadedCount = urlPageSize ? parseInt(urlPageSize, 10) : 0; if (inboxItems.length > 20 && inboxItems.length !== urlLoadedCount) { setSearchParams( { loaded: inboxItems.length.toString() }, { replace: true } ); } else if (inboxItems.length <= 20 && urlLoadedCount > 0) { setSearchParams({}, { replace: true }); } }, [inboxItems.length, hasInitialized]); const handleProcessItem = async ( uid: string, showToast: boolean = true ) => { try { await processInboxItemWithStore(uid); if (showToast) { showSuccessToast(t('inbox.itemProcessed')); } } catch (error) { console.error('Failed to process inbox item:', error); showErrorToast(t('inbox.processError')); } }; const handleUpdateItem = async ( uid: string, newContent: string ): Promise => { try { await updateInboxItemWithStore(uid, newContent); showSuccessToast(t('inbox.itemUpdated')); } catch (error) { console.error('Failed to update inbox item:', error); showErrorToast(t('inbox.updateError')); } }; const handleDeleteItem = async (uid: string) => { try { await deleteInboxItemWithStore(uid); showSuccessToast(t('inbox.itemDeleted')); } catch (error) { console.error('Failed to delete inbox item:', error); showErrorToast(t('inbox.deleteError')); } }; const createTaskAndHandleConversion = async ( taskData: Task, options: { inboxItemUid?: string; navigateAfterCreate?: boolean } = {} ) => { try { const createdTask = await createTask(taskData); const taskLink = ( {t('task.created', 'Task')}{' '} {createdTask.name} {' '} {t('task.createdSuccessfully', 'created successfully!')} ); showSuccessToast(taskLink); const inboxUid = options.inboxItemUid ?? currentConversionItemUid ?? undefined; if (inboxUid) { await handleProcessItem(inboxUid, false); if (!options.inboxItemUid) { setCurrentConversionItemUid(null); } } if (options.navigateAfterCreate && createdTask.uid) { navigate(`/task/${createdTask.uid}`); } return createdTask; } catch (error) { console.error('Failed to create task:', error); showErrorToast(t('task.createError')); throw error; } finally { if (options.inboxItemUid) { setCurrentConversionItemUid(null); } } }; const handleOpenTaskModal = async (task: Task, inboxItemUid?: string) => { if (inboxItemUid) { setCurrentConversionItemUid(inboxItemUid); } try { await createTaskAndHandleConversion(task, { inboxItemUid, navigateAfterCreate: true, }); } catch { // Errors are already reported via toast notifications } }; const handleOpenProjectModal = async ( project: Project | null, inboxItemUid?: string ) => { try { try { const areasData = await fetchAreas(); setAreas(areasData); } catch (error) { console.error('Failed to load areas:', error); showErrorToast(t('area.loadError', 'Failed to load areas')); setAreas([]); } setProjectToEdit(project); if (inboxItemUid) { setCurrentConversionItemUid(inboxItemUid); } setIsProjectModalOpen(true); } catch (error) { console.error('Failed to open project modal:', error); } }; const handleOpenNoteModal = async ( note: Note | null, inboxItemUid?: string ) => { if (note && note.content && isUrl(note.content.trim())) { if (!note.tags) { note.tags = [{ name: 'bookmark' }]; } else if (!note.tags.some((tag) => tag.name === 'bookmark')) { note.tags.push({ name: 'bookmark' }); } } setNoteToEdit(note); if (inboxItemUid) { setCurrentConversionItemUid(inboxItemUid); } setIsNoteModalOpen(true); }; const handleSaveTask = async (task: Task) => { await createTaskAndHandleConversion(task); }; const handleSaveProject = async (project: Project) => { try { await createProject(project); showSuccessToast(t('project.createSuccess')); if (currentConversionItemUid !== null) { await handleProcessItem(currentConversionItemUid, false); setCurrentConversionItemUid(null); } } catch (error) { console.error('Failed to create project:', error); showErrorToast(t('project.createError')); } }; const handleSaveNote = async (note: Note) => { try { const noteContent = note.content || ''; const isBookmarkContent = isUrl(noteContent.trim()); if (!note.tags) { note.tags = []; } if ( isBookmarkContent && !note.tags.some((tag) => tag.name === 'bookmark') ) { note.tags = [...note.tags, { name: 'bookmark' }]; } await createNote(note); showSuccessToast( t('note.createSuccess', 'Note created successfully') ); if (currentConversionItemUid !== null) { await handleProcessItem(currentConversionItemUid, false); setCurrentConversionItemUid(null); } setIsNoteModalOpen(false); } catch (error) { console.error('Failed to create note:', error); showErrorToast(t('note.createError', 'Failed to create note')); } }; const handleCreateProject = async (name: string): Promise => { try { const project = await createProject({ name, status: 'planned' }); showSuccessToast(t('project.createSuccess')); return project; } catch (error) { console.error('Failed to create project:', error); showErrorToast(t('project.createError')); throw error; } }; const handleLoadMore = async () => { try { await loadMoreInboxItemsToStore(); } catch (error) { console.error('Failed to load more inbox items:', error); showErrorToast( t('inbox.loadMoreError', 'Failed to load more items') ); } }; if (isLoading && inboxItems.length === 0) { return ; } return (

{t('inbox.title')}

{t( 'taskViews.inbox', "Inbox is where all uncategorized tasks are located. Tasks that have not been assigned to a project or don't have a due date will appear here. This is your 'brain dump' area where you can quickly note down tasks and organize them later." )}

{inboxItems.length > 0 && (
{inboxItems.map((item) => ( ))}
{pagination.hasMore && (
)} {inboxItems.length > 0 && (
{t( 'inbox.showingItems', 'Showing {{current}} of {{total}} items', { current: inboxItems.length, total: pagination.total, } )}
)}
)} {(() => { return ( isProjectModalOpen && (() => { try { return ( { setIsProjectModalOpen(false); setProjectToEdit(null); }} onSave={handleSaveProject} project={projectToEdit || undefined} areas={ Array.isArray(areas) ? areas : [] } /> ); } catch (error) { console.error( 'ProjectModal rendering error:', error ); return null; } })() ); })()} {(() => { try { return ( { setIsNoteModalOpen(false); setNoteToEdit(null); }} onSave={handleSaveNote} note={noteToEdit} projects={ Array.isArray(projects) ? projects : [] } onCreateProject={handleCreateProject} /> ); } catch (error) { console.error('NoteModal rendering error:', error); return null; } })()}
); }; export default InboxItems;