Browse Source

add item composition in projects.

main
Gisle Aune 3 years ago
parent
commit
62235658eb
  1. 2
      api/task.go
  2. 17
      models/log.go
  3. 8
      models/task.go
  4. 114
      services/loader.go
  5. 108
      svelte-ui/src/components/ItemProgress.svelte
  6. 3
      svelte-ui/src/components/ProjectEntry.svelte
  7. 4
      svelte-ui/src/models/log.ts
  8. 4
      svelte-ui/src/models/task.ts

2
api/task.go

@ -68,7 +68,7 @@ func Task(g *gin.RouterGroup, db database.Database) {
return &models.TaskResult{ return &models.TaskResult{
Task: task, Task: task,
Logs: []*models.Log{},
Logs: []*models.LogWithSecondaryItem{},
Item: &item.Item, Item: &item.Item,
CompletedAmount: 0, CompletedAmount: 0,
}, nil }, nil

17
models/log.go

@ -67,14 +67,19 @@ type LogResult struct {
SecondaryItem *Item `json:"secondaryItem"` SecondaryItem *Item `json:"secondaryItem"`
} }
type LogWithSecondaryItem struct {
Log
SecondaryItem *Item `json:"secondaryItem,omitempty"`
}
type LogFilter struct { type LogFilter struct {
UserID string
UserID string
ProjectGroupIDs []string ProjectGroupIDs []string
ProjectIDs []string
TaskIDs []string
ItemIDs []string
MinTime *time.Time
MaxTime *time.Time
ProjectIDs []string
TaskIDs []string
ItemIDs []string
MinTime *time.Time
MaxTime *time.Time
} }
type LogRepository interface { type LogRepository interface {

8
models/task.go

@ -79,10 +79,10 @@ type TaskWithProject struct {
type TaskResult struct { type TaskResult struct {
Task Task
Item *Item `json:"item"`
Logs []*Log `json:"logs"`
CompletedAmount int `json:"completedAmount"`
Project *Project `json:"project,omitempty"`
Item *Item `json:"item"`
Logs []*LogWithSecondaryItem `json:"logs"`
CompletedAmount int `json:"completedAmount"`
Project *Project `json:"project,omitempty"`
} }
type TaskFilter struct { type TaskFilter struct {

114
services/loader.go

@ -251,6 +251,12 @@ func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectRes
UserID: auth.UserID(ctx), UserID: auth.UserID(ctx),
TaskIDs: taskIDs, TaskIDs: taskIDs,
}) })
for _, log := range logs {
if log.SecondaryItemID == nil {
itemIDs.Add(*log.SecondaryItemID)
}
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -265,12 +271,26 @@ func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectRes
result.Tasks = make([]*models.TaskResult, len(tasks)) result.Tasks = make([]*models.TaskResult, len(tasks))
for i, task := range tasks { for i, task := range tasks {
result.Tasks[i] = &models.TaskResult{ result.Tasks[i] = &models.TaskResult{
Logs: []*models.Log{},
Logs: []*models.LogWithSecondaryItem{},
} }
result.Tasks[i].Task = *task result.Tasks[i].Task = *task
for _, log := range logs { for _, log := range logs {
if log.TaskID == task.ID { if log.TaskID == task.ID {
result.Tasks[i].Logs = append(result.Tasks[i].Logs, log)
log := models.LogWithSecondaryItem{
Log: *log,
SecondaryItem: nil,
}
if log.SecondaryItemID != nil {
for _, item := range items {
if item.ID == *log.SecondaryItemID {
log.SecondaryItem = item
break
}
}
}
result.Tasks[i].Logs = append(result.Tasks[i].Logs, &log)
} }
} }
for _, item := range items { for _, item := range items {
@ -280,6 +300,10 @@ func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectRes
} }
} }
for _, log := range result.Tasks[i].Logs { for _, log := range result.Tasks[i].Logs {
if project.StartTime != nil && log.LoggedTime.Before(*project.StartTime) {
continue
}
result.Tasks[i].CompletedAmount += log.Amount(result.Tasks[i].ItemID) result.Tasks[i].CompletedAmount += log.Amount(result.Tasks[i].ItemID)
} }
} }
@ -319,6 +343,11 @@ func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, log := range logs {
if log.SecondaryItemID != nil {
itemIDs.Add(*log.SecondaryItemID)
}
}
items, err := l.DB.Items().List(ctx, models.ItemFilter{ items, err := l.DB.Items().List(ctx, models.ItemFilter{
UserID: auth.UserID(ctx), UserID: auth.UserID(ctx),
@ -350,11 +379,25 @@ func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter)
taskResult := &models.TaskResult{ taskResult := &models.TaskResult{
Task: *task, Task: *task,
Logs: []*models.Log{},
Logs: []*models.LogWithSecondaryItem{},
} }
for _, log := range logs { for _, log := range logs {
if log.TaskID == task.ID { if log.TaskID == task.ID {
taskResult.Logs = append(taskResult.Logs, log)
log := models.LogWithSecondaryItem{
Log: *log,
SecondaryItem: nil,
}
if log.SecondaryItemID != nil {
for _, item := range items {
if item.ID == *log.SecondaryItemID {
log.SecondaryItem = item
break
}
}
}
taskResult.Logs = append(taskResult.Logs, &log)
} }
} }
for _, item := range items { for _, item := range items {
@ -364,6 +407,10 @@ func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter)
} }
} }
for _, log := range taskResult.Logs { for _, log := range taskResult.Logs {
if project.StartTime != nil && log.LoggedTime.Before(*project.StartTime) {
continue
}
taskResult.CompletedAmount += log.Amount(taskResult.ItemID) taskResult.CompletedAmount += log.Amount(taskResult.ItemID)
} }
results[i].Tasks = append(results[i].Tasks, taskResult) results[i].Tasks = append(results[i].Tasks, taskResult)
@ -468,7 +515,7 @@ func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, e
result.Item, _ = l.DB.Items().Find(ctx, task.ItemID) result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID) result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
logs, err := l.DB.Logs().List(ctx, models.LogFilter{
UserID: task.UserID, UserID: task.UserID,
TaskIDs: []string{task.ID}, TaskIDs: []string{task.ID},
}) })
@ -476,8 +523,34 @@ func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, e
return nil, err return nil, err
} }
for _, log := range result.Logs {
itemIds := stringset.New()
for _, log := range logs {
if log.SecondaryItemID != nil {
itemIds.Add(*log.SecondaryItemID)
}
}
items, err := l.DB.Items().List(ctx, models.ItemFilter{
UserID: task.UserID,
IDs: itemIds.Strings(),
})
for _, log := range logs {
result.CompletedAmount += log.Amount(result.ItemID) result.CompletedAmount += log.Amount(result.ItemID)
log := models.LogWithSecondaryItem{
Log: *log,
SecondaryItem: nil,
}
if log.SecondaryItemID != nil {
for _, item := range items {
if item.ID == *log.SecondaryItemID {
log.SecondaryItem = item
break
}
}
}
result.Logs = append(result.Logs, &log)
} }
return result, nil return result, nil
@ -509,7 +582,11 @@ func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*mo
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, log := range logs {
if log.SecondaryItemID != nil {
itemIDs.Add(*log.SecondaryItemID)
}
}
items, err := l.DB.Items().List(ctx, models.ItemFilter{ items, err := l.DB.Items().List(ctx, models.ItemFilter{
UserID: auth.UserID(ctx), UserID: auth.UserID(ctx),
IDs: itemIDs.Strings(), IDs: itemIDs.Strings(),
@ -530,13 +607,30 @@ func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*mo
for _, task := range tasks { for _, task := range tasks {
result := &models.TaskResult{ result := &models.TaskResult{
Task: *task, Task: *task,
Logs: []*models.Log{},
Logs: []*models.LogWithSecondaryItem{},
} }
for _, log := range logs { for _, log := range logs {
if log.TaskID == task.ID {
result.Logs = append(result.Logs, log)
if log.TaskID != task.ID {
continue
} }
result.CompletedAmount += log.Amount(result.ItemID)
log := models.LogWithSecondaryItem{
Log: *log,
SecondaryItem: nil,
}
if log.SecondaryItemID != nil {
for _, item := range items {
if item.ID == *log.SecondaryItemID {
log.SecondaryItem = item
break
}
}
}
result.Logs = append(result.Logs, &log)
} }
for _, item := range items { for _, item := range items {

108
svelte-ui/src/components/ItemProgress.svelte

@ -0,0 +1,108 @@
<script lang="ts">
import type Item from "../models/item";
import type { ProjectResult } from "../models/project";
import Icon from "./Icon.svelte";
interface ListEntry {
item: Item
amount: number
target: number
extras: number
}
export let project: ProjectResult;
let list: ListEntry[] = [];
$: {
list = [];
for (const task of project.tasks) {
let entry = list.find(e => e.item.id === task.item.id);
if (entry == null) {
entry = {item: task.item, amount: 0, target: 0, extras: 0};
list.push(entry);
}
if (task.completedAmount > task.itemAmount) {
entry.extras += task.completedAmount - task.itemAmount;
entry.amount += task.itemAmount;
} else {
entry.amount += task.completedAmount;
}
entry.target += task.itemAmount;
for (const log of task.logs) {
if (log.secondaryItem != null) {
let entry = list.find(e => e.item.id === log.secondaryItemId);
if (entry == null) {
entry = {item: log.secondaryItem, amount: 0, target: 0, extras: 0};
list.push(entry);
}
entry.extras += log.secondaryItemAmount;
}
}
}
list.sort((a,b) => {
if (a.target === b.target) {
return a.item.name.localeCompare(b.item.name);
} else {
return b.target - a.target;
}
})
}
</script>
<div class="ItemProgress">
{#each list as entry (entry.item.id)}
<div class="entry">
<div class="icon">
<Icon name={entry.item.icon} />
</div>
<div class="name">
{entry.item.name}
</div>
{#if entry.target !== 0}
<div class="amount">
<span>{entry.amount}</span>
<span> / </span>
<span>{entry.target}</span>
{#if entry.extras > 0}
<span class="extras">+ {entry.extras}</span>
{/if}
</div>
{:else}
<div class="extras">
&times;{entry.extras}
</div>
{/if}
</div>
{/each}
</div>
<style>
div.ItemProgress {
display: inline-block;
padding: 0.25em;
font-size: 0.8em;
}
div.ItemProgress div.entry {
display: flex;
flex-direction: row;
color: #777;
}
div.ItemProgress div.entry div {
padding: 0.1em 0.25em;
}
div.ItemProgress div.entry .name {
color: #555;
}
div.ItemProgress div.entry .icon {
padding: 0.20em;
}
div.ItemProgress div.entry .extras {
color: #484;
}
</style>

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

@ -7,6 +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 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";
@ -120,6 +121,8 @@ import stuffLogClient from "../clients/stufflog";
{/if} {/if}
</div> </div>
{#if showAllOptions} {#if showAllOptions}
<ItemProgress project={project} />
<OptionRow> <OptionRow>
<Option open={mdAddTask}>Add Task</Option> <Option open={mdAddTask}>Add Task</Option>
<Option open={mdLinkTask}>Link Task</Option> <Option open={mdLinkTask}>Link Task</Option>

4
svelte-ui/src/models/log.ts

@ -20,6 +20,10 @@ export interface LogFilter {
projectGroupIds?: string[] projectGroupIds?: string[]
} }
export interface LogWithSecondaryItem extends Log {
secondaryItem?: Item
}
export interface LogResult extends Log { export interface LogResult extends Log {
task: TaskWithProject task: TaskWithProject
item: Item item: Item

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

@ -1,6 +1,6 @@
import type { IconName } from "../external/icons"; import type { IconName } from "../external/icons";
import type Item from "./item"; import type Item from "./item";
import type Log from "./log";
import type { LogWithSecondaryItem } from "./log";
import type Project from "./project"; import type Project from "./project";
export default interface Task { export default interface Task {
@ -23,7 +23,7 @@ export interface TaskWithProject extends Task {
export interface TaskResult extends Task { export interface TaskResult extends Task {
item: Item item: Item
logs: Log[]
logs: LogWithSecondaryItem[]
completedAmount: number completedAmount: number
project?: Project project?: Project
} }

Loading…
Cancel
Save