(null);
+
+ const prefix = projectSlug.slice(0, 2).toUpperCase();
const loadTasks = async () => {
try {
@@ -42,55 +44,64 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
}
};
- useEffect(() => {
- loadTasks();
- }, [projectId]);
+ useEffect(() => { loadTasks(); }, [projectId]);
const handleDrop = async (status: string) => {
if (!draggedTask) return;
const task = tasks.find((t) => t.id === draggedTask);
if (!task || task.status === status) return;
-
setTasks((prev) => prev.map((t) => (t.id === draggedTask ? { ...t, status } : t)));
setDraggedTask(null);
-
- try {
- await updateTask(draggedTask, { status });
- } catch {
- loadTasks();
- }
+ try { await updateTask(draggedTask, { status }); } catch { loadTasks(); }
};
const handleMoveTask = async (taskId: string, newStatus: string) => {
setTasks((prev) => prev.map((t) => (t.id === taskId ? { ...t, status: newStatus } : t)));
- try {
- await updateTask(taskId, { status: newStatus });
- } catch {
- loadTasks();
- }
- };
-
- const handleAddTask = async (status: string) => {
- if (!newTaskTitle.trim()) return;
- try {
- const task = await createTask(projectSlug, {
- title: newTaskTitle.trim(),
- status,
- });
- setTasks((prev) => [...prev, task]);
- setNewTaskTitle("");
- setAddingTo(null);
- } catch (e) {
- console.error(e);
- }
+ try { await updateTask(taskId, { status: newStatus }); } catch { loadTasks(); }
};
if (loading) {
return Загрузка...
;
}
- // Mobile: column selector + vertical list
- // Desktop: horizontal kanban
+ const TaskCard = ({ task, showMoveButtons, colIndex }: { task: Task; showMoveButtons?: boolean; colIndex?: number }) => (
+ setDraggedTask(task.id)}
+ onClick={() => setSelectedTask(task)}
+ className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-3 cursor-pointer
+ hover:border-[var(--accent)] transition-colors"
+ >
+
+
+
{prefix}-{task.number}
+ {task.assignee_slug && (
+
→ {task.assignee_slug}
+ )}
+
+
{task.title}
+ {showMoveButtons && colIndex !== undefined && (
+
+ {colIndex > 0 && (
+
+ )}
+ {colIndex < COLUMNS.length - 1 && (
+
+ )}
+
+ )}
+
+ );
+
return (
{/* Mobile column tabs */}
@@ -98,15 +109,9 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
{COLUMNS.map((col) => {
const count = tasks.filter((t) => t.status === col.key).length;
return (
-
- {/* Mobile: single column view */}
+ {/* Mobile: single column */}
{(() => {
const col = COLUMNS.find((c) => c.key === (activeColumn || "backlog"))!;
@@ -124,66 +129,12 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
return (
{colTasks.map((task) => (
-
setSelectedTask(task)}
- >
-
-
-
{`#${task.number}`}
-
-
{task.title}
- {/* Move buttons */}
-
- {colIndex > 0 && (
- { e.stopPropagation(); handleMoveTask(task.id, COLUMNS[colIndex - 1].key); }}
- className="text-xs px-2 py-0.5 rounded bg-white/5 text-[var(--muted)]
- hover:text-[var(--fg)]"
- >
- ← {COLUMNS[colIndex - 1].label}
-
- )}
- {colIndex < COLUMNS.length - 1 && (
- { e.stopPropagation(); handleMoveTask(task.id, COLUMNS[colIndex + 1].key); }}
- className="text-xs px-2 py-0.5 rounded bg-white/5 text-[var(--muted)]
- hover:text-[var(--fg)]"
- >
- {COLUMNS[colIndex + 1].label} →
-
- )}
-
-
+
))}
-
- {addingTo === col.key ? (
-
setNewTaskTitle(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") handleAddTask(col.key);
- if (e.key === "Escape") setAddingTo(null);
- }}
- onBlur={() => !newTaskTitle.trim() && setAddingTo(null)}
- />
- ) : (
-
setAddingTo(col.key)}
- >
- + Добавить задачу
-
- )}
+
setCreateInStatus(col.key)}
+ >+ Добавить задачу
);
})()}
@@ -194,72 +145,33 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
{COLUMNS.map((col) => {
const colTasks = tasks.filter((t) => t.status === col.key);
return (
-
e.preventDefault()}
- onDrop={() => handleDrop(col.key)}
- >
+
e.preventDefault()} onDrop={() => handleDrop(col.key)}>
{col.label}
{colTasks.length}
-
{colTasks.map((task) => (
-
setDraggedTask(task.id)}
- onClick={() => setSelectedTask(task)}
- className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-3 cursor-grab
- hover:border-[var(--accent)] transition-colors active:cursor-grabbing"
- >
-
-
-
{`#${task.number}`}
-
-
{task.title}
-
+
))}
-
- {addingTo === col.key ? (
-
setNewTaskTitle(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") handleAddTask(col.key);
- if (e.key === "Escape") setAddingTo(null);
- }}
- onBlur={() => !newTaskTitle.trim() && setAddingTo(null)}
- />
- ) : (
-
setAddingTo(col.key)}
- >
- + Добавить
-
- )}
+
setCreateInStatus(col.key)}
+ >+ Добавить
);
})}
+
{/* Task modal */}
{selectedTask && (
setSelectedTask(null)}
onUpdated={(updated) => {
setTasks((prev) => prev.map((t) => (t.id === updated.id ? updated : t)));
@@ -271,6 +183,16 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
}}
/>
)}
+
+ {/* Create task modal */}
+ {createInStatus && (
+ setCreateInStatus(null)}
+ onCreated={(task) => setTasks((prev) => [...prev, task])}
+ />
+ )}
);
}
diff --git a/src/components/TaskModal.tsx b/src/components/TaskModal.tsx
index 5ecf03b..7b66dcb 100644
--- a/src/components/TaskModal.tsx
+++ b/src/components/TaskModal.tsx
@@ -32,12 +32,13 @@ const AUTHOR_ICON: Record = {
interface Props {
task: Task;
projectId: string;
+ projectSlug: string;
onClose: () => void;
onUpdated: (task: Task) => void;
onDeleted: (taskId: string) => void;
}
-export default function TaskModal({ task, projectId, onClose, onUpdated, onDeleted }: Props) {
+export default function TaskModal({ task, projectId, projectSlug, onClose, onUpdated, onDeleted }: Props) {
const [title, setTitle] = useState(task.title);
const [description, setDescription] = useState(task.description || "");
const [status, setStatus] = useState(task.status);
@@ -146,7 +147,7 @@ export default function TaskModal({ task, projectId, onClose, onUpdated, onDelet
{title}
)}
- #{task.number}
+ {projectSlug.slice(0, 2).toUpperCase()}-{task.number}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 21f86c9..3aac0c5 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -64,6 +64,7 @@ export interface Project {
repo_urls: string[];
status: string;
task_counter: number;
+ chat_id: string | null;
}
export interface Step {