Skip to content

Commit 2c8145c

Browse files
authored
263 feature categories (#264)
* initial categories * working state left and right * update schema * working condifintal * working input * working * working v2 * working update * mismatch interface * done
1 parent a776c35 commit 2c8145c

23 files changed

+1459
-51
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use server";
2+
3+
import { prisma } from "@/lib/db";
4+
import { getCurrentUser } from "@/lib/session";
5+
6+
export async function calculateSuggestedBudget() {
7+
const user = await getCurrentUser();
8+
if (!user) {
9+
throw new Error("User not authenticated");
10+
}
11+
12+
const oneMonthAgo = new Date();
13+
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
14+
15+
try {
16+
const categorySums = await prisma.transaction.groupBy({
17+
by: ["categoryId"],
18+
where: {
19+
category: {
20+
userId: user.id,
21+
},
22+
date: {
23+
gte: oneMonthAgo,
24+
},
25+
},
26+
_sum: {
27+
amount: true,
28+
},
29+
});
30+
31+
const categories = await prisma.category.findMany({
32+
where: { userId: user.id },
33+
select: { id: true, name: true, icon: true },
34+
});
35+
36+
const suggestedBudget = categories.map((category) => {
37+
const sum = categorySums.find((sum) => sum.categoryId === category.id);
38+
return {
39+
id: category.id,
40+
name: category.name,
41+
icon: category.icon,
42+
suggestedAmount: Math.ceil(Math.abs(Number(sum?._sum?.amount ?? 0))),
43+
};
44+
});
45+
46+
return suggestedBudget;
47+
} catch (error) {
48+
console.error("Error calculating suggested budget:", error);
49+
throw new Error("Failed to calculate suggested budget");
50+
}
51+
}

apps/www/src/actions/create-budget.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use server";
2+
3+
import { revalidatePath } from "next/cache";
4+
5+
import { prisma } from "@/lib/db";
6+
import { getCurrentUser } from "@/lib/session";
7+
8+
interface BudgetData {
9+
budgetName: string;
10+
categories: Record<string, number>;
11+
}
12+
13+
export async function createBudget(data: BudgetData) {
14+
const user = await getCurrentUser();
15+
if (!user) {
16+
throw new Error("User not authenticated");
17+
}
18+
19+
const startDate = new Date();
20+
const endDate = new Date(startDate);
21+
endDate.setFullYear(endDate.getFullYear() + 5);
22+
23+
try {
24+
const budget = await prisma.budget.create({
25+
data: {
26+
name: data.budgetName,
27+
startDate,
28+
endDate,
29+
amount: Object.values(data.categories).reduce(
30+
(sum, amount) => sum + amount,
31+
0,
32+
),
33+
userId: user.id,
34+
categories: {
35+
create: Object.entries(data.categories).map(
36+
([categoryId, amount]) => ({
37+
amount,
38+
categoryId,
39+
}),
40+
),
41+
},
42+
},
43+
include: {
44+
categories: true,
45+
},
46+
});
47+
48+
// Revalidate the categories page to reflect the new budget
49+
revalidatePath("/dashboard/categories");
50+
51+
return { success: true, data: budget };
52+
} catch (error) {
53+
console.error("Error creating budget:", error);
54+
return { success: false, error: "Failed to create budget" };
55+
}
56+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use server";
2+
3+
import { prisma } from "@/lib/db";
4+
import { getCurrentUser } from "@/lib/session";
5+
6+
export async function getCategoriesReview() {
7+
const user = await getCurrentUser();
8+
if (!user) {
9+
throw new Error("User not authenticated");
10+
}
11+
12+
try {
13+
const categories = await prisma.category.findMany({
14+
where: {
15+
userId: user.id,
16+
},
17+
});
18+
return categories;
19+
} catch (error) {
20+
console.error("Error fetching categories:", error);
21+
throw new Error("Failed to fetch categories");
22+
}
23+
}
Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
// actions/get-categories.ts
22
"use server";
33

4+
import { Prisma } from "@prisma/client";
5+
46
import { prisma } from "@/lib/db";
57
import { getCurrentUser } from "@/lib/session";
68

7-
export async function getCategories() {
9+
export async function getUserCategories() {
810
const user = await getCurrentUser();
911
if (!user) {
1012
throw new Error("User not authenticated");
1113
}
1214

13-
try {
14-
const categories = await prisma.category.findMany({
15-
where: {
16-
userId: user.id,
15+
const categories = await prisma.category.findMany({
16+
where: {
17+
userId: user.id,
18+
},
19+
select: {
20+
id: true,
21+
name: true,
22+
icon: true,
23+
_count: {
24+
select: { transactions: true },
1725
},
18-
});
19-
return categories;
20-
} catch (error) {
21-
console.error("Error fetching categories:", error);
22-
throw new Error("Failed to fetch categories");
23-
}
26+
transactions: {
27+
select: {
28+
amount: true,
29+
},
30+
},
31+
},
32+
});
33+
34+
return categories.map((category) => ({
35+
id: category.id,
36+
name: category.name,
37+
icon: category.icon,
38+
spent: category.transactions.reduce(
39+
(sum, transaction) => sum + Number(transaction.amount.toString()),
40+
0,
41+
),
42+
budget: 0, // You'll need to add budget information to your schema and fetch it here
43+
_count: category._count,
44+
}));
2445
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use server";
2+
3+
import { prisma } from "@/lib/db";
4+
import { getCurrentUser } from "@/lib/session";
5+
6+
export async function getTransactionsForCategory(categoryId: string) {
7+
const user = await getCurrentUser();
8+
if (!user) {
9+
throw new Error("User not authenticated");
10+
}
11+
12+
const transactions = await prisma.transaction.findMany({
13+
where: {
14+
categoryId: categoryId,
15+
category: {
16+
userId: user.id,
17+
},
18+
},
19+
select: {
20+
id: true,
21+
date: true,
22+
description: true,
23+
amount: true,
24+
category: {
25+
select: {
26+
name: true,
27+
},
28+
},
29+
},
30+
orderBy: {
31+
date: "desc",
32+
},
33+
take: 10, // Limit to 10 most recent transactions
34+
});
35+
36+
return transactions.map((transaction) => ({
37+
id: transaction.id,
38+
date: transaction.date,
39+
description: transaction.description,
40+
category: transaction.category?.name,
41+
amount: Number(transaction.amount), // Convert Decimal to number
42+
}));
43+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use server";
2+
3+
import { prisma } from "@/lib/db";
4+
import { getCurrentUser } from "@/lib/session";
5+
6+
export async function getUserBudget() {
7+
const user = await getCurrentUser();
8+
if (!user) {
9+
throw new Error("User not authenticated");
10+
}
11+
12+
const currentDate = new Date();
13+
14+
// Fetch the most recent budget that includes the current date
15+
const budget = await prisma.budget.findFirst({
16+
where: {
17+
userId: user.id,
18+
startDate: { lte: currentDate },
19+
endDate: { gte: currentDate },
20+
},
21+
include: {
22+
categories: {
23+
include: {
24+
category: true,
25+
},
26+
},
27+
},
28+
orderBy: {
29+
startDate: "desc",
30+
},
31+
});
32+
33+
if (!budget) {
34+
return null; // Or you could return a default budget structure
35+
}
36+
37+
// Transform the data to match the structure expected by your components
38+
return {
39+
id: budget.id,
40+
name: budget.name,
41+
startDate: budget.startDate,
42+
endDate: budget.endDate,
43+
amount: Number(budget.amount),
44+
categories: budget.categories.map((cb) => ({
45+
id: cb.category.id,
46+
name: cb.category.name,
47+
icon: cb.category.icon,
48+
budget: Number(cb.amount),
49+
})),
50+
};
51+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { prisma } from "@/lib/db";
2+
import { getCurrentUser } from "@/lib/session";
3+
4+
export async function getUserCategories() {
5+
const user = await getCurrentUser();
6+
if (!user) {
7+
throw new Error("User not authenticated");
8+
}
9+
10+
// Fetch categories associated with the user
11+
const categories = await prisma.category.findMany({
12+
where: {
13+
userId: user.id,
14+
},
15+
select: {
16+
id: true,
17+
name: true,
18+
icon: true,
19+
_count: {
20+
select: { transactions: true },
21+
},
22+
},
23+
});
24+
25+
return categories;
26+
}

apps/www/src/actions/update-budget.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use server";
2+
3+
import { revalidatePath } from "next/cache";
4+
5+
import { prisma } from "@/lib/db";
6+
import { getCurrentUser } from "@/lib/session";
7+
8+
interface UpdateBudgetData {
9+
id: string;
10+
budgetName: string;
11+
categories: Record<string, number>;
12+
}
13+
14+
export async function updateBudget(data: UpdateBudgetData) {
15+
const user = await getCurrentUser();
16+
if (!user) {
17+
return { success: false, error: "User not authenticated" };
18+
}
19+
20+
try {
21+
const updatedBudget = await prisma.budget.update({
22+
where: { id: data.id, userId: user.id },
23+
data: {
24+
name: data.budgetName,
25+
categories: {
26+
upsert: Object.entries(data.categories).map(
27+
([categoryId, amount]) => ({
28+
where: { budgetId_categoryId: { budgetId: data.id, categoryId } },
29+
update: { amount },
30+
create: { categoryId, amount },
31+
}),
32+
),
33+
},
34+
},
35+
include: {
36+
categories: true,
37+
},
38+
});
39+
40+
revalidatePath("/dashboard/categories");
41+
42+
return { success: true, data: updatedBudget };
43+
} catch (error) {
44+
console.error("Error updating budget:", error);
45+
return { success: false, error: "Failed to update budget" };
46+
}
47+
}

0 commit comments

Comments
 (0)