From f3eeed5eb9d6aa7a84c1df59e966480c6b0ec96c Mon Sep 17 00:00:00 2001 From: Markov Date: Fri, 27 Feb 2026 23:46:59 +0100 Subject: [PATCH] Labels UI + task labels on kanban + API functions - Labels management in ProjectSettings (CRUD, color picker) - Task labels displayed as colored pills on kanban cards - API: getLabels, createLabel, deleteLabel, addTaskLabel, removeTaskLabel - Task links API functions --- src/components/KanbanBoard.tsx | 9 +++++ src/components/ProjectSettings.tsx | 59 +++++++++++++++++++++++++++++- src/lib/api.ts | 28 ++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/components/KanbanBoard.tsx b/src/components/KanbanBoard.tsx index 6acfd20..28d72d2 100644 --- a/src/components/KanbanBoard.tsx +++ b/src/components/KanbanBoard.tsx @@ -111,6 +111,15 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) { )}
{task.title}
+ {task.labels && task.labels.length > 0 && ( +
+ {task.labels.map((label: string) => ( + + {label} + + ))} +
+ )} {showMoveButtons && colIndex !== undefined && (
{colIndex > 0 && ( diff --git a/src/components/ProjectSettings.tsx b/src/components/ProjectSettings.tsx index 11851d0..7722ee5 100644 --- a/src/components/ProjectSettings.tsx +++ b/src/components/ProjectSettings.tsx @@ -1,13 +1,16 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import type { Project, ProjectMember, Member } from "@/lib/api"; +import type { Project, ProjectMember, Member, Label } from "@/lib/api"; import { updateProject, deleteProject, getProjectMembers, addProjectMember, removeProjectMember, - getMembers + getMembers, + getLabels, + createLabel, + deleteLabel, } from "@/lib/api"; interface Props { @@ -31,6 +34,11 @@ export default function ProjectSettings({ project, onUpdated }: Props) { const [loadingMembers, setLoadingMembers] = useState(true); const [selectedMember, setSelectedMember] = useState(""); + // Labels state + const [labels, setLabels] = useState([]); + const [newLabelName, setNewLabelName] = useState(""); + const [newLabelColor, setNewLabelColor] = useState("#6366f1"); + // Load project members and all members useEffect(() => { const loadMembers = async () => { @@ -41,6 +49,8 @@ export default function ProjectSettings({ project, onUpdated }: Props) { ]); setMembers(projectMembers); setAllMembers(allMembersList); + const projectLabels = await getLabels(project.id); + setLabels(projectLabels); } catch (e) { console.error("Failed to load members:", e); } finally { @@ -165,6 +175,51 @@ export default function ProjectSettings({ project, onUpdated }: Props) { {saved && Сохранено ✓}
+ {/* Labels Section */} +
+
Лейблы
+
+ {labels.map((label) => ( +
+
+ + {label.name} +
+ +
+ ))} +
+
+ setNewLabelName(e.target.value)} + placeholder="Новый лейбл..." + className="flex-1 px-3 py-1.5 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-sm outline-none" + /> + setNewLabelColor(e.target.value)} + className="w-8 h-8 rounded cursor-pointer" + /> + +
+
+ {/* Members Section */}
Участники
diff --git a/src/lib/api.ts b/src/lib/api.ts index 6e4bc86..2a6987d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -448,3 +448,31 @@ export async function createTaskLink(taskId: string, targetId: string, linkType: export async function deleteTaskLink(taskId: string, linkId: string): Promise { return request(`/tasks/${taskId}/links/${linkId}`, { method: "DELETE" }); } + +// --- Labels --- +export interface Label { + id: string; + project_id: string; + name: string; + color: string; +} + +export async function getLabels(projectId: string): Promise { + return request(`/projects/${projectId}/labels`); +} + +export async function createLabel(projectId: string, name: string, color: string = "#6366f1"): Promise