Compare commits
3 Commits
09240e8f89
...
6195e2875e
| Author | SHA1 | Date | |
|---|---|---|---|
| 6195e2875e | |||
| 07febd7dc7 | |||
| 1a24165975 |
@@ -12,6 +12,7 @@ const MatrixView = lazy(() => import('./views/MatrixView').then(m => ({ default:
|
|||||||
const SettingsView = lazy(() => import('./views/SettingsView').then(m => ({ default: m.SettingsView })));
|
const SettingsView = lazy(() => import('./views/SettingsView').then(m => ({ default: m.SettingsView })));
|
||||||
const SnowballView = lazy(() => import('./views/SnowballView').then(m => ({ default: m.SnowballView })));
|
const SnowballView = lazy(() => import('./views/SnowballView').then(m => ({ default: m.SnowballView })));
|
||||||
const DigInView = lazy(() => import('./views/DigInView').then(m => ({ default: m.DigInView })));
|
const DigInView = lazy(() => import('./views/DigInView').then(m => ({ default: m.DigInView })));
|
||||||
|
const ProgressView = lazy(() => import('./views/ProgressView').then(m => ({ default: m.ProgressView })));
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
// Basic routing state
|
// Basic routing state
|
||||||
@@ -38,6 +39,7 @@ const App: Component = () => {
|
|||||||
import('./views/SettingsView');
|
import('./views/SettingsView');
|
||||||
import('./views/SnowballView');
|
import('./views/SnowballView');
|
||||||
import('./views/DigInView');
|
import('./views/DigInView');
|
||||||
|
import('./views/ProgressView');
|
||||||
|
|
||||||
// Remove splash screen after high priority work is done
|
// Remove splash screen after high priority work is done
|
||||||
const splash = document.getElementById('splash');
|
const splash = document.getElementById('splash');
|
||||||
@@ -58,6 +60,7 @@ const App: Component = () => {
|
|||||||
<Show when={currentView() === "matrix"}><MatrixView /></Show>
|
<Show when={currentView() === "matrix"}><MatrixView /></Show>
|
||||||
<Show when={currentView() === "snowball"}><SnowballView /></Show>
|
<Show when={currentView() === "snowball"}><SnowballView /></Show>
|
||||||
<Show when={currentView() === "dig_in"}><DigInView /></Show>
|
<Show when={currentView() === "dig_in"}><DigInView /></Show>
|
||||||
|
<Show when={currentView() === "progress"}><ProgressView /></Show>
|
||||||
<Show when={currentView() === "settings"}><SettingsView /></Show>
|
<Show when={currentView() === "settings"}><SettingsView /></Show>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type Component, For, Show } from "solid-js";
|
import { type Component, For, Show } from "solid-js";
|
||||||
import { LayoutDashboard, ListTodo, Settings, Clock, ArrowUpCircle, PanelLeftClose, Snowflake, Pickaxe, ChevronDown, Gauge, TrendingUp, Users, Box } from "lucide-solid";
|
import { LayoutDashboard, ListTodo, Settings, Clock, ArrowUpCircle, PanelLeftClose, Snowflake, Pickaxe, ChevronDown, Gauge, TrendingUp, Users, Box, BarChart3 } from "lucide-solid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
@@ -16,11 +16,12 @@ interface NavItem {
|
|||||||
// Desktop nav items (all)
|
// Desktop nav items (all)
|
||||||
const desktopNavItems: NavItem[] = [
|
const desktopNavItems: NavItem[] = [
|
||||||
{ icon: ListTodo, label: "Focus", view: "critical" },
|
{ icon: ListTodo, label: "Focus", view: "critical" },
|
||||||
{ icon: Clock, label: "Time", view: "urgency" },
|
{ icon: Clock, label: "Urgency", view: "urgency" },
|
||||||
{ icon: ArrowUpCircle, label: "Priority", view: "priority" },
|
{ icon: ArrowUpCircle, label: "Priority", view: "priority" },
|
||||||
{ icon: LayoutDashboard, label: "Matrix", view: "matrix" },
|
{ icon: LayoutDashboard, label: "Matrix", view: "matrix" },
|
||||||
{ icon: Snowflake, label: "Snowball", view: "snowball" },
|
{ icon: Snowflake, label: "Snowball", view: "snowball" },
|
||||||
{ icon: Pickaxe, label: "Dig In", view: "dig_in" },
|
{ icon: Pickaxe, label: "Dig In", view: "dig_in" },
|
||||||
|
{ icon: BarChart3, label: "Progress", view: "progress" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Mobile nav groups
|
// Mobile nav groups
|
||||||
@@ -37,13 +38,14 @@ const mobileNavGroups: MobileNavGroup[] = [
|
|||||||
{
|
{
|
||||||
icon: TrendingUp, label: "Value", items: [
|
icon: TrendingUp, label: "Value", items: [
|
||||||
{ icon: ArrowUpCircle, label: "Priority", view: "priority" },
|
{ icon: ArrowUpCircle, label: "Priority", view: "priority" },
|
||||||
{ icon: Clock, label: "Time", view: "urgency" },
|
{ icon: Clock, label: "Urgency", view: "urgency" },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Gauge, label: "Flow", items: [
|
icon: Gauge, label: "Flow", items: [
|
||||||
{ icon: Snowflake, label: "Snowball", view: "snowball" },
|
{ icon: Snowflake, label: "Snowball", view: "snowball" },
|
||||||
{ icon: Pickaxe, label: "Dig In", view: "dig_in" },
|
{ icon: Pickaxe, label: "Dig In", view: "dig_in" },
|
||||||
|
{ icon: BarChart3, label: "Progress", view: "progress" },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ icon: Settings, label: "Settings", view: "settings" },
|
{ icon: Settings, label: "Settings", view: "settings" },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type Component, createSignal, createEffect, onCleanup, onMount, For, Show } from "solid-js";
|
import { type Component, createSignal, createEffect, onCleanup, onMount, For, Show } from "solid-js";
|
||||||
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertTagDefinition, type TaskTemplate } from "@/store";
|
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertTagDefinition, currentTaskContext, type TaskTemplate } from "@/store";
|
||||||
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown, Gauge, X } from "lucide-solid";
|
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown, Gauge, X } from "lucide-solid";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -30,6 +30,28 @@ export const QuickEntry: Component = () => {
|
|||||||
const [editorInstance, setEditorInstance] = createSignal<any>(null);
|
const [editorInstance, setEditorInstance] = createSignal<any>(null);
|
||||||
const [size, setSize] = createSignal<string>("5");
|
const [size, setSize] = createSignal<string>("5");
|
||||||
|
|
||||||
|
// Autofill tags based on context when opening
|
||||||
|
createEffect(() => {
|
||||||
|
if (isOpen()) {
|
||||||
|
const ctx = currentTaskContext();
|
||||||
|
if (typeof ctx === 'object') {
|
||||||
|
if ('bucketId' in ctx) {
|
||||||
|
// Bucket View: Autofill bucket name
|
||||||
|
const bucketName = ctx.name;
|
||||||
|
if (bucketName && !tags().includes(bucketName)) {
|
||||||
|
setTags(prev => [...prev, bucketName]);
|
||||||
|
}
|
||||||
|
} else if ('userId' in ctx) {
|
||||||
|
// Shared User View: Autofill user name (assuming name IS the tag for sharing)
|
||||||
|
const userName = ctx.name;
|
||||||
|
if (userName && !tags().includes(userName)) {
|
||||||
|
setTags(prev => [...prev, userName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Reactive shorthand parsing
|
// Reactive shorthand parsing
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const text = input();
|
const text = input();
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
|
|
||||||
{/* Urgency */}
|
{/* Urgency */}
|
||||||
<div class="flex items-center gap-1 group shrink-0">
|
<div class="flex items-center gap-1 group shrink-0">
|
||||||
<span class="text-[0.625rem] uppercase font-bold tracking-wider text-muted-foreground/70 hidden sm:inline">Time</span>
|
<span class="text-[0.625rem] uppercase font-bold tracking-wider text-muted-foreground/70 hidden sm:inline">Urgency</span>
|
||||||
<Select<any>
|
<Select<any>
|
||||||
value={currentUrgency()}
|
value={currentUrgency()}
|
||||||
onChange={(val: string | null) => val && updateUrgency(val)}
|
onChange={(val: string | null) => val && updateUrgency(val)}
|
||||||
|
|||||||
+20
-3
@@ -1017,7 +1017,7 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
|
|||||||
const size = options?.size ?? 5;
|
const size = options?.size ?? 5;
|
||||||
|
|
||||||
const tempId = "temp-" + Date.now();
|
const tempId = "temp-" + Date.now();
|
||||||
const newTask: Task = {
|
const initialTask: Task = {
|
||||||
id: tempId,
|
id: tempId,
|
||||||
title,
|
title,
|
||||||
startDate,
|
startDate,
|
||||||
@@ -1033,12 +1033,17 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
|
|||||||
updated: new Date().toISOString()
|
updated: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply Handoff Logic (e.g. transfer ownership if tagging for another user)
|
||||||
|
// We treat the entire new task as "updates" to check against itself
|
||||||
|
const handoffUpdates = checkForHandoffs(initialTask, initialTask);
|
||||||
|
const newTask = { ...initialTask, ...handoffUpdates };
|
||||||
|
|
||||||
// Optimistic UI update
|
// Optimistic UI update
|
||||||
setStore("tasks", (t) => [newTask, ...t]);
|
setStore("tasks", (t) => [newTask, ...t]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const record = await pb.collection(TASGRID_COLLECTION).create({
|
const record = await pb.collection(TASGRID_COLLECTION).create({
|
||||||
user: pb.authStore.model?.id,
|
user: newTask.ownerId, // Use the (potentially updated) ownerId
|
||||||
title,
|
title,
|
||||||
startDate,
|
startDate,
|
||||||
dueDate,
|
dueDate,
|
||||||
@@ -1047,7 +1052,19 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
|
|||||||
completed: false,
|
completed: false,
|
||||||
tags,
|
tags,
|
||||||
content,
|
content,
|
||||||
size
|
size,
|
||||||
|
// If checking for handoffs added specific sharedWith, we might need to handle that,
|
||||||
|
// but currently checkForHandoffs modifies ownerId/sharedWith.
|
||||||
|
// PB API expects 'sharedWith' if we want to set it, but our types might be complex?
|
||||||
|
// Wait, checkForHandoffs returns Partial<Task>.
|
||||||
|
// If it sets ownerId to someone else, we rely on 'user' field in create.
|
||||||
|
// If it sets sharedWith (e.g. for bucket rule clearing), we need to send that too?
|
||||||
|
// Standard create doesn't support 'sharedWith' directly in this code block usually?
|
||||||
|
// Let's check mapRecordToTask. sharedWith comes from record expansion usually.
|
||||||
|
// But if we are creating, we might need to set standard relation fields if they exist.
|
||||||
|
// However, the original code didn't set sharedWith.
|
||||||
|
// The handoff logic usually just changes OWNER.
|
||||||
|
// If it changes owner, 'user' field covers it.
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replace temp task with real record (merging server fields like id, created, updated)
|
// Replace temp task with real record (merging server fields like id, created, updated)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { type Component, For, createMemo } from "solid-js";
|
||||||
|
import { store, getCombinedScore, matchesFilter } from "@/store";
|
||||||
|
import { TaskCard } from "@/components/TaskCard";
|
||||||
|
|
||||||
|
export const ProgressView: Component = () => {
|
||||||
|
const sortedTasks = createMemo(() => {
|
||||||
|
return [...store.tasks]
|
||||||
|
.filter(t => !t.deletedAt && matchesFilter(t))
|
||||||
|
.sort((a, b) => {
|
||||||
|
// 1. Completed tasks at the bottom
|
||||||
|
if (a.completed !== b.completed) return a.completed ? 1 : -1;
|
||||||
|
|
||||||
|
// 2. Sort by Progress (Status) Descending (Highest progress first)
|
||||||
|
// Assuming status 0-9 are active states, 10 is completed (handled above)
|
||||||
|
if (a.status !== b.status) return b.status - a.status;
|
||||||
|
|
||||||
|
// 3. Then by Focus Score (Combined Score)
|
||||||
|
return getCombinedScore(b) - getCombinedScore(a);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="space-y-6">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-3xl font-bold tracking-tight">Progress</h2>
|
||||||
|
<p class="text-muted-foreground mt-1 text-lg">Tasks sorted by progress status, then by focus score.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<For each={sortedTasks()}>
|
||||||
|
{(task) => <TaskCard task={task} />}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{sortedTasks().length === 0 && (
|
||||||
|
<div class="flex flex-col items-center justify-center py-20 text-center">
|
||||||
|
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mb-4">
|
||||||
|
<span class="text-2xl">📈</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted-foreground">No tasks found in the current filter.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user