Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75dd79127e | ||
|
|
da6cdfdb0d | ||
|
|
7eb5f07299 | ||
|
|
d114f0d38c | ||
|
|
a3336b183c | ||
|
|
d8a269328f |
@@ -36,6 +36,7 @@ type Requirement struct {
|
|||||||
ProjectID int `json:"projectId"`
|
ProjectID int `json:"projectId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
IsCoarse bool `json:"isCoarse"`
|
||||||
Status models.Status `json:"status"`
|
Status models.Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ func (requirement *Requirement) Update(update models.RequirementUpdate) {
|
|||||||
if update.Status != nil && update.Status.Valid() {
|
if update.Status != nil && update.Status.Valid() {
|
||||||
requirement.Status = *update.Status
|
requirement.Status = *update.Status
|
||||||
}
|
}
|
||||||
|
if update.IsCoarse != nil {
|
||||||
|
requirement.IsCoarse = *update.IsCoarse
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequirementStat struct {
|
type RequirementStat struct {
|
||||||
|
|||||||
@@ -40,6 +40,8 @@
|
|||||||
import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
|
import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
|
||||||
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
||||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||||
|
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
||||||
|
import { faCheckToSlot } from "@fortawesome/free-solid-svg-icons/faCheckToSlot";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
"clock": faClock,
|
"clock": faClock,
|
||||||
@@ -62,6 +64,8 @@
|
|||||||
"search": faSearch,
|
"search": faSearch,
|
||||||
"chevron_right": faChevronRight,
|
"chevron_right": faChevronRight,
|
||||||
"chevron_down": faChevronDown,
|
"chevron_down": faChevronDown,
|
||||||
|
"trash": faTrash,
|
||||||
|
"check_slot": faCheckToSlot,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof icons;
|
export type IconName = keyof typeof icons;
|
||||||
|
|||||||
@@ -51,21 +51,6 @@
|
|||||||
div.header
|
div.header
|
||||||
display: flex
|
display: flex
|
||||||
|
|
||||||
h2
|
|
||||||
font-size: 1.25em
|
|
||||||
margin: 0
|
|
||||||
color: $color-entry10
|
|
||||||
|
|
||||||
span.sub
|
|
||||||
color: $color-entry5
|
|
||||||
|
|
||||||
&.big
|
|
||||||
margin-top: 1em
|
|
||||||
font-size: 2em
|
|
||||||
|
|
||||||
&.small
|
|
||||||
font-size: 1em
|
|
||||||
|
|
||||||
div.right
|
div.right
|
||||||
color: $color-entry2-transparent
|
color: $color-entry2-transparent
|
||||||
margin-left: auto
|
margin-left: auto
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let centered = false;
|
export let centered = false;
|
||||||
|
export let vertical = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="option-row" class:centered>
|
<div class="option-row" class:vertical class:centered>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -10,10 +11,18 @@
|
|||||||
div.option-row {
|
div.option-row {
|
||||||
margin-left: -0.4ch;
|
margin-left: -0.4ch;
|
||||||
margin-right: -0.4ch;
|
margin-right: -0.4ch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.option-row.centered {
|
div.option-row.centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 0.5em;
|
padding-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -54,7 +54,6 @@
|
|||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
margin-right: 1.5ch
|
margin-right: 1.5ch
|
||||||
font-size: 0.75em
|
font-size: 0.75em
|
||||||
margin-top: auto
|
|
||||||
|
|
||||||
span.event
|
span.event
|
||||||
font-size: 1.33em
|
font-size: 1.33em
|
||||||
@@ -64,7 +63,8 @@
|
|||||||
display: none
|
display: none
|
||||||
|
|
||||||
&.blankEvent
|
&.blankEvent
|
||||||
margin-right: 0.5ch
|
margin-top: 0.35em
|
||||||
|
margin-right: 0.75ch
|
||||||
|
|
||||||
span.event
|
span.event
|
||||||
display: none
|
display: none
|
||||||
@@ -78,6 +78,6 @@
|
|||||||
margin-top: auto
|
margin-top: auto
|
||||||
|
|
||||||
&.noProgress
|
&.noProgress
|
||||||
margin-top: -0.25em
|
margin-top: 0em
|
||||||
margin-bottom: auto
|
margin-bottom: auto
|
||||||
</style>
|
</style>
|
||||||
@@ -8,40 +8,47 @@
|
|||||||
import Amount from "../common/Amount.svelte";
|
import Amount from "../common/Amount.svelte";
|
||||||
import AmountRow from "../common/AmountRow.svelte";
|
import AmountRow from "../common/AmountRow.svelte";
|
||||||
import type { IconName } from "../layout/Icon.svelte";
|
import type { IconName } from "../layout/Icon.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
|
import { formatTime } from "$lib/utils/date";
|
||||||
|
|
||||||
export let item: Item;
|
export let item: Item;
|
||||||
export let event: "created" | "scheduled" | "acquired" | "none" = "none";
|
export let event: "created" | "scheduled" | "acquired" | "none" = "none";
|
||||||
export let compact = false;
|
export let compact = false;
|
||||||
|
export let showAcquiredTime = false;
|
||||||
|
|
||||||
let icon: IconName
|
let icon: IconName
|
||||||
let status: Status
|
let status: Status
|
||||||
|
let eventText: string
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (event === "none" || event === "acquired") {
|
if (event === "none" || event === "acquired") {
|
||||||
if (item.acquiredTime) {
|
if (item.acquiredTime) {
|
||||||
icon = "check";
|
icon = "check";
|
||||||
status = Status.Completed;
|
status = Status.Completed;
|
||||||
|
eventText = (showAcquiredTime || event === "acquired") ? formatTime(item.acquiredTime) : void(0)
|
||||||
}
|
}
|
||||||
} else if (event === "scheduled") {
|
} else if (event === "scheduled") {
|
||||||
if (item.acquiredTime) {
|
if (item.acquiredTime) {
|
||||||
icon = "hourglass";
|
icon = "hourglass";
|
||||||
status = Status.Blocked;
|
status = Status.Blocked;
|
||||||
|
eventText = "scheduled"
|
||||||
}
|
}
|
||||||
} else if (event === "created") {
|
} else if (event === "created") {
|
||||||
icon = "lightbulb";
|
icon = "lightbulb";
|
||||||
status = Status.Available;
|
status = Status.Available;
|
||||||
|
eventText = formatTime(item.acquiredTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SubSection small noProgress title={item.name} icon={icon} status={status} event={event}>
|
<SubSection small noProgress title={item.name} icon={icon} status={status} event={eventText}>
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
{#if item.acquiredTime == null}
|
{#if item.acquiredTime == null}
|
||||||
<Option open={{name: "item.acquire", item}} color="green">Acquire</Option>
|
<Option open={{name: "item.acquire", item}} color="green"><Icon name="check_slot" /></Option>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !compact}
|
{#if !compact}
|
||||||
<Option open={{name: "item.edit", item}}>Edit</Option>
|
<Option open={{name: "item.edit", item}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "item.delete", item}} color="red">Delete</Option>
|
<Option open={{name: "item.delete", item}} color="red"><Icon name="trash" /></Option>
|
||||||
{/if}
|
{/if}
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
{#if !compact}
|
{#if !compact}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@
|
|||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
<LabeledProgressRow>
|
<LabeledProgressRow>
|
||||||
{#each requirement.stats as stat (stat.id)}
|
{#each requirement.stats as stat (stat.id)}
|
||||||
<LabeledProgress count={stat.acquired} target={stat.required} name={stat.name} />
|
{#if !requirement.isCoarse || stat.required > 0}
|
||||||
|
<LabeledProgress count={stat.acquired} target={stat.required} name={stat.name} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</LabeledProgressRow>
|
</LabeledProgressRow>
|
||||||
{#each requirement.items as item (item.id)}
|
{#each requirement.items as item (item.id)}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</LabeledProgressRow>
|
</LabeledProgressRow>
|
||||||
{/if}
|
{/if}
|
||||||
{#if sprint.kind === SprintKind.Items}
|
{#if sprint.kind === SprintKind.Items}
|
||||||
{#each sprint.items as item (item.id)}
|
{#each (sprint.items||[]) as item (item.id)}
|
||||||
{#if !item.acquiredTime}
|
{#if !item.acquiredTime}
|
||||||
<ItemSubSection compact item={item} />
|
<ItemSubSection compact item={item} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
import StatInput from "$lib/components/controls/StatInput.svelte";
|
import StatInput from "$lib/components/controls/StatInput.svelte";
|
||||||
import StatusSelect from "$lib/components/controls/StatusSelect.svelte";
|
import StatusSelect from "$lib/components/controls/StatusSelect.svelte";
|
||||||
|
import Checkbox from "$lib/components/layout/Checkbox.svelte";
|
||||||
import type { ProjectEntry, Requirement, RequirementInput } from "$lib/models/project";
|
import type { ProjectEntry, Requirement, RequirementInput } from "$lib/models/project";
|
||||||
import Status from "$lib/models/status";
|
import Status from "$lib/models/status";
|
||||||
import { statDiff } from "$lib/utils/stat";
|
import { statDiff } from "$lib/utils/stat";
|
||||||
@@ -49,23 +50,25 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
description: "",
|
description: "",
|
||||||
stats: [],
|
stats: [],
|
||||||
status: Status.Available,
|
status: Status.Available,
|
||||||
|
isCoarse: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
show = true;
|
show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initEdit(prev: Requirement, project: ProjectEntry) {
|
function initEdit(current: Requirement, project: ProjectEntry) {
|
||||||
requirement = {
|
requirement = {
|
||||||
name: prev.name,
|
name: current.name,
|
||||||
description: prev.description,
|
description: current.description,
|
||||||
status: prev.status,
|
status: current.status,
|
||||||
stats: prev.stats.map(s => ({acquired: s.acquired, required: s.required, statId: s.id})),
|
stats: current.stats.map(s => ({acquired: s.acquired, required: s.required, statId: s.id})),
|
||||||
|
isCoarse: current.isCoarse,
|
||||||
}
|
}
|
||||||
|
|
||||||
oldStats = [...requirement.stats];
|
oldStats = [...requirement.stats];
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
requirementId = prev.id;
|
requirementId = current.id;
|
||||||
show = true;
|
show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +121,7 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
<StatusSelect bind:status={requirement.status} />
|
<StatusSelect bind:status={requirement.status} />
|
||||||
<label for="stats">Stats</label>
|
<label for="stats">Stats</label>
|
||||||
<StatInput showRequired bind:value={requirement.stats} />
|
<StatInput showRequired bind:value={requirement.stats} />
|
||||||
|
<Checkbox bind:checked={requirement.isCoarse} label="Hide 0-requirement stats." />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
@@ -37,6 +37,7 @@ export interface Requirement {
|
|||||||
totalAcquired: number
|
totalAcquired: number
|
||||||
totalRequired: number
|
totalRequired: number
|
||||||
totalPlanned: number
|
totalPlanned: number
|
||||||
|
isCoarse: boolean
|
||||||
stats: StatProgressWithPlanned[]
|
stats: StatProgressWithPlanned[]
|
||||||
items: Item[]
|
items: Item[]
|
||||||
}
|
}
|
||||||
@@ -45,5 +46,6 @@ export interface RequirementInput {
|
|||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
status: Status
|
status: Status
|
||||||
|
isCoarse: boolean
|
||||||
stats: StatValueInput[]
|
stats: StatValueInput[]
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,6 @@
|
|||||||
export const load: Load = async({params, url, fetch}) => {
|
export const load: Load = async({params, url, fetch}) => {
|
||||||
const scopeId = parseInt(params.scope.split("-")[0]);
|
const scopeId = parseInt(params.scope.split("-")[0]);
|
||||||
|
|
||||||
console.log(
|
|
||||||
datesOf(parseInterval("next:7d", new Date)),
|
|
||||||
parseInterval("today", new Date),
|
|
||||||
)
|
|
||||||
|
|
||||||
const scheduledItems = (await sl3(fetch).listItems(scopeId, {
|
const scheduledItems = (await sl3(fetch).listItems(scopeId, {
|
||||||
scheduledDate: datesOf(parseInterval("next:7d", new Date)),
|
scheduledDate: datesOf(parseInterval("next:7d", new Date)),
|
||||||
}))
|
}))
|
||||||
@@ -92,7 +87,7 @@
|
|||||||
{#if acquiredItems.length > 0}
|
{#if acquiredItems.length > 0}
|
||||||
<Row title="Today">
|
<Row title="Today">
|
||||||
{#each acquiredItems as item (item.id)}
|
{#each acquiredItems as item (item.id)}
|
||||||
<ItemSubSection item={item} />
|
<ItemSubSection showAcquiredTime item={item} />
|
||||||
{/each}
|
{/each}
|
||||||
</Row>
|
</Row>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ type RequirementUpdate struct {
|
|||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Status *Status `json:"status"`
|
Status *Status `json:"status"`
|
||||||
|
IsCoarse *bool `json:"isCoarse"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ type ProjectRequirement struct {
|
|||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
|
IsCoarse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectRequirementStat struct {
|
type ProjectRequirementStat struct {
|
||||||
|
|||||||
@@ -156,8 +156,8 @@ func (q *Queries) InsertProject(ctx context.Context, arg InsertProjectParams) (s
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insertProjectRequirement = `-- name: InsertProjectRequirement :execresult
|
const insertProjectRequirement = `-- name: InsertProjectRequirement :execresult
|
||||||
INSERT INTO project_requirement (scope_id, project_id, name, status, description)
|
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertProjectRequirementParams struct {
|
type InsertProjectRequirementParams struct {
|
||||||
@@ -166,6 +166,7 @@ type InsertProjectRequirementParams struct {
|
|||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
|
IsCoarse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjectRequirementParams) (sql.Result, error) {
|
func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjectRequirementParams) (sql.Result, error) {
|
||||||
@@ -175,11 +176,12 @@ func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjec
|
|||||||
arg.Name,
|
arg.Name,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
|
arg.IsCoarse,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const listProjectRequirements = `-- name: ListProjectRequirements :many
|
const listProjectRequirements = `-- name: ListProjectRequirements :many
|
||||||
SELECT id, scope_id, project_id, name, status, description FROM project_requirement WHERE project_id = ?
|
SELECT id, scope_id, project_id, name, status, description, is_coarse FROM project_requirement WHERE project_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
|
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
|
||||||
@@ -198,6 +200,7 @@ func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([
|
|||||||
&i.Name,
|
&i.Name,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
|
&i.IsCoarse,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -326,7 +329,8 @@ const updateProjectRequirement = `-- name: UpdateProjectRequirement :exec
|
|||||||
UPDATE project_requirement
|
UPDATE project_requirement
|
||||||
SET name = ?,
|
SET name = ?,
|
||||||
status = ?,
|
status = ?,
|
||||||
description = ?
|
description = ?,
|
||||||
|
is_coarse = ?
|
||||||
WHERE id = ? AND scope_id = ?
|
WHERE id = ? AND scope_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -334,6 +338,7 @@ type UpdateProjectRequirementParams struct {
|
|||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
|
IsCoarse bool
|
||||||
ID int
|
ID int
|
||||||
ScopeID int
|
ScopeID int
|
||||||
}
|
}
|
||||||
@@ -343,6 +348,7 @@ func (q *Queries) UpdateProjectRequirement(ctx context.Context, arg UpdateProjec
|
|||||||
arg.Name,
|
arg.Name,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
|
arg.IsCoarse,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
arg.ScopeID,
|
arg.ScopeID,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
|
|||||||
return []entities.Requirement{}, []entities.RequirementStat{}, nil
|
return []entities.Requirement{}, []entities.RequirementStat{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
query, args, err := squirrel.Select("id, scope_id, project_id, name, status, description").
|
query, args, err := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse").
|
||||||
From("project_requirement").
|
From("project_requirement").
|
||||||
Where(squirrel.Eq{"scope_id": scopeID}).
|
Where(squirrel.Eq{"scope_id": scopeID}).
|
||||||
Where(squirrel.Eq{"id": requirementIDs}).
|
Where(squirrel.Eq{"id": requirementIDs}).
|
||||||
@@ -162,6 +162,7 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
|
|||||||
&requirement.Name,
|
&requirement.Name,
|
||||||
&requirement.Status,
|
&requirement.Status,
|
||||||
&requirement.Description,
|
&requirement.Description,
|
||||||
|
&requirement.IsCoarse,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -217,6 +218,7 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
|
|||||||
ProjectID: row.ProjectID,
|
ProjectID: row.ProjectID,
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
Description: row.Description,
|
Description: row.Description,
|
||||||
|
IsCoarse: row.IsCoarse,
|
||||||
Status: models.Status(row.Status),
|
Status: models.Status(row.Status),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -240,6 +242,7 @@ func (r *projectRepository) CreateRequirement(ctx context.Context, requirement e
|
|||||||
Name: requirement.Name,
|
Name: requirement.Name,
|
||||||
Status: int(requirement.Status),
|
Status: int(requirement.Status),
|
||||||
Description: requirement.Description,
|
Description: requirement.Description,
|
||||||
|
IsCoarse: requirement.IsCoarse,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -261,6 +264,7 @@ func (r *projectRepository) UpdateRequirement(ctx context.Context, requirement e
|
|||||||
Name: requirement.Name,
|
Name: requirement.Name,
|
||||||
Status: int(requirement.Status),
|
Status: int(requirement.Status),
|
||||||
Description: requirement.Description,
|
Description: requirement.Description,
|
||||||
|
IsCoarse: requirement.IsCoarse,
|
||||||
ID: requirement.ID,
|
ID: requirement.ID,
|
||||||
ScopeID: requirement.ScopeID,
|
ScopeID: requirement.ScopeID,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,14 +23,15 @@ DELETE FROM project WHERE id = ? AND scope_id = ?;
|
|||||||
SELECT * FROM project_requirement WHERE project_id = ?;
|
SELECT * FROM project_requirement WHERE project_id = ?;
|
||||||
|
|
||||||
-- name: InsertProjectRequirement :execresult
|
-- name: InsertProjectRequirement :execresult
|
||||||
INSERT INTO project_requirement (scope_id, project_id, name, status, description)
|
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse)
|
||||||
VALUES (?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
-- name: UpdateProjectRequirement :exec
|
-- name: UpdateProjectRequirement :exec
|
||||||
UPDATE project_requirement
|
UPDATE project_requirement
|
||||||
SET name = ?,
|
SET name = ?,
|
||||||
status = ?,
|
status = ?,
|
||||||
description = ?
|
description = ?,
|
||||||
|
is_coarse = ?
|
||||||
WHERE id = ? AND scope_id = ?;
|
WHERE id = ? AND scope_id = ?;
|
||||||
|
|
||||||
-- name: DeleteProjectRequirement :exec
|
-- name: DeleteProjectRequirement :exec
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project_requirement ADD COLUMN is_coarse BOOLEAN NOT NULL DEFAULT 0;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project_requirement DROP COLUMN IF EXISTS is_coarse;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -3,7 +3,6 @@ package items
|
|||||||
import (
|
import (
|
||||||
"git.aiterp.net/stufflog3/stufflog3/entities"
|
"git.aiterp.net/stufflog3/stufflog3/entities"
|
||||||
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -53,13 +52,22 @@ func (r *Result) AddStat(scope scopes.Result, stat entities.ItemStat) {
|
|||||||
Acquired: stat.Acquired,
|
Acquired: stat.Acquired,
|
||||||
Required: stat.Required,
|
Required: stat.Required,
|
||||||
})
|
})
|
||||||
r.SortStats()
|
r.SortStats(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) SortStats() {
|
func (r *Result) SortStats(scope scopes.Result) {
|
||||||
sort.Slice(r.Stats, func(i, j int) bool {
|
sortedStats := make([]ResultStat, 0, len(r.Stats))
|
||||||
return r.Stats[i].Name < r.Stats[j].Name
|
|
||||||
})
|
for _, scopeStat := range scope.Stats {
|
||||||
|
for _, itemStat := range r.Stats {
|
||||||
|
if itemStat.ID == scopeStat.ID {
|
||||||
|
sortedStats = append(sortedStats, itemStat)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Stats = sortedStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) RemoveStat(id int) {
|
func (r *Result) RemoveStat(id int) {
|
||||||
@@ -112,7 +120,7 @@ func generateResult(item entities.Item, scope scopes.Result, progresses []entiti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.SortStats()
|
res.SortStats(scope)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
|
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
|
||||||
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
||||||
"math"
|
"math"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ type RequirementResult struct {
|
|||||||
TotalAcquired int `json:"totalAcquired"`
|
TotalAcquired int `json:"totalAcquired"`
|
||||||
TotalRequired int `json:"totalRequired"`
|
TotalRequired int `json:"totalRequired"`
|
||||||
TotalPlanned int `json:"totalPlanned"`
|
TotalPlanned int `json:"totalPlanned"`
|
||||||
|
IsCoarse bool `json:"isCoarse"`
|
||||||
Stats []RequirementResultStat `json:"stats"`
|
Stats []RequirementResultStat `json:"stats"`
|
||||||
Items []items.Result `json:"items"`
|
Items []items.Result `json:"items"`
|
||||||
|
|
||||||
@@ -150,6 +152,7 @@ func generateRequirementResult(req entities.Requirement, scope scopes.Result, re
|
|||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
StatusName: scope.StatusName(req.Status),
|
StatusName: scope.StatusName(req.Status),
|
||||||
|
IsCoarse: req.IsCoarse,
|
||||||
Stats: make([]RequirementResultStat, 0, 8),
|
Stats: make([]RequirementResultStat, 0, 8),
|
||||||
Items: make([]items.Result, 0, 8),
|
Items: make([]items.Result, 0, 8),
|
||||||
|
|
||||||
@@ -194,6 +197,21 @@ func generateRequirementResult(req entities.Requirement, scope scopes.Result, re
|
|||||||
resReq.Items = append(resReq.Items, item)
|
resReq.Items = append(resReq.Items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort items so that they're in order of created, with acquired items being in order of acquired below.
|
||||||
|
sort.Slice(resReq.Items, func(i, j int) bool {
|
||||||
|
ii := resReq.Items[i]
|
||||||
|
ij := resReq.Items[j]
|
||||||
|
if ii.AcquiredTime != nil && ij.AcquiredTime != nil {
|
||||||
|
return ii.AcquiredTime.Before(*ij.AcquiredTime)
|
||||||
|
} else if ii.AcquiredTime != nil && ij.AcquiredTime == nil {
|
||||||
|
return false
|
||||||
|
} else if ii.AcquiredTime == nil && ij.AcquiredTime != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ii.CreatedTime.Before(ij.CreatedTime)
|
||||||
|
})
|
||||||
|
|
||||||
for _, stat := range scope.Stats {
|
for _, stat := range scope.Stats {
|
||||||
if rs := resStats[stat.ID]; rs != nil && rs.Required > 0 {
|
if rs := resStats[stat.ID]; rs != nil && rs.Required > 0 {
|
||||||
resReq.Stats = append(resReq.Stats, *rs)
|
resReq.Stats = append(resReq.Stats, *rs)
|
||||||
|
|||||||
Reference in New Issue
Block a user