This commit is contained in:
@@ -51,7 +51,7 @@ func (r *taskRepository) List(ctx context.Context, filter models.TaskFilter) ([]
|
||||
}
|
||||
|
||||
sq = sq.InnerJoin("project AS p ON task.project_id = p.project_id")
|
||||
sq = sq.OrderBy("created_time")
|
||||
sq = sq.OrderBy("active DESC", "created_time")
|
||||
|
||||
query, args, err := sq.ToSql()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import LogsPage from "./pages/LogsPage.svelte";
|
||||
import GroupPage from "./pages/GroupPage.svelte";
|
||||
import GoalPage from "./pages/GoalPage.svelte";
|
||||
import QlPage from "./pages/QLPage.svelte";
|
||||
|
||||
import GroupForm from "./forms/GroupForm.svelte";
|
||||
import GoalForm from "./forms/GoalForm.svelte";
|
||||
@@ -36,6 +37,7 @@
|
||||
<Route path="/" component={FrontPage} />
|
||||
<Route path="/goals/" component={GoalPage} />
|
||||
<Route path="/projects/" component={ProjectPage} />
|
||||
<Route path="/questlog/" component={QlPage} />
|
||||
<Route path="/logs/" component={LogsPage} />
|
||||
<Route path="/items/" component={GroupPage} />
|
||||
</main>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { link } from "svelte-routing";
|
||||
import selectionStore from "../stores/selection";
|
||||
|
||||
export let location: string = window.location.pathname.split("?")[0];
|
||||
|
||||
@@ -9,19 +10,23 @@
|
||||
}, 0);
|
||||
}
|
||||
|
||||
$: console.log($selectionStore);
|
||||
|
||||
$: selected = {
|
||||
home: location == "/",
|
||||
goals: location.startsWith("/goals"),
|
||||
projects: location.startsWith("/projects"),
|
||||
items: location.startsWith("/items"),
|
||||
logs: location.startsWith("/logs"),
|
||||
home: $selectionStore.path === "/",
|
||||
goals: $selectionStore.path.startsWith("/goals"),
|
||||
questlog: $selectionStore.path.startsWith("/questlog"),
|
||||
projects: $selectionStore.path.startsWith("/projects"),
|
||||
items: $selectionStore.path.startsWith("/items"),
|
||||
logs: $selectionStore.path.startsWith("/logs"),
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<a on:click={updateLocation} class:selected={selected.home} use:link href="/">Stufflog</a>
|
||||
<a on:click={updateLocation} class:selected={selected.goals} use:link href="/goals">Goals</a>
|
||||
<a on:click={updateLocation} class:selected={selected.projects} use:link href="/projects">Projects</a>
|
||||
<a class="desktop" on:click={updateLocation} class:selected={selected.questlog} use:link href="/questlog">Projects</a>
|
||||
<a class="mobile" on:click={updateLocation} class:selected={selected.projects} use:link href="/projects">Projects</a>
|
||||
<a on:click={updateLocation} class:selected={selected.items} use:link href="/items">Items</a>
|
||||
<a on:click={updateLocation} class:selected={selected.logs} use:link href="/logs">Logs</a>
|
||||
</nav>
|
||||
@@ -41,4 +46,17 @@
|
||||
a.selected {
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
a.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
a.mobile {
|
||||
display: inline-block;
|
||||
}
|
||||
a.desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { IconName } from "../external/icons";
|
||||
import type { TaskResult } from "../models/task";
|
||||
import DaysLeft from "./DaysLeft.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import LinkHook from "./LinkHook.svelte";
|
||||
import Markdown from "./Markdown.svelte";
|
||||
import Progress from "./Progress.svelte";
|
||||
import ProjectProgress from "./ProjectProgress.svelte";
|
||||
|
||||
interface EntryIconHolder {
|
||||
icon: IconName
|
||||
@@ -20,14 +21,15 @@
|
||||
createdTime?: string
|
||||
group?: EntryIconHolder
|
||||
project?: EntryIconHolder
|
||||
tasks?: TaskResult[]
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
export let entry: EntryCommon;
|
||||
export let full = false;
|
||||
export let headerLink = "";
|
||||
|
||||
export let progressAmount: number = null;
|
||||
export let progressTarget: number = null;
|
||||
export let hideProgress: boolean = false;
|
||||
export let hideIcon: boolean = false;
|
||||
|
||||
let iconName: IconName;
|
||||
|
||||
@@ -44,7 +46,11 @@
|
||||
|
||||
<div class="parent-entry" class:full={full}>
|
||||
<LinkHook id={entry.id} />
|
||||
<div class="icon"><Icon block name={iconName} /></div>
|
||||
{#if !hideIcon}
|
||||
<div class="icon" class:completed={entry.active === false}>
|
||||
<Icon block name={iconName} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="body">
|
||||
<div class="header">
|
||||
<div class="name">
|
||||
@@ -60,8 +66,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if (progressAmount != null)}
|
||||
<Progress thin green count={progressAmount} target={progressTarget} />
|
||||
{#if (!hideProgress && entry.tasks != null)}
|
||||
<ProjectProgress project={entry} />
|
||||
{/if}
|
||||
{#if (full)}
|
||||
<Markdown source={entry.description} />
|
||||
@@ -86,6 +92,9 @@
|
||||
padding-top: 0.125em;
|
||||
color: #333;
|
||||
}
|
||||
div.icon.completed {
|
||||
color: #484;
|
||||
}
|
||||
div.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import type { TaskResult } from "../models/task";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import projectStore from "../stores/project";
|
||||
import taskStore from "../stores/tasks";
|
||||
import Option from "./Option.svelte";
|
||||
import OptionRow from "./OptionRow.svelte";
|
||||
import ParentEntry from "./ParentEntry.svelte";
|
||||
@@ -9,30 +12,25 @@
|
||||
export let project: ProjectResult = null;
|
||||
export let showAllOptions: boolean = false;
|
||||
export let hideInactive: boolean = false;
|
||||
export let hideProgress: boolean = false;
|
||||
export let linkProject: boolean = false;
|
||||
export let hideIcon: boolean = false;
|
||||
|
||||
let mdAddTask: ModalData;
|
||||
let mdProjectEdit: ModalData;
|
||||
let mdProjectDelete: ModalData;
|
||||
let progressAmount: number;
|
||||
let progressTarget: number;
|
||||
|
||||
$: mdAddTask = {name:"task.add", project};
|
||||
$: mdProjectEdit = {name:"project.edit", project};
|
||||
$: mdProjectDelete = {name:"project.delete", project};
|
||||
$: progressAmount = project.tasks.map(t => t.active
|
||||
? Math.min(t.completedAmount, t.itemAmount) * t.item.groupWeight
|
||||
: t.itemAmount * t.item.groupWeight
|
||||
).reduce((n,m) => n+m, 0);
|
||||
$: progressTarget = Math.max(project.tasks.map(t => t.itemAmount * t.item.groupWeight).reduce((n,m) => n+m, 0), 1);
|
||||
</script>
|
||||
|
||||
<ParentEntry
|
||||
full={showAllOptions}
|
||||
entry={project}
|
||||
headerLink={linkProject ? "/projects#"+project.id : ""}
|
||||
progressAmount={progressAmount}
|
||||
progressTarget={progressTarget}
|
||||
hideProgress={hideProgress}
|
||||
hideIcon={hideIcon}
|
||||
>
|
||||
{#if showAllOptions}
|
||||
<OptionRow>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { TaskResult } from "../models/task";
|
||||
import Progress from "./Progress.svelte";
|
||||
|
||||
interface ProjectLike {
|
||||
tasks?: TaskResult[]
|
||||
}
|
||||
|
||||
export let project: ProjectLike;
|
||||
|
||||
let progressAmount: number;
|
||||
let progressTarget: number;
|
||||
|
||||
$: progressAmount = (project.tasks||[]).map(t => t.active
|
||||
? Math.min(t.completedAmount, t.itemAmount) * t.item.groupWeight
|
||||
: t.itemAmount * t.item.groupWeight
|
||||
).reduce((n,m) => n+m, 0);
|
||||
$: progressTarget = Math.max((project.tasks||[]).map(t => t.itemAmount * t.item.groupWeight).reduce((n,m) => n+m, 0), 1);
|
||||
</script>
|
||||
|
||||
<Progress thin green count={progressAmount} target={progressTarget} />
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import QlListItem from "./QLListItem.svelte";
|
||||
|
||||
export let projects: ProjectResult[];
|
||||
export let label: string = "";
|
||||
</script>
|
||||
|
||||
{#if projects.length > 0}
|
||||
<div class="ql-list">
|
||||
<h2>{label}</h2>
|
||||
{#each projects as project (project.id)}
|
||||
<QlListItem project={project} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
div.ql-list {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 100;
|
||||
margin: 0;
|
||||
padding-bottom: 0.125em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import selectionStore from "../stores/selection";
|
||||
import Icon from "./Icon.svelte";
|
||||
import ProjectProgress from "./ProjectProgress.svelte";
|
||||
|
||||
export let project: ProjectResult;
|
||||
|
||||
let selected: boolean;
|
||||
let completed: boolean;
|
||||
|
||||
function handleClick() {
|
||||
window.location.hash = "#" + project.id;
|
||||
selectionStore.change("hash", project.id);
|
||||
}
|
||||
|
||||
$: selected = $selectionStore.hash === project.id;
|
||||
$: completed = !project.active;
|
||||
</script>
|
||||
|
||||
<div class="ql-list-item" on:click={handleClick} class:selected>
|
||||
<div class="icon" class:completed>
|
||||
<Icon block name={project.icon} />
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="name">{project.name}</div>
|
||||
<ProjectProgress project={project} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div.ql-list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
||||
color: #777;
|
||||
padding: 0.2em;
|
||||
padding-bottom: 0.05em;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
div.ql-list-item:hover {
|
||||
color: #ccc;
|
||||
background-color: #191919;
|
||||
}
|
||||
|
||||
div.ql-list-item.selected {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
div.icon {
|
||||
color: #444;
|
||||
|
||||
padding: 0.3em 0.5ch;
|
||||
padding-right: 1ch;
|
||||
}
|
||||
div.ql-list-item.selected div.icon {
|
||||
color: #666;
|
||||
}
|
||||
div.icon.completed {
|
||||
color: #484;
|
||||
}
|
||||
div.ql-list-item.selected div.icon.completed {
|
||||
color: #78ff78;
|
||||
}
|
||||
div.ql-list-item:hover div.icon {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
div.header {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
padding-right: 0.5ch;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import selectionStore from "../stores/selection";
|
||||
import ProjectEntry from "./ProjectEntry.svelte";
|
||||
import QlList from "./QLList.svelte";
|
||||
|
||||
export let projects: ProjectResult[];
|
||||
|
||||
let expiringProjects: ProjectResult[];
|
||||
let activeProjects: ProjectResult[];
|
||||
let completedProjects: ProjectResult[];
|
||||
let project: ProjectResult = null;
|
||||
|
||||
$: project = $selectionStore.hash.startsWith("P") ? projects.find(p => p.id === $selectionStore.hash) : null;
|
||||
$: expiringProjects = projects.filter(p => p.active && p.endTime).sort((a,b) => Date.parse(a.endTime) - Date.parse(b.endTime));
|
||||
$: activeProjects = projects.filter(p => p.active && !p.endTime).sort((a,b) => a.name.localeCompare(b.name));
|
||||
$: completedProjects = projects.filter(p => !p.active).sort((a,b) => a.name.localeCompare(b.name));
|
||||
|
||||
$: {
|
||||
if (project === null && projects.length > 0) {
|
||||
project = expiringProjects[0] || activeProjects[0] || completedProjects[0] || null;
|
||||
if (project !== null) {
|
||||
selectionStore.change("hash", project.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="quest-log">
|
||||
<div class="list">
|
||||
<QlList label="Deadlines" projects={expiringProjects} />
|
||||
<QlList label="Acitve" projects={activeProjects} />
|
||||
<QlList label="Completed" projects={completedProjects} />
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if project != null}
|
||||
<ProjectEntry hideIcon project={project} showAllOptions />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div.quest-log {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div.list {
|
||||
flex-shrink: 0;
|
||||
width: 32ch;
|
||||
}
|
||||
|
||||
div.body {
|
||||
flex-grow: 1;
|
||||
margin: 1em 1ch;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import selectionStore from "../stores/selection";
|
||||
|
||||
onMount(() => {
|
||||
selectionStore.refresh();
|
||||
})
|
||||
</script>
|
||||
@@ -2,10 +2,11 @@
|
||||
import EmptyList from "../components/EmptyList.svelte";
|
||||
import GoalEntry from "../components/GoalEntry.svelte";
|
||||
import ProjectEntry from "../components/ProjectEntry.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
import type { ProjectResult } from "../models/project";
|
||||
import { fpGoalStore } from "../stores/goal";
|
||||
import projectStore, { fpProjectStore } from "../stores/project";
|
||||
import taskStore, { fpTaskStore } from "../stores/tasks";
|
||||
import { fpProjectStore } from "../stores/project";
|
||||
import { fpTaskStore } from "../stores/tasks";
|
||||
|
||||
let fakeProject: ProjectResult
|
||||
let sortedProjects: ProjectResult[]
|
||||
@@ -85,6 +86,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import GoalEntry from "../components/GoalEntry.svelte";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import goalStore from "../stores/goal";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
|
||||
const mdGoalAdd: ModalData = {name: "goal.add"};
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
{/each}
|
||||
<Boi open={mdGoalAdd}>Add Goal</Boi>
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import GroupEntry from "../components/GroupEntry.svelte";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import groupStore from "../stores/group";
|
||||
import TableOfContent from "../components/TableOfContent.svelte";
|
||||
import TableOfContent from "../components/TableOfContent.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
|
||||
const mdGroupAdd: ModalData = {name: "group.add"};
|
||||
|
||||
@@ -21,6 +22,7 @@ import TableOfContent from "../components/TableOfContent.svelte";
|
||||
{/each}
|
||||
<Boi open={mdGroupAdd}>Add Group</Boi>
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { formatDate, formatTime, formatWeekdayDate } from "../utils/time";
|
||||
import Boi from "../components/Boi.svelte";
|
||||
import EmptyList from "../components/EmptyList.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
|
||||
let groupedLogs: {day: number, text: string, logs: LogResult[]}[] = [];
|
||||
let minTime = $logStore.filter.minTime || new Date(Date.now() - (86400000*30));
|
||||
@@ -95,6 +96,7 @@
|
||||
<Boi disabled>Loading...</Boi>
|
||||
{/if}
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Boi from "../components/Boi.svelte";
|
||||
import Checkbox from "../components/Checkbox.svelte";
|
||||
import ProjectEntry from "../components/ProjectEntry.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
import TableOfContent from "../components/TableOfContent.svelte";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import projectStore from "../stores/project";
|
||||
@@ -37,6 +38,7 @@
|
||||
{/each}
|
||||
<Boi open={mdProjectAdd}>Add Project</Boi>
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import Boi from "../components/Boi.svelte";
|
||||
import QuestLog from "../components/QuestLog.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
import type { ModalData } from "../stores/modal";
|
||||
import projectStore from "../stores/project";
|
||||
|
||||
const mdProjectAdd: ModalData = {name: "project.add"};
|
||||
|
||||
$: {
|
||||
if (($projectStore.stale || $projectStore.filter.active != null) && !$projectStore.loading) {
|
||||
projectStore.load({});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
<QuestLog projects={$projectStore.projects} />
|
||||
<Boi open={mdProjectAdd}>Add Project</Boi>
|
||||
</div>
|
||||
<RefreshSelection />
|
||||
|
||||
|
||||
<style>
|
||||
div.page {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
width: 1600px;
|
||||
margin-top: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
interface SelectionData {
|
||||
path: string,
|
||||
hash: string,
|
||||
}
|
||||
|
||||
function createSelectionStore() {
|
||||
const {update, set, subscribe} = writable<SelectionData>({
|
||||
path: window.location.pathname,
|
||||
hash: window.location.hash.slice(1),
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
refresh() {
|
||||
set({
|
||||
path: window.location.pathname,
|
||||
hash: window.location.hash.slice(1),
|
||||
});
|
||||
},
|
||||
|
||||
change(key: keyof(SelectionData), value: string) {
|
||||
update(d => ({...d, [key]: value}));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const selectionStore = createSelectionStore();
|
||||
|
||||
export default selectionStore;
|
||||
Reference in New Issue
Block a user