diff --git a/backend/src/index.js b/backend/src/index.js index 21eb705..dfd1a9e 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -106,7 +106,7 @@ app.use('/api/customers', passport.authenticate('jwt', {session: false}), custom app.use('/api/categories', passport.authenticate('jwt', {session: false}), categoriesRoutes); -app.use('/api/products', passport.authenticate('jwt', {session: false}), productsRoutes); +app.use('/api/products', productsRoutes); app.use('/api/orders', passport.authenticate('jwt', {session: false}), ordersRoutes); diff --git a/backend/src/routes/products.js b/backend/src/routes/products.js index 9b0c4c3..630fc07 100644 --- a/backend/src/routes/products.js +++ b/backend/src/routes/products.js @@ -15,6 +15,93 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); +/** + * @swagger + * /api/products: + * get: + * tags: [Products] + * summary: Get all products + * description: Get all products + * responses: + * 200: + * description: Products list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Products" + * 404: + * description: Data not found + * 500: + * description: Some server error +*/ +router.get('/', wrapAsync(async (req, res) => { + const filetype = req.query.filetype + + const currentUser = req.currentUser; + const payload = await ProductsDBApi.findAll( + req.query, { currentUser } + ); + if (filetype && filetype === 'csv') { + const fields = ['id','name','sku','description', + 'stock', + 'price', + + ]; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv) + + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + +})); + +/** + * @swagger + * /api/products/{id}: + * get: + * tags: [Products] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Products" + * 400: + * description: Invalid ID supplied + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get('/:id', wrapAsync(async (req, res) => { + const payload = await ProductsDBApi.findBy( + { id: req.params.id }, + ); + + + + res.status(200).send(payload); +})); + router.use(checkCrudPermissions('products')); @@ -108,7 +195,7 @@ router.post('/', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * properties: +* properties: * data: * description: Data of the updated items * type: array @@ -267,174 +354,7 @@ router.post('/deleteByIds', wrapAsync(async (req, res) => { res.status(200).send(payload); })); -/** - * @swagger - * /api/products: - * get: - * security: - * - bearerAuth: [] - * tags: [Products] - * summary: Get all products - * description: Get all products - * responses: - * 200: - * description: Products list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Products" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await ProductsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','sku','description', - 'stock', - 'price', - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - -/** - * @swagger - * /api/products/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Products] - * summary: Count all products - * description: Count all products - * responses: - * 200: - * description: Products count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Products" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await ProductsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/products/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Products] - * summary: Find all products that match search criteria - * description: Find all products that match search criteria - * responses: - * 200: - * description: Products list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Products" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await ProductsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/products/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Products] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Products" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await ProductsDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); router.use('/', require('../helpers').commonErrorHandler); diff --git a/frontend/src/components/Products/TableProducts.tsx b/frontend/src/components/Products/TableProducts.tsx index 0241ff5..6089a7b 100644 --- a/frontend/src/components/Products/TableProducts.tsx +++ b/frontend/src/components/Products/TableProducts.tsx @@ -103,7 +103,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) const generateFilterRequests = useMemo(() => { let request = '&'; - filterItems.forEach((item) => { + filterItems?.forEach((item) => { const isRangeFilter = filters.find( (filter) => filter.title === item.fields.selectedField && diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 9a6c93e..46d045b 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -7,6 +7,8 @@ import LayoutAuthenticated from '../layouts/Authenticated' import SectionMain from '../components/SectionMain' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' import BaseIcon from "../components/BaseIcon"; +import ProductsTable from '../components/Products/TableProducts'; +import { useSampleClients } from '../hooks/sampleData'; import { getPageTitle } from '../config' import Link from "next/link"; @@ -22,6 +24,7 @@ const Dashboard = () => { const corners = useAppSelector((state) => state.style.corners); const cardsStyle = useAppSelector((state) => state.style.cardsStyle); + const { clients } = useSampleClients(); const loadingMessage = 'Loading...'; @@ -141,6 +144,13 @@ const Dashboard = () => { {!!rolesWidgets.length &&
} + +
+ +
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index e37106b..c1a1c4e 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,57 @@ - -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; -import Head from 'next/head'; -import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; +import React, { useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { fetch as fetchProducts } from '../stores/products/productsSlice'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; +import CardBox from '../components/CardBox'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import Head from 'next/head'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import { mdiStorefrontOutline } from '@mdi/js'; + +const IndexPage = () => { + const dispatch = useAppDispatch(); + const { products, loading } = useAppSelector((state) => state.products); + + useEffect(() => { + dispatch(fetchProducts({})); + }, [dispatch]); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Store Operations Dashboard' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; return ( -
+ - {getPageTitle('Starter Page')} + {getPageTitle('Shop')} + + + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - + {loading &&
Loading...
} -
-
-
-
-
-
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
- -
+ {!loading && products && ( +
+ {products.map((product) => ( + +
+ {product.images && product.images.length > 0 ? ( + {product.name} + ) : ( + No Image + )} +
+
+

{product.name}

+

${product.price}

+
+
+ ))} +
+ )} + + ); -} - -Starter.getLayout = function getLayout(page: ReactElement) { - return {page}; }; +export default IndexPage; \ No newline at end of file diff --git a/frontend/src/pages/products/products-list.tsx b/frontend/src/pages/products/products-list.tsx index fcb456b..216c7b9 100644 --- a/frontend/src/pages/products/products-list.tsx +++ b/frontend/src/pages/products/products-list.tsx @@ -1,166 +1,18 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableProducts from '../../components/Products/TableProducts' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/products/productsSlice'; +import { useSampleClients } from '../../hooks/sampleData'; +import { getPageTitle } from '../../config'; +import ProductsTable from '../../components/Products/TableProducts'; +import LayoutAuthenticated from '../../layouts/Authenticated'; -import {hasPermission} from "../../helpers/userPermissions"; - - - -const ProductsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - - const { currentUser } = useAppSelector((state) => state.auth); - - - const dispatch = useAppDispatch(); - - - const [filters] = useState([{label: 'Name', title: 'name'},{label: 'SKU', title: 'sku'},{label: 'Description', title: 'description'}, - {label: 'Stock', title: 'stock', number: 'true'}, - {label: 'Price', title: 'price', number: 'true'}, - - - - {label: 'Category', title: 'category'}, - - - - - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_PRODUCTS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getProductsCSV = async () => { - const response = await axios({url: '/products?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'productsCSV.csv' - link.click() - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; +const ProductsPage = () => { + const { clients } = useSampleClients(); return ( - <> - - {getPageTitle('Products')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
- -
- - - - - -
- - - - - ) -} + + {getPageTitle('Products')} + + + ); +}; -ProductsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default ProductsTablesPage +export default ProductsPage;