fix task sorting and individual task deadline issues on front page.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
+15
-1
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/gissleh/stufflog/internal/slerrors"
|
||||
"github.com/gissleh/stufflog/models"
|
||||
"github.com/gissleh/stufflog/services"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,7 +27,19 @@ func Task(g *gin.RouterGroup, db database.Database) {
|
||||
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) {
|
||||
|
||||
@@ -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 = sq.Where(squirrel.Eq{"task.user_id": filter.UserID})
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
+19
-1
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,6 +21,7 @@ type Project struct {
|
||||
StatusTag *string `json:"statusTag" db:"status_tag"`
|
||||
Favorite bool `json:"favorite" db:"favorite"`
|
||||
Tags []string `json:"tags" db:"tags"`
|
||||
TaskSortFields []string `json:"taskSortFields" db:"task_sort_fields"`
|
||||
}
|
||||
|
||||
func (project *Project) Update(update ProjectUpdate) {
|
||||
@@ -74,6 +76,9 @@ func (project *Project) Update(update ProjectUpdate) {
|
||||
if update.SetTags != nil {
|
||||
project.Tags = update.SetTags
|
||||
}
|
||||
if update.TaskSortFields != nil {
|
||||
project.TaskSortFields = append(update.TaskSortFields[:0:0], update.TaskSortFields...)
|
||||
}
|
||||
|
||||
if project.StatusTag != nil && project.Active {
|
||||
project.StatusTag = nil
|
||||
@@ -95,14 +100,27 @@ type ProjectUpdate struct {
|
||||
ClearStatusTag bool `json:"clearStatusTag"`
|
||||
SetTags []string `json:"setTags"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
TaskSortFields []string `json:"taskSortFields"`
|
||||
}
|
||||
|
||||
type ProjectResult struct {
|
||||
Project
|
||||
Tasks []*TaskResult `json:"tasks"`
|
||||
Tasks []*TaskResult `json:"tasks"`
|
||||
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 {
|
||||
Item Item `json:"item"`
|
||||
Amount int `json:"amount"`
|
||||
|
||||
@@ -94,6 +94,94 @@ type TaskFilter struct {
|
||||
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 {
|
||||
Find(ctx context.Context, id string) (*Task, error)
|
||||
List(ctx context.Context, filter TaskFilter) ([]*Task, error)
|
||||
|
||||
@@ -317,6 +317,8 @@ func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectRes
|
||||
}
|
||||
}
|
||||
|
||||
result.SortTasks()
|
||||
|
||||
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].SortTasks()
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -189,7 +189,7 @@ export class StufflogClient {
|
||||
return data.task;
|
||||
}
|
||||
|
||||
async listTasks({active, expiring}: TaskFilter): Promise<TaskResult[]> {
|
||||
async listTasks({active, expiring, sort}: TaskFilter): Promise<TaskResult[]> {
|
||||
let queries = [];
|
||||
if (active != null) {
|
||||
queries.push(`active=${active}`);
|
||||
@@ -197,6 +197,9 @@ export class StufflogClient {
|
||||
if (expiring != null) {
|
||||
queries.push(`expiring=${expiring}`);
|
||||
}
|
||||
if (sort != null) {
|
||||
queries.push(`sort=${sort.join(",")}`)
|
||||
}
|
||||
|
||||
const query = queries.length > 0 ? `?${queries.join("&")}` : "";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import stuffLogClient from "../clients/stufflog";
|
||||
import stuffLogClient from "../clients/stufflog";
|
||||
|
||||
import type { ProjectResult, ProjectUpdate } from "../models/project";
|
||||
import type { TaskResult } from "../models/task";
|
||||
@@ -7,7 +7,7 @@ import stuffLogClient from "../clients/stufflog";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import IS_MOBILE from "../utils/phone-check";
|
||||
import Icon from "./Icon.svelte";
|
||||
import ItemProgress from "./ItemProgress.svelte";
|
||||
import ItemProgress from "./ItemProgress.svelte";
|
||||
import Option from "./Option.svelte";
|
||||
import OptionRow from "./OptionRow.svelte";
|
||||
import ParentEntry from "./ParentEntry.svelte";
|
||||
|
||||
@@ -8,15 +8,11 @@
|
||||
export let tasks: TaskResult[];
|
||||
export let showAllOptions: boolean;
|
||||
export let header: string;
|
||||
|
||||
let sortedTasks: TaskResult[] = [];
|
||||
|
||||
$: sortedTasks = tasks.sort((a,b) => a.createdTime.localeCompare(b.createdTime))
|
||||
</script>
|
||||
|
||||
{#if tasks.length > 0}
|
||||
<h3>{header}</h3>
|
||||
{#each sortedTasks as task (task.id)}
|
||||
{#each tasks as task (task.id)}
|
||||
<TaskEntry
|
||||
showAllOptions={showAllOptions}
|
||||
task={task} project={project}
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface TaskLink {
|
||||
export interface TaskFilter {
|
||||
active?: boolean
|
||||
expiring?: boolean
|
||||
sort?: string[]
|
||||
}
|
||||
|
||||
export interface TaskInput {
|
||||
|
||||
@@ -38,6 +38,7 @@ import ParentEntry from "../components/ParentEntry.svelte";
|
||||
fpTaskStore.load({
|
||||
active: true,
|
||||
expiring: true,
|
||||
sort: ["status"],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -77,17 +78,19 @@ import ParentEntry from "../components/ParentEntry.svelte";
|
||||
|
||||
fakeProjects = [];
|
||||
fakeMap = {};
|
||||
console.log(individualTasks, $fpTaskStore.tasks);
|
||||
for (let task of individualTasks) {
|
||||
if (!task.project.active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fakeProject = fakeProjects.find(p => p.id === task.id);
|
||||
let fakeProject = fakeProjects.find(p => p.id === task.projectId);
|
||||
if (fakeProject == null) {
|
||||
fakeMap[task.projectId] = true;
|
||||
fakeProjects.push({
|
||||
...task.project,
|
||||
tasks: [task],
|
||||
subtractions: [],
|
||||
});
|
||||
} else {
|
||||
fakeProject.tasks.push(task);
|
||||
|
||||
Reference in New Issue
Block a user