react-node-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.gitignore +6 -0
  2. package/README.md +0 -0
  3. package/backend-project/config/db.js +13 -0
  4. package/backend-project/controllers/authController.js +63 -0
  5. package/backend-project/controllers/productController.js +113 -0
  6. package/backend-project/controllers/reportController.js +93 -0
  7. package/backend-project/controllers/saleController.js +91 -0
  8. package/backend-project/controllers/stockController.js +126 -0
  9. package/backend-project/middleware/auth.js +8 -0
  10. package/backend-project/models/Product.js +40 -0
  11. package/backend-project/models/Sale.js +38 -0
  12. package/backend-project/models/StockStatus.js +35 -0
  13. package/backend-project/models/User.js +24 -0
  14. package/backend-project/package.json +24 -0
  15. package/backend-project/routes/authRoutes.js +9 -0
  16. package/backend-project/routes/productRoutes.js +9 -0
  17. package/backend-project/routes/reportRoutes.js +14 -0
  18. package/backend-project/routes/saleRoutes.js +8 -0
  19. package/backend-project/routes/stockRoutes.js +18 -0
  20. package/backend-project/server.js +55 -0
  21. package/backend-project/utils/seed.js +44 -0
  22. package/frontend-project/index.html +13 -0
  23. package/frontend-project/package.json +32 -0
  24. package/frontend-project/postcss.config.js +6 -0
  25. package/frontend-project/src/App.jsx +7 -0
  26. package/frontend-project/src/components/LoadingSpinner.jsx +8 -0
  27. package/frontend-project/src/components/Pagination.jsx +74 -0
  28. package/frontend-project/src/components/ProtectedRoute.jsx +22 -0
  29. package/frontend-project/src/components/SearchBar.jsx +16 -0
  30. package/frontend-project/src/components/StatCard.jsx +20 -0
  31. package/frontend-project/src/context/AuthContext.jsx +60 -0
  32. package/frontend-project/src/index.css +3 -0
  33. package/frontend-project/src/layouts/MainLayout.jsx +12 -0
  34. package/frontend-project/src/layouts/Sidebar.jsx +96 -0
  35. package/frontend-project/src/main.jsx +16 -0
  36. package/frontend-project/src/pages/Dashboard.jsx +184 -0
  37. package/frontend-project/src/pages/Login.jsx +111 -0
  38. package/frontend-project/src/pages/Products.jsx +192 -0
  39. package/frontend-project/src/pages/Reports.jsx +320 -0
  40. package/frontend-project/src/pages/Sales.jsx +150 -0
  41. package/frontend-project/src/pages/StockStatus.jsx +306 -0
  42. package/frontend-project/src/routes/AppRoutes.jsx +66 -0
  43. package/frontend-project/src/services/api.js +52 -0
  44. package/frontend-project/src/services/authService.js +5 -0
  45. package/frontend-project/src/services/productService.js +4 -0
  46. package/frontend-project/src/services/reportService.js +5 -0
  47. package/frontend-project/src/services/saleService.js +3 -0
  48. package/frontend-project/src/services/stockService.js +7 -0
  49. package/frontend-project/tailwind.config.js +8 -0
  50. package/frontend-project/vite.config.js +9 -0
  51. package/package.json +59 -0
@@ -0,0 +1,9 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { requireAuth } = require('../middleware/auth');
4
+ const { createProduct, getProducts } = require('../controllers/productController');
5
+
6
+ router.post('/', requireAuth, createProduct);
7
+ router.get('/', requireAuth, getProducts);
8
+
9
+ module.exports = router;
@@ -0,0 +1,14 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { requireAuth } = require('../middleware/auth');
4
+ const {
5
+ getDailySalesReport,
6
+ getStockStatusReport,
7
+ getDashboardData,
8
+ } = require('../controllers/reportController');
9
+
10
+ router.get('/daily-sales', requireAuth, getDailySalesReport);
11
+ router.get('/stock-status', requireAuth, getStockStatusReport);
12
+ router.get('/dashboard', requireAuth, getDashboardData);
13
+
14
+ module.exports = router;
@@ -0,0 +1,8 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { requireAuth } = require('../middleware/auth');
4
+ const { createSale } = require('../controllers/saleController');
5
+
6
+ router.post('/', requireAuth, createSale);
7
+
8
+ module.exports = router;
@@ -0,0 +1,18 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { requireAuth } = require('../middleware/auth');
4
+ const {
5
+ createStockStatus,
6
+ getStockStatuses,
7
+ getStockStatus,
8
+ updateStockStatus,
9
+ deleteStockStatus,
10
+ } = require('../controllers/stockController');
11
+
12
+ router.post('/', requireAuth, createStockStatus);
13
+ router.get('/', requireAuth, getStockStatuses);
14
+ router.get('/:id', requireAuth, getStockStatus);
15
+ router.put('/:id', requireAuth, updateStockStatus);
16
+ router.delete('/:id', requireAuth, deleteStockStatus);
17
+
18
+ module.exports = router;
@@ -0,0 +1,55 @@
1
+ const express = require('express');
2
+ const mongoose = require('mongoose');
3
+ const session = require('express-session');
4
+ const cors = require('cors');
5
+ const dotenv = require('dotenv');
6
+ const path = require('path');
7
+
8
+ dotenv.config();
9
+
10
+ const connectDB = require('./config/db');
11
+ const authRoutes = require('./routes/authRoutes');
12
+ const productRoutes = require('./routes/productRoutes');
13
+ const saleRoutes = require('./routes/saleRoutes');
14
+ const stockRoutes = require('./routes/stockRoutes');
15
+ const reportRoutes = require('./routes/reportRoutes');
16
+
17
+ const app = express();
18
+
19
+ connectDB();
20
+
21
+ app.use(cors({
22
+ origin: 'http://localhost:5173',
23
+ credentials: true,
24
+ }));
25
+
26
+ app.use(express.json());
27
+ app.use(express.urlencoded({ extended: true }));
28
+
29
+ app.use(
30
+ session({
31
+ secret: process.env.SESSION_SECRET || 'fallback_secret',
32
+ resave: false,
33
+ saveUninitialized: false,
34
+ cookie: {
35
+ secure: false,
36
+ httpOnly: true,
37
+ maxAge: 24 * 60 * 60 * 1000,
38
+ },
39
+ })
40
+ );
41
+
42
+ app.use('/api/auth', authRoutes);
43
+ app.use('/api/products', productRoutes);
44
+ app.use('/api/sales', saleRoutes);
45
+ app.use('/api/stock', stockRoutes);
46
+ app.use('/api/reports', reportRoutes);
47
+
48
+ app.get('/api/health', (req, res) => {
49
+ res.json({ status: 'ok', message: 'BWS API is running' });
50
+ });
51
+
52
+ const PORT = process.env.PORT || 5000;
53
+ app.listen(PORT, () => {
54
+ console.log(`BWS Server running on port ${PORT}`);
55
+ });
@@ -0,0 +1,44 @@
1
+ const mongoose = require('mongoose');
2
+ const bcrypt = require('bcryptjs');
3
+ const dotenv = require('dotenv');
4
+ const path = require('path');
5
+
6
+ dotenv.config({ path: path.join(__dirname, '..', '.env') });
7
+
8
+ const User = require('../models/User');
9
+
10
+ const seedAdmin = async () => {
11
+ try {
12
+ await mongoose.connect(process.env.MONGO_URI);
13
+ console.log('MongoDB connected for seeding...');
14
+
15
+ const existing = await User.findOne({ username: 'admin' });
16
+ if (existing) {
17
+ console.log('Admin user already exists. Skipping seed.');
18
+ await mongoose.disconnect();
19
+ return;
20
+ }
21
+
22
+ const salt = await bcrypt.genSalt(10);
23
+ const hashedPassword = await bcrypt.hash('Admin@123', salt);
24
+
25
+ await User.create({
26
+ username: 'admin',
27
+ password: hashedPassword,
28
+ role: 'admin',
29
+ });
30
+
31
+ console.log('Admin user created successfully!');
32
+ console.log('Username: admin');
33
+ console.log('Password: Admin@123');
34
+
35
+ await mongoose.disconnect();
36
+ console.log('Seeding complete.');
37
+ process.exit(0);
38
+ } catch (error) {
39
+ console.error('Seeding error:', error);
40
+ process.exit(1);
41
+ }
42
+ };
43
+
44
+ seedAdmin();
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>BWS - Business Web Solution</title>
8
+ </head>
9
+ <body class="bg-slate-50">
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "bws-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Business Web Solution - React Frontend (Vite + Tailwind CSS)",
6
+ "keywords": ["bws", "react", "vite", "tailwind", "dashboard"],
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "scripts": {
10
+ "dev": "vite",
11
+ "build": "vite build",
12
+ "preview": "vite preview"
13
+ },
14
+ "dependencies": {
15
+ "axios": "^1.6.2",
16
+ "react": "^18.2.0",
17
+ "react-dom": "^18.2.0",
18
+ "react-hook-form": "^7.48.2",
19
+ "react-icons": "^4.12.0",
20
+ "react-router-dom": "^6.21.1",
21
+ "recharts": "^2.10.3",
22
+ "sweetalert2": "^11.10.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/react": "^18.2.43",
26
+ "@vitejs/plugin-react": "^4.2.1",
27
+ "autoprefixer": "^10.4.16",
28
+ "postcss": "^8.4.32",
29
+ "tailwindcss": "^3.4.0",
30
+ "vite": "^5.0.8"
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,7 @@
1
+ import AppRoutes from './routes/AppRoutes';
2
+
3
+ function App() {
4
+ return <AppRoutes />;
5
+ }
6
+
7
+ export default App;
@@ -0,0 +1,8 @@
1
+ export default function LoadingSpinner({ message = 'Loading...' }) {
2
+ return (
3
+ <div className="flex flex-col items-center justify-center py-12">
4
+ <div className="w-10 h-10 border-4 border-slate-200 border-t-slate-800 rounded-full animate-spin" />
5
+ <p className="mt-4 text-slate-500 text-sm">{message}</p>
6
+ </div>
7
+ );
8
+ }
@@ -0,0 +1,74 @@
1
+ import { HiOutlineChevronLeft, HiOutlineChevronRight } from 'react-icons/hi2';
2
+
3
+ export default function Pagination({ page, pages, onPageChange }) {
4
+ if (pages <= 1) return null;
5
+
6
+ const getPages = () => {
7
+ const items = [];
8
+ const maxVisible = 3;
9
+ let start = Math.max(1, page - Math.floor(maxVisible / 2));
10
+ let end = Math.min(pages, start + maxVisible - 1);
11
+
12
+ if (end - start + 1 < maxVisible) {
13
+ start = Math.max(1, end - maxVisible + 1);
14
+ }
15
+
16
+ if (start > 1) {
17
+ items.push(
18
+ <button key={1} onClick={() => onPageChange(1)} className="px-3 py-1.5 text-sm rounded-lg text-slate-600 hover:bg-slate-200">
19
+ 1
20
+ </button>
21
+ );
22
+ if (start > 2) {
23
+ items.push(<span key="dots1" className="px-2 text-slate-400">...</span>);
24
+ }
25
+ }
26
+
27
+ for (let i = start; i <= end; i++) {
28
+ items.push(
29
+ <button
30
+ key={i}
31
+ onClick={() => onPageChange(i)}
32
+ className={`px-3 py-1.5 text-sm rounded-lg ${
33
+ i === page ? 'bg-slate-800 text-white' : 'text-slate-600 hover:bg-slate-200'
34
+ }`}
35
+ >
36
+ {i}
37
+ </button>
38
+ );
39
+ }
40
+
41
+ if (end < pages) {
42
+ if (end < pages - 1) {
43
+ items.push(<span key="dots2" className="px-2 text-slate-400">...</span>);
44
+ }
45
+ items.push(
46
+ <button key={pages} onClick={() => onPageChange(pages)} className="px-3 py-1.5 text-sm rounded-lg text-slate-600 hover:bg-slate-200">
47
+ {pages}
48
+ </button>
49
+ );
50
+ }
51
+
52
+ return items;
53
+ };
54
+
55
+ return (
56
+ <div className="flex items-center justify-center gap-1 mt-6">
57
+ <button
58
+ onClick={() => onPageChange(page - 1)}
59
+ disabled={page === 1}
60
+ className="px-3 py-1.5 text-sm rounded-lg text-slate-600 hover:bg-slate-200 disabled:opacity-50 disabled:cursor-not-allowed"
61
+ >
62
+ <HiOutlineChevronLeft className="w-4 h-4" />
63
+ </button>
64
+ {getPages()}
65
+ <button
66
+ onClick={() => onPageChange(page + 1)}
67
+ disabled={page === pages}
68
+ className="px-3 py-1.5 text-sm rounded-lg text-slate-600 hover:bg-slate-200 disabled:opacity-50 disabled:cursor-not-allowed"
69
+ >
70
+ <HiOutlineChevronRight className="w-4 h-4" />
71
+ </button>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,22 @@
1
+ import { Navigate } from 'react-router-dom';
2
+ import { useAuth } from '../context/AuthContext';
3
+ import LoadingSpinner from './LoadingSpinner';
4
+ import MainLayout from '../layouts/MainLayout';
5
+
6
+ export default function ProtectedRoute({ children }) {
7
+ const { user, loading } = useAuth();
8
+
9
+ if (loading) {
10
+ return (
11
+ <div className="min-h-screen flex items-center justify-center bg-slate-50">
12
+ <LoadingSpinner message="Checking authentication..." />
13
+ </div>
14
+ );
15
+ }
16
+
17
+ if (!user) {
18
+ return <Navigate to="/login" replace />;
19
+ }
20
+
21
+ return <MainLayout>{children}</MainLayout>;
22
+ }
@@ -0,0 +1,16 @@
1
+ import { HiOutlineMagnifyingGlass } from 'react-icons/hi2';
2
+
3
+ export default function SearchBar({ value, onChange, placeholder = 'Search...' }) {
4
+ return (
5
+ <div className="relative">
6
+ <HiOutlineMagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
7
+ <input
8
+ type="text"
9
+ value={value}
10
+ onChange={(e) => onChange(e.target.value)}
11
+ placeholder={placeholder}
12
+ className="w-full pl-10 pr-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-800 focus:border-transparent bg-white"
13
+ />
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,20 @@
1
+ export default function StatCard({ title, value, icon: Icon, subtitle }) {
2
+ return (
3
+ <div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
4
+ <div className="flex items-center justify-between">
5
+ <div>
6
+ <p className="text-sm text-slate-500 font-medium">{title}</p>
7
+ <p className="text-2xl font-bold text-slate-800 mt-1">{value}</p>
8
+ {subtitle && (
9
+ <p className="text-xs text-slate-400 mt-1">{subtitle}</p>
10
+ )}
11
+ </div>
12
+ {Icon && (
13
+ <div className="w-12 h-12 bg-slate-100 rounded-lg flex items-center justify-center">
14
+ <Icon className="w-6 h-6 text-slate-700" />
15
+ </div>
16
+ )}
17
+ </div>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,60 @@
1
+ import { createContext, useContext, useState, useEffect } from 'react';
2
+ import { checkSession, loginUser, logoutUser } from '../services/authService';
3
+ import Swal from 'sweetalert2';
4
+
5
+ const AuthContext = createContext(null);
6
+
7
+ export function AuthProvider({ children }) {
8
+ const [user, setUser] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ const initAuth = async () => {
13
+ try {
14
+ const res = await checkSession();
15
+ if (res.data.authenticated) {
16
+ setUser(res.data.user);
17
+ }
18
+ } catch {
19
+ setUser(null);
20
+ } finally {
21
+ setLoading(false);
22
+ }
23
+ };
24
+ initAuth();
25
+ }, []);
26
+
27
+ const login = async (username, password) => {
28
+ const res = await loginUser({ username, password });
29
+ setUser(res.data.user);
30
+ Swal.fire({
31
+ icon: 'success',
32
+ title: 'Welcome back!',
33
+ text: 'Login successful',
34
+ timer: 1500,
35
+ showConfirmButton: false,
36
+ toast: true,
37
+ position: 'top-end',
38
+ });
39
+ return res.data;
40
+ };
41
+
42
+ const logout = async () => {
43
+ await logoutUser();
44
+ setUser(null);
45
+ };
46
+
47
+ return (
48
+ <AuthContext.Provider value={{ user, loading, login, logout }}>
49
+ {children}
50
+ </AuthContext.Provider>
51
+ );
52
+ }
53
+
54
+ export const useAuth = () => {
55
+ const context = useContext(AuthContext);
56
+ if (!context) {
57
+ throw new Error('useAuth must be used within AuthProvider');
58
+ }
59
+ return context;
60
+ };
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,12 @@
1
+ import Sidebar from './Sidebar';
2
+
3
+ export default function MainLayout({ children }) {
4
+ return (
5
+ <div className="min-h-screen bg-slate-50">
6
+ <Sidebar />
7
+ <main className="lg:ml-64 min-h-screen p-4 md:p-6 lg:p-8 pt-16 lg:pt-8">
8
+ {children}
9
+ </main>
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,96 @@
1
+ import { NavLink } from 'react-router-dom';
2
+ import {
3
+ HiOutlineSquares2X2,
4
+ HiOutlineCube,
5
+ HiOutlineShoppingCart,
6
+ HiOutlineClipboardDocumentList,
7
+ HiOutlineDocumentChartBar,
8
+ HiOutlineArrowRightOnRectangle,
9
+ } from 'react-icons/hi2';
10
+ import { useAuth } from '../context/AuthContext';
11
+ import { useState } from 'react';
12
+
13
+ const menuItems = [
14
+ { path: '/dashboard', label: 'Dashboard', icon: HiOutlineSquares2X2 },
15
+ { path: '/products', label: 'Products', icon: HiOutlineCube },
16
+ { path: '/sales', label: 'Sales', icon: HiOutlineShoppingCart },
17
+ { path: '/stock-status', label: 'Stock Status', icon: HiOutlineClipboardDocumentList },
18
+ { path: '/reports', label: 'Reports', icon: HiOutlineDocumentChartBar },
19
+ ];
20
+
21
+ export default function Sidebar() {
22
+ const { logout } = useAuth();
23
+ const [mobileOpen, setMobileOpen] = useState(false);
24
+
25
+ const handleLogout = async () => {
26
+ await logout();
27
+ };
28
+
29
+ const linkClass = ({ isActive }) =>
30
+ `flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors ${
31
+ isActive
32
+ ? 'bg-slate-700 text-white'
33
+ : 'text-slate-300 hover:bg-slate-800 hover:text-white'
34
+ }`;
35
+
36
+ return (
37
+ <>
38
+ <button
39
+ onClick={() => setMobileOpen(!mobileOpen)}
40
+ className="lg:hidden fixed top-4 left-4 z-50 p-2 bg-slate-900 text-white rounded-lg"
41
+ >
42
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
+ {mobileOpen ? (
44
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
45
+ ) : (
46
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
47
+ )}
48
+ </svg>
49
+ </button>
50
+
51
+ {mobileOpen && (
52
+ <div
53
+ className="fixed inset-0 bg-black/50 z-30 lg:hidden"
54
+ onClick={() => setMobileOpen(false)}
55
+ />
56
+ )}
57
+
58
+ <aside
59
+ className={`fixed top-0 left-0 z-40 h-full w-64 bg-slate-900 transform transition-transform duration-200 ease-in-out lg:translate-x-0 ${
60
+ mobileOpen ? 'translate-x-0' : '-translate-x-full'
61
+ }`}
62
+ >
63
+ <div className="flex flex-col h-full">
64
+ <div className="p-6 border-b border-slate-700">
65
+ <h1 className="text-xl font-bold text-white">DAB Enterprise</h1>
66
+ <p className="text-xs text-slate-400 mt-1">Business Web Solution</p>
67
+ </div>
68
+
69
+ <nav className="flex-1 p-4 space-y-1 overflow-y-auto">
70
+ {menuItems.map((item) => (
71
+ <NavLink
72
+ key={item.path}
73
+ to={item.path}
74
+ className={linkClass}
75
+ onClick={() => setMobileOpen(false)}
76
+ >
77
+ <item.icon className="w-5 h-5" />
78
+ {item.label}
79
+ </NavLink>
80
+ ))}
81
+ </nav>
82
+
83
+ <div className="p-4 border-t border-slate-700">
84
+ <button
85
+ onClick={handleLogout}
86
+ className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium text-slate-300 hover:bg-slate-800 hover:text-white w-full transition-colors"
87
+ >
88
+ <HiOutlineArrowRightOnRectangle className="w-5 h-5" />
89
+ Logout
90
+ </button>
91
+ </div>
92
+ </div>
93
+ </aside>
94
+ </>
95
+ );
96
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import App from './App';
5
+ import { AuthProvider } from './context/AuthContext';
6
+ import './index.css';
7
+
8
+ ReactDOM.createRoot(document.getElementById('root')).render(
9
+ <React.StrictMode>
10
+ <BrowserRouter>
11
+ <AuthProvider>
12
+ <App />
13
+ </AuthProvider>
14
+ </BrowserRouter>
15
+ </React.StrictMode>
16
+ );