after the fact handoff and clear search
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { type Component, createEffect, createSignal, For, createMemo, onCleanup, Show } from "solid-js";
|
||||
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
||||
import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag, now, isCurrentUserContextTag, getVisibleTaskTags, isTaskLockedNoteTag, deriveTaskRelationsFromTags, getTaskShareModeForTag, getTaskSharePolicy } from "@/store";
|
||||
import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag, now, isCurrentUserContextTag, getVisibleTaskTags, isTaskLockedNoteTag, deriveTaskRelationsFromTags, getTaskShareModeForTag, getTaskSharePolicy, canHandoffCollaborativelySharedTask, handoffCollaborativelySharedTask } from "@/store";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy, MoreHorizontal, Gauge, Star, FileText } from "lucide-solid";
|
||||
@@ -732,6 +732,21 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={canHandoffCollaborativelySharedTask(props.task)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full h-8 text-[0.6875rem] justify-center"
|
||||
onClick={async () => {
|
||||
const didHandoff = await handoffCollaborativelySharedTask(props.task.id);
|
||||
if (didHandoff) {
|
||||
props.onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Handoff to collaborators
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ export const TaskSearchPopover: Component = () => {
|
||||
const [isSaving, setIsSaving] = createSignal(false);
|
||||
let triggerRef: HTMLDivElement | undefined;
|
||||
let panelRef: HTMLDivElement | undefined;
|
||||
let searchInputRef: HTMLInputElement | undefined;
|
||||
|
||||
const hasActiveFilters = () => {
|
||||
const f = store.filter;
|
||||
@@ -109,6 +110,7 @@ export const TaskSearchPopover: Component = () => {
|
||||
<Search size={16} class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/50 group-focus-within:text-primary transition-colors" />
|
||||
<input
|
||||
ref={(el) => {
|
||||
searchInputRef = el;
|
||||
if (el) setTimeout(() => el.focus(), 50);
|
||||
}}
|
||||
type="text"
|
||||
@@ -138,12 +140,16 @@ export const TaskSearchPopover: Component = () => {
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-10 w-10 rounded-xl shrink-0"
|
||||
title="Close search"
|
||||
onClick={closePanel}
|
||||
size="sm"
|
||||
class="h-10 rounded-xl shrink-0 px-3 text-xs font-bold uppercase tracking-wide"
|
||||
title="Clear search"
|
||||
disabled={!store.filter.query}
|
||||
onClick={() => {
|
||||
setFilter({ query: "" });
|
||||
searchInputRef?.focus();
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
+44
-1
@@ -1229,6 +1229,17 @@ export const isTaskCollaborativelyShared = (task: Task) =>
|
||||
return ref.kind === "user" && policy === "collaborative" && !!targetUserId && targetUserId !== currentUserId;
|
||||
});
|
||||
|
||||
export const canHandoffCollaborativelySharedTask = (task: Pick<Task, "shareRefs" | "ownerId">) => {
|
||||
const currentUserId = pb.authStore.model?.id;
|
||||
if (!currentUserId || !taskHasPersonalContextAccess(task, currentUserId)) return false;
|
||||
|
||||
return task.shareRefs.some(ref => {
|
||||
if (ref.kind !== "user" || getTaskSharePolicy(ref) !== "collaborative") return false;
|
||||
const targetUserId = getPersonalContextUserIdFromRef(ref);
|
||||
return !!targetUserId && targetUserId !== currentUserId;
|
||||
});
|
||||
};
|
||||
|
||||
export const isTaskCollapsedUntilUpdated = (task: Task) =>
|
||||
store.collapsedUntilUpdatedTasks[task.id] === task.updated;
|
||||
|
||||
@@ -1242,6 +1253,36 @@ export const expandCollapsedTask = (taskId: string) => {
|
||||
void syncPreferences();
|
||||
};
|
||||
|
||||
export const handoffCollaborativelySharedTask = async (taskId: string) => {
|
||||
const task = store.tasks.find(existing => existing.id === taskId);
|
||||
const currentUserId = pb.authStore.model?.id;
|
||||
const senderPersonalContext = currentUserId ? getPersonalContext(currentUserId) : null;
|
||||
|
||||
if (!task || !currentUserId || !senderPersonalContext) return false;
|
||||
if (!canHandoffCollaborativelySharedTask(task)) {
|
||||
toast.info("This task needs another collaborative @user before you can hand it off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextShareRefs = task.shareRefs.filter(ref => !(
|
||||
ref.kind === "user" &&
|
||||
(ref.contextId === senderPersonalContext.id || ref.key === senderPersonalContext.key)
|
||||
));
|
||||
|
||||
const didUpdate = await updateTask(taskId, {
|
||||
shareRefs: nextShareRefs,
|
||||
labelTags: task.labelTags,
|
||||
noteRefs: task.noteRefs,
|
||||
tags: buildTaskTags(task.labelTags, nextShareRefs, task.noteRefs, getFavoriteTag())
|
||||
});
|
||||
|
||||
if (didUpdate) {
|
||||
toast.success("Task handed off successfully.");
|
||||
}
|
||||
|
||||
return didUpdate;
|
||||
};
|
||||
|
||||
// Auto-persist changes to localStorage (wrapped in root to avoid console warning)
|
||||
createRoot(() => {
|
||||
createEffect(() => {
|
||||
@@ -2929,7 +2970,7 @@ const checkForHandoffs = (task: Task, updates: Partial<Task>): Partial<Task> =>
|
||||
};
|
||||
|
||||
export const updateTask = async (id: string, updates: Partial<Task>) => {
|
||||
if (!pb.authStore.isValid) return;
|
||||
if (!pb.authStore.isValid) return false;
|
||||
|
||||
// 0. Check for Hand-off Logic (Tag-based ownership transfer)
|
||||
const currentTask = store.tasks.find(t => t.id === id);
|
||||
@@ -3025,9 +3066,11 @@ export const updateTask = async (id: string, updates: Partial<Task>) => {
|
||||
|
||||
// Disable auto-cancellation for updates to prevent aborted errors during rapid typing
|
||||
await pb.collection(TASGRID_COLLECTION).update(id, pbUpdates, { requestKey: null });
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("update failed", err);
|
||||
toast.error("Failed to update task.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user