Task sharing wording improved
This commit is contained in:
+244
-244
@@ -119,7 +119,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<Share2 size={16} />
|
<Share2 size={16} />
|
||||||
Tag Sharing
|
Tag Sharing
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-muted-foreground">Share through tags only: `@people` collaborate, `@buckets` route work, and `#notes` stay link-only.</p>
|
<p class="text-sm text-muted-foreground">Share through tags: `@people` to collaborate, `@buckets` to move tasks to buckets, and `#notes` to link tasks to notes.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-muted/50 group-hover:bg-muted transition-colors">
|
<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} />}
|
{isSharingOpen() ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||||
@@ -131,7 +131,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
|
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
|
||||||
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">User Tag Policies</h4>
|
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">User Tag Policies</h4>
|
||||||
<p class="text-[0.6875rem] text-muted-foreground">
|
<p class="text-[0.6875rem] text-muted-foreground">
|
||||||
Control how each <span class="font-semibold text-foreground">@user</span> tag behaves. Collaborative tags keep work shared. Handoff tags route work into that user's queue.
|
Control how each <span class="font-semibold text-foreground">@user</span> share tag behaves. Collaborative keeps tasks in both of your lists. Handoff moves tasks from your list to theirs.
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<For each={userContexts()} fallback={
|
<For each={userContexts()} fallback={
|
||||||
@@ -234,261 +234,261 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={false}>
|
<Show when={false}>
|
||||||
{/* Add new share rule */}
|
{/* Add new share rule */}
|
||||||
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
|
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant={shareType() === 'all' ? 'default' : 'outline'}
|
variant={shareType() === 'all' ? 'default' : 'outline'}
|
||||||
size="sm"
|
size="sm"
|
||||||
class="h-8 text-xs gap-1"
|
class="h-8 text-xs gap-1"
|
||||||
disabled
|
disabled
|
||||||
title="All-task sharing is no longer supported"
|
title="All-task sharing is no longer supported"
|
||||||
>
|
>
|
||||||
<Users size={12} />
|
<Users size={12} />
|
||||||
Legacy Off
|
Legacy Off
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={shareType() === 'tag' ? 'default' : 'outline'}
|
variant={shareType() === 'tag' ? 'default' : 'outline'}
|
||||||
size="sm"
|
size="sm"
|
||||||
class="h-8 text-xs gap-1"
|
class="h-8 text-xs gap-1"
|
||||||
onClick={() => setShareType('tag')}
|
onClick={() => setShareType('tag')}
|
||||||
>
|
>
|
||||||
<Tag size={12} />
|
<Tag size={12} />
|
||||||
By Tag
|
By Tag
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Share Mode Selection */}
|
{/* Share Mode Selection */}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant={shareMode() === 'ADD_USER' ? 'default' : 'outline'}
|
variant={shareMode() === 'ADD_USER' ? 'default' : 'outline'}
|
||||||
size="sm"
|
size="sm"
|
||||||
class="h-7 text-[0.625rem] gap-1 px-2"
|
class="h-7 text-[0.625rem] gap-1 px-2"
|
||||||
title="Recipient gets edit access (Collaboration)"
|
title="Recipient gets edit access (Collaboration)"
|
||||||
onClick={() => setShareMode('ADD_USER')}
|
onClick={() => setShareMode('ADD_USER')}
|
||||||
>
|
>
|
||||||
<Users size={10} />
|
<Users size={10} />
|
||||||
Collaborate
|
Collaborate
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={shareMode() === 'SEND_TASK' ? 'default' : 'outline'}
|
variant={shareMode() === 'SEND_TASK' ? 'default' : 'outline'}
|
||||||
size="sm"
|
size="sm"
|
||||||
class="h-7 text-[0.625rem] gap-1 px-2"
|
class="h-7 text-[0.625rem] gap-1 px-2"
|
||||||
title="Task ownership is transferred to recipient (Handoff)"
|
title="Task ownership is transferred to recipient (Handoff)"
|
||||||
onClick={() => setShareMode('SEND_TASK')}
|
onClick={() => setShareMode('SEND_TASK')}
|
||||||
>
|
>
|
||||||
<ArrowLeftRight size={10} />
|
<ArrowLeftRight size={10} />
|
||||||
Handoff
|
Handoff
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={shareType() === 'tag'}>
|
<Show when={shareType() === 'tag'}>
|
||||||
<Select
|
<Select
|
||||||
value={shareTag()}
|
value={shareTag()}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
setShareTag(val);
|
setShareTag(val);
|
||||||
const tagDef = store.tagDefinitions.find(t => t.name === val);
|
const tagDef = store.tagDefinitions.find(t => t.name === val);
|
||||||
const isBucket = tagDef?.isBucket || store.buckets.some(b => b.name === val);
|
const isBucket = tagDef?.isBucket || store.buckets.some(b => b.name === val);
|
||||||
const isUser = tagDef?.isUser;
|
const isUser = tagDef?.isUser;
|
||||||
|
|
||||||
if (isBucket) {
|
if (isBucket) {
|
||||||
setShareMode('SEND_TASK');
|
setShareMode('SEND_TASK');
|
||||||
} else if (isUser) {
|
} else if (isUser) {
|
||||||
setShareMode('ADD_USER');
|
setShareMode('ADD_USER');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
options={store.tagDefinitions.filter(t => t.name.startsWith("@")).map(t => t.name)}
|
||||||
options={store.tagDefinitions.filter(t => t.name.startsWith("@")).map(t => t.name)}
|
placeholder="Select a tag"
|
||||||
placeholder="Select a tag"
|
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
||||||
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
|
>
|
||||||
>
|
<SelectTrigger class="h-9 text-sm">
|
||||||
<SelectTrigger class="h-9 text-sm">
|
<span>{shareTag() || "Select a tag..."}</span>
|
||||||
<span>{shareTag() || "Select a tag..."}</span>
|
</SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectContent />
|
||||||
<SelectContent />
|
</Select>
|
||||||
</Select>
|
</Show>
|
||||||
</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() || !shareTag()}
|
||||||
|
onClick={async () => {
|
||||||
|
if (shareUserId() && shareTag()) {
|
||||||
|
await addShareRule('tag', shareUserId(), shareTag(), shareMode());
|
||||||
|
setShareUserId("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus size={14} />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p class="text-[0.625rem] text-muted-foreground italic px-1 pt-1">
|
||||||
|
{shareMode() === 'ADD_USER'
|
||||||
|
? "Collaborative contexts add access through the selected @tag."
|
||||||
|
: "Handoff contexts transfer ownership when that @tag is applied."}
|
||||||
|
</p>
|
||||||
|
|
||||||
<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() || !shareTag()}
|
|
||||||
onClick={async () => {
|
|
||||||
if (shareUserId() && shareTag()) {
|
|
||||||
await addShareRule('tag', shareUserId(), shareTag(), shareMode());
|
|
||||||
setShareUserId("");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus size={14} />
|
|
||||||
Share
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[0.625rem] text-muted-foreground italic px-1 pt-1">
|
|
||||||
{shareMode() === 'ADD_USER'
|
|
||||||
? "Collaborative contexts add access through the selected @tag."
|
|
||||||
: "Handoff contexts transfer ownership when that @tag is applied."}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
{/* Active share rules */}
|
||||||
|
{/* Active share rules - Outgoing */}
|
||||||
{/* Active share rules */}
|
<div class="space-y-3">
|
||||||
{/* Active share rules - Outgoing */}
|
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground pb-1 flex items-center gap-2">
|
||||||
<div class="space-y-3">
|
<div class="w-1 h-1 rounded-full bg-primary" />
|
||||||
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground pb-1 flex items-center gap-2">
|
Sharing With Others
|
||||||
<div class="w-1 h-1 rounded-full bg-primary" />
|
</h4>
|
||||||
Sharing With Others
|
|
||||||
</h4>
|
|
||||||
<For each={store.shareRules.filter(r => {
|
|
||||||
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
|
||||||
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
|
||||||
const isSystem = tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
|
||||||
const isOwner = r.ownerId === pb.authStore.model?.id;
|
|
||||||
return !isSystem && isOwner;
|
|
||||||
})} fallback={
|
|
||||||
<div class="text-center py-4 text-muted-foreground text-[0.625rem] uppercase tracking-widest italic border border-dashed border-border/40 rounded-xl bg-muted/5">
|
|
||||||
You aren't sharing with anyone yet.
|
|
||||||
</div>
|
|
||||||
}>
|
|
||||||
{(rule) => (
|
|
||||||
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3 group/rule hover:bg-muted/30 transition-all">
|
|
||||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
|
||||||
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0 transition-transform group-hover/rule:scale-110">
|
|
||||||
{rule.type === 'all' ? <Users size={14} /> : <Tag size={14} />}
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="text-xs font-bold truncate">To: {getUserName(rule.targetUserId)}</p>
|
|
||||||
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
|
||||||
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
|
||||||
<span class="mx-1">•</span>
|
|
||||||
<button
|
|
||||||
class="hover:text-primary transition-colors hover:underline"
|
|
||||||
onClick={() => updateShareRule(rule.id, {
|
|
||||||
share_mode: rule.share_mode === 'SEND_TASK' ? 'ADD_USER' : 'SEND_TASK'
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
class="h-8 w-8 hover:bg-destructive/10 hover:text-destructive rounded-lg transition-all opacity-0 group-hover/rule:opacity-100"
|
|
||||||
onClick={() => removeShareRule(rule.id)}
|
|
||||||
>
|
|
||||||
<Trash2 size={14} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Active share rules - Incoming */}
|
|
||||||
<div class="space-y-3 pt-4 border-t border-border/20">
|
|
||||||
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground pb-1 flex items-center gap-2">
|
|
||||||
<div class="w-1 h-1 rounded-full bg-indigo-500" />
|
|
||||||
Shared With You
|
|
||||||
</h4>
|
|
||||||
<For each={store.shareRules.filter(r => {
|
|
||||||
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
|
||||||
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
|
||||||
const isSystem = tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
|
||||||
const isTarget = r.targetUserId === pb.authStore.model?.id;
|
|
||||||
return !isSystem && isTarget;
|
|
||||||
})} fallback={
|
|
||||||
<div class="text-center py-4 text-muted-foreground text-[0.625rem] uppercase tracking-widest italic border border-dashed border-border/40 rounded-xl bg-muted/5">
|
|
||||||
No one has shared with you yet.
|
|
||||||
</div>
|
|
||||||
}>
|
|
||||||
{(rule) => (
|
|
||||||
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3 group/rule hover:bg-muted/30 transition-all opacity-80">
|
|
||||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
|
||||||
<div class="w-8 h-8 rounded-lg bg-indigo-500/10 flex items-center justify-center text-indigo-500 shrink-0">
|
|
||||||
{rule.type === 'all' ? <Users size={14} /> : <Tag size={14} />}
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="text-xs font-bold truncate">From: {getUserName(rule.ownerId)}</p>
|
|
||||||
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
|
||||||
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
|
||||||
<span class="mx-1">•</span>
|
|
||||||
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* No delete button for incoming rules unless we add leave logic later */}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Active share rules - System */}
|
|
||||||
<details class="group/details">
|
|
||||||
<summary class="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-muted-foreground cursor-pointer hover:text-foreground transition-colors py-2 select-none">
|
|
||||||
<div class="bg-muted/50 p-1 rounded-md group-open/details:rotate-90 transition-transform">
|
|
||||||
<ChevronRight size={12} />
|
|
||||||
</div>
|
|
||||||
System Rules (Auto-Generated)
|
|
||||||
</summary>
|
|
||||||
<div class="pt-2 pl-4 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200">
|
|
||||||
<For each={store.shareRules.filter(r => {
|
<For each={store.shareRules.filter(r => {
|
||||||
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
||||||
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
||||||
return tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
const isSystem = tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
||||||
})}>
|
const isOwner = r.ownerId === pb.authStore.model?.id;
|
||||||
{(rule) => {
|
return !isSystem && isOwner;
|
||||||
const tagDef = store.tagDefinitions.find(t => t.name === rule.tagName);
|
})} fallback={
|
||||||
return (
|
<div class="text-center py-4 text-muted-foreground text-[0.625rem] uppercase tracking-widest italic border border-dashed border-border/40 rounded-xl bg-muted/5">
|
||||||
<div class="flex items-center justify-between p-2 rounded-lg bg-muted/10 border border-border/20 gap-3 opacity-70">
|
You aren't sharing with anyone yet.
|
||||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
</div>
|
||||||
<Tag size={14} class="text-muted-foreground shrink-0" />
|
}>
|
||||||
<div class="min-w-0">
|
{(rule) => (
|
||||||
<p class="text-sm font-medium truncate flex items-center gap-2">
|
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3 group/rule hover:bg-muted/30 transition-all">
|
||||||
{/* If it's a user tag, show the person's name. If it's a bucket, show the bucket name (tagName). */}
|
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||||
{tagDef?.isUser ? getUserName(rule.targetUserId) : rule.tagName}
|
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0 transition-transform group-hover/rule:scale-110">
|
||||||
{tagDef?.isUser ? <Users size={10} class="text-muted-foreground" /> : <Archive size={10} class="text-muted-foreground" />}
|
{rule.type === 'all' ? <Users size={14} /> : <Tag size={14} />}
|
||||||
</p>
|
</div>
|
||||||
<p class="text-[0.625rem] text-muted-foreground uppercase tracking-wider flex items-center gap-1">
|
<div class="min-w-0">
|
||||||
Auto-shared via {tagDef?.isUser ? 'User' : 'Bucket'} Tag
|
<p class="text-xs font-bold truncate">To: {getUserName(rule.targetUserId)}</p>
|
||||||
<span class="mx-1">•</span>
|
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
||||||
<button
|
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
||||||
class="hover:text-primary transition-colors hover:underline"
|
<span class="mx-1">•</span>
|
||||||
onClick={() => updateShareRule(rule.id, {
|
<button
|
||||||
share_mode: rule.share_mode === 'SEND_TASK' ? 'ADD_USER' : 'SEND_TASK'
|
class="hover:text-primary transition-colors hover:underline"
|
||||||
})}
|
onClick={() => updateShareRule(rule.id, {
|
||||||
>
|
share_mode: rule.share_mode === 'SEND_TASK' ? 'ADD_USER' : 'SEND_TASK'
|
||||||
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
})}
|
||||||
</button>
|
>
|
||||||
</p>
|
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
||||||
</div>
|
</button>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<Button
|
||||||
}}
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="h-8 w-8 hover:bg-destructive/10 hover:text-destructive rounded-lg transition-all opacity-0 group-hover/rule:opacity-100"
|
||||||
|
onClick={() => removeShareRule(rule.id)}
|
||||||
|
>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
|
||||||
|
{/* Active share rules - Incoming */}
|
||||||
|
<div class="space-y-3 pt-4 border-t border-border/20">
|
||||||
|
<h4 class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground pb-1 flex items-center gap-2">
|
||||||
|
<div class="w-1 h-1 rounded-full bg-indigo-500" />
|
||||||
|
Shared With You
|
||||||
|
</h4>
|
||||||
|
<For each={store.shareRules.filter(r => {
|
||||||
|
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
||||||
|
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
||||||
|
const isSystem = tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
||||||
|
const isTarget = r.targetUserId === pb.authStore.model?.id;
|
||||||
|
return !isSystem && isTarget;
|
||||||
|
})} fallback={
|
||||||
|
<div class="text-center py-4 text-muted-foreground text-[0.625rem] uppercase tracking-widest italic border border-dashed border-border/40 rounded-xl bg-muted/5">
|
||||||
|
No one has shared with you yet.
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
{(rule) => (
|
||||||
|
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3 group/rule hover:bg-muted/30 transition-all opacity-80">
|
||||||
|
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-indigo-500/10 flex items-center justify-center text-indigo-500 shrink-0">
|
||||||
|
{rule.type === 'all' ? <Users size={14} /> : <Tag size={14} />}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="text-xs font-bold truncate">From: {getUserName(rule.ownerId)}</p>
|
||||||
|
<p class="text-[0.5625rem] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
|
||||||
|
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName} `}
|
||||||
|
<span class="mx-1">•</span>
|
||||||
|
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* No delete button for incoming rules unless we add leave logic later */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active share rules - System */}
|
||||||
|
<details class="group/details">
|
||||||
|
<summary class="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-muted-foreground cursor-pointer hover:text-foreground transition-colors py-2 select-none">
|
||||||
|
<div class="bg-muted/50 p-1 rounded-md group-open/details:rotate-90 transition-transform">
|
||||||
|
<ChevronRight size={12} />
|
||||||
|
</div>
|
||||||
|
System Rules (Auto-Generated)
|
||||||
|
</summary>
|
||||||
|
<div class="pt-2 pl-4 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200">
|
||||||
|
<For each={store.shareRules.filter(r => {
|
||||||
|
const tagDef = r.type === 'tag' ? store.tagDefinitions.find(t => t.name === r.tagName) : null;
|
||||||
|
const isBucketTag = r.type === 'tag' && store.buckets.some(b => b.name === r.tagName);
|
||||||
|
return tagDef?.isUser || tagDef?.isBucket || isBucketTag;
|
||||||
|
})}>
|
||||||
|
{(rule) => {
|
||||||
|
const tagDef = store.tagDefinitions.find(t => t.name === rule.tagName);
|
||||||
|
return (
|
||||||
|
<div class="flex items-center justify-between p-2 rounded-lg bg-muted/10 border border-border/20 gap-3 opacity-70">
|
||||||
|
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||||
|
<Tag size={14} class="text-muted-foreground shrink-0" />
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="text-sm font-medium truncate flex items-center gap-2">
|
||||||
|
{/* If it's a user tag, show the person's name. If it's a bucket, show the bucket name (tagName). */}
|
||||||
|
{tagDef?.isUser ? getUserName(rule.targetUserId) : rule.tagName}
|
||||||
|
{tagDef?.isUser ? <Users size={10} class="text-muted-foreground" /> : <Archive size={10} class="text-muted-foreground" />}
|
||||||
|
</p>
|
||||||
|
<p class="text-[0.625rem] text-muted-foreground uppercase tracking-wider flex items-center gap-1">
|
||||||
|
Auto-shared via {tagDef?.isUser ? 'User' : 'Bucket'} Tag
|
||||||
|
<span class="mx-1">•</span>
|
||||||
|
<button
|
||||||
|
class="hover:text-primary transition-colors hover:underline"
|
||||||
|
onClick={() => updateShareRule(rule.id, {
|
||||||
|
share_mode: rule.share_mode === 'SEND_TASK' ? 'ADD_USER' : 'SEND_TASK'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -506,7 +506,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
<Box size={16} />
|
<Box size={16} />
|
||||||
Shared Buckets
|
Shared Buckets
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-muted-foreground">Each bucket is a real `@context` with a policy, metadata, soft delete, and personal pinning.</p>
|
<p class="text-sm text-muted-foreground">Buckets are shared spaces where tasks can be moved and collaborated on.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-muted/50 group-hover:bg-muted transition-colors">
|
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-muted/50 group-hover:bg-muted transition-colors">
|
||||||
{isBucketsOpen() ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
{isBucketsOpen() ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||||
|
|||||||
Reference in New Issue
Block a user