diff --git a/src/app/api/shared-transactions/route.ts b/src/app/api/shared-transactions/route.ts
index 52b9ccb..493ae81 100644
--- a/src/app/api/shared-transactions/route.ts
+++ b/src/app/api/shared-transactions/route.ts
@@ -7,7 +7,9 @@ export async function GET(req: NextRequest) {
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const tagParam = req.nextUrl.searchParams.get("tag_ids");
- const tagIds = tagParam ? tagParam.split(",").map(Number).filter(Boolean) : undefined;
- const transactions = await getSharedTransactions(user.id, tagIds);
+ const rawIds = tagParam ? tagParam.split(",").filter(Boolean) : [];
+ const noTags = rawIds.includes("untagged");
+ const tagIds = rawIds.filter((id) => id !== "untagged").map(Number).filter((n) => !isNaN(n));
+ const transactions = await getSharedTransactions(user.id, tagIds.length ? tagIds : undefined, noTags);
return NextResponse.json(transactions);
}
diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx
index c377b8d..d1039f3 100644
--- a/src/app/shared/page.tsx
+++ b/src/app/shared/page.tsx
@@ -40,12 +40,20 @@ function TagFilter({ value, onChange }: { value: string[]; onChange: (v: string[
return () => document.removeEventListener("mousedown", handler);
}, []);
- if (!tags.length) return null;
-
- const toggle = (id: string) =>
- onChange(value.includes(id) ? value.filter((x) => x !== id) : [...value, id]);
+ const toggle = (id: string) => {
+ let next: string[];
+ if (value.includes(id)) {
+ next = value.filter((x) => x !== id);
+ } else if (id === "untagged") {
+ next = ["untagged"];
+ } else {
+ next = [...value.filter((x) => x !== "untagged"), id];
+ }
+ onChange(next);
+ };
const label = value.length === 0 ? "All Tags"
+ : value.includes("untagged") ? "No tags"
: value.length === 1 ? (tags.find((t) => String(t.id) === value[0])?.name ?? "1 tag")
: `${value.length} tags`;
@@ -61,6 +69,11 @@ function TagFilter({ value, onChange }: { value: string[]; onChange: (v: string[
{open && (
+
{tags.map((t) => (