first commit

This commit is contained in:
cifobandung771-spec 2026-05-27 15:08:20 +07:00
commit 8cd8fefcba
69 changed files with 7344 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

73
README.md Normal file
View File

@ -0,0 +1,73 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

22
eslint.config.js Normal file
View File

@ -0,0 +1,22 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>kasir</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

3981
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "kasir",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.16.0",
"lucide-react": "^1.16.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-router-dom": "^7.14.2"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.5.0",
"eslint": "^10.2.1",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.5.0",
"postcss": "^8.5.12",
"tailwindcss": "^3.4.19",
"typescript": "~6.0.2",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.10"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
public/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

24
public/icons.svg Normal file
View File

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

184
src/App.css Normal file
View File

@ -0,0 +1,184 @@
.counter {
font-size: 16px;
padding: 5px 10px;
border-radius: 5px;
color: var(--accent);
background: var(--accent-bg);
border: 2px solid transparent;
transition: border-color 0.3s;
margin-bottom: 24px;
&:hover {
border-color: var(--accent-border);
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
}
.hero {
position: relative;
.base,
.framework,
.vite {
inset-inline: 0;
margin: 0 auto;
}
.base {
width: 170px;
position: relative;
z-index: 0;
}
.framework,
.vite {
position: absolute;
}
.framework {
z-index: 1;
top: 34px;
height: 28px;
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
scale(1.4);
}
.vite {
z-index: 0;
top: 107px;
height: 26px;
width: auto;
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
scale(0.8);
}
}
#center {
display: flex;
flex-direction: column;
gap: 25px;
place-content: center;
place-items: center;
flex-grow: 1;
@media (max-width: 1024px) {
padding: 32px 20px 24px;
gap: 18px;
}
}
#next-steps {
display: flex;
border-top: 1px solid var(--border);
text-align: left;
& > div {
flex: 1 1 0;
padding: 32px;
@media (max-width: 1024px) {
padding: 24px 20px;
}
}
.icon {
margin-bottom: 16px;
width: 22px;
height: 22px;
}
@media (max-width: 1024px) {
flex-direction: column;
text-align: center;
}
}
#docs {
border-right: 1px solid var(--border);
@media (max-width: 1024px) {
border-right: none;
border-bottom: 1px solid var(--border);
}
}
#next-steps ul {
list-style: none;
padding: 0;
display: flex;
gap: 8px;
margin: 32px 0 0;
.logo {
height: 18px;
}
a {
color: var(--text-h);
font-size: 16px;
border-radius: 6px;
background: var(--social-bg);
display: flex;
padding: 6px 12px;
align-items: center;
gap: 8px;
text-decoration: none;
transition: box-shadow 0.3s;
&:hover {
box-shadow: var(--shadow);
}
.button-icon {
height: 18px;
width: 18px;
}
}
@media (max-width: 1024px) {
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
li {
flex: 1 1 calc(50% - 8px);
}
a {
width: 100%;
justify-content: center;
box-sizing: border-box;
}
}
}
#spacer {
height: 88px;
border-top: 1px solid var(--border);
@media (max-width: 1024px) {
height: 48px;
}
}
.ticks {
position: relative;
width: 100%;
&::before,
&::after {
content: '';
position: absolute;
top: -4.5px;
border: 5px solid transparent;
}
&::before {
left: 0;
border-left-color: var(--border);
}
&::after {
right: 0;
border-right-color: var(--border);
}
}

8
src/App.tsx Normal file
View File

@ -0,0 +1,8 @@
import { RouterProvider } from "react-router-dom";
import { router } from "./Routes";
function App() {
return <RouterProvider router={router} />;
}
export default App;

60
src/Routes.tsx Normal file
View File

@ -0,0 +1,60 @@
// src/Routes.tsx
import { createBrowserRouter } from "react-router-dom";
import DashboardLayout from "./layout/DashboardLayout";
// ROUTES
import Dashboard from "./routes/Dashboard";
import MasterData from "./routes/MasterData";
import LoginKasir from "./routes/LoginKasir";
import BAST from "./routes/BAST";
import Settings from "./routes/Settings";
import Transaksi from "./routes/Transaksi";
import Laporan from "./routes/Laporan";
export const router = createBrowserRouter([
/* LOGIN */
{
path: "/login",
element: <LoginKasir />,
},
/* DASHBOARD */
{
path: "/",
element: <DashboardLayout />,
children: [
{
index: true,
element: <Dashboard />,
},
{
path: "master-data",
element: <MasterData />,
},
{
path: "BAST",
element: <BAST />,
},
{
path: "settings",
element: <Settings />,
},
{
path: "Transaksi",
element: <Transaksi />,
},
{
path: "Laporan",
element: <Laporan />,
},
],
},
]);

BIN
src/assets/Filsafat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
src/assets/alat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

BIN
src/assets/buku.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

BIN
src/assets/elektronik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
src/assets/komputer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
src/assets/lampu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/laptop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
src/assets/matematika.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
src/assets/novel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
src/assets/pensil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

BIN
src/assets/pulang.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
src/assets/pulpen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 KiB

BIN
src/assets/setrika.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
src/assets/tas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

BIN
src/assets/tipex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
src/assets/welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -0,0 +1,40 @@
// components/transaksiBAST/TransaksiBASTFilter.tsx
import { Search, Plus } from "lucide-react";
type BASTFilterProps = {
searchTerm: string;
setSearchTerm: React.Dispatch<
React.SetStateAction<string>
>;
};
export default function BASTFilter({
searchTerm,
setSearchTerm,
}: BASTFilterProps) {
return (
<div className="flex justify-between items-center flex-wrap gap-4">
{/* SEARCH */}
<div className="flex items-center border border-gray-300 bg-white px-5 py-4 rounded-2xl">
<Search className="text-gray-400" size={22} />
<input
type="text"
placeholder="Cari nama barang..."
value={searchTerm}
onChange={(e) =>
setSearchTerm(e.target.value)
}
className="outline-none ml-3 text-lg"
/>
</div>
{/* BUTTON */}
<button className="bg-blue-600 hover:bg-blue-700 transition text-white px-7 py-4 rounded-2xl flex items-center gap-3 text-lg font-medium">
<Plus size={22} />
Tambah BAST
</button>
</div>
);
}

View File

@ -0,0 +1,13 @@
export default function BASTHero() {
return (
<div>
<h1 className="text-6xl font-bold text-black">
BAST
</h1>
<p className="text-gray-500 text-xl mt-3">
Data transaksi peminjaman barang sekolah
</p>
</div>
);
}

View File

@ -0,0 +1,90 @@
// components/transaksiBAST/TransaksiBASTTable.tsx
import { Eye } from "lucide-react";
type Props = {
data: {
id: string;
penerima: string;
barang: string;
tanggal: string;
jam: string;
status: string;
}[];
};
export default function TransaksiBASTTable({
data,
}: Props) {
return (
<>
<div className="overflow-x-auto rounded-[30px] border border-gray-200">
<table className="w-full">
<thead className="bg-blue-600 text-white">
<tr>
<th className="p-6 text-left text-lg">No BAST</th>
<th className="p-6 text-left text-lg">Penerima</th>
<th className="p-6 text-left text-lg">Barang</th>
<th className="p-6 text-left text-lg">Tanggal</th>
<th className="p-6 text-left text-lg">Jam</th>
<th className="p-6 text-left text-lg">Status</th>
<th className="p-6 text-left text-lg">Aksi</th>
</tr>
</thead>
<tbody>
{data.map((item, index) => (
<tr
key={index}
className="border-b hover:bg-gray-50 transition"
>
<td className="p-6 font-semibold text-lg">
{item.id}
</td>
<td className="p-6 text-lg">
{item.penerima}
</td>
<td className="p-6 text-lg">
{item.barang}
</td>
<td className="p-6 text-lg">
{item.tanggal}
</td>
<td className="p-6 text-lg">
{item.jam}
</td>
<td className="p-6">
{item.status === "Setuju" ? (
<span className="bg-green-100 text-green-700 px-5 py-2 rounded-full text-sm font-semibold">
Setuju
</span>
) : (
<span className="bg-red-100 text-red-600 px-5 py-2 rounded-full text-sm font-semibold">
Tidak Setuju
</span>
)}
</td>
<td className="p-6">
<button className="border border-blue-500 text-blue-600 hover:bg-blue-600 hover:text-white transition px-5 py-3 rounded-2xl flex items-center gap-2">
<Eye size={18} />
Detail
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-6 text-gray-500 text-lg">
Menampilkan {data.length} data
</div>
</>
);
}

View File

@ -0,0 +1,13 @@
export default function About() {
return (
<div className="bg-white rounded-2xl shadow p-6 mb-8">
<h2 className="text-2xl font-bold mb-3">
Tentang Sistem
</h2>
<p className="text-gray-600">
Aplikasi kasir dan peminjaman barang sekolah ini dirancang untuk mempermudah pengelolaan transaksi penjualan, pencatatan data barang, serta proses peminjaman perlengkapan sekolah seperti buku, alat tulis, dan perangkat pendukung lainnya. Dengan sistem yang modern dan terintegrasi, pengguna dapat mengelola stok barang, melakukan transaksi, memantau data peminjaman, serta melihat laporan dengan lebih cepat, akurat, dan aman. Dashboard utama memberikan akses mudah ke seluruh fitur penting sehingga operasional sekolah menjadi lebih terorganisir, efisien, dan profesional.
</p>
</div>
);
}

View File

@ -0,0 +1,61 @@
import novel from "../../assets/novel.png";
import elektronik from "../../assets/elektronik.png";
import alat from "../../assets/alat.png";
export default function Content() {
const items = [
{
name: "novel",
image: novel,
function:
"rekreatif (hiburan), didaktif (mendidik), moralitas (penanaman nilai baik-buruk), religius (ajaran agama), dan estetis (keindahan bahasa/cerita).",
},
{
name: "elektronik",
image: elektronik,
function:
"Elektronik digunakan untuk mempermudah pekerjaan manusia, memberikan hiburan, membantu komunikasi, serta meningkatkan efisiensi dan keamanan dalam kehidupan sehari-hari.",
},
{
name: "Alat Tulis",
image: alat ,
function:
"mencatat informasi, mendukung administrasi perkantoran, menunjang kegiatan belajar-mengajar, serta memfasilitasi ekspresi kreatif seperti menggambar dan merancang.",
},
];
return (
<div className="space-y-8 bg-white p-6 rounded-2xl">
<h2 className="text-3xl font-bold text-black mb-6 border-b-4 border-blue-600 pb-2">
Barang Barang
</h2>
{items.map((item, index) => (
<div
key={index}
className="bg-black text-white rounded-2xl shadow-lg p-6 flex items-center gap-8 border-l-4 border-blue-600"
>
{/* KIRI - PENJELASAN */}
<div className="flex-1">
<h3 className="text-2xl font-bold text-blue-400 mb-3">
{item.name}
</h3>
<p className="text-gray-300 leading-relaxed">
{item.function}
</p>
</div>
{/* KANAN - FOTO */}
<div className="w-72">
<img
src={item.image}
alt={item.name}
className="w-full h-56 object-cover rounded-xl shadow border-2 border-blue-500"
/>
</div>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,107 @@
// src/components/dashboard/Hero.tsx
export default function Hero() {
return (
<div className="bg-blue-50 rounded-3xl p-8 shadow-sm border border-blue-100 mb-8">
{/* HEADER */}
<div className="flex justify-between items-start mb-8">
{/* LEFT */}
<div>
<p className="text-gray-500 text-lg">
Selamat datang kembali 👋
</p>
<h1 className="text-5xl font-extrabold text-blue-700 mt-2">
Dashboard
</h1>
</div>
{/* PROFILE */}
<div className="flex items-center gap-3 bg-white rounded-full px-4 py-2 shadow border hover:shadow-lg transition">
{/* AVATAR */}
<div className="w-12 h-12 bg-blue-600 rounded-full flex items-center justify-center text-white font-bold text-lg">
Y
</div>
{/* USER INFO */}
<div>
<h3 className="font-semibold text-black">
Yudha
</h3>
<p className="text-gray-500 text-sm">
Users
</p>
</div>
</div>
</div>
{/* STAT CARDS */}
<div className="grid grid-cols-4 gap-6">
{/* CARD 1 */}
<div className="bg-white rounded-2xl p-6 shadow border hover:shadow-lg transition">
<p className="text-gray-500 mb-2">
Permintaan Hari Ini
</p>
<h2 className="text-4xl font-bold text-blue-700">
27
</h2>
<p className="text-green-600 mt-2">
+5 dari kemarin
</p>
</div>
{/* CARD 2 */}
<div className="bg-white rounded-2xl p-6 shadow border hover:shadow-lg transition">
<p className="text-gray-500 mb-2">
Menunggu Persetujuan
</p>
<h2 className="text-4xl font-bold text-blue-700">
5
</h2>
<p className="text-yellow-600 mt-2">
Perlu tindakan segera
</p>
</div>
{/* CARD 3 */}
<div className="bg-white rounded-2xl p-6 shadow border hover:shadow-lg transition">
<p className="text-gray-500 mb-2">
Total Barang
</p>
<h2 className="text-4xl font-bold text-blue-700">
48
</h2>
<p className="text-green-600 mt-2">
3 stok hampir habis
</p>
</div>
{/* CARD 4 */}
<div className="bg-white rounded-2xl p-6 shadow border hover:shadow-lg transition">
<p className="text-gray-500 mb-2">
Pengguna Aktif
</p>
<h2 className="text-4xl font-bold text-blue-700">
214
</h2>
<p className="text-green-600 mt-2">
Siswa & petugas
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,45 @@
import type { LucideIcon } from 'lucide-react';
interface Props {
label: string;
name: string;
type: string;
icon: LucideIcon;
placeholder: string;
showEye?: boolean;
toggleEye?: () => void;
eyeIcon?: LucideIcon;
// --- TAMBAHKAN DUA INI ---
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
export default function LoginInput({
label, name, type, icon: Icon, placeholder, showEye, toggleEye, eyeIcon: EyeIcon,
value, onChange // <--- Terima props di sini
}: Props) {
return (
<div className="mb-4">
<label className="block text-gray-400 text-sm mb-1">{label}:</label>
<div className="relative group">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 group-focus-within:text-[#4A90E2]">
<Icon size={20} />
</div>
<input
name={name}
type={type}
placeholder={placeholder}
// --- PASANG DI SINI ---
value={value}
onChange={onChange}
className="w-full bg-[#3D444D] border border-gray-600 rounded-lg pl-11 pr-10 py-3 text-white focus:outline-none focus:border-[#4A90E2] transition-all placeholder:text-gray-500"
/>
{showEye && toggleEye && EyeIcon && (
<button type="button" onClick={toggleEye} className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-white">
<EyeIcon size={20} />
</button>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,10 @@
export default function MyButton({ text, type = "button" }: { text: string; type?: "submit" | "button" }) {
return (
<button
type={type}
className="w-full bg-[#4A90E2] hover:bg-[#357ABD] text-white font-semibold py-3 rounded-lg transition-all active:scale-95 shadow-lg mt-4"
>
{text}
</button>
);
}

View File

@ -0,0 +1,28 @@
// src/components/manajemenUsers/UsersFilter.tsx
import { useState } from "react";
export default function UsersFilter() {
const [selectedRole, setSelectedRole] =
useState("Semua");
const roles = ["Semua", "Siswa"];
return (
<div className="flex gap-4 mb-6">
{roles.map((role) => (
<button
key={role}
onClick={() => setSelectedRole(role)}
className={`px-4 py-2 rounded-lg font-medium transition ${
selectedRole === role
? "bg-blue-600 text-white"
: "bg-white border hover:bg-gray-100"
}`}
>
{role}
</button>
))}
</div>
);
}

View File

@ -0,0 +1,20 @@
export default function UsersHero() {
return (
<div className="bg-blue-50 rounded-3xl p-8 shadow-sm border border-blue-100 mb-8">
<div className="flex justify-between items-center">
<div>
<p className="text-gray-500 text-lg">
Kelola data pengguna sekolah 👥
</p>
<h1 className="text-5xl font-extrabold text-blue-700 mt-2">
Manajemen Users
</h1>
</div>
<button className="bg-blue-600 text-white px-6 py-3 rounded-xl font-semibold shadow hover:bg-blue-700 transition">
+ Tambah User
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,150 @@
// src/components/manajemenUsers/UsersTable.tsx
import { useState } from "react";
export default function UsersTable() {
const [search, setSearch] = useState("");
const users = [
{
id: "#S001",
nama: "Sulthan",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S002",
nama: "Zahir",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S003",
nama: "Arief",
role: "Siswa",
kelas: "XI RPL",
status: "Pending",
},
{
id: "#S004",
nama: "Muhammad",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S005",
nama: "Fajar",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S006",
nama: "Akbar",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S007",
nama: "Yudha",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
{
id: "#S008",
nama: "Heru",
role: "Siswa",
kelas: "XI RPL",
status: "Aktif",
},
];
// FILTER SEARCH
const filteredUsers = users.filter((user) =>
user.nama
.toLowerCase()
.includes(search.toLowerCase())
);
return (
<div className="bg-white rounded-3xl shadow-lg p-6 border">
{/* HEADER */}
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-black">
Data Siswa
</h2>
<input
type="text"
placeholder="Cari nama siswa..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="border border-gray-300 rounded-xl px-4 py-2 w-72 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{/* TABLE HEADER */}
<div className="grid grid-cols-6 bg-gray-100 p-4 rounded-xl font-semibold text-gray-700 mb-4">
<div>ID</div>
<div>Nama</div>
<div>Role</div>
<div>Kelas</div>
<div>Status</div>
<div>Aksi</div>
</div>
{/* TABLE BODY */}
<div className="space-y-3">
{filteredUsers.map((user, index) => (
<div
key={user.id}
className={`grid grid-cols-6 items-center p-4 rounded-xl border ${
index % 2 === 0
? "bg-white"
: "bg-blue-50"
}`}
>
<div>{user.id}</div>
<div>{user.nama}</div>
<div>{user.role}</div>
<div>{user.kelas}</div>
<div>
<span
className={`px-3 py-1 rounded-full text-sm ${
user.status === "Aktif"
? "bg-green-100 text-green-700"
: "bg-yellow-100 text-yellow-700"
}`}
>
{user.status}
</span>
</div>
<div className="flex gap-2">
<button className="bg-blue-600 text-white px-3 py-1 rounded hover:bg-blue-700">
Edit
</button>
<button className="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
Hapus
</button>
</div>
</div>
))}
</div>
{/* EMPTY */}
{filteredUsers.length === 0 && (
<div className="text-center py-8 text-gray-500">
Siswa tidak ditemukan.
</div>
)}
</div>
);
}

View File

@ -0,0 +1,113 @@
// src/components/masterData/barangData.ts
import matematika from "../../assets/matematika.png";
import pulang from "../../assets/pulang.png";
import buku from "../../assets/buku.png";
import filsafat from "../../assets/Filsafat.png";
import laptop from "../../assets/laptop.png";
import lampu from "../../assets/lampu.png";
import komputer from "../../assets/komputer.png";
import setrika from "../../assets/setrika.png";
import tas from "../../assets/tas.png";
import pensil from "../../assets/pensil.png";
import pulpen from "../../assets/pulpen.png";
import tipex from "../../assets/tipex.png";
export const barangData = [
{
id: 1,
nama: "Buku Matematika",
foto: matematika,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 2,
nama: "Buku Pulang",
foto: pulang,
kondisi: "Kurang Bagus",
tersedia: false,
kategori: "Buku",
},
{
id: 3,
nama: "Buku Sebuah Mimpi",
foto: buku,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 4,
nama: "Filsafat: Sebuah Pengantar",
foto: filsafat,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 5,
nama: "Laptop",
foto: laptop,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 6,
nama: "Komputer",
foto: komputer,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 7,
nama: "Lampu",
foto: lampu,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 8,
nama: "Setrika",
foto: setrika,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 9,
nama: "Tas",
foto: tas,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 10,
nama: "Pensil",
foto: pensil,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 11,
nama: "Pulpen",
foto: pulpen,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 12,
nama: "Tipe X",
foto: tipex,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
];

View File

@ -0,0 +1,40 @@
// src/components/masterData/BarangFilter.tsx
type Props = {
selectedKategori: string;
setSelectedKategori: (
kategori: string
) => void;
};
export default function BarangFilter({
selectedKategori,
setSelectedKategori,
}: Props) {
const kategoriList = [
"Semua",
"Buku",
"Elektronik",
"Alat Tulis",
];
return (
<div className="flex gap-4 mb-8 flex-wrap">
{kategoriList.map((kategori) => (
<button
key={kategori}
onClick={() =>
setSelectedKategori(kategori)
}
className={`px-5 py-2 rounded-xl font-medium transition ${
selectedKategori === kategori
? "bg-blue-600 text-white"
: "bg-white border border-gray-300 text-black hover:bg-gray-100"
}`}
>
{kategori}
</button>
))}
</div>
);
}

View File

@ -0,0 +1,74 @@
// src/components/masterData/BarangGrid.tsx
type Barang = {
id: number;
nama: string;
foto: string;
kondisi: string;
tersedia: boolean;
kategori: string;
};
type Props = {
barangList: Barang[];
onEdit: (barang: Barang) => void;
};
export default function BarangGrid({
barangList,
onEdit,
}: Props) {
return (
<div className="grid grid-cols-4 gap-6">
{barangList.map((barang) => (
<div
key={barang.id}
onClick={() => onEdit(barang)}
className="bg-white rounded-3xl shadow-lg p-4 cursor-pointer hover:shadow-2xl transition"
>
{/* FOTO DINAMIS BERDASARKAN KATEGORI */}
<div
className={`w-full overflow-hidden rounded-2xl mb-4 bg-gray-100 ${
barang.kategori === "Elektronik"
? "aspect-square"
: "aspect-[2/3]"
}`}
>
<img
src={barang.foto}
alt={barang.nama}
className="w-full h-full object-cover"
/>
</div>
{/* NAMA */}
<h2 className="text-lg font-bold text-black mb-2">
{barang.nama}
</h2>
{/* KATEGORI */}
<p className="text-sm text-blue-600 font-medium mb-1">
{barang.kategori}
</p>
{/* KONDISI */}
<p className="text-gray-600">
Kondisi: {barang.kondisi}
</p>
{/* STATUS */}
<p
className={`text-sm font-medium ${
barang.tersedia
? "text-green-600"
: "text-red-500"
}`}
>
{barang.tersedia
? "Bisa Dipinjam"
: "Tidak Tersedia"}
</p>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,13 @@
export default function BarangHero() {
return (
<div className="bg-gradient-to-r from-black via-blue-900 to-blue-600 rounded-3xl p-8 text-white mb-8">
<h1 className="text-4xl font-bold mb-2">
Kelola Barang Sekolah
</h1>
<p className="text-gray-200">
Atur stok, harga, dan data barang sekolah
dengan mudah.
</p>
</div>
);
}

View File

@ -0,0 +1,140 @@
import { Pencil, Trash2, Eye } from "lucide-react";
type BarangType = {
id: number;
nama: string;
foto: string;
kondisi: string;
tersedia: boolean;
kategori: string;
};
type BarangTableProps = {
barangList: BarangType[];
onEdit: (barang: BarangType) => void;
};
export default function BarangTable({
barangList,
onEdit,
}: BarangTableProps) {
return (
<div className="bg-white rounded-3xl shadow-sm p-6 mt-6">
<div className="overflow-x-auto">
<table className="w-full">
{/* HEADER */}
<thead>
<tr className="border-b text-left text-gray-500">
<th className="py-4">Foto</th>
<th className="py-4">Nama Barang</th>
<th className="py-4">Kategori</th>
<th className="py-4">Kondisi</th>
<th className="py-4">Status</th>
<th className="py-4">Aksi</th>
</tr>
</thead>
{/* BODY */}
<tbody>
{barangList.map((barang) => (
<tr
key={barang.id}
className="border-b hover:bg-gray-50 transition"
>
{/* FOTO */}
<td className="py-4">
<img
src={barang.foto}
alt={barang.nama}
className="w-14 h-14 object-cover rounded-xl"
/>
</td>
{/* NAMA */}
<td className="py-4 font-semibold">
{barang.nama}
</td>
{/* KATEGORI */}
<td className="py-4">
{barang.kategori}
</td>
{/* KONDISI */}
<td className="py-4">
<span
className={`px-3 py-1 rounded-full text-sm font-medium ${
barang.kondisi === "Bagus"
? "bg-green-100 text-green-700"
: "bg-yellow-100 text-yellow-700"
}`}
>
{barang.kondisi}
</span>
</td>
{/* STATUS */}
<td className="py-4">
<span
className={`px-3 py-1 rounded-full text-sm font-medium ${
barang.tersedia
? "bg-blue-100 text-blue-700"
: "bg-red-100 text-red-700"
}`}
>
{barang.tersedia
? "Tersedia"
: "Dipinjam"}
</span>
</td>
{/* AKSI */}
<td className="py-4">
<div className="flex gap-2">
<button className="p-2 rounded-xl border hover:bg-gray-100">
<Eye size={18} />
</button>
<button
onClick={() =>
onEdit(barang)
}
className="p-2 rounded-xl border hover:bg-blue-50 text-blue-600"
>
<Pencil size={18} />
</button>
<button className="p-2 rounded-xl border hover:bg-red-50 text-red-600">
<Trash2 size={18} />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* FOOTER */}
<div className="flex justify-between items-center mt-6 text-sm text-gray-500">
<p>
Menampilkan {barangList.length} barang
</p>
<div className="flex gap-2">
<button className="px-4 py-2 border rounded-xl hover:bg-gray-50">
{"<"}
</button>
<button className="px-4 py-2 bg-blue-600 text-white rounded-xl">
1
</button>
<button className="px-4 py-2 border rounded-xl hover:bg-gray-50">
{">"}
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,146 @@
// src/components/masterData/EditBarangModal.tsx
import { useState } from "react";
type BarangType = {
id: number;
nama: string;
foto: string;
kondisi: string;
tersedia: boolean;
kategori: string;
};
type Props = {
barang: BarangType;
onClose: () => void;
onSave: (barang: BarangType) => void;
};
export default function EditBarangModal({
barang,
onClose,
onSave,
}: Props) {
const [kondisi, setKondisi] = useState(
barang.kondisi
);
const [tersedia, setTersedia] =
useState(barang.tersedia);
const handleSubmit = () => {
onSave({
...barang,
kondisi,
tersedia,
});
};
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
{/* MODAL BOX */}
<div className="bg-white w-[420px] rounded-3xl shadow-2xl p-5">
{/* HEADER */}
<h2 className="text-xl font-bold text-center mb-4">
Edit Barang
</h2>
{/* FOTO */}
<div className="w-28 h-28 mx-auto rounded-2xl overflow-hidden mb-3 bg-gray-100">
<img
src={barang.foto}
alt={barang.nama}
className="w-full h-full object-cover"
/>
</div>
{/* NAMA */}
<p className="text-center font-semibold mb-1">
{barang.nama}
</p>
{/* KATEGORI */}
<p className="text-center text-sm text-blue-600 mb-4">
{barang.kategori}
</p>
{/* FORM */}
<div className="space-y-3">
{/* KONDISI */}
<div>
<label className="block text-sm font-medium mb-1">
Kondisi
</label>
<select
value={kondisi}
onChange={(e) =>
setKondisi(
e.target.value
)
}
className="w-full border rounded-lg p-2"
>
<option value="Bagus">
Bagus
</option>
<option value="Kurang Bagus">
Kurang Bagus
</option>
</select>
</div>
{/* STATUS */}
<div>
<label className="block text-sm font-medium mb-1">
Status
</label>
<select
value={
tersedia
? "true"
: "false"
}
onChange={(e) =>
setTersedia(
e.target.value ===
"true"
)
}
className="w-full border rounded-lg p-2"
>
<option value="true">
Bisa Dipinjam
</option>
<option value="false">
Tidak Tersedia
</option>
</select>
</div>
</div>
{/* BUTTON */}
<div className="flex justify-end gap-3 mt-5">
<button
onClick={onClose}
className="px-4 py-2 rounded-lg bg-gray-300 hover:bg-gray-400"
>
Batal
</button>
<button
onClick={handleSubmit}
className="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700"
>
pinjam
</button>
<button
onClick={handleSubmit}
className="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700"
>
kembalikan
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,50 @@
import { History } from "lucide-react";
export default function ProfileActivity() {
return (
<div className="bg-white rounded-3xl p-8 shadow-sm">
<h2 className="text-xl font-bold flex items-center gap-2 mb-6">
<History size={22} />
Aktivitas Akun
</h2>
<div className="space-y-5 text-sm">
<div>
<p className="text-gray-400">
ID Akun
</p>
<p className="font-semibold">
USR-001
</p>
</div>
<div>
<p className="text-gray-400">
Login Terakhir
</p>
<p className="font-semibold">
18 Mei 2026
</p>
</div>
<div>
<p className="text-gray-400">
Total Login
</p>
<p className="font-semibold">
23 kali
</p>
</div>
<div>
<p className="text-gray-400">
Role
</p>
<p className="font-semibold">
Admin Dashboard
</p>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,24 @@
import { Camera } from "lucide-react";
export default function ProfileCard() {
return (
<div className="bg-white rounded-3xl p-8 shadow-sm text-center">
<div className="w-28 h-28 rounded-full bg-blue-600 mx-auto flex items-center justify-center text-white text-4xl font-bold">
Y
</div>
<h2 className="text-2xl font-bold mt-5">
Yudha
</h2>
<p className="text-gray-500">
Admin Dashboard
</p>
<button className="mt-6 w-full flex justify-center items-center gap-2 border py-3 rounded-2xl hover:bg-gray-50 transition">
<Camera size={18} />
Ubah Foto
</button>
</div>
);
}

View File

@ -0,0 +1,66 @@
import { useState } from "react";
export default function ProfileForm() {
const [isEditing, setIsEditing] =
useState(false);
return (
<div className="bg-white rounded-3xl p-6 shadow-sm">
{/* HEADER */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold">
Profile
</h2>
{!isEditing ? (
<button
onClick={() =>
setIsEditing(true)
}
className="bg-blue-500 text-white px-4 py-2 rounded-xl"
>
Edit Profile
</button>
) : (
<button
onClick={() =>
setIsEditing(false)
}
className="bg-gray-500 text-white px-4 py-2 rounded-xl"
>
Batal
</button>
)}
</div>
{/* FORM */}
<div className="space-y-4">
<div>
<label className="block mb-1">
Nama
</label>
<input
type="text"
defaultValue="Sulthan"
disabled={!isEditing}
className="w-full border rounded-xl p-3 disabled:bg-gray-100"
/>
</div>
<div>
<label className="block mb-1">
Email
</label>
<input
type="email"
defaultValue="email@gmail.com"
disabled={!isEditing}
className="w-full border rounded-xl p-3 disabled:bg-gray-100"
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,16 @@
import { User } from "lucide-react";
export default function ProfileHero() {
return (
<div className="bg-white rounded-3xl p-8 shadow-sm">
<h1 className="text-4xl font-bold flex items-center gap-3">
<User className="text-blue-600" size={36} />
Profile Saya
</h1>
<p className="text-gray-500 mt-3 text-lg">
Kelola informasi akun pengguna
</p>
</div>
);
}

View File

@ -0,0 +1,123 @@
// src/components/sidebar/Sidebar.tsx
import { NavLink } from "react-router-dom";
import {
LayoutDashboard,
ClipboardList,
Settings,
Menu,
} from "lucide-react";
type SidebarProps = {
isOpen: boolean;
setIsOpen: (value: boolean) => void;
};
const menuItems = [
{
name: "Dashboard",
path: "/",
icon: <LayoutDashboard size={22} />,
},
{
name: "Master Barang",
path: "/master-data",
icon: <ClipboardList size={22} />,
},
{
name: "Settings",
path: "/settings",
icon: <Settings size={22} />,
},
{
name: "BAST",
path: "/BAST",
icon: <ClipboardList size={22} />,
},
{
name: "Transaksi",
path: "/Transaksi",
icon: <ClipboardList size={22} />,
},
{
name: "Laporan",
path: "/Laporan",
icon: <ClipboardList size={22} />,
},
];
export default function Sidebar({
isOpen,
setIsOpen,
}: SidebarProps) {
return (
<aside
className={`fixed top-0 left-0 h-screen bg-black text-white shadow-2xl transition-all duration-300 z-50
${isOpen ? "w-64" : "w-20"}`}
>
{/* HEADER */}
<div className="h-24 px-4 border-b border-gray-800 flex items-center justify-between">
{isOpen && (
<div>
<h1 className="text-3xl font-bold text-blue-500 leading-none">
SCHOOL
<br />
LOAN
</h1>
<p className="text-sm text-gray-400 mt-2">
Sistem Peminjaman
</p>
</div>
)}
{/* TOGGLE */}
<button
onClick={() => setIsOpen(!isOpen)}
className="bg-blue-600 hover:bg-blue-700 transition p-3 rounded-2xl"
>
<Menu size={22} />
</button>
</div>
{/* MENU */}
<nav className="mt-6 px-4 space-y-3">
{menuItems.map((item, index) => (
<NavLink
key={index}
to={item.path}
className={({ isActive }) =>
`flex items-center ${
isOpen ? "justify-start" : "justify-center"
} gap-4 px-5 py-4 rounded-2xl transition-all duration-200 ${
isActive
? "bg-blue-600 text-white shadow-lg"
: "text-gray-300 hover:bg-gray-900 hover:text-white"
}`
}
>
{item.icon}
{isOpen && (
<span className="text-lg font-medium">
{item.name}
</span>
)}
</NavLink>
))}
</nav>
{/* FOOTER */}
{isOpen && (
<div className="absolute bottom-5 left-5 text-sm text-gray-500">
© 2026 School Loan
</div>
)}
</aside>
);
}

3
src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,31 @@
// src/layout/DashboardLayout.tsx
import { useState } from "react";
import { Outlet } from "react-router-dom";
import Sidebar from "../components/sidebar/Sidebar";
export default function DashboardLayout() {
const [isOpen, setIsOpen] =
useState(true);
return (
<div className="flex min-h-screen bg-[#f4f8ff]">
{/* SIDEBAR */}
<Sidebar
isOpen={isOpen}
setIsOpen={setIsOpen}
/>
{/* MAIN CONTENT */}
<main
className={`flex-1 transition-all duration-300 ${
isOpen
? "ml-64"
: "ml-20"
}`}
>
<Outlet />
</main>
</div>
);
}

10
src/main.tsx Normal file
View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

0
src/root.tsx Normal file
View File

184
src/routes/BAST.tsx Normal file
View File

@ -0,0 +1,184 @@
import { Search, Plus, Eye } from "lucide-react";
export default function BAST() {
const dataBAST = [
{
id: "BAST-001",
penerima: "Lab Komputer",
barang: "Laptop ASUS",
tanggal: "12 Mei 2026",
jam: "08:30 WIB",
status: "Setuju",
},
{
id: "BAST-002",
penerima: "Ruang Guru",
barang: "Proyektor Epson",
tanggal: "12 Mei 2026",
jam: "10:15 WIB",
status: "Tidak Setuju",
},
{
id: "BAST-003",
penerima: "Perpustakaan",
barang: "Buku Matematika",
tanggal: "13 Mei 2026",
jam: "09:00 WIB",
status: "Setuju",
},
{
id: "BAST-004",
penerima: "Kelas 10 IPA",
barang: "Mic Wireless",
tanggal: "13 Mei 2026",
jam: "11:20 WIB",
status: "Tidak Setuju",
},
{
id: "BAST-005",
penerima: "Lab Bahasa",
barang: "Headset Logitech",
tanggal: "14 Mei 2026",
jam: "08:45 WIB",
status: "Setuju",
},
];
return (
<div className="p-8 bg-[#f3f4f6] min-h-screen">
<div className="bg-white rounded-[35px] p-8 shadow-sm">
{/* HEADER */}
<div className="flex justify-between items-center flex-wrap gap-4 mb-8">
<div>
<h1 className="text-6xl font-bold text-black">
Transaksi BAST
</h1>
<p className="text-gray-500 text-xl mt-3">
Data transaksi peminjaman barang sekolah
</p>
</div>
{/* ACTION */}
<div className="flex gap-4 flex-wrap">
{/* SEARCH */}
<div className="flex items-center border border-gray-300 bg-white px-5 py-4 rounded-2xl">
<Search className="text-gray-400" size={22} />
<input
type="text"
placeholder="Cari transaksi..."
className="outline-none ml-3 text-lg"
/>
</div>
{/* BUTTON */}
<button className="bg-blue-600 hover:bg-blue-700 transition text-white px-7 py-4 rounded-2xl flex items-center gap-3 text-lg font-medium">
<Plus size={22} />
Tambah BAST
</button>
</div>
</div>
{/* TABLE */}
<div className="overflow-x-auto rounded-[30px] border border-gray-200">
<table className="w-full">
{/* HEAD */}
<thead className="bg-blue-600 text-white">
<tr>
<th className="p-6 text-left text-lg">No BAST</th>
<th className="p-6 text-left text-lg">Penerima</th>
<th className="p-6 text-left text-lg">Barang</th>
<th className="p-6 text-left text-lg">Tanggal</th>
<th className="p-6 text-left text-lg">Jam</th>
<th className="p-6 text-left text-lg">Status</th>
<th className="p-6 text-left text-lg">Aksi</th>
</tr>
</thead>
{/* BODY */}
<tbody>
{dataBAST.map((item, index) => (
<tr
key={index}
className="border-b hover:bg-gray-50 transition"
>
<td className="p-6 font-semibold text-lg">
{item.id}
</td>
<td className="p-6 text-lg">
{item.penerima}
</td>
<td className="p-6 text-lg">
{item.barang}
</td>
<td className="p-6 text-lg">
{item.tanggal}
</td>
<td className="p-6 text-lg">
{item.jam}
</td>
{/* STATUS */}
<td className="p-6">
{item.status === "Setuju" ? (
<span className="bg-green-100 text-green-700 px-5 py-2 rounded-full text-sm font-semibold">
Setuju
</span>
) : (
<span className="bg-red-100 text-red-600 px-5 py-2 rounded-full text-sm font-semibold">
Tidak Setuju
</span>
)}
</td>
{/* BUTTON */}
<td className="p-6">
<button className="border border-blue-500 text-blue-600 hover:bg-blue-600 hover:text-white transition px-5 py-3 rounded-2xl flex items-center gap-2">
<Eye size={18} />
Detail
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* FOOTER */}
<div className="flex justify-between items-center mt-8 flex-wrap gap-4">
<p className="text-gray-500 text-lg">
Menampilkan 1 sampai 5 dari 15 data
</p>
{/* PAGINATION */}
<div className="flex gap-3">
<button className="w-12 h-12 rounded-2xl border hover:bg-gray-100 text-lg">
{"<"}
</button>
<button className="w-12 h-12 rounded-2xl bg-blue-600 text-white text-lg">
1
</button>
<button className="w-12 h-12 rounded-2xl border hover:bg-gray-100 text-lg">
2
</button>
<button className="w-12 h-12 rounded-2xl border hover:bg-gray-100 text-lg">
3
</button>
<button className="w-12 h-12 rounded-2xl border hover:bg-gray-100 text-lg">
{">"}
</button>
</div>
</div>
</div>
</div>
);
}

13
src/routes/Dashboard.tsx Normal file
View File

@ -0,0 +1,13 @@
import Hero from "../components/dashboard/Hero";
import About from "../components/dashboard/About";
import Content from "../components/dashboard/Content";
export default function Dashboard() {
return (
<div className="min-h-screen bg-gray-100 p-6">
<Hero />
<About />
<Content />
</div>
);
}

98
src/routes/Laporan.tsx Normal file
View File

@ -0,0 +1,98 @@
export default function LaporanTransaksi() {
const dataTransaksi = [
{
id: 1,
pengirim: "Andi",
penerima: "Budi",
barang: "Laptop",
jumlah: 1,
tanggal: "20-05-2026",
status: "Berhasil",
},
{
id: 2,
pengirim: "Sinta",
penerima: "Rina",
barang: "Mouse",
jumlah: 2,
tanggal: "21-05-2026",
status: "Berhasil",
},
{
id: 3,
pengirim: "Doni",
penerima: "Agus",
barang: "Keyboard",
jumlah: 1,
tanggal: "22-05-2026",
status: "Berhasil",
},
];
return (
<div className="min-h-screen bg-blue-100 p-6">
<div className="max-w-6xl mx-auto bg-white shadow-2xl rounded-2xl p-6 border border-blue-300">
<h1 className="text-3xl font-bold text-center text-blue-900 mb-6">
Laporan Transaksi Berhasil User ke User
</h1>
<div className="overflow-x-auto rounded-xl">
<table className="w-full border border-blue-300">
<thead className="bg-blue-700 text-white">
<tr>
<th className="p-3 border border-blue-300">No</th>
<th className="p-3 border border-blue-300">Pengirim</th>
<th className="p-3 border border-blue-300">Penerima</th>
<th className="p-3 border border-blue-300">Barang</th>
<th className="p-3 border border-blue-300">Jumlah</th>
<th className="p-3 border border-blue-300">Tanggal</th>
<th className="p-3 border border-blue-300">Status</th>
</tr>
</thead>
<tbody>
{dataTransaksi.map((item, index) => (
<tr
key={item.id}
className="text-center hover:bg-blue-50 transition"
>
<td className="p-3 border border-blue-200">
{index + 1}
</td>
<td className="p-3 border border-blue-200">
{item.pengirim}
</td>
<td className="p-3 border border-blue-200">
{item.penerima}
</td>
<td className="p-3 border border-blue-200">
{item.barang}
</td>
<td className="p-3 border border-blue-200">
{item.jumlah}
</td>
<td className="p-3 border border-blue-200">
{item.tanggal}
</td>
<td className="p-3 border border-blue-200 text-green-600 font-bold">
{item.status}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}

107
src/routes/LoginKasir.tsx Normal file
View File

@ -0,0 +1,107 @@
import { useState } from "react";
export default function LoginKasir() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
function handleLogin(e: React.FormEvent) {
e.preventDefault();
// username & password sederhana
if (username === "users" && password === "users") {
localStorage.setItem("isLogin", "true");
window.location.href = "/";
} else {
alert("Username atau Password salah");
}
}
return (
<div
style={{
height: "100vh",
background: "#f1f5f9",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<form
onSubmit={handleLogin}
style={{
background: "white",
padding: "40px",
borderRadius: "20px",
width: "350px",
boxShadow: "0 5px 20px rgba(0,0,0,0.1)",
}}
>
<h1
style={{
textAlign: "center",
marginBottom: "30px",
}}
>
Login users
</h1>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{
width: "100%",
padding: "14px",
marginBottom: "15px",
borderRadius: "10px",
border: "1px solid #ccc",
}}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{
width: "100%",
padding: "14px",
marginBottom: "20px",
borderRadius: "10px",
border: "1px solid #ccc",
}}
/>
<button
type="submit"
style={{
width: "100%",
padding: "14px",
border: "none",
borderRadius: "10px",
background: "#2563eb",
color: "white",
fontWeight: "bold",
cursor: "pointer",
}}
>
Login
</button>
<p
style={{
textAlign: "center",
marginTop: "20px",
color: "#6b7280",
fontSize: "14px",
}}
>
username: users <br />
password: users
</p>
</form>
</div>
);
}

View File

@ -0,0 +1,21 @@
// src/routes/ManajemenUsers.tsx
// COMPONENTS
import UsersHero from "../components/manajemenUsers/UserHero";
import UsersFilter from "../components/manajemenUsers/UserFilter";
import UsersTable from "../components/manajemenUsers/UserTable";
export default function ManajemenUsers() {
return (
<div className="min-h-screen bg-[#f4f8ff] p-6">
{/* HERO */}
<UsersHero />
{/* FILTER */}
<UsersFilter />
{/* TABLE */}
<UsersTable />
</div>
);
}

197
src/routes/MasterData.tsx Normal file
View File

@ -0,0 +1,197 @@
// src/routes/MasterData.tsx
import { useState } from "react";
// IMPORT GAMBAR
import matematika from "../assets/matematika.png";
import pulang from "../assets/pulang.png";
import buku from "../assets/buku.png";
import filsafat from "../assets/Filsafat.png";
import laptop from "../assets/laptop.png";
import lampu from "../assets/lampu.png";
import komputer from "../assets/komputer.png";
import setrika from "../assets/setrika.png";
import tas from "../assets/tas.png";
import pensil from "../assets/pensil.png";
import pulpen from "../assets/pulpen.png";
import tipex from "../assets/tipex.png";
// COMPONENTS
import BarangHero from "../components/masterData/BarangHero";
import BarangFilter from "../components/masterData/BarangFilter";
import BarangGrid from "../components/masterData/BarangGrid";
import EditBarangModal from "../components/masterData/EditBarangModal";
// TYPE
type BarangType = {
id: number;
nama: string;
foto: string;
kondisi: string;
tersedia: boolean;
kategori: string;
};
export default function MasterData() {
// STATE MODAL
const [selectedBarang, setSelectedBarang] =
useState<BarangType | null>(null);
// STATE FILTER
const [selectedKategori, setSelectedKategori] =
useState("Semua");
// DATA BARANG
const [barangList, setBarangList] = useState<
BarangType[]
>([
{
id: 1,
nama: "Buku Matematika",
foto: matematika,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 2,
nama: "Buku Pulang",
foto: pulang,
kondisi: "Kurang Bagus",
tersedia: false,
kategori: "Buku",
},
{
id: 3,
nama: "Buku sebuah mimpi",
foto: buku,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 4,
nama: "Filsafat: Sebuah Pengantar",
foto: filsafat,
kondisi: "Bagus",
tersedia: true,
kategori: "Buku",
},
{
id: 5,
nama: "Laptop",
foto: laptop,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 6,
nama: "komputer",
foto: komputer,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 7,
nama: "lampu",
foto: lampu,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 8,
nama: "setrika",
foto: setrika,
kondisi: "Bagus",
tersedia: true,
kategori: "Elektronik",
},
{
id: 9,
nama: "tas",
foto: tas,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 10,
nama: "pensil",
foto: pensil,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 11,
nama: "pulpen",
foto: pulpen,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
},
{
id: 12,
nama: "tipe x",
foto: tipex,
kondisi: "Bagus",
tersedia: true,
kategori: "Alat Tulis",
}
]);
// FILTER DATA
const filteredBarang = barangList.filter(
(barang) =>
selectedKategori === "Semua" ||
barang.kategori === selectedKategori
);
// SAVE EDIT
const handleSave = (
updatedBarang: BarangType
) => {
setBarangList((prev) =>
prev.map((item) =>
item.id === updatedBarang.id
? updatedBarang
: item
)
);
setSelectedBarang(null);
};
return (
<div className="min-h-screen bg-[#f4f8ff] p-6">
{/* HERO */}
<BarangHero />
{/* FILTER */}
<BarangFilter
selectedKategori={selectedKategori}
setSelectedKategori={setSelectedKategori}
/>
{/* GRID */}
<BarangGrid
barangList={filteredBarang}
onEdit={setSelectedBarang}
/>
{/* MODAL */}
{selectedBarang && (
<EditBarangModal
barang={selectedBarang}
onClose={() =>
setSelectedBarang(null)
}
onSave={handleSave}
/>
)}
</div>
);
}

39
src/routes/Profile.tsx Normal file
View File

@ -0,0 +1,39 @@
import ProfileHero from "../components/profile/ProfileHero";
import ProfileCard from "../components/profile/ProfileCard";
import ProfileForm from "../components/profile/ProfileForm";
import ProfileActivity from "../components/profile/ProfileActivity";
type ProfileProps = {
isOpen?: boolean;
};
export default function Profile({
isOpen = false,
}: ProfileProps) {
return (
<div
className={`min-h-screen bg-[#f4f8ff] p-8 transition-all duration-300 ${
isOpen ? "ml-72" : "ml-24"
}`}
>
<ProfileHero />
<div className="grid grid-cols-12 gap-6 mt-8">
{/* LEFT */}
<div className="col-span-4">
<ProfileCard />
</div>
{/* CENTER */}
<div className="col-span-5">
<ProfileForm />
</div>
{/* RIGHT */}
<div className="col-span-3">
<ProfileActivity />
</div>
</div>
</div>
);
}

330
src/routes/Settings.tsx Normal file
View File

@ -0,0 +1,330 @@
import { useState } from "react";
import {
User,
Lock,
Bell,
Shield,
Globe,
Palette,
History,
HelpCircle,
LogOut,
Camera,
} from "lucide-react";
export default function Settings() {
const [darkMode, setDarkMode] = useState(false);
return (
<div
className={`min-h-screen p-6 transition-all duration-300 ${
darkMode
? "bg-[#0F172A] text-white"
: "bg-gray-100 text-black"
}`}
>
{/* HEADER */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-4xl font-bold flex items-center gap-3">
<User className="text-purple-600" size={35} />
Profile User
</h1>
<p
className={`mt-2 ${
darkMode ? "text-gray-300" : "text-gray-500"
}`}
>
Kelola profile akun anda
</p>
</div>
{/* BUTTON DARK MODE */}
<button
onClick={() => setDarkMode(!darkMode)}
className={`px-5 py-3 rounded-2xl font-semibold transition-all ${
darkMode
? "bg-yellow-400 text-black"
: "bg-[#0F172A] text-white"
}`}
>
{darkMode ? "☀️ Light" : "🌙 Dark"}
</button>
</div>
{/* CONTENT */}
<div className="grid grid-cols-12 gap-6">
{/* LEFT MENU */}
<div
className={`col-span-3 rounded-3xl p-5 shadow-sm ${
darkMode ? "bg-[#1E293B]" : "bg-white"
}`}
>
<h2 className="font-bold text-xl mb-5">
Menu Settings
</h2>
<div className="space-y-3">
<button className="w-full flex items-center gap-3 bg-blue-50 text-blue-600 p-4 rounded-2xl font-semibold">
<User size={20} />
Profil Saya
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<Lock size={20} />
Ubah Password
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<Bell size={20} />
Notifikasi
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<Shield size={20} />
Keamanan
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<Globe size={20} />
Bahasa
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<Palette size={20} />
Tema
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<History size={20} />
Aktivitas Login
</button>
<button className="w-full flex items-center gap-3 p-4 rounded-2xl hover:bg-gray-100 hover:text-black">
<HelpCircle size={20} />
Bantuan
</button>
</div>
<div className="border-t mt-8 pt-5 border-gray-600">
<button className="flex items-center gap-3 text-red-500 font-semibold">
<LogOut size={20} />
Logout
</button>
</div>
</div>
{/* CENTER */}
<div
className={`col-span-6 rounded-3xl p-6 shadow-sm ${
darkMode ? "bg-[#1E293B]" : "bg-white"
}`}
>
{/* PROFILE */}
<div className="mb-8">
<h2 className="font-bold text-lg mb-4">
Foto Profil
</h2>
<div className="flex items-center gap-5">
<div className="w-24 h-24 rounded-full bg-blue-600 flex items-center justify-center text-white text-5xl font-bold">
Y
</div>
<div>
<button className="flex items-center gap-2 border px-4 py-2 rounded-xl">
<Camera size={18} />
Ubah Foto
</button>
<p
className={`text-sm mt-2 ${
darkMode
? "text-gray-300"
: "text-gray-400"
}`}
>
PNG, JPG maksimal 2MB
</p>
</div>
</div>
</div>
{/* FORM */}
<div className="grid grid-cols-2 gap-5">
<div>
<label className="font-semibold text-sm">
Nama Lengkap
</label>
<input
type="text"
defaultValue="Yudha"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
<div>
<label className="font-semibold text-sm">
No. Telepon
</label>
<input
type="text"
defaultValue="081234567890"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
<div>
<label className="font-semibold text-sm">
Email
</label>
<input
type="email"
defaultValue="yudha@gmail.com"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
<div>
<label className="font-semibold text-sm">
Alamat
</label>
<textarea
defaultValue="Jl. Merdeka No.123"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
<div>
<label className="font-semibold text-sm">
Role / Jabatan
</label>
<input
type="text"
defaultValue="Users Dashboard"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
<div>
<label className="font-semibold text-sm">
Username
</label>
<input
type="text"
defaultValue="yudha_users"
className="w-full border p-4 rounded-2xl mt-2 text-black"
/>
</div>
</div>
{/* BUTTON */}
<div className="flex gap-4 mt-8">
<button className="bg-blue-600 text-white px-6 py-3 rounded-2xl font-semibold">
Simpan Perubahan
</button>
<button className="border px-6 py-3 rounded-2xl font-semibold">
Reset
</button>
</div>
</div>
{/* RIGHT */}
<div className="col-span-3 space-y-6">
{/* INFO */}
<div
className={`rounded-3xl p-5 shadow-sm ${
darkMode ? "bg-[#1E293B]" : "bg-white"
}`}
>
<h2 className="font-bold text-xl mb-5">
Informasi Akun
</h2>
<div className="space-y-5 text-sm">
<div>
<p className="text-gray-400">ID Akun</p>
<p className="font-semibold">USR-0001</p>
</div>
<div>
<p className="text-gray-400">Terakhir Login</p>
<p className="font-semibold">
15 Mei 2026
</p>
</div>
<div>
<p className="text-gray-400">Perangkat</p>
<p className="font-semibold">
Chrome - Windows
</p>
</div>
<div>
<p className="text-gray-400">Role</p>
<p className="font-semibold">
Users Dashboard
</p>
</div>
</div>
</div>
{/* AKTIVITAS */}
<div
className={`rounded-3xl p-5 shadow-sm ${
darkMode ? "bg-[#1E293B]" : "bg-white"
}`}
>
<h2 className="font-bold text-xl mb-5">
Ringkasan Aktivitas
</h2>
<div className="space-y-4">
<div className="flex justify-between">
<span>Total Login</span>
<span className="font-bold">23 kali</span>
</div>
<div className="flex justify-between text-green-600">
<span>Login Berhasil</span>
<span className="font-bold">22 kali</span>
</div>
<div className="flex justify-between text-red-500">
<span>Login Gagal</span>
<span className="font-bold">1 kali</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

206
src/routes/Transaksi.tsx Normal file
View File

@ -0,0 +1,206 @@
import { Check, X, MapPin, User, UserCheck, Tag } from "lucide-react";
import matematika from "../assets/matematika.png";
import pulang from "../assets/pulang.png";
import buku from "../assets/buku.png";
import filsafat from "../assets/Filsafat.png";
import laptop from "../assets/laptop.png";
import lampu from "../assets/lampu.png";
import komputer from "../assets/komputer.png";
import setrika from "../assets/setrika.png";
import tas from "../assets/tas.png";
import pensil from "../assets/pensil.png";
import pulpen from "../assets/pulpen.png";
import tipex from "../assets/tipex.png";
export default function Transaksi() {
const dataBarang = [
{
id: 1,
nama: "Buku Matematika",
gambar: matematika,
kategori: "Buku",
lokasi: "Gudang A",
penyerah: "user",
penerima: "user",
},
{
id: 2,
nama: "Tas Sekolah",
gambar: tas,
kategori: "Aksesoris",
lokasi: "Ruang 2",
penyerah: "user",
penerima: "user",
},
{
id: 3,
nama: "Laptop ASUS",
gambar: laptop,
kategori: "Elektronik",
lokasi: "Lab Komputer",
penyerah: "user",
penerima: "user",
},
{
id: 4,
nama: "Komputer",
gambar: komputer,
kategori: "Elektronik",
lokasi: "Lab 1",
penyerah: "user",
penerima: "user",
},
{
id: 5,
nama: "Lampu Belajar",
gambar: lampu,
kategori: "Peralatan",
lokasi: "Gudang B",
penyerah: "user",
penerima: "user",
},
{
id: 6,
nama: "Setrika",
gambar: setrika,
kategori: "Elektronik",
lokasi: "Gudang C",
penyerah: "user",
penerima: "user",
},
{
id: 7,
nama: "Pulpen",
gambar: pulpen,
kategori: "ATK",
lokasi: "Ruang Guru",
penyerah: "user",
penerima: "user",
},
{
id: 8,
nama: "Pensil",
gambar: pensil,
kategori: "ATK",
lokasi: "Kelas 12",
penyerah: "user",
penerima: "user",
},
{
id: 9,
nama: "Tipex",
gambar: tipex,
kategori: "ATK",
lokasi: "Gudang",
penyerah: "user",
penerima: "user",
},
{
id: 10,
nama: "Buku Filsafat",
gambar: filsafat,
kategori: "Buku",
lokasi: "Perpustakaan",
penyerah: "user",
penerima: "user",
},
{
id: 11,
nama: "Buku",
gambar: buku,
kategori: "Buku",
lokasi: "Rak Buku",
penyerah: "user",
penerima: "user",
},
{
id: 12,
nama: "Pulang",
gambar: pulang,
kategori: "Lainnya",
lokasi: "Gudang Utama",
penyerah: "user",
penerima: "user",
},
];
return (
<div className="min-h-screen bg-gray-100 p-6">
{/* JUDUL */}
<h1 className="text-3xl font-bold mb-6">Transaksi</h1>
{/* LIST TRANSAKSI */}
<div className="space-y-5">
{dataBarang.map((item) => (
<div
key={item.id}
className="bg-white border rounded-2xl shadow-md p-4 flex items-center justify-between"
>
{/* KIRI */}
<div className="flex items-center gap-5">
{/* GAMBAR */}
<img
src={item.gambar}
alt={item.nama}
className="w-24 h-24 object-cover rounded-xl border"
/>
{/* NAMA BARANG */}
<div>
<h2 className="text-xl font-semibold text-gray-800">
{item.nama}
</h2>
{/* INFO TRANSAKSI */}
<div className="grid grid-cols-2 gap-x-6 gap-y-2 mt-3 text-sm text-gray-600">
<div className="flex items-center gap-2">
<Tag size={16} />
<span>Kategori: {item.kategori}</span>
</div>
<div className="flex items-center gap-2">
<MapPin size={16} />
<span>Lokasi: {item.lokasi}</span>
</div>
<div className="flex items-center gap-2">
<UserCheck size={16} />
<span>Penyerah: {item.penyerah}</span>
</div>
<div className="flex items-center gap-2">
<User size={16} />
<span>Penerima: {item.penerima}</span>
</div>
</div>
</div>
</div>
{/* BUTTON */}
<div className="flex gap-3">
{/* SETUJU */}
<button className="flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition">
<Check size={18} />
Setuju
</button>
{/* TIDAK SETUJU */}
<button className="flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition">
<X size={18} />
Tidak Setuju
</button>
</div>
</div>
))}
</div>
</div>
);
}

View File

@ -0,0 +1,220 @@
import {
FileText,
Search,
Eye,
Download,
Package,
Clock,
CheckCircle,
ShoppingBag,
} from "lucide-react";
export default function TransaksiBASTUsers() {
const transaksi = [
{
no: "BAST-001",
tanggal: "15 Mei 2026",
barang: "Laptop ASUS ROG",
status: "Dipinjam",
},
{
no: "BAST-002",
tanggal: "16 Mei 2026",
barang: "Mouse Logitech",
status: "Dibeli",
},
{
no: "BAST-003",
tanggal: "17 Mei 2026",
barang: "Monitor Samsung",
status: "Dipinjam",
},
];
return (
<div className="min-h-screen bg-gray-100 p-6">
{/* HEADER */}
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-4xl font-bold flex items-center gap-3">
<FileText className="text-blue-600" size={35} />
Transaksi BAST Users
</h1>
<p className="text-gray-500 mt-2">
Riwayat transaksi barang users
</p>
</div>
<button className="bg-blue-600 text-white px-5 py-3 rounded-2xl font-semibold">
+ Tambah Transaksi
</button>
</div>
{/* CARD */}
<div className="grid grid-cols-4 gap-5 mb-8">
<div className="bg-white rounded-3xl p-5 shadow-sm">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400">
Total Transaksi
</p>
<h2 className="text-3xl font-bold mt-2">
120
</h2>
</div>
<Package className="text-blue-600" size={40} />
</div>
</div>
<div className="bg-white rounded-3xl p-5 shadow-sm">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400">
Dipinjam
</p>
<h2 className="text-3xl font-bold mt-2">
85
</h2>
</div>
<Clock className="text-yellow-500" size={40} />
</div>
</div>
<div className="bg-white rounded-3xl p-5 shadow-sm">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400">
Dibeli
</p>
<h2 className="text-3xl font-bold mt-2">
35
</h2>
</div>
<ShoppingBag className="text-green-600" size={40} />
</div>
</div>
<div className="bg-white rounded-3xl p-5 shadow-sm">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-400">
Selesai
</p>
<h2 className="text-3xl font-bold mt-2">
118
</h2>
</div>
<CheckCircle className="text-purple-600" size={40} />
</div>
</div>
</div>
{/* TABLE */}
<div className="bg-white rounded-3xl p-6 shadow-sm">
{/* TOP */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold">
Data Transaksi
</h2>
<div className="relative">
<Search
className="absolute left-4 top-4 text-gray-400"
size={18}
/>
<input
type="text"
placeholder="Cari transaksi..."
className="border pl-11 pr-4 py-3 rounded-2xl outline-none"
/>
</div>
</div>
{/* TABLE */}
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b text-left text-gray-500">
<th className="pb-4">No BAST</th>
<th className="pb-4">Tanggal</th>
<th className="pb-4">Barang</th>
<th className="pb-4">Status</th>
<th className="pb-4">Aksi</th>
</tr>
</thead>
<tbody>
{transaksi.map((item, index) => (
<tr
key={index}
className="border-b hover:bg-gray-50"
>
<td className="py-5 font-semibold">
{item.no}
</td>
<td>{item.tanggal}</td>
<td>{item.barang}</td>
<td>
<span
className={`px-4 py-2 rounded-xl text-sm font-semibold ${
item.status === "Dipinjam"
? "bg-yellow-100 text-yellow-700"
: "bg-green-100 text-green-700"
}`}
>
{item.status}
</span>
</td>
<td>
<div className="flex gap-3">
<button className="bg-blue-100 text-blue-600 p-3 rounded-xl">
<Eye size={18} />
</button>
<button className="bg-green-100 text-green-600 p-3 rounded-xl">
<Download size={18} />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}

7
tailwind.config.js Normal file
View File

@ -0,0 +1,7 @@
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}

25
tsconfig.app.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023", "DOM"],
"module": "esnext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

24
tsconfig.node.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"module": "esnext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})