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:
parent
8df4179dd7
commit
c7144328fa
@ -125,7 +125,7 @@ export default function ChatPanel({ chatId }: Props) {
|
|||||||
<div key={msg.id} className="text-sm" style={bgStyle}>
|
<div key={msg.id} className="text-sm" style={bgStyle}>
|
||||||
<div className="flex items-center gap-1 text-xs text-[var(--muted)] mb-0.5">
|
<div className="flex items-center gap-1 text-xs text-[var(--muted)] mb-0.5">
|
||||||
<span>{AUTHOR_ICON[msg.author_type] || "👤"}</span>
|
<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>·</span>
|
||||||
<span>{new Date(msg.created_at).toLocaleString("ru-RU", { hour: "2-digit", minute: "2-digit" })}</span>
|
<span>{new Date(msg.created_at).toLocaleString("ru-RU", { hour: "2-digit", minute: "2-digit" })}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
|
|||||||
|
|
||||||
const unsubscribeAssigned = wsClient.on("task.assigned", (data: any) => {
|
const unsubscribeAssigned = wsClient.on("task.assigned", (data: any) => {
|
||||||
if (data.project_id === projectId) {
|
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" }}
|
style={{ background: PRIORITY_COLORS[task.priority] || "#737373" }}
|
||||||
title={task.priority} />
|
title={task.priority} />
|
||||||
<span className="text-xs text-[var(--muted)]">{prefix}-{task.number}</span>
|
<span className="text-xs text-[var(--muted)]">{prefix}-{task.number}</span>
|
||||||
{task.assignee_slug && (
|
{task.assignee && (
|
||||||
<span className="text-xs text-[var(--muted)] ml-auto">→ {task.assignee_slug}</span>
|
<span className="text-xs text-[var(--muted)] ml-auto">→ {task.assignee.slug}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm ml-3.5">{task.title}</div>
|
<div className="text-sm ml-3.5">{task.title}</div>
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
|||||||
const [description, setDescription] = useState(task.description || "");
|
const [description, setDescription] = useState(task.description || "");
|
||||||
const [status, setStatus] = useState(task.status);
|
const [status, setStatus] = useState(task.status);
|
||||||
const [priority, setPriority] = useState(task.priority);
|
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 [members, setMembers] = useState<Member[]>([]);
|
||||||
const [steps, setSteps] = useState<Step[]>(task.steps || []);
|
const [steps, setSteps] = useState<Step[]>(task.steps || []);
|
||||||
const [comments, setComments] = useState<Message[]>([]);
|
const [comments, setComments] = useState<Message[]>([]);
|
||||||
@ -307,16 +307,16 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-[var(--muted)] mb-1">Исполнитель</div>
|
<div className="text-xs text-[var(--muted)] mb-1">Исполнитель</div>
|
||||||
<select
|
<select
|
||||||
value={assigneeSlug}
|
value={assigneeId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setAssigneeSlug(e.target.value);
|
setAssigneeId(e.target.value);
|
||||||
save({ assignee_slug: e.target.value || undefined } as any);
|
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)]"
|
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>
|
<option value="">Не назначен</option>
|
||||||
{members.map((m) => (
|
{members.map((m) => (
|
||||||
<option key={m.slug} value={m.slug}>
|
<option key={m.id} value={m.id}>
|
||||||
{m.type === "agent" ? "🤖 " : "👤 "}{m.name}
|
{m.type === "agent" ? "🤖 " : "👤 "}{m.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -106,9 +106,11 @@ export interface Task {
|
|||||||
status: string;
|
status: string;
|
||||||
priority: string;
|
priority: string;
|
||||||
labels: string[];
|
labels: string[];
|
||||||
assignee_slug: string | null;
|
assignee_id: string | null;
|
||||||
reviewer_slug: string | null;
|
assignee: Member | null;
|
||||||
watchers: string[];
|
reviewer_id: string | null;
|
||||||
|
reviewer: Member | null;
|
||||||
|
watcher_ids: string[];
|
||||||
depends_on: string[];
|
depends_on: string[];
|
||||||
position: number;
|
position: number;
|
||||||
time_spent: number;
|
time_spent: number;
|
||||||
@ -128,7 +130,8 @@ export interface Message {
|
|||||||
task_id: string | null;
|
task_id: string | null;
|
||||||
parent_id: string | null;
|
parent_id: string | null;
|
||||||
author_type: string;
|
author_type: string;
|
||||||
author_slug: string;
|
author_id: string;
|
||||||
|
author: Member | null;
|
||||||
content: string;
|
content: string;
|
||||||
mentions: string[];
|
mentions: string[];
|
||||||
voice_url: string | null;
|
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`, {
|
return request(`/api/v1/tasks/${taskId}/assign`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ assignee_slug: assigneeSlug })
|
body: JSON.stringify({ assignee_id: assigneeId })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user