From e4968af7a539ff18ad67ea3d888fe07b0439c41d Mon Sep 17 00:00:00 2001 From: Francisco Castillo Date: Tue, 24 Jun 2025 13:49:12 -0600 Subject: [PATCH 1/9] feat: added proper global metrics --- .../inventory/backend/config/WebConfig.java | 16 ++++++++++++++ frontend/package.json | 10 --------- frontend/src/pages/ProductListPage.tsx | 22 +++++++++++++++++++ frontend/src/services/productService.ts | 2 +- 4 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 backend/src/main/java/com/inventory/backend/config/WebConfig.java diff --git a/backend/src/main/java/com/inventory/backend/config/WebConfig.java b/backend/src/main/java/com/inventory/backend/config/WebConfig.java new file mode 100644 index 0000000..e0145f0 --- /dev/null +++ b/backend/src/main/java/com/inventory/backend/config/WebConfig.java @@ -0,0 +1,16 @@ +package com.inventory.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.*; + +@Configuration +public class WebConfig implements WebMvcConfigurer{ + @Override + public void addCorsMappings(CorsRegistry registry){ + registry.addMapping("/**") + .allowedOrigins("http://localhost:8080") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/frontend/package.json b/frontend/package.json index 4db3087..b5a1825 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,13 +12,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.7", "@types/react-dom": "^19.1.6", -<<<<<<< HEAD - "autoprefixer": "^10.4.21", - "axios": "^1.10.0", - "postcss": "^8.5.6", -======= "axios": "^1.10.0", ->>>>>>> fullstack "react": "^19.1.0", "react-dom": "^19.1.0", "react-scripts": "5.0.1", @@ -65,12 +59,8 @@ ] }, "devDependencies": { -<<<<<<< HEAD - "@types/uuid": "^10.0.0" -======= "autoprefixer": "^10.4.21", "postcss": "^8.5.6", "tailwindcss": "^3.4.17" ->>>>>>> fullstack } } diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index e488842..9c153ae 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -90,6 +90,28 @@ const ProductListPage: React.FC = () => {

Total in stock: {metrics.totalInStock}

Total Value: {metrics.totalValue.toFixed(2)}

Average price: ${metrics.averagePrice.toFixed(2)}

+ +

Per Category

+ + + + + + + + + + + {Object.entries(metrics.byCategory).map(([category, data]) => ( + + + + + + + ))} + +
CategoryStockTotal ValueAverage price
{category}{data.inStock}${data.totalValue.toFixed(2)}${data.averagePrice.toFixed(2)}
)} diff --git a/frontend/src/services/productService.ts b/frontend/src/services/productService.ts index 18c9c77..00e9bbd 100644 --- a/frontend/src/services/productService.ts +++ b/frontend/src/services/productService.ts @@ -25,7 +25,7 @@ export const markOutOfStock = async (id: string): Promise => { }; export const markInStock = async (id: string, quantity: number = 10): Promise => { - await api.post(`/products/${id}/instock?defaultQuantity=${quantity}`); + await api.put(`/products/${id}/instock?defaultQuantity=${quantity}`); }; export const getMetrics = async (): Promise => { From 15372e20a7a223d896219a62642d7b1ff11f5f14 Mon Sep 17 00:00:00 2001 From: Francisco Castillo Date: Tue, 24 Jun 2025 13:52:44 -0600 Subject: [PATCH 2/9] fix: fixed package-lock --- frontend/package-lock.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 729f7f9..27a9a86 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,13 +16,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.7", "@types/react-dom": "^19.1.6", -<<<<<<< HEAD - "autoprefixer": "^10.4.21", - "axios": "^1.10.0", - "postcss": "^8.5.6", -======= "axios": "^1.10.0", ->>>>>>> fullstack "react": "^19.1.0", "react-dom": "^19.1.0", "react-scripts": "5.0.1", @@ -31,13 +25,9 @@ "web-vitals": "^2.1.4" }, "devDependencies": { -<<<<<<< HEAD - "@types/uuid": "^10.0.0" -======= "autoprefixer": "^10.4.21", "postcss": "^8.5.6", "tailwindcss": "^3.4.17" ->>>>>>> fullstack } }, "node_modules/@adobe/css-tools": { From 54e74a3a4ac78a88832829ea8b36c3a0c0c8ab15 Mon Sep 17 00:00:00 2001 From: Francisco Castillo Date: Tue, 24 Jun 2025 13:59:04 -0600 Subject: [PATCH 3/9] fix: missing import --- frontend/package-lock.json | 107 ++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 27a9a86..5fe6a7a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3939,13 +3939,6 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -14090,55 +14083,6 @@ } } }, - "node_modules/react-scripts/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/react-scripts/node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.6", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16005,10 +15949,53 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", - "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", - "license": "MIT" + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } }, "node_modules/tapable": { "version": "2.2.2", From 6f6f629b21a8f844cfe1419e78de72a4f4587213 Mon Sep 17 00:00:00 2001 From: Francisco Castillo Date: Wed, 25 Jun 2025 14:12:07 -0600 Subject: [PATCH 4/9] fix: fixed categories filter --- README.md | 2 +- .../backend/controller/ProductController.java | 6 ++-- .../backend/repository/ProductRepository.java | 4 +-- .../backend/service/ProductService.java | 4 +-- frontend/src/components/ProductFilters.tsx | 30 +++++++------------ frontend/src/pages/ProductListPage.tsx | 2 +- 6 files changed, 19 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ecb708d..64c5097 100644 --- a/README.md +++ b/README.md @@ -94,5 +94,5 @@ To run all back-end unit and integration tests (full code coverage): ``` 2. Run the tests: ```bash - + mvn test ``` diff --git a/backend/src/main/java/com/inventory/backend/controller/ProductController.java b/backend/src/main/java/com/inventory/backend/controller/ProductController.java index bd4f766..f1a8b82 100644 --- a/backend/src/main/java/com/inventory/backend/controller/ProductController.java +++ b/backend/src/main/java/com/inventory/backend/controller/ProductController.java @@ -24,7 +24,7 @@ public ProductController(ProductService service) { @GetMapping public ProductPage getProducts( @RequestParam Optional name, - @RequestParam Optional> category, + @RequestParam Optional category, @RequestParam Optional availability, @RequestParam Optional sortBy, @RequestParam Optional sortBy2, @@ -33,10 +33,10 @@ public ProductPage getProducts( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ){ - Set categorySet = category.map(HashSet::new).orElse(null); + //Set categorySet = category.map(HashSet::new).orElse(null); return service.getFilteredProductsPage( name, - Optional.ofNullable(categorySet), + category, availability, sortBy, sortBy2, diff --git a/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java b/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java index d61cd93..8ac8a14 100644 --- a/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java +++ b/backend/src/main/java/com/inventory/backend/repository/ProductRepository.java @@ -37,12 +37,12 @@ public void clear() { public List findByNameOrCategoryOrAvailability( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability ) { return products.values().stream() .filter(p -> nameFilter.map(name -> p.getName().toLowerCase().contains(name.toLowerCase())).orElse(true)) - .filter(p -> categoryFilter.map(categories -> categories.contains(p.getCategory())).orElse(true)) + .filter(p -> categoryFilter.filter(cat -> !cat.isBlank()).map(cat -> cat.equalsIgnoreCase(p.getCategory())).orElse(true)) .filter(p -> availability.map(avail -> avail == p.isInStock()).orElse(true)) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/inventory/backend/service/ProductService.java b/backend/src/main/java/com/inventory/backend/service/ProductService.java index 23ac353..04e8d1c 100644 --- a/backend/src/main/java/com/inventory/backend/service/ProductService.java +++ b/backend/src/main/java/com/inventory/backend/service/ProductService.java @@ -21,7 +21,7 @@ public ProductService(ProductRepository repository) { public List getFilteredAndSortedProducts( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability, Optional sortBy, Optional sortBy2, @@ -45,7 +45,7 @@ public List getFilteredAndSortedProducts( } public ProductPage getFilteredProductsPage( Optional nameFilter, - Optional> categoryFilter, + Optional categoryFilter, Optional availability, Optional sortBy, Optional sortBy2, diff --git a/frontend/src/components/ProductFilters.tsx b/frontend/src/components/ProductFilters.tsx index 86ffb28..f2ea5a1 100644 --- a/frontend/src/components/ProductFilters.tsx +++ b/frontend/src/components/ProductFilters.tsx @@ -8,14 +8,14 @@ interface Props { const ProductFilters: React.FC = ({filters, setFilters}) => { const [name, setName] = useState(filters.name || ""); - const [category, setCategory] = useState(filters.category || []); + const [category, setCategory] = useState(filters.category || ""); const [availability, setAvailability] = useState(filters.availability || ""); const [allCategories, setAllCategories] = useState ([]); useEffect(() => { getCategories().then(setAllCategories).catch(err => { - console.error("Error getting categories"); + console.error("Error getting categories", err); setAllCategories([]); }); }, []); @@ -24,19 +24,12 @@ const ProductFilters: React.FC = ({filters, setFilters}) => { setFilters((prevFilters: any) =>({ ...prevFilters, name, - category, + category: category === "" ? "" : category, availability: availability === "" ? "" : availability === "in", page: 0, })); }, [setFilters, name, category, availability]); - const toggleCategory = (value: string) => { - setCategory(prev => - prev.includes(value) - ? prev.filter(c => c !== value) - : [...prev, value] - ); - }; useEffect(() => { handleApply(); @@ -55,17 +48,14 @@ const ProductFilters: React.FC = ({filters, setFilters}) => {
-
- {allCategories.map(cat => ( - +
diff --git a/frontend/src/pages/ProductListPage.tsx b/frontend/src/pages/ProductListPage.tsx index 9c153ae..14a2b26 100644 --- a/frontend/src/pages/ProductListPage.tsx +++ b/frontend/src/pages/ProductListPage.tsx @@ -88,7 +88,7 @@ const ProductListPage: React.FC = () => {

General Metrics

Total in stock: {metrics.totalInStock}

-

Total Value: {metrics.totalValue.toFixed(2)}

+

Total Value: ${metrics.totalValue.toFixed(2)}

Average price: ${metrics.averagePrice.toFixed(2)}

Per Category

From 4e61df58f013a3d908615889828eb9b335542c10 Mon Sep 17 00:00:00 2001 From: Francisco Castillo Date: Wed, 25 Jun 2025 14:34:30 -0600 Subject: [PATCH 5/9] feat: added categories context --- frontend/src/components/ProductFilters.tsx | 11 ++---- frontend/src/components/ProductFormModal.tsx | 4 +++ frontend/src/components/ProductTable.tsx | 11 +++--- frontend/src/context/CategoryContext.tsx | 37 ++++++++++++++++++++ frontend/src/index.tsx | 5 ++- 5 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 frontend/src/context/CategoryContext.tsx diff --git a/frontend/src/components/ProductFilters.tsx b/frontend/src/components/ProductFilters.tsx index f2ea5a1..2010cad 100644 --- a/frontend/src/components/ProductFilters.tsx +++ b/frontend/src/components/ProductFilters.tsx @@ -1,5 +1,5 @@ import React, {useState, useEffect, useCallback} from "react"; -import { getCategories } from "../services/productService"; +import { useCategoryContext } from "../context/CategoryContext"; interface Props { filters: any; @@ -11,14 +11,7 @@ const ProductFilters: React.FC = ({filters, setFilters}) => { const [category, setCategory] = useState(filters.category || ""); const [availability, setAvailability] = useState(filters.availability || ""); - const [allCategories, setAllCategories] = useState ([]); - - useEffect(() => { - getCategories().then(setAllCategories).catch(err => { - console.error("Error getting categories", err); - setAllCategories([]); - }); - }, []); + const {categories: allCategories} = useCategoryContext(); const handleApply = useCallback( () => { setFilters((prevFilters: any) =>({ diff --git a/frontend/src/components/ProductFormModal.tsx b/frontend/src/components/ProductFormModal.tsx index e7041ae..fa8014d 100644 --- a/frontend/src/components/ProductFormModal.tsx +++ b/frontend/src/components/ProductFormModal.tsx @@ -1,6 +1,7 @@ import React, {useEffect, useState} from "react"; import { Product, ProductDTO } from "../types/Product"; import { createProduct, updateProduct } from "../services/productService"; +import { useCategoryContext } from "../context/CategoryContext"; interface Props{ isOpen: boolean; @@ -20,6 +21,8 @@ const ProductFormModal: React.FC = ({isOpen, onClose, onSucces, initialDa const [errors, setErrors] = useState>({}); + const {refreshCategories} = useCategoryContext(); + useEffect(() => { if(initialData) { setForm({ @@ -67,6 +70,7 @@ const ProductFormModal: React.FC = ({isOpen, onClose, onSucces, initialDa } else { await createProduct(form); } + await refreshCategories(); onSucces(); onClose(); }catch (error) { diff --git a/frontend/src/components/ProductTable.tsx b/frontend/src/components/ProductTable.tsx index 01b6753..4167ac9 100644 --- a/frontend/src/components/ProductTable.tsx +++ b/frontend/src/components/ProductTable.tsx @@ -73,14 +73,15 @@ const ProductTable: React.FC = ({products, filters, setFilters, onEdit}) toggleStock(p)}/> + onChange={() => toggleStock(p)} + className="ml-4 mr-6"/> {p.name} - {p.category} - ${p.unitPrice.toFixed(2)} + {p.category} + ${p.unitPrice.toFixed(2)} {p.quantityInStock} - {p.expirationDate ?? "-"} - + {p.expirationDate ?? "-"} +