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/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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 (
|
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"`
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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("&")}` : "";
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user