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
140 lines
5.4 KiB
TypeScript
140 lines
5.4 KiB
TypeScript
import { create } from 'zustand';
|
||
|
||
interface Project {
|
||
id: string;
|
||
name: string;
|
||
path: string;
|
||
parentId: string | null;
|
||
description?: string | null;
|
||
attributes?: Record<string, any>;
|
||
children?: Project[];
|
||
}
|
||
|
||
interface ProjectState {
|
||
projects: Project[];
|
||
expandedKeys: string[];
|
||
selectedKey: string | null;
|
||
setProjects: (projects: Project[]) => void;
|
||
setExpandedKeys: (keys: string[]) => void;
|
||
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[] = [
|
||
{ id: '1', name: 'Genel Merkez', path: 'root', parentId: null },
|
||
{ id: '2', name: 'Yazılım Departmanı', path: 'root.software', parentId: '1' },
|
||
{ id: '3', name: 'İnsan Kaynakları', path: 'root.hr', parentId: '1' },
|
||
{ id: '4', name: 'Evrak Projesi', path: 'root.software.evrak', parentId: '2' },
|
||
{ id: '5', name: 'Website Yenileme', path: 'root.software.website', parentId: '2' },
|
||
];
|
||
|
||
export const useProjectStore = create<ProjectState>((set) => ({
|
||
projects: [],
|
||
expandedKeys: [],
|
||
selectedKey: null,
|
||
setProjects: (projects) => set({ projects }),
|
||
setExpandedKeys: (expandedKeys) => set({ expandedKeys }),
|
||
setSelectedKey: (selectedKey) => set({ selectedKey }),
|
||
expandNode: (key) =>
|
||
set((state) => ({
|
||
expandedKeys: state.expandedKeys.includes(key)
|
||
? state.expandedKeys
|
||
: [...state.expandedKeys, key],
|
||
})),
|
||
fetchProjects: async () => {
|
||
try {
|
||
// 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();
|
||
// 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 });
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching projects:', error);
|
||
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;
|
||
}
|
||
},
|
||
}));
|