├── client ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── images │ │ ├── sale.png │ │ ├── user.png │ │ ├── money.png │ │ ├── product.png │ │ ├── statistic.svg │ │ ├── admin.svg │ │ ├── customer.svg │ │ └── responsive.svg │ ├── manifest.json │ └── index.html ├── postcss.config.js ├── src │ ├── redux │ │ ├── store.js │ │ └── cartSlice.js │ ├── components │ │ ├── categories │ │ │ ├── style.css │ │ │ ├── Add.jsx │ │ │ ├── Categories.jsx │ │ │ └── Edit.jsx │ │ ├── header │ │ │ ├── index.css │ │ │ └── Header.jsx │ │ ├── auth │ │ │ └── AuthCarousel.jsx │ │ ├── statistics │ │ │ └── StatisticCard.jsx │ │ ├── products │ │ │ ├── ProductItem.jsx │ │ │ ├── Products.jsx │ │ │ ├── Add.jsx │ │ │ └── Edit.jsx │ │ ├── cart │ │ │ ├── CreateBill.jsx │ │ │ └── CartTotals.jsx │ │ └── bills │ │ │ └── PrintBill.jsx │ ├── index.js │ ├── pages │ │ ├── ProductPage.jsx │ │ ├── HomePage.jsx │ │ ├── StatisticPage.jsx │ │ ├── auth │ │ │ ├── Login.jsx │ │ │ └── Register.jsx │ │ ├── CustomerPage.jsx │ │ ├── BillPage.jsx │ │ └── CartPage.jsx │ ├── index.css │ └── App.jsx ├── tailwind.config.js ├── .gitignore ├── package.json └── README.md └── api ├── models ├── Category.js ├── User.js ├── Product.js └── Bill.js ├── .gitignore ├── package.json ├── routes ├── users.js ├── bills.js ├── auth.js ├── products.js └── categories.js └── server.js /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/images/sale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/images/sale.png -------------------------------------------------------------------------------- /client/public/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/images/user.png -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/images/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/images/money.png -------------------------------------------------------------------------------- /client/public/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eminbasbayan/pos-application-udemy/HEAD/client/public/images/product.png -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import cartSlice from "./cartSlice"; 3 | 4 | export default configureStore({ 5 | reducer: { 6 | cart: cartSlice, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/components/categories/style.css: -------------------------------------------------------------------------------- 1 | .category-item { 2 | @apply bg-green-700 px-6 py-10 text-white cursor-pointer hover:bg-pink-700 transition-all text-center min-w-[145px] flex items-center justify-center; 3 | } 4 | -------------------------------------------------------------------------------- /api/models/Category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const CategorySchema = mongoose.Schema( 4 | { 5 | title: { type: String, require: true }, 6 | }, 7 | { timestamps: true } 8 | ); 9 | 10 | const Category = mongoose.model("categories", CategorySchema); 11 | module.exports = Category; 12 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: { 6 | gridTemplateColumns: { 7 | "card": "repeat(auto-fill, minmax(150px, 1fr))" 8 | }, 9 | }, 10 | }, 11 | plugins: [], 12 | }; 13 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const UserSchema = mongoose.Schema( 4 | { 5 | username: { type: String, require: true }, 6 | email: { type: String, require: true }, 7 | password: { type: String, require: true }, 8 | }, 9 | { timestamps: true } 10 | ); 11 | 12 | const User = mongoose.model("users", UserSchema); 13 | module.exports = User; 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import { Provider } from "react-redux"; 6 | import store from "./redux/store"; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root")); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /client/src/components/header/index.css: -------------------------------------------------------------------------------- 1 | .menu-links { 2 | @apply flex justify-between items-center gap-7 md:static fixed z-50 bottom-0 md:w-auto w-screen md:bg-transparent bg-white left-0 md:border-t-0 border-t md:px-0 px-4 py-1; 3 | } 4 | 5 | .menu-link { 6 | @apply flex flex-col hover:text-[#40a9ff] transition-all gap-y-[2px]; 7 | } 8 | 9 | .menu-link.active { 10 | @apply !text-[#40a9ff]; 11 | } 12 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .env -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .env -------------------------------------------------------------------------------- /api/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const ProductSchema = mongoose.Schema( 4 | { 5 | title: { type: String, require: true }, 6 | img: { type: String, require: true }, 7 | price: { type: Number, require: true }, 8 | category: { type: String, require: true }, 9 | }, 10 | { timestamps: true } 11 | ); 12 | 13 | const Product = mongoose.model("products", ProductSchema); 14 | module.exports = Product; 15 | -------------------------------------------------------------------------------- /client/src/pages/ProductPage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "../components/header/Header"; 3 | import Edit from "../components/products/Edit"; 4 | 5 | const ProductPage = () => { 6 | return ( 7 | <> 8 |
9 |
10 |

Ürünler

11 | 12 |
13 | 14 | ); 15 | }; 16 | 17 | export default ProductPage; 18 | -------------------------------------------------------------------------------- /client/src/components/auth/AuthCarousel.jsx: -------------------------------------------------------------------------------- 1 | const AuthCarousel = ({ img, title, desc }) => { 2 | return ( 3 |
4 | 5 |

{title}

6 |

{desc}

7 |
8 | ); 9 | }; 10 | 11 | export default AuthCarousel; 12 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.0.3", 16 | "express": "^4.18.2", 17 | "mongoose": "^6.7.3", 18 | "morgan": "^1.10.0", 19 | "nodemon": "^2.0.20" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/models/Bill.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const BillSchema = mongoose.Schema( 4 | { 5 | customerName: { type: String, require: true }, 6 | customerPhoneNumber: { type: String, require: true }, 7 | paymentMode: { type: String, require: true }, 8 | cartItems: { type: Array, require: true }, 9 | subTotal: { type: Number, require: true }, 10 | tax: { type: Number, require: true }, 11 | totalAmount: { type: Number, require: true }, 12 | }, 13 | { timestamps: true } 14 | ); 15 | 16 | const Bill = mongoose.model("bills", BillSchema); 17 | module.exports = Bill; 18 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/components/statistics/StatisticCard.jsx: -------------------------------------------------------------------------------- 1 | const StatisticCard = ({ title, amount,img }) => { 2 | return ( 3 |
4 |
5 |
6 | 7 |
8 |
9 |

{title}

10 |

{amount}

11 |
12 |
13 |
14 | ); 15 | }; 16 | 17 | export default StatisticCard; 18 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import url("https://fonts.googleapis.com/css2?family=Electrolize&display=swap"); 6 | 7 | body { 8 | margin: 0; 9 | font-family: "Electrolize", sans-serif; 10 | @apply bg-gray-50; 11 | } 12 | 13 | .ant-btn-primary { 14 | background-color: #1677ff; 15 | } 16 | 17 | .menu-link .anticon{ 18 | display: flex; 19 | justify-content: center; 20 | } 21 | 22 | @media (min-width: 768px) { 23 | body { 24 | overflow: hidden; 25 | } 26 | 27 | .categories::-webkit-scrollbar { 28 | display: none; 29 | } 30 | 31 | .products::-webkit-scrollbar { 32 | display: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/routes/users.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User.js"); 2 | const express = require("express"); 3 | const router = express.Router(); 4 | 5 | //! get all User 6 | router.get("/get-all", async (req, res) => { 7 | try { 8 | const users = await User.find(); 9 | res.status(200).json(users); 10 | } catch (error) { 11 | res.status(500).json(error); 12 | } 13 | }); 14 | 15 | //! get a user 16 | router.get("/", async (req, res) => { 17 | const userId = req.body.userId; 18 | try { 19 | const user = await User.findById(userId); 20 | res.status(200).json(user); 21 | } catch (error) { 22 | res.status(500).json(error); 23 | } 24 | }); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /api/routes/bills.js: -------------------------------------------------------------------------------- 1 | const Bill = require("../models/Bill.js"); 2 | const express = require("express"); 3 | const router = express.Router(); 4 | 5 | //! get all Bill 6 | router.get("/get-all", async (req, res) => { 7 | try { 8 | const bills = await Bill.find(); 9 | res.status(200).json(bills); 10 | } catch (error) { 11 | res.status(500).json(error); 12 | } 13 | }); 14 | 15 | //! create 16 | router.post("/add-bill", async (req, res) => { 17 | try { 18 | const newBill = new Bill(req.body); 19 | await newBill.save(); 20 | res.status(200).json("Item added successfully."); 21 | } catch (error) { 22 | res.status(500).json(error); 23 | } 24 | }); 25 | 26 | 27 | module.exports = router; 28 | -------------------------------------------------------------------------------- /client/src/components/products/ProductItem.jsx: -------------------------------------------------------------------------------- 1 | import { addProduct } from "../../redux/cartSlice"; 2 | import { useDispatch } from "react-redux"; 3 | import { message } from "antd"; 4 | 5 | const ProductItem = ({ item }) => { 6 | const dispatch = useDispatch(); 7 | 8 | const handleClick = () => { 9 | dispatch(addProduct({ ...item, quantity: 1 })); 10 | message.success("Ürün Sepete Eklendi.") 11 | }; 12 | 13 | return ( 14 |
18 |
19 | 24 |
25 |
26 | {item.title} 27 | {item.price}₺ 28 |
29 |
30 | ); 31 | }; 32 | 33 | export default ProductItem; 34 | -------------------------------------------------------------------------------- /api/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const dotenv = require("dotenv"); 4 | const app = express(); 5 | const cors = require("cors"); 6 | const logger = require("morgan"); 7 | const port = process.env.PORT || 5000; 8 | 9 | // routes 10 | const categoryRoute = require("./routes/categories.js"); 11 | const productRoute = require("./routes/products.js"); 12 | const billRoute = require("./routes/bills.js"); 13 | const authRoute = require("./routes/auth.js"); 14 | const userRoute = require("./routes/users.js"); 15 | 16 | dotenv.config(); 17 | 18 | const connect = async () => { 19 | try { 20 | await mongoose.connect(process.env.MONGO_URI); 21 | console.log("Connected to mongoDB"); 22 | } catch (error) { 23 | throw error; 24 | } 25 | }; 26 | 27 | // middlewares 28 | app.use(logger("dev")); 29 | app.use(express.json()); 30 | app.use(cors()); 31 | 32 | app.use("/api/categories", categoryRoute); 33 | app.use("/api/products", productRoute); 34 | app.use("/api/bills", billRoute); 35 | app.use("/api/auth", authRoute); 36 | app.use("/api/users", userRoute); 37 | 38 | app.listen(port, () => { 39 | connect(); 40 | console.log(`Example app listening on port ${port}`); 41 | }); 42 | -------------------------------------------------------------------------------- /api/routes/auth.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User.js"); 2 | const router = require("express").Router(); 3 | const bcrypt = require("bcryptjs"); 4 | 5 | //! register 6 | router.post("/register", async (req, res) => { 7 | try { 8 | const { username, email, password } = req.body; 9 | const salt = await bcrypt.genSalt(10); 10 | const hashedPassword = await bcrypt.hash(password, salt); 11 | const newUser = new User({ 12 | username, 13 | email, 14 | password: hashedPassword, 15 | }); 16 | await newUser.save(); 17 | res.status(200).json("A new user created successfully."); 18 | } catch (error) { 19 | res.status(500).json(error); 20 | } 21 | }); 22 | 23 | //! login 24 | router.post("/login", async (req, res) => { 25 | try { 26 | const user = await User.findOne({ email: req.body.email }); 27 | if (!user) { 28 | return res.status(404).send({ error: "User not found!" }); 29 | } 30 | 31 | const validPassword = await bcrypt.compare( 32 | req.body.password, 33 | user.password 34 | ); 35 | 36 | if (!validPassword) { 37 | res.status(403).json("Invalid password!"); 38 | } else { 39 | res.status(200).json(user); 40 | } 41 | } catch (error) { 42 | res.status(500).json(error); 43 | } 44 | }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /api/routes/products.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/Product.js"); 2 | const express = require("express"); 3 | const router = express.Router(); 4 | 5 | //! get all Product 6 | router.get("/get-all", async (req, res) => { 7 | try { 8 | const products = await Product.find(); 9 | res.status(200).json(products); 10 | } catch (error) { 11 | res.status(500).json(error); 12 | } 13 | }); 14 | 15 | //! create 16 | router.post("/add-product", async (req, res) => { 17 | try { 18 | const newProduct = new Product(req.body); 19 | await newProduct.save(); 20 | res.status(200).json("Item added successfully."); 21 | } catch (error) { 22 | res.status(500).json(error); 23 | } 24 | }); 25 | 26 | //! update 27 | router.put("/update-product", async (req, res) => { 28 | try { 29 | await Product.findOneAndUpdate({ _id: req.body.productId }, req.body); 30 | res.status(200).json("Item updated successfully."); 31 | } catch (error) { 32 | res.status(500).json(error); 33 | } 34 | }); 35 | 36 | //! delete 37 | router.delete("/delete-product", async (req, res) => { 38 | try { 39 | await Product.findOneAndDelete({ _id: req.body.productId }); 40 | res.status(200).json("Item deleted successfully."); 41 | } catch (error) { 42 | res.status(500).json(error); 43 | } 44 | }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /api/routes/categories.js: -------------------------------------------------------------------------------- 1 | const Category = require("../models/Category.js"); 2 | const express = require("express"); 3 | const router = express.Router(); 4 | 5 | //! get all category 6 | router.get("/get-all", async (req, res) => { 7 | try { 8 | const categories = await Category.find(); 9 | res.status(200).json(categories); 10 | } catch (error) { 11 | res.status(500).json(error); 12 | } 13 | }); 14 | 15 | //! create 16 | router.post("/add-category", async (req, res) => { 17 | try { 18 | const newCategory = new Category(req.body); 19 | await newCategory.save(); 20 | res.status(200).json("Item added successfully."); 21 | } catch (error) { 22 | res.status(500).json(error); 23 | } 24 | }); 25 | 26 | //! update 27 | router.put("/update-category", async (req, res) => { 28 | try { 29 | await Category.findOneAndUpdate({ _id: req.body.categoryId }, req.body); 30 | res.status(200).json("Item updated successfully."); 31 | } catch (error) { 32 | res.status(500).json(error); 33 | } 34 | }); 35 | 36 | //! delete 37 | router.delete("/delete-category", async (req, res) => { 38 | try { 39 | await Category.findOneAndDelete({ _id: req.body.categoryId }); 40 | res.status(200).json("Item deleted successfully."); 41 | } catch (error) { 42 | res.status(500).json(error); 43 | } 44 | }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/charts": "^1.4.2", 7 | "@reduxjs/toolkit": "^1.9.1", 8 | "@testing-library/jest-dom": "^5.16.5", 9 | "@testing-library/react": "^13.4.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "antd": "^5.0.1", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-highlight-words": "^0.18.0", 15 | "react-redux": "^8.0.5", 16 | "react-router-dom": "^6.4.3", 17 | "react-scripts": "5.0.1", 18 | "react-to-print": "^2.14.10", 19 | "web-vitals": "^2.1.4" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "autoprefixer": "^10.4.13", 47 | "postcss": "^8.4.19", 48 | "tailwindcss": "^3.2.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/components/categories/Add.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Form, Input, message, Modal } from "antd"; 2 | import React from "react"; 3 | 4 | const Add = ({ 5 | isAddModalOpen, 6 | setIsAddModalOpen, 7 | categories, 8 | setCategories, 9 | }) => { 10 | const [form] = Form.useForm(); 11 | 12 | const onFinish = (values) => { 13 | try { 14 | fetch(process.env.REACT_APP_SERVER_URL + "/api/categories/add-category", { 15 | method: "POST", 16 | body: JSON.stringify(values), 17 | headers: { "Content-type": "application/json; charset=UTF-8" }, 18 | }); 19 | message.success("Kategori başarıyla eklendi."); 20 | form.resetFields(); 21 | setCategories([ 22 | ...categories, 23 | { 24 | _id: Math.random(), 25 | title: values.title, 26 | }, 27 | ]); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | return ( 34 | setIsAddModalOpen(false)} 38 | footer={false} 39 | > 40 |
41 | 46 | 47 | 48 | 49 | 52 | 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default Add; 59 | -------------------------------------------------------------------------------- /client/src/components/products/Products.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import ProductItem from "./ProductItem"; 3 | import { PlusOutlined, EditOutlined } from "@ant-design/icons"; 4 | import Add from "./Add"; 5 | import { useNavigate } from "react-router-dom"; 6 | 7 | const Products = ({ categories, filtered, products, setProducts, search }) => { 8 | const [isAddModalOpen, setIsAddModalOpen] = useState(false); 9 | const navigate = useNavigate(); 10 | 11 | return ( 12 |
13 | {filtered 14 | .filter((product) => product.title?.toLowerCase().includes(search)) 15 | .map((item) => ( 16 | 17 | ))} 18 | 19 |
setIsAddModalOpen(true)} 22 | > 23 | 24 |
25 |
navigate("/products")} 28 | > 29 | 30 |
31 | 38 |
39 | ); 40 | }; 41 | 42 | export default Products; 43 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/src/redux/cartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const cartSlice = createSlice({ 4 | name: "cart", 5 | initialState: { 6 | cartItems: localStorage.getItem("cart") 7 | ? JSON.parse(localStorage.getItem("cart")).cartItems 8 | : [], 9 | total: localStorage.getItem("cart") 10 | ? JSON.parse(localStorage.getItem("cart")).total 11 | : 0, 12 | tax: 8, 13 | }, 14 | reducers: { 15 | addProduct: (state, action) => { 16 | const findCartItem = state.cartItems.find( 17 | (item) => item._id === action.payload._id 18 | ); 19 | 20 | if (findCartItem) { 21 | findCartItem.quantity = findCartItem.quantity + 1; 22 | } else { 23 | state.cartItems.push(action.payload); 24 | } 25 | state.total += action.payload.price; 26 | }, 27 | deleteCart: (state, action) => { 28 | state.cartItems = state.cartItems.filter( 29 | (item) => item._id !== action.payload._id 30 | ); 31 | state.total -= action.payload.price * action.payload.quantity; 32 | }, 33 | increase: (state, action) => { 34 | const cartItem = state.cartItems.find( 35 | (item) => item._id === action.payload._id 36 | ); 37 | cartItem.quantity += 1; 38 | state.total += cartItem.price; 39 | }, 40 | decrease: (state, action) => { 41 | const cartItem = state.cartItems.find( 42 | (item) => item._id === action.payload._id 43 | ); 44 | cartItem.quantity -= 1; 45 | if (cartItem.quantity === 0) { 46 | state.cartItems = state.cartItems.filter( 47 | (item) => item._id !== action.payload._id 48 | ); 49 | } 50 | state.total -= cartItem.price; 51 | }, 52 | reset: (state) => { 53 | state.cartItems = []; 54 | state.total = 0; 55 | }, 56 | }, 57 | }); 58 | 59 | export const { addProduct, deleteCart, increase, decrease, reset } = 60 | cartSlice.actions; 61 | export default cartSlice.reducer; 62 | -------------------------------------------------------------------------------- /client/src/components/categories/Categories.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { PlusOutlined, EditOutlined } from "@ant-design/icons"; 3 | import Add from "./Add"; 4 | import Edit from "./Edit"; 5 | import "./style.css"; 6 | 7 | const Categories = ({ categories, setCategories, setFiltered, products }) => { 8 | const [isAddModalOpen, setIsAddModalOpen] = useState(false); 9 | const [isEditModalOpen, setIsEditModalOpen] = useState(false); 10 | const [categoryTitle, setCategoryTitle] = useState("Tümü"); 11 | 12 | useEffect(() => { 13 | if (categoryTitle === "Tümü") { 14 | setFiltered(products); 15 | } else { 16 | setFiltered(products.filter((item) => item.category === categoryTitle)); 17 | } 18 | }, [products, setFiltered, categoryTitle]); 19 | 20 | return ( 21 | 58 | ); 59 | }; 60 | 61 | export default Categories; 62 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; 4 | import Login from "./pages/auth/Login"; 5 | import Register from "./pages/auth/Register"; 6 | import BillPage from "./pages/BillPage"; 7 | import CartPage from "./pages/CartPage"; 8 | import CustomerPage from "./pages/CustomerPage"; 9 | import HomePage from "./pages/HomePage"; 10 | import ProductPage from "./pages/ProductPage"; 11 | import StatisticPage from "./pages/StatisticPage"; 12 | 13 | function App() { 14 | const cart = useSelector((state) => state.cart); 15 | 16 | useEffect(() => { 17 | localStorage.setItem("cart", JSON.stringify(cart)); 18 | }, [cart]); 19 | 20 | return ( 21 | 22 | 23 | 27 | 28 | 29 | } 30 | /> 31 | 35 | 36 | 37 | } 38 | /> 39 | 43 | 44 | 45 | } 46 | /> 47 | 51 | 52 | 53 | } 54 | /> 55 | 59 | 60 | 61 | } 62 | /> 63 | 67 | 68 | 69 | } 70 | /> 71 | } /> 72 | } /> 73 | 74 | 75 | ); 76 | } 77 | 78 | export default App; 79 | 80 | export const RouteControl = ({ children }) => { 81 | if (localStorage.getItem("posUser")) { 82 | return children; 83 | } else { 84 | return ; 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /client/src/pages/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import { Spin } from "antd"; 2 | import { useEffect, useState } from "react"; 3 | import CartTotals from "../components/cart/CartTotals"; 4 | import Categories from "../components/categories/Categories"; 5 | import Header from "../components/header/Header"; 6 | import Products from "../components/products/Products"; 7 | 8 | const HomePage = () => { 9 | const [categories, setCategories] = useState(); 10 | const [products, setProducts] = useState(); 11 | const [filtered, setFiltered] = useState([]); 12 | const [search, setSearch] = useState(""); 13 | 14 | useEffect(() => { 15 | const getCategories = async () => { 16 | try { 17 | const res = await fetch( 18 | process.env.REACT_APP_SERVER_URL + "/api/categories/get-all" 19 | ); 20 | const data = await res.json(); 21 | data && 22 | setCategories( 23 | data.map((item) => { 24 | return { ...item, value: item.title }; 25 | }) 26 | ); 27 | } catch (error) { 28 | console.log(error); 29 | } 30 | }; 31 | 32 | getCategories(); 33 | }, []); 34 | 35 | useEffect(() => { 36 | const getProducts = async () => { 37 | try { 38 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/products/get-all"); 39 | const data = await res.json(); 40 | setProducts(data); 41 | } catch (error) { 42 | console.log(error); 43 | } 44 | }; 45 | 46 | getProducts(); 47 | }, []); 48 | 49 | return ( 50 | <> 51 |
52 | {products && categories ? ( 53 |
54 |
55 | 61 |
62 |
63 | 70 |
71 |
72 | 73 |
74 |
75 | ) : ( 76 | 80 | )} 81 | 82 | ); 83 | }; 84 | 85 | export default HomePage; 86 | -------------------------------------------------------------------------------- /client/src/components/products/Add.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Form, Input, message, Modal, Select } from "antd"; 2 | import React from "react"; 3 | 4 | const Add = ({ 5 | isAddModalOpen, 6 | setIsAddModalOpen, 7 | categories, 8 | products, 9 | setProducts, 10 | }) => { 11 | const [form] = Form.useForm(); 12 | 13 | const onFinish = (values) => { 14 | try { 15 | fetch(process.env.REACT_APP_SERVER_URL + "/api/products/add-product", { 16 | method: "POST", 17 | body: JSON.stringify(values), 18 | headers: { "Content-type": "application/json; charset=UTF-8" }, 19 | }); 20 | message.success("Ürün başarıyla eklendi."); 21 | form.resetFields(); 22 | setProducts([ 23 | ...products, 24 | { 25 | ...values, 26 | _id: Math.random(), 27 | price: Number(values.price), 28 | }, 29 | ]); 30 | setIsAddModalOpen(false) 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | }; 35 | 36 | return ( 37 | setIsAddModalOpen(false)} 41 | footer={false} 42 | > 43 |
44 | 49 | 50 | 51 | 58 | 59 | 60 | 67 | 68 | 69 | 74 | 61 | 62 | ); 63 | } else { 64 | return

{record.title}

; 65 | } 66 | }, 67 | }, 68 | { 69 | title: "Action", 70 | dataIndex: "action", 71 | render: (_, record) => { 72 | return ( 73 |
74 | 81 | 84 | 91 |
92 | ); 93 | }, 94 | }, 95 | ]; 96 | 97 | return ( 98 | setIsEditModalOpen(false)} 103 | > 104 | 105 | 111 | 112 | 113 | ); 114 | }; 115 | 116 | export default Edit; 117 | -------------------------------------------------------------------------------- /client/src/components/cart/CreateBill.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, Form, Input, message, Modal, Select } from "antd"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { reset } from "../../redux/cartSlice"; 5 | 6 | const CreateBill = ({ isModalOpen, setIsModalOpen }) => { 7 | const cart = useSelector((state) => state.cart); 8 | const dispatch = useDispatch(); 9 | const navigate = useNavigate(); 10 | 11 | const onFinish = async (values) => { 12 | try { 13 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/bills/add-bill", { 14 | method: "POST", 15 | body: JSON.stringify({ 16 | ...values, 17 | subTotal: cart.total, 18 | tax: ((cart.total * cart.tax) / 100).toFixed(2), 19 | totalAmount: (cart.total + (cart.total * cart.tax) / 100).toFixed(2), 20 | cartItems: cart.cartItems, 21 | }), 22 | headers: { "Content-type": "application/json; charset=UTF-8" }, 23 | }); 24 | 25 | if (res.status === 200) { 26 | message.success("Fatura başarıyla oluşturuldu."); 27 | dispatch(reset()); 28 | navigate("/bills"); 29 | } 30 | } catch (error) { 31 | message.danger("Bir şeyler yanlış gitti."); 32 | console.log(error); 33 | } 34 | }; 35 | 36 | return ( 37 | setIsModalOpen(false)} 42 | > 43 |
44 | 54 | 55 | 56 | 61 | 62 | 63 | 68 | 72 | 73 | 74 |
75 | Ara Toplam 76 | {cart.total > 0 ? cart.total.toFixed(2) : 0}₺ 77 |
78 |
79 | KDV %{cart.tax} 80 | 81 | {(cart.total * cart.tax) / 100 > 0 82 | ? `+${((cart.total * cart.tax) / 100).toFixed(2)}` 83 | : 0} 84 | ₺ 85 | 86 |
87 |
88 | Genel Toplam 89 | 90 | {cart.total + (cart.total * cart.tax) / 100 > 0 91 | ? (cart.total + (cart.total * cart.tax) / 100).toFixed(2) 92 | : 0} 93 | ₺ 94 | 95 |
96 |
97 | 106 |
107 |
108 | 109 |
110 | ); 111 | }; 112 | 113 | export default CreateBill; 114 | -------------------------------------------------------------------------------- /client/src/components/header/Header.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useLocation, useNavigate } from "react-router-dom"; 2 | import { Badge, Input, message } from "antd"; 3 | import { 4 | SearchOutlined, 5 | HomeOutlined, 6 | ShoppingCartOutlined, 7 | CopyOutlined, 8 | UserOutlined, 9 | BarChartOutlined, 10 | LogoutOutlined, 11 | } from "@ant-design/icons"; 12 | import { useSelector } from "react-redux"; 13 | import "./index.css"; 14 | 15 | const Header = ({ setSearch }) => { 16 | const cart = useSelector((state) => state.cart); 17 | const { pathname } = useLocation(); 18 | const navigate = useNavigate(); 19 | 20 | const logOut = () => { 21 | if (window.confirm("Çıkış yapmak istediğinize emin misiniz?")) { 22 | localStorage.removeItem("posUser"); 23 | navigate("/login"); 24 | message.success("Çıkış işlemi başarılı."); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 |
31 |
32 | 33 |

LOGO

34 | 35 |
36 |
{ 39 | pathname !== "/" && navigate("/"); 40 | }} 41 | > 42 | } 46 | className="rounded-full max-w-[800px]" 47 | onChange={(e) => setSearch(e.target.value.toLowerCase())} 48 | /> 49 |
50 |
51 | 55 | 56 | Ana Sayfa 57 | 58 | 63 | 67 | 68 | Sepet 69 | 70 | 71 | 75 | 76 | Faturalar 77 | 78 | 82 | 83 | Müşteriler 84 | 85 | 89 | 90 | İstatistikler 91 | 92 |
93 | 94 | 95 | Çıkış 96 | 97 |
98 |
99 | 104 | 108 | 109 | Sepet 110 | 111 | 112 |
113 |
114 | ); 115 | }; 116 | 117 | export default Header; 118 | -------------------------------------------------------------------------------- /client/src/pages/StatisticPage.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import Header from "../components/header/Header.jsx"; 3 | import StatisticCard from "../components/statistics/StatisticCard.jsx"; 4 | import { Area, Pie } from "@ant-design/plots"; 5 | import { Spin } from "antd"; 6 | 7 | const StatisticPage = () => { 8 | const [data, setData] = useState(); 9 | const [products, setProducts] = useState([]); 10 | const user = JSON.parse(localStorage.getItem("posUser")); 11 | 12 | useEffect(() => { 13 | asyncFetch(); 14 | }, []); 15 | 16 | useEffect(() => { 17 | const getProducts = async () => { 18 | try { 19 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/products/get-all"); 20 | const data = await res.json(); 21 | setProducts(data); 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | }; 26 | 27 | getProducts(); 28 | }, []); 29 | 30 | const asyncFetch = () => { 31 | fetch(process.env.REACT_APP_SERVER_URL + "/api/bills/get-all") 32 | .then((response) => response.json()) 33 | .then((json) => setData(json)) 34 | .catch((error) => { 35 | console.log("fetch data failed", error); 36 | }); 37 | }; 38 | 39 | const config = { 40 | data, 41 | xField: "customerName", 42 | yField: "subTotal", 43 | xAxis: { 44 | range: [0, 1], 45 | }, 46 | }; 47 | 48 | const config2 = { 49 | appendPadding: 10, 50 | data, 51 | angleField: "subTotal", 52 | colorField: "customerName", 53 | radius: 1, 54 | innerRadius: 0.6, 55 | label: { 56 | type: "inner", 57 | offset: "-50%", 58 | content: "{value}", 59 | style: { 60 | textAlign: "center", 61 | fontSize: 14, 62 | }, 63 | }, 64 | interactions: [ 65 | { 66 | type: "element-selected", 67 | }, 68 | { 69 | type: "element-active", 70 | }, 71 | ], 72 | statistic: { 73 | title: false, 74 | content: { 75 | style: { 76 | whiteSpace: "pre-wrap", 77 | overflow: "hidden", 78 | textOverflow: "ellipsis", 79 | }, 80 | content: "Toplam\nDeğer", 81 | }, 82 | }, 83 | }; 84 | 85 | const totalAmount = () => { 86 | const amount = data.reduce((total, item) => item.totalAmount + total, 0); 87 | return `${amount.toFixed(2)}₺`; 88 | }; 89 | 90 | return ( 91 | <> 92 |
93 |

İstatistiklerim

94 | {data ? ( 95 |
96 |
97 |

98 | Hoş geldin{" "} 99 | 100 | {user.username} 101 | 102 | . 103 |

104 |
105 | 110 | 115 | 120 | 125 |
126 |
127 |
128 | 129 |
130 |
131 | 132 |
133 |
134 |
135 |
136 | ) : ( 137 | 141 | )} 142 | 143 | ); 144 | }; 145 | 146 | export default StatisticPage; 147 | -------------------------------------------------------------------------------- /client/src/pages/auth/Login.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Carousel, Checkbox, Form, Input, message } from "antd"; 2 | import { useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import AuthCarousel from "../../components/auth/AuthCarousel"; 5 | 6 | const Login = () => { 7 | const navigate = useNavigate(); 8 | const [loading, setLoading] = useState(false); 9 | 10 | const onFinish = async (values) => { 11 | setLoading(true); 12 | try { 13 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/auth/login", { 14 | method: "POST", 15 | body: JSON.stringify(values), 16 | headers: { "Content-type": "application/json; charset=UTF-8" }, 17 | }); 18 | 19 | const user = await res.json(); 20 | 21 | if (res.status === 200) { 22 | localStorage.setItem( 23 | "posUser", 24 | JSON.stringify({ 25 | username: user.username, 26 | email: user.email, 27 | }) 28 | ); 29 | message.success("Giriş işlemi başarılı."); 30 | navigate("/"); 31 | } else if (res.status === 404) { 32 | message.error("Kullanıcı bulunamadı!"); 33 | } else if (res.status === 403) { 34 | message.error("Şifre yanlış!"); 35 | } 36 | setLoading(false); 37 | } catch (error) { 38 | message.error("Bir şeyler yanlış gitti."); 39 | console.log(error); 40 | setLoading(false); 41 | } 42 | }; 43 | 44 | return ( 45 |
46 |
47 |
48 |

LOGO

49 |
56 | 66 | 67 | 68 | 78 | 79 | 80 | 81 |
82 | Remember me 83 | Forgot Password? 84 |
85 |
86 | 87 | 96 | 97 | 98 |
99 | Henüz bir hesabınız yok mu?  100 | 101 | Şimdi kaydol 102 | 103 |
104 |
105 |
106 |
107 |
108 | 109 | 114 | 119 | 124 | 129 | 130 |
131 |
132 |
133 |
134 |
135 | ); 136 | }; 137 | 138 | export default Login; 139 | -------------------------------------------------------------------------------- /client/src/pages/auth/Register.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Carousel, Form, Input, message } from "antd"; 2 | import { Link } from "react-router-dom"; 3 | import AuthCarousel from "../../components/auth/AuthCarousel"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { useState } from "react"; 6 | 7 | const Register = () => { 8 | const navigate = useNavigate(); 9 | const [loading, setLoading] = useState(false); 10 | 11 | const onFinish = async (values) => { 12 | setLoading(true); 13 | try { 14 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/auth/register", { 15 | method: "POST", 16 | body: JSON.stringify(values), 17 | headers: { "Content-type": "application/json; charset=UTF-8" }, 18 | }); 19 | if (res.status === 200) { 20 | message.success("Kayıt işlemi başarılı."); 21 | navigate("/login"); 22 | setLoading(false); 23 | } 24 | } catch (error) { 25 | message.error("Bir şeyler yanlış gitti."); 26 | console.log(error); 27 | } 28 | }; 29 | 30 | return ( 31 |
32 |
33 |
34 |

LOGO

35 |
36 | 46 | 47 | 48 | 58 | 59 | 60 | 70 | 71 | 72 | ({ 82 | validator(_, value) { 83 | if (!value || getFieldValue("password") === value) { 84 | return Promise.resolve(); 85 | } 86 | return Promise.reject( 87 | new Error("Şifreler Aynı Olmak Zorunda!") 88 | ); 89 | }, 90 | }), 91 | ]} 92 | > 93 | 94 | 95 | 96 | 105 | 106 | 107 |
108 | Bir hesabınız var mı?  109 | 110 | Şimdi giriş yap 111 | 112 |
113 |
114 |
115 |
116 |
117 | 118 | 123 | 128 | 133 | 138 | 139 |
140 |
141 |
142 |
143 |
144 | ); 145 | }; 146 | 147 | export default Register; 148 | -------------------------------------------------------------------------------- /client/public/images/statistic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/cart/CartTotals.jsx: -------------------------------------------------------------------------------- 1 | import { Button, message } from "antd"; 2 | import { 3 | ClearOutlined, 4 | PlusCircleOutlined, 5 | MinusCircleOutlined, 6 | } from "@ant-design/icons"; 7 | import { useSelector } from "react-redux"; 8 | import { useDispatch } from "react-redux"; 9 | import { deleteCart, increase, decrease, reset } from "../../redux/cartSlice"; 10 | import { useNavigate } from "react-router-dom"; 11 | 12 | const CartTotals = () => { 13 | const cart = useSelector((state) => state.cart); 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate() 16 | 17 | return ( 18 |
19 |

20 | Sepetteki Ürünler 21 |

22 |
    23 | {cart.cartItems.length > 0 24 | ? cart.cartItems.map((item) => ( 25 |
  • 26 |
    27 | { 32 | dispatch(deleteCart(item)); 33 | message.success("Ürün Sepetten Silindi."); 34 | }} 35 | /> 36 |
    37 | {item.title} 38 | 39 | {item.price}₺ x {item.quantity} 40 | 41 |
    42 |
    43 |
    44 |
    72 |
  • 73 | )).reverse() 74 | : "Sepette hiç ürün yok..."} 75 |
76 |
77 |
78 |
79 | Ara Toplam 80 | {cart.total > 0 ? cart.total.toFixed(2) : 0}₺ 81 |
82 |
83 | KDV %{cart.tax} 84 | 85 | {(cart.total * cart.tax) / 100 > 0 86 | ? `+${((cart.total * cart.tax) / 100).toFixed(2)}` 87 | : 0} 88 | ₺ 89 | 90 |
91 |
92 |
93 |
94 | Genel Toplam 95 | 96 | {cart.total + (cart.total * cart.tax) / 100 > 0 97 | ? (cart.total + (cart.total * cart.tax) / 100).toFixed(2) 98 | : 0} 99 | ₺ 100 | 101 |
102 |
103 |
104 | 113 | 129 |
130 |
131 |
132 | ); 133 | }; 134 | 135 | export default CartTotals; 136 | -------------------------------------------------------------------------------- /client/src/pages/CustomerPage.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Space, Spin, Table } from "antd"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import Header from "../components/header/Header.jsx"; 4 | import { SearchOutlined } from "@ant-design/icons"; 5 | import Highlighter from "react-highlight-words"; 6 | 7 | const CustomerPage = () => { 8 | const [billItems, setBillItems] = useState(); 9 | const [searchText, setSearchText] = useState(""); 10 | const [searchedColumn, setSearchedColumn] = useState(""); 11 | const searchInput = useRef(null); 12 | 13 | const handleSearch = (selectedKeys, confirm, dataIndex) => { 14 | confirm(); 15 | setSearchText(selectedKeys[0]); 16 | setSearchedColumn(dataIndex); 17 | }; 18 | 19 | const handleReset = (clearFilters) => { 20 | clearFilters(); 21 | setSearchText(""); 22 | }; 23 | 24 | const getColumnSearchProps = (dataIndex) => ({ 25 | filterDropdown: ({ 26 | setSelectedKeys, 27 | selectedKeys, 28 | confirm, 29 | clearFilters, 30 | close, 31 | }) => ( 32 |
e.stopPropagation()} 37 | > 38 | 43 | setSelectedKeys(e.target.value ? [e.target.value] : []) 44 | } 45 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)} 46 | style={{ 47 | marginBottom: 8, 48 | display: "block", 49 | }} 50 | /> 51 | 52 | 63 | 72 | 85 | 94 | 95 |
96 | ), 97 | filterIcon: (filtered) => ( 98 | 103 | ), 104 | onFilter: (value, record) => 105 | record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), 106 | onFilterDropdownOpenChange: (visible) => { 107 | if (visible) { 108 | setTimeout(() => searchInput.current?.select(), 100); 109 | } 110 | }, 111 | render: (text) => 112 | searchedColumn === dataIndex ? ( 113 | 122 | ) : ( 123 | text 124 | ), 125 | }); 126 | 127 | useEffect(() => { 128 | const getBills = async () => { 129 | try { 130 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/bills/get-all"); 131 | const data = await res.json(); 132 | setBillItems(data); 133 | } catch (error) { 134 | console.log(error); 135 | } 136 | }; 137 | 138 | getBills(); 139 | }, []); 140 | 141 | const columns = [ 142 | { 143 | title: "Müşteri Adı", 144 | dataIndex: "customerName", 145 | key: "customerName", 146 | ...getColumnSearchProps("customerName"), 147 | }, 148 | { 149 | title: "Telefon Numarası", 150 | dataIndex: "customerPhoneNumber", 151 | key: "customerPhoneNumber", 152 | ...getColumnSearchProps("customerPhoneNumber"), 153 | }, 154 | { 155 | title: "İşlem Tarihi", 156 | dataIndex: "createdAt", 157 | key: "createdAt", 158 | render: (text) => { 159 | return {text.substring(0, 10)}; 160 | }, 161 | }, 162 | ]; 163 | 164 | return ( 165 | <> 166 |
167 |

Müşterilerim

168 | {billItems ? ( 169 |
170 |
181 | 182 | ) : ( 183 | 187 | )} 188 | 189 | ); 190 | }; 191 | 192 | export default CustomerPage; 193 | -------------------------------------------------------------------------------- /client/src/pages/BillPage.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Space, Spin, Table } from "antd"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import PrintBill from "../components/bills/PrintBill.jsx"; 4 | import Header from "../components/header/Header.jsx"; 5 | import { SearchOutlined } from "@ant-design/icons"; 6 | import Highlighter from "react-highlight-words"; 7 | 8 | const BillPage = () => { 9 | const [isModalOpen, setIsModalOpen] = useState(false); 10 | const [billItems, setBillItems] = useState(); 11 | const [customer, setCustomer] = useState(); 12 | const [searchText, setSearchText] = useState(""); 13 | const [searchedColumn, setSearchedColumn] = useState(""); 14 | const searchInput = useRef(null); 15 | 16 | const handleSearch = (selectedKeys, confirm, dataIndex) => { 17 | confirm(); 18 | setSearchText(selectedKeys[0]); 19 | setSearchedColumn(dataIndex); 20 | }; 21 | 22 | const handleReset = (clearFilters) => { 23 | clearFilters(); 24 | setSearchText(""); 25 | }; 26 | 27 | const getColumnSearchProps = (dataIndex) => ({ 28 | filterDropdown: ({ 29 | setSelectedKeys, 30 | selectedKeys, 31 | confirm, 32 | clearFilters, 33 | close, 34 | }) => ( 35 |
e.stopPropagation()} 40 | > 41 | 46 | setSelectedKeys(e.target.value ? [e.target.value] : []) 47 | } 48 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)} 49 | style={{ 50 | marginBottom: 8, 51 | display: "block", 52 | }} 53 | /> 54 | 55 | 66 | 75 | 88 | 97 | 98 |
99 | ), 100 | filterIcon: (filtered) => ( 101 | 106 | ), 107 | onFilter: (value, record) => 108 | record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), 109 | onFilterDropdownOpenChange: (visible) => { 110 | if (visible) { 111 | setTimeout(() => searchInput.current?.select(), 100); 112 | } 113 | }, 114 | render: (text) => 115 | searchedColumn === dataIndex ? ( 116 | 125 | ) : ( 126 | text 127 | ), 128 | }); 129 | 130 | useEffect(() => { 131 | const getBills = async () => { 132 | try { 133 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/bills/get-all"); 134 | const data = await res.json(); 135 | setBillItems(data); 136 | } catch (error) { 137 | console.log(error); 138 | } 139 | }; 140 | 141 | getBills(); 142 | }, []); 143 | 144 | const columns = [ 145 | { 146 | title: "Müşteri Adı", 147 | dataIndex: "customerName", 148 | key: "customerName", 149 | ...getColumnSearchProps("customerName"), 150 | }, 151 | { 152 | title: "Telefon Numarası", 153 | dataIndex: "customerPhoneNumber", 154 | key: "customerPhoneNumber", 155 | ...getColumnSearchProps("customerPhoneNumber"), 156 | }, 157 | { 158 | title: "Oluşturma Tarihi", 159 | dataIndex: "createdAt", 160 | key: "createdAt", 161 | render: (text) => { 162 | return {text.substring(0, 10)}; 163 | }, 164 | }, 165 | { 166 | title: "Ödeme Yöntemi", 167 | dataIndex: "paymentMode", 168 | key: "paymentMode", 169 | ...getColumnSearchProps("paymentMode"), 170 | }, 171 | { 172 | title: "Toplam Fiyat", 173 | dataIndex: "totalAmount", 174 | key: "totalAmount", 175 | render: (text) => { 176 | return {text}₺; 177 | }, 178 | sorter: (a, b) => a.totalAmount - b.totalAmount, 179 | }, 180 | { 181 | title: "Actions", 182 | dataIndex: "action", 183 | key: "action", 184 | render: (_, record) => { 185 | return ( 186 | 196 | ); 197 | }, 198 | }, 199 | ]; 200 | 201 | return ( 202 | <> 203 |
204 |

Faturalar

205 | {billItems ? ( 206 |
207 |
218 | 219 | ) : ( 220 | 224 | )} 225 | 230 | 231 | ); 232 | }; 233 | 234 | export default BillPage; 235 | -------------------------------------------------------------------------------- /client/src/components/products/Edit.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Form, Input, message, Modal, Select, Table } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | const Edit = () => { 5 | const [products, setProducts] = useState([]); 6 | const [categories, setCategories] = useState([]); 7 | const [isEditModalOpen, setIsEditModalOpen] = useState(false); 8 | const [editingItem, setEditingItem] = useState({}); 9 | const [form] = Form.useForm(); 10 | 11 | useEffect(() => { 12 | const getProducts = async () => { 13 | try { 14 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/products/get-all"); 15 | const data = await res.json(); 16 | setProducts(data); 17 | } catch (error) { 18 | console.log(error); 19 | } 20 | }; 21 | 22 | getProducts(); 23 | }, []); 24 | 25 | useEffect(() => { 26 | const getCategories = async () => { 27 | try { 28 | const res = await fetch(process.env.REACT_APP_SERVER_URL + "/api/categories/get-all"); 29 | const data = await res.json(); 30 | data && 31 | setCategories( 32 | data.map((item) => { 33 | return { ...item, value: item.title }; 34 | }) 35 | ); 36 | } catch (error) { 37 | console.log(error); 38 | } 39 | }; 40 | 41 | getCategories(); 42 | }, []); 43 | 44 | const onFinish = (values) => { 45 | try { 46 | fetch(process.env.REACT_APP_SERVER_URL + "/api/products/update-product", { 47 | method: "PUT", 48 | body: JSON.stringify({ ...values, productId: editingItem._id }), 49 | headers: { "Content-type": "application/json; charset=UTF-8" }, 50 | }); 51 | message.success("Ürün başarıyla güncellendi."); 52 | setProducts( 53 | products.map((item) => { 54 | if (item._id === editingItem._id) { 55 | return values; 56 | } 57 | return item; 58 | }) 59 | ); 60 | } catch (error) { 61 | message.error("Bir şeyler yanlış gitti."); 62 | console.log(error); 63 | } 64 | }; 65 | 66 | const deleteCategory = (id) => { 67 | if (window.confirm("Emin misiniz?")) { 68 | try { 69 | fetch(process.env.REACT_APP_SERVER_URL + "/api/products/delete-product", { 70 | method: "DELETE", 71 | body: JSON.stringify({ productId: id }), 72 | headers: { "Content-type": "application/json; charset=UTF-8" }, 73 | }); 74 | message.success("Ürün başarıyla silindi."); 75 | setProducts(products.filter((item) => item._id !== id)); 76 | } catch (error) { 77 | message.error("Bir şeyler yanlış gitti."); 78 | console.log(error); 79 | } 80 | } 81 | }; 82 | 83 | const columns = [ 84 | { 85 | title: "Ürün Adı", 86 | dataIndex: "title", 87 | width: "8%", 88 | render: (_, record) => { 89 | return

{record.title}

; 90 | }, 91 | }, 92 | { 93 | title: "Ürün Görseli", 94 | dataIndex: "img", 95 | width: "4%", 96 | render: (_, record) => { 97 | return ( 98 | 99 | ); 100 | }, 101 | }, 102 | { 103 | title: "Ürün Fiyatı", 104 | dataIndex: "price", 105 | width: "8%", 106 | }, 107 | { 108 | title: "Kategori", 109 | dataIndex: "category", 110 | width: "8%", 111 | }, 112 | { 113 | title: "Action", 114 | dataIndex: "action", 115 | width: "8%", 116 | render: (_, record) => { 117 | return ( 118 |
119 | 129 | 136 |
137 | ); 138 | }, 139 | }, 140 | ]; 141 | 142 | return ( 143 | <> 144 |
154 | setIsEditModalOpen(false)} 158 | footer={false} 159 | > 160 |
166 | 173 | 174 | 175 | 182 | 183 | 184 | 191 | 192 | 193 | 200 | 49 | setSelectedKeys(e.target.value ? [e.target.value] : []) 50 | } 51 | onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)} 52 | style={{ 53 | marginBottom: 8, 54 | display: "block", 55 | }} 56 | /> 57 | 58 | 69 | 78 | 91 | 100 | 101 | 102 | ), 103 | filterIcon: (filtered) => ( 104 | 109 | ), 110 | onFilter: (value, record) => 111 | record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), 112 | onFilterDropdownOpenChange: (visible) => { 113 | if (visible) { 114 | setTimeout(() => searchInput.current?.select(), 100); 115 | } 116 | }, 117 | render: (text) => 118 | searchedColumn === dataIndex ? ( 119 | 128 | ) : ( 129 | text 130 | ), 131 | }); 132 | 133 | const columns = [ 134 | { 135 | title: "Ürün Görseli", 136 | dataIndex: "img", 137 | key: "img", 138 | width: "125px", 139 | render: (text) => { 140 | return ; 141 | }, 142 | }, 143 | { 144 | title: "Ürün Adı", 145 | dataIndex: "title", 146 | key: "title", 147 | ...getColumnSearchProps("title") 148 | }, 149 | { 150 | title: "Kategori", 151 | dataIndex: "category", 152 | key: "category", 153 | ...getColumnSearchProps("category") 154 | }, 155 | { 156 | title: "Ürün Fiyatı", 157 | dataIndex: "price", 158 | key: "price", 159 | render: (text) => { 160 | return {text.toFixed(2)}₺; 161 | }, 162 | sorter: (a, b) => a.price - b.price, 163 | }, 164 | { 165 | title: "Ürün Adeti", 166 | dataIndex: "quantity", 167 | key: "quantity", 168 | render: (text, record) => { 169 | return ( 170 |
171 |
199 | ); 200 | }, 201 | }, 202 | { 203 | title: "Toplam Fiyat", 204 | render: (text, record) => { 205 | return {(record.quantity * record.price).toFixed(2)}₺; 206 | }, 207 | }, 208 | { 209 | title: "Actions", 210 | render: (_, record) => { 211 | return ( 212 | { 215 | dispatch(deleteCart(record)); 216 | message.success("Ürün Sepetten Silindi."); 217 | }} 218 | okText="Evet" 219 | cancelText="Hayır" 220 | > 221 | 224 | 225 | ); 226 | }, 227 | }, 228 | ]; 229 | 230 | return ( 231 | <> 232 |
233 |
234 |
244 |
245 | 246 |
247 | Ara Toplam 248 | {cart.total > 0 ? cart.total.toFixed(2) : 0}₺ 249 |
250 |
251 | KDV %{cart.tax} 252 | 253 | {(cart.total * cart.tax) / 100 > 0 254 | ? `+${((cart.total * cart.tax) / 100).toFixed(2)}` 255 | : 0} 256 | ₺ 257 | 258 |
259 |
260 | Genel Toplam 261 | 262 | {cart.total + (cart.total * cart.tax) / 100 > 0 263 | ? (cart.total + (cart.total * cart.tax) / 100).toFixed(2) 264 | : 0} 265 | ₺ 266 | 267 |
268 | 277 |
278 |
279 | 280 | 281 | 282 | ); 283 | }; 284 | 285 | export default CartPage; 286 | -------------------------------------------------------------------------------- /client/src/components/bills/PrintBill.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal } from "antd"; 2 | import { useRef } from "react"; 3 | import { useReactToPrint } from "react-to-print"; 4 | const PrintBill = ({ isModalOpen, setIsModalOpen, customer }) => { 5 | const componentRef = useRef(); 6 | const handlePrint = useReactToPrint({ 7 | content: () => componentRef.current, 8 | }); 9 | 10 | return ( 11 | setIsModalOpen(false)} 16 | width={800} 17 | > 18 |
19 |
20 |
21 |
22 |

LOGO

23 |
24 |
25 |
26 |
27 |

Fatura Detayı:

28 |

{customer?.customerName}

29 |

Fake Street 123

30 |

San Javier

31 |

CA 1234

32 |
33 |
34 |

Fatura:

35 | The Boring Company 36 |

Tesla Street 007

37 |

Frisco

38 |

CA 0000

39 |
40 |
41 |
42 |

Fatura numarası:

43 |

000{Math.floor(Math.random() * 100)}

44 |
45 |
46 |

47 | Veriliş Tarihi: 48 |

49 |

{customer?.createdAt.substring(0, 10)}

50 |
51 |
52 |
53 |
54 |

Şartlar:

55 |

10 gün

56 |
57 |
58 |

Vade:

59 |

2023-11-21

60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | 74 | 81 | 89 | 95 | 101 | 107 | 108 | 109 | 110 | {customer?.cartItems.map((item) => ( 111 | 112 | 119 | 127 | 135 | 138 | 141 | 144 | 145 | ))} 146 | 147 | 148 | 149 | 158 | 165 | 170 | 171 | 172 | 179 | 186 | 191 | 192 | 193 | 202 | 209 | 214 | 215 | 216 |
72 | Görsel 73 | 78 | {" "} 79 | Başlık 80 | 86 | {" "} 87 | Başlık 88 | 93 | Fiyat 94 | 99 | Adet 100 | 105 | Toplam 106 |
113 | 118 | 120 |
121 | {item.title} 122 | 123 | Birim Fiyatı {item.price}₺ 124 | 125 |
126 |
128 |
129 | {item.title} 130 | 131 | Birim Fiyatı {item.price}₺ 132 | 133 |
134 |
136 | {item.price.toFixed(2)}₺ 137 | 139 | {item.quantity} 140 | 142 | {(item.price * item.quantity).toFixed(2)}₺ 143 |
154 | 155 | Ara Toplam 156 | 157 | 163 |

Ara Toplam

164 |
166 | 167 | {customer?.subTotal}₺ 168 | 169 |
177 | KDV 178 | 184 |

KDV

185 |
187 | 188 | +{customer?.tax}₺ 189 | 190 |
198 | 199 | Genel Toplam 200 | 201 | 207 |

Genel Toplam

208 |
210 | 211 | {customer?.totalAmount}₺ 212 | 213 |
217 |
218 |
219 |

220 | Ödeme koşulları 14 gündür. Paketlenmemiş Borçların Geç 221 | Ödenmesi Yasası 0000'e göre, serbest çalışanların bu süreden 222 | sonra borçların ödenmemesi durumunda 00.00 gecikme ücreti 223 | talep etme hakkına sahip olduklarını ve bu noktada bu ücrete 224 | ek olarak yeni bir fatura sunulacağını lütfen unutmayın. 225 | Revize faturanın 14 gün içinde ödenmemesi durumunda, vadesi 226 | geçmiş hesaba ek faiz ve %8 yasal oran artı %0,5 Bank of 227 | England tabanı olmak üzere toplam %8,5 uygulanacaktır. 228 | Taraflar Kanun hükümleri dışında sözleşme yapamazlar. 229 |

230 |
231 |
232 | 233 | 234 | 235 | 236 |
237 | 240 |
241 |
242 | ); 243 | }; 244 | 245 | export default PrintBill; 246 | -------------------------------------------------------------------------------- /client/public/images/customer.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /client/public/images/responsive.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | --------------------------------------------------------------------------------