diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..5e63467 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +go.sum +backend diff --git a/backend/api/GetAllCategories.go b/backend/api/GetAllCategories.go new file mode 100644 index 0000000..b26bd0e --- /dev/null +++ b/backend/api/GetAllCategories.go @@ -0,0 +1,17 @@ +package api + +import ( + "backend/internal/category" + + "github.com/gofiber/fiber/v2" +) + +func GetAllCategories(c *fiber.Ctx) error { + categories, err := category.GetAllCategories() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.JSON(categories) +} diff --git a/backend/api/GetAllOperation.go b/backend/api/GetAllOperation.go new file mode 100644 index 0000000..8e03bc8 --- /dev/null +++ b/backend/api/GetAllOperation.go @@ -0,0 +1,17 @@ +package api + +import ( + "backend/internal/operation" + + "github.com/gofiber/fiber/v2" +) + +func GetAllOperation(c *fiber.Ctx) error { + operations, err := operation.GetAllOperation() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.JSON(operations) +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..a2b0a9e --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,24 @@ +module backend + +go 1.24.5 + +require ( + github.com/gofiber/fiber v1.14.6 + github.com/gofiber/fiber/v2 v2.52.9 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/utils v0.0.10 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/schema v1.1.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/backend/internal/category/GetAllCategories.go b/backend/internal/category/GetAllCategories.go new file mode 100644 index 0000000..1d5127b --- /dev/null +++ b/backend/internal/category/GetAllCategories.go @@ -0,0 +1,33 @@ +package category + +import "backend/model" + +func GetAllCategories() ([]model.Category, error) { + return []model.Category{ + { + ID: "1", + Title: "Electronics", + Icon: "😀", + }, + { + ID: "2", + Title: "Clothing", + Icon: "😁", + }, + { + ID: "3", + Title: "Books", + Icon: "😂", + Subcategories: []model.Subcategory{ + { + ID: "1", + Title: "Fiction", + }, + { + ID: "2", + Title: "Non-Fiction", + }, + }, + }, + }, nil +} diff --git a/backend/internal/operation/GetAllOperation.go b/backend/internal/operation/GetAllOperation.go new file mode 100644 index 0000000..423d68e --- /dev/null +++ b/backend/internal/operation/GetAllOperation.go @@ -0,0 +1,30 @@ +package operation + +import "backend/model" + +func GetAllOperation() ([]model.Operation, error) { + return []model.Operation{ + { + ID: "1", + Title: "Operation 1", + Amount: 100, + Category: "Category 1", + SubCategory: model.Subcategory{ + ID: "1", + Title: "subcategory", + }, + Date: "2022-01-01T00:00:00Z", + Type: "income", + Icon: "😀", + }, + { + ID: "2", + Title: "Operation 2", + Amount: 200, + Category: "Category 2", + Date: "2022-01-02T00:00:00Z", + Type: "expense", + Icon: "😂", + }, + }, nil +} diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..0bfabba --- /dev/null +++ b/backend/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "backend/server" + + "github.com/gofiber/fiber/v2" +) + +func main() { + app := fiber.New() + + server.Configure(app) + + app.Listen(":3000") +} diff --git a/backend/model/category.go b/backend/model/category.go new file mode 100644 index 0000000..d468b0d --- /dev/null +++ b/backend/model/category.go @@ -0,0 +1,8 @@ +package model + +type Category struct { + ID string `json:"id"` + Icon string `json:"icon"` + Title string `json:"title"` + Subcategories []Subcategory `json:"subcategories"` +} diff --git a/backend/model/operation.go b/backend/model/operation.go new file mode 100644 index 0000000..1e0e4c5 --- /dev/null +++ b/backend/model/operation.go @@ -0,0 +1,13 @@ +package model + +type Operation struct { + ID string `json:"id"` + Title string `json:"title"` + Amount float64 `json:"amount"` + Category string `json:"category"` + SubCategory Subcategory `json:"subcategory,omitzero"` + Note string `json:"note,omitempty"` + Icon string `json:"icon"` + Type string `json:"type"` + Date string `json:"date"` +} diff --git a/backend/model/subcategory.go b/backend/model/subcategory.go new file mode 100644 index 0000000..73bf613 --- /dev/null +++ b/backend/model/subcategory.go @@ -0,0 +1,6 @@ +package model + +type Subcategory struct { + ID string `json:"id"` + Title string `json:"title"` +} diff --git a/backend/server/configure.go b/backend/server/configure.go new file mode 100644 index 0000000..ab5c4c7 --- /dev/null +++ b/backend/server/configure.go @@ -0,0 +1,18 @@ +package server + +import ( + "backend/api" + + "github.com/gofiber/fiber/v2" +) + +func Configure(app *fiber.App) { + app.Static("/", "../frontend/build") + apiGroup := app.Group("/api") + + operationGroup := apiGroup.Group("/operation") + operationGroup.Get("/", api.GetAllOperation) + + categoryGroup := apiGroup.Group("/category") + categoryGroup.Get("/", api.GetAllCategories) +} diff --git a/.gitignore b/frontend/.gitignore similarity index 100% rename from .gitignore rename to frontend/.gitignore diff --git a/README.md b/frontend/README.md similarity index 100% rename from README.md rename to frontend/README.md diff --git a/package.json b/frontend/package.json similarity index 97% rename from package.json rename to frontend/package.json index 6e6c745..600a6ba 100644 --- a/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.11.0", "bootstrap": "^5.3.7", "chart.js": "^4.5.0", "classnames": "^2.5.1", diff --git a/public/favicon.ico b/frontend/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to frontend/public/favicon.ico diff --git a/public/index.html b/frontend/public/index.html similarity index 100% rename from public/index.html rename to frontend/public/index.html diff --git a/public/logo192.png b/frontend/public/logo192.png similarity index 100% rename from public/logo192.png rename to frontend/public/logo192.png diff --git a/public/logo512.png b/frontend/public/logo512.png similarity index 100% rename from public/logo512.png rename to frontend/public/logo512.png diff --git a/public/manifest.json b/frontend/public/manifest.json similarity index 100% rename from public/manifest.json rename to frontend/public/manifest.json diff --git a/public/robots.txt b/frontend/public/robots.txt similarity index 100% rename from public/robots.txt rename to frontend/public/robots.txt diff --git a/src/App.jsx b/frontend/src/App.jsx similarity index 100% rename from src/App.jsx rename to frontend/src/App.jsx diff --git a/src/App.test.js b/frontend/src/App.test.js similarity index 100% rename from src/App.test.js rename to frontend/src/App.test.js diff --git a/frontend/src/category/GetAllCategories.js b/frontend/src/category/GetAllCategories.js new file mode 100644 index 0000000..b58a726 --- /dev/null +++ b/frontend/src/category/GetAllCategories.js @@ -0,0 +1,14 @@ +import axios from "axios"; +import routes from "../routes"; + +const fetchCategories = async () => { + try { + const response = await axios.get(routes.categories); + return response.data; + } catch (error) { + console.error("Error fetching categories:", error); + return []; + } +}; + +export default fetchCategories; diff --git a/src/components/AppointmentDashboard/AppointmentDashboard.jsx b/frontend/src/components/AppointmentDashboard/AppointmentDashboard.jsx similarity index 100% rename from src/components/AppointmentDashboard/AppointmentDashboard.jsx rename to frontend/src/components/AppointmentDashboard/AppointmentDashboard.jsx diff --git a/src/components/BalanceBlock/BalanceBlock.jsx b/frontend/src/components/BalanceBlock/BalanceBlock.jsx similarity index 100% rename from src/components/BalanceBlock/BalanceBlock.jsx rename to frontend/src/components/BalanceBlock/BalanceBlock.jsx diff --git a/src/components/CategorySign/CategorySign.jsx b/frontend/src/components/CategorySign/CategorySign.jsx similarity index 98% rename from src/components/CategorySign/CategorySign.jsx rename to frontend/src/components/CategorySign/CategorySign.jsx index 85b37d0..3cc82d2 100644 --- a/src/components/CategorySign/CategorySign.jsx +++ b/frontend/src/components/CategorySign/CategorySign.jsx @@ -55,7 +55,7 @@ const CategorySign = ({ icon, title, subcategories }) => { style={{ borderRadius: "25px", padding: "0 15px" }} key={subcategory.id} > - {subcategory.name} + {subcategory.title} ))} diff --git a/src/components/CategorySign/CategorySign.module.css b/frontend/src/components/CategorySign/CategorySign.module.css similarity index 100% rename from src/components/CategorySign/CategorySign.module.css rename to frontend/src/components/CategorySign/CategorySign.module.css diff --git a/src/components/EmojiPickerButton/EmojiPickerButton.jsx b/frontend/src/components/EmojiPickerButton/EmojiPickerButton.jsx similarity index 100% rename from src/components/EmojiPickerButton/EmojiPickerButton.jsx rename to frontend/src/components/EmojiPickerButton/EmojiPickerButton.jsx diff --git a/src/components/FinancialDashboard/FinancialDashboard.jsx b/frontend/src/components/FinancialDashboard/FinancialDashboard.jsx similarity index 100% rename from src/components/FinancialDashboard/FinancialDashboard.jsx rename to frontend/src/components/FinancialDashboard/FinancialDashboard.jsx diff --git a/src/components/FinancialDashboard/FinancialDashboard.module.css b/frontend/src/components/FinancialDashboard/FinancialDashboard.module.css similarity index 100% rename from src/components/FinancialDashboard/FinancialDashboard.module.css rename to frontend/src/components/FinancialDashboard/FinancialDashboard.module.css diff --git a/src/components/NavigationBar/NavigationBar.jsx b/frontend/src/components/NavigationBar/NavigationBar.jsx similarity index 100% rename from src/components/NavigationBar/NavigationBar.jsx rename to frontend/src/components/NavigationBar/NavigationBar.jsx diff --git a/src/components/NavigationBar/NavigationBar.module.css b/frontend/src/components/NavigationBar/NavigationBar.module.css similarity index 100% rename from src/components/NavigationBar/NavigationBar.module.css rename to frontend/src/components/NavigationBar/NavigationBar.module.css diff --git a/frontend/src/components/OperationSign/OperationSign.jsx b/frontend/src/components/OperationSign/OperationSign.jsx new file mode 100644 index 0000000..20ab8a2 --- /dev/null +++ b/frontend/src/components/OperationSign/OperationSign.jsx @@ -0,0 +1,60 @@ +import getDateString from "../../logic/getDateString"; +import { Button } from "react-bootstrap"; +import classNames from "classnames"; +import styles from "./OperationSign.module.css"; + +const OperationSign = ({ operation }) => { + return ( +
+
+
+
😀
+
+
+

+ {operation.type === "income" ? "" : "-"} + {operation.amount} ₽ +

+
+ {operation.category} +
+ {!operation.subcategory ? ( + "" + ) : ( +
+ {operation.subcategory.title} +
+ )} +
+ {operation.note && operation.note !== "" + ? operation.note + : "No note"} +
+
+
+
+ {getDateString(operation.date)} +
+
+
+
+ ); +}; + +export default OperationSign; diff --git a/frontend/src/components/OperationSign/OperationSign.module.css b/frontend/src/components/OperationSign/OperationSign.module.css new file mode 100644 index 0000000..f199a05 --- /dev/null +++ b/frontend/src/components/OperationSign/OperationSign.module.css @@ -0,0 +1,7 @@ +.hideScrollbar { + scrollbar-width: none; + -ms-overflow-style: none; +} +.hideScrollbar::-webkit-scrollbar { + display: none; +} diff --git a/src/components/ThemeToggle.jsx b/frontend/src/components/ThemeToggle.jsx similarity index 100% rename from src/components/ThemeToggle.jsx rename to frontend/src/components/ThemeToggle.jsx diff --git a/src/contexts/ThemeContext.js b/frontend/src/contexts/ThemeContext.js similarity index 100% rename from src/contexts/ThemeContext.js rename to frontend/src/contexts/ThemeContext.js diff --git a/src/css/App.css b/frontend/src/css/App.css similarity index 100% rename from src/css/App.css rename to frontend/src/css/App.css diff --git a/src/css/index.css b/frontend/src/css/index.css similarity index 100% rename from src/css/index.css rename to frontend/src/css/index.css diff --git a/src/index.js b/frontend/src/index.js similarity index 100% rename from src/index.js rename to frontend/src/index.js diff --git a/frontend/src/logic/getDateString.js b/frontend/src/logic/getDateString.js new file mode 100644 index 0000000..31c2238 --- /dev/null +++ b/frontend/src/logic/getDateString.js @@ -0,0 +1,8 @@ +const getDateString = (date) => { + const dateData = new Date(date); + return [ + dateData.getHours().toString().padStart(2, "0"), + dateData.getMinutes().toString().padStart(2, "0"), + ].join(":"); +}; +export default getDateString; diff --git a/src/modals/AddCategoryModal/AddCategoryModal.jsx b/frontend/src/modals/AddCategoryModal/AddCategoryModal.jsx similarity index 100% rename from src/modals/AddCategoryModal/AddCategoryModal.jsx rename to frontend/src/modals/AddCategoryModal/AddCategoryModal.jsx diff --git a/src/modals/AddOperationModal/AddOperationModal.jsx b/frontend/src/modals/AddOperationModal/AddOperationModal.jsx similarity index 100% rename from src/modals/AddOperationModal/AddOperationModal.jsx rename to frontend/src/modals/AddOperationModal/AddOperationModal.jsx diff --git a/src/modals/AnalyticsModal/AnalyticsModal.jsx b/frontend/src/modals/AnalyticsModal/AnalyticsModal.jsx similarity index 100% rename from src/modals/AnalyticsModal/AnalyticsModal.jsx rename to frontend/src/modals/AnalyticsModal/AnalyticsModal.jsx diff --git a/src/modals/AnalyticsModal/AnalyticsModal.module.css b/frontend/src/modals/AnalyticsModal/AnalyticsModal.module.css similarity index 100% rename from src/modals/AnalyticsModal/AnalyticsModal.module.css rename to frontend/src/modals/AnalyticsModal/AnalyticsModal.module.css diff --git a/src/modals/CategoriesModal/CategoriesModal.jsx b/frontend/src/modals/CategoriesModal/CategoriesModal.jsx similarity index 70% rename from src/modals/CategoriesModal/CategoriesModal.jsx rename to frontend/src/modals/CategoriesModal/CategoriesModal.jsx index 2934435..9cbf164 100644 --- a/src/modals/CategoriesModal/CategoriesModal.jsx +++ b/frontend/src/modals/CategoriesModal/CategoriesModal.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState, useMemo, useCallback } from "react"; import { useTheme } from "../../contexts/ThemeContext"; import { Modal, @@ -13,12 +13,31 @@ import { import styles from "./CategoriesModal.module.css"; import CategorySign from "../../components/CategorySign/CategorySign"; import AddCategoryModal from "../../modals/AddCategoryModal/AddCategoryModal"; +import fetchCategories from "../../category/GetAllCategories"; const CategoriesModal = ({ variant }) => { + let [categories, setCategories] = useState([]); const { theme } = useTheme(); const [show, setShow] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); const handleClose = () => setShow(false); const handleShow = () => setShow(true); + useEffect(() => { + fetchCategories().then((data) => { + setCategories(data); + console.log(data); + }); + }, []); + const filteredCategories = useMemo(() => { + if (!searchTerm) return categories; + const term = searchTerm.toLowerCase(); + return categories.filter((category) => + category.title.toLowerCase().includes(term), + ); + }, [categories, searchTerm]); + const handleSearch = useCallback((e) => { + setSearchTerm(e.target.value); + }, []); return ( <>