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.

236 lines
6.7 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. <script lang="ts">
  2. import LogEntry from "../components/LogEntry.svelte";
  3. import type { LogFilter, LogResult } from "../models/log";
  4. import logStore from "../stores/logs";
  5. import { addDays, formatWeekdayDate, startOfDay } from "../utils/time";
  6. import { allOffsets, RelativeDateRange } from "../utils/date-range";
  7. import EmptyList from "../components/EmptyList.svelte";
  8. import RefreshSelection from "../components/RefreshSelection.svelte";
  9. import DateRangeSelect from "../components/DateRangeSelect.svelte";
  10. import EveryMinute from "../components/EveryMinute.svelte";
  11. import ProjectGroupSelect from "../components/ProjectGroupSelect.svelte";
  12. import ProjectSelect from "../components/ProjectSelect.svelte";
  13. import ItemSelect from "../components/ItemSelect.svelte";
  14. import projectStore from "../stores/project";
  15. import projectGroupStore from "../stores/projectGroup";
  16. import ItemProgress from "../components/ItemProgress.svelte";
  17. const lsValue = localStorage.getItem("sl2.logspage.range")
  18. let groupedLogs: {day: Date, text: string, logs: LogResult[]}[] = [];
  19. let range = allOffsets.find(o => o.name === lsValue) || new RelativeDateRange(-7, "day");
  20. let now = new Date();
  21. let error = "";
  22. let emptyMessage = "Loading...";
  23. let min: Date;
  24. let max: Date;
  25. let projectGroupId = localStorage.getItem("sl2.logspage.project_group_id") || "";
  26. let projectId = localStorage.getItem("sl2.logspage.project_id") || "";
  27. let itemId = localStorage.getItem("sl2.logspage.item_id") || "";
  28. $: [min, max] = range.calculate(now);
  29. $: localStorage.setItem("sl2.logspage.range", range.name);
  30. $: localStorage.setItem("sl2.logspage.project_group_id", projectGroupId);
  31. $: localStorage.setItem("sl2.logspage.project_id", projectId);
  32. $: localStorage.setItem("sl2.logspage.item_id", itemId);
  33. $: {
  34. if ($projectStore.stale && !$projectStore.loading) {
  35. projectStore.load({});
  36. }
  37. }
  38. $: {
  39. if ($projectGroupStore.stale && !$projectGroupStore.loading) {
  40. projectGroupStore.load();
  41. }
  42. }
  43. $: {
  44. error = "";
  45. if (!Number.isNaN(min.getTime()) && !Number.isNaN(max.getTime())) {
  46. if (min.getTime() < max.getTime()) {
  47. if (!$logStore.loading) {
  48. const filter: LogFilter = {
  49. minTime: min,
  50. maxTime: max,
  51. itemIds: itemId ? itemId.split(",") : void(0),
  52. projectIds: projectId ? [projectId] : void(0),
  53. projectGroupIds: (!projectId && projectGroupId) ? [projectGroupId] : void(0),
  54. }
  55. if ($logStore.stale || JSON.stringify(filter) !== JSON.stringify($logStore.filter)) {
  56. logStore.load(filter);
  57. }
  58. }
  59. } else {
  60. error = "Time only goes one way in this universe, you should respect that."
  61. }
  62. } else {
  63. error = "The dates must be valid."
  64. }
  65. }
  66. $: {
  67. if (!$logStore.loading && !$logStore.stale) {
  68. emptyMessage = `No matching logs between ${formatWeekdayDate(min)} and ${formatWeekdayDate(max)}.`;
  69. } else {
  70. emptyMessage = "Loading...";
  71. }
  72. }
  73. $: {
  74. if (!$logStore.loading) {
  75. groupedLogs = [];
  76. if ($logStore.logs.length > 0) {
  77. const today = new Date();
  78. const tomorrow = addDays(today, 1);
  79. const yesterday = addDays(today, -1);
  80. let current = new Date($logStore.logs[0].loggedTime);
  81. let remainingLogs = $logStore.logs;
  82. while (remainingLogs.length > 0) {
  83. const currentDay = startOfDay(current);
  84. const currentLogs: LogResult[] = [];
  85. for (const log of remainingLogs) {
  86. if (Date.parse(log.loggedTime) >= currentDay.getTime()) {
  87. currentLogs.push(log);
  88. } else {
  89. break;
  90. }
  91. }
  92. if (currentLogs.length > 0) {
  93. remainingLogs = remainingLogs.slice(currentLogs.length);
  94. let text = formatWeekdayDate(current);
  95. if (current.getTime() === tomorrow.getTime()) {
  96. text = "Tomorrow";
  97. } else if (current.getTime() === today.getTime()) {
  98. text = "Today";
  99. } else if (current.getTime() === yesterday.getTime()) {
  100. text = "Yesterday";
  101. }
  102. groupedLogs.push({day: current, text, logs: currentLogs});
  103. }
  104. current = addDays(current, -1);
  105. }
  106. }
  107. }
  108. }
  109. </script>
  110. <div class="page">
  111. <DateRangeSelect label="Time Filter" noFuture styled bind:value={range} />
  112. <div class="extra-filters">
  113. <div class="filter-group">
  114. <label for="projectGroup">Project Group</label>
  115. <ProjectGroupSelect name="projectGroup" optional bind:value={projectGroupId} placeholder="Any" />
  116. </div>
  117. <div class="filter-group">
  118. <label for="project">Project</label>
  119. <ProjectSelect name="project" disabled={projectGroupId === ""} optional bind:value={projectId} groupId={projectGroupId} forceGroup placeholder="Any" />
  120. </div>
  121. <div class="filter-group">
  122. <label for="item">Item</label>
  123. <ItemSelect name="item" optional bind:value={itemId} enableWholeGroup placeholder="Any" />
  124. </div>
  125. </div>
  126. <div class="error">{error}</div>
  127. <ItemProgress logs={$logStore.logs} centered />
  128. <div class="log-list" class:loading={$logStore.loading}>
  129. {#each groupedLogs as logGroup (logGroup.day.getTime())}
  130. <h2>{logGroup.text}</h2>
  131. <ItemProgress logs={logGroup.logs} centered />
  132. {#each logGroup.logs as log (log.id)}
  133. <LogEntry log={log} />
  134. {/each}
  135. {/each}
  136. {#if groupedLogs.length === 0}
  137. <EmptyList icon="list" text={emptyMessage} />
  138. {/if}
  139. </div>
  140. </div>
  141. <RefreshSelection />
  142. <EveryMinute bind:now={now} />
  143. <style>
  144. div.error {
  145. color: #ff4545;
  146. text-align: center;
  147. }
  148. div.error:empty {
  149. display: none;
  150. }
  151. div.page {
  152. display: block;
  153. margin: auto;
  154. max-width: 100%;
  155. width: 640px;
  156. margin-top: 0;
  157. box-sizing: border-box;
  158. }
  159. div.log-list.loading {
  160. opacity: 0.75;
  161. }
  162. div.extra-filters {
  163. display: flex;
  164. flex-direction: row;
  165. }
  166. div.filter-group {
  167. flex: 1;
  168. padding: 0 0.5em;
  169. margin: 0 auto;
  170. }
  171. div.filter-group label {
  172. line-height: 1.5em;
  173. }
  174. div.filter-group :global(select) {
  175. width: 100%;
  176. margin-bottom: 0.5em;
  177. background: #222;
  178. color: #777;
  179. border: none;
  180. outline: none;
  181. resize: vertical;
  182. }
  183. div.filter-group :global(select:focus) {
  184. background: #191919;
  185. color: #CCC;
  186. border: none;
  187. outline: none;
  188. }
  189. h2 {
  190. font-size: 1.5em;
  191. font-weight: 100;
  192. text-align: center;
  193. margin: 0;
  194. margin-top: 1em;
  195. }
  196. @media screen and (max-width: 900px) {
  197. div.extra-filters {
  198. flex-direction: column;
  199. }
  200. div.filter-group {
  201. width: 90%;
  202. }
  203. }
  204. </style>