added system user tags/sharing and workspaces for recieving share-all rules
This commit is contained in:
+140
-5
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user