177 lines
5.7 KiB
Svelte
177 lines
5.7 KiB
Svelte
<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;
|
|
}
|
|
}
|
|
|
|
async function setName(newName){
|
|
return await 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}
|