From 9761c8c5fce2cf19505905f33dc37e3e6a676388 Mon Sep 17 00:00:00 2001 From: Timothy Cardoza Date: Wed, 11 Feb 2026 13:21:23 -0600 Subject: [PATCH] fixed shared to viewing in settings and added workspace navigation in mobile view --- src/components/Layout.tsx | 7 +++- src/components/Navigation.tsx | 73 ++++++++++++++++++++++------------- src/views/SettingsView.tsx | 64 ++++++++++++++++++++++++------ 3 files changed, 105 insertions(+), 39 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index cb5b480..5ae194e 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,5 +1,5 @@ import { type Component, type JSX, createSignal, Show, lazy, Suspense, onMount } from "solid-js"; -import { Sidebar, BottomNav } from "./Navigation"; +import { Sidebar, BottomNav, ContextSwitcher } from "./Navigation"; import { store, activeTaskId, setActiveTaskId } from "@/store"; import { PanelLeftOpen } from "lucide-solid"; @@ -62,6 +62,11 @@ export const Layout: Component = (props) => { )} + {/* Mobile Context Switcher */} +
+ +
+ {/* Main Content Area */}
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index f34e59d..09e9b53 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -1,5 +1,5 @@ import { type Component, For, Show } from "solid-js"; -import { LayoutDashboard, ListTodo, Settings, Clock, ArrowUpCircle, PanelLeftClose, Snowflake, Pickaxe, ChevronDown, Gauge, TrendingUp } from "lucide-solid"; +import { LayoutDashboard, ListTodo, Settings, Clock, ArrowUpCircle, PanelLeftClose, Snowflake, Pickaxe, ChevronDown, Gauge, TrendingUp, Users } from "lucide-solid"; import { cn } from "@/lib/utils"; import { Button } from "./ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -112,11 +112,12 @@ export const BottomNav: Component<{ currentView: string; setView: (v: string) => ); }; -const ContextSwitcher: Component<{ - isLocked: boolean; - isPeeking: boolean; - setIsPeeking: (v: boolean) => void; - onOpenChange?: (open: boolean) => void +export const ContextSwitcher: Component<{ + isLocked?: boolean; + isPeeking?: boolean; + setIsPeeking?: (v: boolean) => void; + onOpenChange?: (open: boolean) => void; + isMobile?: boolean; }> = (props) => { const currentUserId = pb.authStore.model?.id; const [open, setOpen] = createSignal(false); @@ -126,23 +127,28 @@ const ContextSwitcher: Component<{ props.onOpenChange?.(open()); }); - // Close popover when sidebar disappears + // Close popover when sidebar disappears (Desktop only) createEffect(() => { - if (!props.isLocked && !props.isPeeking) { + if (!props.isMobile && !props.isLocked && !props.isPeeking) { setOpen(false); } }); const [oversightUsers] = createResource( - () => store.shareRules.filter(r => r.targetUserId === currentUserId && r.type === 'all').map(r => r.ownerId), + () => [...new Set(store.shareRules.filter(r => r.targetUserId === currentUserId && r.type === 'all').map(r => r.ownerId))], async (ownerIds) => { if (ownerIds.length === 0) return []; try { - // Fetch user names for these IDs - // Optimization: Could check if we already have them or just fetch - const users = await Promise.all(ownerIds.map(id => pb.collection('users').getOne(id, { fields: 'id,name,email' }))); + // Fetch user names for these IDs in a single query + const filter = ownerIds.map(id => `id="${id}"`).join(' || '); + const users = await pb.collection('users').getFullList({ + filter, + fields: 'id,name,email', + requestKey: null // Disable auto-cancellation + }); return users.map(u => ({ id: u.id, name: u.name || u.email })); - } catch (e) { + } catch (e: any) { + if (e.isAbort) return []; // Ignore explicit aborts console.error("Failed to load oversight users", e); return []; } @@ -166,8 +172,8 @@ const ContextSwitcher: Component<{ // Close popover setOpen(false); - // Collapse sidebar if peaking - if (!props.isLocked && props.isPeeking) { + // Collapse sidebar if peaking (Desktop only) + if (!props.isMobile && props.setIsPeeking && !props.isLocked && props.isPeeking) { props.setIsPeeking(false); } }; @@ -175,21 +181,34 @@ const ContextSwitcher: Component<{ return ( 0}> - +
-
-
-
+ +
+
+
+
+ {label()} +
+ + + }> +
+ + {ctx() !== 'mine' && ( +
+ )}
- {label()} -
- +
diff --git a/src/views/SettingsView.tsx b/src/views/SettingsView.tsx index 3eee7ae..bef1768 100644 --- a/src/views/SettingsView.tsx +++ b/src/views/SettingsView.tsx @@ -179,32 +179,39 @@ export const SettingsView: Component = () => {
{/* Active share rules */} - {/* Active share rules - Custom */} -
-

Custom Rules

+ {/* Active share rules - Outgoing */} +
+

+
+ Sharing With Others +

{ const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser; - return !isSystem; + const isOwner = r.ownerId === pb.authStore.model?.id; + return !isSystem && isOwner; })} fallback={ -
- No custom share rules. +
+ You aren't sharing with anyone yet.
}> {(rule) => ( -
+
- {rule.type === 'all' ? : } +
+ {rule.type === 'all' ? : } +
-

{getUserName(rule.targetUserId)}

-

+

To: {getUserName(rule.targetUserId)}

+

{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`} +

+ {/* Active share rules - Incoming */} +
+

+
+ Shared With You +

+ { + const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser; + const isTarget = r.targetUserId === pb.authStore.model?.id; + return !isSystem && isTarget; + })} fallback={ +
+ No one has shared with you yet. +
+ }> + {(rule) => ( +
+
+
+ {rule.type === 'all' ? : } +
+
+

From: {getUserName(rule.ownerId)}

+

+ {rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`} + +

+
+
+ {/* No delete button for incoming rules unless we add leave logic later */} +
+ )} +
+
+ {/* Active share rules - System */}