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()) {
|
||||
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.UserID = auth.UserID(c)
|
||||
@@ -80,6 +83,11 @@ func Project(g *gin.RouterGroup, db database.Database) {
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
_, err := r.db.NamedExecContext(ctx, `
|
||||
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 (
|
||||
: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)
|
||||
if err != nil {
|
||||
@@ -94,6 +94,7 @@ func (r *projectRepository) Update(ctx context.Context, project models.Project)
|
||||
description = :description,
|
||||
icon = :icon,
|
||||
active = :active,
|
||||
start_time = :start_time,
|
||||
end_time = :end_time,
|
||||
status_tag = :status_tag,
|
||||
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"`
|
||||
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"`
|
||||
StatusTag *string `json:"statusTag" db:"status_tag"`
|
||||
Favorite bool `json:"favorite" db:"favorite"`
|
||||
@@ -31,6 +32,13 @@ func (project *Project) Update(update ProjectUpdate) {
|
||||
if update.Active != nil {
|
||||
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 {
|
||||
endTimeCopy := update.EndTime.UTC()
|
||||
project.EndTime = &endTimeCopy
|
||||
@@ -58,6 +66,8 @@ type ProjectUpdate struct {
|
||||
Description *string `json:"description"`
|
||||
Icon *string `json:"icon"`
|
||||
Active *bool `json:"active"`
|
||||
StartTime *time.Time `json:"startTime"`
|
||||
ClearStartTime bool `json:"clearStartTime"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
ClearEndTime bool `json:"clearEndTime"`
|
||||
StatusTag *string `json:"statusTag"`
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{:else if (started)}
|
||||
<span class:danger class="started">{amountStr}{compact ? "" : " "}{unit} {!compact ? "left" : ""}</span>
|
||||
{:else}
|
||||
<span class="pending">In {amountStr}{compact ? "" : " "}{unit}</span>
|
||||
<span class="pending">{compact ? "" : "In "}{amountStr}{compact ? "" : " "}{unit}</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
|
||||
@@ -75,17 +75,17 @@
|
||||
|
||||
options.push({
|
||||
id: "this_year",
|
||||
label: "This Year",
|
||||
label: "End of this year",
|
||||
value: endOfYear(now),
|
||||
});
|
||||
options.push({
|
||||
id: "next_year",
|
||||
label: "Next Year",
|
||||
label: "End of next year",
|
||||
value: endOfYear(new Date(Date.now() + (365.25 * 86400000))),
|
||||
});
|
||||
options.push({
|
||||
id: "last_year",
|
||||
label: "Last Year",
|
||||
label: "End of last year",
|
||||
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);
|
||||
|
||||
if (project.endTime) {
|
||||
const start = Date.parse(project.createdTime);
|
||||
const start = Date.parse(project.startTime || project.createdTime);
|
||||
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);
|
||||
} else {
|
||||
@@ -37,7 +37,7 @@
|
||||
if (goal != null) {
|
||||
const start = Date.parse(goal.startTime);
|
||||
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;
|
||||
progressTarget = goal.amount;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="content">{project.name}</div>
|
||||
{#if project.endTime}
|
||||
<div class="times">
|
||||
<DaysLeft compact startTime={project.createdTime} endTime={project.endTime} />
|
||||
<DaysLeft compact startTime={project.startTime || project.createdTime} endTime={project.endTime} />
|
||||
</div>
|
||||
{/if}
|
||||
</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 IconSelect from "../components/IconSelect.svelte";
|
||||
import Modal from "../components/Modal.svelte";
|
||||
import StartTimeSelect from "../components/StartTimeSelect.svelte";
|
||||
import StatusTagSelect from "../components/StatusTagSelect.svelte";
|
||||
import { DEFAULT_ICON } from "../external/icons";
|
||||
import type { ProjectResult } from "../models/project";
|
||||
@@ -34,6 +35,7 @@
|
||||
throw new Error("Wrong form")
|
||||
}
|
||||
|
||||
let startTime = formatFormTime(project.startTime);
|
||||
let endTime = formatFormTime(project.endTime);
|
||||
let name = project.name;
|
||||
let description = project.description;
|
||||
@@ -52,6 +54,7 @@
|
||||
if (creation) {
|
||||
stuffLogClient.createProject({
|
||||
active: statusTag === "",
|
||||
startTime: ( startTime == "" ) ? null : new Date(startTime),
|
||||
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
||||
statusTag: statusTag !== "" ? statusTag : null,
|
||||
|
||||
@@ -77,6 +80,8 @@
|
||||
stuffLogClient.updateProject(project.id, {
|
||||
endTime: ( endTime == "" ) ? null : new Date(endTime),
|
||||
clearEndTime: ( endTime == "" ),
|
||||
startTime: ( startTime == "" ) ? null : new Date(startTime),
|
||||
clearStartTime: ( startTime == "" ),
|
||||
active: statusTag === "",
|
||||
statusTag: statusTag || null,
|
||||
clearStatusTag: statusTag === "",
|
||||
@@ -111,6 +116,10 @@
|
||||
<IconSelect disabled={deletion} bind:value={icon} />
|
||||
<label for="endTime">Deadline (Optional)</label>
|
||||
<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>
|
||||
<StatusTagSelect disabled={deletion} isProject bind:value={statusTag} />
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export default interface Project {
|
||||
active: boolean
|
||||
createdTime: string
|
||||
favorite: boolean
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
statusTag?: string
|
||||
}
|
||||
@@ -29,6 +30,7 @@ export interface ProjectInput {
|
||||
description: string
|
||||
icon: IconName
|
||||
active: boolean
|
||||
startTime?: string | Date
|
||||
endTime?: string | Date
|
||||
statusTag?: string
|
||||
favorite?: boolean
|
||||
@@ -39,6 +41,8 @@ export interface ProjectUpdate {
|
||||
description?: string
|
||||
icon?: string
|
||||
active?: boolean
|
||||
startTime?: string | Date
|
||||
clearStartTime?: boolean
|
||||
endTime?: string | Date
|
||||
clearEndTime?: boolean
|
||||
statusTag?: string
|
||||
|
||||
Reference in New Issue
Block a user