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
+140 -5
View File
@@ -10,7 +10,9 @@ const getStorageKey = () => {
};
export const [now, setNow] = createSignal(Date.now());
// Context: 'mine' = My Workspace (default), { userId: string } = Oversight View for that user
export const [activeTaskId, setActiveTaskId] = createSignal<string | null>(null);
export const [currentTaskContext, setCurrentTaskContext] = createSignal<'mine' | { userId: string, name: string }>('mine');
export type UrgencyLevel = number;
@@ -22,6 +24,7 @@ export interface Task {
priority: number; // 1-10
completed: boolean;
tags: string[];
ownerId: string; // Add ownerId to interface
content?: string; // HTML content from Tiptap
deletedAt?: number; // Timestamp of soft delete
created: string; // ISO string from PB
@@ -123,6 +126,7 @@ export interface TagDefinition {
value: number;
color?: string;
theme?: "light" | "dark";
isUser?: boolean; // New flag to identify user-based tags
}
export interface FilterTag {
@@ -181,6 +185,31 @@ export const [store, setStore] = createStore<TaskStore>({
});
export const matchesFilter = (task: Task) => {
const currentUserId = pb.authStore.model?.id;
const ctx = currentTaskContext();
// 0. Context Filter
if (ctx === 'mine') {
const isOwner = task.ownerId === currentUserId;
const isExplicitlyShared = task.sharedWith?.some(s => s.userId === currentUserId);
// Tag-based share rules targeting me
const isTagRuleShared = store.shareRules.some(r =>
r.targetUserId === currentUserId &&
r.ownerId === task.ownerId &&
r.type === 'tag' &&
task.tags?.includes(r.tagName || "")
);
// If context is "mine", we hide tasks that only match a "Share All" rule
// (i.e., not owner, not explicit, not tag rule)
if (!isOwner && !isExplicitlyShared && !isTagRuleShared) return false;
} else {
// Context is Oversight for specific user
// Show ONLY tasks owned by that user
if (task.ownerId !== ctx.userId) return false;
}
// Hide templates from all regular views
if (task.tags?.includes("__template__")) return false;
@@ -316,6 +345,7 @@ const mapRecordToTask = (r: any): Task => {
priority: r.priority,
completed: r.completed,
tags: tags,
ownerId: r.user,
content: r.content,
deletedAt: r.deletedAt ? new Date(r.deletedAt).getTime() : undefined,
created: r.created,
@@ -583,6 +613,9 @@ export const initStore = async () => {
console.warn("Failed to load tags:", tagErr);
}
// 1.8 Sync User Tags & Share Rules
await syncUserTagsAndRules();
// 2. Fetch Tasks & Templates + Shared Tasks in PARALLEL
const currentUserId = pb.authStore.model?.id;
@@ -692,9 +725,11 @@ export const initStore = async () => {
});
}
// Set store ONCE with all tasks
// Set store ONCE with all tasks and rules
setStore("tasks", reconcile(allTasks));
setStore("templates", loadedTemplates);
// Make sure we include incomingRules in the store so ContextSwitcher can see them
setStore("shareRules", incomingRules.map(mapRecordToShareRule));
// Check recurring tasks after load
checkRecurringTasks();
@@ -754,6 +789,7 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
priority,
completed: false,
tags,
ownerId: pb.authStore.model?.id || "",
content,
size,
created: new Date().toISOString(),
@@ -789,7 +825,8 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
...task,
id: record.id,
created: record.created,
updated: record.updated
updated: record.updated,
ownerId: record.user // Add missing ownerId from record
} : task);
});
} catch (err) {
@@ -1001,6 +1038,104 @@ export const removeTagDefinition = async (name: string) => {
}
};
export const syncUserTagsAndRules = async () => {
if (!pb.authStore.isValid) return;
const currentUserId = pb.authStore.model?.id;
try {
// 1. Fetch only verified users
const usersPromise = pb.collection('users').getFullList({
filter: 'verified = true',
fields: 'id,name,email,verified',
requestKey: null
});
// 2. Fetch existing rules DIRECTLY from PB to avoid store race conditions
const rulesPromise = pb.collection(SHARE_RULES_COLLECTION).getFullList({
filter: `ownerId = "${currentUserId}"`,
requestKey: null
});
const [users, pbRules] = await Promise.all([usersPromise, rulesPromise]);
// 2a. Map PB rules to check for duplicates
const existingTags = store.tagDefinitions;
const normalizedRules = pbRules.map(r => ({
id: r.id,
targetUserId: r.targetUserId,
type: r.type,
tagName: r.tagName
}));
for (const user of users) {
const userName = user.name || user.email;
if (!userName) continue;
// --- Tag Sync ---
const tagExists = existingTags.find(t => t.name === userName);
if (!tagExists) {
// ... create tag (logic unchanged) ...
try {
const record = await pb.collection(TAGS_COLLECTION).create({
user: currentUserId,
name: userName,
value: 5,
color: "#64748b",
theme: "dark"
});
setStore("tagDefinitions", prev => [...prev, {
id: record.id,
name: record.name,
value: record.value,
color: record.color,
theme: record.theme,
isUser: true
}]);
} catch (e) {
// console.error(`Failed to create user tag ${userName}`, e);
}
} else {
if (!tagExists.isUser) {
setStore("tagDefinitions", d => d.id === tagExists.id, { isUser: true });
}
}
// --- Share Rule Sync ---
if (user.id !== currentUserId) {
// Find ALL rules matching this criteria
const matchingRules = normalizedRules.filter(r =>
r.targetUserId === user.id &&
r.type === 'tag' &&
r.tagName === userName
);
if (matchingRules.length === 0) {
console.log(`Creating auto-share rule for ${userName}`);
await addShareRule('tag', user.id, userName);
} else if (matchingRules.length > 1) {
// DUPLICATE DETECTED: Keep first, delete others
console.warn(`Duplicate share rules found for ${userName}. Cleaning up...`);
const [, ...remove] = matchingRules;
for (const rule of remove) {
try {
await pb.collection(SHARE_RULES_COLLECTION).delete(rule.id);
// Update local store immediately to reflect deletion
setStore("shareRules", list => list.filter(r => r.id !== rule.id));
} catch (e) {
console.error("Failed to delete duplicate rule:", e);
}
}
}
}
}
} catch (err) {
console.error("Failed to sync user tags/rules:", err);
}
};
export const renameTagDefinition = async (oldName: string, newName: string) => {
if (!newName || !newName.trim() || newName === oldName) return;
if (!pb.authStore.isValid) return;
@@ -1227,8 +1362,9 @@ export const loadShareRules = async () => {
if (!pb.authStore.isValid) return;
try {
const currentUserId = pb.authStore.model?.id;
const records = await pb.collection(SHARE_RULES_COLLECTION).getFullList({
filter: `ownerId = "${pb.authStore.model?.id}"`
filter: `ownerId = "${currentUserId}" || targetUserId = "${currentUserId}"`
});
const rules: ShareRule[] = records.map(r => ({
@@ -1239,10 +1375,9 @@ export const loadShareRules = async () => {
tagName: r.tagName
}));
setStore("shareRules", rules);
setStore("shareRules", reconcile(rules));
} catch (err) {
console.error("Failed to load share rules:", err);
// Collection might not exist yet, that's okay
}
};