Gisle Aune
2 years ago
15 changed files with 466 additions and 25 deletions
-
6frontend/src/lib/clients/sl3.ts
-
6frontend/src/lib/components/layout/Columns.svelte
-
24frontend/src/lib/components/project/ItemSubSection.svelte
-
5frontend/src/lib/models/common.ts
-
32frontend/src/lib/models/item.ts
-
119frontend/src/lib/utils/date.ts
-
53frontend/src/lib/utils/items.ts
-
78frontend/src/lib/utils/timeinterval.ts
-
10frontend/src/routes/[scope=prettyid]/history.svelte
-
50frontend/src/routes/[scope=prettyid]/history/[interval].svelte
-
44frontend/src/routes/[scope=prettyid]/index.svelte
-
4frontend/src/routes/api/[...any].ts
-
2models/item.go
-
34ports/httpapi/items.go
@ -0,0 +1,5 @@ |
|||
export interface TimeInterval<T> { |
|||
min: T |
|||
max: T |
|||
display?: "none" | "days" | "weeks" | "month" | "months" | "year" |
|||
} |
@ -0,0 +1,53 @@ |
|||
import type { ItemGroup, ItemGroupReference } from "$lib/models/item"; |
|||
import type Item from "$lib/models/item"; |
|||
import { formatDate, formatDateTime, formatTime, formatWeekdayDate } from "./date"; |
|||
|
|||
export function groupItems(items: Item[]): ItemGroup[] { |
|||
let groups: Record<string, ItemGroup> = {}; |
|||
|
|||
for (let i = 0; i < items.length; ++i) { |
|||
const item = items[i]; |
|||
const createKey = formatDate(item.createdTime) |
|||
const createSortKey = `${createKey} ${formatTime(item.createdTime)}` |
|||
|
|||
addItem(groups, createKey, i, createSortKey, item, "created"); |
|||
|
|||
if (item.scheduledDate) { |
|||
addItem(groups, item.scheduledDate, i, `${item.scheduledDate} 23:59:59`, item, "scheduled"); |
|||
} |
|||
|
|||
if (item.acquiredTime) { |
|||
addItem(groups, formatDate(item.acquiredTime), i, formatDateTime(item.acquiredTime), item, "acquired"); |
|||
} |
|||
} |
|||
|
|||
return Object.keys(groups).sort((a,b) => ( |
|||
b.localeCompare(a) |
|||
)).map(k => ({ |
|||
...groups[k], |
|||
list: groups[k].list.sort((a, b) => { |
|||
return b.sortKey.localeCompare(a.sortKey); |
|||
}).map(r => ({...r, sortKey: void(0)})), |
|||
})); |
|||
} |
|||
|
|||
function addItem(groups: Record<string, ItemGroup>, key: string, index: number, sortKey: string, item: Item, event: ItemGroupReference["event"]) { |
|||
if (groups[key] == null) { |
|||
groups[key] = { |
|||
label: formatWeekdayDate(sortKey), |
|||
list: [], |
|||
} |
|||
} |
|||
|
|||
const i = groups[key].list.findIndex(r => r.idx === index) |
|||
if (i == -1) { |
|||
groups[key].list.push({ |
|||
idx: index, |
|||
event: event, |
|||
sortKey: sortKey, |
|||
}) |
|||
} else { |
|||
groups[key].list[i].event = event; |
|||
groups[key].list[i].sortKey = sortKey; |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
import type { TimeInterval } from "../models/common"; |
|||
import { addDays, addMonths, addYears, formatDate, startOfDay, startOfMonth, startOfWeek, startOfYear } from "./date"; |
|||
|
|||
export default function parseInterval(s: string, date: Date): TimeInterval<Date> { |
|||
const [verb, args] = s.split(":") |
|||
|
|||
switch (verb) { |
|||
case "": |
|||
case "all_time": |
|||
return null; |
|||
case "next": { |
|||
const [amount, unit] = unitNumber(args); |
|||
switch (unit) { |
|||
case "d": case "days": |
|||
return {display: "days", min: startOfDay(date), max: startOfDay(addDays(date, amount))} |
|||
case "w": case "weeks": |
|||
return {display: "days", min: startOfDay(date), max: startOfDay(addDays(date, amount * 7))} |
|||
case "m": case "months": |
|||
return {display: "days", min: startOfDay(date), max: startOfDay(addMonths(date, amount))} |
|||
case "y": case "years": |
|||
return {display: "none", min: startOfDay(date), max: startOfDay(addYears(date, amount))} |
|||
} |
|||
} |
|||
case "last": { |
|||
const [amount, unit] = unitNumber(args); |
|||
switch (unit) { |
|||
case "d": case "days": |
|||
return {display: "days", min: startOfDay(addDays(date, -(amount - 1))), max: startOfDay(addDays(date, 1))} |
|||
case "w": case "weeks": |
|||
return {display: "weeks", min: startOfDay(addDays(date, -amount * 7)), max: startOfDay(addDays(date, 1))} |
|||
case "m": case "months": |
|||
return {display: "days", min: startOfDay(addMonths(date, -amount - 1)), max: startOfDay(addDays(date, 1))} |
|||
case "y": case "years": |
|||
return {display: "none", min: startOfDay(addYears(date, -amount)), max: startOfDay(addDays(date, 1))} |
|||
} |
|||
} |
|||
case "today": |
|||
return {display: "none", min: startOfDay(date), max: startOfDay(addDays(date, 1))} |
|||
case "next_week": |
|||
return {display: "weeks", min: startOfWeek(addDays(date, 7)), max: startOfWeek(addDays(date, 14))} |
|||
case "this_week": |
|||
return {display: "weeks", min: startOfWeek(date), max: startOfWeek(addDays(date, 7))} |
|||
case "last_week": |
|||
return {display: "weeks", min: startOfWeek(addDays(date, -7)), max: startOfWeek(date)} |
|||
case "next_month": |
|||
return {display: "month", min: startOfMonth(addMonths(date, 1)), max: startOfMonth(addMonths(date, 2))} |
|||
case "this_month": |
|||
return {display: "month", min: startOfMonth(date), max: startOfMonth(addMonths(date, 1))} |
|||
case "last_month": |
|||
return {display: "month", min: startOfMonth(addMonths(date, -1)), max: startOfMonth(date)} |
|||
case "next_year": |
|||
return {display: "year", min: startOfYear(addYears(date, 1)), max: startOfYear(addYears(date, 2))} |
|||
case "this_year": |
|||
return {display: "year", min: startOfYear(date), max: startOfYear(addYears(date, 1))} |
|||
case "last_year": |
|||
return {display: "year", min: startOfYear(addYears(date, -1)), max: startOfYear(date)} |
|||
} |
|||
} |
|||
|
|||
export function datesOf(interval: TimeInterval<string | Date>): TimeInterval<string> { |
|||
if (interval == null) { |
|||
return void(0) |
|||
} |
|||
|
|||
return {min: formatDate(interval.min), max: formatDate(interval.max)} |
|||
} |
|||
|
|||
function unitNumber(s: string): [number, string] { |
|||
for (let i = 0; i < s.length; ++i) { |
|||
const ch = s.charAt(i); |
|||
|
|||
if (ch < '0' || ch > '9') { |
|||
return [parseInt(s.slice(0, i)), s.slice(i)] |
|||
} |
|||
} |
|||
|
|||
return [parseInt(s), ""]; |
|||
} |
@ -0,0 +1,10 @@ |
|||
<script lang="ts" context="module"> |
|||
import type { Load } from "@sveltejs/kit/types/internal"; |
|||
|
|||
export const load: Load = async({}) => { |
|||
return { |
|||
status: 303, |
|||
redirect: "history/this_week", |
|||
}; |
|||
} |
|||
</script> |
@ -0,0 +1,50 @@ |
|||
<script lang="ts" context="module"> |
|||
import type { Load } from "@sveltejs/kit/types/internal"; |
|||
|
|||
import { sl3 } from "$lib/clients/sl3"; |
|||
import parseInterval, { datesOf } from "$lib/utils/timeinterval"; |
|||
import { groupItems } from "$lib/utils/items"; |
|||
import type Item from "$lib/models/item"; |
|||
import type { ItemGroup } from "$lib/models/item"; |
|||
|
|||
export const load: Load = async({ fetch, params }) => { |
|||
const scopeId = parseInt(params.scope.split("-")[0]); |
|||
|
|||
const interval = parseInterval(params.interval, new Date()); |
|||
|
|||
const items = await sl3(fetch).listItems(scopeId, { |
|||
createdTime: interval, |
|||
acquiredTime: interval, |
|||
scheduledDate: datesOf(interval), |
|||
}); |
|||
|
|||
return { |
|||
props: { items, groups: groupItems(items), }, |
|||
}; |
|||
} |
|||
</script> |
|||
|
|||
<script lang="ts"> |
|||
import ItemSubSection from "$lib/components/project/ItemSubSection.svelte" |
|||
|
|||
export let items: Item[] |
|||
export let groups: ItemGroup[] |
|||
</script> |
|||
|
|||
<h1>History</h1> |
|||
|
|||
{#each groups as group (group.label)} |
|||
<h2>{group.label}</h2> |
|||
{#each group.list as ref (ref.idx)} |
|||
<ItemSubSection item={items[ref.idx]} event={ref.event} /> |
|||
{/each} |
|||
{/each} |
|||
|
|||
<style lang="sass"> |
|||
h1 |
|||
margin-top: 1.5em |
|||
|
|||
h2 |
|||
text-align: center |
|||
font-weight: 100 |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue