Browse Source

add status tags to tasks and reorganize them to have more familiar names.

main
Gisle Aune 4 years ago
parent
commit
270694e49e
  1. 9
      database/postgres/tasks.go
  2. 9
      migrations/postgres/20210124104325_rename_project_status_tag_todo.sql
  3. 9
      migrations/postgres/20210124104330_rename_project_status_tag_onhold.sql
  4. 11
      migrations/postgres/20210124105003_add_task_column_status_tag.sql
  5. 9
      migrations/postgres/20210124110913_update_task_default_inactive_tag.sql
  6. 9
      models/task.go
  7. 2
      svelte-ui/src/components/Progress.svelte
  8. 49
      svelte-ui/src/components/ProjectEntry.svelte
  9. 28
      svelte-ui/src/components/ProjectIcon.svelte
  10. 2
      svelte-ui/src/components/QLListItem.svelte
  11. 10
      svelte-ui/src/components/QuestLog.svelte
  12. 64
      svelte-ui/src/components/StatusColor.svelte
  13. 13
      svelte-ui/src/components/StatusTagSelect.svelte
  14. 53
      svelte-ui/src/components/TaskEntry.svelte
  15. 63
      svelte-ui/src/components/TaskIcon.svelte
  16. 30
      svelte-ui/src/components/TaskList.svelte
  17. 44
      svelte-ui/src/external/icons.ts
  18. 10
      svelte-ui/src/forms/ProjectForm.svelte
  19. 16
      svelte-ui/src/forms/TaskForm.svelte
  20. 7
      svelte-ui/src/models/goal.ts
  21. 4
      svelte-ui/src/models/task.ts

9
database/postgres/tasks.go

@ -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 {

9
migrations/postgres/20210124104325_rename_project_status_tag_todo.sql

@ -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

9
migrations/postgres/20210124104330_rename_project_status_tag_onhold.sql

@ -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

11
migrations/postgres/20210124105003_add_task_column_status_tag.sql

@ -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

9
migrations/postgres/20210124110913_update_task_default_inactive_tag.sql

@ -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

9
models/task.go

@ -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,6 +43,12 @@ 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 {
@ -52,6 +59,8 @@ type TaskUpdate struct {
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 {

2
svelte-ui/src/components/Progress.svelte

@ -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);
} }
$: { $: {

49
svelte-ui/src/components/ProjectEntry.svelte

@ -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 TaskEntry from "./TaskEntry.svelte";
import StatusColor from "./StatusColor.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 || task.active}
<TaskEntry showAllOptions={showAllOptions} task={task} project={project} />
{#if hideInactive}
<TaskList header="" tasks={activeTasks} project={project} showAllOptions={showAllOptions} />
{:else}
<TaskList header="Active" tasks={activeTasks} project={project} showAllOptions={showAllOptions} />
<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} {/if}
{/each}
</ParentEntry> </ParentEntry>
</StatusColor> </StatusColor>

28
svelte-ui/src/components/ProjectIcon.svelte

@ -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>

2
svelte-ui/src/components/QLListItem.svelte

@ -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} />

10
svelte-ui/src/components/QuestLog.svelte

@ -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);
$: postponedProjects = inactiveProjects.filter(p => p.statusTag === "postponed" || p.statusTag == null);
$: ideaProjects = inactiveProjects.filter(p => p.statusTag === "idea" || p.statusTag == null);
$: failedProjects = inactiveProjects.filter(p => p.statusTag === "failed" || p.statusTag === "declined");
$: postponedProjects = inactiveProjects.filter(p => p.statusTag === "on hold" || p.statusTag === "postponed");
$: 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">

64
svelte-ui/src/components/StatusColor.svelte

@ -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 idea: boolean;
let onhold: boolean;
let todo: boolean;
let wontdo: boolean;
let project: boolean;
let task: 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";
$: completed = !entry.active && entry.statusTag === "completed";
$: failed = !entry.active && entry.statusTag === "failed";
$: onhold = !entry.active && entry.statusTag === "on hold";
$: 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.selected :global(.sccfg) { color: #666 !important; }
.status-color-context :global(.sccpb) { background-color: #78ff78 !important; }
.status-color-context.completed :global(.sccfg) { color: #484 !important; }
.status-color-context.completed.selected :global(.sccfg) { color: #78ff78 !important; }
.status-color-context.completed :global(.sccpb) { background-color: #78ff78 !important; }
.status-color-context.failed :global(.sccfg) { color: #852a24 !important; }
.status-color-context.failed.selected :global(.sccfg) { color: #ff4545 !important; }
.status-color-context.failed :global(.sccpb) { background-color: #ff4545 !important; }
.status-color-context.postponed :global(.sccfg) { color: #446d88 !important; }
.status-color-context.postponed.selected :global(.sccfg) { color: #78c9ff !important; }
.status-color-context.postponed :global(.sccpb) { background-color: #78c9ff !important; }
.status-color-context.idea :global(.sccfg) { color: #878844 !important; }
.status-color-context.idea.selected :global(.sccfg) { color: #e7e55e !important; }
.status-color-context.idea :global(.sccpb) { background-color: #e7e55e !important; }
.status-color-context.project :global(.sccfg) { color: #444 !important; }
.status-color-context.project.selected :global(.sccfg) { color: #666 !important; }
.status-color-context.project :global(.sccpb) { background-color: #78ff78 !important; }
.status-color-context.project.completed :global(.sccfg) { color: #484 !important; }
.status-color-context.project.completed.selected :global(.sccfg) { color: #78ff78 !important; }
.status-color-context.project.completed :global(.sccpb) { background-color: #78ff78 !important; }
.status-color-context.project.failed :global(.sccfg) { color: #852a24 !important; }
.status-color-context.project.failed.selected :global(.sccfg) { color: #ff4545 !important; }
.status-color-context.project.failed :global(.sccpb) { background-color: #ff4545 !important; }
.status-color-context.project.onhold :global(.sccfg) { color: #446d88 !important; }
.status-color-context.project.onhold.selected :global(.sccfg) { color: #78c9ff !important; }
.status-color-context.project.onhold :global(.sccpb) { background-color: #78c9ff !important; }
.status-color-context.project.todo :global(.sccfg) { color: #7a7429 !important; }
.status-color-context.project.todo.selected :global(.sccfg) { 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>

13
svelte-ui/src/components/StatusTagSelect.svelte

@ -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>

53
svelte-ui/src/components/TaskEntry.svelte

@ -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,16 +29,10 @@ import Markdown from "./Markdown.svelte";
$: mdTaskDelete = {name: "task.delete", task: {...task, project}}; $: mdTaskDelete = {name: "task.delete", task: {...task, project}};
</script> </script>
<ChildEntry entry={task}>
<div slot="icon" class="icon" class:done={!task.active}>
{#if !task.active}
<span class="on"><Icon block name="check" /></span>
<span class="off">
{task.completedAmount}&nbsp;/&nbsp;{task.itemAmount}
</span>
{:else}
{task.completedAmount}&nbsp;/&nbsp;{task.itemAmount}
{/if}
<StatusColor affects="task" entry={task}>
<ChildEntry entry={task}>
<div slot="icon">
<TaskIcon task={task} />
</div> </div>
<ItemLink item={task.item} /> <ItemLink item={task.item} />
<OptionRow> <OptionRow>
@ -62,40 +57,10 @@ import Markdown from "./Markdown.svelte";
{/each} {/each}
</div> </div>
{/if} {/if}
</ChildEntry>
</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;
} }

63
svelte-ui/src/components/TaskIcon.svelte

@ -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}&nbsp;/&nbsp;{task.itemAmount}
</span>
{:else}
{task.completedAmount}&nbsp;/&nbsp;{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>

30
svelte-ui/src/components/TaskList.svelte

@ -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>

44
svelte-ui/src/external/icons.ts

@ -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;

10
svelte-ui/src/forms/ProjectForm.svelte

@ -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}>
<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>
<StatusTagSelect disabled={deletion} bind:value={statusTag} />
<hr /> <hr />
<button disabled={loading} type="submit">{verb} Project</button> <button disabled={loading} type="submit">{verb} Project</button>

16
svelte-ui/src/forms/TaskForm.svelte

@ -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 />

7
svelte-ui/src/models/goal.ts

@ -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
} }

4
svelte-ui/src/models/task.ts

@ -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
} }
Loading…
Cancel
Save