This commit is contained in:
@@ -47,6 +47,9 @@ func Project(g *gin.RouterGroup, db database.Database) {
|
|||||||
if project.EndTime != nil && project.EndTime.Before(time.Now()) {
|
if project.EndTime != nil && project.EndTime.Before(time.Now()) {
|
||||||
return nil, slerrors.BadRequest("Project end time must be later than current time.")
|
return nil, slerrors.BadRequest("Project end time must be later than current time.")
|
||||||
}
|
}
|
||||||
|
if project.StartTime != nil && (project.EndTime == nil || !project.StartTime.Before(*project.EndTime)) {
|
||||||
|
return nil, slerrors.BadRequest("Project start time must be before end time.")
|
||||||
|
}
|
||||||
|
|
||||||
project.ID = generate.ProjectID()
|
project.ID = generate.ProjectID()
|
||||||
project.UserID = auth.UserID(c)
|
project.UserID = auth.UserID(c)
|
||||||
@@ -80,6 +83,11 @@ func Project(g *gin.RouterGroup, db database.Database) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
project.Update(update)
|
project.Update(update)
|
||||||
|
|
||||||
|
if project.StartTime != nil && (project.EndTime == nil || !project.StartTime.Before(*project.EndTime)) {
|
||||||
|
return nil, slerrors.BadRequest("Project start time must be before end time.")
|
||||||
|
}
|
||||||
|
|
||||||
err = db.Projects().Update(c.Request.Context(), project.Project)
|
err = db.Projects().Update(c.Request.Context(), project.Project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ func (r *projectRepository) List(ctx context.Context, filter models.ProjectFilte
|
|||||||
func (r *projectRepository) Insert(ctx context.Context, project models.Project) error {
|
func (r *projectRepository) Insert(ctx context.Context, project models.Project) error {
|
||||||
_, err := r.db.NamedExecContext(ctx, `
|
_, err := r.db.NamedExecContext(ctx, `
|
||||||
INSERT INTO project(
|
INSERT INTO project(
|
||||||
project_id, user_id, name, description, icon, active, created_time, end_time, status_tag, favorite
|
project_id, user_id, name, description, icon, active, created_time, start_time, end_time, status_tag, favorite
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :end_time, :status_tag, :favorite
|
:project_id, :user_id, :name, :description, :icon, :active, :created_time, :start_time, :end_time, :status_tag, :favorite
|
||||||
)
|
)
|
||||||
`, &project)
|
`, &project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,6 +94,7 @@ func (r *projectRepository) Update(ctx context.Context, project models.Project)
|
|||||||
description = :description,
|
description = :description,
|
||||||
icon = :icon,
|
icon = :icon,
|
||||||
active = :active,
|
active = :active,
|
||||||
|
start_time = :start_time,
|
||||||
end_time = :end_time,
|
end_time = :end_time,
|
||||||
status_tag = :status_tag,
|
status_tag = :status_tag,
|
||||||
favorite = :favorite
|
favorite = :favorite
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project
|
||||||
|
ADD COLUMN start_time TIMESTAMP;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project
|
||||||
|
DROP COLUMN IF EXISTS start_time;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -13,6 +13,7 @@ type Project struct {
|
|||||||
Icon string `json:"icon" db:"icon"`
|
Icon string `json:"icon" db:"icon"`
|
||||||
Active bool `json:"active" db:"active"`
|
Active bool `json:"active" db:"active"`
|
||||||
CreatedTime time.Time `json:"createdTime" db:"created_time"`
|
CreatedTime time.Time `json:"createdTime" db:"created_time"`
|
||||||
|
StartTime *time.Time `json:"startTime" db:"start_time"`
|
||||||
EndTime *time.Time `json:"endTime" db:"end_time"`
|
EndTime *time.Time `json:"endTime" db:"end_time"`
|
||||||
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"`
|
||||||
@@ -31,6 +32,13 @@ func (project *Project) Update(update ProjectUpdate) {
|
|||||||
if update.Active != nil {
|
if update.Active != nil {
|
||||||
project.Active = *update.Active
|
project.Active = *update.Active
|
||||||
}
|
}
|
||||||
|
if update.StartTime != nil {
|
||||||
|
startTimeCopy := update.StartTime.UTC()
|
||||||
|
project.StartTime = &startTimeCopy
|
||||||
|
}
|
||||||
|
if update.ClearStartTime {
|
||||||
|
project.StartTime = nil
|
||||||
|
}
|
||||||
if update.EndTime != nil {
|
if update.EndTime != nil {
|
||||||
endTimeCopy := update.EndTime.UTC()
|
endTimeCopy := update.EndTime.UTC()
|
||||||
project.EndTime = &endTimeCopy
|
project.EndTime = &endTimeCopy
|
||||||
@@ -58,6 +66,8 @@ type ProjectUpdate struct {
|
|||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Icon *string `json:"icon"`
|
Icon *string `json:"icon"`
|
||||||
Active *bool `json:"active"`
|
Active *bool `json:"active"`
|
||||||
|
StartTime *time.Time `json:"startTime"`
|
||||||
|
ClearStartTime bool `json:"clearStartTime"`
|
||||||
EndTime *time.Time `json:"endTime"`
|
EndTime *time.Time `json:"endTime"`
|
||||||
ClearEndTime bool `json:"clearEndTime"`
|
ClearEndTime bool `json:"clearEndTime"`
|
||||||
StatusTag *string `json:"statusTag"`
|
StatusTag *string `json:"statusTag"`
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
{:else if (started)}
|
{:else if (started)}
|
||||||
<span class:danger class="started">{amountStr}{compact ? "" : " "}{unit} {!compact ? "left" : ""}</span>
|
<span class:danger class="started">{amountStr}{compact ? "" : " "}{unit} {!compact ? "left" : ""}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="pending">In {amountStr}{compact ? "" : " "}{unit}</span>
|
<span class="pending">{compact ? "" : "In "}{amountStr}{compact ? "" : " "}{unit}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -75,17 +75,17 @@
|
|||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
id: "this_year",
|
id: "this_year",
|
||||||
label: "This Year",
|
label: "End of this year",
|
||||||
value: endOfYear(now),
|
value: endOfYear(now),
|
||||||
});
|
});
|
||||||
options.push({
|
options.push({
|
||||||
id: "next_year",
|
id: "next_year",
|
||||||
label: "Next Year",
|
label: "End of next year",
|
||||||
value: endOfYear(new Date(Date.now() + (365.25 * 86400000))),
|
value: endOfYear(new Date(Date.now() + (365.25 * 86400000))),
|
||||||
});
|
});
|
||||||
options.push({
|
options.push({
|
||||||
id: "last_year",
|
id: "last_year",
|
||||||
label: "Last Year",
|
label: "End of last year",
|
||||||
value: endOfYear(new Date(Date.now() - (365.25 * 86400000))),
|
value: endOfYear(new Date(Date.now() - (365.25 * 86400000))),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * (t.item.groupWeight || 1)).reduce((n,m) => n+m, 0), 1);
|
progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * (t.item.groupWeight || 1)).reduce((n,m) => n+m, 0), 1);
|
||||||
|
|
||||||
if (project.endTime) {
|
if (project.endTime) {
|
||||||
const start = Date.parse(project.createdTime);
|
const start = Date.parse(project.startTime || project.createdTime);
|
||||||
const end = Date.parse(project.endTime);
|
const end = Date.parse(project.endTime);
|
||||||
const now = Math.min(Date.now(), end);
|
const now = Math.max(Math.min(Date.now(), end), start);
|
||||||
|
|
||||||
timeProgress = (now - start) / (end - start);
|
timeProgress = (now - start) / (end - start);
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
if (goal != null) {
|
if (goal != null) {
|
||||||
const start = Date.parse(goal.startTime);
|
const start = Date.parse(goal.startTime);
|
||||||
const end = Date.parse(goal.endTime);
|
const end = Date.parse(goal.endTime);
|
||||||
const now = Math.min(Date.now(), end);
|
const now = Math.max(Math.min(Date.now(), end), start);
|
||||||
|
|
||||||
progressAmount = goal.completedAmount;
|
progressAmount = goal.completedAmount;
|
||||||
progressTarget = goal.amount;
|
progressTarget = goal.amount;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="content">{project.name}</div>
|
<div class="content">{project.name}</div>
|
||||||
{#if project.endTime}
|
{#if project.endTime}
|
||||||
<div class="times">
|
<div class="times">
|
||||||
<DaysLeft compact startTime={project.createdTime} endTime={project.endTime} />
|
<DaysLeft compact startTime={project.startTime || project.createdTime} endTime={project.endTime} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { startOfMonth, startOfWeek, startOfYear, formatFormTime, lastMonth, nextMonth } from "../utils/time";
|
||||||
|
import EveryMinute from "./EveryMinute.svelte";
|
||||||
|
|
||||||
|
interface DateOption {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export let value: string;
|
||||||
|
export let disabled: boolean;
|
||||||
|
|
||||||
|
let selected: string = "custom";
|
||||||
|
let options: DateOption[] = [];
|
||||||
|
let now: Date = new Date();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (value === "") {
|
||||||
|
selected = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const option of options) {
|
||||||
|
if (formatFormTime(option.value) === value) {
|
||||||
|
selected = option.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (options.length === 0) {
|
||||||
|
let current = startOfMonth(now);
|
||||||
|
const nextWeek = new Date(now.getTime() + (86400000 * 7));
|
||||||
|
const lastWeek = new Date(now.getTime() - (86400000 * 7));
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
id: "none",
|
||||||
|
label: "Project's creation date",
|
||||||
|
value: new Date(Number.NaN),
|
||||||
|
});
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
id: "this_week",
|
||||||
|
label: "Start of this Week",
|
||||||
|
value: startOfWeek(now),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "next_week",
|
||||||
|
label: "Start of next Week",
|
||||||
|
value: startOfWeek(nextWeek),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "last_week",
|
||||||
|
label: "Start of last Week",
|
||||||
|
value: startOfWeek(lastWeek),
|
||||||
|
});
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
id: "this_month",
|
||||||
|
label: "Start of this month",
|
||||||
|
value: startOfMonth(current),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "next_month",
|
||||||
|
label: "Start of next month",
|
||||||
|
value: startOfMonth(nextMonth(current)),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "last_month",
|
||||||
|
label: "Start of last month",
|
||||||
|
value: startOfMonth(lastMonth(current)),
|
||||||
|
});
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
id: "this_year",
|
||||||
|
label: "Beginning of this year",
|
||||||
|
value: startOfYear(now),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "next_year",
|
||||||
|
label: "Beginning of next year",
|
||||||
|
value: startOfYear(new Date(Date.now() + (365.25 * 86400000))),
|
||||||
|
});
|
||||||
|
options.push({
|
||||||
|
id: "last_year",
|
||||||
|
label: "Beginning of last year",
|
||||||
|
value: startOfYear(new Date(Date.now() - (365.25 * 86400000))),
|
||||||
|
});
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
id: "custom",
|
||||||
|
label: "Specific Date",
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (selected !== "custom") {
|
||||||
|
const option = options.find(o => o.id === selected);
|
||||||
|
if (option != null) {
|
||||||
|
value = formatFormTime(option.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<EveryMinute bind:now={now} />
|
||||||
|
<select name="endTime" disabled={disabled} bind:value={selected}>
|
||||||
|
{#each options as option (option.id)}
|
||||||
|
<option value={option.id}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{#if selected === "custom"}
|
||||||
|
<input disabled={disabled} name="value" type="datetime-local" bind:value={value} />
|
||||||
|
{/if}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
import DeadlineSelect from "../components/DeadlineSelect.svelte";
|
import DeadlineSelect from "../components/DeadlineSelect.svelte";
|
||||||
import IconSelect from "../components/IconSelect.svelte";
|
import IconSelect from "../components/IconSelect.svelte";
|
||||||
import Modal from "../components/Modal.svelte";
|
import Modal from "../components/Modal.svelte";
|
||||||
|
import StartTimeSelect from "../components/StartTimeSelect.svelte";
|
||||||
import StatusTagSelect from "../components/StatusTagSelect.svelte";
|
import StatusTagSelect from "../components/StatusTagSelect.svelte";
|
||||||
import { DEFAULT_ICON } from "../external/icons";
|
import { DEFAULT_ICON } from "../external/icons";
|
||||||
import type { ProjectResult } from "../models/project";
|
import type { ProjectResult } from "../models/project";
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
throw new Error("Wrong form")
|
throw new Error("Wrong form")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startTime = formatFormTime(project.startTime);
|
||||||
let endTime = formatFormTime(project.endTime);
|
let endTime = formatFormTime(project.endTime);
|
||||||
let name = project.name;
|
let name = project.name;
|
||||||
let description = project.description;
|
let description = project.description;
|
||||||
@@ -52,6 +54,7 @@
|
|||||||
if (creation) {
|
if (creation) {
|
||||||
stuffLogClient.createProject({
|
stuffLogClient.createProject({
|
||||||
active: statusTag === "",
|
active: statusTag === "",
|
||||||
|
startTime: ( startTime == "" ) ? null : new Date(startTime),
|
||||||
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
||||||
statusTag: statusTag !== "" ? statusTag : null,
|
statusTag: statusTag !== "" ? statusTag : null,
|
||||||
|
|
||||||
@@ -77,6 +80,8 @@
|
|||||||
stuffLogClient.updateProject(project.id, {
|
stuffLogClient.updateProject(project.id, {
|
||||||
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
||||||
clearEndTime: ( endTime == "" ),
|
clearEndTime: ( endTime == "" ),
|
||||||
|
startTime: ( startTime == "" ) ? null : new Date(startTime),
|
||||||
|
clearStartTime: ( startTime == "" ),
|
||||||
active: statusTag === "",
|
active: statusTag === "",
|
||||||
statusTag: statusTag || null,
|
statusTag: statusTag || null,
|
||||||
clearStatusTag: statusTag === "",
|
clearStatusTag: statusTag === "",
|
||||||
@@ -111,6 +116,10 @@
|
|||||||
<IconSelect disabled={deletion} bind:value={icon} />
|
<IconSelect disabled={deletion} bind:value={icon} />
|
||||||
<label for="endTime">Deadline (Optional)</label>
|
<label for="endTime">Deadline (Optional)</label>
|
||||||
<DeadlineSelect disabled={deletion} bind:value={endTime} />
|
<DeadlineSelect disabled={deletion} bind:value={endTime} />
|
||||||
|
{#if endTime != ""}
|
||||||
|
<label for="endTime">Start time (Optional)</label>
|
||||||
|
<StartTimeSelect disabled={deletion} bind:value={startTime} />
|
||||||
|
{/if}
|
||||||
<label for="statusTag">Status</label>
|
<label for="statusTag">Status</label>
|
||||||
<StatusTagSelect disabled={deletion} isProject bind:value={statusTag} />
|
<StatusTagSelect disabled={deletion} isProject bind:value={statusTag} />
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default interface Project {
|
|||||||
active: boolean
|
active: boolean
|
||||||
createdTime: string
|
createdTime: string
|
||||||
favorite: boolean
|
favorite: boolean
|
||||||
|
startTime?: string
|
||||||
endTime?: string
|
endTime?: string
|
||||||
statusTag?: string
|
statusTag?: string
|
||||||
}
|
}
|
||||||
@@ -29,6 +30,7 @@ export interface ProjectInput {
|
|||||||
description: string
|
description: string
|
||||||
icon: IconName
|
icon: IconName
|
||||||
active: boolean
|
active: boolean
|
||||||
|
startTime?: string | Date
|
||||||
endTime?: string | Date
|
endTime?: string | Date
|
||||||
statusTag?: string
|
statusTag?: string
|
||||||
favorite?: boolean
|
favorite?: boolean
|
||||||
@@ -39,6 +41,8 @@ export interface ProjectUpdate {
|
|||||||
description?: string
|
description?: string
|
||||||
icon?: string
|
icon?: string
|
||||||
active?: boolean
|
active?: boolean
|
||||||
|
startTime?: string | Date
|
||||||
|
clearStartTime?: boolean
|
||||||
endTime?: string | Date
|
endTime?: string | Date
|
||||||
clearEndTime?: boolean
|
clearEndTime?: boolean
|
||||||
statusTag?: string
|
statusTag?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user