Fix an issue with create-from buttons in inbox
This commit is contained in:
parent
84c29f7a12
commit
cf8b1c9709
5 changed files with 196 additions and 138 deletions
|
|
@ -216,65 +216,75 @@ const InboxItemDetail: React.FC<InboxItemDetailProps> = ({
|
|||
const cleanedContent = cleanTextFromTagsAndProjects(item.content);
|
||||
|
||||
const handleConvertToTask = () => {
|
||||
// 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()
|
||||
);
|
||||
return existingTag || { name: hashtagName };
|
||||
});
|
||||
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()
|
||||
);
|
||||
return existingTag || { name: hashtagName };
|
||||
});
|
||||
|
||||
// Find the project to assign (use first project reference if any)
|
||||
let projectId = undefined;
|
||||
if (projectRefs.length > 0) {
|
||||
// Look for an existing project with the first project reference name
|
||||
const projectName = projectRefs[0];
|
||||
const matchingProject = projects.find(
|
||||
(project) =>
|
||||
project.name.toLowerCase() === projectName.toLowerCase()
|
||||
);
|
||||
if (matchingProject) {
|
||||
projectId = matchingProject.id;
|
||||
// Find the project to assign (use first project reference if any)
|
||||
let projectId = undefined;
|
||||
if (projectRefs.length > 0) {
|
||||
// Look for an existing project with the first project reference name
|
||||
const projectName = projectRefs[0];
|
||||
const matchingProject = projects.find(
|
||||
(project) =>
|
||||
project.name.toLowerCase() === projectName.toLowerCase()
|
||||
);
|
||||
if (matchingProject) {
|
||||
projectId = matchingProject.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newTask: Task = {
|
||||
name: cleanedContent || item.content,
|
||||
status: 'not_started',
|
||||
priority: 'medium',
|
||||
tags: taskTags,
|
||||
project_id: projectId,
|
||||
};
|
||||
const newTask: Task = {
|
||||
name: cleanedContent || item.content,
|
||||
status: 'not_started',
|
||||
priority: 'medium',
|
||||
tags: taskTags,
|
||||
project_id: projectId,
|
||||
};
|
||||
|
||||
if (item.id !== undefined) {
|
||||
openTaskModal(newTask, item.id);
|
||||
} else {
|
||||
openTaskModal(newTask);
|
||||
if (item.id !== undefined) {
|
||||
openTaskModal(newTask, item.id);
|
||||
} else {
|
||||
openTaskModal(newTask);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error converting to task:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConvertToProject = () => {
|
||||
// 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()
|
||||
);
|
||||
return existingTag || { name: hashtagName };
|
||||
});
|
||||
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()
|
||||
);
|
||||
return existingTag || { name: hashtagName };
|
||||
});
|
||||
|
||||
const newProject: Project = {
|
||||
name: cleanedContent || item.content,
|
||||
description: '',
|
||||
active: true,
|
||||
tags: projectTags,
|
||||
};
|
||||
const newProject: Project = {
|
||||
name: cleanedContent || item.content,
|
||||
description: '',
|
||||
active: true,
|
||||
tags: projectTags,
|
||||
};
|
||||
|
||||
if (item.id !== undefined) {
|
||||
openProjectModal(newProject, item.id);
|
||||
} else {
|
||||
openProjectModal(newProject);
|
||||
if (item.id !== undefined) {
|
||||
openProjectModal(newProject, item.id);
|
||||
} else {
|
||||
openProjectModal(newProject);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error converting to project:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,37 +213,57 @@ const InboxItems: React.FC = () => {
|
|||
|
||||
// Modal handlers
|
||||
const handleOpenTaskModal = async (task: Task, inboxItemId?: number) => {
|
||||
// Load projects first before opening the modal
|
||||
try {
|
||||
const projectData = await fetchProjects();
|
||||
// Make sure we always set an array
|
||||
setProjects(Array.isArray(projectData) ? projectData : []);
|
||||
// Load projects first before opening the modal
|
||||
try {
|
||||
const projectData = await fetchProjects();
|
||||
// Make sure we always set an array
|
||||
setProjects(Array.isArray(projectData) ? projectData : []);
|
||||
} catch (error) {
|
||||
console.error('Failed to load projects:', error);
|
||||
showErrorToast(
|
||||
t('project.loadError', 'Failed to load projects')
|
||||
);
|
||||
setProjects([]); // Ensure we have an empty array even on error
|
||||
}
|
||||
|
||||
setTaskToEdit(task);
|
||||
|
||||
if (inboxItemId) {
|
||||
setCurrentConversionItemId(inboxItemId);
|
||||
}
|
||||
|
||||
setIsTaskModalOpen(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to load projects:', error);
|
||||
showErrorToast(t('project.loadError', 'Failed to load projects'));
|
||||
setProjects([]); // Ensure we have an empty array even on error
|
||||
console.error('Failed to open task modal:', error);
|
||||
}
|
||||
|
||||
setTaskToEdit(task);
|
||||
|
||||
if (inboxItemId) {
|
||||
setCurrentConversionItemId(inboxItemId);
|
||||
}
|
||||
|
||||
setIsTaskModalOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenProjectModal = (
|
||||
const handleOpenProjectModal = async (
|
||||
project: Project | null,
|
||||
inboxItemId?: number
|
||||
) => {
|
||||
setProjectToEdit(project);
|
||||
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
|
||||
}
|
||||
|
||||
if (inboxItemId) {
|
||||
setCurrentConversionItemId(inboxItemId);
|
||||
setProjectToEdit(project);
|
||||
|
||||
if (inboxItemId) {
|
||||
setCurrentConversionItemId(inboxItemId);
|
||||
}
|
||||
|
||||
setIsProjectModalOpen(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to open project modal:', error);
|
||||
}
|
||||
|
||||
setIsProjectModalOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenNoteModal = async (
|
||||
|
|
@ -490,29 +545,35 @@ const InboxItems: React.FC = () => {
|
|||
})()}
|
||||
|
||||
{/* Project Modal - Only render when needed to prevent infinite loops */}
|
||||
{isProjectModalOpen &&
|
||||
(() => {
|
||||
try {
|
||||
return (
|
||||
<ProjectModal
|
||||
isOpen={isProjectModalOpen}
|
||||
onClose={() => {
|
||||
setIsProjectModalOpen(false);
|
||||
setProjectToEdit(null);
|
||||
}}
|
||||
onSave={handleSaveProject}
|
||||
project={projectToEdit || undefined}
|
||||
areas={[]}
|
||||
/>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'ProjectModal rendering error:',
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
{(() => {
|
||||
return (
|
||||
isProjectModalOpen &&
|
||||
(() => {
|
||||
try {
|
||||
return (
|
||||
<ProjectModal
|
||||
isOpen={isProjectModalOpen}
|
||||
onClose={() => {
|
||||
setIsProjectModalOpen(false);
|
||||
setProjectToEdit(null);
|
||||
}}
|
||||
onSave={handleSaveProject}
|
||||
project={projectToEdit || undefined}
|
||||
areas={
|
||||
Array.isArray(areas) ? areas : []
|
||||
}
|
||||
/>
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'ProjectModal rendering error:',
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Note Modal - Always render it but control visibility with isOpen */}
|
||||
{(() => {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
setTimeout(() => {
|
||||
titleInputRef.current?.focus();
|
||||
}, 100);
|
||||
}, [isOpen]);
|
||||
|
||||
// Initialize filtered projects from props - like TaskModal
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// Auto-focus on the name input when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen && !tagsHasLoaded && !tagsLoading) {
|
||||
loadTags();
|
||||
}
|
||||
|
||||
// Auto-focus on the name input when modal opens
|
||||
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
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue