added system user tags/sharing and workspaces for recieving share-all rules

This commit is contained in:
2026-02-10 17:09:08 -06:00
parent 414eead28d
commit bf12c6f814
3 changed files with 469 additions and 102 deletions
+145 -1
View File
@@ -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 (
<Show when={oversightUsers() && oversightUsers()!.length > 0}>
<Popover open={open()} onOpenChange={setOpen}>
<PopoverTrigger class="w-full">
<div class={cn(
"flex items-center justify-between w-full px-3 py-2.5 rounded-lg transition-all duration-200 group text-sm",
"text-muted-foreground hover:bg-muted hover:text-foreground border border-transparent hover:border-border/50"
)}>
<div class="flex items-center gap-3 min-w-0">
<div class={cn(
"w-4 h-4 rounded-full flex items-center justify-center shrink-0",
ctx() === 'mine' ? "bg-primary/20 text-primary" : "bg-orange-500/20 text-orange-500"
)}>
<div class="w-2 h-2 rounded-full bg-current" />
</div>
<span class="font-medium truncate">{label()}</span>
</div>
<ChevronDown size={14} class="opacity-50 group-hover:opacity-100 transition-opacity" />
</div>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-1 bg-card border-border shadow-xl">
<div class="space-y-0.5">
<button
onClick={() => handleSwitch('mine', '')}
class={cn(
"w-full flex items-center gap-2 p-2 rounded-md text-sm transition-colors",
ctx() === 'mine' ? "bg-primary/10 text-primary font-semibold" : "hover:bg-muted text-foreground"
)}
>
<div class="w-4 h-4 rounded-full bg-primary/20 flex items-center justify-center">
<div class="w-2 h-2 rounded-full bg-primary" />
</div>
My Workspace
</button>
<div class="py-1">
<div class="h-px bg-border/50" />
</div>
<div class="px-2 py-1 text-[10px] font-bold uppercase text-muted-foreground tracking-wider flex items-center gap-2">
<span>Oversight</span>
<span class="ml-auto bg-muted px-1.5 rounded text-[9px]">{oversightUsers()?.length}</span>
</div>
<For each={oversightUsers()}>
{(user) => (
<button
onClick={() => handleSwitch(user.id, user.name)}
class={cn(
"w-full flex items-center gap-2 p-2 rounded-md text-sm transition-colors",
(ctx() as any).userId === user.id ? "bg-orange-500/10 text-orange-600 font-semibold" : "hover:bg-muted text-foreground"
)}
>
<div class="w-4 h-4 rounded-full bg-orange-500/20 flex items-center justify-center">
<div class="w-2 h-2 rounded-full bg-orange-500" />
</div>
<span class="truncate">{user.name}</span>
</button>
)}
</For>
</div>
</PopoverContent>
</Popover>
</Show>
);
};
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 (
<aside
onMouseEnter={() => !props.isLocked && props.setIsPeeking(true)}
onMouseLeave={() => !props.isLocked && props.setIsPeeking(false)}
onMouseLeave={() => {
if (!props.isLocked && !switcherOpen()) {
props.setIsPeeking(false);
}
}}
class={cn(
"hidden md:flex flex-col border-r border-border bg-card h-screen fixed left-0 top-0 transition-all duration-300 ease-in-out z-[50]",
props.isLocked ? "w-48 translate-x-0" : "w-48 -translate-x-full",
@@ -151,6 +284,17 @@ export const Sidebar: Component<{
</Button>
</Show>
</div>
{/* Context Switcher - Only visible if there are oversight contexts */}
<div class={cn("px-3 mb-2", !props.isLocked && !props.isPeeking && "hidden")}>
<ContextSwitcher
isLocked={props.isLocked}
isPeeking={props.isPeeking}
setIsPeeking={props.setIsPeeking}
onOpenChange={setSwitcherOpen}
/>
</div>
<nav class="flex-1 px-3 space-y-1 mt-2">
<For each={desktopNavItems}>
{(item) => (