Merge branch 'main' into module/timetracking

This commit is contained in:
2025-08-26 09:07:38 +02:00
23 changed files with 310 additions and 62 deletions

View File

@@ -5,6 +5,7 @@ import { useTinyRouter } from 'svelte-tiny-router';
import { logout, user } from '../user.svelte.js';
import { t } from '../translations.svelte.js';
let key = $state(null);
const router = useTinyRouter();
const modules = $state([]);
@@ -27,6 +28,12 @@ function go(path){
return false;
}
async function search(e){
e.preventDefault();
router.navigate(`/search?key=${key}`);
return false;
}
onMount(fetchModules);
</script>
@@ -37,6 +44,10 @@ onMount(fetchModules);
</style>
<nav>
<form onsubmit={search}>
<input type="text" bind:value={key} />
<button type="submit">{t('search')}</button>
</form>
<a href="#" onclick={() => go('/user')}>{t('users')}</a>
<a href="#" onclick={() => go('/company')}>{t('companies')}</a>
<a href="#" onclick={() => go('/project')}>{t('projects')}</a>

View File

@@ -7,8 +7,6 @@
import TypeSelector from './TypeSelector.svelte';
let error = null;
let companies = {};
let router = useTinyRouter();
@@ -27,7 +25,7 @@
}
if (company_id) {
for (let comp of Object.keys(companies)){
for (let comp of companies){
if (comp.id == company_id){
load(comp);
break;

View File

@@ -22,6 +22,8 @@
let tasks = $state({});
let users = {};
let columns = $derived(project.allowed_states?Object.keys(project.allowed_states).length+1:1);
const controller = new AbortController();
const signal = controller.signal;
$effect(() => updateUrl(filter_input));
@@ -91,15 +93,20 @@
}
async function load(){
await loadProject();
await loadTasks({project_id:+id,parent_task_id:0});
ready = true;
loadTags();
try {
await loadProject();
await loadTasks({project_id:+id,parent_task_id:0});
ready = true;
loadTags();
} catch (ignored) {}
}
async function loadProject(){
const url = api(`project/${id}`);
const resp = await fetch(url,{credentials:'include'});
const resp = await fetch(url,{
credentials:'include',
signal: signal
});
if (resp.ok){
project = await resp.json();
for (var uid of Object.keys(project.members)){
@@ -114,12 +121,17 @@
}
async function loadTag(task){
const url = api(`tags/task/${task.id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok) {
const tags = await resp.json();
if (tags.length) task.tags = tags.sort();
}
try {
const url = api(`tags/task/${task.id}`);
const resp = await fetch(url,{
credentials:'include',
signal: signal
});
if (resp.ok) {
const tags = await resp.json();
if (tags.length) task.tags = tags.sort();
}
} catch (ignored) {}
}
function loadTags(){
@@ -138,7 +150,8 @@
selector.show_closed = true;
selector.no_index = true;
var resp = await fetch(url,{
credentials : 'include',
credentials :'include',
signal : signal,
method : 'POST',
body : JSON.stringify(selector)
});
@@ -146,6 +159,7 @@
var json = await resp.json();
for (var task_id of Object.keys(json)) {
let task = json[task_id];
if (task.no_index) continue;
let state = task.status;
let owner = null;
let assignee = null;
@@ -172,6 +186,11 @@
highlight = {user:user_id,state:state};
}
function openTask(task_id){
controller.abort();
router.navigate(`/task/${task_id}/view`)
}
onMount(load);
</script>
@@ -200,13 +219,13 @@
<div class={['state_'+state, highlight.user == uid && highlight.state == state ? 'highlight':'']} ondragover={ev => hover(ev,uid,state)} ondrop={ev => drop(uid,state)} >
{#if stateList[state]}
{#each Object.values(stateList[state]).sort((a,b) => a.name.localeCompare(b.name)) as task}
{#if !filter || task.name.toLowerCase().includes(filter) || (task.tags && task.tags.filter(tag => tag.toLowerCase().includes(filter)).length)}
<Card onclick={() => router.navigate(`/task/${task.id}/view`)} ondragstart={ev => dragged=task} {task} />
{/if}
{#if !filter || task.name.toLowerCase().includes(filter) || (task.tags && task.tags.filter(tag => tag.toLowerCase().includes(filter)).length)}
<Card onclick={e => openTask(task.id)} ondragstart={ev => dragged=task} {task} />
{/if}
{/each}
{/if}
<div class="add_task">
<LineEditor value={t('add_task')} editable={true} onSet={(name) => create(name,uid,state)}/>
<LineEditor value={t('add_object',{object:t('task')})} editable={true} onSet={(name) => create(name,uid,state)}/>
</div>
</div>
{/each}

View File

@@ -1,40 +1,93 @@
<script>
import { onMount } from 'svelte';
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
import { t } from '../../translations.svelte.js';
import { t } from '../../translations.svelte.js';
import Bookmark from '../bookmark/Template.svelte';
const router = useTinyRouter();
console.log(router);
let bookmarks = $state(null);
let error = $state(null);
let fulltext = false;
let html = "";
let key = "";
let key = $state(router.getQueryParam('key'));
let input = $state(router.getQueryParam('key'));
let projects = $state(null);
let tasks = $state(null);
async function doSearch(ev){
async function setKey(ev){
if (ev) ev.preventDefault();
const url = `${location.protocol}//${location.host.replace('5173','8080')}/legacy/search`;
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : JSON.stringify({key:key,fulltext:fulltext?'on':'off'})
});
if (resp.ok){
html = await resp.text();
if (!html) html = t('nothing_found');
}
key = input;
}
onMount(() => {
let params = new URLSearchParams(location.search);
key = params.get('key');
if (key) doSearch();
});
function doSearch(ignored){
let url = window.location.origin + window.location.pathname;
if (key) url += '?key=' + encodeURI(key);
window.history.replaceState(history.state, '', url);
const data = { key : key, fulltext : fulltext };
const options = {
credentials:'include',
method: 'POST',
body: JSON.stringify(data)
};
fetch(api('bookmark/search'),options).then(handleBookmarks);
fetch(api('project/search'),options).then(handleProjects);
fetch(api('task/search'),options).then(handleTasks);
}
function go(path){
router.navigate(path);
return false;
}
async function handleBookmarks(resp){
if (resp.ok){
const res = await resp.json();
bookmarks = Object.keys(res).length ? res : null;
} else {
error = await resp.text();
}
}
async function handleProjects(resp){
if (resp.ok){
const res = await resp.json();
projects = Object.keys(res).length ? res : null;
} else {
error = await resp.text();
}
}
async function handleTasks(resp){
if (resp.ok){
const res = await resp.json();
tasks = Object.keys(res).length ? res : null;
} else {
error = await resp.text();
}
}
$effect(() => doSearch(key))
</script>
<fieldset class="search">
<legend>{t('search')}</legend>
<form onsubmit={doSearch}>
{#if error}
<span class="error">{error}</span>
{/if}
<form onsubmit={setKey}>
<label>
{t('key')}
<input type="text" bind:value={key} />
<input type="text" bind:value={input} />
</label>
<label>
<input type="checkbox" bind:checked={fulltext} />
@@ -43,11 +96,45 @@
<button type="submit">{t('go')}</button>
</form>
</fieldset>
{#if html}
{#if projects}
<fieldset>
<legend>
{t('results')}
{t('projects')}
</legend>
{@html html}
<ul>
{#each Object.values(projects) as project}
<li>
<a href="#" onclick={e=>go(`/project/${project.id}/view`)} >{project.name}</a>
</li>
{/each}
</ul>
</fieldset>
{/if}
{#if tasks}
<fieldset>
<legend>
{t('tasks')}
</legend>
<ul>
{#each Object.values(tasks) as task}
<li>
<a href="#" onclick={e=>go(`/task/${task.id}/view`)} >{task.name}</a>
</li>
{/each}
</ul>
</fieldset>
{/if}
{#if bookmarks}
<fieldset>
<legend>
{t('bookmarks')}
</legend>
<ul>
{#each Object.values(bookmarks) as bookmark}
<li>
<a href={bookmark.url} target="_blank">{@html bookmark.comment.rendered}</a>
</li>
{/each}
</ul>
</fieldset>
{/if}

View File

@@ -17,6 +17,7 @@
let router = useTinyRouter();
async function addTag(){
if (!newTag) return;
if (!id) {
// when creating elements, they don`t have an id, yet
tags.push(newTag);

View File

@@ -46,7 +46,6 @@
function updateOn(id){
task = null;
loadTask();
console.log(router);
}
function addChild(){