share modes partially implemented

This commit is contained in:
2026-02-17 13:23:25 -06:00
parent 01348ce229
commit e5c7666a2d
2 changed files with 240 additions and 67 deletions
+93 -23
View File
@@ -1,6 +1,6 @@
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, addShareRule, removeShareRule, createBucket, updateBucket, deleteBucket, toggleBucketSubscription } from "@/store";
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule, updateShareRule, createBucket, updateBucket, deleteBucket, toggleBucketSubscription } from "@/store";
import { useTheme } from "@/components/ThemeProvider";
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type, Share2, Users, Tag, Upload, Box, Edit2, Archive } from "lucide-solid";
import { TagPicker } from "@/components/TagPicker";
@@ -26,6 +26,7 @@ export const SettingsView: Component = () => {
const [expandedTemplateId, setExpandedTemplateId] = createSignal<string | null>(null);
const [shareUserId, setShareUserId] = createSignal("");
const [shareType, setShareType] = createSignal<'all' | 'tag'>('all');
const [shareMode, setShareMode] = createSignal<'ADD_USER' | 'SEND_TASK'>('ADD_USER');
const [shareTag, setShareTag] = createSignal<string>("");
const [allUsers, setAllUsers] = createSignal<Array<{ id: string; name: string }>>([]);
const [usersLoading, setUsersLoading] = createSignal(false);
@@ -128,10 +129,47 @@ export const SettingsView: Component = () => {
</Button>
</div>
{/* Share Mode Selection */}
<div class="flex gap-2">
<Button
variant={shareMode() === 'ADD_USER' ? 'default' : 'outline'}
size="sm"
class="h-7 text-[10px] gap-1 px-2"
title="Recipient gets edit access (Collaboration)"
onClick={() => setShareMode('ADD_USER')}
>
<Users size={10} />
Collaborate
</Button>
<Button
variant={shareMode() === 'SEND_TASK' ? 'default' : 'outline'}
size="sm"
class="h-7 text-[10px] gap-1 px-2"
title="Task ownership is transferred to recipient (Handoff)"
onClick={() => setShareMode('SEND_TASK')}
>
<ArrowLeftRight size={10} />
Handoff
</Button>
</div>
<Show when={shareType() === 'tag'}>
<Select
value={shareTag()}
onChange={(val) => val && setShareTag(val)}
onChange={(val) => {
if (val) {
setShareTag(val);
const tagDef = store.tagDefinitions.find(t => t.name === val);
const isBucket = tagDef?.isBucket || store.buckets.some(b => b.name === val);
const isUser = tagDef?.isUser;
if (isBucket) {
setShareMode('SEND_TASK');
} else if (isUser) {
setShareMode('ADD_USER');
}
}
}}
options={store.tagDefinitions.map(t => t.name)}
placeholder="Select a tag"
itemComponent={(props: any) => <SelectItem item={props.item}>{props.item.rawValue}</SelectItem>}
@@ -168,7 +206,7 @@ export const SettingsView: Component = () => {
disabled={!shareUserId()}
onClick={async () => {
if (shareUserId()) {
await addShareRule(shareType(), shareUserId(), shareType() === 'tag' ? shareTag() : undefined);
await addShareRule(shareType(), shareUserId(), shareType() === 'tag' ? shareTag() : undefined, shareMode());
setShareUserId("");
}
}}
@@ -177,6 +215,12 @@ export const SettingsView: Component = () => {
Share
</Button>
</div>
<p class="text-[10px] text-muted-foreground italic px-1 pt-1">
{shareMode() === 'ADD_USER'
? "User will be added to the task's shared list."
: "Task owner will change to the recipient immediately when tagged."}
</p>
</div>
{/* Active share rules */}
@@ -187,7 +231,9 @@ export const SettingsView: Component = () => {
Sharing With Others
</h4>
<For each={store.shareRules.filter(r => {
const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser;
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={
@@ -205,7 +251,15 @@ export const SettingsView: Component = () => {
<p class="text-xs font-bold truncate">To: {getUserName(rule.targetUserId)}</p>
<p class="text-[9px] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`}
<ArrowLeftRight size={8} class="rotate-45" />
<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>
@@ -229,7 +283,9 @@ export const SettingsView: Component = () => {
Shared With You
</h4>
<For each={store.shareRules.filter(r => {
const isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser;
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={
@@ -247,7 +303,8 @@ export const SettingsView: Component = () => {
<p class="text-xs font-bold truncate">From: {getUserName(rule.ownerId)}</p>
<p class="text-[9px] text-muted-foreground uppercase font-black tracking-widest opacity-60 flex items-center gap-1">
{rule.type === 'all' ? 'All Tasks' : `Tag: ${rule.tagName}`}
<ArrowLeftRight size={8} class="-rotate-45" />
<span class="mx-1">•</span>
{rule.share_mode === 'SEND_TASK' ? 'HANDOFF' : 'COLLAB'}
</p>
</div>
</div>
@@ -267,25 +324,38 @@ export const SettingsView: Component = () => {
</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 isSystem = r.type === 'tag' && store.tagDefinitions.find(t => t.name === r.tagName)?.isUser;
return isSystem;
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) => (
<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">
{getUserName(rule.targetUserId)}
<Users size={10} class="text-muted-foreground" />
</p>
<p class="text-[10px] text-muted-foreground uppercase tracking-wider">
Auto-shared via User Tag
</p>
{(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">
{getUserName(rule.targetUserId)}
{tagDef?.isUser ? <Users size={10} class="text-muted-foreground" /> : <Archive size={10} class="text-muted-foreground" />}
</p>
<p class="text-[10px] 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>
</div>
)}
);
}}
</For>
</div>
</details>