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.

233 lines
6.4 KiB

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