diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 0308d45..f34e59d 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -3,6 +3,9 @@ import { LayoutDashboard, ListTodo, Settings, Clock, ArrowUpCircle, PanelLeftClo import { cn } from "@/lib/utils"; import { Button } from "./ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { store, currentTaskContext, setCurrentTaskContext } from "@/store"; +import { pb } from "@/lib/pocketbase"; +import { createResource, createSignal, createEffect } from "solid-js"; interface NavItem { icon: any; @@ -109,6 +112,130 @@ export const BottomNav: Component<{ currentView: string; setView: (v: string) => ); }; +const ContextSwitcher: Component<{ + isLocked: boolean; + isPeeking: boolean; + setIsPeeking: (v: boolean) => void; + onOpenChange?: (open: boolean) => void +}> = (props) => { + const currentUserId = pb.authStore.model?.id; + const [open, setOpen] = createSignal(false); + + // Sync with parent if needed + createEffect(() => { + props.onOpenChange?.(open()); + }); + + // Close popover when sidebar disappears + createEffect(() => { + if (!props.isLocked && !props.isPeeking) { + setOpen(false); + } + }); + + const [oversightUsers] = createResource( + () => 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' }))); + return users.map(u => ({ id: u.id, name: u.name || u.email })); + } catch (e) { + console.error("Failed to load oversight users", e); + return []; + } + } + ); + + const ctx = currentTaskContext; // Accessor + + const label = () => { + const c = ctx(); + return c === 'mine' ? "My Workspace" : c.name; + }; + + const handleSwitch = (userId: string, name: string) => { + if (userId === 'mine') { + setCurrentTaskContext('mine'); + } else { + setCurrentTaskContext({ userId, name }); + } + + // Close popover + setOpen(false); + + // Collapse sidebar if peaking + if (!props.isLocked && props.isPeeking) { + props.setIsPeeking(false); + } + }; + + return ( + 0}> + + +
+
+
+
+
+ {label()} +
+ +
+ + +
+ +
+
+
+
+ Oversight + {oversightUsers()?.length} +
+ + {(user) => ( + + )} + +
+ + + + ); +}; + export const Sidebar: Component<{ currentView: string; setView: (v: string) => void; @@ -117,10 +244,16 @@ export const Sidebar: Component<{ isPeeking: boolean; setIsPeeking: (v: boolean) => void; }> = (props) => { + const [switcherOpen, setSwitcherOpen] = createSignal(false); + return (
+ + {/* Context Switcher - Only visible if there are oversight contexts */} +
+ +
+
{/* Active share rules */} - - No active share rules. - - }> - {(rule) => ( -
-
- {rule.type === 'all' ? : } -
-

{getUserName(rule.targetUserId)}

-

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

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

Custom Rules

+ { + const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser; + return !isSystem; + })} fallback={ +
+ No custom share rules.
- )} -
+ }> + {(rule) => ( +
+
+ {rule.type === 'all' ? : } +
+

{getUserName(rule.targetUserId)}

+

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

+
+
+ +
+ )} + +
+ + {/* Active share rules - System */} +
+ +
+ +
+ System Rules (Auto-Generated) +
+
+ { + const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser; + return isSystem; + })}> + {(rule) => ( +
+
+ +
+

+ {getUserName(rule.targetUserId)} + +

+

+ Auto-shared via User Tag +

+
+
+
+ )} +
+
+
@@ -257,84 +297,132 @@ export const SettingsView: Component = () => {
- a.name.localeCompare(b.name))} fallback={ -
- No specific tag definitions found. -
- }> - {(tag) => { - const [isEditingName, setIsEditingName] = createSignal(false); - const [tempName, setTempName] = createSignal(tag.name); +
+ !t.isUser).sort((a, b) => a.name.localeCompare(b.name))} fallback={ +
+ No custom tags found. +
+ }> + {(tag) => { + const [isEditingName, setIsEditingName] = createSignal(false); + const [tempName, setTempName] = createSignal(tag.name); - const handleRename = () => { - if (tempName() && tempName() !== tag.name) { - renameTagDefinition(tag.name, tempName()); - } - setIsEditingName(false); - }; + const handleRename = () => { + if (tempName() && tempName() !== tag.name) { + renameTagDefinition(tag.name, tempName()); + } + setIsEditingName(false); + }; - return ( -
-
- upsertTagDefinition(tag.name, tag.value, e.currentTarget.value, resolvedTheme())} - class="w-4 h-4 rounded-full border-none p-0 bg-transparent cursor-pointer shrink-0 overflow-hidden" - title="Tag accent color" - /> - - {isEditingName() ? ( + return ( +
+
setTempName(e.currentTarget.value)} - onBlur={handleRename} - onKeyDown={(e) => e.key === "Enter" && handleRename()} - autofocus + type="color" + value={tag.color || "#6366f1"} + onInput={(e) => upsertTagDefinition(tag.name, tag.value, e.currentTarget.value, resolvedTheme())} + class="w-4 h-4 rounded-full border-none p-0 bg-transparent cursor-pointer shrink-0 overflow-hidden" + title="Tag accent color" /> - ) : ( - setIsEditingName(true)} - title="Click to rename" - > - {tag.name} - - )} -
-
-
- Val - + {isEditingName() ? ( + setTempName(e.currentTarget.value)} + onBlur={handleRename} + onKeyDown={(e) => e.key === "Enter" && handleRename()} + autofocus + /> + ) : ( + setIsEditingName(true)} + title="Click to rename" + > + {tag.name} + + )}
- +
+
+ Val + +
+ + +
-
- ); - }} - + ); + }} + +
+ + {/* User Tags Expansion */} +
+ +
+ +
+ User Tags (System) +
+
+ t.isUser).sort((a, b) => a.name.localeCompare(b.name))}> + {(tag) => ( +
+
+ upsertTagDefinition(tag.name, tag.value, e.currentTarget.value, resolvedTheme())} + class="w-4 h-4 rounded-full border-none p-0 bg-transparent cursor-pointer shrink-0 overflow-hidden" + title="Tag accent color" + /> + + {tag.name} + + +
+ +
+
+ Val + +
+
+
+ )} +
+
+