refactor: UUID-based member refs in API types and components

- Task interface: assignee_id/assignee, reviewer_id/reviewer, watcher_ids
- Message interface: author_id/author вместо author_slug
- API functions: assignTask использует assignee_id
- KanbanBoard: обновлен под новые поля задач
- TaskModal: assignee selection по ID вместо slug
- ChatPanel: отображение author через relationship
This commit is contained in:
Markov 2026-02-25 00:13:36 +01:00
parent 8df4179dd7
commit c7144328fa
4 changed files with 18 additions and 15 deletions

View File

@ -125,7 +125,7 @@ export default function ChatPanel({ chatId }: Props) {
<div key={msg.id} className="text-sm" style={bgStyle}>
<div className="flex items-center gap-1 text-xs text-[var(--muted)] mb-0.5">
<span>{AUTHOR_ICON[msg.author_type] || "👤"}</span>
<span className="font-medium">{msg.author_slug}</span>
<span className="font-medium">{msg.author?.name || msg.author?.slug || "Unknown"}</span>
<span>·</span>
<span>{new Date(msg.created_at).toLocaleString("ru-RU", { hour: "2-digit", minute: "2-digit" })}</span>
</div>

View File

@ -63,7 +63,7 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
const unsubscribeAssigned = wsClient.on("task.assigned", (data: any) => {
if (data.project_id === projectId) {
setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, assignee_slug: data.assignee_slug, assigned_at: data.assigned_at } : t));
setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, assignee_id: data.assignee_id, assignee: data.assignee, assigned_at: data.assigned_at } : t));
}
});
@ -106,8 +106,8 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
style={{ background: PRIORITY_COLORS[task.priority] || "#737373" }}
title={task.priority} />
<span className="text-xs text-[var(--muted)]">{prefix}-{task.number}</span>
{task.assignee_slug && (
<span className="text-xs text-[var(--muted)] ml-auto"> {task.assignee_slug}</span>
{task.assignee && (
<span className="text-xs text-[var(--muted)] ml-auto"> {task.assignee.slug}</span>
)}
</div>
<div className="text-sm ml-3.5">{task.title}</div>

View File

@ -42,7 +42,7 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
const [description, setDescription] = useState(task.description || "");
const [status, setStatus] = useState(task.status);
const [priority, setPriority] = useState(task.priority);
const [assigneeSlug, setAssigneeSlug] = useState(task.assignee_slug || "");
const [assigneeId, setAssigneeId] = useState(task.assignee_id || "");
const [members, setMembers] = useState<Member[]>([]);
const [steps, setSteps] = useState<Step[]>(task.steps || []);
const [comments, setComments] = useState<Message[]>([]);
@ -307,16 +307,16 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
<div>
<div className="text-xs text-[var(--muted)] mb-1">Исполнитель</div>
<select
value={assigneeSlug}
value={assigneeId}
onChange={(e) => {
setAssigneeSlug(e.target.value);
save({ assignee_slug: e.target.value || undefined } as any);
setAssigneeId(e.target.value);
save({ assignee_id: e.target.value || undefined } as any);
}}
className="w-full bg-[var(--bg)] border border-[var(--border)] rounded px-2 py-1.5 text-sm outline-none focus:border-[var(--accent)]"
>
<option value="">Не назначен</option>
{members.map((m) => (
<option key={m.slug} value={m.slug}>
<option key={m.id} value={m.id}>
{m.type === "agent" ? "🤖 " : "👤 "}{m.name}
</option>
))}

View File

@ -106,9 +106,11 @@ export interface Task {
status: string;
priority: string;
labels: string[];
assignee_slug: string | null;
reviewer_slug: string | null;
watchers: string[];
assignee_id: string | null;
assignee: Member | null;
reviewer_id: string | null;
reviewer: Member | null;
watcher_ids: string[];
depends_on: string[];
position: number;
time_spent: number;
@ -128,7 +130,8 @@ export interface Message {
task_id: string | null;
parent_id: string | null;
author_type: string;
author_slug: string;
author_id: string;
author: Member | null;
content: string;
mentions: string[];
voice_url: string | null;
@ -203,10 +206,10 @@ export async function rejectTask(taskId: string, reason: string): Promise<{ok: b
});
}
export async function assignTask(taskId: string, assigneeSlug: string): Promise<Task> {
export async function assignTask(taskId: string, assigneeId: string): Promise<Task> {
return request(`/api/v1/tasks/${taskId}/assign`, {
method: "POST",
body: JSON.stringify({ assignee_slug: assigneeSlug })
body: JSON.stringify({ assignee_id: assigneeId })
});
}