f35882c967
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
111 lines
3.4 KiB
Svelte
111 lines
3.4 KiB
Svelte
<script>
|
|
import { onMount } from 'svelte';
|
|
import { useTinyRouter } from 'svelte-tiny-router';
|
|
|
|
import { api, get, patch, post } from '../../urls.svelte';
|
|
import { error, yikes } from '../../warn.svelte';
|
|
import { t } from '../../translations.svelte';
|
|
|
|
let { task = null } = $props();
|
|
|
|
let candidates = $state({});
|
|
let key = $state(null);
|
|
let requiredTasks = $state({});
|
|
let router = useTinyRouter();
|
|
let sortedTasks = $derived(Object.values(candidates).sort((a, b) => a.name.localeCompare(b.name)));
|
|
let timer = null;
|
|
|
|
async function add(new_task_id){
|
|
let url = api(`task/${new_task_id}`);
|
|
let resp = await get(url);
|
|
if (resp.ok){
|
|
yikes();
|
|
let newTask = await resp.json();
|
|
if (newTask.project_id != task.project_id){
|
|
alert('project mismatch!');
|
|
return;
|
|
}
|
|
task.required_tasks_ids.push(new_task_id);
|
|
requiredTasks[new_task_id] = newTask;
|
|
await update();
|
|
delete candidates[new_task_id];
|
|
} else {
|
|
error(resp);
|
|
}
|
|
}
|
|
|
|
async function doSearch(){
|
|
if (!key.trim()) {
|
|
candidates = {};
|
|
return;
|
|
}
|
|
const data = { key : key, project_id : task?.project_id };
|
|
const url = api('task/search');
|
|
const res = await fetch(url,{
|
|
credentials : 'include',
|
|
method : 'POST',
|
|
body : JSON.stringify(data)
|
|
});
|
|
if (res.ok) {
|
|
yikes();
|
|
candidates = await res.json();
|
|
for (var taskId of Object.keys(requiredTasks)) delete candidates[taskId];
|
|
delete candidates[task.id];
|
|
} else {
|
|
error(resp);
|
|
}
|
|
}
|
|
|
|
async function loadTasks(){
|
|
if (!task || !task.required_tasks_ids || !task.required_tasks_ids.length) return;
|
|
const url = api('task/list');
|
|
const res = await post(url,{ids:task.required_tasks_ids});
|
|
if (res.ok){
|
|
yikes();
|
|
requiredTasks = await res.json();
|
|
} else error(resp);
|
|
}
|
|
|
|
function oninput(){
|
|
if (timer) clearTimeout(timer);
|
|
timer = setTimeout(doSearch,1000);
|
|
}
|
|
|
|
function openTask(e){
|
|
e.preventDefault();
|
|
let href = e.target.getAttribute('href');
|
|
if (href) router.navigate(href);
|
|
return false;
|
|
}
|
|
|
|
async function update(){
|
|
const url = api(`task/${task.id}`);
|
|
const resp = await patch(url,{required_tasks_ids:task.required_tasks_ids});
|
|
if (!resp.ok) error(resp);
|
|
}
|
|
|
|
async function unlink(required_task){
|
|
task.required_tasks_ids = task.required_tasks_ids.filter(item => item != required_task.id);
|
|
update();
|
|
delete requiredTasks[required_task.id];
|
|
}
|
|
|
|
onMount(loadTasks);
|
|
</script>
|
|
|
|
{#if requiredTasks}
|
|
<ul class="required task list">
|
|
{#each Object.values(requiredTasks) as task}
|
|
<li class="task" >
|
|
<a href="/task/{task.id}/view" onclick={openTask} >{task.name}</a> <button onclick={() => unlink(task)} class="symbol"></button>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
{/if}
|
|
{t('add_object',{object:t('task')})}: <input type="text" bind:value={key} {oninput} />
|
|
<div class="candidate task list">
|
|
{#each sortedTasks as task}
|
|
<div class="task" onclick={e => add(task.id)}>{task.name}</div>
|
|
{/each}
|
|
</div>
|