feat: Add full CRUD functionality, project detail panel, and improved UI
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
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:
@@ -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;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user