hide completed tasks in projects, and add individual deadline tasks to front page.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
+4
-4
@@ -14,15 +14,15 @@ import (
|
|||||||
func Task(g *gin.RouterGroup, db database.Database) {
|
func Task(g *gin.RouterGroup, db database.Database) {
|
||||||
l := services.Loader{DB: db}
|
l := services.Loader{DB: db}
|
||||||
|
|
||||||
defaultActive := true
|
|
||||||
|
|
||||||
g.GET("/", handler("tasks", func(c *gin.Context) (interface{}, error) {
|
g.GET("/", handler("tasks", func(c *gin.Context) (interface{}, error) {
|
||||||
filter := models.TaskFilter{}
|
filter := models.TaskFilter{}
|
||||||
if setting := c.Query("active"); setting != "" {
|
if setting := c.Query("active"); setting != "" {
|
||||||
active := setting == "true"
|
active := setting == "true"
|
||||||
filter.Active = &active
|
filter.Active = &active
|
||||||
} else {
|
}
|
||||||
filter.Active = &defaultActive
|
if setting := c.Query("expiring"); setting != "" {
|
||||||
|
expiring := setting == "true"
|
||||||
|
filter.Expiring = &expiring
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.ListTasks(c, filter)
|
return l.ListTasks(c, filter)
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ func (r *taskRepository) List(ctx context.Context, filter models.TaskFilter) ([]
|
|||||||
if filter.Active != nil {
|
if filter.Active != nil {
|
||||||
sq = sq.Where(squirrel.Eq{"task.active": *filter.Active})
|
sq = sq.Where(squirrel.Eq{"task.active": *filter.Active})
|
||||||
}
|
}
|
||||||
|
if filter.Expiring != nil {
|
||||||
|
if *filter.Expiring {
|
||||||
|
sq = sq.Where("task.end_time IS NOT NULL")
|
||||||
|
} else {
|
||||||
|
sq = sq.Where("task.end_time IS NULL")
|
||||||
|
}
|
||||||
|
}
|
||||||
if filter.IDs != nil {
|
if filter.IDs != nil {
|
||||||
sq = sq.Where(squirrel.Eq{"task.task_id": filter.IDs})
|
sq = sq.Where(squirrel.Eq{"task.task_id": filter.IDs})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ type TaskResult struct {
|
|||||||
type TaskFilter struct {
|
type TaskFilter struct {
|
||||||
UserID string
|
UserID string
|
||||||
Active *bool
|
Active *bool
|
||||||
|
Expiring *bool
|
||||||
IDs []string
|
IDs []string
|
||||||
ItemIDs []string
|
ItemIDs []string
|
||||||
ProjectIDs []string
|
ProjectIDs []string
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getJwt } from "./amplify";
|
import { getJwt } from "./amplify";
|
||||||
import type { GoalFilter, GoalInput, GoalResult, GoalUpdate } from "../models/goal";
|
import type { GoalFilter, GoalInput, GoalResult, GoalUpdate } from "../models/goal";
|
||||||
import type { ProjectFilter, ProjectInput, ProjectResult, ProjectUpdate } from "../models/project";
|
import type { ProjectFilter, ProjectInput, ProjectResult, ProjectUpdate } from "../models/project";
|
||||||
import type { TaskInput, TaskResult, TaskUpdate } from "../models/task";
|
import type { TaskFilter, TaskInput, TaskResult, TaskUpdate } from "../models/task";
|
||||||
import type { LogFilter, LogInput, LogResult, LogUpdate } from "../models/log";
|
import type { LogFilter, LogInput, LogResult, LogUpdate } from "../models/log";
|
||||||
import type { GroupInput, GroupResult, GroupUpdate } from "../models/group";
|
import type { GroupInput, GroupResult, GroupUpdate } from "../models/group";
|
||||||
import type { ItemInput, ItemResult, ItemUpdate } from "../models/item";
|
import type { ItemInput, ItemResult, ItemUpdate } from "../models/item";
|
||||||
@@ -134,8 +134,16 @@ export class StufflogClient {
|
|||||||
return data.task;
|
return data.task;
|
||||||
}
|
}
|
||||||
|
|
||||||
async listTasks(active?: boolean): Promise<TaskResult[]> {
|
async listTasks({active, expiring}: TaskFilter): Promise<TaskResult[]> {
|
||||||
let query = (active != null) ? `?active=${active}` : "";
|
let queries = [];
|
||||||
|
if (active != null) {
|
||||||
|
queries.push(`active=${active}`);
|
||||||
|
}
|
||||||
|
if (expiring != null) {
|
||||||
|
queries.push(`expiring=${expiring}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = queries.length > 0 ? `?${queries.join("&")}` : "";
|
||||||
|
|
||||||
const data = await this.fetch("GET", `/api/task/${query}`);
|
const data = await this.fetch("GET", `/api/task/${query}`);
|
||||||
return data.tasks;
|
return data.tasks;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IconName } from "../external/icons";
|
import type { IconName } from "../external/icons";
|
||||||
|
import TaskAddForm from "../forms/TaskAddForm.svelte";
|
||||||
import type { ProjectResult } from "../models/project";
|
import type { ProjectResult } from "../models/project";
|
||||||
import type { ModalData } from "../stores/modal";
|
import type { ModalData } from "../stores/modal";
|
||||||
import DaysLeft from "./DaysLeft.svelte";
|
import DaysLeft from "./DaysLeft.svelte";
|
||||||
@@ -10,16 +11,15 @@
|
|||||||
|
|
||||||
export let project: ProjectResult = null;
|
export let project: ProjectResult = null;
|
||||||
export let showAllOptions: boolean = false;
|
export let showAllOptions: boolean = false;
|
||||||
|
export let hideInactive: boolean = false;
|
||||||
|
|
||||||
let iconName: IconName = "question";
|
let iconName: IconName = "question";
|
||||||
let mdAddTask: ModalData;
|
let mdAddTask: ModalData;
|
||||||
let mdProjectAdd: ModalData;
|
|
||||||
let mdProjectEdit: ModalData;
|
let mdProjectEdit: ModalData;
|
||||||
let mdProjectDelete: ModalData;
|
let mdProjectDelete: ModalData;
|
||||||
|
|
||||||
$: iconName = project.icon as IconName;
|
$: iconName = project.icon as IconName;
|
||||||
$: mdAddTask = {name:"task.add", project};
|
$: mdAddTask = {name:"task.add", project};
|
||||||
$: mdProjectAdd = {name:"project.add"};
|
|
||||||
$: mdProjectEdit = {name:"project.edit", project};
|
$: mdProjectEdit = {name:"project.edit", project};
|
||||||
$: mdProjectDelete = {name:"project.delete", project};
|
$: mdProjectDelete = {name:"project.delete", project};
|
||||||
</script>
|
</script>
|
||||||
@@ -47,7 +47,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="list" class:full={showAllOptions}>
|
<div class="list" class:full={showAllOptions}>
|
||||||
{#each project.tasks as task (task.id)}
|
{#each project.tasks as task (task.id)}
|
||||||
<TaskEntry showAllOptions={showAllOptions} task={task} />
|
{#if !hideInactive || task.active}
|
||||||
|
<TaskEntry showAllOptions={showAllOptions} task={task} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { IconName } from "../external/icons";
|
||||||
import type Item from "./item";
|
import type Item from "./item";
|
||||||
|
|
||||||
export default interface Group {
|
export default interface Group {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
icon: string
|
icon: IconName
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import type { IconName } from "../external/icons";
|
||||||
import type Group from "./group";
|
import type Group from "./group";
|
||||||
|
|
||||||
export default interface Item {
|
export default interface Item {
|
||||||
id: string
|
id: string
|
||||||
groupId: string
|
groupId: string
|
||||||
groupWeight: number
|
groupWeight: number
|
||||||
icon: string
|
icon: IconName
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import type { IconName } from "../external/icons";
|
||||||
import type { TaskResult } from "./task";
|
import type { TaskResult } from "./task";
|
||||||
|
|
||||||
export default interface Project {
|
export default interface Project {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
icon: string
|
icon: IconName
|
||||||
active: boolean
|
active: boolean
|
||||||
createdTime: string
|
createdTime: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
@@ -22,7 +23,7 @@ export interface ProjectFilter {
|
|||||||
export interface ProjectInput {
|
export interface ProjectInput {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
icon: string
|
icon: IconName
|
||||||
active: boolean
|
active: boolean
|
||||||
endTime?: string | Date
|
endTime?: string | Date
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { IconName } from "../external/icons";
|
||||||
import type Item from "./item";
|
import type Item from "./item";
|
||||||
import type Log from "./log";
|
import type Log from "./log";
|
||||||
|
|
||||||
@@ -8,18 +9,22 @@ export default interface Task {
|
|||||||
itemAmount: number
|
itemAmount: number
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
icon: string
|
icon: IconName
|
||||||
active: boolean
|
active: boolean
|
||||||
createdTime: string
|
createdTime: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskResult extends Task {
|
export interface TaskResult extends Task {
|
||||||
item: Item
|
item: Item
|
||||||
logs: Log[]
|
logs: Log[]
|
||||||
completedAmount: number
|
completedAmount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TaskFilter {
|
||||||
|
active?: boolean
|
||||||
|
expiring?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface TaskInput {
|
export interface TaskInput {
|
||||||
itemId: string
|
itemId: string
|
||||||
projectId: string
|
projectId: string
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import EmptyList from "../components/EmptyList.svelte";
|
import EmptyList from "../components/EmptyList.svelte";
|
||||||
import GoalEntry from "../components/GoalEntry.svelte";
|
import GoalEntry from "../components/GoalEntry.svelte";
|
||||||
import ProjectEntry from "../components/ProjectEntry.svelte";
|
import ProjectEntry from "../components/ProjectEntry.svelte";
|
||||||
|
import type { ProjectResult } from "../models/project";
|
||||||
import { fpGoalStore } from "../stores/goal";
|
import { fpGoalStore } from "../stores/goal";
|
||||||
import { fpProjectStore } from "../stores/project";
|
import { fpProjectStore } from "../stores/project";
|
||||||
|
import { fpTaskStore } from "../stores/tasks";
|
||||||
|
|
||||||
|
let fakeProject: ProjectResult
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($fpGoalStore.stale && !$fpGoalStore.loading) {
|
if ($fpGoalStore.stale && !$fpGoalStore.loading) {
|
||||||
@@ -14,6 +18,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if ($fpTaskStore.stale && !$fpTaskStore.loading) {
|
||||||
|
fpTaskStore.load({
|
||||||
|
active: true,
|
||||||
|
expiring: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($fpProjectStore.stale && !$fpProjectStore.loading) {
|
if ($fpProjectStore.stale && !$fpProjectStore.loading) {
|
||||||
fpProjectStore.load({
|
fpProjectStore.load({
|
||||||
@@ -22,6 +35,21 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
fakeProject = {
|
||||||
|
id: "P_fakeProject",
|
||||||
|
active: true,
|
||||||
|
createdTime: new Date().toISOString(),
|
||||||
|
description: "",
|
||||||
|
icon: "list",
|
||||||
|
name: "Individual Tasks",
|
||||||
|
tasks: $fpTaskStore.tasks.filter(t => $fpProjectStore.projects.find(p => p.id === t.id) == null),
|
||||||
|
endTime: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(fakeProject, $fpTaskStore);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
@@ -41,9 +69,12 @@
|
|||||||
<h1>Upcoming Deadlines</h1>
|
<h1>Upcoming Deadlines</h1>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $fpProjectStore.projects as project (project.id)}
|
{#each $fpProjectStore.projects as project (project.id)}
|
||||||
<ProjectEntry project={project} />
|
<ProjectEntry hideInactive project={project} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if !$fpProjectStore.loading && $fpProjectStore.projects.length === 0}
|
{#if fakeProject.tasks.length > 0}
|
||||||
|
<ProjectEntry project={fakeProject} />
|
||||||
|
{/if}
|
||||||
|
{#if fakeProject.tasks.length === 0 && !$fpProjectStore.loading && $fpProjectStore.projects.length === 0}
|
||||||
<EmptyList icon="check" text="All good!" />
|
<EmptyList icon="check" text="All good!" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<Checkbox centered bind:checked={showInactive} label="Show inactive projects." />
|
<Checkbox centered bind:checked={showInactive} label="Show completed tasks and projects." />
|
||||||
</div>
|
</div>
|
||||||
{#each $projectStore.projects as project (project.id)}
|
{#each $projectStore.projects as project (project.id)}
|
||||||
<ProjectEntry showAllOptions project={project} />
|
<ProjectEntry hideInactive={!showInactive} showAllOptions project={project} />
|
||||||
{/each}
|
{/each}
|
||||||
<Boi open={mdProjectAdd}>Add Project</Boi>
|
<Boi open={mdProjectAdd}>Add Project</Boi>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ function createProjectStore() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async load(filter: ProjectFilter) {
|
async load(filter: ProjectFilter) {
|
||||||
console.log(filter);
|
|
||||||
update(v => ({...v, loading: true, stale: false, filter}));
|
update(v => ({...v, loading: true, stale: false, filter}));
|
||||||
const projects = await stuffLogClient.listProjects(filter);
|
const projects = await stuffLogClient.listProjects(filter);
|
||||||
update(v => ({...v, loading: false, projects: projects.reverse()}));
|
update(v => ({...v, loading: false, projects: projects.reverse()}));
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import stuffLogClient from "../clients/stufflog";
|
||||||
|
import type { TaskFilter, TaskResult } from "../models/task";
|
||||||
|
|
||||||
|
interface TaskStoreData {
|
||||||
|
loading: boolean
|
||||||
|
stale: boolean
|
||||||
|
filter: TaskFilter
|
||||||
|
tasks: TaskResult[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTaskStore() {
|
||||||
|
const {update, subscribe} = writable<TaskStoreData>({
|
||||||
|
loading: false,
|
||||||
|
stale: true,
|
||||||
|
filter: {},
|
||||||
|
tasks: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
|
||||||
|
markStale() {
|
||||||
|
update(v => ({...v, stale: true}));
|
||||||
|
},
|
||||||
|
|
||||||
|
async load(filter: TaskFilter) {
|
||||||
|
update(v => ({...v, loading: true, stale: false, filter}));
|
||||||
|
const tasks = await stuffLogClient.listTasks(filter);
|
||||||
|
update(v => ({...v, loading: false, tasks: [...tasks]}));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskStore = createTaskStore();
|
||||||
|
|
||||||
|
export default taskStore;
|
||||||
|
export const fpTaskStore = createTaskStore();
|
||||||
Reference in New Issue
Block a user