add status tags to tasks and reorganize them to have more familiar names.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -51,7 +51,7 @@ func (r *taskRepository) List(ctx context.Context, filter models.TaskFilter) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
sq = sq.InnerJoin("project AS p ON task.project_id = p.project_id")
|
sq = sq.InnerJoin("project AS p ON task.project_id = p.project_id")
|
||||||
sq = sq.OrderBy("active DESC", "created_time")
|
sq = sq.OrderBy("active DESC", "status_tag ASC", "created_time")
|
||||||
|
|
||||||
query, args, err := sq.ToSql()
|
query, args, err := sq.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,9 +73,9 @@ func (r *taskRepository) List(ctx context.Context, filter models.TaskFilter) ([]
|
|||||||
func (r *taskRepository) Insert(ctx context.Context, task models.Task) error {
|
func (r *taskRepository) Insert(ctx context.Context, task models.Task) error {
|
||||||
_, err := r.db.NamedExecContext(ctx, `
|
_, err := r.db.NamedExecContext(ctx, `
|
||||||
INSERT INTO task (
|
INSERT INTO task (
|
||||||
task_id, user_id, item_id, project_id, item_amount, name, description, active, created_time, end_time
|
task_id, user_id, item_id, project_id, item_amount, name, description, active, created_time, end_time, status_tag
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:task_id, :user_id, :item_id, :project_id, :item_amount, :name, :description, :active, :created_time, :end_time
|
:task_id, :user_id, :item_id, :project_id, :item_amount, :name, :description, :active, :created_time, :end_time, :status_tag
|
||||||
)
|
)
|
||||||
`, &task)
|
`, &task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,7 +93,8 @@ func (r *taskRepository) Update(ctx context.Context, task models.Task) error {
|
|||||||
name = :name,
|
name = :name,
|
||||||
description = :description,
|
description = :description,
|
||||||
active = :active,
|
active = :active,
|
||||||
end_time = :end_time
|
end_time = :end_time,
|
||||||
|
status_tag = :status_tag
|
||||||
WHERE task_id=:task_id
|
WHERE task_id=:task_id
|
||||||
`, &task)
|
`, &task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
UPDATE project SET status_tag='to do' WHERE status_tag='idea';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'Nothing to do';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
UPDATE project SET status_tag='on hold' WHERE status_tag='postponed';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'Nothing to do';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE task
|
||||||
|
ADD COLUMN status_tag TEXT DEFAULT NULL;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE task
|
||||||
|
DROP COLUMN status_tag;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
UPDATE task SET status_tag='completed' WHERE active=false;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'Nothing to do';
|
||||||
|
-- +goose StatementEnd
|
||||||
+16
-7
@@ -17,6 +17,7 @@ type Task struct {
|
|||||||
Active bool `json:"active" db:"active"`
|
Active bool `json:"active" db:"active"`
|
||||||
CreatedTime time.Time `json:"createdTime" db:"created_time"`
|
CreatedTime time.Time `json:"createdTime" db:"created_time"`
|
||||||
EndTime *time.Time `json:"endTime" db:"end_time"`
|
EndTime *time.Time `json:"endTime" db:"end_time"`
|
||||||
|
StatusTag *string `json:"statusTag" db:"status_tag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) Update(update TaskUpdate) {
|
func (task *Task) Update(update TaskUpdate) {
|
||||||
@@ -42,16 +43,24 @@ func (task *Task) Update(update TaskUpdate) {
|
|||||||
if update.ClearEndTime {
|
if update.ClearEndTime {
|
||||||
task.EndTime = nil
|
task.EndTime = nil
|
||||||
}
|
}
|
||||||
|
if update.StatusTag != nil {
|
||||||
|
task.StatusTag = update.StatusTag
|
||||||
|
}
|
||||||
|
if update.ClearStatusTag {
|
||||||
|
task.StatusTag = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskUpdate struct {
|
type TaskUpdate struct {
|
||||||
ItemID *string `json:"itemId"`
|
ItemID *string `json:"itemId"`
|
||||||
ItemAmount *int `json:"itemAmount"`
|
ItemAmount *int `json:"itemAmount"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Active *bool `json:"active"`
|
Active *bool `json:"active"`
|
||||||
EndTime *time.Time `json:"endTime"`
|
EndTime *time.Time `json:"endTime"`
|
||||||
ClearEndTime bool `json:"clearEndTime"`
|
ClearEndTime bool `json:"clearEndTime"`
|
||||||
|
StatusTag *string `json:"statusTag"`
|
||||||
|
ClearStatusTag bool `json:"clearStatusTag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskResult struct {
|
type TaskResult struct {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
// Mark it non-segmented if the target is too high. This prevents a clunky progress bar,
|
// Mark it non-segmented if the target is too high. This prevents a clunky progress bar,
|
||||||
// or a browser freeze if you set the target very high.
|
// or a browser freeze if you set the target very high.
|
||||||
nonSegmented = (target >= 50);
|
nonSegmented = (target >= 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ProjectResult } from "../models/project";
|
import type { ProjectResult } from "../models/project";
|
||||||
import type { TaskResult } from "../models/task";
|
import type { TaskResult } from "../models/task";
|
||||||
import type { ModalData } from "../stores/modal";
|
import type { ModalData } from "../stores/modal";
|
||||||
import projectStore from "../stores/project";
|
|
||||||
import taskStore from "../stores/tasks";
|
|
||||||
import Composition from "./Composition.svelte";
|
|
||||||
import Option from "./Option.svelte";
|
import Option from "./Option.svelte";
|
||||||
import OptionRow from "./OptionRow.svelte";
|
import OptionRow from "./OptionRow.svelte";
|
||||||
import ParentEntry from "./ParentEntry.svelte";
|
import ParentEntry from "./ParentEntry.svelte";
|
||||||
import StatusColor from "./StatusColor.svelte";
|
import StatusColor from "./StatusColor.svelte";
|
||||||
import TaskEntry from "./TaskEntry.svelte";
|
import TaskList from "./TaskList.svelte";
|
||||||
|
|
||||||
export let project: ProjectResult = null;
|
export let project: ProjectResult = null;
|
||||||
export let showAllOptions: boolean = false;
|
export let showAllOptions: boolean = false;
|
||||||
@@ -22,12 +19,38 @@ import StatusColor from "./StatusColor.svelte";
|
|||||||
let mdProjectEdit: ModalData;
|
let mdProjectEdit: ModalData;
|
||||||
let mdProjectDelete: ModalData;
|
let mdProjectDelete: ModalData;
|
||||||
|
|
||||||
|
let activeTasks: TaskResult[] = [];
|
||||||
|
let inactiveTasks: TaskResult[] = [];
|
||||||
|
let completedTasks: TaskResult[] = [];
|
||||||
|
let todoTasks: TaskResult[] = [];
|
||||||
|
let onholdTasks: TaskResult[] = [];
|
||||||
|
let failedTasks: TaskResult[] = [];
|
||||||
|
|
||||||
$: mdAddTask = {name:"task.add", project};
|
$: mdAddTask = {name:"task.add", project};
|
||||||
$: mdProjectEdit = {name:"project.edit", project};
|
$: mdProjectEdit = {name:"project.edit", project};
|
||||||
$: mdProjectDelete = {name:"project.delete", project};
|
$: mdProjectDelete = {name:"project.delete", project};
|
||||||
|
|
||||||
|
$: {
|
||||||
|
activeTasks = project.tasks.filter(t => t.active);
|
||||||
|
|
||||||
|
if (!hideInactive) {
|
||||||
|
inactiveTasks = project.tasks.filter(t => !t.active);
|
||||||
|
|
||||||
|
completedTasks = inactiveTasks.filter(t => t.statusTag === "completed" || t.statusTag == null);
|
||||||
|
todoTasks = inactiveTasks.filter(t => t.statusTag === "to do" || t.statusTag === "idea");
|
||||||
|
onholdTasks = inactiveTasks.filter(t => t.statusTag === "on hold" || t.statusTag === "postponed");
|
||||||
|
failedTasks = inactiveTasks.filter(t => t.statusTag === "failed" || t.statusTag === "declined" || t.statusTag === "wont do");
|
||||||
|
} else {
|
||||||
|
inactiveTasks = [];
|
||||||
|
completedTasks = [];
|
||||||
|
todoTasks = [];
|
||||||
|
onholdTasks = [];
|
||||||
|
failedTasks = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<StatusColor selected project={project}>
|
<StatusColor affects="project" selected entry={project}>
|
||||||
<ParentEntry
|
<ParentEntry
|
||||||
full={showAllOptions}
|
full={showAllOptions}
|
||||||
entry={project}
|
entry={project}
|
||||||
@@ -43,10 +66,14 @@ import StatusColor from "./StatusColor.svelte";
|
|||||||
<Option open={mdProjectDelete}>Delete</Option>
|
<Option open={mdProjectDelete}>Delete</Option>
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
{/if}
|
{/if}
|
||||||
{#each project.tasks as task (task.id)}
|
{#if hideInactive}
|
||||||
{#if !hideInactive || task.active}
|
<TaskList header="" tasks={activeTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
<TaskEntry showAllOptions={showAllOptions} task={task} project={project} />
|
{:else}
|
||||||
{/if}
|
<TaskList header="Active" tasks={activeTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
{/each}
|
<TaskList header="Completed" tasks={completedTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
|
<TaskList header="To Do" tasks={todoTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
|
<TaskList header="On Hold" tasks={onholdTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
|
<TaskList header="Failed" tasks={failedTasks} project={project} showAllOptions={showAllOptions} />
|
||||||
|
{/if}
|
||||||
</ParentEntry>
|
</ParentEntry>
|
||||||
</StatusColor>
|
</StatusColor>
|
||||||
|
|||||||
@@ -5,37 +5,11 @@
|
|||||||
|
|
||||||
interface ProjectLike {
|
interface ProjectLike {
|
||||||
icon?: IconName
|
icon?: IconName
|
||||||
active?: boolean
|
|
||||||
statusTag?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export let project: ProjectLike
|
export let project: ProjectLike
|
||||||
export let selected: boolean;
|
|
||||||
|
|
||||||
let completed: boolean;
|
|
||||||
let failed: boolean;
|
|
||||||
let postponed: boolean;
|
|
||||||
let idea: boolean;
|
|
||||||
|
|
||||||
$: completed = !project.active && project.statusTag === "completed";
|
|
||||||
$: failed = !project.active && project.statusTag === "failed";
|
|
||||||
$: postponed = !project.active && project.statusTag === "postponed";
|
|
||||||
$: idea = !project.active && project.statusTag === "idea";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class:selected class:completed class:failed class:postponed class:idea>
|
<span class="sccfg" >
|
||||||
<Icon block name={project.icon} />
|
<Icon block name={project.icon} />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<style>
|
|
||||||
span { color: #444; }
|
|
||||||
span.selected { color: #666; }
|
|
||||||
span.completed { color: #484; }
|
|
||||||
span.completed.selected { color: #78ff78; }
|
|
||||||
span.failed { color: #884844; }
|
|
||||||
span.failed.selected { color: #ff9878; }
|
|
||||||
span.postponed { color: #446d88; }
|
|
||||||
span.postponed.selected { color: #78c9ff; }
|
|
||||||
span.idea { color: #878844; }
|
|
||||||
span.idea.selected { color: #fffd78; }
|
|
||||||
</style>
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
$: completed = !project.active;
|
$: completed = !project.active;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<StatusColor project={project} selected={selected}>
|
<StatusColor affects="project" entry={project} selected={selected}>
|
||||||
<div class="ql-list-item" on:click={handleClick} class:selected>
|
<div class="ql-list-item" on:click={handleClick} class:selected>
|
||||||
<div class="icon sccfg" class:completed>
|
<div class="icon sccfg" class:completed>
|
||||||
<Icon block name={project.icon} />
|
<Icon block name={project.icon} />
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
$: activeProjects = projects.filter(p => p.active && !p.endTime).sort((a,b) => a.name.localeCompare(b.name));
|
$: activeProjects = projects.filter(p => p.active && !p.endTime).sort((a,b) => a.name.localeCompare(b.name));
|
||||||
$: inactiveProjects = projects.filter(p => !p.active).sort((a,b) => a.name.localeCompare(b.name));
|
$: inactiveProjects = projects.filter(p => !p.active).sort((a,b) => a.name.localeCompare(b.name));
|
||||||
$: completedProjects = inactiveProjects.filter(p => p.statusTag === "completed" || p.statusTag == null);
|
$: completedProjects = inactiveProjects.filter(p => p.statusTag === "completed" || p.statusTag == null);
|
||||||
$: failedProjects = inactiveProjects.filter(p => p.statusTag === "failed" || p.statusTag == null);
|
$: failedProjects = inactiveProjects.filter(p => p.statusTag === "failed" || p.statusTag === "declined");
|
||||||
$: postponedProjects = inactiveProjects.filter(p => p.statusTag === "postponed" || p.statusTag == null);
|
$: postponedProjects = inactiveProjects.filter(p => p.statusTag === "on hold" || p.statusTag === "postponed");
|
||||||
$: ideaProjects = inactiveProjects.filter(p => p.statusTag === "idea" || p.statusTag == null);
|
$: ideaProjects = inactiveProjects.filter(p => p.statusTag === "to do" || p.statusTag === "idea");
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (project === null && projects.length > 0) {
|
if (project === null && projects.length > 0) {
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
<div class="list">
|
<div class="list">
|
||||||
<QlList label="Deadlines" projects={expiringProjects} />
|
<QlList label="Deadlines" projects={expiringProjects} />
|
||||||
<QlList label="Active" projects={activeProjects} />
|
<QlList label="Active" projects={activeProjects} />
|
||||||
|
<QlList label="To Do" projects={ideaProjects} />
|
||||||
|
<QlList label="On Hold" projects={postponedProjects} />
|
||||||
<QlList label="Completed" projects={completedProjects} />
|
<QlList label="Completed" projects={completedProjects} />
|
||||||
<QlList label="Postponed" projects={postponedProjects} />
|
|
||||||
<QlList label="Ideas" projects={ideaProjects} />
|
|
||||||
<QlList label="Failed" projects={failedProjects} />
|
<QlList label="Failed" projects={failedProjects} />
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
|||||||
@@ -1,42 +1,58 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface ProjectLike {
|
interface EntryCommon {
|
||||||
active: boolean
|
active: boolean
|
||||||
statusTag?: string
|
statusTag?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export let selected = false;
|
export let selected = false;
|
||||||
export let project: ProjectLike;
|
export let entry: EntryCommon;
|
||||||
|
export let affects: "project" | "task" = "project";
|
||||||
|
|
||||||
let completed: boolean;
|
let completed: boolean;
|
||||||
let failed: boolean;
|
let failed: boolean;
|
||||||
let postponed: boolean;
|
let onhold: boolean;
|
||||||
let idea: boolean;
|
let todo: boolean;
|
||||||
|
let wontdo: boolean;
|
||||||
|
let project: boolean;
|
||||||
|
let task: boolean;
|
||||||
|
|
||||||
$: completed = !project.active && project.statusTag === "completed";
|
$: completed = !entry.active && entry.statusTag === "completed";
|
||||||
$: failed = !project.active && project.statusTag === "failed";
|
$: failed = !entry.active && entry.statusTag === "failed";
|
||||||
$: postponed = !project.active && project.statusTag === "postponed";
|
$: onhold = !entry.active && entry.statusTag === "on hold";
|
||||||
$: idea = !project.active && project.statusTag === "idea";
|
$: todo = !entry.active && entry.statusTag === "to do";
|
||||||
|
$: wontdo = !entry.active && entry.statusTag === "declined";
|
||||||
|
$: task = affects === "task";
|
||||||
|
$: project = affects === "project";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="status-color-context" class:selected class:completed class:failed class:postponed class:idea>
|
<div class="status-color-context" class:project class:task class:selected class:completed class:failed class:onhold class:todo class:wontdo>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.status-color-context :global(.sccfg) { color: #444 !important; }
|
.status-color-context.project :global(.sccfg) { color: #444 !important; }
|
||||||
.status-color-context.selected :global(.sccfg) { color: #666 !important; }
|
.status-color-context.project.selected :global(.sccfg) { color: #666 !important; }
|
||||||
.status-color-context :global(.sccpb) { background-color: #78ff78 !important; }
|
.status-color-context.project :global(.sccpb) { background-color: #78ff78 !important; }
|
||||||
.status-color-context.completed :global(.sccfg) { color: #484 !important; }
|
.status-color-context.project.completed :global(.sccfg) { color: #484 !important; }
|
||||||
.status-color-context.completed.selected :global(.sccfg) { color: #78ff78 !important; }
|
.status-color-context.project.completed.selected :global(.sccfg) { color: #78ff78 !important; }
|
||||||
.status-color-context.completed :global(.sccpb) { background-color: #78ff78 !important; }
|
.status-color-context.project.completed :global(.sccpb) { background-color: #78ff78 !important; }
|
||||||
.status-color-context.failed :global(.sccfg) { color: #852a24 !important; }
|
.status-color-context.project.failed :global(.sccfg) { color: #852a24 !important; }
|
||||||
.status-color-context.failed.selected :global(.sccfg) { color: #ff4545 !important; }
|
.status-color-context.project.failed.selected :global(.sccfg) { color: #ff4545 !important; }
|
||||||
.status-color-context.failed :global(.sccpb) { background-color: #ff4545 !important; }
|
.status-color-context.project.failed :global(.sccpb) { background-color: #ff4545 !important; }
|
||||||
.status-color-context.postponed :global(.sccfg) { color: #446d88 !important; }
|
.status-color-context.project.onhold :global(.sccfg) { color: #446d88 !important; }
|
||||||
.status-color-context.postponed.selected :global(.sccfg) { color: #78c9ff !important; }
|
.status-color-context.project.onhold.selected :global(.sccfg) { color: #78c9ff !important; }
|
||||||
.status-color-context.postponed :global(.sccpb) { background-color: #78c9ff !important; }
|
.status-color-context.project.onhold :global(.sccpb) { background-color: #78c9ff !important; }
|
||||||
.status-color-context.idea :global(.sccfg) { color: #878844 !important; }
|
.status-color-context.project.todo :global(.sccfg) { color: #7a7429 !important; }
|
||||||
.status-color-context.idea.selected :global(.sccfg) { color: #e7e55e !important; }
|
.status-color-context.project.todo.selected :global(.sccfg) { color: #e7e55e !important; }
|
||||||
.status-color-context.idea :global(.sccpb) { background-color: #e7e55e !important; }
|
.status-color-context.project.todo :global(.sccpb) { background-color: #e7e55e !important; }
|
||||||
|
.status-color-context.project.wontdo :global(.sccfg) { color: #7a2973 !important; }
|
||||||
|
.status-color-context.project.wontdo.selected :global(.sccfg) { color: #e75ed0 !important; }
|
||||||
|
.status-color-context.project.wontdo :global(.sccpb) { background-color: #e75ed0 !important; }
|
||||||
|
|
||||||
|
.status-color-context.task.completed :global(.sccsi) { background-color: #484 !important; color: #78ff78 !important; }
|
||||||
|
.status-color-context.task.failed :global(.sccsi) { background-color: #85242c !important; color: #ff4545 !important; }
|
||||||
|
.status-color-context.task.onhold :global(.sccsi) { background-color: #447288 !important; color: #78c9ff !important; }
|
||||||
|
.status-color-context.task.todo :global(.sccsi) { background-color: #7a7429 !important; color: #e7e55e !important; }
|
||||||
|
.status-color-context.task.wontdo :global(.sccsi) { background-color: #7a2973 !important; color: #e75ed0 !important; }
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let value: string;
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select name="statusTag" bind:value={value} disabled={disabled}>
|
||||||
|
<option value="" selected={"" === value}>Active / In Progress</option>
|
||||||
|
<option value="completed" selected={"completed" === value}>Completed</option>
|
||||||
|
<option value="failed" selected={"failed" === value}>Failed</option>
|
||||||
|
<option value="on hold" selected={"on hold" === value}>On Hold</option>
|
||||||
|
<option value="to do" selected={"to do" === value}>To Do</option>
|
||||||
|
<option value="declined" selected={"declined" === value}>Won't Do</option>
|
||||||
|
</select>
|
||||||
@@ -4,11 +4,12 @@
|
|||||||
import type { ModalData } from "../stores/modal";
|
import type { ModalData } from "../stores/modal";
|
||||||
import ChildEntry from "./ChildEntry.svelte";
|
import ChildEntry from "./ChildEntry.svelte";
|
||||||
import DateSpan from "./DateSpan.svelte";
|
import DateSpan from "./DateSpan.svelte";
|
||||||
import Icon from "./Icon.svelte";
|
|
||||||
import ItemLink from "./ItemLink.svelte";
|
import ItemLink from "./ItemLink.svelte";
|
||||||
import Markdown from "./Markdown.svelte";
|
import Markdown from "./Markdown.svelte";
|
||||||
import Option from "./Option.svelte";
|
import Option from "./Option.svelte";
|
||||||
import OptionRow from "./OptionRow.svelte";
|
import OptionRow from "./OptionRow.svelte";
|
||||||
|
import StatusColor from "./StatusColor.svelte";
|
||||||
|
import TaskIcon from "./TaskIcon.svelte";
|
||||||
|
|
||||||
export let task: TaskResult = null;
|
export let task: TaskResult = null;
|
||||||
export let project: Project = null;
|
export let project: Project = null;
|
||||||
@@ -28,74 +29,38 @@ import Markdown from "./Markdown.svelte";
|
|||||||
$: mdTaskDelete = {name: "task.delete", task: {...task, project}};
|
$: mdTaskDelete = {name: "task.delete", task: {...task, project}};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ChildEntry entry={task}>
|
<StatusColor affects="task" entry={task}>
|
||||||
<div slot="icon" class="icon" class:done={!task.active}>
|
<ChildEntry entry={task}>
|
||||||
{#if !task.active}
|
<div slot="icon">
|
||||||
<span class="on"><Icon block name="check" /></span>
|
<TaskIcon task={task} />
|
||||||
<span class="off">
|
|
||||||
{task.completedAmount} / {task.itemAmount}
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
{task.completedAmount} / {task.itemAmount}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<ItemLink item={task.item} />
|
|
||||||
<OptionRow>
|
|
||||||
{#if task.logs.length > 0}
|
|
||||||
<Option on:click={toggleShowLogs}>{showLogs ? "Hide Logs" : "Show Logs"}</Option>
|
|
||||||
{/if}
|
|
||||||
<Option open={mdLogAdd}>Add Log</Option>
|
|
||||||
{#if showAllOptions}
|
|
||||||
<Option open={mdTaskEdit}>Edit</Option>
|
|
||||||
<Option open={mdTaskDelete}>Delete</Option>
|
|
||||||
{/if}
|
|
||||||
</OptionRow>
|
|
||||||
{#if showLogs && task.logs.length > 0}
|
|
||||||
<div class="log-list">
|
|
||||||
{#each task.logs as log (log.id)}
|
|
||||||
<div class="log">
|
|
||||||
<div class="log-time"><DateSpan time={log.loggedTime} /></div>
|
|
||||||
<div class="log-description">
|
|
||||||
<Markdown source={log.description} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<ItemLink item={task.item} />
|
||||||
</ChildEntry>
|
<OptionRow>
|
||||||
|
{#if task.logs.length > 0}
|
||||||
|
<Option on:click={toggleShowLogs}>{showLogs ? "Hide Logs" : "Show Logs"}</Option>
|
||||||
|
{/if}
|
||||||
|
<Option open={mdLogAdd}>Add Log</Option>
|
||||||
|
{#if showAllOptions}
|
||||||
|
<Option open={mdTaskEdit}>Edit</Option>
|
||||||
|
<Option open={mdTaskDelete}>Delete</Option>
|
||||||
|
{/if}
|
||||||
|
</OptionRow>
|
||||||
|
{#if showLogs && task.logs.length > 0}
|
||||||
|
<div class="log-list">
|
||||||
|
{#each task.logs as log (log.id)}
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-time"><DateSpan time={log.loggedTime} /></div>
|
||||||
|
<div class="log-description">
|
||||||
|
<Markdown source={log.description} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</ChildEntry>
|
||||||
|
</StatusColor>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div.icon {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 0.2em .5ch;
|
|
||||||
|
|
||||||
margin-right: 0.5em;
|
|
||||||
background: #444;
|
|
||||||
color: #CCC;
|
|
||||||
}
|
|
||||||
div.icon.done {
|
|
||||||
padding-top: 0.285em;
|
|
||||||
padding-bottom: 0.115em;
|
|
||||||
|
|
||||||
background: #484;
|
|
||||||
color: #78ff78;
|
|
||||||
}
|
|
||||||
div.icon.done span.off {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
div.icon.done:hover {
|
|
||||||
padding-top: 0.2em;
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
}
|
|
||||||
div.icon.done:hover span.on {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
div.icon.done:hover span.off {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.log-list {
|
div.log-list {
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { IconName } from "../external/icons";
|
||||||
|
|
||||||
|
import type { TaskResult } from "../models/task";
|
||||||
|
import Icon from "./Icon.svelte";
|
||||||
|
|
||||||
|
export let task: TaskResult;
|
||||||
|
export let iconName: IconName;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
switch (task.statusTag) {
|
||||||
|
case "to do": iconName = "lightbulb"; break;
|
||||||
|
case "on hold": iconName = "hourglass"; break;
|
||||||
|
case "declined": iconName = "times"; break;
|
||||||
|
case "failed": iconName = "times"; break;
|
||||||
|
default: iconName = "check"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="icon sccsi" class:inactive={!task.active}>
|
||||||
|
{#if !task.active}
|
||||||
|
<span class="on"><Icon block name={iconName} /></span>
|
||||||
|
<span class="off">
|
||||||
|
{task.completedAmount} / {task.itemAmount}
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
{task.completedAmount} / {task.itemAmount}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.icon {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.2em .5ch;
|
||||||
|
|
||||||
|
margin-right: 0.5em;
|
||||||
|
background: #444;
|
||||||
|
color: #CCC;
|
||||||
|
}
|
||||||
|
div.icon.inactive {
|
||||||
|
padding-top: 0.295em;
|
||||||
|
padding-bottom: 0.115em;
|
||||||
|
|
||||||
|
background: #484;
|
||||||
|
color: #78ff78;
|
||||||
|
}
|
||||||
|
div.icon.inactive span.off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.icon.inactive:hover {
|
||||||
|
padding-top: 0.16em;
|
||||||
|
padding-bottom: 0.27em;
|
||||||
|
}
|
||||||
|
div.icon.inactive:hover span.on {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.icon.inactive:hover span.off {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type Project from "../models/project";
|
||||||
|
import type { TaskResult } from "../models/task";
|
||||||
|
import TaskEntry from "./TaskEntry.svelte";
|
||||||
|
|
||||||
|
export let project: Project;
|
||||||
|
export let tasks: TaskResult[];
|
||||||
|
export let showAllOptions: boolean;
|
||||||
|
export let header: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if tasks.length > 0}
|
||||||
|
<h3>{header}</h3>
|
||||||
|
{#each tasks as task (task.id)}
|
||||||
|
<TaskEntry showAllOptions={showAllOptions} task={task} project={project} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h3:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Vendored
+44
@@ -72,6 +72,28 @@ import { faMoon } from "@fortawesome/free-solid-svg-icons/faMoon";
|
|||||||
import { faCloudMoon } from "@fortawesome/free-solid-svg-icons/faCloudMoon";
|
import { faCloudMoon } from "@fortawesome/free-solid-svg-icons/faCloudMoon";
|
||||||
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
||||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||||
|
import { faFilter } from "@fortawesome/free-solid-svg-icons/faFilter";
|
||||||
|
import { faShoppingBag } from "@fortawesome/free-solid-svg-icons/faShoppingBag";
|
||||||
|
import { faTags } from "@fortawesome/free-solid-svg-icons/faTags";
|
||||||
|
import { faBalanceScale } from "@fortawesome/free-solid-svg-icons/faBalanceScale";
|
||||||
|
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||||
|
import { faHourglass } from "@fortawesome/free-solid-svg-icons/faHourglass";
|
||||||
|
import { faCalendar } from "@fortawesome/free-solid-svg-icons/faCalendar";
|
||||||
|
import { faWeight } from "@fortawesome/free-solid-svg-icons/faWeight";
|
||||||
|
import { faAnchor } from "@fortawesome/free-solid-svg-icons/faAnchor";
|
||||||
|
import { faAngry } from "@fortawesome/free-solid-svg-icons/faAngry";
|
||||||
|
import { faAnkh } from "@fortawesome/free-solid-svg-icons/faAnkh";
|
||||||
|
import { faDumbbell } from "@fortawesome/free-solid-svg-icons/faDumbbell";
|
||||||
|
import { faDollarSign } from "@fortawesome/free-solid-svg-icons/faDollarSign";
|
||||||
|
import { faExpand } from "@fortawesome/free-solid-svg-icons/faExpand";
|
||||||
|
import { faFeather } from "@fortawesome/free-solid-svg-icons/faFeather";
|
||||||
|
import { faFingerprint } from "@fortawesome/free-solid-svg-icons/faFingerprint";
|
||||||
|
import { faPaintBrush } from "@fortawesome/free-solid-svg-icons/faPaintBrush";
|
||||||
|
import { faPaintRoller } from "@fortawesome/free-solid-svg-icons/faPaintRoller";
|
||||||
|
import { faPastafarianism } from "@fortawesome/free-solid-svg-icons/faPastafarianism";
|
||||||
|
import { faSpider } from "@fortawesome/free-solid-svg-icons/faSpider";
|
||||||
|
import { faStroopwafel } from "@fortawesome/free-solid-svg-icons/faStroopwafel";
|
||||||
|
import { faSearch } from "@fortawesome/free-solid-svg-icons/faSearch";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
"question": faQuestion,
|
"question": faQuestion,
|
||||||
@@ -148,6 +170,28 @@ const icons = {
|
|||||||
"cloud_moon": faCloudMoon,
|
"cloud_moon": faCloudMoon,
|
||||||
"chevron_right": faChevronRight,
|
"chevron_right": faChevronRight,
|
||||||
"chevron_down": faChevronDown,
|
"chevron_down": faChevronDown,
|
||||||
|
"filter": faFilter,
|
||||||
|
"shopping_bag": faShoppingBag,
|
||||||
|
"tags": faTags,
|
||||||
|
"balance_scale": faBalanceScale,
|
||||||
|
"spinner": faSpinner,
|
||||||
|
"hourglass": faHourglass,
|
||||||
|
"calendar": faCalendar,
|
||||||
|
"weight": faWeight,
|
||||||
|
"anchor": faAnchor,
|
||||||
|
"angry": faAngry,
|
||||||
|
"ankh": faAnkh,
|
||||||
|
"dumbbell": faDumbbell,
|
||||||
|
"dollar_sign": faDollarSign,
|
||||||
|
"expand": faExpand,
|
||||||
|
"feather": faFeather,
|
||||||
|
"fingerprint": faFingerprint,
|
||||||
|
"paint_brush": faPaintBrush,
|
||||||
|
"paint_roller": faPaintRoller,
|
||||||
|
"pastafarianism": faPastafarianism,
|
||||||
|
"spider": faSpider,
|
||||||
|
"stroopwafel": faStroopwafel,
|
||||||
|
"bottle_opener": faSearch,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof icons;
|
export type IconName = keyof typeof icons;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Checkbox from "../components/Checkbox.svelte";
|
import Checkbox from "../components/Checkbox.svelte";
|
||||||
import IconSelect from "../components/IconSelect.svelte";
|
import IconSelect from "../components/IconSelect.svelte";
|
||||||
import Modal from "../components/Modal.svelte";
|
import Modal from "../components/Modal.svelte";
|
||||||
|
import StatusTagSelect from "../components/StatusTagSelect.svelte";
|
||||||
import { iconNames } from "../external/icons";
|
import { iconNames } from "../external/icons";
|
||||||
import type { ProjectResult } from "../models/project";
|
import type { ProjectResult } from "../models/project";
|
||||||
import markStale from "../stores/markStale";
|
import markStale from "../stores/markStale";
|
||||||
@@ -103,13 +104,8 @@
|
|||||||
<label for="endTime">Deadline (Optional)</label>
|
<label for="endTime">Deadline (Optional)</label>
|
||||||
<input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} />
|
<input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} />
|
||||||
<label for="statusTag">Status</label>
|
<label for="statusTag">Status</label>
|
||||||
<select name="statusTag" bind:value={statusTag} disabled={deletion}>
|
<StatusTagSelect disabled={deletion} bind:value={statusTag} />
|
||||||
<option value="" selected={"" === statusTag}>Active / In Progress</option>
|
|
||||||
<option value="completed" selected={"completed" === statusTag}>Completed</option>
|
|
||||||
<option value="failed" selected={"failed" === statusTag}>Failed</option>
|
|
||||||
<option value="postponed" selected={"postponed" === statusTag}>Postponed</option>
|
|
||||||
<option value="idea" selected={"idea" === statusTag}>Idea</option>
|
|
||||||
</select>
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<button disabled={loading} type="submit">{verb} Project</button>
|
<button disabled={loading} type="submit">{verb} Project</button>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import stuffLogClient from "../clients/stufflog";
|
import stuffLogClient from "../clients/stufflog";
|
||||||
import Checkbox from "../components/Checkbox.svelte";
|
|
||||||
import ItemSelect from "../components/ItemSelect.svelte";
|
import ItemSelect from "../components/ItemSelect.svelte";
|
||||||
import Modal from "../components/Modal.svelte";
|
import Modal from "../components/Modal.svelte";
|
||||||
|
import StatusTagSelect from "../components/StatusTagSelect.svelte";
|
||||||
import { iconNames } from "../external/icons";
|
import { iconNames } from "../external/icons";
|
||||||
import type { TaskResult } from "../models/task";
|
import type { TaskResult } from "../models/task";
|
||||||
import markStale from "../stores/markStale";
|
import markStale from "../stores/markStale";
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
item: null,
|
item: null,
|
||||||
logs: [],
|
logs: [],
|
||||||
completedAmount: 0,
|
completedAmount: 0,
|
||||||
|
statusTag: null,
|
||||||
}
|
}
|
||||||
let verb = "Add";
|
let verb = "Add";
|
||||||
if (md.name === "task.edit" || md.name === "task.delete") {
|
if (md.name === "task.edit" || md.name === "task.delete") {
|
||||||
@@ -47,7 +48,8 @@
|
|||||||
let name = task.name;
|
let name = task.name;
|
||||||
let description = task.description;
|
let description = task.description;
|
||||||
let itemAmount = task.itemAmount;
|
let itemAmount = task.itemAmount;
|
||||||
let completed = !task.active;
|
let statusTag = task.statusTag || "";
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
@@ -59,7 +61,8 @@
|
|||||||
stuffLogClient.createTask({
|
stuffLogClient.createTask({
|
||||||
projectId: task.projectId,
|
projectId: task.projectId,
|
||||||
itemId: itemId,
|
itemId: itemId,
|
||||||
active: !completed,
|
active: statusTag === "",
|
||||||
|
statusTag: statusTag || null,
|
||||||
endTime: (endTime == "") ? null : new Date(endTime),
|
endTime: (endTime == "") ? null : new Date(endTime),
|
||||||
|
|
||||||
name, description, itemAmount,
|
name, description, itemAmount,
|
||||||
@@ -84,7 +87,9 @@
|
|||||||
stuffLogClient.updateTask(task.id, {
|
stuffLogClient.updateTask(task.id, {
|
||||||
endTime: (endTime == "") ? null : new Date(endTime),
|
endTime: (endTime == "") ? null : new Date(endTime),
|
||||||
clearEndTime: endTime == "",
|
clearEndTime: endTime == "",
|
||||||
active: !completed,
|
active: statusTag === "",
|
||||||
|
statusTag: statusTag || null,
|
||||||
|
clearStatusTag: statusTag === "",
|
||||||
|
|
||||||
itemId, name, description, itemAmount,
|
itemId, name, description, itemAmount,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -113,7 +118,8 @@
|
|||||||
<input disabled={deletion} name="itemAmount" type="number" bind:value={itemAmount} />
|
<input disabled={deletion} name="itemAmount" type="number" bind:value={itemAmount} />
|
||||||
<label for="endTime">Deadline (Optional)</label>
|
<label for="endTime">Deadline (Optional)</label>
|
||||||
<input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} />
|
<input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} />
|
||||||
<Checkbox disabled={deletion} bind:checked={completed} label="Task is completed." />
|
<label for="statusTag">Status</label>
|
||||||
|
<StatusTagSelect disabled={deletion} bind:value={statusTag} />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
|||||||
@@ -28,15 +28,10 @@ export interface GoalFilter {
|
|||||||
export interface GoalResult extends Goal {
|
export interface GoalResult extends Goal {
|
||||||
group: Group
|
group: Group
|
||||||
items: GoalResultItem[]
|
items: GoalResultItem[]
|
||||||
logs: GoalLogResult[]
|
logs: LogResult[]
|
||||||
completedAmount: number
|
completedAmount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GoalLogResult extends LogResult {
|
|
||||||
itemCounted: boolean
|
|
||||||
secondaryItemCounted: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GoalResultItem extends Item {
|
interface GoalResultItem extends Item {
|
||||||
completedAmount: number
|
completedAmount: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default interface Task {
|
|||||||
active: boolean
|
active: boolean
|
||||||
createdTime: string
|
createdTime: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
|
statusTag?: string
|
||||||
}
|
}
|
||||||
export interface TaskResult extends Task {
|
export interface TaskResult extends Task {
|
||||||
item: Item
|
item: Item
|
||||||
@@ -35,6 +36,7 @@ export interface TaskInput {
|
|||||||
description: string
|
description: string
|
||||||
active: boolean
|
active: boolean
|
||||||
endTime?: string | Date
|
endTime?: string | Date
|
||||||
|
statusTag?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskUpdate {
|
export interface TaskUpdate {
|
||||||
@@ -45,4 +47,6 @@ export interface TaskUpdate {
|
|||||||
active?: boolean
|
active?: boolean
|
||||||
endTime?: string | Date
|
endTime?: string | Date
|
||||||
clearEndTime?: boolean
|
clearEndTime?: boolean
|
||||||
|
statusTag?: string
|
||||||
|
clearStatusTag?: boolean
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user