From 3006d1332c01c1031e8c491e045ea6d8efdf80be Mon Sep 17 00:00:00 2001
From: zadit biasa aja <75159257+insvrgent@users.noreply.github.com>
Date: Thu, 26 Jun 2025 09:28:19 +0000
Subject: [PATCH] ok
---
package-lock.json | 50 ++++
package.json | 1 +
src/App.js | 9 +-
src/Dashboard.js | 24 ++
src/Dashboard.module.css | 26 ++
src/KTPScanner.js | 458 ++++++++++++++++++-----------
src/RoleCard.js | 0
src/components/Chart.js | 8 +
src/components/Chart.module.css | 11 +
src/components/Header.js | 13 +
src/components/Header.module.css | 5 +
src/components/RoleCard.js | 14 +
src/components/RoleCard.module.css | 25 ++
src/components/Sidebar.js | 18 ++
src/components/Sidebar.module.css | 17 ++
src/index.js | 22 +-
16 files changed, 514 insertions(+), 187 deletions(-)
create mode 100644 src/Dashboard.js
create mode 100644 src/Dashboard.module.css
create mode 100644 src/RoleCard.js
create mode 100644 src/components/Chart.js
create mode 100644 src/components/Chart.module.css
create mode 100644 src/components/Header.js
create mode 100644 src/components/Header.module.css
create mode 100644 src/components/RoleCard.js
create mode 100644 src/components/RoleCard.module.css
create mode 100644 src/components/Sidebar.js
create mode 100644 src/components/Sidebar.module.css
diff --git a/package-lock.json b/package-lock.json
index d078af7..2f7fa4e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-router-dom": "^7.6.2",
"react-scripts": "5.0.1",
"tesseract.js": "^6.0.1",
"web-vitals": "^2.1.4"
@@ -12944,6 +12945,50 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz",
+ "integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.2.tgz",
+ "integrity": "sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==",
+ "dependencies": {
+ "react-router": "7.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-router/node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -13781,6 +13826,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
diff --git a/package.json b/package.json
index 67be00c..4ab9a48 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-router-dom": "^7.6.2",
"react-scripts": "5.0.1",
"tesseract.js": "^6.0.1",
"web-vitals": "^2.1.4"
diff --git a/src/App.js b/src/App.js
index 11ef573..f324308 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,11 +1,14 @@
-import logo from "./logo.svg";
-import "./App.css";
+import { Routes, Route } from "react-router-dom";
import CameraKtp from "./KTPScanner";
+import Dashboard from "./Dashboard";
function App() {
return (
-
+
+ } />
+ } />
+
);
}
diff --git a/src/Dashboard.js b/src/Dashboard.js
new file mode 100644
index 0000000..92a1a20
--- /dev/null
+++ b/src/Dashboard.js
@@ -0,0 +1,24 @@
+import React from "react";
+import styles from "./Dashboard.module.css";
+import Header from "./components/Header";
+import Sidebar from "./components/Sidebar";
+import RoleCard from "./components/RoleCard";
+import Chart from "./components/Chart";
+
+const Dashboard = () => {
+ return (
+
+ );
+};
+
+export default Dashboard;
diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css
new file mode 100644
index 0000000..18c95cf
--- /dev/null
+++ b/src/Dashboard.module.css
@@ -0,0 +1,26 @@
+.dashboard {
+ display: flex;
+ width: 100%;
+ max-width: 1200px;
+ background: white;
+ border-radius: 20px;
+ overflow: hidden;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+}
+
+.mainContent {
+ flex: 1;
+ padding: 1rem;
+}
+
+.cards {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+@media (max-width: 768px) {
+ .dashboard {
+ flex-direction: column;
+ }
+}
diff --git a/src/KTPScanner.js b/src/KTPScanner.js
index cda6783..4fe3b49 100644
--- a/src/KTPScanner.js
+++ b/src/KTPScanner.js
@@ -1,17 +1,26 @@
import React, { useEffect, useRef, useState } from "react";
+import { v4 as uuidv4 } from "uuid";
const STORAGE_KEY = "camera_canvas_gallery";
const CameraCanvas = () => {
const videoRef = useRef(null);
- const canvasRef = useRef(null); // visible canvas
- const hiddenCanvasRef = useRef(null); // hidden canvas for capture
+ const canvasRef = useRef(null);
+ const hiddenCanvasRef = useRef(null);
const [capturedImage, setCapturedImage] = useState(null);
const [galleryImages, setGalleryImages] = useState([]);
+ const [fileTemp, setFileTemp] = useState(null);
const [isFreeze, setIsFreeze] = useState(false);
- const freezeFrameRef = useRef(null); // menyimpan freeze frame imageData
+ const freezeFrameRef = useRef(null);
+
+ const rectRef = useRef({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ radius: 20,
+ });
- // Fungsi untuk gambar rounded rectangle
const drawRoundedRect = (ctx, x, y, width, height, radius) => {
ctx.beginPath();
ctx.moveTo(x + radius, y);
@@ -29,13 +38,9 @@ const CameraCanvas = () => {
ctx.stroke();
};
- // Fungsi untuk mewarnai area luar rectangle dengan hitam semi transparan
const fillOutsideRect = (ctx, rect, canvasWidth, canvasHeight) => {
ctx.save();
-
const { x, y, width, height, radius } = rect;
-
- // Buat path rounded rectangle
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
@@ -47,39 +52,20 @@ const CameraCanvas = () => {
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
-
- // Buat clipping inverse area (area luar rectangle)
ctx.rect(0, 0, canvasWidth, canvasHeight);
-
- // Fill dengan mode 'evenodd' supaya area di luar path rectangle terisi
- ctx.fillStyle = "rgba(173, 173, 173, 1)"; // hitam semi transparan
+ ctx.fillStyle = "rgba(173, 173, 173, 1)";
ctx.fill("evenodd");
-
ctx.restore();
};
- // Variabel global untuk posisi rectangle dan ukurannya supaya bisa dipakai di shootImage
- const rectRef = useRef({
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- radius: 20,
- });
-
useEffect(() => {
- // Load gallery dari localStorage saat pertama kali mount
const savedGallery = localStorage.getItem(STORAGE_KEY);
- if (savedGallery) {
- setGalleryImages(JSON.parse(savedGallery));
- }
+ if (savedGallery) setGalleryImages(JSON.parse(savedGallery));
const getCameraStream = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
- video: {
- facingMode: { ideal: "environment" },
- },
+ video: { facingMode: { ideal: "environment" } },
audio: false,
});
@@ -88,26 +74,21 @@ const CameraCanvas = () => {
videoRef.current.onloadedmetadata = () => {
videoRef.current.play();
-
const video = videoRef.current;
- const canvas = canvasRef.current; // visible canvas
- const hiddenCanvas = hiddenCanvasRef.current; // hidden canvas
+ const canvas = canvasRef.current;
+ const hiddenCanvas = hiddenCanvasRef.current;
const ctx = canvas.getContext("2d");
- // Set ukuran canvas sesuai video asli
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
-
- // Style visible canvas supaya scaled sesuai container dan tidak overflow
canvas.style.maxWidth = "100%";
canvas.style.height = "auto";
hiddenCanvas.width = video.videoWidth;
hiddenCanvas.height = video.videoHeight;
- // Hitung ukuran rectangle KTP
const rectWidth = canvas.width * 0.9;
- const rectHeight = (53.98 / 85.6) * rectWidth; // aspek rasio KTP
+ const rectHeight = (53.98 / 85.6) * rectWidth;
const rectX = (canvas.width - rectWidth) / 2;
const rectY = (canvas.height - rectHeight) / 2;
@@ -122,38 +103,26 @@ const CameraCanvas = () => {
const drawToCanvas = () => {
if (video.readyState === 4) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
-
if (isFreeze && freezeFrameRef.current) {
- // Tampilkan freeze frame yang sudah disimpan
ctx.putImageData(freezeFrameRef.current, 0, 0);
-
- drawRoundedRect(
- ctx,
- rectRef.current.x,
- rectRef.current.y,
- rectRef.current.width,
- rectRef.current.height,
- rectRef.current.radius
- );
-
- // Overlay area luar rectangle dengan hitam
+ } else {
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+ }
+ drawRoundedRect(
+ ctx,
+ rectRef.current.x,
+ rectRef.current.y,
+ rectRef.current.width,
+ rectRef.current.height,
+ rectRef.current.radius
+ );
+ if (isFreeze) {
fillOutsideRect(
ctx,
rectRef.current,
canvas.width,
canvas.height
);
- } else {
- // Render video live + rectangle
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
- drawRoundedRect(
- ctx,
- rectRef.current.x,
- rectRef.current.y,
- rectRef.current.width,
- rectRef.current.height,
- rectRef.current.radius
- );
}
}
requestAnimationFrame(drawToCanvas);
@@ -170,65 +139,6 @@ const CameraCanvas = () => {
getCameraStream();
}, [isFreeze]);
- useEffect(() => {
- const canvas = canvasRef.current;
- const ctx = canvas.getContext("2d");
- const video = videoRef.current;
-
- // Hitung posisi rectangle sekali
- const rectWidth = canvas.width * 0.9;
- const rectHeight = (53.98 / 85.6) * rectWidth;
- const rectX = (canvas.width - rectWidth) / 2;
- const rectY = (canvas.height - rectHeight) / 2;
-
- rectRef.current = {
- x: rectX,
- y: rectY,
- width: rectWidth,
- height: rectHeight,
- radius: 20,
- };
-
- let animationFrameId;
-
- const drawToCanvas = () => {
- if (video.readyState === 4) {
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- if (isFreeze && freezeFrameRef.current) {
- ctx.putImageData(freezeFrameRef.current, 0, 0);
-
- drawRoundedRect(
- ctx,
- rectRef.current.x,
- rectRef.current.y,
- rectRef.current.width,
- rectRef.current.height,
- rectRef.current.radius
- );
-
- // Overlay area luar rectangle dengan hitam
- fillOutsideRect(ctx, rectRef.current, canvas.width, canvas.height);
- } else {
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
- drawRoundedRect(
- ctx,
- rectRef.current.x,
- rectRef.current.y,
- rectRef.current.width,
- rectRef.current.height,
- rectRef.current.radius
- );
- }
- }
- animationFrameId = requestAnimationFrame(drawToCanvas);
- };
-
- drawToCanvas();
-
- return () => cancelAnimationFrame(animationFrameId);
- }, [isFreeze]);
-
- // Fungsi untuk capture gambar area rectangle dan simpan ke localStorage + freeze effect
const shootImage = () => {
const video = videoRef.current;
const { x, y, width, height } = rectRef.current;
@@ -236,27 +146,21 @@ const CameraCanvas = () => {
const hiddenCtx = hiddenCanvas.getContext("2d");
const visibleCtx = canvasRef.current.getContext("2d");
- // Ambil image data canvas visible untuk freeze frame
freezeFrameRef.current = visibleCtx.getImageData(
0,
0,
canvasRef.current.width,
canvasRef.current.height
);
-
- // Aktifkan freeze frame
setIsFreeze(true);
- // Tangkap gambar video ke hidden canvas
hiddenCtx.drawImage(video, 0, 0, hiddenCanvas.width, hiddenCanvas.height);
- // Buat canvas crop
const cropCanvas = document.createElement("canvas");
cropCanvas.width = Math.floor(width);
cropCanvas.height = Math.floor(height);
const cropCtx = cropCanvas.getContext("2d");
- // Crop area rectangle dari hidden canvas
cropCtx.drawImage(
hiddenCanvas,
Math.floor(x),
@@ -273,7 +177,88 @@ const CameraCanvas = () => {
setCapturedImage(imageDataUrl);
};
- // Fungsi hapus gambar dari gallery
+ const ReadImage = async (capturedImage) => {
+ const imageId = uuidv4();
+
+ try {
+ let res = await fetch(
+ "https://bot.kediritechnopark.com/webhook/mastersnapper/read",
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ imageId, image: capturedImage }),
+ }
+ );
+
+ const { output } = await res.json();
+
+ // Bersihkan dan parsing JSON dari output
+ const jsonString = output
+ .replace(/^```json/, "")
+ .replace(/```$/, "")
+ .trim();
+
+ const data = JSON.parse(jsonString);
+
+ const newImage = {
+ imageId,
+ NIK: data.NIK || "",
+ Nama: data.Nama || "",
+ TTL: data.TTL || "",
+ Kelamin: data.Kelamin || "",
+ Alamat: data.Alamat || "",
+ RtRw: data["RT/RW"] || "",
+ KelDesa: data["Kel/Desa"] || "",
+ Kec: data.Kec || "",
+ Agama: data.Agama || "",
+ Hingga: data.Hingga || "",
+ Pembuatan: data.Pembuatan || "",
+ Kota: data["Kota Pembuatan"] || "",
+ };
+
+ setFileTemp(newImage);
+ } catch (error) {
+ console.error("Failed to read image:", error);
+ }
+ };
+
+ const handleSaveTemp = async () => {
+ try {
+ await fetch(
+ "https://bot.kediritechnopark.com/webhook/mastersnapper/save",
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ fileTemp }),
+ }
+ );
+
+ const updatedGallery = [fileTemp, ...galleryImages];
+ setGalleryImages(updatedGallery);
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedGallery));
+ setFileTemp(null);
+ } catch (err) {
+ console.error("Gagal menyimpan ke server:", err);
+ }
+ };
+
+ const handleDeleteTemp = async () => {
+ try {
+ await fetch(
+ "https://bot.kediritechnopark.com/webhook/mastersnapper/delete",
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ fileTemp }),
+ }
+ );
+
+ setFileTemp(null);
+ } catch (err) {
+ console.error("Gagal menghapus dari server:", err);
+ }
+ };
+
const removeImage = (index) => {
const newGallery = [...galleryImages];
newGallery.splice(index, 1);
@@ -281,9 +266,128 @@ const CameraCanvas = () => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(newGallery));
};
- // Rasio KTP
const aspectRatio = 53.98 / 85.6;
+ const handleManualUpload = async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ const imageDataUrl = reader.result;
+ setCapturedImage(imageDataUrl);
+ setIsFreeze(true);
+
+ // Create an image object from the uploaded file
+ const image = new Image();
+ image.onload = () => {
+ // Get the width of the rounded rectangle from rectRef
+ const rectWidth = rectRef.current.width;
+ const rectHeight = rectRef.current.height;
+
+ // Create a canvas to draw the uploaded image
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext("2d");
+
+ // Set the scale factor based on the rectangle width
+ const scaleFactor = rectWidth / image.width;
+
+ // Calculate the new height based on the aspect ratio
+ const newHeight = image.height * scaleFactor;
+
+ // Clear the canvas and draw the video or freeze frame
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (isFreeze && freezeFrameRef.current) {
+ ctx.putImageData(freezeFrameRef.current, 0, 0);
+ } else {
+ const video = videoRef.current;
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+ }
+
+ // Draw the rounded rectangle
+ drawRoundedRect(
+ ctx,
+ rectRef.current.x,
+ rectRef.current.y,
+ rectRef.current.width,
+ rectRef.current.height,
+ rectRef.current.radius
+ );
+
+ // Draw the image inside the rounded rectangle
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(
+ rectRef.current.x + rectRef.current.radius,
+ rectRef.current.y
+ );
+ ctx.lineTo(
+ rectRef.current.x + rectRef.current.width - rectRef.current.radius,
+ rectRef.current.y
+ );
+ ctx.quadraticCurveTo(
+ rectRef.current.x + rectRef.current.width,
+ rectRef.current.y,
+ rectRef.current.x + rectRef.current.width,
+ rectRef.current.y + rectRef.current.radius
+ );
+ ctx.lineTo(
+ rectRef.current.x + rectRef.current.width,
+ rectRef.current.y + rectRef.current.height - rectRef.current.radius
+ );
+ ctx.quadraticCurveTo(
+ rectRef.current.x + rectRef.current.width,
+ rectRef.current.y + rectRef.current.height,
+ rectRef.current.x + rectRef.current.width - rectRef.current.radius,
+ rectRef.current.y + rectRef.current.height
+ );
+ ctx.lineTo(
+ rectRef.current.x + rectRef.current.radius,
+ rectRef.current.y + rectRef.current.height
+ );
+ ctx.quadraticCurveTo(
+ rectRef.current.x,
+ rectRef.current.y + rectRef.current.height,
+ rectRef.current.x,
+ rectRef.current.y + rectRef.current.height - rectRef.current.radius
+ );
+ ctx.lineTo(
+ rectRef.current.x,
+ rectRef.current.y + rectRef.current.radius
+ );
+ ctx.quadraticCurveTo(
+ rectRef.current.x,
+ rectRef.current.y,
+ rectRef.current.x + rectRef.current.radius,
+ rectRef.current.y
+ );
+ ctx.closePath();
+ ctx.clip(); // Clip the image within the rounded rectangle
+
+ // Draw the uploaded image inside the clipped region
+ ctx.drawImage(
+ image,
+ rectRef.current.x,
+ rectRef.current.y,
+ rectWidth,
+ newHeight // Height is scaled based on the image's aspect ratio
+ );
+
+ ctx.restore();
+
+ // Save the image data into the freeze frame reference
+ freezeFrameRef.current = ctx.getImageData(
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ );
+ };
+ image.src = imageDataUrl;
+ };
+ reader.readAsDataURL(file);
+ };
+
return (
diff --git a/src/RoleCard.js b/src/RoleCard.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/Chart.js b/src/components/Chart.js
new file mode 100644
index 0000000..87a575c
--- /dev/null
+++ b/src/components/Chart.js
@@ -0,0 +1,8 @@
+import React from "react";
+import styles from "./Chart.module.css";
+
+const Chart = () => {
+ return [Chart Here]
;
+};
+
+export default Chart;
diff --git a/src/components/Chart.module.css b/src/components/Chart.module.css
new file mode 100644
index 0000000..96636da
--- /dev/null
+++ b/src/components/Chart.module.css
@@ -0,0 +1,11 @@
+.chart {
+ margin-top: 2rem;
+ height: 200px;
+ background: linear-gradient(to top, #fdd, #fff);
+ border-radius: 12px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: #df3422;
+ font-weight: 600;
+}
diff --git a/src/components/Header.js b/src/components/Header.js
new file mode 100644
index 0000000..c3ebf36
--- /dev/null
+++ b/src/components/Header.js
@@ -0,0 +1,13 @@
+// Header.js
+import React from "react";
+import styles from "./Header.module.css";
+
+const Header = () => {
+ return (
+
+ );
+};
+
+export default Header;
diff --git a/src/components/Header.module.css b/src/components/Header.module.css
new file mode 100644
index 0000000..36010e7
--- /dev/null
+++ b/src/components/Header.module.css
@@ -0,0 +1,5 @@
+.header {
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+}
diff --git a/src/components/RoleCard.js b/src/components/RoleCard.js
new file mode 100644
index 0000000..9575935
--- /dev/null
+++ b/src/components/RoleCard.js
@@ -0,0 +1,14 @@
+import React from "react";
+import styles from "./RoleCard.module.css";
+
+const RoleCard = ({ title, value, code }) => {
+ return (
+
+
{title}
+
{value}
+ {code &&
{code}
}
+
+ );
+};
+
+export default RoleCard;
diff --git a/src/components/RoleCard.module.css b/src/components/RoleCard.module.css
new file mode 100644
index 0000000..048516d
--- /dev/null
+++ b/src/components/RoleCard.module.css
@@ -0,0 +1,25 @@
+.card {
+ background: #fff;
+ border-radius: 16px;
+ padding: 1rem;
+ flex: 1;
+ min-width: 160px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+}
+
+.title {
+ font-weight: 600;
+ color: #555;
+}
+
+.value {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: #000;
+}
+
+.code {
+ font-size: 0.9rem;
+ color: #df3422;
+ margin-top: 0.5rem;
+}
diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js
new file mode 100644
index 0000000..d68cf22
--- /dev/null
+++ b/src/components/Sidebar.js
@@ -0,0 +1,18 @@
+// Sidebar.js
+import React from "react";
+import styles from "./Sidebar.module.css";
+
+const Sidebar = () => {
+ return (
+
+
Dashboard
+
+
Officers
+
Roles
+
Key Performances
+
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/components/Sidebar.module.css b/src/components/Sidebar.module.css
new file mode 100644
index 0000000..303756d
--- /dev/null
+++ b/src/components/Sidebar.module.css
@@ -0,0 +1,17 @@
+.sidebar {
+ width: 200px;
+ background-color: #df3422;
+ color: white;
+ padding: 1rem;
+}
+
+.logo {
+ font-size: 1.25rem;
+ font-weight: bold;
+ margin-bottom: 2rem;
+}
+
+.menuItem {
+ margin: 1rem 0;
+ cursor: pointer;
+}
diff --git a/src/index.js b/src/index.js
index d563c0f..43850af 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,17 +1,15 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
+// index.js
+import React from "react";
+import ReactDOM from "react-dom/client"; // ✅ use 'react-dom/client' in React 18+
+import { BrowserRouter } from "react-router-dom";
+import App from "./App";
-const root = ReactDOM.createRoot(document.getElementById('root'));
+// ✅ createRoot instead of render
+const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
-
+
+
+
);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();