diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx index d85027c..260bc79 100644 --- a/src/components/FilterBar.tsx +++ b/src/components/FilterBar.tsx @@ -1,6 +1,6 @@ import { type Component, createSignal, For, Show } from "solid-js"; import { Search, X, Tag, ArrowUpCircle, Clock, Minus, Save, Trash2, Bookmark } from "lucide-solid"; -import { store, setFilter, clearFilter, saveFilterTemplate, applyFilterTemplate, removeFilterTemplate, loadAllHistory } from "@/store"; +import { store, setFilter, clearFilter, saveFilterTemplate, applyFilterTemplate, removeFilterTemplate, loadAllHistory, setNoteFilter, clearNoteFilter, saveNoteFilterTemplate, applyNoteFilterTemplate, removeNoteFilterTemplate, syncPreferences, type Filter } from "@/store"; import { Button } from "./ui/button"; import { cn } from "@/lib/utils"; import { Badge } from "./ui/badge"; @@ -9,21 +9,34 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { useTheme } from "./ThemeProvider"; import { getThemeAdjustedColor } from "@/lib/colors"; -export const FilterBar: Component = () => { +export const FilterBar: Component<{ mode?: 'tasks' | 'notes', class?: string, triggerClass?: string }> = (props) => { const { resolvedTheme } = useTheme(); const [isOpen, setIsOpen] = createSignal(false); const [newTemplateName, setNewTemplateName] = createSignal(""); const [isSaving, setIsSaving] = createSignal(false); + const mode = () => props.mode || 'tasks'; + + const currentFilter = () => mode() === 'tasks' ? store.filter : store.noteFilter; + const currentSetFilter = (val: Partial) => mode() === 'tasks' ? setFilter(val) : setNoteFilter(val); + const currentClearFilter = () => mode() === 'tasks' ? clearFilter() : clearNoteFilter(); + const currentSaveTemplate = (name: string) => mode() === 'tasks' ? saveFilterTemplate(name) : saveNoteFilterTemplate(name); + const currentApplyTemplate = (id: string) => mode() === 'tasks' ? applyFilterTemplate(id) : applyNoteFilterTemplate(id); + const currentRemoveTemplate = (id: string) => mode() === 'tasks' ? removeFilterTemplate(id) : removeNoteFilterTemplate(id); + const hasActiveFilters = () => { - const f = store.filter; - return f.query !== "" || f.tags.length > 0 || f.priorityMin !== 1 || f.priorityMax !== 10 || f.urgencyMin !== 1 || f.urgencyMax !== 10 || f.editedToday; + const f = currentFilter(); + const baseActive = (f.query || "") !== "" || (f.tags || []).length > 0 || f.editedToday; + if (mode() === 'tasks') { + return baseActive || f.priorityMin !== 1 || f.priorityMax !== 10 || f.urgencyMin !== 1 || f.urgencyMax !== 10; + } + return baseActive; }; const allTags = () => store.tagDefinitions.map(d => d.name).sort(); return ( -
+
{ class={cn( "bg-background/80 backdrop-blur-sm border border-border hover:bg-muted shadow-sm transition-all duration-300", hasActiveFilters() && "border-primary/50 ring-1 ring-primary/20", - isOpen() && "bg-muted ring-1 ring-border" + isOpen() && "bg-muted ring-1 ring-border", + props.triggerClass )} title="Filters & Search" > @@ -43,7 +57,7 @@ export const FilterBar: Component = () => { )}
- + {/* Header */}
@@ -53,37 +67,39 @@ export const FilterBar: Component = () => {
- + + + @@ -110,9 +126,9 @@ export const FilterBar: Component = () => { if (el) setTimeout(() => el.focus(), 50); }} type="text" - placeholder="Search tasks, descriptions, tags..." - value={store.filter.query} - onInput={(e) => setFilter({ query: e.currentTarget.value })} + placeholder={mode() === 'tasks' ? "Search tasks, descriptions, tags..." : "Search notes, tags..."} + value={currentFilter().query} + onInput={(e) => currentSetFilter({ query: e.currentTarget.value })} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -124,77 +140,79 @@ export const FilterBar: Component = () => {
-
- {/* Priority Range */} -
- -
- -
- + +
+ {/* Priority Range */} +
+ +
+ +
+ +
-
- {/* Urgency Range */} -
- -
- -
- + {/* Urgency Range */} +
+ +
+ +
+ +
-
+ {/* Tags Multi-select */}
@@ -206,7 +224,7 @@ export const FilterBar: Component = () => {
- + {(tag) => ( {
{ - const newTags = store.filter.tags.map(t => + const newTags = currentFilter().tags.map(t => t.name === tag.name ? { ...t, excluded: !t.excluded } : t ); - setFilter({ tags: newTags }); + currentSetFilter({ tags: newTags }); }} > }> @@ -244,7 +262,7 @@ export const FilterBar: Component = () => { class="p-0.5 hover:bg-foreground/10 rounded transition-colors" onClick={(e) => { e.stopPropagation(); - setFilter({ tags: store.filter.tags.filter(t => t.name !== tag.name) }); + currentSetFilter({ tags: currentFilter().tags.filter(t => t.name !== tag.name) }); }} > @@ -255,8 +273,8 @@ export const FilterBar: Component = () => { setSearchQuery(e.currentTarget.value)} - class="w-full bg-muted/50 border border-border/50 rounded-lg pl-8 pr-3 py-1.5 text-xs outline-none focus:ring-1 focus:ring-primary/30" +
+
- -
- - -
-
- - -
-
- -
- No tags found}> - {(tag) => { - const isSelected = store.noteFilter.tags.includes(tag); - return ( - - ); - }} - -
-
-
-
{/* List */} diff --git a/src/store/index.ts b/src/store/index.ts index 6d25537..b6674e6 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -269,7 +269,7 @@ interface TaskStore { notes: Note[]; isNotepadMode: boolean; quickloadTasks: string[]; - noteFilter: { tags: string[]; ownedByMe: boolean }; + noteFilter: Filter; } // Initial empty state @@ -297,7 +297,15 @@ export const [store, setStore] = createStore({ notes: [], isNotepadMode: false, quickloadTasks: [], - noteFilter: { tags: [], ownedByMe: false } + noteFilter: { + query: "", + tags: [], + priorityMin: 1, + priorityMax: 10, + urgencyMin: 1, + urgencyMax: 10, + editedToday: false + } }); export const matchesFilter = (task: Task) => { @@ -1138,7 +1146,15 @@ export const initStore = async () => { prefId: userId, subscribedBuckets: user.subscribedBuckets || [], quickloadTasks: prefs.quickloadTasks || [], - noteFilter: prefs.noteFilter || { tags: [], ownedByMe: false } + noteFilter: prefs.noteFilter || { + query: "", + tags: [], + priorityMin: 1, + priorityMax: 10, + urgencyMin: 1, + urgencyMax: 10, + editedToday: false + } }); } } catch (prefErr) { @@ -2692,6 +2708,86 @@ export const clearFilter = () => { }); }; +export const setNoteFilter = (update: Partial) => { + setStore("noteFilter", (f) => ({ ...f, ...update })); +}; + +const NOTE_FILTER_TEMPLATES_COLLECTION = 'TasGrid_NoteFilterTemplates'; + +export const loadNoteFilterTemplates = async () => { + if (!pb.authStore.isValid) return; + try { + const records = await pb.collection(NOTE_FILTER_TEMPLATES_COLLECTION).getFullList({ + filter: `user = "${pb.authStore.model?.id}"`, + requestKey: null + }); + const templates: FilterTemplate[] = records.map(r => ({ + id: r.id, + name: r.name, + filter: r.filter as Filter + })); + setStore("filterTemplates", templates); + } catch (err) { + console.error("Failed to load note filter templates:", err); + } +}; + +export const saveNoteFilterTemplate = async (name: string) => { + if (!pb.authStore.isValid) return; + try { + const record = await pb.collection(NOTE_FILTER_TEMPLATES_COLLECTION).create({ + user: pb.authStore.model?.id, + name, + filter: store.noteFilter + }, { requestKey: null }); + + const newTemplate: FilterTemplate = { + id: record.id, + name: record.name, + filter: record.filter as Filter + }; + + setStore("filterTemplates", prev => [...prev, newTemplate]); + toast.success(`Note filter template "${name}" saved`); + } catch (err) { + console.error("Failed to save note filter template:", err); + toast.error("Failed to save note filter template."); + } +}; + +export const applyNoteFilterTemplate = (id: string) => { + const template = store.filterTemplates.find(t => t.id === id); + if (template) { + setNoteFilter(template.filter); + toast.success(`Applied note filter: ${template.name}`); + } +}; + +export const removeNoteFilterTemplate = async (id: string) => { + if (!pb.authStore.isValid) return; + try { + await pb.collection(NOTE_FILTER_TEMPLATES_COLLECTION).delete(id); + setStore("filterTemplates", prev => prev.filter(t => t.id !== id)); + toast.success("Note filter template deleted"); + } catch (err) { + console.error("Failed to delete note filter template:", err); + toast.error("Failed to delete note filter template."); + } +}; + +export const clearNoteFilter = () => { + setStore("noteFilter", { + query: "", + tags: [], + priorityMin: 1, + priorityMax: 10, + urgencyMin: 1, + urgencyMax: 10, + editedToday: false + }); + syncPreferences(); +}; + // Legacy cleanup - We don't need local storage persistence setup anymore export const setupPersistence = () => { // Moved logic to initStore which is called on auth. diff --git a/src/views/NotepadView.tsx b/src/views/NotepadView.tsx index 2e22180..8149d61 100644 --- a/src/views/NotepadView.tsx +++ b/src/views/NotepadView.tsx @@ -19,7 +19,7 @@ export const NotepadView: Component<{ }> = (props) => { const [isLinking, setIsLinking] = createSignal(false); const [linkSearchQuery, setLinkSearchQuery] = createSignal(""); - const [isLinkedTasksOpen, setIsLinkedTasksOpen] = createSignal(true); + const [isLinkedTasksOpen, setIsLinkedTasksOpen] = createSignal(false); const [isDragging, setIsDragging] = createSignal(false); const currentUserId = pb.authStore.model?.id; @@ -307,7 +307,7 @@ export const NotepadView: Component<{ {/* Note Editor (Main Panel) */}
@@ -382,10 +382,10 @@ export const NotepadView: Component<{
{/* Editor Area Container */} -
+
{/* Editor Area */} -
+