|
|
<script> |
|
|
import { onMount } from 'svelte'; |
|
|
import { useTinyRouter } from 'svelte-tiny-router'; |
|
|
|
|
|
import { dragged } from './dragndrop.svelte'; |
|
|
import { api } from '../../urls.svelte'; |
|
|
import { error, yikes } from '../../warn.svelte'; |
|
|
import { t } from '../../translations.svelte'; |
|
|
import { timetrack } from '../../user.svelte'; |
|
|
import { now } from '../../time.svelte'; |
|
|
|
|
|
import TaskList from './TaskList.svelte'; |
|
|
import LineEditor from '../../Components/LineEditor.svelte'; |
|
|
|
|
|
let { |
|
|
estimated_time, |
|
|
show_closed, |
|
|
siblings, |
|
|
states = {}, |
|
|
task |
|
|
} = $props(); |
|
|
let children = $state(null); |
|
|
let deleted = $state(false); |
|
|
const router = useTinyRouter(); |
|
|
let start = 0; |
|
|
|
|
|
function addSubtask(){ |
|
|
router.navigate(`/task/${task.id}/add_subtask`); |
|
|
} |
|
|
|
|
|
async function addTime(){ |
|
|
const url = api(`time/track_task/${task.id}`); |
|
|
const resp = await fetch(url,{ |
|
|
credentials : 'include', |
|
|
method : 'POST', |
|
|
body : now() |
|
|
}); // create new time or return time with assigned tasks |
|
|
if (resp.ok) { |
|
|
const track = await resp.json(); |
|
|
timetrack.running = track; |
|
|
} else { |
|
|
error(resp); |
|
|
} |
|
|
} |
|
|
|
|
|
async function deleteTask(){ |
|
|
if (confirm(t('confirm_delete',{element:task.name}))){ |
|
|
const url = api(`task/${task.id}`); |
|
|
const resp = await fetch(url,{ |
|
|
credentials : 'include', |
|
|
method : 'DELETE' |
|
|
}); |
|
|
if (resp.ok){ |
|
|
deleted = true; |
|
|
} else { |
|
|
error(resp); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function ondragstart(ev){ |
|
|
ev.stopPropagation(); |
|
|
dragged.element = task; |
|
|
dragged.siblings = siblings; |
|
|
} |
|
|
|
|
|
|
|
|
async function ondrop(ev){ |
|
|
ev.stopPropagation(); |
|
|
if (dragged.element.id == task.id) return; |
|
|
const url = api(`task/${dragged.element.id}`); |
|
|
const resp = await fetch(url,{ |
|
|
credentials : 'include', |
|
|
method : 'PATCH', |
|
|
body : JSON.stringify({ parent_task_id : task.id}) |
|
|
}); |
|
|
if (resp.ok) { |
|
|
children[dragged.element.id]=dragged.element; |
|
|
if (dragged.siblings[dragged.element.id]) delete dragged.siblings[dragged.element.id]; |
|
|
} else { |
|
|
error(resp); |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
async function loadChildren(){ |
|
|
const url = api('task/list'); |
|
|
const data = { |
|
|
parent_task_id : +task.id, |
|
|
show_closed : show_closed |
|
|
}; |
|
|
if (task.show_closed) data.show_closed = true; |
|
|
const resp = await fetch(url,{ |
|
|
credentials : 'include', |
|
|
method : 'POST', |
|
|
body : JSON.stringify(data) |
|
|
}); |
|
|
if (resp.ok){ |
|
|
children = await resp.json(); |
|
|
yikes(); |
|
|
} else { |
|
|
error(resp); |
|
|
} |
|
|
} |
|
|
|
|
|
function openTask(e){ |
|
|
e.preventDefault(); |
|
|
let href = e.target.getAttribute('href'); |
|
|
if (href) router.navigate(href); |
|
|
return false; |
|
|
} |
|
|
|
|
|
async function patchTask(changeset){ |
|
|
const url = api(`task/${task.id}`); |
|
|
const resp = await fetch(url,{ |
|
|
credentials : 'include', |
|
|
method : 'PATCH', |
|
|
body : JSON.stringify(changeset) |
|
|
}); |
|
|
if (resp.ok){ |
|
|
task = await resp.json(); |
|
|
return true; |
|
|
} else { |
|
|
error(resp); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
function setName(newName){ |
|
|
patchTask({name:newName}); |
|
|
} |
|
|
|
|
|
function map_state(tx){ |
|
|
for (let [code,name] of Object.entries(states)){ |
|
|
if (tx == name) return {status:+code}; |
|
|
} |
|
|
return {state:0}; |
|
|
} |
|
|
|
|
|
if (task.estimated_time){ |
|
|
estimated_time.sum += task.estimated_time; |
|
|
} |
|
|
|
|
|
onMount(loadChildren); |
|
|
</script> |
|
|
|
|
|
{#if !deleted} |
|
|
<li draggable="true" {ondrop} ondragover={e => e.preventDefault()} {ondragstart} class="task {states[task.status]?.toLowerCase()}"> |
|
|
<LineEditor bind:value={task.name} onclick={openTask} editable={true} onSet={setName} type="a" href={`/task/${task.id}/view`} /> |
|
|
{#if task.estimated_time} |
|
|
<span class="estimated_time">({+task.estimated_time} h)</span> |
|
|
{/if} |
|
|
<button class="symbol" title={t('drag_n_drop')}></button> |
|
|
{#if states[task.status] != 'PENDING'} |
|
|
<button class="symbol" title={t('postpone')} onclick={() => patchTask(map_state('PENDING'))}></button> |
|
|
{/if} |
|
|
{#if states[task.status] != 'OPEN'} |
|
|
<button class="symbol" title={t('do_open')} onclick={() => patchTask(map_state('OPEN'))}></button> |
|
|
{/if} |
|
|
{#if states[task.status] != 'STARTED'} |
|
|
<button class="symbol" title={t('started')} onclick={() => patchTask(map_state('STARTED'))}></button> |
|
|
{/if} |
|
|
{#if states[task.status] != 'COMPLETE'} |
|
|
<button class="symbol" title={t('complete')} onclick={() => patchTask(map_state('COMPLETE'))}></button> |
|
|
{/if} |
|
|
{#if states[task.status] != 'CANCELLED'} |
|
|
<button class="symbol" title={t('abort')} onclick={() => patchTask(map_state('CANCELLED'))} ></button> |
|
|
{/if} |
|
|
<button class="symbol" title={t('delete_object',{object:t('subtask')})} onclick={deleteTask} ></button> |
|
|
<button class="symbol" title={t('add_object',{object:t('subtask')})} onclick={addSubtask}></button> |
|
|
<button class="symbol" title={t('timetracking')} onclick={addTime}></button> |
|
|
{#if children} |
|
|
<TaskList {states} tasks={children} {estimated_time} {show_closed} /> |
|
|
{/if} |
|
|
</li> |
|
|
{/if}
|
|
|
|