Loggest thine Stuff
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.

176 lines
5.7 KiB

  1. <script lang="ts">
  2. import { getStores } from "$app/stores";
  3. import { sl3 } from "$lib/clients/sl3";
  4. import Modal from "$lib/components/common/Modal.svelte";
  5. import ModalBody from "$lib/components/common/ModalBody.svelte";
  6. import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
  7. import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
  8. import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
  9. import PartInput from "$lib/components/controls/PartInput.svelte";
  10. import SprintKindSelect from "$lib/components/controls/SprintKindSelect.svelte";
  11. import TagInput from "$lib/components/controls/TagInput.svelte";
  12. import TimeRangeInput from "$lib/components/controls/TimeRangeInput.svelte";
  13. import Checkbox from "$lib/components/layout/Checkbox.svelte";
  14. import type Scope from "$lib/models/scope";
  15. import type Sprint from "$lib/models/sprint";
  16. import { SprintKind, type SprintInput, type SprintInputPart } from "$lib/models/sprint";
  17. import { formatFormTime } from "$lib/utils/date";
  18. import { partsDiff } from "$lib/utils/sprint";
  19. const {currentModal, closeModal} = getModalContext();
  20. const {scope} = getScopeContext();
  21. const {reloadSprintList} = getSprintListContext();
  22. const {page} = getStores();
  23. let sprint: SprintInput
  24. let sprintId: number
  25. let scopeId: number
  26. let oldParts: SprintInputPart[]
  27. let intervalName: string;
  28. let openedDate: Date
  29. let op: string
  30. let error: string
  31. let loading: boolean
  32. let show: boolean
  33. $: switch ($currentModal.name) {
  34. case "sprint.create":
  35. initCreate($scope)
  36. break;
  37. case "sprint.edit":
  38. initEdit($currentModal.sprint)
  39. break;
  40. default:
  41. loading = false;
  42. error = null;
  43. show = false;
  44. }
  45. function initCreate(scope: Scope) {
  46. sprint = {
  47. name: "",
  48. description: "",
  49. fromTime: "",
  50. toTime: "",
  51. kind: SprintKind.Stats,
  52. aggregateName: "",
  53. aggregateRequired: 0,
  54. isCoarse: false,
  55. isTimed: false,
  56. isUnweighted: false,
  57. parts: [],
  58. tags: [],
  59. }
  60. intervalName = "this_month";
  61. scopeId = scope.id;
  62. op = "Create"
  63. openedDate = new Date();
  64. show = true;
  65. }
  66. function initEdit(current: Sprint) {
  67. sprint = {
  68. name: current.name,
  69. description: current.description,
  70. fromTime: formatFormTime(current.fromTime),
  71. toTime: formatFormTime(current.toTime),
  72. kind: current.kind,
  73. aggregateName: current.aggregateName,
  74. aggregateRequired: current.aggregateRequired,
  75. isCoarse: current.isCoarse,
  76. isTimed: current.isTimed,
  77. isUnweighted: current.isUnweighted,
  78. tags: [...(current.tags||[])],
  79. };
  80. sprintId = current.id;
  81. scopeId = current.scopeId;
  82. intervalName = "specific_dates";
  83. if (sprint.kind === SprintKind.Stats) {
  84. sprint.parts = current.progress.map(p => ({partId: p.id, required: p.required}));
  85. } else {
  86. sprint.parts = current.partIds.map(p => ({partId: p, required: 0}));
  87. }
  88. oldParts = [...sprint.parts];
  89. op = "Edit"
  90. openedDate = new Date();
  91. show = true;
  92. }
  93. async function submit() {
  94. error = null;
  95. loading = true;
  96. const submission: SprintInput = {
  97. ...sprint,
  98. fromTime: new Date(sprint.fromTime).toISOString(),
  99. toTime: new Date(sprint.toTime).toISOString(),
  100. }
  101. try {
  102. switch (op) {
  103. case "Create":
  104. await sl3(fetch).createSprint(scopeId, submission);
  105. break;
  106. case "Edit":
  107. await sl3(fetch).updateSprint(scopeId, sprintId, submission);
  108. const {added, removed} = partsDiff(oldParts, submission.parts)
  109. for (const part of added) {
  110. await sl3(fetch).upsertSprintPart(scopeId, sprintId, part).catch(() => {});
  111. }
  112. for (const part of removed) {
  113. await sl3(fetch).deleteSprintPart(scopeId, sprintId, part).catch(() => {});
  114. }
  115. break;
  116. }
  117. await reloadSprintList();
  118. closeModal();
  119. } catch(err) {
  120. if (err.statusCode != null) {
  121. error = err.statusMessage;
  122. } else {
  123. error = err
  124. }
  125. } finally {
  126. loading = false;
  127. }
  128. }
  129. </script>
  130. <form on:submit|preventDefault={submit}>
  131. <Modal wide closable show={show} verb={op} noun="sprint" disabled={loading} error={error}>
  132. <ModalBody>
  133. <label for="name">Name</label>
  134. <input name="name" type="text" bind:value={sprint.name} />
  135. <label for="description">Description</label>
  136. <textarea name="description" bind:value={sprint.description} />
  137. <label for="time">Time</label>
  138. <TimeRangeInput openDate={openedDate} bind:from={sprint.fromTime} bind:to={sprint.toTime} bind:intervalName={intervalName} />
  139. <label for="aggregateName">Aggregate Name</label>
  140. <input name="aggregateName" type="text" bind:value={sprint.aggregateName} />
  141. <label for="tags">Tags</label>
  142. <TagInput exclaimMode bind:value={sprint.tags} />
  143. {#if sprint.kind != SprintKind.Items}
  144. <label for="aggregateValue">Aggregate Goal</label>
  145. <input name="aggregateValue" type="number" bind:value={sprint.aggregateRequired} />
  146. {/if}
  147. <Checkbox bind:checked={sprint.isTimed} label="Sprint is timed" />
  148. <Checkbox bind:checked={sprint.isCoarse} label="Show only aggregate" />
  149. <Checkbox bind:checked={sprint.isUnweighted} label="Aggregate is unweighted" />
  150. </ModalBody>
  151. <ModalBody>
  152. <label for="kind">Kind</label>
  153. <SprintKindSelect disabled={op === "Edit"} bind:kind={sprint.kind} />
  154. <label for="parts">Parts</label>
  155. <PartInput bind:value={sprint.parts} kind={sprint.kind} scopeId={scopeId} />
  156. </ModalBody>
  157. </Modal>
  158. </form>