From 83eb9862fc5e98fc245ed7c47384d63f03c7c19c Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sun, 18 Jul 2021 22:11:16 +0200 Subject: [PATCH] add project groups to frontend. --- api/project.go | 4 + database/postgres/projectgroups.go | 2 +- models/project.go | 5 +- services/loader.go | 2 +- svelte-ui/src/App.svelte | 12 ++ svelte-ui/src/clients/stufflog.ts | 17 ++- svelte-ui/src/components/ChildEntry.svelte | 6 +- svelte-ui/src/components/ColoredNumber.svelte | 17 +++ svelte-ui/src/components/FocusHandler.svelte | 6 +- svelte-ui/src/components/Modal.svelte | 3 + svelte-ui/src/components/OptionRow.svelte | 11 +- .../src/components/ProjectGroupMenu.svelte | 111 ++++++++++++++++++ .../src/components/ProjectGroupSelect.svelte | 19 +++ svelte-ui/src/components/ProjectSelect.svelte | 1 - svelte-ui/src/components/QLList.svelte | 3 +- svelte-ui/src/components/QLListItem.svelte | 14 +-- svelte-ui/src/components/QuestLog.svelte | 84 +++++++------ svelte-ui/src/components/StatusColor.svelte | 2 +- svelte-ui/src/components/Tag.svelte | 2 - svelte-ui/src/forms/ProjectForm.svelte | 19 ++- svelte-ui/src/forms/ProjectGroupForm.svelte | 105 +++++++++++++++++ svelte-ui/src/models/project.ts | 3 + svelte-ui/src/models/projectgroup.ts | 18 ++- svelte-ui/src/pages/ProjectPage.svelte | 2 +- svelte-ui/src/pages/QLPage.svelte | 36 +++++- svelte-ui/src/stores/markStale.ts | 2 + svelte-ui/src/stores/modal.ts | 6 +- svelte-ui/src/stores/projectGroup.ts | 48 ++++++++ svelte-ui/src/utils/sorters.ts | 28 +++++ 29 files changed, 524 insertions(+), 64 deletions(-) create mode 100644 svelte-ui/src/components/ColoredNumber.svelte create mode 100644 svelte-ui/src/components/ProjectGroupMenu.svelte create mode 100644 svelte-ui/src/components/ProjectGroupSelect.svelte create mode 100644 svelte-ui/src/forms/ProjectGroupForm.svelte create mode 100644 svelte-ui/src/stores/projectGroup.ts create mode 100644 svelte-ui/src/utils/sorters.ts diff --git a/api/project.go b/api/project.go index 7375e5d..7fd261c 100644 --- a/api/project.go +++ b/api/project.go @@ -62,6 +62,10 @@ func Project(g *gin.RouterGroup, db database.Database) { project.UserID = auth.UserID(c) project.CreatedTime = time.Now().UTC() + if project.GroupID != nil && *project.GroupID == "" { + project.GroupID = nil + } + err = db.Projects().Insert(c.Request.Context(), project) if err != nil { return nil, err diff --git a/database/postgres/projectgroups.go b/database/postgres/projectgroups.go index a3d441d..565dce2 100644 --- a/database/postgres/projectgroups.go +++ b/database/postgres/projectgroups.go @@ -31,7 +31,7 @@ func (r *projectGroupRepository) Find(ctx context.Context, id string) (*models.P func (r *projectGroupRepository) List(ctx context.Context, filter models.ProjectGroupFilter) ([]*models.ProjectGroup, error) { res := make([]*projectGroupDBO, 0, 16) - err := r.db.SelectContext(ctx, &res, "SELECT * FROM project_group WHERE user_id=$1", filter.UserID) + err := r.db.SelectContext(ctx, &res, "SELECT * FROM project_group WHERE user_id=$1 ORDER BY abbreviation", filter.UserID) if err != nil { if err == sql.ErrNoRows { return []*models.ProjectGroup{}, nil diff --git a/models/project.go b/models/project.go index f398233..38229ce 100644 --- a/models/project.go +++ b/models/project.go @@ -24,9 +24,10 @@ type Project struct { func (project *Project) Update(update ProjectUpdate) { if update.GroupID != nil { - project.GroupID = update.GroupID - if *project.GroupID == "" { + if *update.GroupID == "" { project.GroupID = nil + } else { + project.GroupID = update.GroupID } } if update.Name != nil { diff --git a/services/loader.go b/services/loader.go index bdd8f17..4de6a99 100644 --- a/services/loader.go +++ b/services/loader.go @@ -442,7 +442,7 @@ func (l *Loader) ListProjectGroups(ctx context.Context) ([]*models.ProjectGroupR result := &models.ProjectGroupResult{ ProjectGroup: models.ProjectGroup{ ID: "META_UNGROUPED", - Name: "Ungrouped", + Name: "Ungrouped Projects", Abbreviation: "OTHER", CategoryNames: map[string]string{}, }, diff --git a/svelte-ui/src/App.svelte b/svelte-ui/src/App.svelte index fd158b7..2662a6d 100644 --- a/svelte-ui/src/App.svelte +++ b/svelte-ui/src/App.svelte @@ -17,6 +17,7 @@ import GoalForm from "./forms/GoalForm.svelte"; import LogForm from "./forms/LogForm.svelte"; import ProjectForm from "./forms/ProjectForm.svelte"; + import ProjectGroupForm from "./forms/ProjectGroupForm.svelte"; import ItemForm from "./forms/ItemForm.svelte"; import TaskForm from "./forms/TaskForm.svelte"; import TaskLinkForm from "./forms/TaskLinkForm.svelte"; @@ -25,10 +26,12 @@ import ModalRoute from "./components/ModalRoute.svelte"; import FocusHandler from "./components/FocusHandler.svelte"; import Menu from "./components/Menu.svelte"; +import markStale from "./stores/markStale"; async function logout() { await signOut(); await authStore.check(); + markStale("*"); } onMount(() => { @@ -45,6 +48,12 @@ + + + + + + @@ -63,6 +72,9 @@ + + + diff --git a/svelte-ui/src/clients/stufflog.ts b/svelte-ui/src/clients/stufflog.ts index 2295bc7..aa73ead 100644 --- a/svelte-ui/src/clients/stufflog.ts +++ b/svelte-ui/src/clients/stufflog.ts @@ -5,7 +5,7 @@ import type { TaskFilter, TaskInput, TaskLink, TaskResult, TaskUpdate } from ".. import type { LogFilter, LogInput, LogResult, LogUpdate } from "../models/log"; import type { GroupInput, GroupResult, GroupUpdate } from "../models/group"; import type { ItemInput, ItemResult, ItemUpdate } from "../models/item"; -import type { ProjectGroupResult } from "../models/projectgroup"; +import type { ProjectGroupInput, ProjectGroupResult, ProjectGroupUpdate } from "../models/projectgroup"; export class StufflogClient { private root: string; @@ -109,6 +109,21 @@ export class StufflogClient { return data.projectGroups; } + async createProjectGroup(input: ProjectGroupInput): Promise { + const data = await this.fetch("POST", "/api/projectgroup/", input); + return data.projectGroup; + } + + async updateProjectGroup(id: string, input: ProjectGroupUpdate): Promise { + const data = await this.fetch("PUT", `/api/projectgroup/${id}`, input); + return data.projectGroup; + } + + async deleteProjectGroup(id: string): Promise { + const data = await this.fetch("DELETE", `/api/projectgroup/${id}`); + return data.project; + } + async findLog(id: string): Promise { diff --git a/svelte-ui/src/components/ChildEntry.svelte b/svelte-ui/src/components/ChildEntry.svelte index 57341c2..a0d2b54 100644 --- a/svelte-ui/src/components/ChildEntry.svelte +++ b/svelte-ui/src/components/ChildEntry.svelte @@ -8,6 +8,7 @@ interface ActualParent { id: string + groupId: string name: string } @@ -30,6 +31,7 @@ interface EntryCommonSub { id?: string + groupId?: string name: string icon: IconName project?: EntryCommonSub @@ -64,12 +66,12 @@ {/if} {:else if (entry.task && entry.task.project != null)} {/if}
{displayName}
diff --git a/svelte-ui/src/components/ColoredNumber.svelte b/svelte-ui/src/components/ColoredNumber.svelte new file mode 100644 index 0000000..622546b --- /dev/null +++ b/svelte-ui/src/components/ColoredNumber.svelte @@ -0,0 +1,17 @@ + + +{#if (!!number)} + +
{number}
+
+{/if} diff --git a/svelte-ui/src/components/FocusHandler.svelte b/svelte-ui/src/components/FocusHandler.svelte index 5948e8a..199b3ae 100644 --- a/svelte-ui/src/components/FocusHandler.svelte +++ b/svelte-ui/src/components/FocusHandler.svelte @@ -1,9 +1,13 @@ + +
@@ -7,4 +11,9 @@ margin-left: -0.5ch; margin-right: -0.5ch; } + + div.option-row.centered { + text-align: center; + padding-bottom: 0.5em; + } \ No newline at end of file diff --git a/svelte-ui/src/components/ProjectGroupMenu.svelte b/svelte-ui/src/components/ProjectGroupMenu.svelte new file mode 100644 index 0000000..a9ef543 --- /dev/null +++ b/svelte-ui/src/components/ProjectGroupMenu.svelte @@ -0,0 +1,111 @@ + + +
+ {#each groups as group (group.id)} +
onNavigate(group)} class:selected={selected === group.id}> +
{group.abbreviation}
+
+ + + + + +
+
+ {/each} + +
+
+
+
+
+ + \ No newline at end of file diff --git a/svelte-ui/src/components/ProjectGroupSelect.svelte b/svelte-ui/src/components/ProjectGroupSelect.svelte new file mode 100644 index 0000000..adf612d --- /dev/null +++ b/svelte-ui/src/components/ProjectGroupSelect.svelte @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/svelte-ui/src/components/ProjectSelect.svelte b/svelte-ui/src/components/ProjectSelect.svelte index e8b84a5..b4553f4 100644 --- a/svelte-ui/src/components/ProjectSelect.svelte +++ b/svelte-ui/src/components/ProjectSelect.svelte @@ -1,7 +1,6 @@ {#if projects.length > 0}

{label}

{#each projects as project (project.id)} - + {/each}
{/if} diff --git a/svelte-ui/src/components/QLListItem.svelte b/svelte-ui/src/components/QLListItem.svelte index cb6f5ff..411c377 100644 --- a/svelte-ui/src/components/QLListItem.svelte +++ b/svelte-ui/src/components/QLListItem.svelte @@ -1,26 +1,24 @@ diff --git a/svelte-ui/src/components/QuestLog.svelte b/svelte-ui/src/components/QuestLog.svelte index f4f97fc..e734f98 100644 --- a/svelte-ui/src/components/QuestLog.svelte +++ b/svelte-ui/src/components/QuestLog.svelte @@ -9,11 +9,21 @@ import QlList from "./QLList.svelte"; import Boi from "../components/Boi.svelte"; import type { ModalData } from "../stores/modal"; + import type { ProjectGroupResult } from "../models/projectgroup"; + import ProjectGroupMenu from "./ProjectGroupMenu.svelte"; + import OptionRow from "./OptionRow.svelte"; + import Option from "./Option.svelte"; + import type ProjectGroup from "../models/projectgroup"; - export let projects: ProjectResult[]; + export let groups: ProjectGroupResult[]; + export let projectId = ""; + export let groupId = ""; - const mdProjectAdd: ModalData = {name: "project.add"}; + let mdProjectAdd: ModalData = {name: "project.add", groupId: null}; + let mdGroupEdit: ModalData = {name: "projectgroup.edit", projectGroup: {} as ProjectGroup}; + let mdGroupDelete: ModalData = {name: "projectgroup.delete", projectGroup: {} as ProjectGroup}; + let projects: ProjectResult[] = []; let expiringProjects: ProjectResult[]; let activeProjects: ProjectResult[]; let inactiveProjects: ProjectResult[]; @@ -22,16 +32,29 @@ let onholdProjects: ProjectResult[]; let ideaProjects: ProjectResult[]; let project: ProjectResult = null; + let selectedGroup: ProjectGroupResult | null = null; function sortProjects(a: ProjectResult, b: ProjectResult) { const aName = `${a.tags.slice(0, 1).map(t => t+":").join("")} ${a.name}`.trim(); const bName = `${b.tags.slice(0, 1).map(t => t+":").join("")} ${b.name}`.trim(); - - console.log(aName, bName) return aName.localeCompare(bName); } + // Ensure a selection. + $: { + if (groupId == "" && groups.length > 0) { + groupId = groups[0].id; + } + } + + $: selectedGroup = groups.find(g => g.id === groupId) + $: projects = selectedGroup?.projects || []; + + $: mdProjectAdd = { name: "project.add", groupId: groupId } + $: mdGroupEdit = { name: "projectgroup.edit", projectGroup: selectedGroup } + $: mdGroupDelete = { name: "projectgroup.delete", projectGroup: selectedGroup } + $: 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)); $: activeProjects = projects.filter(p => p.active && !p.endTime).sort(sortProjects); @@ -43,40 +66,28 @@ $: backgroundProjects = inactiveProjects.filter(p => p.statusTag === "background").sort(sortProjects); $: progressProjects = inactiveProjects.filter(p => p.statusTag === "progress").sort(sortProjects); - $: { - if (project === null && projects.length > 0) { - if (lastQuest !== "") { - project = projects.find(p => p.id === lastQuest) || null; - } - - if (project === null) { - project = expiringProjects[0] || activeProjects[0] || completedProjects[0] || null; - } - - if (project !== null) { - selectionStore.change("hash", project.id); - } - } - } - - $: { - if ($selectionStore.hash.startsWith("P")) { - lastQuest = $selectionStore.hash; - } - } + $: project = selectedGroup?.projects.find(p => p.id === projectId) || null; +
+

{selectedGroup?.name || ""}

+ {#if !!groupId && groupId !== "META_UNGROUPED"} + + + + + {/if} Add Project - - - - - - - - + + + + + + + +
{#if project != null} @@ -96,6 +107,13 @@ width: 32ch; } + h2 { + font-weight: 200; + margin: 0; + padding-bottom: 0.2em; + text-align: center; + } + div.body { flex-grow: 1; margin: 1em 1ch; diff --git a/svelte-ui/src/components/StatusColor.svelte b/svelte-ui/src/components/StatusColor.svelte index 3dd2609..97c75f2 100644 --- a/svelte-ui/src/components/StatusColor.svelte +++ b/svelte-ui/src/components/StatusColor.svelte @@ -1,6 +1,6 @@ + + +
+ + + + + +