OpenSource Projekt-Management-Software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

176 lines
5.6 KiB

<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}&nbsp;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}