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.
 
 
 
 
 

264 lines
7.7 KiB

<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
import { t } from '../../translations.svelte.js';
import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import PermissionEditor from '../../Components/PermissionEditor.svelte';
import Notes from '../notes/RelatedNotes.svelte';
import StateSelector from '../../Components/StateSelector.svelte';
import Tags from '../tags/TagList.svelte';
import TaskList from '../task/TaskList.svelte';
let { id } = $props();
let error = $state(null);
let estimated_time = $state({sum:0});
let project = $state(null);
let router = useTinyRouter();
let showSettings = $state(false);
let tasks = $state(null);
let new_state = $state({code:null,name:null})
let state_available=$derived(new_state.name && new_state.code && !project.allowed_states[new_state.code]);
async function addMember(entry){
const ids = Object.keys(entry);
if (ids) update({new_member:+ids.pop()});
}
async function addState(){
const url = api(`project/${id}/state`);
const resp = await fetch(url,{
credentials: 'include',
method: 'POST',
body: JSON.stringify(new_state)
});
if (resp.ok){
const json = await resp.json();
project.allowed_states[json.code] = json.name;
error = null;
} else {
error = await resp.text();
}
}
function addTask(){
router.navigate(`/project/${id}/add_task`);
}
function changeClosed(){
update({show_closed:project.show_closed});
loadTasks();
}
async function dropMember(member){
update({drop_member:member.user.id});
}
async function getCandidates(text){
const url = api('user/search');
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : text
});
if (resp.ok){
var json = await resp.json();
return Object.fromEntries(Object.values(json).map(user => [user.id,user.name]));
} else {
return [];
}
}
function kanban(){
router.navigate(`/project/${id}/kanban`);
}
async function loadProject(){
const url = api(`project/${id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
project = await resp.json();
// console.log(project);
error = null;
loadTasks();
} else {
error = await resp.text();
}
}
async function loadTasks(){
const url = api('task/list');
const data = {
project_id:+id,
show_closed:project.show_closed
}
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : JSON.stringify(data)
});
if (resp.ok){
tasks = {};
estimated_time.sum = 0;
tasks = await resp.json();
error = null;
} else {
error = await resp.text();
}
}
async function update(data){
const url = api(`project/${id}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'PATCH',
body : JSON.stringify(data)
});
if (resp.ok){
error = null;
project = await resp.json();
return true;
} else {
error = await resp.text();
return false;
}
}
function toggleSettings(){
showSettings = !showSettings;
}
function updatePermission(user_id,permission){
let members = {};
members[user_id] = permission.code;
update({members:members});
}
onMount(loadProject);
</script>
{#if error}
<span class="error">{error}</span>
{/if}
{#if project}
<table class="project">
<tbody>
<tr>
<th>
{t('project')}
<button onclick={kanban}>{t('show_kanban')}</button>
</th>
<td class="name">
<LineEditor bind:value={project.name} editable={true} onSet={val => update({name:val})} />
<button onclick={toggleSettings}><span class="symbol"></span> {t('settings')}</button>
</td>
</tr>
<tr>
<th>{t('state')}</th>
<td>
<StateSelector selected={project.status} onchange={val => update({status:val})} {project} />
</td>
</tr>
{#if project.company}
<tr>
<th>{t('company')}</th>
<td class="company">{project.company.name}</td>
</tr>
{/if}
<tr>
<th>{t('context')}</th>
<td>
<button>{t('files')}</button>
<button>{t('models')}</button>
<button>{t('times')}</button>
</td>
</tr>
<tr>
<th>{t('description')}</th>
<td class="description">
<MarkdownEditor bind:value={project.description} editable={true} onSet={val => update({description:val})} />
</td>
</tr>
{#if showSettings}
<tr>
<th>
{t('extended_settings')}
</th>
<td>
<label>
<input type="checkbox" bind:checked={project.show_closed} onchange={changeClosed} />
{t('display_closed_tasks')}
</label>
</td>
</tr>
<tr>
<th>
{t('members')}
</th>
<td>
<PermissionEditor members={project.members} {updatePermission} {addMember} {dropMember} {getCandidates} />
</td>
</tr>
{#if project.allowed_states}
{#each Object.keys(project.allowed_states) as key,idx}
<tr>
<th>
{#if !idx}
{t('allowed_states')}:
{/if}
{key}
</th>
<td>
{project.allowed_states[key]}
</td>
</tr>
{/each}
<tr>
<th>
<input type="number" bind:value={new_state.code} />
</th>
<td>
<input type="text" bind:value={new_state.name} />
{#if state_available}
<button onclick={addState} >{t('add_state')}</button>
{/if}
</td>
</tr>
{/if}
{/if}
{#if estimated_time.sum}
<tr>
<th>{t('estimated_time')}</th>
<td class="estimated_time">{estimated_time.sum} h</td>
</tr>
{/if}
<tr>
<th>{t('tags')}</th>
<td>
<Tags module="project" {id} user_list={null} />
</td>
</tr>
<tr>
<th>
{t('tasks')}
<button onclick={addTask}>{t('add_object',{object:t('task')})}</button>
</th>
<td class="tasks">
{#if tasks}
<TaskList {tasks} {estimated_time} states={project?.allowed_states} show_closed={project.show_closed} />
{/if}
</td>
</tr>
</tbody>
</table>
{/if}
<div class="notes">
<h3>{t('notes')}</h3>
<Notes module="project" entity_id={id} />
</div>