Browse Source

add multiple statuses to proejcts, though it's a bit hacked on.

main
Gisle Aune 4 years ago
parent
commit
f22b9b4947
  1. 7
      database/postgres/project.go
  2. 11
      migrations/postgres/20210120210752_add_project_column_status_tag.sql
  3. 9
      migrations/postgres/20210120211301_update_project_inactive_status_tag.sql
  4. 21
      models/project.go
  5. 8
      svelte-ui/src/components/ParentEntry.svelte
  6. 5
      svelte-ui/src/components/Progress.svelte
  7. 43
      svelte-ui/src/components/ProjectEntry.svelte
  8. 41
      svelte-ui/src/components/ProjectIcon.svelte
  9. 3
      svelte-ui/src/components/ProjectProgress.svelte
  10. 2
      svelte-ui/src/components/QLList.svelte
  11. 57
      svelte-ui/src/components/QLListItem.svelte
  12. 13
      svelte-ui/src/components/QuestLog.svelte
  13. 42
      svelte-ui/src/components/StatusColor.svelte
  14. 18
      svelte-ui/src/forms/ProjectForm.svelte
  15. 4
      svelte-ui/src/models/project.ts

7
database/postgres/project.go

@ -62,9 +62,9 @@ func (r *projectRepository) List(ctx context.Context, filter models.ProjectFilte
func (r *projectRepository) Insert(ctx context.Context, project models.Project) error { func (r *projectRepository) Insert(ctx context.Context, project models.Project) error {
_, err := r.db.NamedExecContext(ctx, ` _, err := r.db.NamedExecContext(ctx, `
INSERT INTO project( INSERT INTO project(
project_id, user_id, name, description, icon, active, created_time, end_time
project_id, user_id, name, description, icon, active, created_time, end_time, status_tag
) VALUES ( ) VALUES (
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :end_time
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :end_time, :status_tag
) )
`, &project) `, &project)
if err != nil { if err != nil {
@ -81,7 +81,8 @@ func (r *projectRepository) Update(ctx context.Context, project models.Project)
description = :description, description = :description,
icon = :icon, icon = :icon,
active = :active, active = :active,
end_time = :end_time
end_time = :end_time,
status_tag = :status_tag
WHERE project_id=:project_id WHERE project_id=:project_id
`, &project) `, &project)
if err != nil { if err != nil {

11
migrations/postgres/20210120210752_add_project_column_status_tag.sql

@ -0,0 +1,11 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE project
ADD COLUMN status_tag TEXT DEFAULT NULL;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE project
DROP COLUMN status_tag;
-- +goose StatementEnd

9
migrations/postgres/20210120211301_update_project_inactive_status_tag.sql

@ -0,0 +1,9 @@
-- +goose Up
-- +goose StatementBegin
UPDATE project SET status_tag='completed' WHERE active=false;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
SELECT 'Nothing to do';
-- +goose StatementEnd

21
models/project.go

@ -14,6 +14,7 @@ type Project 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 (project *Project) Update(update ProjectUpdate) { func (project *Project) Update(update ProjectUpdate) {
@ -36,15 +37,23 @@ func (project *Project) Update(update ProjectUpdate) {
if update.ClearEndTime { if update.ClearEndTime {
project.EndTime = nil project.EndTime = nil
} }
if update.StatusTag != nil {
project.StatusTag = update.StatusTag
}
if update.ClearStatusTag {
project.StatusTag = nil
}
} }
type ProjectUpdate struct { type ProjectUpdate struct {
Name *string `json:"name"`
Description *string `json:"description"`
Icon *string `json:"icon"`
Active *bool `json:"active"`
EndTime *time.Time `json:"endTime"`
ClearEndTime bool `json:"clearEndTime"`
Name *string `json:"name"`
Description *string `json:"description"`
Icon *string `json:"icon"`
Active *bool `json:"active"`
EndTime *time.Time `json:"endTime"`
ClearEndTime bool `json:"clearEndTime"`
StatusTag *string `json:"statusTag"`
ClearStatusTag bool `json:"clearStatusTag"`
} }
type ProjectResult struct { type ProjectResult struct {

8
svelte-ui/src/components/ParentEntry.svelte

@ -5,6 +5,7 @@
import Icon from "./Icon.svelte"; import Icon from "./Icon.svelte";
import LinkHook from "./LinkHook.svelte"; import LinkHook from "./LinkHook.svelte";
import Markdown from "./Markdown.svelte"; import Markdown from "./Markdown.svelte";
import ProjectIcon from "./ProjectIcon.svelte";
import ProjectProgress from "./ProjectProgress.svelte"; import ProjectProgress from "./ProjectProgress.svelte";
import TimeProgress from "./TimeProgress.svelte"; import TimeProgress from "./TimeProgress.svelte";
@ -24,6 +25,7 @@ import TimeProgress from "./TimeProgress.svelte";
project?: EntryIconHolder project?: EntryIconHolder
tasks?: TaskResult[] tasks?: TaskResult[]
active?: boolean active?: boolean
statusName?: string
} }
export let entry: EntryCommon; export let entry: EntryCommon;
@ -50,7 +52,11 @@ import TimeProgress from "./TimeProgress.svelte";
<LinkHook id={entry.id} /> <LinkHook id={entry.id} />
{#if !hideIcon} {#if !hideIcon}
<div class="icon" class:completed={entry.active === false}> <div class="icon" class:completed={entry.active === false}>
<Icon block name={iconName} />
{#if entry.active != null}
<ProjectIcon project={entry} />
{:else}
<Icon block name={iconName} />
{/if}
</div> </div>
{/if} {/if}
<div class="body"> <div class="body">

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

@ -18,6 +18,7 @@
export let gray = false; export let gray = false;
export let titlePercentageOnly = false; export let titlePercentageOnly = false;
export let titleTime = false; export let titleTime = false;
export let contextColor = false;
let offClass = COLORS[0]; let offClass = COLORS[0];
let onClass = COLORS[1]; let onClass = COLORS[1];
@ -52,6 +53,10 @@
onClass = "gray" onClass = "gray"
offClass = "none" offClass = "none"
} }
if (contextColor) {
onClass = "sccpb"
offClass = "none"
}
// 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.

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

@ -8,6 +8,7 @@ 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 TaskEntry from "./TaskEntry.svelte";
export let project: ProjectResult = null; export let project: ProjectResult = null;
@ -26,24 +27,26 @@ import Composition from "./Composition.svelte";
$: mdProjectDelete = {name:"project.delete", project}; $: mdProjectDelete = {name:"project.delete", project};
</script> </script>
<ParentEntry
full={showAllOptions}
entry={project}
headerLink={linkProject ? "/projects#"+project.id : ""}
hideProgress={hideProgress}
hideIcon={hideIcon}
showTimeProgress={!hideProgress}
>
{#if showAllOptions}
<OptionRow>
<Option open={mdAddTask}>Add Task</Option>
<Option open={mdProjectEdit}>Edit</Option>
<Option open={mdProjectDelete}>Delete</Option>
</OptionRow>
{/if}
{#each project.tasks as task (task.id)}
{#if !hideInactive || task.active}
<TaskEntry showAllOptions={showAllOptions} task={task} project={project} />
<StatusColor selected project={project}>
<ParentEntry
full={showAllOptions}
entry={project}
headerLink={linkProject ? "/projects#"+project.id : ""}
hideProgress={hideProgress}
hideIcon={hideIcon}
showTimeProgress={!hideProgress}
>
{#if showAllOptions}
<OptionRow>
<Option open={mdAddTask}>Add Task</Option>
<Option open={mdProjectEdit}>Edit</Option>
<Option open={mdProjectDelete}>Delete</Option>
</OptionRow>
{/if} {/if}
{/each}
</ParentEntry>
{#each project.tasks as task (task.id)}
{#if !hideInactive || task.active}
<TaskEntry showAllOptions={showAllOptions} task={task} project={project} />
{/if}
{/each}
</ParentEntry>
</StatusColor>

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

@ -0,0 +1,41 @@
<script lang="ts">
import type { IconName } from "../external/icons";
import Icon from "./Icon.svelte";
interface ProjectLike {
icon?: IconName
active?: boolean
statusTag?: string
}
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>
<span class:selected class:completed class:failed class:postponed class:idea>
<Icon block name={project.icon} />
</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>

3
svelte-ui/src/components/ProjectProgress.svelte

@ -4,6 +4,7 @@
interface ProjectLike { interface ProjectLike {
tasks?: TaskResult[] tasks?: TaskResult[]
statusTag?: string
} }
export let project: ProjectLike; export let project: ProjectLike;
@ -18,4 +19,4 @@
$: progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * t.item.groupWeight).reduce((n,m) => n+m, 0), 1); $: progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * t.item.groupWeight).reduce((n,m) => n+m, 0), 1);
</script> </script>
<Progress thin green count={progressAmount} target={progressTarget} />
<Progress thin contextColor count={progressAmount} target={progressTarget} />

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

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ProjectResult } from "../models/project"; import type { ProjectResult } from "../models/project";
import QlListItem from "./QLListItem.svelte";
import QlListItem from "./QLListItem.svelte";
export let projects: ProjectResult[]; export let projects: ProjectResult[];
export let label: string = ""; export let label: string = "";

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

@ -3,8 +3,10 @@
import selectionStore from "../stores/selection"; import selectionStore from "../stores/selection";
import DaysLeft from "./DaysLeft.svelte"; import DaysLeft from "./DaysLeft.svelte";
import Icon from "./Icon.svelte"; import Icon from "./Icon.svelte";
import ProjectIcon from "./ProjectIcon.svelte";
import ProjectProgress from "./ProjectProgress.svelte"; import ProjectProgress from "./ProjectProgress.svelte";
import TimeProgress from "./TimeProgress.svelte";
import StatusColor from "./StatusColor.svelte";
import TimeProgress from "./TimeProgress.svelte";
export let project: ProjectResult; export let project: ProjectResult;
@ -20,25 +22,27 @@ import TimeProgress from "./TimeProgress.svelte";
$: completed = !project.active; $: completed = !project.active;
</script> </script>
<div class="ql-list-item" on:click={handleClick} class:selected>
<div class="icon" class:completed>
<Icon block name={project.icon} />
</div>
<div class="header">
<div class="name">
<div class="content">{project.name}</div>
<StatusColor project={project} selected={selected}>
<div class="ql-list-item" on:click={handleClick} class:selected>
<div class="icon sccfg" class:completed>
<Icon block name={project.icon} />
</div>
<div class="header">
<div class="name">
<div class="content">{project.name}</div>
{#if project.endTime}
<div class="times">
<DaysLeft compact startTime={project.createdTime} endTime={project.endTime} />
</div>
{/if}
</div>
<ProjectProgress project={project} />
{#if project.endTime} {#if project.endTime}
<div class="times">
<DaysLeft compact startTime={project.createdTime} endTime={project.endTime} />
</div>
<TimeProgress startTime={project.createdTime} endTime={project.endTime} />
{/if} {/if}
</div> </div>
<ProjectProgress project={project} />
{#if project.endTime}
<TimeProgress startTime={project.createdTime} endTime={project.endTime} />
{/if}
</div> </div>
</div>
</StatusColor>
<style> <style>
div.ql-list-item { div.ql-list-item {
@ -54,33 +58,22 @@ import TimeProgress from "./TimeProgress.svelte";
cursor: pointer; cursor: pointer;
} }
div.ql-list-item:hover { div.ql-list-item:hover {
color: #ccc;
background-color: #191919; background-color: #191919;
} }
div.ql-list-item.selected { div.ql-list-item.selected {
background-color: #222; background-color: #222;
color: #aaa;
}
div.ql-list-item:hover {
color: #ccc;
} }
div.icon {
color: #444;
div.icon {
padding: 0.3em 0.5ch; padding: 0.3em 0.5ch;
padding-right: 1ch; padding-right: 1ch;
padding-top: 0.4em; padding-top: 0.4em;
} }
div.ql-list-item.selected div.icon {
color: #666;
}
div.icon.completed {
color: #484;
}
div.ql-list-item.selected div.icon.completed {
color: #78ff78;
}
div.ql-list-item:hover div.icon {
color: #ccc;
}
div.header { div.header {
flex-grow: 1; flex-grow: 1;

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

@ -8,13 +8,21 @@
let expiringProjects: ProjectResult[]; let expiringProjects: ProjectResult[];
let activeProjects: ProjectResult[]; let activeProjects: ProjectResult[];
let inactiveProjects: ProjectResult[];
let completedProjects: ProjectResult[]; let completedProjects: ProjectResult[];
let failedProjects: ProjectResult[];
let posponedProjects: ProjectResult[];
let ideaProjects: ProjectResult[];
let project: ProjectResult = null; let project: ProjectResult = null;
$: project = $selectionStore.hash.startsWith("P") ? projects.find(p => p.id === $selectionStore.hash) : null; $: project = $selectionStore.hash.startsWith("P") ? projects.find(p => p.id === $selectionStore.hash) : null;
$: expiringProjects = projects.filter(p => p.active && p.endTime).sort((a,b) => Date.parse(a.endTime) - Date.parse(b.endTime)); $: expiringProjects = projects.filter(p => p.active && p.endTime).sort((a,b) => Date.parse(a.endTime) - Date.parse(b.endTime));
$: 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));
$: completedProjects = 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);
$: 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);
$: { $: {
if (project === null && projects.length > 0) { if (project === null && projects.length > 0) {
@ -31,6 +39,9 @@
<QlList label="Deadlines" projects={expiringProjects} /> <QlList label="Deadlines" projects={expiringProjects} />
<QlList label="Active" projects={activeProjects} /> <QlList label="Active" projects={activeProjects} />
<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} />
</div> </div>
<div class="body"> <div class="body">
{#if project != null} {#if project != null}

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

@ -0,0 +1,42 @@
<script lang="ts">
interface ProjectLike {
active: boolean
statusTag?: string
}
export let selected = false;
export let project: ProjectLike;
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>
<div class="status-color-context" class:selected class:completed class:failed class:postponed class:idea>
<slot></slot>
</div>
<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; }
</style>

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

@ -34,7 +34,7 @@
let endTime = formatFormTime(project.endTime); let endTime = formatFormTime(project.endTime);
let name = project.name; let name = project.name;
let description = project.description; let description = project.description;
let completed = !project.active;
let statusTag = project.statusTag || "";
let icon = project.icon; let icon = project.icon;
let error = null; let error = null;
let loading = false; let loading = false;
@ -45,8 +45,9 @@
if (creation) { if (creation) {
stuffLogClient.createProject({ stuffLogClient.createProject({
active: !completed,
active: statusTag === "",
endTime: ( endTime == "" ) ? null : new Date(endTime), endTime: ( endTime == "" ) ? null : new Date(endTime),
statusTag: statusTag || null,
name, description, icon, name, description, icon,
}).then(() => { }).then(() => {
@ -70,7 +71,8 @@
stuffLogClient.updateProject(project.id, { stuffLogClient.updateProject(project.id, {
endTime: ( endTime == "" ) ? null : new Date(endTime), endTime: ( endTime == "" ) ? null : new Date(endTime),
clearEndTime: ( endTime == "" ), clearEndTime: ( endTime == "" ),
active: !completed,
active: statusTag === "",
statusTag: statusTag || null,
name, description, icon, name, description, icon,
}).then(() => { }).then(() => {
@ -100,8 +102,14 @@
<IconSelect disabled={deletion} bind:value={icon} /> <IconSelect disabled={deletion} bind:value={icon} />
<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="Project is completed." />
<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>
<hr /> <hr />
<button disabled={loading} type="submit">{verb} Project</button> <button disabled={loading} type="submit">{verb} Project</button>

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

@ -9,6 +9,7 @@ export default interface Project {
active: boolean active: boolean
createdTime: string createdTime: string
endTime?: string endTime?: string
statusTag?: string
} }
export interface ProjectResult extends Project { export interface ProjectResult extends Project {
@ -26,6 +27,7 @@ export interface ProjectInput {
icon: IconName icon: IconName
active: boolean active: boolean
endTime?: string | Date endTime?: string | Date
statusTag?: string
} }
export interface ProjectUpdate { export interface ProjectUpdate {
@ -35,4 +37,6 @@ export interface ProjectUpdate {
active?: boolean active?: boolean
endTime?: string | Date endTime?: string | Date
clearEndTime?: boolean clearEndTime?: boolean
statusTag?: string
clearStatusTag?: boolean
} }
Loading…
Cancel
Save