Fix an issue with create-from buttons in inbox

This commit is contained in:
Chris Veleris 2025-07-22 10:51:26 +03:00 committed by antanst
parent 84c29f7a12
commit cf8b1c9709
5 changed files with 196 additions and 138 deletions

View file

@ -216,11 +216,13 @@ const InboxItemDetail: React.FC<InboxItemDetailProps> = ({
const cleanedContent = cleanTextFromTagsAndProjects(item.content);
const handleConvertToTask = () => {
try {
// Convert hashtags to Tag objects
const taskTags = hashtags.map((hashtagName) => {
// Find existing tag or create a placeholder for new tag
const existingTag = tags.find(
(tag) => tag.name.toLowerCase() === hashtagName.toLowerCase()
(tag) =>
tag.name.toLowerCase() === hashtagName.toLowerCase()
);
return existingTag || { name: hashtagName };
});
@ -252,14 +254,19 @@ const InboxItemDetail: React.FC<InboxItemDetailProps> = ({
} else {
openTaskModal(newTask);
}
} catch (error) {
console.error('Error converting to task:', error);
}
};
const handleConvertToProject = () => {
try {
// Convert hashtags to Tag objects (ignore any existing project references)
const projectTags = hashtags.map((hashtagName) => {
// Find existing tag or create a placeholder for new tag
const existingTag = tags.find(
(tag) => tag.name.toLowerCase() === hashtagName.toLowerCase()
(tag) =>
tag.name.toLowerCase() === hashtagName.toLowerCase()
);
return existingTag || { name: hashtagName };
});
@ -276,6 +283,9 @@ const InboxItemDetail: React.FC<InboxItemDetailProps> = ({
} else {
openProjectModal(newProject);
}
} catch (error) {
console.error('Error converting to project:', error);
}
};
const handleConvertToNote = async () => {

View file

@ -22,6 +22,7 @@ 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 { useStore } from '../../store/useStore';
const InboxItems: React.FC = () => {
@ -30,6 +31,16 @@ const InboxItems: React.FC = () => {
// Access store data
const { inboxItems, isLoading } = useStore((state) => state.inboxStore);
const {
areas,
setAreas,
setError: setAreasError,
} = useStore((state) => state.areasStore);
const {
loadTags,
hasLoaded: tagsHasLoaded,
isLoading: tagsLoading,
} = useStore((state) => state.tagsStore);
// Modal states
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
@ -70,6 +81,30 @@ const InboxItems: React.FC = () => {
};
loadInitialProjects();
// Load areas initially
const loadInitialAreas = async () => {
try {
const areasData = await fetchAreas();
setAreas(areasData);
} catch (error) {
console.error('Failed to load initial areas:', error);
setAreasError(true);
}
};
loadInitialAreas();
// Load tags initially
const loadInitialTags = async () => {
if (!tagsHasLoaded && !tagsLoading) {
try {
await loadTags();
} catch (error) {
console.error('Failed to load initial tags:', error);
}
}
};
loadInitialTags();
// Set up an event listener for force reload
const handleForceReload = () => {
// Wait a short time to ensure the backend has processed the new item
@ -178,6 +213,7 @@ const InboxItems: React.FC = () => {
// Modal handlers
const handleOpenTaskModal = async (task: Task, inboxItemId?: number) => {
try {
// Load projects first before opening the modal
try {
const projectData = await fetchProjects();
@ -185,7 +221,9 @@ const InboxItems: React.FC = () => {
setProjects(Array.isArray(projectData) ? projectData : []);
} catch (error) {
console.error('Failed to load projects:', error);
showErrorToast(t('project.loadError', 'Failed to load projects'));
showErrorToast(
t('project.loadError', 'Failed to load projects')
);
setProjects([]); // Ensure we have an empty array even on error
}
@ -196,12 +234,26 @@ const InboxItems: React.FC = () => {
}
setIsTaskModalOpen(true);
} catch (error) {
console.error('Failed to open task modal:', error);
}
};
const handleOpenProjectModal = (
const handleOpenProjectModal = async (
project: Project | null,
inboxItemId?: number
) => {
try {
// Load areas first before opening the modal (similar to task modal)
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([]); // Ensure we have an empty array even on error
}
setProjectToEdit(project);
if (inboxItemId) {
@ -209,6 +261,9 @@ const InboxItems: React.FC = () => {
}
setIsProjectModalOpen(true);
} catch (error) {
console.error('Failed to open project modal:', error);
}
};
const handleOpenNoteModal = async (
@ -490,7 +545,9 @@ const InboxItems: React.FC = () => {
})()}
{/* Project Modal - Only render when needed to prevent infinite loops */}
{isProjectModalOpen &&
{(() => {
return (
isProjectModalOpen &&
(() => {
try {
return (
@ -502,7 +559,9 @@ const InboxItems: React.FC = () => {
}}
onSave={handleSaveProject}
project={projectToEdit || undefined}
areas={[]}
areas={
Array.isArray(areas) ? areas : []
}
/>
);
} catch (error) {
@ -512,6 +571,8 @@ const InboxItems: React.FC = () => {
);
return null;
}
})()
);
})()}
{/* Note Modal - Always render it but control visibility with isOpen */}

View file

@ -43,12 +43,7 @@ const NoteModal: React.FC<NoteModalProps> = ({
}) => {
const { t } = useTranslation();
const {
tagsStore: {
tags: availableTagsStore,
loadTags,
isLoading: tagsLoading,
hasLoaded: tagsHasLoaded,
},
tagsStore: { tags: availableTagsStore },
} = useStore();
const [formData, setFormData] = useState<Note>(
note || {
@ -87,17 +82,11 @@ const NoteModal: React.FC<NoteModalProps> = ({
useEffect(() => {
if (!isOpen) return;
if (!tagsHasLoaded && !tagsLoading) {
loadTags();
}
// Auto-focus on the title input when modal opens
if (isOpen) {
setTimeout(() => {
titleInputRef.current?.focus();
}, 100);
}
}, [isOpen, tagsHasLoaded, tagsLoading]);
}, [isOpen]);
// Initialize filtered projects from props - like TaskModal
useEffect(() => {

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { Area } from '../../entities/Area';
import { Project } from '../../entities/Project';
import ConfirmDialog from '../Shared/ConfirmDialog';
@ -61,12 +62,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
const [isUploading, setIsUploading] = useState(false);
const { tagsStore } = useStore();
const {
tags: availableTags,
loadTags,
isLoading: tagsLoading,
hasLoaded: tagsHasLoaded,
} = tagsStore;
const { tags: availableTags } = tagsStore;
const modalRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
@ -88,19 +84,26 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
const { showSuccessToast, showErrorToast } = useToast();
const { t } = useTranslation();
// Load tags when modal opens
useEffect(() => {
if (isOpen && !tagsHasLoaded && !tagsLoading) {
loadTags();
}
// Auto-focus on the name input when modal opens
useEffect(() => {
if (isOpen) {
setTimeout(() => {
nameInputRef.current?.focus();
}, 100);
}, 200);
}
}, [isOpen, tagsHasLoaded, tagsLoading]);
}, [isOpen]);
// Manage body scroll when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
useEffect(() => {
if (project) {
@ -404,8 +407,12 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
if (!isOpen) return null;
return (
// Don't render if areas aren't loaded yet (prevents race condition)
if (!areas || !Array.isArray(areas)) return null;
return createPortal(
<>
,
<div
className={`fixed top-16 left-0 right-0 bottom-0 flex items-start sm:items-center justify-center bg-gray-900 bg-opacity-80 z-40 transition-opacity duration-300 ${
isClosing ? 'opacity-0' : 'opacity-100'
@ -868,7 +875,6 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
</div>
</div>
</div>
{showConfirmDialog && (
<ConfirmDialog
title="Delete Project"
@ -877,7 +883,8 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
onCancel={() => setShowConfirmDialog(false)}
/>
)}
</>
</>,
document.body
);
};

View file

@ -52,12 +52,7 @@ const TaskModal: React.FC<TaskModalProps> = ({
onEditParentTask,
}) => {
const {
tagsStore: {
tags: availableTags,
loadTags,
isLoading: tagsLoading,
hasLoaded: tagsHasLoaded,
},
tagsStore: { tags: availableTags },
} = useStore();
const [formData, setFormData] = useState<Task>(task);
const [tags, setTags] = useState<string[]>(
@ -200,12 +195,8 @@ const TaskModal: React.FC<TaskModalProps> = ({
}));
};
useEffect(() => {
// Load tags when modal opens if they're not already loaded
if (isOpen && !tagsHasLoaded && !tagsLoading) {
loadTags();
}
}, [isOpen, tagsHasLoaded, tagsLoading]);
// Note: Tags loading removed to prevent modal closing issues
// Tags will be loaded by other components or on app startup
const getPriorityString = (
priority: PriorityType | number | undefined