matarial-init 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.
- package/README.md +63 -0
- package/bin/cli.js +7 -0
- package/bin/copy-src.js +86 -0
- package/bin/postinstall.js +6 -0
- package/index.js +9 -0
- package/package.json +49 -0
- package/src/App.css +24 -0
- package/src/App.jsx +51 -0
- package/src/components/Button.jsx +21 -0
- package/src/components/Header.jsx +29 -0
- package/src/components/Input.jsx +33 -0
- package/src/components/Modal.jsx +43 -0
- package/src/components/Navbar.jsx +19 -0
- package/src/components/Sidebar.jsx +30 -0
- package/src/components/Table.jsx +56 -0
- package/src/constants/permissions.js +27 -0
- package/src/constants/storageKeys.js +151 -0
- package/src/context/DataContext.jsx +19 -0
- package/src/context/dataContext.js +22 -0
- package/src/context/useData.jsx +98 -0
- package/src/hooks/usePermission.jsx +14 -0
- package/src/index.css +0 -0
- package/src/layout/DashboardLayout.jsx +31 -0
- package/src/layout/PublicLayout.jsx +13 -0
- package/src/main.jsx +10 -0
- package/src/pages/Books.jsx +121 -0
- package/src/pages/DashBoard.jsx +414 -0
- package/src/pages/Home.jsx +9 -0
- package/src/pages/Members.jsx +80 -0
- package/src/pages/MyBooks.jsx +79 -0
- package/src/pages/Penalty.jsx +9 -0
- package/src/pages/SignUp.jsx +69 -0
- package/src/pages/Signin.jsx +73 -0
- package/src/routes/ProtectedRoute.jsx +11 -0
- package/src/routes/PublicRoute.jsx +11 -0
- package/src/uttils/persistence.js +49 -0
- package/src/validation/Validation.jsx +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# matarial-init
|
|
2
|
+
|
|
3
|
+
An NPM package that automatically installs the full `src` folder (featuring React Context dynamic workflow, public placeholder API integration, Library Management system, and interactive Dashboard) into any React project.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Quick Start
|
|
8
|
+
|
|
9
|
+
### Option 1: Automatic installation via `npm install`
|
|
10
|
+
Run inside your React project:
|
|
11
|
+
```bash
|
|
12
|
+
npm install matarial-init
|
|
13
|
+
```
|
|
14
|
+
During installation, the `postinstall` script will automatically copy the full `src/` directory into your project root.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### Option 2: Run via `npx`
|
|
19
|
+
You can also run the initialization script directly anytime:
|
|
20
|
+
```bash
|
|
21
|
+
npx matarial-init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📦 What gets installed in `src/`
|
|
27
|
+
|
|
28
|
+
- **`src/context/`**:
|
|
29
|
+
- `dataContext.js`: `DataContext` declaration & default public placeholder API fetch handler (`https://jsonplaceholder.typicode.com`).
|
|
30
|
+
- `DataContext.jsx`: `DataProvider` component wrapping your React app.
|
|
31
|
+
- `useData.jsx`: Dynamic workflow hook supporting fetching, caching, loading/error states, and mutations (`addData`, `updateData`, `deleteData`, `refresh`).
|
|
32
|
+
- **`src/pages/`**:
|
|
33
|
+
- `DashBoard.jsx`: Dynamic Dashboard with live public placeholder API integration, resource tabs (Posts, Todos, Users), metrics cards, search filter, pagination, record modals, and deletion.
|
|
34
|
+
- `Books.jsx`, `Members.jsx`, `MyBooks.jsx`, `Penalty.jsx`, `Home.jsx`, `Signin.jsx`, `SignUp.jsx`.
|
|
35
|
+
- **`src/components/`**:
|
|
36
|
+
- `Table.jsx`, `Modal.jsx`, `Button.jsx`, `Input.jsx`, `Header.jsx`, `Sidebar.jsx`, `Navbar.jsx`.
|
|
37
|
+
- **`src/layout/`**: `DashboardLayout.jsx`, `PublicLayout.jsx`.
|
|
38
|
+
- **`src/routes/`**: `ProtectedRoute.jsx`, `PublicRoute.jsx`.
|
|
39
|
+
- **`src/validation/`**: Yup validation schemas.
|
|
40
|
+
- **`src/constants/`**: Permissions and storage keys.
|
|
41
|
+
- **`src/uttils/`**: Local persistence helpers.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## ⚙️ Peer Dependencies
|
|
46
|
+
Ensure your project has the required dependencies:
|
|
47
|
+
```bash
|
|
48
|
+
npm install react react-dom react-router-dom formik yup react-toastify
|
|
49
|
+
```
|
|
50
|
+
And include Tailwind CSS or styling support (e.g. in your `index.html` or CSS):
|
|
51
|
+
```html
|
|
52
|
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🚢 Publishing to NPM
|
|
58
|
+
|
|
59
|
+
To publish this package to NPM:
|
|
60
|
+
```bash
|
|
61
|
+
npm login
|
|
62
|
+
npm publish --access public
|
|
63
|
+
```
|
package/bin/cli.js
ADDED
package/bin/copy-src.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { fileURLToPath } from "url"
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
+
const __dirname = path.dirname(__filename)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively copies all files and directories from source to destination.
|
|
10
|
+
*/
|
|
11
|
+
export function copyFolderSync(from, to) {
|
|
12
|
+
if (!fs.existsSync(to)) {
|
|
13
|
+
fs.mkdirSync(to, { recursive: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const entries = fs.readdirSync(from, { withFileTypes: true })
|
|
17
|
+
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const srcPath = path.join(from, entry.name)
|
|
20
|
+
const destPath = path.join(to, entry.name)
|
|
21
|
+
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
copyFolderSync(srcPath, destPath)
|
|
24
|
+
} else {
|
|
25
|
+
fs.copyFileSync(srcPath, destPath)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Detects the root directory of the consumer project.
|
|
32
|
+
*/
|
|
33
|
+
export function detectConsumerDir() {
|
|
34
|
+
const packageDir = path.resolve(__dirname, "..")
|
|
35
|
+
|
|
36
|
+
// Case 1: If installed inside node_modules, find the parent project root
|
|
37
|
+
const parts = packageDir.split(path.sep)
|
|
38
|
+
const nmIndex = parts.lastIndexOf("node_modules")
|
|
39
|
+
if (nmIndex > 0) {
|
|
40
|
+
const consumerRoot = parts.slice(0, nmIndex).join(path.sep)
|
|
41
|
+
if (consumerRoot && fs.existsSync(consumerRoot)) {
|
|
42
|
+
return consumerRoot
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Case 2: If invoked via npm install, npm sets INIT_CWD to the invoking directory
|
|
47
|
+
if (process.env.INIT_CWD) {
|
|
48
|
+
const initCwd = path.resolve(process.env.INIT_CWD)
|
|
49
|
+
if (initCwd !== packageDir) {
|
|
50
|
+
return initCwd
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Case 3: Direct CLI execution (e.g. npx matarial-init)
|
|
55
|
+
return process.cwd()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Installs the package's src directory into the target project directory.
|
|
60
|
+
*/
|
|
61
|
+
export function installSrc(targetBaseDir = null) {
|
|
62
|
+
const packageDir = path.resolve(__dirname, "..")
|
|
63
|
+
const resolvedTarget = path.resolve(targetBaseDir || detectConsumerDir())
|
|
64
|
+
|
|
65
|
+
// Skip if running within the package's own root during local development or build
|
|
66
|
+
if (resolvedTarget === packageDir) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sourceSrc = path.join(packageDir, "src")
|
|
71
|
+
const targetSrc = path.join(resolvedTarget, "src")
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(sourceSrc)) {
|
|
74
|
+
console.error(`\x1b[31m[matarial-init] Error: Source 'src' directory not found at: ${sourceSrc}\x1b[0m`)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
console.log(`\x1b[36m[matarial-init] Installing 'src' folder into: ${targetSrc}...\x1b[0m`)
|
|
80
|
+
copyFolderSync(sourceSrc, targetSrc)
|
|
81
|
+
console.log(`\x1b[32m✔ [matarial-init] Successfully installed 'src' folder into your project!\x1b[0m`)
|
|
82
|
+
console.log(`\x1b[35m[matarial-init] Included: DataContext, useData, Dashboard, Components, Routes, Layout, and Utils.\x1b[0m`)
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(`\x1b[31m[matarial-init] Failed to copy 'src' folder: ${err.message}\x1b[0m`)
|
|
85
|
+
}
|
|
86
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DataContext, defaultSource } from "./src/context/dataContext.js"
|
|
2
|
+
export { DataProvider } from "./src/context/DataContext.jsx"
|
|
3
|
+
export { useData } from "./src/context/useData.jsx"
|
|
4
|
+
export { default as Dashboard } from "./src/pages/DashBoard.jsx"
|
|
5
|
+
export { default as Books } from "./src/pages/Books.jsx"
|
|
6
|
+
export { default as Members } from "./src/pages/Members.jsx"
|
|
7
|
+
export { default as MyBooks } from "./src/pages/MyBooks.jsx"
|
|
8
|
+
export { default as SignIn } from "./src/pages/Signin.jsx"
|
|
9
|
+
export { default as SignUp } from "./src/pages/SignUp.jsx"
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "matarial-init",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatically installs the complete src folder with React Context dynamic workflow, components, and Dashboard into your project.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"matarial-init": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"bin",
|
|
13
|
+
"index.js",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node ./bin/postinstall.js",
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "vite build",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"preview": "vite preview"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"matarial-init",
|
|
25
|
+
"react",
|
|
26
|
+
"context",
|
|
27
|
+
"dashboard",
|
|
28
|
+
"dynamic-workflow"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"formik": "^2.4.9",
|
|
32
|
+
"react": "^19.2.7",
|
|
33
|
+
"react-dom": "^19.2.7",
|
|
34
|
+
"react-router-dom": "^7.18.0",
|
|
35
|
+
"react-toastify": "^11.1.0",
|
|
36
|
+
"yup": "^1.7.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^10.0.1",
|
|
40
|
+
"@types/react": "^19.2.17",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
43
|
+
"eslint": "^10.5.0",
|
|
44
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
45
|
+
"eslint-plugin-react-refresh": "^0.5.3",
|
|
46
|
+
"globals": "^17.6.0",
|
|
47
|
+
"vite": "^8.1.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/App.css
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.navbar {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: space-between;
|
|
4
|
+
height: 40px;
|
|
5
|
+
background: gray;
|
|
6
|
+
text-align: center;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-inline: 10px;
|
|
9
|
+
gap: 5px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.logo {
|
|
13
|
+
font-size: 20px;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
color: #fff;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.input {
|
|
20
|
+
border: solid 1px;
|
|
21
|
+
border-radius: 8px;
|
|
22
|
+
padding: 5px;
|
|
23
|
+
color: black;
|
|
24
|
+
}
|
package/src/App.jsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Route, Routes } from "react-router-dom"
|
|
2
|
+
import Home from "./pages/Home"
|
|
3
|
+
import SignIn from "./pages/Signin"
|
|
4
|
+
import SignUp from "./pages/SignUp"
|
|
5
|
+
import Dashboard from "./pages/DashBoard"
|
|
6
|
+
import { ToastContainer } from "react-toastify"
|
|
7
|
+
import "react-toastify/dist/ReactToastify.css";
|
|
8
|
+
import { ProtectedRoute } from "./routes/ProtectedRoute"
|
|
9
|
+
import { PublicRoute } from "./routes/PublicRoute"
|
|
10
|
+
import { PublicLayout } from "./layout/PublicLayout"
|
|
11
|
+
import { DashboardLayout } from "./layout/DashboardLayout"
|
|
12
|
+
import Books from "./pages/Books"
|
|
13
|
+
import Members from "./pages/Members"
|
|
14
|
+
import Penalty from "./pages/Penalty"
|
|
15
|
+
import MyBooks from "./pages/MyBooks"
|
|
16
|
+
import { DataProvider } from "./context/DataContext.jsx"
|
|
17
|
+
|
|
18
|
+
function App() {
|
|
19
|
+
return (
|
|
20
|
+
<DataProvider>
|
|
21
|
+
<ToastContainer
|
|
22
|
+
position="top-right"
|
|
23
|
+
autoClose={3000}
|
|
24
|
+
theme="colored"
|
|
25
|
+
/>
|
|
26
|
+
<Routes>
|
|
27
|
+
<Route element={
|
|
28
|
+
<PublicRoute>
|
|
29
|
+
<PublicLayout />
|
|
30
|
+
</PublicRoute>}>
|
|
31
|
+
<Route path="/" element={<Home />} />
|
|
32
|
+
<Route path="/signin" element={<SignIn />} />
|
|
33
|
+
<Route path="/signup" element={<SignUp />} />
|
|
34
|
+
</Route>
|
|
35
|
+
|
|
36
|
+
<Route element={
|
|
37
|
+
<ProtectedRoute>
|
|
38
|
+
<DashboardLayout />
|
|
39
|
+
</ProtectedRoute>}>
|
|
40
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
41
|
+
<Route path="/books" element={<Books />} />
|
|
42
|
+
<Route path="/members" element={<Members />} />
|
|
43
|
+
<Route path="/penalty" element={<Penalty />} />
|
|
44
|
+
<Route path="/mybooks" element={<MyBooks />} />
|
|
45
|
+
</Route>
|
|
46
|
+
</Routes>
|
|
47
|
+
</DataProvider>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default App
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const Button = ({ className = "", text, type = "button", onClick, disabled = false }) => {
|
|
2
|
+
return (
|
|
3
|
+
<button
|
|
4
|
+
className={`
|
|
5
|
+
rounded-md
|
|
6
|
+
px-3
|
|
7
|
+
py-2
|
|
8
|
+
cursor-pointer
|
|
9
|
+
transition
|
|
10
|
+
${disabled ? "opacity-50 cursor-not-allowed" : ""}
|
|
11
|
+
${className}
|
|
12
|
+
`}
|
|
13
|
+
type={type}
|
|
14
|
+
onClick={onClick}
|
|
15
|
+
disabled={disabled}
|
|
16
|
+
>
|
|
17
|
+
{text}
|
|
18
|
+
</button>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
export default Button
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { clearData } from "../uttils/persistence"
|
|
3
|
+
import Button from "./Button"
|
|
4
|
+
import { Modal } from "./Modal"
|
|
5
|
+
import { STORAGE_KEYS } from "../constants/storageKeys"
|
|
6
|
+
import { useNavigate } from "react-router-dom"
|
|
7
|
+
|
|
8
|
+
export const Header = () => {
|
|
9
|
+
const navigate = useNavigate()
|
|
10
|
+
const [open, setOpen] = useState(false)
|
|
11
|
+
const handleLogout = () => {
|
|
12
|
+
clearData(STORAGE_KEYS.CURRENT_USER)
|
|
13
|
+
navigate("/signin")
|
|
14
|
+
setOpen(false)
|
|
15
|
+
}
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<header className="w-full flex justify-between items-center p-4">
|
|
19
|
+
<h1>My Header</h1>
|
|
20
|
+
<Button className="bg-gray-400 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded" text="Logout" onClick={() => setOpen(true)} />
|
|
21
|
+
</header>
|
|
22
|
+
<Modal isOpen={open} onClose={() => setOpen(false)} title="Confirm Logout" submit={handleLogout}>
|
|
23
|
+
<p className="text-gray-600 mb-4">
|
|
24
|
+
Are you sure you want to logout?
|
|
25
|
+
</p>
|
|
26
|
+
</Modal>
|
|
27
|
+
</>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const Input = ({ type, name, value, onChange, onBlur, placeholder, options, min, max }) => {
|
|
2
|
+
if (type === "select") {
|
|
3
|
+
return (
|
|
4
|
+
<select
|
|
5
|
+
className="input w-full"
|
|
6
|
+
name={name}
|
|
7
|
+
value={value}
|
|
8
|
+
onChange={onChange}
|
|
9
|
+
onBlur={onBlur}
|
|
10
|
+
placeholder={placeholder}
|
|
11
|
+
>
|
|
12
|
+
{options?.map((item, i) =>
|
|
13
|
+
<option key={i} value={item.value}>
|
|
14
|
+
{item.label}
|
|
15
|
+
</option>)}
|
|
16
|
+
</select>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return (
|
|
20
|
+
<input
|
|
21
|
+
className="input w-full"
|
|
22
|
+
type={type}
|
|
23
|
+
name={name}
|
|
24
|
+
value={value}
|
|
25
|
+
onChange={onChange}
|
|
26
|
+
onBlur={onBlur}
|
|
27
|
+
placeholder={placeholder}
|
|
28
|
+
min={min}
|
|
29
|
+
max={max}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
export default Input
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Button from "./Button";
|
|
2
|
+
|
|
3
|
+
export const Modal = ({
|
|
4
|
+
isOpen,
|
|
5
|
+
onClose,
|
|
6
|
+
title,
|
|
7
|
+
children,
|
|
8
|
+
submit,
|
|
9
|
+
}) => {
|
|
10
|
+
|
|
11
|
+
if (!isOpen) return null;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="fixed inset-0 bg-black/50 flex justify-center items-center">
|
|
15
|
+
|
|
16
|
+
<div className="bg-white rounded-md p-5 min-w-[400px]">
|
|
17
|
+
|
|
18
|
+
{title &&
|
|
19
|
+
<h2 className="text-xl text-gray-800 font-bold mb-4 border-b pb-2">
|
|
20
|
+
{title}
|
|
21
|
+
</h2>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
{children}
|
|
25
|
+
|
|
26
|
+
<div className="flex justify-end gap-2">
|
|
27
|
+
|
|
28
|
+
<Button
|
|
29
|
+
className="bg-red-400 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
|
30
|
+
text="Close"
|
|
31
|
+
onClick={onClose}
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<Button
|
|
35
|
+
className="bg-gray-400 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded"
|
|
36
|
+
text="Submit"
|
|
37
|
+
onClick={submit}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Link, useNavigate } from "react-router-dom"
|
|
2
|
+
import Button from "./Button"
|
|
3
|
+
|
|
4
|
+
const Navbar = () => {
|
|
5
|
+
const navigate = useNavigate()
|
|
6
|
+
return (
|
|
7
|
+
<nav className="navbar">
|
|
8
|
+
<div className="logo" onClick={() => navigate("/")}>
|
|
9
|
+
Library
|
|
10
|
+
</div>
|
|
11
|
+
<div className="flex gap-2">
|
|
12
|
+
<Button text="Sign In" onClick={() => navigate("/signin")}/>
|
|
13
|
+
<Button text="Sign Up" onClick={() => navigate("/signup")} />
|
|
14
|
+
</div>
|
|
15
|
+
</nav>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default Navbar
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useLocation, useNavigate } from "react-router-dom"
|
|
2
|
+
import Button from "./Button"
|
|
3
|
+
import { SIDEBAR_MENU } from "../constants/storageKeys"
|
|
4
|
+
import { usePermission } from "../hooks/usePermission"
|
|
5
|
+
|
|
6
|
+
export const Sidebar = () => {
|
|
7
|
+
const navigate = useNavigate()
|
|
8
|
+
const location = useLocation()
|
|
9
|
+
const { hashPermission } = usePermission()
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<div className="flex justify-start items-center h-16 px-4 text-black font-bold border-b">LOGO</div>
|
|
13
|
+
<div className="flex flex-col gap-2 p-2">
|
|
14
|
+
{SIDEBAR_MENU.filter((menu) => hashPermission(menu.permission)).map((item) => (
|
|
15
|
+
<Button
|
|
16
|
+
key={item.id}
|
|
17
|
+
text={item.title}
|
|
18
|
+
onClick={() => navigate(item.path)}
|
|
19
|
+
className={
|
|
20
|
+
location.pathname === item.path
|
|
21
|
+
? "bg-gray-400 text-white w-full"
|
|
22
|
+
: "w-full bg-gray-200 hover:bg-gray-300"
|
|
23
|
+
}
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Button from "./Button"
|
|
2
|
+
|
|
3
|
+
export const Table = ({ data, column, action = [] }) => {
|
|
4
|
+
if (!data.length) {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<h1>No records Found.</h1>
|
|
8
|
+
</div>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
<table className="table-auto border-collapse w-full">
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
{column?.map((column) => (
|
|
16
|
+
<th key={column.key} className="bg-gray-400 text-white px-4 py-2 text-left">
|
|
17
|
+
{column.title.toUpperCase()}
|
|
18
|
+
</th>
|
|
19
|
+
))}
|
|
20
|
+
|
|
21
|
+
{action.length > 0 &&
|
|
22
|
+
<th className="bg-gray-400 text-white px-4 py-2 text-left">
|
|
23
|
+
Action
|
|
24
|
+
</th>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
{data.map((item, index) => (
|
|
31
|
+
<tr key={index}>
|
|
32
|
+
{column?.map((column, idx) => (
|
|
33
|
+
<td key={column.key} className="px-4 py-2">
|
|
34
|
+
{item[column.key]}
|
|
35
|
+
</td>
|
|
36
|
+
))}
|
|
37
|
+
{action.length > 0 && (
|
|
38
|
+
<td className="px-4 py-2">
|
|
39
|
+
<div className="flex gap-2">
|
|
40
|
+
{action.map((actionItem, i) => (
|
|
41
|
+
<Button
|
|
42
|
+
key={i}
|
|
43
|
+
text={actionItem.label}
|
|
44
|
+
className={actionItem.className}
|
|
45
|
+
onClick={() => actionItem.onClick(item)}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
</td>
|
|
50
|
+
)}
|
|
51
|
+
</tr>
|
|
52
|
+
))}
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Role } from "./storageKeys";
|
|
2
|
+
|
|
3
|
+
export const PERMISSIONS = {
|
|
4
|
+
VIEW_DASHBOARD:[
|
|
5
|
+
Role.ADMIN,
|
|
6
|
+
Role.LIBRARIAN,
|
|
7
|
+
Role.Member
|
|
8
|
+
],
|
|
9
|
+
VIEW_BOOKS:[
|
|
10
|
+
Role.ADMIN,
|
|
11
|
+
Role.LIBRARIAN,
|
|
12
|
+
Role.Member
|
|
13
|
+
],
|
|
14
|
+
VIEW_MEMBER:[
|
|
15
|
+
Role.ADMIN,
|
|
16
|
+
Role.LIBRARIAN,
|
|
17
|
+
],
|
|
18
|
+
VIEW_PANALTY:[
|
|
19
|
+
Role.ADMIN,
|
|
20
|
+
Role.LIBRARIAN,
|
|
21
|
+
Role.Member
|
|
22
|
+
],
|
|
23
|
+
VIEW_MyBOOKS:[
|
|
24
|
+
Role.Member
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
}
|