import { type Component, createMemo, createResource, For, Show } from "solid-js"; import { store, setNoteFilter, getFavoriteTag } from "@/store"; import { pb } from "@/lib/pocketbase"; import { Plus, Lock, Star } from "lucide-solid"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { NOTES_COLLECTION } from "@/lib/constants"; import { FilterBar } from "./FilterBar"; export const NotesSidebar: Component<{ selectedNoteId: string | null; setSelectedNoteId: (id: string | null) => void; }> = (props) => { const currentUserId = pb.authStore.model?.id; const [accessibleJobs] = createResource(currentUserId, async (userId) => { if (!userId) return new Set(); try { const items = await pb.collection('Job_Info_Prod').getFullList({ filter: [ `EstimatorUser = "${userId}"`, `OfficeRepUser = "${userId}"`, `ProjectManagerUser = "${userId}"`, `Estimator = "${userId}"`, `Office_Rep = "${userId}"`, `Project_Manager = "${userId}"` ].join(" || "), fields: "id", requestKey: null }); return new Set(items.map((r: any) => String(r.id))); } catch (e) { console.warn("Failed to load job access list for notes filter:", e); return new Set(); } }); const [jobFilterIds] = createResource( () => { const f = store.noteFilter; const normalize = (items: Array | undefined) => { if (!items) return [] as { name: string; excluded: boolean }[]; return items.map((i: any) => (typeof i === "string" ? { name: i, excluded: false } : { name: i.name, excluded: !!i.excluded })); }; const status = normalize(f.jobStatus as any).sort((a, b) => a.name.localeCompare(b.name)); const division = normalize(f.jobDivision as any).sort((a, b) => a.name.localeCompare(b.name)); const key = JSON.stringify({ status, division }); return key; }, async (key) => { if (!key) return null; const parsed = JSON.parse(key) as { status: Array<{ name: string; excluded: boolean }>; division: Array<{ name: string; excluded: boolean }> }; const status = parsed.status || []; const division = parsed.division || []; if (status.length === 0 && division.length === 0) return null; const parts: string[] = []; const statusInclude = status.filter(s => !s.excluded).map(s => s.name); const statusExclude = status.filter(s => s.excluded).map(s => s.name); const divisionInclude = division.filter(d => !d.excluded).map(d => d.name); const divisionExclude = division.filter(d => d.excluded).map(d => d.name); if (statusInclude.length > 0) { parts.push(`(${statusInclude.map(s => `Job_Status = "${s}"`).join(" || ")})`); } if (statusExclude.length > 0) { parts.push(`(${statusExclude.map(s => `Job_Status != "${s}"`).join(" && ")})`); } if (divisionInclude.length > 0) { parts.push(`(${divisionInclude.map(d => `Job_Division = "${d}"`).join(" || ")})`); } if (divisionExclude.length > 0) { parts.push(`(${divisionExclude.map(d => `Job_Division != "${d}"`).join(" && ")})`); } const filter = parts.join(" && "); try { const items = await pb.collection('Job_Info_Prod').getFullList({ filter, fields: "id", requestKey: null }); return new Set(items.map((r: any) => String(r.id))); } catch (e) { console.warn("Failed to load job filter list for notes:", e); return new Set(); } } ); const filteredNotes = createMemo(() => { const filter = store.noteFilter; let n = store.notes.filter(note => { if (note.deletedAt) return false; // Hide child notes from the main list if (note.tags && note.tags.some(t => t.startsWith("child-of-"))) return false; return true; }); // Query Filter if (filter.query) { const q = filter.query.toLowerCase(); n = n.filter(note => note.title.toLowerCase().includes(q) || note.tags.some(t => t.toLowerCase().includes(q)) ); } // Tags Filter if (filter.tags.length > 0) { const required = filter.tags.filter(t => !t.excluded).map(t => t.name); const excluded = filter.tags.filter(t => t.excluded).map(t => t.name); if (required.length > 0) { n = n.filter(note => required.some(r => note.tags.includes(r))); } if (excluded.length > 0) { n = n.filter(note => !excluded.some(e => note.tags.includes(e))); } } // Edited Today Filter if (filter.editedToday) { const today = new Date(); today.setHours(0, 0, 0, 0); n = n.filter(note => { const updated = new Date(note.updated); return updated >= today; }); } // Owned By Me Filter if (filter.ownedByMe) { const accessible = accessibleJobs(); n = n.filter(note => { if (note.tags?.includes("prism_job")) { if (!accessible) return false; const jobIdTag = note.tags.find(t => t.startsWith("job_id:")); const jobId = jobIdTag?.split(":")[1]?.trim(); if (!jobId) return false; return accessible.has(jobId); } return note.user === currentUserId; }); } // Job Status / Division Filter (notes only) if ((filter.jobStatus?.length || 0) > 0 || (filter.jobDivision?.length || 0) > 0) { const allowedJobIds = jobFilterIds(); n = n.filter(note => { if (!note.tags?.includes("prism_job")) return false; if (!allowedJobIds) return false; const jobIdTag = note.tags.find(t => t.startsWith("job_id:")); const jobId = jobIdTag?.split(":")[1]?.trim(); if (!jobId) return false; return allowedJobIds.has(jobId); }); } // Starred Filter if (filter.starred) { n = n.filter(note => note.tags?.includes(getFavoriteTag())); } return n.sort((a, b) => { const titleA = a.title || "Untitled"; const titleB = b.title || "Untitled"; return titleB.localeCompare(titleA); }); }); const handleCreateNote = async () => { if (!currentUserId) return; try { const result = await pb.collection(NOTES_COLLECTION).create({ title: "New Note", content: "", tags: [], isPrivate: false, user: currentUserId, }); props.setSelectedNoteId(result.id); } catch (e) { console.error("Failed to create note", e); } }; return (
{/* Header */}
setNoteFilter({ query: e.currentTarget.value })} class="w-full bg-muted/30 border border-border/50 rounded-xl px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/40 transition-all placeholder:text-muted-foreground/30" />
{/* List */}
No notes found.
}> {(note) => ( )}
); };