@@ -7,6 +7,8 @@ import (
|
||||
"github.com/gissleh/stufflog/internal/slerrors"
|
||||
"github.com/gissleh/stufflog/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type projectRepository struct {
|
||||
@@ -14,17 +16,17 @@ type projectRepository struct {
|
||||
}
|
||||
|
||||
func (r *projectRepository) Find(ctx context.Context, id string) (*models.Project, error) {
|
||||
res := models.Project{}
|
||||
res := projectDBO{}
|
||||
err := r.db.GetContext(ctx, &res, "SELECT * FROM project WHERE project_id=$1", id)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, slerrors.NotFound("Log")
|
||||
return nil, slerrors.NotFound("Project")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return res.ToProject(), nil
|
||||
}
|
||||
|
||||
func (r *projectRepository) List(ctx context.Context, filter models.ProjectFilter) ([]*models.Project, error) {
|
||||
@@ -60,26 +62,35 @@ func (r *projectRepository) List(ctx context.Context, filter models.ProjectFilte
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]*models.Project, 0, 8)
|
||||
res := make([]*projectDBO, 0, 8)
|
||||
err = r.db.SelectContext(ctx, &res, query, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return res, nil
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
res2 := make([]*models.Project, len(res))
|
||||
for i, v := range res {
|
||||
res2[i] = v.ToProject()
|
||||
}
|
||||
|
||||
return res2, nil
|
||||
}
|
||||
|
||||
func (r *projectRepository) Insert(ctx context.Context, project models.Project) error {
|
||||
if project.Tags == nil {
|
||||
project.Tags = []string{}
|
||||
}
|
||||
|
||||
_, err := r.db.NamedExecContext(ctx, `
|
||||
INSERT INTO project(
|
||||
project_id, user_id, name, description, icon, active, created_time, start_time, end_time, subtract_amount, status_tag, favorite
|
||||
project_id, user_id, name, description, icon, active, created_time, start_time, end_time, subtract_amount, status_tag, favorite, tags
|
||||
) VALUES (
|
||||
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :start_time, :end_time, :subtract_amount, :status_tag, :favorite
|
||||
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :start_time, :end_time, :subtract_amount, :status_tag, :favorite, :tags
|
||||
)
|
||||
`, &project)
|
||||
`, toProjectDBO(project))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,6 +99,10 @@ func (r *projectRepository) Insert(ctx context.Context, project models.Project)
|
||||
}
|
||||
|
||||
func (r *projectRepository) Update(ctx context.Context, project models.Project) error {
|
||||
if project.Tags == nil {
|
||||
project.Tags = []string{}
|
||||
}
|
||||
|
||||
_, err := r.db.NamedExecContext(ctx, `
|
||||
UPDATE project SET
|
||||
name = :name,
|
||||
@@ -98,9 +113,10 @@ func (r *projectRepository) Update(ctx context.Context, project models.Project)
|
||||
end_time = :end_time,
|
||||
subtract_amount = :subtract_amount,
|
||||
status_tag = :status_tag,
|
||||
favorite = :favorite
|
||||
favorite = :favorite,
|
||||
tags = :tags
|
||||
WHERE project_id=:project_id
|
||||
`, &project)
|
||||
`, toProjectDBO(project))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,3 +140,55 @@ func (r *projectRepository) Delete(ctx context.Context, project models.Project)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type projectDBO struct {
|
||||
ID string `json:"id" db:"project_id"`
|
||||
UserID string `json:"-" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
Icon string `json:"icon" db:"icon"`
|
||||
Active bool `json:"active" db:"active"`
|
||||
CreatedTime time.Time `json:"createdTime" db:"created_time"`
|
||||
StartTime *time.Time `json:"startTime" db:"start_time"`
|
||||
EndTime *time.Time `json:"endTime" db:"end_time"`
|
||||
SubtractAmount int `json:"subtractAmount" db:"subtract_amount"`
|
||||
StatusTag *string `json:"statusTag" db:"status_tag"`
|
||||
Favorite bool `json:"favorite" db:"favorite"`
|
||||
Tags pq.StringArray `json:"tags" db:"tags"`
|
||||
}
|
||||
|
||||
func toProjectDBO(project models.Project) *projectDBO {
|
||||
return &projectDBO{
|
||||
ID: project.ID,
|
||||
UserID: project.UserID,
|
||||
Name: project.Name,
|
||||
Description: project.Description,
|
||||
Icon: project.Icon,
|
||||
Active: project.Active,
|
||||
CreatedTime: project.CreatedTime,
|
||||
StartTime: project.StartTime,
|
||||
EndTime: project.EndTime,
|
||||
SubtractAmount: project.SubtractAmount,
|
||||
StatusTag: project.StatusTag,
|
||||
Favorite: project.Favorite,
|
||||
Tags: project.Tags,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *projectDBO) ToProject() *models.Project {
|
||||
return &models.Project{
|
||||
ID: d.ID,
|
||||
UserID: d.UserID,
|
||||
Name: d.Name,
|
||||
Description: d.Description,
|
||||
Icon: d.Icon,
|
||||
Active: d.Active,
|
||||
CreatedTime: d.CreatedTime,
|
||||
StartTime: d.StartTime,
|
||||
EndTime: d.EndTime,
|
||||
SubtractAmount: d.SubtractAmount,
|
||||
StatusTag: d.StatusTag,
|
||||
Favorite: d.Favorite,
|
||||
Tags: d.Tags,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE project
|
||||
ADD COLUMN tags TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE project
|
||||
DROP COLUMN tags;
|
||||
-- +goose StatementEnd
|
||||
@@ -18,6 +18,7 @@ type Project struct {
|
||||
SubtractAmount int `json:"subtractAmount" db:"subtract_amount"`
|
||||
StatusTag *string `json:"statusTag" db:"status_tag"`
|
||||
Favorite bool `json:"favorite" db:"favorite"`
|
||||
Tags []string `json:"tags" db:"tags"`
|
||||
}
|
||||
|
||||
func (project *Project) Update(update ProjectUpdate) {
|
||||
@@ -62,6 +63,9 @@ func (project *Project) Update(update ProjectUpdate) {
|
||||
if update.Favorite != nil {
|
||||
project.Favorite = *update.Favorite
|
||||
}
|
||||
if update.SetTags != nil {
|
||||
project.Tags = update.SetTags
|
||||
}
|
||||
|
||||
if project.StatusTag != nil && project.Active {
|
||||
project.StatusTag = nil
|
||||
@@ -80,6 +84,7 @@ type ProjectUpdate struct {
|
||||
SubtractAmount *int `json:"subtractAmount"`
|
||||
StatusTag *string `json:"statusTag"`
|
||||
ClearStatusTag bool `json:"clearStatusTag"`
|
||||
SetTags []string `json:"setTags"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import "context"
|
||||
|
||||
type ProjectGroup struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
UserID string `json:"-" db:"user_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Abbreviation string `json:"abbreviation" db:"abbreviation"`
|
||||
CategoryNames map[string]string `json:"categoryNames" db:"category_names"`
|
||||
}
|
||||
|
||||
type ProjectGroupUpdate struct {
|
||||
Name *string `json:"name"`
|
||||
Abbreviation *string `json:"abbreviation"`
|
||||
SetCategoryNames map[string]string `json:"setCategoryNames"`
|
||||
ResetCategoryName *string `json:"resetCategoryName"`
|
||||
}
|
||||
|
||||
type ProjectGroupFilter struct {
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
type ProjectGroupRepository interface {
|
||||
Find(ctx context.Context, id string) (*ProjectGroup, error)
|
||||
List(ctx context.Context, filter ProjectGroupFilter) ([]*ProjectGroup, error)
|
||||
Insert(ctx context.Context, group ProjectGroup) error
|
||||
Update(ctx context.Context, group ProjectGroup) error
|
||||
Delete(ctx context.Context, group ProjectGroup) error
|
||||
}
|
||||
@@ -92,6 +92,7 @@ import TimeProgress from "./TimeProgress.svelte";
|
||||
<TimeProgress startTime={entry.startTime || entry.createdTime} endTime={entry.endTime} />
|
||||
{/if}
|
||||
{/if}
|
||||
<slot name="above-description"></slot>
|
||||
{#if (full)}
|
||||
<Markdown source={entry.description} />
|
||||
{/if}
|
||||
|
||||
@@ -12,6 +12,7 @@ import stuffLogClient from "../clients/stufflog";
|
||||
import ParentEntry from "./ParentEntry.svelte";
|
||||
import ProgressNumbers from "./ProgressNumbers.svelte";
|
||||
import StatusColor from "./StatusColor.svelte";
|
||||
import TagList from "./TagList.svelte";
|
||||
import TaskList from "./TaskList.svelte";
|
||||
|
||||
export let project: ProjectResult = null;
|
||||
@@ -113,6 +114,9 @@ import stuffLogClient from "../clients/stufflog";
|
||||
<div slot="post-seprator">
|
||||
<ProgressNumbers project={project} />
|
||||
</div>
|
||||
<div slot="above-description">
|
||||
<TagList tags={project.tags} />
|
||||
</div>
|
||||
{#if showAllOptions}
|
||||
<OptionRow>
|
||||
<Option open={mdAddTask}>Add Task</Option>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import App from "../App.svelte";
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import selectionStore from "../stores/selection";
|
||||
import DaysLeft from "./DaysLeft.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import ProjectIcon from "./ProjectIcon.svelte";
|
||||
import ProjectProgress from "./ProjectProgress.svelte";
|
||||
import StatusColor from "./StatusColor.svelte";
|
||||
import Tag from "./Tag.svelte";
|
||||
import TimeProgress from "./TimeProgress.svelte";
|
||||
|
||||
export let project: ProjectResult;
|
||||
@@ -30,6 +32,9 @@
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="name">
|
||||
{#if project.tags.length > 0}
|
||||
<Tag small value={project.tags[0]} />
|
||||
{/if}
|
||||
<div class="content">{project.name}</div>
|
||||
{#if project.endTime}
|
||||
<div class="times">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
export let value = "";
|
||||
export let small = false;
|
||||
|
||||
let style = "";
|
||||
|
||||
$: {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
hash = value.charCodeAt(i) + ((hash << 5) - hash);
|
||||
hash = hash & hash;
|
||||
}
|
||||
|
||||
console.log(value, hash);
|
||||
|
||||
style = `color: hsl(${(hash << 4) % 360}, ${40 + (hash % 8)}%, ${50+(hash % 4)}%)`
|
||||
}
|
||||
</script>
|
||||
|
||||
<div style={style} class="tag" class:small>{value}</div>
|
||||
|
||||
<style>
|
||||
div.tag {
|
||||
padding: 0.25em 0.75ch;
|
||||
margin: 0.25em 0.5ch;
|
||||
font-size: 0.75em;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
div.tag.small {
|
||||
padding: 0em 0.5ch;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import Tag from "./Tag.svelte";
|
||||
|
||||
export let tags: string[] = [];
|
||||
</script>
|
||||
|
||||
<div class="tag-list">
|
||||
{#each tags as tag (tag)}
|
||||
<Tag value={tag} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div.tag-list {
|
||||
display: flex;
|
||||
margin-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -27,6 +27,7 @@
|
||||
tasks: [],
|
||||
favorite: false,
|
||||
subtractAmount: 0,
|
||||
tags: [],
|
||||
}
|
||||
let verb = "Add";
|
||||
if (md.name === "project.edit" || md.name === "project.delete") {
|
||||
@@ -46,6 +47,7 @@
|
||||
let subtractAmount = project.subtractAmount;
|
||||
let error = null;
|
||||
let loading = false;
|
||||
let tags = project.tags.join(", ");
|
||||
|
||||
function onSubmit() {
|
||||
loading = true;
|
||||
@@ -60,6 +62,7 @@
|
||||
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
||||
statusTag: statusTag !== "" ? statusTag : null,
|
||||
subtractAmount: Math.min(subtractAmount, 0),
|
||||
tags: tags.length > 0 ? tags.split(",").map(t => t.trim()) : [],
|
||||
|
||||
name, description, icon, favorite,
|
||||
}).then(() => {
|
||||
@@ -89,6 +92,7 @@
|
||||
statusTag: statusTag || null,
|
||||
clearStatusTag: statusTag === "",
|
||||
subtractAmount: subtractAmount,
|
||||
setTags: tags.length > 0 ? tags.split(",").map(t => t.trim()) : [],
|
||||
|
||||
name, description, icon, favorite,
|
||||
}).then(() => {
|
||||
@@ -128,6 +132,8 @@
|
||||
{/if}
|
||||
<label for="statusTag">Status</label>
|
||||
<StatusTagSelect disabled={deletion} isProject bind:value={statusTag} />
|
||||
<label for="tags">Tags (Comma Separated)</label>
|
||||
<input disabled={deletion} name="tags" type="text" bind:value={tags} />
|
||||
|
||||
<Checkbox disabled={deletion} bind:checked={favorite} label="Mark as favorite." />
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export default interface Project {
|
||||
createdTime: string
|
||||
favorite: boolean
|
||||
subtractAmount: number
|
||||
tags: string[]
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
statusTag?: string
|
||||
@@ -36,6 +37,7 @@ export interface ProjectInput {
|
||||
endTime?: string | Date
|
||||
statusTag?: string
|
||||
favorite?: boolean
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
export interface ProjectUpdate {
|
||||
@@ -51,4 +53,5 @@ export interface ProjectUpdate {
|
||||
clearStatusTag?: boolean
|
||||
subtractAmount?: number
|
||||
favorite?: boolean
|
||||
setTags?: string[]
|
||||
}
|
||||
Reference in New Issue
Block a user