From 7e4e9e981ddb1db2ab1e6ec3383b1632ffbd43d8 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Tue, 26 Aug 2025 20:21:15 +0300 Subject: [PATCH 01/17] style: use datepicker instead of date textfield --- client/src/components/IncomeForm.tsx | 20 +++++++---- client/src/pages/IncomesPage.tsx | 52 ++++++++++++++++------------ 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/client/src/components/IncomeForm.tsx b/client/src/components/IncomeForm.tsx index eac4750..ccdf662 100644 --- a/client/src/components/IncomeForm.tsx +++ b/client/src/components/IncomeForm.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import type { Income, IncomeFormData } from "../types/Income"; import { useIncomeCategories } from "../hooks/useIncomeCategories"; -import { Button, TextField, Select, Dialog, Skeleton } from "../ui"; +import { Button, TextField, Select, Dialog, Skeleton, DatePicker } from "../ui"; import { useMascot } from "../hooks/useMascot"; interface IncomeFormProps { @@ -77,6 +77,13 @@ export const IncomeForm: React.FC = ({ })); }; + const handleDateChange = (date: Date | null) => { + if (date) { + const formattedDate = date.toISOString().split("T")[0]; + handleChange("date", formattedDate); + } + }; + return ( = ({ fullWidth /> - handleChange("date", e.target.value)} - fullWidth + { navigate("/incomes/new"); }; + const handleStartDateChange = (date: Date | null) => { + setDateFilter((prev) => ({ + ...prev, + start: date ? date.toISOString().split("T")[0] : undefined, + })); + }; + + const handleEndDateChange = (date: Date | null) => { + setDateFilter((prev) => ({ + ...prev, + end: date ? date.toISOString().split("T")[0] : undefined, + })); + }; + return (
@@ -60,28 +74,20 @@ export const IncomesPage = () => {
- - setDateFilter((prev) => ({ - ...prev, - start: e.target.value || undefined, - })) - } - /> - - setDateFilter((prev) => ({ - ...prev, - end: e.target.value || undefined, - })) - } - /> + + + +
Date: Wed, 27 Aug 2025 18:57:09 +0300 Subject: [PATCH 02/17] refactor: fix prisma models to match openapi spec --- server/prisma/schema.prisma | 114 +++++++++++++++++------------------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 7f9dd94..af8e58a 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -14,79 +14,73 @@ datasource db { } model User { - user_id Int @id @default(autoincrement()) - username String @db.VarChar(50) - email String @unique @db.VarChar(255) - hashed_password String @db.VarChar(255) - firstname String @db.VarChar(100) - lastname String @db.VarChar(100) - created_at DateTime @default(now()) - updated_at DateTime @updatedAt - expenses Expense[] - incomes Income[] - expense_categories ExpenseCategory[] - income_categories IncomeCategory[] + id Int @id @default(autoincrement()) + email String @unique @db.VarChar(255) + password String @db.VarChar(255) + username String? @db.VarChar(50) + firstName String? @db.VarChar(100) + lastName String? @db.VarChar(100) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + expenses Expense[] + incomes Income[] + categories Category[] @@map("user") } -model ExpenseCategory { - category_id Int @id @default(autoincrement()) - category_name String @db.VarChar(50) - icon_url String? @db.VarChar(500) - is_custom Boolean @default(false) - user_id Int? - user User? @relation(fields: [user_id], references: [user_id], onDelete: Cascade) - expenses Expense[] - created_at DateTime @default(now()) - updated_at DateTime @updatedAt - - @@map("expense_category") -} +model Category { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + iconUrl String? @db.VarChar(500) + isCustom Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + userId Int? + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) -model IncomeCategory { - category_id Int @id @default(autoincrement()) - category_name String @db.VarChar(50) - icon_url String? @db.VarChar(500) - is_custom Boolean @default(false) - user_id Int? - user User? @relation(fields: [user_id], references: [user_id], onDelete: Cascade) - incomes Income[] - created_at DateTime @default(now()) - updated_at DateTime @updatedAt - - @@map("income_category") + expenses Expense[] + + @@map("category") } model Expense { - expense_id Int @id @default(autoincrement()) - amount Float - description String? @db.VarChar(2000) - type String @default("ONE_TIME") @db.VarChar(20) - receipt_upload String? @db.VarChar(2000) - creation_date DateTime @default(now()) - expense_date DateTime? - start_date DateTime? - end_date DateTime? - user_id Int - user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) - category_id Int - category ExpenseCategory @relation(fields: [category_id], references: [category_id], onDelete: Cascade) + id Int @id @default(autoincrement()) + amount Float + description String? @db.VarChar(2000) + type ExpenseType @default(ONE_TIME) + receiptUrl String? @db.VarChar(2000) + createdAt DateTime @default(now()) + expenseDate DateTime? + startDate DateTime? + endDate DateTime? + + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + categoryId Int + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) @@map("expense") } model Income { - income_id Int @id @default(autoincrement()) - amount Float - date DateTime @default(now()) - source String @db.VarChar(100) - description String? @db.VarChar(2000) - creation_date DateTime @default(now()) - user_id Int - user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) - category_id Int - category IncomeCategory @relation(fields: [category_id], references: [category_id], onDelete: Cascade) + id Int @id @default(autoincrement()) + amount Float + date DateTime @default(now()) + source String? @db.VarChar(100) + description String? @db.VarChar(2000) + createdAt DateTime @default(now()) + + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("income") } + +enum ExpenseType { + ONE_TIME + RECURRING +} From a2d4062a7362d2266adfce5e69404acfce5997c4 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 18:58:59 +0300 Subject: [PATCH 03/17] refactor: remove unnecessary endpoints (income categories) --- server/docs/Expense Tracker API.yaml | 92 ++++++++-------------------- 1 file changed, 25 insertions(+), 67 deletions(-) diff --git a/server/docs/Expense Tracker API.yaml b/server/docs/Expense Tracker API.yaml index c4dee71..d23ab7f 100644 --- a/server/docs/Expense Tracker API.yaml +++ b/server/docs/Expense Tracker API.yaml @@ -141,67 +141,15 @@ paths: description: Expense updated delete: summary: Delete an expense + parameters: + - in: path + name: id + required: true + schema: + type: string responses: "204": description: Expense deleted - /incomes/categories: - get: - summary: Get system income categories - responses: - "200": - description: List of system income categories - /incomes/custom-categories: - get: - summary: Get user's custom income categories - responses: - "200": - description: List of user's custom income categories - post: - summary: Create a new custom income category - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [category_name] - properties: - category_name: - type: string - icon_url: - type: string - responses: - "201": - description: Income category created - /incomes/custom-categories/{id}: - parameters: - - in: path - name: id - required: true - schema: - type: string - put: - summary: Update an income category - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [category_name] - properties: - category_name: - type: string - icon_url: - type: string - responses: - "200": - description: Income category updated - delete: - summary: Delete an income category - responses: - "204": - description: Income category deleted /incomes: get: summary: List all incomes @@ -224,7 +172,7 @@ paths: application/json: schema: type: object - required: [amount] + required: [amount, date] properties: amount: type: number @@ -234,8 +182,6 @@ paths: type: string description: type: string - category_id: - type: number responses: "201": description: Income created @@ -272,6 +218,12 @@ paths: description: Income updated delete: summary: Delete an income entry + parameters: + - in: path + name: id + required: true + schema: + type: string responses: "204": description: Income deleted @@ -296,14 +248,14 @@ paths: "201": description: Category created /categories/{id}: - parameters: - - in: path - name: id - required: true - schema: - type: string put: summary: Rename a category + parameters: + - in: path + name: id + required: true + schema: + type: string requestBody: content: application/json: @@ -317,6 +269,12 @@ paths: description: Category updated delete: summary: Delete a category + parameters: + - in: path + name: id + required: true + schema: + type: string responses: "204": description: Category deleted From 93473c2e7e733dca24d6969c28c8930439ec3fd1 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:03:01 +0300 Subject: [PATCH 04/17] refactor: remove income category from service layer --- client/src/api/services/DefaultService.ts | 89 ++------------- server/services/income.service.js | 125 ++-------------------- 2 files changed, 14 insertions(+), 200 deletions(-) diff --git a/client/src/api/services/DefaultService.ts b/client/src/api/services/DefaultService.ts index 22c9ff8..f3a11b9 100644 --- a/client/src/api/services/DefaultService.ts +++ b/client/src/api/services/DefaultService.ts @@ -145,98 +145,19 @@ export class DefaultService { /** * Delete an expense * @param id + * @param id * @returns void * @throws ApiError */ public static deleteExpenses( id: string, + id: string, ): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/expenses/{id}', path: { 'id': id, - }, - }); - } - /** - * Get system income categories - * @returns any List of system income categories - * @throws ApiError - */ - public static getIncomesCategories(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/incomes/categories', - }); - } - /** - * Get user's custom income categories - * @returns any List of user's custom income categories - * @throws ApiError - */ - public static getIncomesCustomCategories(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/incomes/custom-categories', - }); - } - /** - * Create a new custom income category - * @param requestBody - * @returns any Income category created - * @throws ApiError - */ - public static postIncomesCustomCategories( - requestBody: { - category_name: string; - icon_url?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/incomes/custom-categories', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Update an income category - * @param id - * @param requestBody - * @returns any Income category updated - * @throws ApiError - */ - public static putIncomesCustomCategories( - id: string, - requestBody: { - category_name: string; - icon_url?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/incomes/custom-categories/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Delete an income category - * @param id - * @returns void - * @throws ApiError - */ - public static deleteIncomesCustomCategories( - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/incomes/custom-categories/{id}', - path: { 'id': id, }, }); @@ -270,10 +191,9 @@ export class DefaultService { public static postIncomes( requestBody?: { amount: number; - date?: string; + date: string; source?: string; description?: string; - category_id?: number; }, ): CancelablePromise { return __request(OpenAPI, { @@ -329,17 +249,20 @@ export class DefaultService { /** * Delete an income entry * @param id + * @param id * @returns void * @throws ApiError */ public static deleteIncomes( id: string, + id: string, ): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/incomes/{id}', path: { 'id': id, + 'id': id, }, }); } diff --git a/server/services/income.service.js b/server/services/income.service.js index d285487..77e21f3 100644 --- a/server/services/income.service.js +++ b/server/services/income.service.js @@ -7,15 +7,14 @@ const createIncome = async (userId, data) => { return await prisma.income.create({ data: { ...data, - user: { connect: { user_id: userId } }, + user: { connect: { id: userId } }, }, - include: { category: true }, }); }; //GET ALL (with start and end date query params) const getIncomes = async (userId, filters = {}) => { - const where = { user_id: userId }; + const where = { userId }; if (filters.start || filters.end) { where.date = {}; @@ -25,26 +24,21 @@ const getIncomes = async (userId, filters = {}) => { return await prisma.income.findMany({ where, - include: { category: true }, - orderBy: [ - { date: "desc" }, - { creation_date: "desc" }, // might change { income_id: "desc" } if that doesnt work - ], + orderBy: [{ date: "desc" }, { createdAt: "desc" }], }); }; //GET BY ID const getIncomeById = async (id, userId) => { return await prisma.income.findFirst({ - where: { income_id: parseInt(id), user_id: userId }, - include: { category: true }, + where: { id: parseInt(id), userId }, }); }; //UPDATE const updateIncome = async (id, userId, data) => { const income = await prisma.income.findFirst({ - where: { income_id: parseInt(id), user_id: userId }, + where: { id: parseInt(id), userId }, }); if (!income) { @@ -52,16 +46,15 @@ const updateIncome = async (id, userId, data) => { } return await prisma.income.update({ - where: { income_id: parseInt(id) }, + where: { id: parseInt(id) }, data, - include: { category: true }, }); }; //DELETE const deleteIncome = async (id, userId) => { const income = await prisma.income.findFirst({ - where: { income_id: parseInt(id), user_id: userId }, + where: { id: parseInt(id), userId }, }); if (!income) { @@ -69,104 +62,7 @@ const deleteIncome = async (id, userId) => { } await prisma.income.delete({ - where: { income_id: parseInt(id) }, - }); -}; - -//---------------INCOME CATEGORY---------------// - -//GET ALL -const getIncomeCategories = async (userId) => { - return await prisma.incomeCategory.findMany({ - where: { - OR: [{ user_id: null }, { user_id: userId }], - }, - orderBy: [{ is_custom: "asc" }, { category_name: "asc" }], - }); -}; - -//GET ALL BY USER -const getIncomeCategoriesByUser = async (userId) => { - return await prisma.incomeCategory.findMany({ - where: { - user_id: userId, - }, - orderBy: [{ is_custom: "asc" }, { category_name: "asc" }], - }); -}; - -//POST -const createIncomeCategory = async (userId, data) => { - const existingCategory = await prisma.incomeCategory.findFirst({ - where: { - category_name: data.category_name, - user_id: userId, - }, - }); - - if (existingCategory) { - throw new Error("Category already exists"); - } - - return await prisma.incomeCategory.create({ - data: { - ...data, - is_custom: true, - user: { connect: { user_id: userId } }, - }, - }); -}; - -//UPDATE -const updateIncomeCategory = async (id, userId, data) => { - const category = await prisma.incomeCategory.findFirst({ - where: { category_id: parseInt(id), user_id: userId }, - }); - - if (!category) { - throw new Error("Category not found or not authorized"); - } - - if (data.category_name) { - const duplicateCategory = await prisma.incomeCategory.findFirst({ - where: { - category_name: data.category_name, - user_id: userId, - NOT: { category_id: parseInt(id) }, - }, - }); - - if (duplicateCategory) { - throw new Error("Category name already exists"); - } - } - - return await prisma.incomeCategory.update({ - where: { category_id: parseInt(id) }, - data, - }); -}; - -//DELETE -const deleteIncomeCategory = async (id, userId) => { - const category = await prisma.incomeCategory.findFirst({ - where: { category_id: parseInt(id), user_id: userId }, - }); - - if (!category) { - throw new Error("Category not found or not authorized"); - } - - const incomesUsingCategory = await prisma.income.count({ - where: { category_id: parseInt(id) }, - }); - - if (incomesUsingCategory > 0) { - throw new Error("Cannot delete category that is being used by incomes"); - } - - await prisma.incomeCategory.delete({ - where: { category_id: parseInt(id) }, + where: { id: parseInt(id) }, }); }; @@ -176,9 +72,4 @@ export default { getIncomeById, updateIncome, deleteIncome, - getIncomeCategories, - createIncomeCategory, - getIncomeCategoriesByUser, - updateIncomeCategory, - deleteIncomeCategory, }; From ed7ed6e2c7af6d06bf0930ebe34af78b8c08ab0a Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:10:41 +0300 Subject: [PATCH 05/17] refactor: update to use new field names (id) --- server/controllers/income.controller.js | 172 +----------------------- server/prisma/schema.prisma | 34 ++--- server/services/income.service.js | 24 ++-- 3 files changed, 35 insertions(+), 195 deletions(-) diff --git a/server/controllers/income.controller.js b/server/controllers/income.controller.js index 2226afa..3fe6310 100644 --- a/server/controllers/income.controller.js +++ b/server/controllers/income.controller.js @@ -4,7 +4,7 @@ import { asyncHandler } from "../utils/asyncHandler.js"; //---------------INCOME---------------// export const createIncome = asyncHandler(async (req, res) => { - const { amount, date, source, description, category_id } = req.body; + const { amount, date, source, description } = req.body; if (!amount) { return res.status(400).json({ error: "Missing required fields" }); @@ -16,12 +16,6 @@ export const createIncome = asyncHandler(async (req, res) => { description: description || "", }; - if (category_id) { - incomeData.category = { connect: { category_id: parseInt(category_id) } }; - } else { - incomeData.category = { connect: { category_id: 1 } }; - } - if (date) { const parsedDate = new Date(date); if (!isNaN(parsedDate.getTime())) { @@ -34,21 +28,10 @@ export const createIncome = asyncHandler(async (req, res) => { } try { - const income = await incomeService.createIncome( - req.user.user_id, - incomeData - ); + const income = await incomeService.createIncome(req.user.id, incomeData); res.status(201).json(income); } catch (error) { console.error("Error creating income:", error); - - if ( - error.message.includes("category") || - error.message.includes("Category") - ) { - return res.status(400).json({ error: "Invalid category specified" }); - } - res.status(500).json({ error: "Failed to create income" }); } }); @@ -76,7 +59,7 @@ export const getIncomes = asyncHandler(async (req, res) => { } try { - const incomes = await incomeService.getIncomes(req.user.user_id, filters); + const incomes = await incomeService.getIncomes(req.user.id, filters); res.json(incomes); } catch (error) { console.error("Error fetching incomes:", error); @@ -92,7 +75,7 @@ export const getIncome = asyncHandler(async (req, res) => { } try { - const income = await incomeService.getIncomeById(id, req.user.user_id); + const income = await incomeService.getIncomeById(id, req.user.id); if (!income) { return res.status(404).json({ error: "Income not found" }); @@ -129,25 +112,8 @@ export const updateIncome = asyncHandler(async (req, res) => { updates.date = parsedDate; } - if (updates.category_id !== undefined) { - if (updates.category_id === null || updates.category_id === "") { - updates.category = { connect: { category_id: 1 } }; - } else { - const categoryId = parseInt(updates.category_id); - if (isNaN(categoryId)) { - return res.status(400).json({ error: "Invalid category ID" }); - } - updates.category = { connect: { category_id: categoryId } }; - } - delete updates.category_id; - } - try { - const income = await incomeService.updateIncome( - id, - req.user.user_id, - updates - ); + const income = await incomeService.updateIncome(id, req.user.id, updates); res.json(income); } catch (error) { console.error("Error updating income:", error); @@ -161,13 +127,6 @@ export const updateIncome = asyncHandler(async (req, res) => { .json({ error: "Income not found or not authorized" }); } - if ( - error.message.includes("category") || - error.message.includes("Category") - ) { - return res.status(400).json({ error: "Invalid category specified" }); - } - res.status(500).json({ error: "Failed to update income" }); } }); @@ -180,7 +139,7 @@ export const deleteIncome = asyncHandler(async (req, res) => { } try { - await incomeService.deleteIncome(id, req.user.user_id); + await incomeService.deleteIncome(id, req.user.id); res.status(204).send(); } catch (error) { console.error("Error deleting income:", error); @@ -197,122 +156,3 @@ export const deleteIncome = asyncHandler(async (req, res) => { res.status(500).json({ error: "Failed to delete income" }); } }); - -//---------------INCOME CATEGORIES---------------// - -export const getIncomeCategories = asyncHandler(async (req, res) => { - try { - const categories = await incomeService.getIncomeCategories( - req.user.user_id - ); - res.json(categories); - } catch (error) { - console.error("Error fetching income categories:", error); - res.status(500).json({ error: "Failed to fetch income categories" }); - } -}); - -export const getIncomeCategoriesByUser = asyncHandler(async (req, res) => { - try { - const categories = await incomeService.getIncomeCategoriesByUser( - req.user.user_id - ); - res.json(categories); - } catch (error) { - console.error("Error fetching user income categories:", error); - res.status(500).json({ error: "Failed to fetch user income categories" }); - } -}); - -export const createIncomeCategory = asyncHandler(async (req, res) => { - try { - const { category_name, icon_url, icon_emoji } = req.body; - - if (!category_name) { - return res.status(400).json({ error: "Category name is required" }); - } - - const category = await incomeService.createIncomeCategory( - req.user.user_id, - { - category_name, - icon_url, - icon_emoji, - } - ); - res.status(201).json(category); - } catch (error) { - console.error("Error creating income category:", error); - - if (error.message.includes("already exists")) { - return res.status(409).json({ error: error.message }); - } - - res.status(500).json({ error: "Failed to create income category" }); - } -}); - -export const updateIncomeCategory = asyncHandler(async (req, res) => { - try { - const { id } = req.params; - const { category_name, icon_url, icon_emoji } = req.body; - - if (!category_name) { - return res.status(400).json({ error: "Category name is required" }); - } - - if (!id || isNaN(parseInt(id))) { - return res.status(400).json({ error: "Invalid category ID" }); - } - - const category = await incomeService.updateIncomeCategory( - id, - req.user.user_id, - { category_name, icon_url, icon_emoji } - ); - res.json(category); - } catch (error) { - console.error("Error updating income category:", error); - - if ( - error.message.includes("not found") || - error.message.includes("not authorized") - ) { - return res.status(404).json({ error: error.message }); - } - - if (error.message.includes("already exists")) { - return res.status(409).json({ error: error.message }); - } - - res.status(500).json({ error: "Failed to update income category" }); - } -}); - -export const deleteIncomeCategory = asyncHandler(async (req, res) => { - try { - const { id } = req.params; - - if (!id || isNaN(parseInt(id))) { - return res.status(400).json({ error: "Invalid category ID" }); - } - - await incomeService.deleteIncomeCategory(id, req.user.user_id); - res.status(204).send(); - } catch (error) { - console.error("Error deleting income category:", error); - - if ( - error.message.includes("not found") || - error.message.includes("not authorized") - ) { - return res.status(404).json({ error: error.message }); - } - - if (error.message.includes("being used")) { - return res.status(409).json({ error: error.message }); - } - - res.status(500).json({ error: "Failed to delete income category" }); - } -}); diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index af8e58a..6bcc51d 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -14,7 +14,7 @@ datasource db { } model User { - id Int @id @default(autoincrement()) + user_id Int @id @default(autoincrement()) @map("id") email String @unique @db.VarChar(255) password String @db.VarChar(255) username String? @db.VarChar(50) @@ -31,15 +31,15 @@ model User { } model Category { - id Int @id @default(autoincrement()) - name String @db.VarChar(50) - iconUrl String? @db.VarChar(500) - isCustom Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + category_id Int @id @default(autoincrement()) @map("id") + name String @db.VarChar(50) + iconUrl String? @db.VarChar(500) + isCustom Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - userId Int? - user User? @relation(fields: [userId], references: [id], onDelete: Cascade) + user_id Int? + user User? @relation(fields: [user_id], references: [user_id], onDelete: Cascade) expenses Expense[] @@ -47,7 +47,7 @@ model Category { } model Expense { - id Int @id @default(autoincrement()) + expense_id Int @id @default(autoincrement()) @map("id") amount Float description String? @db.VarChar(2000) type ExpenseType @default(ONE_TIME) @@ -57,25 +57,25 @@ model Expense { startDate DateTime? endDate DateTime? - userId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user_id Int + user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) - categoryId Int - category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + category_id Int + category Category @relation(fields: [category_id], references: [category_id], onDelete: Cascade) @@map("expense") } model Income { - id Int @id @default(autoincrement()) + income_id Int @id @default(autoincrement()) @map("id") amount Float date DateTime @default(now()) source String? @db.VarChar(100) description String? @db.VarChar(2000) createdAt DateTime @default(now()) - userId Int - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user_id Int + user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) @@map("income") } diff --git a/server/services/income.service.js b/server/services/income.service.js index 77e21f3..b353139 100644 --- a/server/services/income.service.js +++ b/server/services/income.service.js @@ -3,18 +3,18 @@ import { prisma } from "../db/prisma.js"; //---------------INCOME---------------// //POST -const createIncome = async (userId, data) => { +const createIncome = async (user_id, data) => { return await prisma.income.create({ data: { ...data, - user: { connect: { id: userId } }, + user: { connect: { user_id } }, }, }); }; //GET ALL (with start and end date query params) -const getIncomes = async (userId, filters = {}) => { - const where = { userId }; +const getIncomes = async (user_id, filters = {}) => { + const where = { user_id }; if (filters.start || filters.end) { where.date = {}; @@ -29,16 +29,16 @@ const getIncomes = async (userId, filters = {}) => { }; //GET BY ID -const getIncomeById = async (id, userId) => { +const getIncomeById = async (id, user_id) => { return await prisma.income.findFirst({ - where: { id: parseInt(id), userId }, + where: { income_id: parseInt(id), user_id }, }); }; //UPDATE -const updateIncome = async (id, userId, data) => { +const updateIncome = async (id, user_id, data) => { const income = await prisma.income.findFirst({ - where: { id: parseInt(id), userId }, + where: { income_id: parseInt(id), user_id }, }); if (!income) { @@ -46,15 +46,15 @@ const updateIncome = async (id, userId, data) => { } return await prisma.income.update({ - where: { id: parseInt(id) }, + where: { income_id: parseInt(id) }, data, }); }; //DELETE -const deleteIncome = async (id, userId) => { +const deleteIncome = async (id, user_id) => { const income = await prisma.income.findFirst({ - where: { id: parseInt(id), userId }, + where: { income_id: parseInt(id), user_id }, }); if (!income) { @@ -62,7 +62,7 @@ const deleteIncome = async (id, userId) => { } await prisma.income.delete({ - where: { id: parseInt(id) }, + where: { income_id: parseInt(id) }, }); }; From aeda3802d4b0d3d2ee108a19fd59c4ecc8c023c0 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:12:35 +0300 Subject: [PATCH 06/17] refactor: remove category references --- server/controllers/income.controller.js | 35 ++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/server/controllers/income.controller.js b/server/controllers/income.controller.js index 3fe6310..6b98842 100644 --- a/server/controllers/income.controller.js +++ b/server/controllers/income.controller.js @@ -6,8 +6,10 @@ import { asyncHandler } from "../utils/asyncHandler.js"; export const createIncome = asyncHandler(async (req, res) => { const { amount, date, source, description } = req.body; - if (!amount) { - return res.status(400).json({ error: "Missing required fields" }); + if (!amount || !date) { + return res + .status(400) + .json({ error: "Missing required fields: amount and date are required" }); } const incomeData = { @@ -16,19 +18,18 @@ export const createIncome = asyncHandler(async (req, res) => { description: description || "", }; - if (date) { - const parsedDate = new Date(date); - if (!isNaN(parsedDate.getTime())) { - incomeData.date = parsedDate; - } else { - return res.status(400).json({ error: "Invalid date format" }); - } + const parsedDate = new Date(date); + if (!isNaN(parsedDate.getTime())) { + incomeData.date = parsedDate; } else { - incomeData.date = new Date(); + return res.status(400).json({ error: "Invalid date format" }); } try { - const income = await incomeService.createIncome(req.user.id, incomeData); + const income = await incomeService.createIncome( + req.user.user_id, + incomeData + ); res.status(201).json(income); } catch (error) { console.error("Error creating income:", error); @@ -59,7 +60,7 @@ export const getIncomes = asyncHandler(async (req, res) => { } try { - const incomes = await incomeService.getIncomes(req.user.id, filters); + const incomes = await incomeService.getIncomes(req.user.user_id, filters); res.json(incomes); } catch (error) { console.error("Error fetching incomes:", error); @@ -75,7 +76,7 @@ export const getIncome = asyncHandler(async (req, res) => { } try { - const income = await incomeService.getIncomeById(id, req.user.id); + const income = await incomeService.getIncomeById(id, req.user.user_id); if (!income) { return res.status(404).json({ error: "Income not found" }); @@ -113,7 +114,11 @@ export const updateIncome = asyncHandler(async (req, res) => { } try { - const income = await incomeService.updateIncome(id, req.user.id, updates); + const income = await incomeService.updateIncome( + id, + req.user.user_id, + updates + ); res.json(income); } catch (error) { console.error("Error updating income:", error); @@ -139,7 +144,7 @@ export const deleteIncome = asyncHandler(async (req, res) => { } try { - await incomeService.deleteIncome(id, req.user.id); + await incomeService.deleteIncome(id, req.user.user_id); res.status(204).send(); } catch (error) { console.error("Error deleting income:", error); From 93fe19df75e8e4008b17011192fd2e14a792ffc2 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:13:28 +0300 Subject: [PATCH 07/17] refactor: simplify income routes by removing category endpoints --- server/routes/income.route.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/server/routes/income.route.js b/server/routes/income.route.js index 32e8cc8..9495e6c 100644 --- a/server/routes/income.route.js +++ b/server/routes/income.route.js @@ -1,22 +1,19 @@ -import { Router } from 'express'; -import { createIncome, getIncomes, getIncome, updateIncome, deleteIncome, getIncomeCategories, getIncomeCategoriesByUser,createIncomeCategory, updateIncomeCategory, deleteIncomeCategory } from '../controllers/income.controller.js'; +import { Router } from "express"; +import { + createIncome, + getIncomes, + getIncome, + updateIncome, + deleteIncome, +} from "../controllers/income.controller.js"; const router = Router(); -//INCOME CATEGORY -router.get('/categories', getIncomeCategories); -router.get('/custom-categories', getIncomeCategoriesByUser) -router.post('/custom-categories', createIncomeCategory); -router.put('/custom-categories/:id', updateIncomeCategory); -router.delete('/custom-categories/:id', deleteIncomeCategory); +// INCOME ROUTES +router.get("/", getIncomes); +router.get("/:id", getIncome); +router.post("/", createIncome); +router.put("/:id", updateIncome); +router.delete("/:id", deleteIncome); -//INCOME -router.get('/', getIncomes); -router.get('/:id', getIncome); -router.post('/', createIncome); -router.put('/:id', updateIncome); -router.delete('/:id', deleteIncome); - - - -export default router; \ No newline at end of file +export default router; From f19ddcddb9fe7719bf80b4a949e25cd441aa3396 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:21:20 +0300 Subject: [PATCH 08/17] refactor: remove income category from types --- client/src/api/services/DefaultService.ts | 782 +++++++++++----------- client/src/types/Income.ts | 17 +- 2 files changed, 378 insertions(+), 421 deletions(-) diff --git a/client/src/api/services/DefaultService.ts b/client/src/api/services/DefaultService.ts index f3a11b9..93bed92 100644 --- a/client/src/api/services/DefaultService.ts +++ b/client/src/api/services/DefaultService.ts @@ -2,413 +2,385 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { CancelablePromise } from "../core/CancelablePromise"; +import { OpenAPI } from "../core/OpenAPI"; +import { request as __request } from "../core/request"; export class DefaultService { - /** - * Register a new user - * @param requestBody - * @returns any User created - * @throws ApiError - */ - public static postAuthSignup( - requestBody: { - email: string; - password: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/auth/signup', - body: requestBody, - mediaType: 'application/json', - }); + /** + * Register a new user + * @param requestBody + * @returns any User created + * @throws ApiError + */ + public static postAuthSignup(requestBody: { + email: string; + password: string; + }): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/auth/signup", + body: requestBody, + mediaType: "application/json", + }); + } + /** + * Login and receive JWT token + * @param requestBody + * @returns any Token returned + * @throws ApiError + */ + public static postAuthLogin(requestBody: { + email: string; + password: string; + }): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/auth/login", + body: requestBody, + mediaType: "application/json", + }); + } + /** + * List all user expenses + * @param start Start date + * @param end End date + * @param category + * @param type + * @returns any List of expenses + * @throws ApiError + */ + public static getExpenses( + start?: string, + end?: string, + category?: string, + type?: "recurring" | "one-time" + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/expenses", + query: { + start: start, + end: end, + category: category, + type: type, + }, + }); + } + /** + * Create a new expense + * @param formData + * @returns any Expense created + * @throws ApiError + */ + public static postExpenses(formData?: { + amount: number; + date: string; + categoryId: string; + description?: string; + type?: "one-time" | "recurring"; + startDate?: string; + endDate?: string; + receipt?: Blob; + }): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/expenses", + formData: formData, + mediaType: "multipart/form-data", + }); + } + /** + * Get a single expense + * @param id + * @returns any Expense data + * @throws ApiError + */ + public static getExpenses1(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/expenses/{id}", + path: { + id: id, + }, + }); + } + /** + * Update an expense + * @param id + * @param formData + * @returns any Expense updated + * @throws ApiError + */ + public static putExpenses( + id: string, + formData?: { + amount?: number; + date?: string; + categoryId?: string; + description?: string; + type?: "one-time" | "recurring"; + startDate?: string; + endDate?: string; + receipt?: Blob; } - /** - * Login and receive JWT token - * @param requestBody - * @returns any Token returned - * @throws ApiError - */ - public static postAuthLogin( - requestBody: { - email: string; - password: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/auth/login', - body: requestBody, - mediaType: 'application/json', - }); + ): CancelablePromise { + return __request(OpenAPI, { + method: "PUT", + url: "/expenses/{id}", + path: { + id: id, + }, + formData: formData, + mediaType: "multipart/form-data", + }); + } + /** + * Delete an expense + * @param id + * @param id + * @returns void + * @throws ApiError + */ + public static deleteExpenses(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/expenses/{id}", + path: { + id: id, + }, + }); + } + /** + * List all incomes + * @param start + * @param end + * @returns any List of incomes + * @throws ApiError + */ + public static getIncomes( + start?: string, + end?: string + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/incomes", + query: { + start: start, + end: end, + }, + }); + } + /** + * Create new income + * @param requestBody + * @returns any Income created + * @throws ApiError + */ + public static postIncomes(requestBody?: { + amount: number; + date: string; + source?: string; + description?: string; + }): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/incomes", + body: requestBody, + mediaType: "application/json", + }); + } + /** + * Get an income entry + * @param id + * @returns any Income data + * @throws ApiError + */ + public static getIncomes1(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/incomes/{id}", + path: { + id: id, + }, + }); + } + /** + * Update an income entry + * @param id + * @param requestBody + * @returns any Income updated + * @throws ApiError + */ + public static putIncomes( + id: string, + requestBody?: { + amount?: number; + date?: string; + source?: string; + description?: string; } - /** - * List all user expenses - * @param start Start date - * @param end End date - * @param category - * @param type - * @returns any List of expenses - * @throws ApiError - */ - public static getExpenses( - start?: string, - end?: string, - category?: string, - type?: 'recurring' | 'one-time', - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/expenses', - query: { - 'start': start, - 'end': end, - 'category': category, - 'type': type, - }, - }); - } - /** - * Create a new expense - * @param formData - * @returns any Expense created - * @throws ApiError - */ - public static postExpenses( - formData?: { - amount: number; - date: string; - categoryId: string; - description?: string; - type?: 'one-time' | 'recurring'; - startDate?: string; - endDate?: string; - receipt?: Blob; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/expenses', - formData: formData, - mediaType: 'multipart/form-data', - }); - } - /** - * Get a single expense - * @param id - * @returns any Expense data - * @throws ApiError - */ - public static getExpenses1( - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/expenses/{id}', - path: { - 'id': id, - }, - }); - } - /** - * Update an expense - * @param id - * @param formData - * @returns any Expense updated - * @throws ApiError - */ - public static putExpenses( - id: string, - formData?: { - amount?: number; - date?: string; - categoryId?: string; - description?: string; - type?: 'one-time' | 'recurring'; - startDate?: string; - endDate?: string; - receipt?: Blob; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/expenses/{id}', - path: { - 'id': id, - }, - formData: formData, - mediaType: 'multipart/form-data', - }); - } - /** - * Delete an expense - * @param id - * @param id - * @returns void - * @throws ApiError - */ - public static deleteExpenses( - id: string, - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/expenses/{id}', - path: { - 'id': id, - 'id': id, - }, - }); - } - /** - * List all incomes - * @param start - * @param end - * @returns any List of incomes - * @throws ApiError - */ - public static getIncomes( - start?: string, - end?: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/incomes', - query: { - 'start': start, - 'end': end, - }, - }); - } - /** - * Create new income - * @param requestBody - * @returns any Income created - * @throws ApiError - */ - public static postIncomes( - requestBody?: { - amount: number; - date: string; - source?: string; - description?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/incomes', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Get an income entry - * @param id - * @returns any Income data - * @throws ApiError - */ - public static getIncomes1( - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/incomes/{id}', - path: { - 'id': id, - }, - }); - } - /** - * Update an income entry - * @param id - * @param requestBody - * @returns any Income updated - * @throws ApiError - */ - public static putIncomes( - id: string, - requestBody?: { - amount?: number; - date?: string; - source?: string; - description?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/incomes/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Delete an income entry - * @param id - * @param id - * @returns void - * @throws ApiError - */ - public static deleteIncomes( - id: string, - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/incomes/{id}', - path: { - 'id': id, - 'id': id, - }, - }); - } - /** - * List user categories - * @returns any List of categories - * @throws ApiError - */ - public static getCategories(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/categories', - }); - } - /** - * Create new category - * @param requestBody - * @returns any Category created - * @throws ApiError - */ - public static postCategories( - requestBody?: { - name: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/categories', - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Rename a category - * @param id - * @param requestBody - * @returns any Category updated - * @throws ApiError - */ - public static putCategories( - id: string, - requestBody?: { - name?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/categories/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - }); - } - /** - * Delete a category - * @param id - * @returns void - * @throws ApiError - */ - public static deleteCategories( - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/categories/{id}', - path: { - 'id': id, - }, - }); - } - /** - * Get current month summary - * @param month - * @returns any Monthly summary - * @throws ApiError - */ - public static getSummaryMonthly( - month?: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/summary/monthly', - query: { - 'month': month, - }, - }); - } - /** - * Get summary for custom range - * @param start - * @param end - * @returns any Summary - * @throws ApiError - */ - public static getSummary( - start?: string, - end?: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/summary', - query: { - 'start': start, - 'end': end, - }, - }); - } - /** - * Budget overrun alert - * @returns any Alert info - * @throws ApiError - */ - public static getSummaryAlerts(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/summary/alerts', - }); - } - /** - * Download/view receipt - * @param id - * @returns any Receipt file - * @throws ApiError - */ - public static getReceipts( - id: string, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/receipts/{id}', - path: { - 'id': id, - }, - }); - } - /** - * Get user profile - * @returns any Profile info - * @throws ApiError - */ - public static getUserProfile(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/user/profile', - }); + ): CancelablePromise { + return __request(OpenAPI, { + method: "PUT", + url: "/incomes/{id}", + path: { + id: id, + }, + body: requestBody, + mediaType: "application/json", + }); + } + /** + * Delete an income entry + * @param id + * @param id + * @returns void + * @throws ApiError + */ + public static deleteIncomes(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/incomes/{id}", + path: { + id: id, + }, + }); + } + /** + * List user categories + * @returns any List of categories + * @throws ApiError + */ + public static getCategories(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/categories", + }); + } + /** + * Create new category + * @param requestBody + * @returns any Category created + * @throws ApiError + */ + public static postCategories(requestBody?: { + name: string; + }): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/categories", + body: requestBody, + mediaType: "application/json", + }); + } + /** + * Rename a category + * @param id + * @param requestBody + * @returns any Category updated + * @throws ApiError + */ + public static putCategories( + id: string, + requestBody?: { + name?: string; } + ): CancelablePromise { + return __request(OpenAPI, { + method: "PUT", + url: "/categories/{id}", + path: { + id: id, + }, + body: requestBody, + mediaType: "application/json", + }); + } + /** + * Delete a category + * @param id + * @returns void + * @throws ApiError + */ + public static deleteCategories(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/categories/{id}", + path: { + id: id, + }, + }); + } + /** + * Get current month summary + * @param month + * @returns any Monthly summary + * @throws ApiError + */ + public static getSummaryMonthly(month?: string): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/summary/monthly", + query: { + month: month, + }, + }); + } + /** + * Get summary for custom range + * @param start + * @param end + * @returns any Summary + * @throws ApiError + */ + public static getSummary( + start?: string, + end?: string + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/summary", + query: { + start: start, + end: end, + }, + }); + } + /** + * Budget overrun alert + * @returns any Alert info + * @throws ApiError + */ + public static getSummaryAlerts(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/summary/alerts", + }); + } + /** + * Download/view receipt + * @param id + * @returns any Receipt file + * @throws ApiError + */ + public static getReceipts(id: string): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/receipts/{id}", + path: { + id: id, + }, + }); + } + /** + * Get user profile + * @returns any Profile info + * @throws ApiError + */ + public static getUserProfile(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/user/profile", + }); + } } diff --git a/client/src/types/Income.ts b/client/src/types/Income.ts index 1a11bb1..e9ca23f 100644 --- a/client/src/types/Income.ts +++ b/client/src/types/Income.ts @@ -6,8 +6,6 @@ export interface Income { description?: string; creation_date: string; user_id: number; - category_id: number; - category?: IncomeCategory; } export interface IncomeFormData { @@ -15,25 +13,13 @@ export interface IncomeFormData { date: string; source: string; description: string; - category_id: number; -} - -export interface IncomeCategory { - category_id: number; - category_name: string; - icon_url?: string; - is_custom: boolean; - user_id?: number; - created_at: string; - updated_at: string; } export interface CreateIncomeRequest { amount: number; - date?: string; + date: string; source: string; description?: string; - category_id: number; } export interface UpdateIncomeRequest { @@ -41,5 +27,4 @@ export interface UpdateIncomeRequest { date?: string; source?: string; description?: string; - category_id?: number; } From 4512003935ce08e7eac71a8d548e10c4592de9f4 Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:23:16 +0300 Subject: [PATCH 09/17] refactor: remove income category methods from services --- client/src/services/IncomeService.ts | 91 ---------------------------- 1 file changed, 91 deletions(-) diff --git a/client/src/services/IncomeService.ts b/client/src/services/IncomeService.ts index 30ed6c9..5dfc3bf 100644 --- a/client/src/services/IncomeService.ts +++ b/client/src/services/IncomeService.ts @@ -2,7 +2,6 @@ import { DefaultService } from "../api/services/DefaultService"; import { useMascotStore } from "../stores/mascotStore"; import type { Income, - IncomeCategory, CreateIncomeRequest, UpdateIncomeRequest, } from "../types/Income"; @@ -16,7 +15,6 @@ export class IncomeService { return response as Income[]; } catch (error) { useMascotStore.getState().setExpression("error"); - console.error("Error fetching incomes:", error); throw new Error("Failed to fetch incomes"); } @@ -27,11 +25,9 @@ export class IncomeService { try { const response = await DefaultService.getIncomes1(id); useMascotStore.getState().setExpression("success"); - return response as Income; } catch (error) { useMascotStore.getState().setExpression("error"); - console.error(`Error fetching income ${id}:`, error); throw new Error("Failed to fetch income"); } @@ -45,9 +41,6 @@ export class IncomeService { amount: Number(incomeData.amount), }; - if (incomeData.date) { - requestData.date = incomeData.date; - } const response = await DefaultService.postIncomes(requestData); useMascotStore.getState().setExpression("success"); return response as Income; @@ -68,11 +61,9 @@ export class IncomeService { const response = await DefaultService.putIncomes(id, requestData); useMascotStore.getState().setExpression("success"); - return response as Income; } catch (error) { useMascotStore.getState().setExpression("error"); - console.error(`Error updating income ${id}:`, error); throw new Error("Failed to update income"); } @@ -85,90 +76,8 @@ export class IncomeService { useMascotStore.getState().setExpression("success"); } catch (error) { useMascotStore.getState().setExpression("error"); - console.error(`Error deleting income ${id}:`, error); throw new Error("Failed to delete income"); } } - - //GET ALL income categories (custom and system) - static async getIncomeCategories() { - try { - const response = await DefaultService.getIncomesCategories(); - useMascotStore.getState().setExpression("success"); - - return response as IncomeCategory[]; - } catch (error) { - useMascotStore.getState().setExpression("error"); - - console.error("Error fetching income categories:", error); - throw new Error("Failed to fetch income categories"); - } - } - - //GET custom categories - static async getCustomIncomeCategories() { - try { - const response = await DefaultService.getIncomesCustomCategories(); - useMascotStore.getState().setExpression("success"); - - return response as IncomeCategory[]; - } catch (error) { - useMascotStore.getState().setExpression("error"); - - console.error("Error fetching custom income categories:", error); - throw new Error("Failed to fetch custom income categories"); - } - } - - //POST new category - static async createIncomeCategory(name: string) { - try { - const response = await DefaultService.postIncomesCustomCategories({ - category_name: name, - }); - useMascotStore.getState().setExpression("success"); - - return response as IncomeCategory; - } catch (error) { - useMascotStore.getState().setExpression("error"); - - console.error("Error creating income category:", error); - throw new Error("Failed to create income category"); - } - } - - //UPDATE a category - static async updateIncomeCategory( - id: string, - name: string - ): Promise { - try { - const response = await DefaultService.putIncomesCustomCategories(id, { - category_name: name, - }); - useMascotStore.getState().setExpression("success"); - - return response as IncomeCategory; - } catch (error) { - useMascotStore.getState().setExpression("error"); - - console.error(`Error updating income category ${id}:`, error); - throw new Error("Failed to update income category"); - } - } - - //DELETE category - static async deleteIncomeCategory(id: string) { - try { - useMascotStore.getState().setExpression("success"); - - await DefaultService.deleteIncomesCustomCategories(id); - } catch (error) { - useMascotStore.getState().setExpression("error"); - - console.error(`Error deleting income category ${id}:`, error); - throw new Error("Failed to delete income category"); - } - } } From 9434623e8b09efb959f101de421e2658b019b50a Mon Sep 17 00:00:00 2001 From: colombefioren Date: Wed, 27 Aug 2025 19:24:55 +0300 Subject: [PATCH 10/17] refactor: remove category field and set default date (because of the date required in backend) --- client/src/components/IncomeForm.tsx | 33 +++++----------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/client/src/components/IncomeForm.tsx b/client/src/components/IncomeForm.tsx index ccdf662..4eca75e 100644 --- a/client/src/components/IncomeForm.tsx +++ b/client/src/components/IncomeForm.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from "react"; import type { Income, IncomeFormData } from "../types/Income"; -import { useIncomeCategories } from "../hooks/useIncomeCategories"; -import { Button, TextField, Select, Dialog, Skeleton, DatePicker } from "../ui"; +import { Button, TextField, Dialog, DatePicker } from "../ui"; import { useMascot } from "../hooks/useMascot"; interface IncomeFormProps { @@ -17,7 +16,6 @@ export const IncomeForm: React.FC = ({ onCancel, open, }) => { - const { categories, loading: categoriesLoading } = useIncomeCategories(); const [saving, setSaving] = useState(false); const { showSuccess, showError } = useMascot(); @@ -28,7 +26,6 @@ export const IncomeForm: React.FC = ({ : new Date().toISOString().split("T")[0], source: income?.source || "", description: income?.description || "", - category_id: income?.category_id || 1, }); useEffect(() => { @@ -38,7 +35,6 @@ export const IncomeForm: React.FC = ({ date: new Date(income.date).toISOString().split("T")[0], source: income.source, description: income.description || "", - category_id: income.category_id, }); } else { setFormData({ @@ -46,7 +42,6 @@ export const IncomeForm: React.FC = ({ date: new Date().toISOString().split("T")[0], source: "", description: "", - category_id: 1, }); } }, [income, open]); @@ -72,8 +67,7 @@ export const IncomeForm: React.FC = ({ ) => { setFormData((prev) => ({ ...prev, - [field]: - field === "amount" || field === "category_id" ? Number(value) : value, + [field]: field === "amount" ? Number(value) : value, })); }; @@ -101,10 +95,11 @@ export const IncomeForm: React.FC = ({ /> = ({ fullWidth /> - {categoriesLoading ? ( - - ) : ( -
- -