Browse Source

fix task sorting and individual task deadline issues on front page.

main
Gisle Aune 2 years ago
parent
commit
531ae46f5b
  1. 16
      api/task.go
  2. 16
      database/postgres/tasks.go
  3. 11
      migrations/postgres/20211204105833_add_project_column_task_sort_fields.sql
  4. 20
      models/project.go
  5. 88
      models/task.go
  6. 4
      services/loader.go
  7. 5
      svelte-ui/src/clients/stufflog.ts
  8. 4
      svelte-ui/src/components/ProjectEntry.svelte
  9. 6
      svelte-ui/src/components/TaskList.svelte
  10. 1
      svelte-ui/src/models/task.ts
  11. 5
      svelte-ui/src/pages/FrontPage.svelte

16
api/task.go

@ -8,6 +8,8 @@ import (
"github.com/gissleh/stufflog/internal/slerrors" "github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models" "github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services" "github.com/gissleh/stufflog/services"
"sort"
"strings"
"time" "time"
) )
@ -25,7 +27,19 @@ func Task(g *gin.RouterGroup, db database.Database) {
filter.Expiring = &expiring filter.Expiring = &expiring
} }
return l.ListTasks(c, filter)
tasks, err := l.ListTasks(c, filter)
if err != nil {
return nil, err
}
sort.Sort(
models.TaskSorter{
Fields: strings.Split(c.Query("sort"), ","),
Data: tasks,
},
)
return tasks, nil
})) }))
g.GET("/:id", handler("task", func(c *gin.Context) (interface{}, error) { g.GET("/:id", handler("task", func(c *gin.Context) (interface{}, error) {

16
database/postgres/tasks.go

@ -41,7 +41,21 @@ func (r *taskRepository) ListWithLinks(ctx context.Context, filter models.TaskFi
sq := squirrel.Select("task.*", "tl.project_id as tl_project_id", "p.icon").From("task").PlaceholderFormat(squirrel.Dollar) sq := squirrel.Select("task.*", "tl.project_id as tl_project_id", "p.icon").From("task").PlaceholderFormat(squirrel.Dollar)
sq = sq.Where(squirrel.Eq{"task.user_id": filter.UserID}) sq = sq.Where(squirrel.Eq{"task.user_id": filter.UserID})
if filter.Active != nil { if filter.Active != nil {
sq = sq.Where(squirrel.Eq{"task.active": *filter.Active})
if *filter.Active {
sq = sq.Where(squirrel.Or{
squirrel.Eq{"task.active": true},
squirrel.Eq{"task.status_tag": []string{
"on hold", "to do",
}},
})
} else {
sq = sq.Where(squirrel.And{
squirrel.Eq{"task.active": false},
squirrel.Eq{"task.status_tag": []string{
"failed", "completed", "declined",
}},
})
}
} }
if filter.Expiring != nil { if filter.Expiring != nil {
if *filter.Expiring { if *filter.Expiring {

11
migrations/postgres/20211204105833_add_project_column_task_sort_fields.sql

@ -0,0 +1,11 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE project
ADD COLUMN task_sort_fields TEXT[] DEFAULT ARRAY[]::TEXT[];
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE project
DROP COLUMN task_sort_fields;
-- +goose StatementEnd

20
models/project.go

@ -2,6 +2,7 @@ package models
import ( import (
"context" "context"
"sort"
"time" "time"
) )
@ -20,6 +21,7 @@ type Project struct {
StatusTag *string `json:"statusTag" db:"status_tag"` StatusTag *string `json:"statusTag" db:"status_tag"`
Favorite bool `json:"favorite" db:"favorite"` Favorite bool `json:"favorite" db:"favorite"`
Tags []string `json:"tags" db:"tags"` Tags []string `json:"tags" db:"tags"`
TaskSortFields []string `json:"taskSortFields" db:"task_sort_fields"`
} }
func (project *Project) Update(update ProjectUpdate) { func (project *Project) Update(update ProjectUpdate) {
@ -74,6 +76,9 @@ func (project *Project) Update(update ProjectUpdate) {
if update.SetTags != nil { if update.SetTags != nil {
project.Tags = update.SetTags project.Tags = update.SetTags
} }
if update.TaskSortFields != nil {
project.TaskSortFields = append(update.TaskSortFields[:0:0], update.TaskSortFields...)
}
if project.StatusTag != nil && project.Active { if project.StatusTag != nil && project.Active {
project.StatusTag = nil project.StatusTag = nil
@ -95,14 +100,27 @@ type ProjectUpdate struct {
ClearStatusTag bool `json:"clearStatusTag"` ClearStatusTag bool `json:"clearStatusTag"`
SetTags []string `json:"setTags"` SetTags []string `json:"setTags"`
Favorite *bool `json:"favorite"` Favorite *bool `json:"favorite"`
TaskSortFields []string `json:"taskSortFields"`
} }
type ProjectResult struct { type ProjectResult struct {
Project Project
Tasks []*TaskResult `json:"tasks"`
Tasks []*TaskResult `json:"tasks"`
Subtractions []ProjectSubtraction `json:"subtractions"` Subtractions []ProjectSubtraction `json:"subtractions"`
} }
func (result *ProjectResult) SortTasks() {
sorter := TaskSorter{
Data: result.Tasks,
Fields: result.Project.TaskSortFields,
}
if len(sorter.Fields) == 0 {
sorter.Fields = []string{"status"}
}
sort.Sort(sorter)
}
type ProjectSubtraction struct { type ProjectSubtraction struct {
Item Item `json:"item"` Item Item `json:"item"`
Amount int `json:"amount"` Amount int `json:"amount"`

88
models/task.go

@ -94,6 +94,94 @@ type TaskFilter struct {
ProjectIDs []string ProjectIDs []string
} }
var taskStatusOrder = []string{"", "to do", "on hold", "completed", "failed", "declined"}
type TaskSorter struct {
Data []*TaskResult
Fields []string
}
func (s *TaskSorter) Valid() bool {
for _, field := range s.Fields {
switch field {
case "name", "-name", "createdTime", "-createdTime", "amount", "-amount", "status", "-status":
default:
return false
}
}
return true
}
func (s TaskSorter) Len() int {
return len(s.Data)
}
func (s TaskSorter) Less(i, j int) bool {
a := s.Data[i]
b := s.Data[j]
for _, field := range s.Fields {
switch field {
case "status", "-status":
as := ""
if a.StatusTag != nil {
as = *a.StatusTag
}
bs := ""
if b.StatusTag != nil {
bs = *b.StatusTag
}
if as != bs {
asi := 1000
bsi := 1000
for i, sn := range taskStatusOrder {
if sn == as {
asi = i
}
if sn == bs {
bsi = i
}
}
if field == "-status" {
return asi > bsi
} else {
return asi < bsi
}
}
case "amount":
if a.ItemAmount != b.ItemAmount {
return a.ItemAmount < b.ItemAmount
}
case "-amount":
if a.ItemAmount != b.ItemAmount {
return a.ItemAmount > b.ItemAmount
}
case "name":
if a.Name != b.Name {
return a.Name < b.Name
}
case "-name":
if a.Name != b.Name {
return a.Name > b.Name
}
case "-time":
return a.CreatedTime.After(b.CreatedTime)
case "time":
return a.CreatedTime.Before(b.CreatedTime)
}
}
return a.CreatedTime.Before(b.CreatedTime)
}
func (s TaskSorter) Swap(i, j int) {
s.Data[i], s.Data[j] = s.Data[j], s.Data[i]
}
type TaskRepository interface { type TaskRepository interface {
Find(ctx context.Context, id string) (*Task, error) Find(ctx context.Context, id string) (*Task, error)
List(ctx context.Context, filter TaskFilter) ([]*Task, error) List(ctx context.Context, filter TaskFilter) ([]*Task, error)

4
services/loader.go

@ -317,6 +317,8 @@ func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectRes
} }
} }
result.SortTasks()
return result, nil return result, nil
} }
@ -433,6 +435,8 @@ func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter)
} }
results[i].Tasks = append(results[i].Tasks, taskResult) results[i].Tasks = append(results[i].Tasks, taskResult)
} }
results[i].SortTasks()
} }
return results, nil return results, nil

5
svelte-ui/src/clients/stufflog.ts

@ -189,7 +189,7 @@ export class StufflogClient {
return data.task; return data.task;
} }
async listTasks({active, expiring}: TaskFilter): Promise<TaskResult[]> {
async listTasks({active, expiring, sort}: TaskFilter): Promise<TaskResult[]> {
let queries = []; let queries = [];
if (active != null) { if (active != null) {
queries.push(`active=${active}`); queries.push(`active=${active}`);
@ -197,6 +197,9 @@ export class StufflogClient {
if (expiring != null) { if (expiring != null) {
queries.push(`expiring=${expiring}`); queries.push(`expiring=${expiring}`);
} }
if (sort != null) {
queries.push(`sort=${sort.join(",")}`)
}
const query = queries.length > 0 ? `?${queries.join("&")}` : ""; const query = queries.length > 0 ? `?${queries.join("&")}` : "";

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

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import stuffLogClient from "../clients/stufflog";
import stuffLogClient from "../clients/stufflog";
import type { ProjectResult, ProjectUpdate } from "../models/project"; import type { ProjectResult, ProjectUpdate } from "../models/project";
import type { TaskResult } from "../models/task"; import type { TaskResult } from "../models/task";
@ -7,7 +7,7 @@ import stuffLogClient from "../clients/stufflog";
import type { ModalData } from "../stores/modal"; import type { ModalData } from "../stores/modal";
import IS_MOBILE from "../utils/phone-check"; import IS_MOBILE from "../utils/phone-check";
import Icon from "./Icon.svelte"; import Icon from "./Icon.svelte";
import ItemProgress from "./ItemProgress.svelte";
import ItemProgress from "./ItemProgress.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";

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

@ -8,15 +8,11 @@
export let tasks: TaskResult[]; export let tasks: TaskResult[];
export let showAllOptions: boolean; export let showAllOptions: boolean;
export let header: string; export let header: string;
let sortedTasks: TaskResult[] = [];
$: sortedTasks = tasks.sort((a,b) => a.createdTime.localeCompare(b.createdTime))
</script> </script>
{#if tasks.length > 0} {#if tasks.length > 0}
<h3>{header}</h3> <h3>{header}</h3>
{#each sortedTasks as task (task.id)}
{#each tasks as task (task.id)}
<TaskEntry <TaskEntry
showAllOptions={showAllOptions} showAllOptions={showAllOptions}
task={task} project={project} task={task} project={project}

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

@ -37,6 +37,7 @@ export interface TaskLink {
export interface TaskFilter { export interface TaskFilter {
active?: boolean active?: boolean
expiring?: boolean expiring?: boolean
sort?: string[]
} }
export interface TaskInput { export interface TaskInput {

5
svelte-ui/src/pages/FrontPage.svelte

@ -38,6 +38,7 @@ import ParentEntry from "../components/ParentEntry.svelte";
fpTaskStore.load({ fpTaskStore.load({
active: true, active: true,
expiring: true, expiring: true,
sort: ["status"],
}) })
} }
} }
@ -77,17 +78,19 @@ import ParentEntry from "../components/ParentEntry.svelte";
fakeProjects = []; fakeProjects = [];
fakeMap = {}; fakeMap = {};
console.log(individualTasks, $fpTaskStore.tasks);
for (let task of individualTasks) { for (let task of individualTasks) {
if (!task.project.active) { if (!task.project.active) {
continue; continue;
} }
let fakeProject = fakeProjects.find(p => p.id === task.id);
let fakeProject = fakeProjects.find(p => p.id === task.projectId);
if (fakeProject == null) { if (fakeProject == null) {
fakeMap[task.projectId] = true; fakeMap[task.projectId] = true;
fakeProjects.push({ fakeProjects.push({
...task.project, ...task.project,
tasks: [task], tasks: [task],
subtractions: [],
}); });
} else { } else {
fakeProject.tasks.push(task); fakeProject.tasks.push(task);

Loading…
Cancel
Save