diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx
index 2736568..85e88f5 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, Hash, ArrowUpCircle, Clock, Minus } from "lucide-solid";
-import { store, setFilter, clearFilter } from "@/store";
+import { Search, X, Hash, ArrowUpCircle, Clock, Minus, Save, Trash2, Bookmark } from "lucide-solid";
+import { store, setFilter, clearFilter, saveFilterTemplate, applyFilterTemplate, removeFilterTemplate } from "@/store";
import { Button } from "./ui/button";
import { cn } from "@/lib/utils";
import { Badge } from "./ui/badge";
@@ -12,6 +12,8 @@ import { getThemeAdjustedColor } from "@/lib/colors";
export const FilterBar: Component = () => {
const { resolvedTheme } = useTheme();
const [isOpen, setIsOpen] = createSignal(false);
+ const [newTemplateName, setNewTemplateName] = createSignal("");
+ const [isSaving, setIsSaving] = createSignal(false);
const hasActiveFilters = () => {
const f = store.filter;
@@ -251,6 +253,109 @@ export const FilterBar: Component = () => {
+
+ {/* Filter Templates Section */}
+
+
+
+
+
+
+
+
+
+
+ setNewTemplateName(e.currentTarget.value)}
+ class="flex-1 bg-muted/30 border border-primary/20 rounded-lg px-3 py-1.5 text-xs outline-none focus:ring-1 focus:ring-primary/30"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ if (newTemplateName().trim()) {
+ saveFilterTemplate(newTemplateName().trim());
+ setNewTemplateName("");
+ setIsSaving(false);
+ }
+ } else if (e.key === 'Escape') {
+ setIsSaving(false);
+ }
+ }}
+ />
+
+
+
+
+
+
+
+ No saved filters yet
+
+ }>
+ {(template) => (
+
+
+
+
+ )}
+
+
+
diff --git a/src/store/index.ts b/src/store/index.ts
index 69dc6d5..e6ab007 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -144,6 +144,13 @@ export interface Filter {
editedToday: boolean;
}
+export interface FilterTemplate {
+ id: string;
+ name: string;
+ filter: Filter;
+}
+
+
export interface ShareRule {
id: string;
ownerId: string;
@@ -162,6 +169,7 @@ interface TaskStore {
templates: TaskTemplate[];
filter: Filter;
shareRules: ShareRule[];
+ filterTemplates: FilterTemplate[];
}
// Initial empty state
@@ -181,7 +189,8 @@ export const [store, setStore] = createStore({
urgencyMax: 10,
editedToday: false
},
- shareRules: []
+ shareRules: [],
+ filterTemplates: []
});
export const matchesFilter = (task: Task) => {
@@ -737,6 +746,9 @@ export const initStore = async () => {
// 2.7. Fetch Share Rules (owned by current user)
await loadShareRules();
+ // 2.8 Fetch Filter Templates
+ await loadFilterTemplates();
+
// 3. Subscribe to Realtime Updates
await subscribeToRealtime();
@@ -1412,6 +1424,70 @@ export const removeShareRule = async (ruleId: string) => {
}
};
+// -- Filter Templates --
+const FILTER_TEMPLATES_COLLECTION = 'TasGrid_FilterTemplates';
+
+export const loadFilterTemplates = async () => {
+ if (!pb.authStore.isValid) return;
+ try {
+ const records = await pb.collection(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 filter templates:", err);
+ }
+};
+
+export const saveFilterTemplate = async (name: string) => {
+ if (!pb.authStore.isValid) return;
+ try {
+ const record = await pb.collection(FILTER_TEMPLATES_COLLECTION).create({
+ user: pb.authStore.model?.id,
+ name,
+ filter: store.filter
+ }, { requestKey: null });
+
+ const newTemplate: FilterTemplate = {
+ id: record.id,
+ name: record.name,
+ filter: record.filter as Filter
+ };
+
+ setStore("filterTemplates", prev => [...prev, newTemplate]);
+ toast.success(`Filter template "${name}" saved`);
+ } catch (err) {
+ console.error("Failed to save filter template:", err);
+ toast.error("Failed to save filter template.");
+ }
+};
+
+export const applyFilterTemplate = (id: string) => {
+ const template = store.filterTemplates.find(t => t.id === id);
+ if (template) {
+ setFilter(template.filter);
+ toast.success(`Applied filter: ${template.name}`);
+ }
+};
+
+export const removeFilterTemplate = async (id: string) => {
+ if (!pb.authStore.isValid) return;
+ try {
+ await pb.collection(FILTER_TEMPLATES_COLLECTION).delete(id);
+ setStore("filterTemplates", prev => prev.filter(t => t.id !== id));
+ toast.success("Filter template deleted");
+ } catch (err) {
+ console.error("Failed to delete filter template:", err);
+ toast.error("Failed to delete filter template.");
+ }
+};
+
export const clearFilter = () => {
setStore("filter", {
query: "",