fixed sharing

This commit is contained in:
2026-02-05 18:43:14 -06:00
parent 120484d54e
commit f783c20f60
4 changed files with 689 additions and 23 deletions
+158 -3
View File
@@ -1,15 +1,16 @@
import { type Component, For, Show, createSignal, lazy, Suspense } from "solid-js";
import { ThemeToggle } from "../components/ThemeToggle";
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition } from "@/store";
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule } from "@/store";
import { useTheme } from "@/components/ThemeProvider";
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type } from "lucide-solid";
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type, Share2, Users, Tag } from "lucide-solid";
import { TagPicker } from "@/components/TagPicker";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { toast } from "solid-sonner";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { PRIORITY_OPTIONS, URGENCY_OPTIONS } from "@/lib/constants";
import { pb } from "@/lib/pocketbase";
import { createEffect } from "solid-js";
const TaskEditor = lazy(() => import("../components/TaskEditor").then(m => ({ default: m.TaskEditor })));
@@ -18,7 +19,35 @@ export const SettingsView: Component = () => {
const [isTagsOpen, setIsTagsOpen] = createSignal(false);
const [isTemplatesOpen, setIsTemplatesOpen] = createSignal(false);
const [isTrashOpen, setIsTrashOpen] = createSignal(false);
const [isSharingOpen, setIsSharingOpen] = createSignal(false);
const [expandedTemplateId, setExpandedTemplateId] = createSignal<string | null>(null);
const [shareUserId, setShareUserId] = createSignal("");
const [shareType, setShareType] = createSignal<'all' | 'tag'>('all');
const [shareTag, setShareTag] = createSignal<string>("");
const [allUsers, setAllUsers] = createSignal<Array<{ id: string; name: string }>>([]);
const [usersLoading, setUsersLoading] = createSignal(false);
// Fetch users when sharing section opens
createEffect(() => {
if (isSharingOpen() && allUsers().length === 0 && !usersLoading()) {
setUsersLoading(true);
pb.collection('users').getFullList({ fields: 'id,name,email' })
.then(records => {
setAllUsers(records.map((r: any) => ({ id: r.id, name: r.name || r.email || r.id })));
})
.catch(err => console.error("Failed to load users:", err))
.finally(() => setUsersLoading(false));
}
});
const getUserName = (userId: string) => {
return allUsers().find(u => u.id === userId)?.name || userId;
};
const availableUsers = () => {
const currentUserId = pb.authStore.model?.id;
return allUsers().filter(u => u.id !== currentUserId);
};
return (
<div class="w-full max-w-2xl mx-auto py-4 sm:py-10 px-4 sm:px-0 space-y-8 sm:space-y-12 animate-in fade-in slide-in-from-bottom-2 duration-500 min-w-0 overflow-hidden text-balance">
@@ -53,6 +82,132 @@ export const SettingsView: Component = () => {
</div>
</section>
{/* Sharing */}
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4 overflow-hidden">
<div
class="flex items-center justify-between cursor-pointer group"
onClick={() => setIsSharingOpen(!isSharingOpen())}
>
<div class="space-y-0.5">
<h3 class="text-base font-semibold flex items-center gap-2">
<Share2 size={16} />
Task Sharing
</h3>
<p class="text-sm text-muted-foreground">Share all tasks or tasks with specific tags with others.</p>
</div>
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-muted/50 group-hover:bg-muted transition-colors">
{isSharingOpen() ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
</div>
</div>
<Show when={isSharingOpen()}>
<div class="space-y-4 pt-2 animate-in fade-in slide-in-from-top-2 duration-300">
{/* Add new share rule */}
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
<div class="flex gap-2">
<Button
variant={shareType() === 'all' ? 'default' : 'outline'}
size="sm"
class="h-8 text-xs gap-1"
onClick={() => setShareType('all')}
>
<Users size={12} />
All Tasks
</Button>
<Button
variant={shareType() === 'tag' ? 'default' : 'outline'}
size="sm"
class="h-8 text-xs gap-1"
onClick={() => setShareType('tag')}
>
<Tag size={12} />
By Tag
</Button>
</div>
<Show when={shareType() === 'tag'}>
<Select
value={shareTag()}
onChange={(val) => val && setShareTag(val)}
options={store.tagDefinitions.map(t => t.name)}
placeholder="Select a tag"
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
>
<SelectTrigger class="h-9 text-sm">
<span>{shareTag() || "Select a tag..."}</span>
</SelectTrigger>
<SelectContent />
</Select>
</Show>
<div class="flex gap-2">
<Select<{ id: string; name: string }>
value={allUsers().find(u => u.id === shareUserId()) || null}
onChange={(user) => setShareUserId(user?.id || "")}
options={availableUsers()}
optionValue="id"
optionTextValue="name"
placeholder={usersLoading() ? "Loading..." : "Select user..."}
itemComponent={(itemProps: any) => (
<SelectItem item={itemProps.item}>{itemProps.item.rawValue.name}</SelectItem>
)}
>
<SelectTrigger class="flex-1 h-9 text-sm">
<SelectValue<{ id: string; name: string }>>
{(state) => state.selectedOption()?.name || "Select user..."}
</SelectValue>
</SelectTrigger>
<SelectContent />
</Select>
<Button
size="sm"
class="h-9 gap-2"
disabled={!shareUserId()}
onClick={async () => {
if (shareUserId()) {
await addShareRule(shareType(), shareUserId(), shareType() === 'tag' ? shareTag() : undefined);
setShareUserId("");
}
}}
>
<Plus size={14} />
Share
</Button>
</div>
</div>
{/* Active share rules */}
<For each={store.shareRules} fallback={
<div class="text-center py-6 text-muted-foreground text-sm italic border border-dashed border-border rounded-xl">
No active share rules.
</div>
}>
{(rule) => (
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3">
<div class="flex items-center gap-3 min-w-0 flex-1">
{rule.type === 'all' ? <Users size={14} class="text-primary shrink-0" /> : <Tag size={14} class="text-primary shrink-0" />}
<div class="min-w-0">
<p class="text-sm font-medium truncate">{getUserName(rule.targetUserId)}</p>
<p class="text-[10px] text-muted-foreground uppercase tracking-wider">
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`}
</p>
</div>
</div>
<Button
variant="ghost"
size="icon"
class="h-8 w-8 hover:bg-destructive/10 hover:text-destructive"
onClick={() => removeShareRule(rule.id)}
>
<Trash2 size={14} />
</Button>
</div>
)}
</For>
</div>
</Show>
</section>
{/* Matrix Configuration */}
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">