You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
5.7 KiB
177 lines
5.7 KiB
<script lang="ts">
|
|
import { getStores } from "$app/stores";
|
|
|
|
import { sl3 } from "$lib/clients/sl3";
|
|
import Modal from "$lib/components/common/Modal.svelte";
|
|
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
|
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
|
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
|
|
import PartInput from "$lib/components/controls/PartInput.svelte";
|
|
import SprintKindSelect from "$lib/components/controls/SprintKindSelect.svelte";
|
|
import TagInput from "$lib/components/controls/TagInput.svelte";
|
|
import TimeRangeInput from "$lib/components/controls/TimeRangeInput.svelte";
|
|
import Checkbox from "$lib/components/layout/Checkbox.svelte";
|
|
import type Scope from "$lib/models/scope";
|
|
import type Sprint from "$lib/models/sprint";
|
|
import { SprintKind, type SprintInput, type SprintInputPart } from "$lib/models/sprint";
|
|
import { formatFormTime } from "$lib/utils/date";
|
|
import { partsDiff } from "$lib/utils/sprint";
|
|
|
|
const {currentModal, closeModal} = getModalContext();
|
|
const {scope} = getScopeContext();
|
|
const {reloadSprintList} = getSprintListContext();
|
|
const {page} = getStores();
|
|
|
|
let sprint: SprintInput
|
|
let sprintId: number
|
|
let scopeId: number
|
|
let oldParts: SprintInputPart[]
|
|
let intervalName: string;
|
|
|
|
let openedDate: Date
|
|
let op: string
|
|
let error: string
|
|
let loading: boolean
|
|
let show: boolean
|
|
|
|
$: switch ($currentModal.name) {
|
|
case "sprint.create":
|
|
initCreate($scope)
|
|
break;
|
|
case "sprint.edit":
|
|
initEdit($currentModal.sprint)
|
|
break;
|
|
|
|
default:
|
|
loading = false;
|
|
error = null;
|
|
show = false;
|
|
}
|
|
|
|
function initCreate(scope: Scope) {
|
|
sprint = {
|
|
name: "",
|
|
description: "",
|
|
fromTime: "",
|
|
toTime: "",
|
|
kind: SprintKind.Stats,
|
|
aggregateName: "",
|
|
aggregateRequired: 0,
|
|
isCoarse: false,
|
|
isTimed: false,
|
|
isUnweighted: false,
|
|
parts: [],
|
|
tags: [],
|
|
}
|
|
intervalName = "this_month";
|
|
scopeId = scope.id;
|
|
|
|
op = "Create"
|
|
openedDate = new Date();
|
|
show = true;
|
|
}
|
|
|
|
function initEdit(current: Sprint) {
|
|
sprint = {
|
|
name: current.name,
|
|
description: current.description,
|
|
fromTime: formatFormTime(current.fromTime),
|
|
toTime: formatFormTime(current.toTime),
|
|
kind: current.kind,
|
|
aggregateName: current.aggregateName,
|
|
aggregateRequired: current.aggregateRequired,
|
|
isCoarse: current.isCoarse,
|
|
isTimed: current.isTimed,
|
|
isUnweighted: current.isUnweighted,
|
|
tags: [...(current.tags||[])],
|
|
};
|
|
sprintId = current.id;
|
|
scopeId = current.scopeId;
|
|
intervalName = "specific_dates";
|
|
|
|
if (sprint.kind === SprintKind.Stats) {
|
|
sprint.parts = current.progress.map(p => ({partId: p.id, required: p.required}));
|
|
} else {
|
|
sprint.parts = current.partIds.map(p => ({partId: p, required: 0}));
|
|
}
|
|
oldParts = [...sprint.parts];
|
|
|
|
op = "Edit"
|
|
openedDate = new Date();
|
|
show = true;
|
|
}
|
|
|
|
async function submit() {
|
|
error = null;
|
|
loading = true;
|
|
|
|
const submission: SprintInput = {
|
|
...sprint,
|
|
fromTime: new Date(sprint.fromTime).toISOString(),
|
|
toTime: new Date(sprint.toTime).toISOString(),
|
|
}
|
|
|
|
try {
|
|
switch (op) {
|
|
case "Create":
|
|
await sl3(fetch).createSprint(scopeId, submission);
|
|
break;
|
|
case "Edit":
|
|
await sl3(fetch).updateSprint(scopeId, sprintId, submission);
|
|
const {added, removed} = partsDiff(oldParts, submission.parts)
|
|
for (const part of added) {
|
|
await sl3(fetch).upsertSprintPart(scopeId, sprintId, part).catch(() => {});
|
|
}
|
|
for (const part of removed) {
|
|
await sl3(fetch).deleteSprintPart(scopeId, sprintId, part).catch(() => {});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
await reloadSprintList();
|
|
|
|
closeModal();
|
|
} catch(err) {
|
|
if (err.statusCode != null) {
|
|
error = err.statusMessage;
|
|
} else {
|
|
error = err
|
|
}
|
|
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<form on:submit|preventDefault={submit}>
|
|
<Modal wide closable show={show} verb={op} noun="sprint" disabled={loading} error={error}>
|
|
<ModalBody>
|
|
<label for="name">Name</label>
|
|
<input name="name" type="text" bind:value={sprint.name} />
|
|
<label for="description">Description</label>
|
|
<textarea name="description" bind:value={sprint.description} />
|
|
<label for="time">Time</label>
|
|
<TimeRangeInput openDate={openedDate} bind:from={sprint.fromTime} bind:to={sprint.toTime} bind:intervalName={intervalName} />
|
|
<label for="aggregateName">Aggregate Name</label>
|
|
<input name="aggregateName" type="text" bind:value={sprint.aggregateName} />
|
|
<label for="tags">Tags</label>
|
|
<TagInput exclaimMode bind:value={sprint.tags} />
|
|
{#if sprint.kind != SprintKind.Items}
|
|
<label for="aggregateValue">Aggregate Goal</label>
|
|
<input name="aggregateValue" type="number" bind:value={sprint.aggregateRequired} />
|
|
{/if}
|
|
<Checkbox bind:checked={sprint.isTimed} label="Sprint is timed" />
|
|
<Checkbox bind:checked={sprint.isCoarse} label="Show only aggregate" />
|
|
<Checkbox bind:checked={sprint.isUnweighted} label="Aggregate is unweighted" />
|
|
</ModalBody>
|
|
<ModalBody>
|
|
<label for="kind">Kind</label>
|
|
<SprintKindSelect disabled={op === "Edit"} bind:kind={sprint.kind} />
|
|
<label for="parts">Parts</label>
|
|
<PartInput bind:value={sprint.parts} kind={sprint.kind} scopeId={scopeId} />
|
|
</ModalBody>
|
|
</Modal>
|
|
</form>
|