feat: Add full CRUD functionality, project detail panel, and improved UI
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled

- Add UPDATE and DELETE endpoints to backend
- Implement project detail panel with comprehensive editing
- Add drag-and-drop functionality for projects in mind map
- Show all projects in map (not just selected + children)
- Fix infinite render loop in MindMap component
- Improve UI spacing and button layouts
- Add local development database schema with RLS disabled
- Update docker-compose for regular docker-compose (not Swarm)
- Add CORS support and nginx API proxying
- Improve button spacing and modern design principles
This commit is contained in:
gitmuhammedalbayrak
2025-11-27 03:18:48 +03:00
parent 066c16221d
commit b9148cfa4b
15 changed files with 1306 additions and 161 deletions

View File

@@ -5,6 +5,8 @@ interface Project {
name: string;
path: string;
parentId: string | null;
description?: string | null;
attributes?: Record<string, any>;
children?: Project[];
}
@@ -17,6 +19,9 @@ interface ProjectState {
setSelectedKey: (key: string | null) => void;
expandNode: (key: string) => void;
fetchProjects: () => Promise<void>;
createProject: (data: { name: string; parentId?: string | null; description?: string }) => Promise<void>;
updateProject: (id: string, data: { name?: string; description?: string; parentId?: string | null }) => Promise<void>;
deleteProject: (id: string) => Promise<void>;
}
const DUMMY_PROJECTS: Project[] = [
@@ -42,10 +47,17 @@ export const useProjectStore = create<ProjectState>((set) => ({
})),
fetchProjects: async () => {
try {
const response = await fetch('http://localhost:3000/projects');
// Use relative path when proxied through nginx, or environment variable
const apiUrl = import.meta.env.VITE_API_URL || '/projects';
const response = await fetch(apiUrl);
if (response.ok) {
const data = await response.json();
set({ projects: data });
// Map API response to frontend format (parent_id -> parentId)
const mappedData = data.map((p: any) => ({
...p,
parentId: p.parent_id || null,
}));
set({ projects: mappedData });
} else {
console.error('Failed to fetch projects');
set({ projects: DUMMY_PROJECTS });
@@ -55,4 +67,73 @@ export const useProjectStore = create<ProjectState>((set) => ({
set({ projects: DUMMY_PROJECTS });
}
},
createProject: async (data) => {
try {
const apiUrl = import.meta.env.VITE_API_URL || '/projects';
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: data.name,
description: data.description || null,
parent_id: data.parentId || null,
tenant_id: '00000000-0000-0000-0000-000000000001', // Default tenant for local dev
}),
});
if (response.ok) {
await useProjectStore.getState().fetchProjects();
} else {
const error = await response.json();
throw new Error(error.message || 'Failed to create project');
}
} catch (error) {
console.error('Error creating project:', error);
throw error;
}
},
updateProject: async (id, data) => {
try {
const apiUrl = import.meta.env.VITE_API_URL || '/projects';
const response = await fetch(`${apiUrl}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...(data.name && { name: data.name }),
...(data.description !== undefined && { description: data.description }),
...(data.parentId !== undefined && { parent_id: data.parentId }),
}),
});
if (response.ok) {
await useProjectStore.getState().fetchProjects();
} else {
const error = await response.json();
throw new Error(error.message || 'Failed to update project');
}
} catch (error) {
console.error('Error updating project:', error);
throw error;
}
},
deleteProject: async (id) => {
try {
const apiUrl = import.meta.env.VITE_API_URL || '/projects';
const response = await fetch(`${apiUrl}/${id}`, {
method: 'DELETE',
});
if (response.ok) {
await useProjectStore.getState().fetchProjects();
// Clear selection if deleted project was selected
const state = useProjectStore.getState();
if (state.selectedKey === id) {
state.setSelectedKey(null);
}
} else {
const error = await response.json();
throw new Error(error.message || 'Failed to delete project');
}
} catch (error) {
console.error('Error deleting project:', error);
throw error;
}
},
}));