Task search autocomplete for dependency linking
- searchTasks API function (q= param) - Autocomplete dropdown: search by number or title - Shows task key + title, click to select - Replaces raw ID input
This commit is contained in:
parent
3df1060970
commit
60cc538b28
@ -7,6 +7,7 @@ import {
|
||||
getSteps, createStep, updateStep, deleteStep as _deleteStepApi,
|
||||
getMessages, sendMessage,
|
||||
getTaskLinks, createTaskLink, deleteTaskLink,
|
||||
searchTasks,
|
||||
} from "@/lib/api";
|
||||
|
||||
const STATUSES = [
|
||||
@ -51,6 +52,8 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
||||
const [links, setLinks] = useState<TaskLink[]>([]);
|
||||
const [showAddLink, setShowAddLink] = useState(false);
|
||||
const [linkTargetId, setLinkTargetId] = useState("");
|
||||
const [linkSearch, setLinkSearch] = useState("");
|
||||
const [linkSearchResults, setLinkSearchResults] = useState<Task[]>([]);
|
||||
const [linkType, setLinkType] = useState("depends_on");
|
||||
const [comments, setComments] = useState<Message[]>([]);
|
||||
const [_saving, setSaving] = useState(false);
|
||||
@ -338,18 +341,48 @@ export default function TaskModal({ task, projectId: _projectId, projectSlug: _p
|
||||
<option value="blocks">Блокирует</option>
|
||||
<option value="relates_to">Связано с</option>
|
||||
</select>
|
||||
<div className="flex-1 relative">
|
||||
<input
|
||||
value={linkTargetId}
|
||||
onChange={e => setLinkTargetId(e.target.value)}
|
||||
placeholder="ID задачи..."
|
||||
className="flex-1 px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs outline-none"
|
||||
value={linkSearch}
|
||||
onChange={async (e) => {
|
||||
const v = e.target.value;
|
||||
setLinkSearch(v);
|
||||
if (v.length >= 1) {
|
||||
const results = await searchTasks(task.project_id, v);
|
||||
setLinkSearchResults(results.filter(t => t.id !== task.id));
|
||||
} else {
|
||||
setLinkSearchResults([]);
|
||||
}
|
||||
}}
|
||||
placeholder="Поиск по номеру или названию..."
|
||||
className="w-full px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs outline-none"
|
||||
/>
|
||||
{linkSearchResults.length > 0 && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1 bg-[var(--card)] border border-[var(--border)] rounded shadow-lg z-10 max-h-40 overflow-y-auto">
|
||||
{linkSearchResults.map(t => (
|
||||
<button
|
||||
key={t.id}
|
||||
onClick={() => {
|
||||
setLinkTargetId(t.id);
|
||||
setLinkSearch(`${t.key} ${t.title}`);
|
||||
setLinkSearchResults([]);
|
||||
}}
|
||||
className="w-full text-left px-2 py-1.5 text-xs hover:bg-white/5 flex gap-2"
|
||||
>
|
||||
<span className="text-[var(--accent)] font-mono shrink-0">{t.key}</span>
|
||||
<span className="truncate">{t.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!linkTargetId) return;
|
||||
const link = await createTaskLink(task.id, linkTargetId, linkType);
|
||||
setLinks([...links, link]);
|
||||
setLinkTargetId("");
|
||||
setLinkSearch("");
|
||||
setShowAddLink(false);
|
||||
}}
|
||||
className="px-2 py-1 bg-[var(--accent)] text-white rounded text-xs"
|
||||
|
||||
@ -488,3 +488,7 @@ export async function addTaskLabel(taskId: string, labelId: string): Promise<voi
|
||||
export async function removeTaskLabel(taskId: string, labelId: string): Promise<void> {
|
||||
return request(`/tasks/${taskId}/labels/${labelId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function searchTasks(projectId: string, query: string): Promise<Task[]> {
|
||||
return request(`/tasks?project_id=${projectId}&q=${encodeURIComponent(query)}`);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user