add project linking.
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-04-03 16:30:43 +02:00
parent 2c5d87134d
commit 2a6c04893d
17 changed files with 365 additions and 46 deletions
+3
View File
@@ -19,6 +19,7 @@
import ProjectForm from "./forms/ProjectForm.svelte";
import ItemForm from "./forms/ItemForm.svelte";
import TaskForm from "./forms/TaskForm.svelte";
import TaskLinkForm from "./forms/TaskLinkForm.svelte";
import LoginForm from "./forms/LoginForm.svelte";
import ModalRoute from "./components/ModalRoute.svelte";
@@ -71,6 +72,8 @@
<ModalRoute name="goal.add"> <GoalForm creation /></ModalRoute>
<ModalRoute name="goal.edit"> <GoalForm /></ModalRoute>
<ModalRoute name="goal.delete"> <GoalForm deletion /></ModalRoute>
<ModalRoute name="tasklink.add"> <TaskLinkForm creation /></ModalRoute>
<ModalRoute name="tasklink.delete"> <TaskLinkForm deletion /></ModalRoute>
{:else}
<LoginForm />
{/if}
+13 -1
View File
@@ -1,7 +1,7 @@
import { getJwt } from "./amplify";
import type { GoalFilter, GoalInput, GoalResult, GoalUpdate } from "../models/goal";
import type { ProjectFilter, ProjectInput, ProjectResult, ProjectUpdate } from "../models/project";
import type { TaskFilter, TaskInput, TaskResult, TaskUpdate } from "../models/task";
import type { TaskFilter, TaskInput, TaskLink, TaskResult, TaskUpdate } from "../models/task";
import type { LogFilter, LogInput, LogResult, LogUpdate } from "../models/log";
import type { GroupInput, GroupResult, GroupUpdate } from "../models/group";
import type { ItemInput, ItemResult, ItemUpdate } from "../models/item";
@@ -132,6 +132,18 @@ export class StufflogClient {
async createTaskLink(projectId: string, taskId: string): Promise<TaskLink> {
const data = await this.fetch("PUT", `/api/task/${taskId}/link/${projectId}`);
return data.taskLink;
}
async deleteTaskLink(projectId: string, taskId: string): Promise<TaskLink> {
const data = await this.fetch("DELETE", `/api/task/${taskId}/link/${projectId}`);
return data.taskLink;
}
async findTask(id: string): Promise<TaskResult> {
const data = await this.fetch("GET", `/api/task/${id}`);
return data.task;
@@ -50,7 +50,6 @@
const boatTarget = Math.ceil(timeProgress * progressTarget);
boat = progressAmount - boatTarget;
console.log(progressTarget, boatTarget);
} else {
boat = null;
}
@@ -67,10 +66,6 @@
}
}
}
$: {
console.log(boat);
}
</script>
<div class="progress">
@@ -26,6 +26,7 @@ import stuffLogClient from "../clients/stufflog";
let mdAddTask: ModalData;
let mdProjectEdit: ModalData;
let mdProjectDelete: ModalData;
let mdLinkTask: ModalData;
let linkTarget: string = "";
let activeTasks: TaskResult[] = [];
let inactiveTasks: TaskResult[] = [];
@@ -73,6 +74,7 @@ import stuffLogClient from "../clients/stufflog";
}
$: mdAddTask = {name:"task.add", project};
$: mdLinkTask = {name:"tasklink.add", project};
$: mdProjectEdit = {name:"project.edit", project};
$: mdProjectDelete = {name:"project.delete", project};
@@ -114,6 +116,7 @@ import stuffLogClient from "../clients/stufflog";
{#if showAllOptions}
<OptionRow>
<Option open={mdAddTask}>Add Task</Option>
<Option open={mdLinkTask}>Link Task</Option>
<Option open={mdProjectEdit}>Edit</Option>
<Option open={mdProjectDelete}>Delete</Option>
{#if canComplete}
@@ -14,12 +14,6 @@
let optGroups: OptGroup[]
$: {
if ($projectStore.stale && !$projectStore.loading) {
projectStore.load({});
}
}
$: {
optGroups = [
{
@@ -42,6 +36,14 @@
status: "Completed",
projects: $projectStore.projects.filter(p => !p.active && p.statusTag === "completed")
},
{
status: "Background",
projects: $projectStore.projects.filter(p => !p.active && p.statusTag === "background")
},
{
status: "Progress",
projects: $projectStore.projects.filter(p => !p.active && p.statusTag === "progress")
},
{
status: "Failed",
projects: $projectStore.projects.filter(p => !p.active && p.statusTag === "failed")
+28 -20
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { tick } from "svelte";
import { tick } from "svelte";
import stuffLogClient from "../clients/stufflog";
import type Project from "../models/project";
@@ -23,7 +23,9 @@ import { tick } from "svelte";
let mdLogAdd: ModalData;
let mdTaskEdit: ModalData;
let mdTaskDelete: ModalData;
let mdTaskUnlink: ModalData;
let isMoving = false;
let isLinked = false;
function toggleShowLogs() {
showLogs = !showLogs;
@@ -66,6 +68,8 @@ import { tick } from "svelte";
$: mdLogAdd = {name: "log.add", task: {...task, project}};
$: mdTaskEdit = {name: "task.edit", task: {...task, project}};
$: mdTaskDelete = {name: "task.delete", task: {...task, project}};
$: mdTaskUnlink = {name: "tasklink.delete", task, project};
$: isLinked = task.projectId !== project.id;
</script>
<StatusColor affects="task" entry={task}>
@@ -81,25 +85,29 @@ import { tick } from "svelte";
<Option open={mdLogAdd}>Add Log</Option>
{#if showAllOptions}
<Option open={mdTaskEdit}>Edit</Option>
<Option open={mdTaskDelete}>Delete</Option>
·
{#if !isMoving && (!task.active) }
<Option on:click={moveToActive}>Active</Option>
{/if}
{#if !isMoving && (task.statusTag !== "to do") }
<Option color="yellow" on:click={moveToToDo}>To Do</Option>
{/if}
{#if !isMoving && (task.statusTag !== "on hold") }
<Option color="blue" on:click={moveToOnHold}>On Hold</Option>
{/if}
{#if !isMoving && (task.statusTag !== "declined") }
<Option color="purple" on:click={moveToDeclined}>Won't Do</Option>
{/if}
{#if !isMoving && task.active }
<Option color="green" on:click={moveToCompleted}>Completed</Option>
{/if}
{#if !isMoving && (task.statusTag !== "failed") }
<Option color="red" on:click={moveToFailed}>Failed</Option>
{#if isLinked}
<Option open={mdTaskUnlink}>Unlink</Option>
{:else}
<Option open={mdTaskDelete}>Delete</Option>
·
{#if !isMoving && (!task.active) }
<Option on:click={moveToActive}>Active</Option>
{/if}
{#if !isMoving && (task.statusTag !== "to do") }
<Option color="yellow" on:click={moveToToDo}>To Do</Option>
{/if}
{#if !isMoving && (task.statusTag !== "on hold") }
<Option color="blue" on:click={moveToOnHold}>On Hold</Option>
{/if}
{#if !isMoving && (task.statusTag !== "declined") }
<Option color="purple" on:click={moveToDeclined}>Won't Do</Option>
{/if}
{#if !isMoving && task.active }
<Option color="green" on:click={moveToCompleted}>Completed</Option>
{/if}
{#if !isMoving && (task.statusTag !== "failed") }
<Option color="red" on:click={moveToFailed}>Failed</Option>
{/if}
{/if}
{/if}
</OptionRow>
@@ -0,0 +1,36 @@
<script lang="ts">
import projectStore from "../stores/project";
export let value = "";
export let name = "";
export let disabled = false;
export let optional = false;
$: {
if ($projectStore.stale && !$projectStore.loading) {
projectStore.load({});
}
}
$: {
if ($projectStore.projects.length > 0 && value === "" && !optional) {
const nonEmpty = $projectStore.projects.find(g => g.tasks.length > 0);
if (nonEmpty != null) {
value = nonEmpty.tasks[0].id;
}
}
}
</script>
<select name={name} bind:value={value} disabled={disabled || $projectStore.loading}>
{#if optional}
<option value={""} selected={"" === value}>None</option>
{/if}
{#each $projectStore.projects as project (project.id)}
<optgroup label={project.name}>
{#each project.tasks as task (task.id)}
<option value={task.id} selected={task.id === value}>{task.name}</option>
{/each}
</optgroup>
{/each}
</select>
+69
View File
@@ -0,0 +1,69 @@
<script lang="ts">
import stuffLogClient from "../clients/stufflog";
import Modal from "../components/Modal.svelte";
import ProjectSelect from "../components/ProjectSelect.svelte";
import TaskSelect from "../components/TaskSelect.svelte";
import markStale from "../stores/markStale";
import modalStore from "../stores/modal";
export let deletion = false;
export let creation = false;
const md = $modalStore;
let projectId = "";
let taskId = "";
let verb = "Add";
if (md.name === "tasklink.delete") {
projectId = md.project.id;
taskId = md.task.id;
verb = "Delete";
} else if (md.name === "tasklink.add") {
projectId = (md.project||{id:""}).id;
taskId = (md.task||{id:""}).id;
} else {
throw new Error(`Wrong form ${md.name}`)
}
let error = null;
let loading = false;
function onSubmit() {
loading = true;
error = null;
if (creation) {
stuffLogClient.createTaskLink(projectId, taskId).then(() => {
markStale("goal", "project");
modalStore.close();
}).catch(err => {
error = err.message ? err.message : err.toString();
}).finally(() => {
loading = false;
})
} else if (deletion) {
stuffLogClient.deleteTaskLink(projectId, taskId).then(() => {
markStale("goal", "project");
modalStore.close();
}).catch(err => {
error = err.message ? err.message : err.toString();
}).finally(() => {
loading = false;
})
}
}
</script>
<Modal show title="{verb} Link" error={error} closable on:close={modalStore.close}>
<form on:submit|preventDefault={onSubmit}>
<label for="taskId">Source Task</label>
<TaskSelect disabled={deletion} name="taskId" bind:value={taskId} />
<label for="projectId">Destination Project</label>
<ProjectSelect disabled={deletion} name="projectId" bind:value={projectId} />
<hr />
<button disabled={loading} type="submit">{verb} Link</button>
</form>
</Modal>
+6
View File
@@ -23,6 +23,12 @@ export interface TaskResult extends Task {
project?: Project
}
// TaskLink is only returned by the API. The tasks are returned transparently.
export interface TaskLink {
taskId: string
projectId: string
}
export interface TaskFilter {
active?: boolean
expiring?: boolean
+1 -1
View File
@@ -4,7 +4,7 @@
import projectStore from "../stores/project";
$: {
if (($projectStore.stale || $projectStore.filter.active != null) && !$projectStore.loading) {
if ($projectStore.stale && !$projectStore.loading) {
projectStore.load({});
}
}
+6 -2
View File
@@ -3,8 +3,10 @@ import type { GoalResult } from "../models/goal";
import type { GroupResult } from "../models/group";
import type { ItemResult } from "../models/item";
import type { LogResult } from "../models/log";
import type { ProjectResult } from "../models/project";
import type { TaskResult } from "../models/task";
import type {ProjectResult} from "../models/project";
import type {TaskResult} from "../models/task";
import type Project from "../models/project";
import type Task from "../models/task";
export type ModalData =
| { name: "none" }
@@ -26,6 +28,8 @@ export type ModalData =
| { name: "goal.add" }
| { name: "goal.edit", goal: GoalResult }
| { name: "goal.delete", goal: GoalResult }
| { name: "tasklink.add", project?: Project, task?: Task }
| { name: "tasklink.delete", project: Project, task: Task }
function createModalStore() {
const {set, subscribe} = writable<ModalData>({name: "none"});