rsabhk-fe

This commit is contained in:
Rizqi30 2025-04-28 00:02:47 +07:00
commit ad7d2aedd4
63 changed files with 18152 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
VITE_API_URL = "http://127.0.0.1:8000/"

25
.eslintrc.cjs Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
settings: { react: { version: "18.2" } },
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off",
"react/display-name": "off",
"react-refresh/only-export-components": "off",
},
};

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# 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?
# Contentlayer
.contentlayer

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# React + 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/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rumah Sakit</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;500;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

12864
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "poliklinik_bk",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.0.1",
"axios": "^1.6.2",
"flowbite-react": "^0.7.2",
"framer-motion": "^10.17.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-redux": "^9.0.4",
"react-router-dom": "^6.21.1",
"react-select": "^5.8.0",
"react-toastify": "^9.1.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"sweetalert2": "^11.10.1",
"swiper": "^11.0.5"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
"vite": "^5.0.8"
}
}

6
postcss.config.js Normal file
View File

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

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

70
src/App.jsx Normal file
View File

@ -0,0 +1,70 @@
import "./index.css";
import { Route, Routes, useLocation } from "react-router-dom";
import Sidebar from "./components/Sidebar";
import DashboardAdmin from "./pages/Admin/DashboardAdmin";
import Dokter from "./pages/Admin/Dokter";
import Pasien from "./pages/Admin/Pasien";
import Poli from "./pages/Admin/Poli";
import Obat from "./pages/Admin/Obat";
import JadwalPeriksa from "./pages/Dokter/JadwalPeriksa/JadwalPeriksa";
import DaftarPeriksa from "./pages/Dokter/PeriksaPasien/DaftarPeriksa";
import RiwayatPasien from "./pages/Dokter/RiwayatPasien";
import ProfileDokter from "./pages/Dokter/ProfileDokter";
import KelolaJadwalPeriksa from "./pages/Dokter/JadwalPeriksa/KelolaJadwalPeriksa";
import PeriksaPasien from "./pages/Dokter/PeriksaPasien/PeriksaPasien";
import Home from "./pages/Home";
// Import Swiper styles
import "swiper/css";
import "swiper/css/effect-coverflow";
import "swiper/css/pagination";
import LoginPortal from "./pages/Auth/LoginPortal";
import { Provider } from "react-redux";
import { store } from "./config";
import "react-toastify/dist/ReactToastify.css";
import { AnimatePresence } from "framer-motion";
import DashboardDokter from "./pages/Dokter/DashboardDokter";
function App() {
const location = useLocation();
return (
<>
<Provider store={store}>
<AnimatePresence mode="wait" initial={false}>
<Routes location={location} key={location.pathname}>
<Route path="/" element={<Home />} />
<Route path="/:id" element={<Home />} />
<Route path="/login" element={<LoginPortal />} />
<Route path="/admin" element={<Sidebar />}>
<Route path=":id" element={<DashboardAdmin />} />
<Route path="doctors/:id" element={<Dokter />} />
<Route path="pasien/:id" element={<Pasien />} />
<Route path="poli/:id" element={<Poli />} />
<Route path="obat/:id" element={<Obat />} />
</Route>
<Route path="/dokter" element={<Sidebar />}>
<Route path=":id" element={<DashboardDokter />} />
<Route path="jadwal-periksa/:id" element={<JadwalPeriksa />} />
<Route
path="jadwal-periksa/kelola-jadwal/:id"
element={<KelolaJadwalPeriksa />}
/>
<Route
path="jadwal-periksa/kelola-jadwal/:idJadwal/:id"
element={<KelolaJadwalPeriksa />}
/>
<Route path="daftar-periksa/:id" element={<DaftarPeriksa />} />
<Route
path="daftar-periksa/periksa/:idPasien/:id"
element={<PeriksaPasien />}
/>
<Route path="riwayat-pasien/:id" element={<RiwayatPasien />} />
<Route path="profile/:id" element={<ProfileDokter />} />
</Route>
</Routes>
</AnimatePresence>
</Provider>
</>
);
}
export default App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
src/assets/logo/udinus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

1
src/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

33
src/components/Input.jsx Normal file
View File

@ -0,0 +1,33 @@
import { Label, TextInput } from "flowbite-react";
const Input = ({
id,
label,
type,
placeholder,
name,
onChange,
value,
disabled,
}) => {
return (
<div className="my-3">
<div className="mb-1 block">
<Label htmlFor={id} value={label} />
</div>
<TextInput
id={id}
type={type}
sizing="md"
placeholder={placeholder}
name={name}
onChange={onChange}
value={value}
disabled={disabled}
required
/>
</div>
);
};
export default Input;

28
src/components/Modals.jsx Normal file
View File

@ -0,0 +1,28 @@
import { Button, Modal } from "flowbite-react";
const Modals = ({
openModal,
setOpenModal,
title,
body,
size,
buttonClose = true,
}) => {
return (
<>
{openModal && (
<Modal show={openModal} onClose={() => setOpenModal(false)} size={size}>
<Modal.Header>{title}</Modal.Header>
<Modal.Body>{body}</Modal.Body>
{buttonClose && (
<Modal.Footer className="flex justify-end">
<Button onClick={() => setOpenModal(false)}>Close</Button>
</Modal.Footer>
)}
</Modal>
)}
</>
);
};
export default Modals;

View File

@ -0,0 +1,83 @@
import { Dropdown } from "flowbite-react";
import { GiHamburgerMenu } from "react-icons/gi";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate } from "react-router-dom";
import { logoutAdmin } from "../config/Redux/Action/adminAction";
import { logoutDokter } from "../config/Redux/Action/dokterAction";
import { FaAngleDown } from "react-icons/fa6";
import { HiLogout } from "react-icons/hi";
const NavbarDashboard = ({ isSidebarOpen, setSidebarOpen }) => {
const { admin } = useSelector((state) => state.adminReducer);
const { dokter } = useSelector((state) => state.dokterReducer);
const dispatch = useDispatch();
const nav = useNavigate();
const token = localStorage.getItem("token");
const role = localStorage.getItem("role");
const handleLogoutAdmin = () => {
dispatch(logoutAdmin(token, nav));
};
const handleLogoutDokter = () => {
dispatch(logoutDokter(token, nav));
};
return (
<nav className="flex items-center justify-between flex-wrap bg-white p-6 w-full h-[5rem] shadow-md">
<div className="flex items-center">
<GiHamburgerMenu
size={20}
onClick={() => setSidebarOpen(!isSidebarOpen)}
className="cursor-pointer"
/>
<Link to="/" className="ml-5 font-bold text-gray-800">
Home
</Link>
<Link to="/" className="ml-3 font-bold text-gray-800">
Contact
</Link>
</div>
<div className="flex items-center">
<Dropdown
renderTrigger={() => (
<div className="flex items-center cursor-pointer">
<div className="flex flex-col justify-center items-end">
<h3 className=" font-bold text-black">
{admin.username ? admin.username : dokter.username}
</h3>
<span className=" text-[12px] text-black">
{admin.role ? admin.role : dokter.role}
</span>
</div>
<FaAngleDown className="text-black mr-2 ml-1" />
</div>
)}
dismissOnClick={false}
className="flex flex-col items-center py-3 px-5"
>
<button
className="flex items-center bg-red-500 hover:bg-red-600 p-2 px-3 text-white rounded-md w-full"
onClick={
role === "admin"
? () => handleLogoutAdmin()
: () => handleLogoutDokter()
}
>
<HiLogout className="mr-2" />
Logout
</button>
</Dropdown>
<img
src="https://i.pravatar.cc/150?img=3"
alt="user"
className="w-10 h-10 rounded-full"
/>
</div>
</nav>
);
};
export default NavbarDashboard;

View File

@ -0,0 +1,20 @@
import Select from "react-select";
const ReactSelect = ({ data, title, onChange, disabled, isMulti, value }) => {
return (
<>
<div className="my-3">
<label className="text-black text-sm font-normal mb-5">{title}</label>
<Select
options={data}
onChange={onChange}
isDisabled={disabled}
isMulti={isMulti}
value={value}
/>
</div>
</>
);
};
export default ReactSelect;

View File

@ -0,0 +1,18 @@
import { Label, Select } from "flowbite-react";
const Selects = ({ id, label, selectData }) => {
return (
<div className="w-full my-2">
<div className="mb-2 block">
<Label htmlFor={id} value={label} />
</div>
<Select id={id} required>
{selectData?.map((data, index) => (
<option key={index}>{data}</option>
))}
</Select>
</div>
);
};
export default Selects;

191
src/components/Sidebar.jsx Normal file
View File

@ -0,0 +1,191 @@
import SidebarItem from "./SidebarItems";
import { MdSpaceDashboard } from "react-icons/md";
import {
FaHospital,
FaNotesMedical,
FaPills,
FaStethoscope,
FaUser,
FaUserInjured,
} from "react-icons/fa";
import { FaUserDoctor } from "react-icons/fa6";
import udinus from "../assets/logo/udinus.png";
import NavbarDashboard from "./NavbarDashboard";
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom";
import { CiMedicalClipboard } from "react-icons/ci";
import { useEffect, useState } from "react";
import { getAdmin } from "../config/Redux/Action/adminAction";
import { useDispatch, useSelector } from "react-redux";
import { getDokter } from "../config/Redux/Action/dokterAction";
import { motion } from "framer-motion";
import { ToastContainer } from "react-toastify";
const Sidebars = () => {
const pathName = useLocation().pathname;
const { id } = useParams();
const { admin, isLogin } = useSelector((state) => state.adminReducer);
const { dokter, isLoginDokter } = useSelector((state) => state.dokterReducer);
const nav = useNavigate();
const dispatch = useDispatch();
const token = localStorage.getItem("token");
const role = localStorage.getItem("role");
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
useEffect(() => {
if (role === "admin") {
if (!token || id === undefined) {
dispatch(getAdmin(token));
if (isLogin) {
nav("/" + admin.id);
}
} else {
dispatch(getAdmin(token));
}
}
}, [dispatch, id, isLogin, nav, token, admin.id, role]);
useEffect(() => {
if (role === "dokter") {
if (!token || id === undefined) {
dispatch(getDokter(token));
if (isLoginDokter) {
nav("/" + dokter.id);
}
} else {
dispatch(getDokter(token));
}
}
}, [dispatch, id, isLoginDokter, nav, token, dokter.id, role]);
useEffect(() => {
if (!admin || !dokter) {
window.location.href = "/login";
}
}, [admin, dokter]);
return (
<>
<ToastContainer />
<div className="flex h-screen">
<motion.nav
className={`sidebar bg-[#00008B] w-fit p-2 h-screen`}
initial={{ width: "auto" }}
animate={isSidebarOpen ? { width: "auto" } : { width: 0, padding: 0 }}
>
<div className="flex flex-row items-center justify-start p-4">
<img src={udinus} alt="udinus" className="w-10 h-10" />
<h1 className="ml-3 text-white font-bold">Poliklinik BK</h1>
</div>
<hr className="border-[#5C8374]" />
<ul className="flex flex-col p-2">
{role === "admin" && (
<>
<SidebarItem
icons={<MdSpaceDashboard color="white" size={20} />}
text="Dashboard"
role="Admin"
to={"/admin/" + id}
className={pathName == "/admin/" + id ? "bg-[#5C8374]" : ""}
/>
<SidebarItem
icons={<FaUserDoctor color="white" size={20} />}
text="Dokter"
role="Admin"
to={"/admin/doctors/" + id}
className={
pathName == "/admin/doctors/" + id ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaUserInjured color="white" size={20} />}
text="Pasien"
role="Admin"
to={"/admin/pasien/" + id}
className={
pathName == "/admin/pasien/" + id ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaHospital color="white" size={20} />}
text="Poli"
role="Admin"
to={"/admin/poli/" + id}
className={
pathName == "/admin/poli/" + id ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaPills color="white" size={20} />}
text="Obat"
role="Admin"
to={"/admin/obat/" + id}
className={
pathName == "/admin/obat/" + id ? "bg-[#5C8374]" : ""
}
/>
</>
)}
{role === "dokter" && (
<>
<SidebarItem
icons={<MdSpaceDashboard color="white" size={20} />}
text="Dashboard"
role="Dokter"
to={"/dokter/" + id}
className={pathName == "/dokter" ? "bg-[#5C8374]" : ""}
/>
<SidebarItem
icons={<CiMedicalClipboard color="white" size={20} />}
text="Jadwal Periksa"
role="Dokter"
to={"/dokter/jadwal-periksa/" + id}
className={
pathName == "/dokter/jadwal-periksa" ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaStethoscope color="white" size={20} />}
text="Memeriksa Pasien"
role="Dokter"
to={"/dokter/daftar-periksa/" + id}
className={
pathName == "/dokter/daftar-periksa" ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaNotesMedical color="white" size={20} />}
text="Riwayat Pasien"
role="Dokter"
to={"/dokter/riwayat-pasien/" + id}
className={
pathName == "/dokter/riwayat-pasien" ? "bg-[#5C8374]" : ""
}
/>
<SidebarItem
icons={<FaUser color="white" size={20} />}
text="Profil"
role="Dokter"
to={"/dokter/profile/" + id}
className={
pathName == "/dokter/profile" ? "bg-[#5C8374]" : ""
}
/>
</>
)}
</ul>
</motion.nav>
<div className={`flex flex-col overflow-auto w-full`}>
<NavbarDashboard
isSidebarOpen={isSidebarOpen}
setSidebarOpen={setIsSidebarOpen}
/>
<main className="bg-[#F0F4F8] overflow-y-auto max-h-[100vh] w-full flex justify-center">
<Outlet context={[role]} />
</main>
</div>
</div>
</>
);
};
export default Sidebars;

View File

@ -0,0 +1,30 @@
import { Link } from "react-router-dom";
const SidebarItems = ({ icons, text, role, to, className }) => {
return (
<Link to={to}>
<li
className={
"flex flex-row items-center justify-between w-[18rem] p-4 my-1 hover:bg-[#1E90FF] rounded-md cursor-pointer " +
className
}
>
<div className="flex">
{icons}
<h2 className="text-white font-bold ml-2">{text}</h2>
</div>
<span
className={`${
role == "Dokter"
? "bg-red-500 text-white text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300"
: "bg-green-500 text-white text-sm font-medium px-1 rounded dark:bg-green-900 dark:text-green-300 text-[10px]"
}`}
>
{role}
</span>
</li>
</Link>
);
};
export default SidebarItems;

View File

@ -0,0 +1,131 @@
import { EffectCoverflow, Pagination } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
const SwiperCoverflow = () => {
return (
<Swiper
effect={"coverflow"}
grabCursor={true}
centeredSlides={true}
slidesPerView={"auto"}
loop={true}
coverflowEffect={{
rotate: 50,
stretch: 0,
depth: 100,
modifier: 1,
slideShadows: true,
}}
modules={[EffectCoverflow, Pagination]}
className="mySwiper my-4"
>
<SwiperSlide>
<div className="card border p-5 rounded hover:shadow-lg">
<div className="card-body">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
voluptatibus.
</p>
<div className="flex justify-center items-center mt-4">
<img
src="https://www.w3schools.com/howto/img_avatar.png"
className="rounded-full h-20 w-20"
alt="avatar"
/>
<div className="ml-4">
<h1 className="text-xl font-medium">El Macho</h1>
<p className="text-gray-700">Pasien</p>
</div>
</div>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="card border p-5 rounded hover:shadow-lg">
<div className="card-body">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
voluptatibus.
</p>
<div className="flex justify-center items-center mt-4">
<img
src="https://www.w3schools.com/howto/img_avatar.png"
className="rounded-full h-20 w-20"
alt="avatar"
/>
<div className="ml-4">
<h1 className="text-xl font-medium">El Macho</h1>
<p className="text-gray-700">Pasien</p>
</div>
</div>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="card border p-5 rounded hover:shadow-lg">
<div className="card-body">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
voluptatibus.
</p>
<div className="flex justify-center items-center mt-4">
<img
src="https://www.w3schools.com/howto/img_avatar.png"
className="rounded-full h-20 w-20"
alt="avatar"
/>
<div className="ml-4">
<h1 className="text-xl font-medium">El Macho</h1>
<p className="text-gray-700">Pasien</p>
</div>
</div>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="card border p-5 rounded hover:shadow-lg">
<div className="card-body">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
voluptatibus.
</p>
<div className="flex justify-center items-center mt-4">
<img
src="https://www.w3schools.com/howto/img_avatar.png"
className="rounded-full h-20 w-20"
alt="avatar"
/>
<div className="ml-4">
<h1 className="text-xl font-medium">El Macho</h1>
<p className="text-gray-700">Pasien</p>
</div>
</div>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="card border p-5 rounded hover:shadow-lg">
<div className="card-body">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
voluptatibus.
</p>
<div className="flex justify-center items-center mt-4">
<img
src="https://www.w3schools.com/howto/img_avatar.png"
className="rounded-full h-20 w-20"
alt="avatar"
/>
<div className="ml-4">
<h1 className="text-xl font-medium">El Macho</h1>
<p className="text-gray-700">Pasien</p>
</div>
</div>
</div>
</div>
</SwiperSlide>
</Swiper>
);
};
export default SwiperCoverflow;

View File

@ -0,0 +1,22 @@
import { Label, Textarea } from "flowbite-react";
const TextArea = ({ id, label, placeholder, name, onChange, value }) => {
return (
<div className="w-full">
<div className="mb-2 block">
<Label htmlFor={id} value={label} />
</div>
<Textarea
id={id}
placeholder={placeholder}
required
rows={4}
name={name}
onChange={onChange}
value={value}
/>
</div>
);
};
export default TextArea;

View File

@ -0,0 +1,61 @@
import axios from "axios";
import { toast } from "react-toastify";
export const getAdmin = (token, isLogin = false, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/admin/me`
);
dispatch({ type: "SET_ADMIN", payload: res.data.data });
dispatch({ type: "SET_IS_LOGIN", payload: true });
localStorage.setItem("role", res.data.data.role);
if (isLogin) {
nav("/" + res.data.data.id);
}
} catch (err) {
console.log(err);
}
};
};
export const loginAdmin = (data, nav, setLoading, setAdmin) => {
return async (dispatch) => {
setLoading(true);
const timer = 2000;
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/admin/login`,
data
);
localStorage.setItem("token", res.data.access_token);
toast.success("Login Berhasil", { autoClose: timer });
setTimeout(() => {
dispatch(getAdmin(res.data.access_token, true, nav));
setLoading(false);
setAdmin(false);
}, timer);
} catch (err) {
console.log(err.response.data.error);
toast.error(err.response.data.error);
setLoading(false);
}
};
};
export const logoutAdmin = (token, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
await axios.post(`${import.meta.env.VITE_API_URL}api/auth/admin/logout`);
localStorage.removeItem("token");
dispatch({ type: "SET_IS_LOGIN", payload: false });
dispatch({ type: "SET_ADMIN", payload: {} });
nav("/");
localStorage.removeItem("role");
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,110 @@
import axios from "axios";
import { toast } from "react-toastify";
export const getDaftarPoli = () => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/daftar_poli/`
);
dispatch({ type: "SET_DAFTAR_POLI", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getDaftarPoliById = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/daftar_poli/${id}`
);
dispatch({ type: "SET_DAFTAR_POLI_BY_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const addDaftarPoli = (data) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/daftar_poli/`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(res);
dispatch(getDaftarPoli());
toast.success("Data berhasil ditambahkan");
} catch (err) {
console.log(err);
toast.error("Data gagal ditambahkan");
}
};
};
export const updateDaftarPoli = (id, data, nav) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.put(
`${import.meta.env.VITE_API_URL}api/daftar_poli/${id}`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(res);
nav("/admin/daftar-poli");
dispatch(getDaftarPoli());
} catch (err) {
console.log(err);
}
};
};
export const deleteDaftarPoli = (id, nav) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.delete(
`${import.meta.env.VITE_API_URL}api/daftar_poli/${id}`
);
console.log(res);
nav("/admin/daftar-poli");
dispatch(getDaftarPoli());
} catch (err) {
console.log(err);
}
};
};
export const getDaftarPoliByPasienId = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/daftar_poli/pasien/${id}`
);
console.log(data);
dispatch({ type: "SET_DAFTAR_POLI_BY_PASIEN_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,93 @@
import axios from "axios";
export const getDetailPriksa = () => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/detail_periksa/`
);
dispatch({ type: "SET_DETAIL_PRIKSA", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getDetailPriksaByPeriksaId = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/detail_periksa/periksa/${id}`
);
dispatch({ type: "SET_DETAIL_PRIKSA_BY_PERIKSA_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getDetailPriksaById = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/detail_periksa/${id}`
);
dispatch({ type: "SET_DETAIL_PRIKSA_BY_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const addDetailPriksa = (values) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
await axios.post(
`${import.meta.env.VITE_API_URL}api/detail_periksa/`,
values
);
dispatch(getDetailPriksa());
} catch (err) {
console.log(err);
}
};
};
export const updateDetailPriksa = (id, values) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.put(
`${import.meta.env.VITE_API_URL}api/detail_periksa/${id}`,
values
);
dispatch({ type: "SET_DETAIL_PRIKSA", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const deleteDetailPriksa = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.delete(
`${import.meta.env.VITE_API_URL}api/detail_periksa/${id}`
);
dispatch({ type: "SET_DETAIL_PRIKSA", payload: data.data });
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,149 @@
import axios from "axios";
import { toast } from "react-toastify";
import Swal from "sweetalert2";
export const getAllDokter = () => {
return async (dispatch) => {
try {
const res = await axios.get(
`${import.meta.env.VITE_API_URL}api/auth/dokter/getAll`
);
dispatch({ type: "SET_ALL_DOKTER", payload: res.data.data });
} catch (err) {
console.log(err);
}
};
};
export const getDokter = (token, isLogin = false, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/dokter/me`
);
dispatch({ type: "SET_DOKTER", payload: res.data });
dispatch({ type: "SET_IS_LOGIN", payload: true });
localStorage.setItem("role", res.data.role);
if (isLogin) {
nav("/" + res.data.id);
}
} catch (err) {
console.log(err);
}
};
};
export const registerDokter = (data, loading) => {
return async (dispatch) => {
if (loading) loading(true);
try {
await axios.post(`${import.meta.env.VITE_API_URL}api/auth/dokter/`, data);
toast.success("Registrasi Berhasil");
if (loading) loading(false);
dispatch(getAllDokter());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const loginDokter = (data, nav, setLoading, setDokter) => {
return async (dispatch) => {
setLoading(true);
const timer = 2000;
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/dokter/login`,
data
);
localStorage.setItem("token", res.data.access_token);
toast.success("Login Berhasil", { autoClose: timer });
setTimeout(() => {
dispatch(getDokter(res.data.access_token, true, nav));
setLoading(false);
setDokter(false);
}, timer);
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
setLoading(false);
}
};
};
export const logoutDokter = (token, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
await axios.post(`${import.meta.env.VITE_API_URL}api/auth/dokter/logout`);
localStorage.removeItem("token");
dispatch({ type: "SET_IS_LOGIN", payload: false });
dispatch({ type: "SET_DOKTER", payload: {} });
nav("/");
localStorage.removeItem("role");
} catch (err) {
console.log(err);
}
};
};
export const getDokterById = (id) => {
return async (dispatch) => {
try {
const res = await axios.get(
`${import.meta.env.VITE_API_URL}api/auth/dokter/get/${id}`
);
dispatch({ type: "SET_DOKTER_BY_ID", payload: res.data.data });
} catch (err) {
console.log(err);
}
};
};
export const updateDokter = (id, data, loading, edit) => {
return async (dispatch) => {
if (loading) loading(true);
try {
await axios.put(
`${import.meta.env.VITE_API_URL}api/auth/dokter/update/${id}`,
data
);
if (loading && edit) {
loading(false);
edit(false);
}
toast.success("Berhasil diupdate");
dispatch(getAllDokter());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const deleteDokter = (id) => {
return async (dispatch) => {
try {
Swal.fire({
title: "Apakah anda yakin?",
text: "Anda akan menghapus dokter ini",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then(async (result) => {
if (result.isConfirmed) {
await axios.delete(
`${import.meta.env.VITE_API_URL}api/auth/dokter/delete/${id}`
);
dispatch(getAllDokter());
Swal.fire("Terhapus!", "Dokter berhasil dihapus.", "success");
}
});
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,9 @@
export * from "./adminAction";
export * from "./obatAction";
export * from "./dokterAction";
export * from "./pasienAction";
export * from "./poliAction";
export * from "./jadwalPeriksaAction";
export * from "./daftarPoliAction";
export * from "./periksaAction";
export * from "./detailPriksaAction";

View File

@ -0,0 +1,106 @@
import axios from "axios";
import Swal from "sweetalert2";
import { toast } from "react-toastify";
export const getJadwalPeriksa = () => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/jadwal_periksa/`
);
dispatch({ type: "SET_JADWAL_PERIKSA", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getJadwalPeriksaById = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/jadwal_periksa/${id}`
);
dispatch({ type: "SET_JADWAL_PERIKSA_BY_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const addJadwalPeriksa = (data, nav) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/jadwal_periksa/`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(res);
nav("/dokter/jadwal-periksa/" + data.id_dokter);
dispatch(getJadwalPeriksa());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const updateJadwalPeriksa = (id, data, nav) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.put(
`${import.meta.env.VITE_API_URL}api/jadwal_periksa/${id}`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(res);
nav("/dokter/jadwal-periksa/" + data.id_dokter);
dispatch(getJadwalPeriksa());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const deleteJadwalPeriksa = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
Swal.fire({
title: "Apakah anda yakin?",
text: "Data yang dihapus tidak dapat dikembalikan!",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya, hapus!",
cancelButtonText: "Batal",
}).then(async (result) => {
if (result.isConfirmed) {
const res = await axios.delete(
`${import.meta.env.VITE_API_URL}api/jadwal_periksa/${id}`
);
console.log(res);
dispatch(getJadwalPeriksa());
Swal.fire("Terhapus!", "Data berhasil dihapus.", "success");
}
});
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,110 @@
import axios from "axios";
import { toast } from "react-toastify";
import Swal from "sweetalert2";
export const getObat = () => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/obat/`
);
dispatch({ type: "SET_OBAT", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getObatById = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/obat/${id}`
);
dispatch({ type: "SET_OBAT_BY_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const storeObat = (data) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
await axios.post(
`${import.meta.env.VITE_API_URL}api/obat/`,
{
nama_obat: data.nama_obat,
kemasan: data.kemasan,
harga: data.harga,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
toast.success("Berhasil ditambahkan");
dispatch(getObat());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const updateObat = (id, data) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
await axios.put(
`${import.meta.env.VITE_API_URL}api/obat/${id}`,
{
nama_obat: data.nama_obat,
kemasan: data.kemasan,
harga: data.harga,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
toast.success("Berhasil diupdate");
dispatch(getObat());
} catch (err) {
console.log(err);
toast.error(err.response.data.error);
}
};
};
export const deleteObat = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
Swal.fire({
title: "Apakah anda yakin?",
text: "Obat akan dihapus secara permanen",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Hapus",
cancelButtonText: "Batal",
}).then(async (result) => {
if (result.isConfirmed) {
await axios.delete(`${import.meta.env.VITE_API_URL}api/obat/${id}`);
dispatch(getObat());
}
});
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,139 @@
import axios from "axios";
import { toast } from "react-toastify";
export const getAllPasien = () => {
return async (dispatch) => {
try {
const res = await axios.get(
`${import.meta.env.VITE_API_URL}api/auth/pasien/getAll`
);
dispatch({ type: "SET_PASIEN_ALL", payload: res.data.data });
} catch (err) {
console.log(err);
}
};
};
export const getPasienById = (id) => {
return async (dispatch) => {
try {
const res = await axios.get(
`${import.meta.env.VITE_API_URL}api/auth/pasien/get/${id}`
);
dispatch({ type: "SET_PASIEN_BY_ID", payload: res.data.data });
console.log(res.data.data);
} catch (err) {
console.log(err);
}
};
};
export const updatePasien = (id, data, setLoading, setPasien) => {
return async (dispatch) => {
if (setLoading) {
setLoading(true);
}
try {
await axios.put(
`${import.meta.env.VITE_API_URL}api/auth/pasien/update/${id}`,
data
);
if (setLoading && setPasien) {
setLoading(false);
setPasien(false);
}
toast.success("Update Berhasil");
dispatch(getAllPasien());
} catch (err) {
console.log(err);
toast.error("Update Gagal");
if (setLoading) {
setLoading(false);
}
}
};
};
export const registerPasien = (data, setPasien, setLoading) => {
return async () => {
if (setLoading) {
setLoading(true);
}
try {
await axios.post(`${import.meta.env.VITE_API_URL}api/auth/pasien/`, data);
if (setLoading && setPasien) {
setLoading(false);
setPasien(false);
}
toast.success("Registrasi Berhasil");
getAllPasien();
} catch (err) {
console.log(err);
toast.error("Registrasi Gagal");
if (setLoading) {
setLoading(false);
}
}
};
};
export const getPasien = (token, isLogin = false, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/pasien/me`
);
dispatch({ type: "SET_PASIEN", payload: res.data.data });
dispatch({ type: "SET_IS_LOGIN", payload: true });
localStorage.setItem("role", res.data.data.role);
if (isLogin) {
nav("/" + res.data.data.id);
}
} catch (err) {
console.log(err);
}
};
};
export const loginPasien = (data, nav, setLoading, setPasien) => {
return async (dispatch) => {
setLoading(true);
const timer = 2000;
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/auth/pasien/login`,
data
);
localStorage.setItem("token", res.data.access_token);
toast.success("Login Berhasil", { autoClose: timer });
setTimeout(() => {
dispatch(getPasien(res.data.access_token, true, nav));
setLoading(false);
setPasien(false);
}, timer);
} catch (err) {
console.log(err.response.data.error);
toast.error(err.response.data.error);
setLoading(false);
}
};
};
export const logoutPasien = (token, nav) => {
return async (dispatch) => {
try {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
await axios.post(`${import.meta.env.VITE_API_URL}api/auth/pasien/logout`);
localStorage.removeItem("token");
dispatch({ type: "SET_IS_LOGIN", payload: false });
dispatch({ type: "SET_PASIEN", payload: {} });
nav("/");
localStorage.removeItem("role");
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,151 @@
import axios from "axios";
import { addDetailPriksa } from "./detailPriksaAction";
export const getPeriksa = () => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/periksa/`
);
dispatch({ type: "SET_PERIKSA", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const getPeriksaByDafPol = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/periksa/daftar_poli/${id}`
);
dispatch({ type: "SET_PERIKSA_BY_DAF_POL_ID", payload: data.data[0] });
} catch (err) {
console.log(err);
}
};
};
export const getPeriksaById = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const { data } = await axios.get(
`${import.meta.env.VITE_API_URL}api/periksa/${id}`
);
dispatch({ type: "SET_PERIKSA_BY_ID", payload: data.data });
} catch (err) {
console.log(err);
}
};
};
export const storePeriksa = (data, nav, id, obat) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
const res = await axios.post(
`${import.meta.env.VITE_API_URL}api/periksa/`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
nav("/dokter/daftar-periksa/" + id);
dispatch(getPeriksa());
dispatch({ type: "SET_ID_DATA_ADD", payload: res.data.data.id });
if (obat.length > 0) {
obat.map((item) => {
const data = {
id_periksa: res.data.data.id,
id_obat: item.value,
};
dispatch(addDetailPriksa(data));
});
}
} catch (err) {
console.log(err);
}
};
};
export const updatePeriksa = (id, data, obat) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
await axios.put(
`${import.meta.env.VITE_API_URL}api/periksa/${id}`,
data,
{
headers: {
"Content-Type": "application/json",
},
}
);
const detailPriksaResponse = await axios.get(
`${import.meta.env.VITE_API_URL}api/detail_periksa/periksa/${id}`
);
const detailPriksa = detailPriksaResponse.data.data;
// Mengumpulkan hanya ID obat dari detailPriksa dan obat
const detailObatIDs = detailPriksa.map((detail) => detail.obat.id);
const obatIDs = obat.map((item) => item.value);
// Menghapus ID obat yang ada di detailPeriksa tetapi tidak ada di obat
const obatToDelete = detailObatIDs.filter(
(obatID) => !obatIDs.includes(obatID)
);
obatToDelete.forEach(async (obatID) => {
// Lakukan operasi hapus data di database untuk ID obat yang tidak ada di obat
await axios.delete(
`${
import.meta.env.VITE_API_URL
}api/detail_periksa/obat/${obatID}/${id}`
);
});
// Menambahkan ID obat yang ada di obat tetapi tidak ada di detailPeriksa
const obatToAdd = obatIDs.filter(
(obatID) => !detailObatIDs.includes(obatID)
);
obatToAdd.forEach(async (obatID) => {
// Lakukan operasi tambah data di database untuk ID obat yang tidak ada di detailPeriksa
const newData = {
id_periksa: id,
id_obat: obatID,
// Jika ada fields tambahan, sesuaikan di sini
};
await axios.post(
`${import.meta.env.VITE_API_URL}api/detail_periksa`,
newData
);
});
dispatch(getPeriksa());
} catch (err) {
console.log(err);
}
};
};
export const deletePeriksa = (id) => {
return async (dispatch) => {
dispatch({ type: "SET_IS_LOADING", payload: true });
try {
await axios.delete(`${import.meta.env.VITE_API_URL}api/periksa/${id}`);
dispatch(getPeriksa());
} catch (err) {
console.log(err);
}
};
};

View File

@ -0,0 +1,85 @@
import axios from "axios";
import { toast } from "react-toastify";
import Swal from "sweetalert2";
export const getPoli = () => {
return async (dispatch) => {
try {
dispatch({ type: "SET_LOADING", payload: true });
const res = await axios.get(`${import.meta.env.VITE_API_URL}api/poli`);
dispatch({ type: "SET_POLI", payload: res.data.data });
} catch (err) {
console.log(err);
dispatch({ type: "SET_LOADING", payload: false });
}
};
};
export const getPoliById = (id) => {
return async (dispatch) => {
try {
dispatch({ type: "SET_LOADING", payload: true });
const res = await axios.get(
`${import.meta.env.VITE_API_URL}api/poli/${id}`
);
dispatch({ type: "SET_POLI_BY_ID", payload: res.data.data });
} catch (err) {
console.log(err);
dispatch({ type: "SET_LOADING", payload: false });
}
};
};
export const addPoli = (data) => {
return async (dispatch) => {
try {
dispatch({ type: "SET_LOADING", payload: true });
await axios.post(`${import.meta.env.VITE_API_URL}api/poli/`, data);
toast.success("Berhasil ditambahkan");
dispatch(getPoli());
} catch (err) {
console.log(err);
dispatch({ type: "SET_LOADING", payload: false });
toast.error(err.response.data.error);
}
};
};
export const deletePoli = (id) => {
return async (dispatch) => {
try {
Swal.fire({
title: "Are you sure?",
text: "You will delete this data",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then(async (result) => {
if (result.isConfirmed) {
dispatch({ type: "SET_LOADING", payload: true });
await axios.delete(`${import.meta.env.VITE_API_URL}api/poli/${id}`);
dispatch(getPoli());
}
});
} catch (err) {
console.log(err);
dispatch({ type: "SET_LOADING", payload: false });
}
};
};
export const updatePoli = (id, data) => {
return async (dispatch) => {
try {
dispatch({ type: "SET_LOADING", payload: true });
await axios.put(`${import.meta.env.VITE_API_URL}api/poli/${id}`, data);
dispatch(getPoli());
toast.success("Berhasil diupdate");
} catch (err) {
console.log(err);
dispatch({ type: "SET_LOADING", payload: false });
toast.error(err.response.data.error);
}
};
};

View File

@ -0,0 +1,29 @@
const initialState = {
admin: {},
isLogin: false,
errorMessageAdmin: "",
};
const adminReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_ADMIN":
return {
...state,
admin: action.payload,
};
case "SET_IS_LOGIN":
return {
...state,
isLogin: action.payload,
};
case "SET_ERROR_MESSAGE_ADMIN":
return {
...state,
errorMessageAdmin: action.payload,
};
default:
return state;
}
};
export default adminReducer;

View File

@ -0,0 +1,31 @@
const initialState = {
daftarPoli: [],
daftarPoliById: {},
daftarPoliByPasienId: {},
loading: false,
error: null,
};
const daftarPoliReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_DAFTAR_POLI":
return {
...state,
daftarPoli: action.payload,
};
case "SET_DAFTAR_POLI_BY_ID":
return {
...state,
daftarPoliById: action.payload,
};
case "SET_DAFTAR_POLI_BY_PASIEN_ID":
return {
...state,
daftarPoliByPasienId: action.payload,
};
default:
return state;
}
};
export default daftarPoliReducer;

View File

@ -0,0 +1,29 @@
const initialState = {
detailPriksa: [],
detailPriksaById: [],
detailPriksaByPriksaId: [],
};
const detailPeriksaReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_DETAIL_PRIKSA":
return {
...state,
detailPriksa: action.payload,
};
case "SET_DETAIL_PRIKSA_BY_ID":
return {
...state,
detailPriksaById: action.payload,
};
case "SET_DETAIL_PRIKSA_BY_PERIKSA_ID":
return {
...state,
detailPriksaByPriksaId: action.payload,
};
default:
return state;
}
};
export default detailPeriksaReducer;

View File

@ -0,0 +1,35 @@
const initialState = {
dokter: {},
allDokter: [],
dokterById: {},
isLoginDokter: false,
};
const dokterReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_DOKTER":
return {
...state,
dokter: action.payload,
};
case "SET_IS_LOGIN":
return {
...state,
isLoginDokter: action.payload,
};
case "SET_ALL_DOKTER":
return {
...state,
allDokter: action.payload,
};
case "SET_DOKTER_BY_ID":
return {
...state,
dokterById: action.payload,
};
default:
return state;
}
};
export default dokterReducer;

View File

@ -0,0 +1,25 @@
const initialState = {
jadwalPeriksa: [],
jadwalPeriksaById: [],
loading: false,
error: null,
};
const jadwalPeriksaReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_JADWAL_PERIKSA":
return {
...state,
jadwalPeriksa: action.payload,
};
case "SET_JADWAL_PERIKSA_BY_ID":
return {
...state,
jadwalPeriksaById: action.payload,
};
default:
return state;
}
};
export default jadwalPeriksaReducer;

View File

@ -0,0 +1,23 @@
const initialState = {
obat: [],
obatById: {},
};
const obatReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_OBAT":
return {
...state,
obat: action.payload,
};
case "SET_OBAT_BY_ID":
return {
...state,
obatById: action.payload,
};
default:
return state;
}
};
export default obatReducer;

View File

@ -0,0 +1,41 @@
const initialState = {
pasien: {},
pasienAll: [],
pasienById: {},
isLogin: false,
errorMessagePasien: "",
};
const pasienReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_PASIEN":
return {
...state,
pasien: action.payload,
};
case "SET_IS_LOGIN":
return {
...state,
isLogin: action.payload,
};
case "SET_PASIEN_ALL":
return {
...state,
pasienAll: action.payload,
};
case "SET_PASIEN_BY_ID":
return {
...state,
pasienById: action.payload,
};
case "SET_ERROR_MESSAGE_PASIEN":
return {
...state,
errorMessagePasien: action.payload,
};
default:
return state;
}
};
export default pasienReducer;

View File

@ -0,0 +1,35 @@
const initialState = {
periksa: [],
periksaById: [],
idDataAdd: "",
periksaByDafPolId: {},
};
const periksaReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_PERIKSA":
return {
...state,
periksa: action.payload,
};
case "SET_PERIKSA_BY_ID":
return {
...state,
periksaById: action.payload,
};
case "SET_ID_DATA_ADD":
return {
...state,
idDataAdd: action.payload,
};
case "SET_PERIKSA_BY_DAF_POL_ID":
return {
...state,
periksaByDafPolId: action.payload,
};
default:
return state;
}
};
export default periksaReducer;

View File

@ -0,0 +1,30 @@
const initialState = {
poli: [],
poliById: {},
isLoading: false,
};
const poli = (state = initialState, action) => {
switch (action.type) {
case "SET_POLI":
return {
...state,
poli: action.payload,
};
case "SET_POLI_BY_ID":
return {
...state,
poliById: action.payload,
};
case "SET_LOADING":
return {
...state,
isLoading: action.payload,
};
default:
return state;
}
};
export default poli;

View File

@ -0,0 +1,24 @@
import { combineReducers } from "redux";
import adminReducer from "./adminReducer";
import obatReducer from "./obatReducer";
import dokterReducer from "./dokterReducer";
import pasienReducer from "./pasienReducer";
import poliReducer from "./poliReducer";
import jadwalPeriksaReducer from "./jadwalPeriksaReducer";
import daftarPoliReducer from "./daftarPoliReducer";
import periksaReducer from "./periksaReducer";
import detailPeriksaReducer from "./detailPeriksaReducer";
const reducer = combineReducers({
adminReducer,
obatReducer,
dokterReducer,
pasienReducer,
poliReducer,
jadwalPeriksaReducer,
daftarPoliReducer,
periksaReducer,
detailPeriksaReducer,
});
export default reducer;

View File

@ -0,0 +1,6 @@
import { applyMiddleware, createStore } from "redux";
import reducer from "./Reducer/reducer";
import { thunk } from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;

2
src/config/index.js Normal file
View File

@ -0,0 +1,2 @@
import store from "./Redux/store";
export { store };

14
src/index.css Normal file
View File

@ -0,0 +1,14 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
font-family: "Poppins", sans-serif;
}
.swiper-slide {
width: 100% !important;
height: 100% !important;
}
.swiper-wrapper {
width: 25% !important;
}

13
src/main.jsx Normal file
View File

@ -0,0 +1,13 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);

View File

@ -0,0 +1,11 @@
import transition from "../../transition";
const DashboardAdmin = () => {
return (
<div className="flex justify-center items-center w-full h-screen">
<h1>Dashboard Admin</h1>
</div>
);
};
export default DashboardAdmin;

347
src/pages/Admin/Dokter.jsx Normal file
View File

@ -0,0 +1,347 @@
import { useLocation, useOutletContext } from "react-router-dom";
import Input from "../../components/Input";
import { Spinner, Table } from "flowbite-react";
import TextArea from "../../components/TextArea";
import { useEffect, useState } from "react";
import ReactSelect from "../../components/ReactSelect";
import { useDispatch, useSelector } from "react-redux";
import { getPoli, getPoliById } from "../../config/Redux/Action/poliAction";
import {
getAllDokter,
getDokterById,
registerDokter,
updateDokter,
} from "../../config/Redux/Action/dokterAction";
import Modals from "../../components/Modals";
const Dokter = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const dispatch = useDispatch();
const { poli, poliById } = useSelector((state) => state.poliReducer);
const { allDokter, dokterById } = useSelector((state) => state.dokterReducer);
const [poliOption, setPoliOption] = useState([]);
const [poliSelected, setPoliSelected] = useState([]);
const [dokterForm, setDokterForm] = useState({
nama: "",
alamat: "",
no_hp: "",
id_poli: "",
username: "",
password: "",
role: "dokter",
});
const [editDokter, setEditDokter] = useState(false);
const [poliEditSelected, setPoliEditSelected] = useState([]);
const [dokterId, setDokterId] = useState("");
const [editDokterForm, setEditDokterForm] = useState({
nama: dokterById?.nama,
alamat: dokterById?.alamat,
no_hp: dokterById?.no_hp,
id_poli: poliEditSelected?.value,
username: dokterById?.username,
role: "dokter",
});
const [loading, setLoading] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(registerDokter(dokterForm, setLoading));
setDokterForm({
nama: "",
alamat: "",
no_hp: "",
id_poli: "",
username: "",
password: "",
role: "dokter",
});
setPoliSelected([]);
};
const handleEdit = (id) => {
setEditDokter(true);
setDokterId(id);
};
const handleUpdate = (e) => {
e.preventDefault();
dispatch(updateDokter(dokterId, editDokterForm, setLoading, setEditDokter));
};
useEffect(() => {
setDokterForm({ ...dokterForm, id_poli: poliSelected?.value });
}, [poliSelected]);
useEffect(() => {
if (dokterById) {
dispatch(getPoliById(dokterById.id_poli));
}
}, [dokterById, dispatch]);
useEffect(() => {
dispatch(getPoli());
dispatch(getAllDokter());
}, [dispatch]);
useEffect(() => {
if (poliById) {
setPoliEditSelected({
value: poliById.id,
label: poliById.nama_poli,
});
}
}, [poliById]);
useEffect(() => {
if (poli) {
setPoliOption(
poli.map((item) => {
return {
value: item.id,
label: item.nama_poli,
};
})
);
}
}, [poli]);
useEffect(() => {
if (dokterId) {
dispatch(getDokterById(dokterId));
}
}, [dokterId, dispatch]);
useEffect(() => {
if (dokterById) {
setEditDokterForm({
nama: dokterById.nama,
alamat: dokterById.alamat,
no_hp: dokterById.no_hp,
username: dokterById.username,
role: "dokter",
});
}
}, [dokterById]);
useEffect(() => {
setEditDokterForm({ ...editDokterForm, id_poli: poliEditSelected?.value });
}, [poliEditSelected]);
useEffect(() => {
if (role !== "admin") {
window.location.href = "/";
}
}, [role]);
return (
<>
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Dokter</h1>
<h1>{pathName}</h1>
</div>
<form className="mt-5" onSubmit={handleSubmit}>
<Input
label="Nama"
type="text"
placeholder="Nama Dokter"
value={dokterForm.nama}
onChange={(e) =>
setDokterForm({ ...dokterForm, nama: e.target.value })
}
/>
<TextArea
label="Alamat"
type="text"
placeholder="Alamat Dokter"
value={dokterForm.alamat}
onChange={(e) =>
setDokterForm({ ...dokterForm, alamat: e.target.value })
}
/>
<Input
label="No HP"
type="number"
placeholder="No. HP Dokter"
value={dokterForm.no_hp}
onChange={(e) =>
setDokterForm({ ...dokterForm, no_hp: e.target.value })
}
/>
<ReactSelect
title="Poli"
data={poliOption}
value={poliSelected}
onChange={(e) => setPoliSelected(e)}
/>
<Input
label="Username"
type="text"
placeholder="Username"
value={dokterForm.username}
onChange={(e) =>
setDokterForm({ ...dokterForm, username: e.target.value })
}
/>
<Input
label="Password"
type="password"
placeholder="Password"
value={dokterForm.password}
onChange={(e) =>
setDokterForm({ ...dokterForm, password: e.target.value })
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-slate-500 p-2 px-3 text-sm rounded text-white"
type="reset"
>
Reset Form
</button>
{loading ? (
<button
className="bg-[#1B4242] p-2 text-sm px-3 rounded text-white mx-2 cursor-default "
type="button"
>
<Spinner color="success" />
</button>
) : (
<button
className="bg-[#1B4242] p-2 text-sm px-3 rounded text-white mx-2"
type="submit"
>
Tambah
</button>
)}
</div>
</form>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium"> Dokter</h1>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama</Table.HeadCell>
<Table.HeadCell>Alamat</Table.HeadCell>
<Table.HeadCell>No. HP</Table.HeadCell>
<Table.HeadCell>Poli</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{allDokter.map((item, index) => (
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>Dr. {item.nama}</Table.Cell>
<Table.Cell>{item.alamat}</Table.Cell>
<Table.Cell>{item.no_hp}</Table.Cell>
<Table.Cell>{item?.poli?.nama_poli}</Table.Cell>
<Table.Cell>
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
onClick={() => handleEdit(item.id)}
>
Edit
</button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Modals
openModal={editDokter}
setOpenModal={() => setEditDokter(false)}
title="Edit Dokter"
buttonClose={false}
body={
<form onSubmit={handleUpdate}>
<Input
label="Nama"
type="text"
placeholder="Nama Dokter"
value={editDokterForm?.nama}
onChange={(e) =>
setEditDokterForm({
...editDokterForm,
nama: e.target.value,
})
}
/>
<TextArea
label="Alamat"
type="text"
placeholder="Alamat Dokter"
value={editDokterForm?.alamat}
onChange={(e) =>
setEditDokterForm({
...editDokterForm,
alamat: e.target.value,
})
}
/>
<Input
label="No HP"
type="number"
placeholder="No. HP Dokter"
value={editDokterForm?.no_hp}
onChange={(e) =>
setEditDokterForm({
...editDokterForm,
no_hp: e.target.value,
})
}
/>
<ReactSelect
title="Poli"
data={poliOption}
value={poliEditSelected}
onChange={(e) => setPoliEditSelected(e)}
/>
<Input
label="Username"
type="text"
placeholder="Username"
value={editDokterForm?.username}
onChange={(e) =>
setEditDokterForm({
...editDokterForm,
username: e.target.value,
})
}
/>
<div className="flex justify-end">
{loading ? (
<button
className="bg-[#1B4242] p-2 text-sm px-3 mt-5 rounded text-white"
type="button"
disabled
>
<Spinner color="white" />
</button>
) : (
<button
className="bg-[#1B4242] p-2 text-sm px-3 mt-5 rounded text-white"
type="submit"
>
Edit
</button>
)}
</div>
</form>
}
/>
</div>
</div>
</div>
</div>
</>
);
};
export default Dokter;

241
src/pages/Admin/Obat.jsx Normal file
View File

@ -0,0 +1,241 @@
import Input from "../../components/Input";
import { useLocation } from "react-router-dom";
import { Table } from "flowbite-react";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
deleteObat,
getObat,
storeObat,
getObatById,
updateObat,
} from "../../config/Redux/Action/obatAction";
import Modals from "../../components/Modals";
const Obat = () => {
const pathName = useLocation().pathname;
const dispatch = useDispatch();
const [editObat, setEditObat] = useState(false);
const { obat, obatById } = useSelector((state) => state.obatReducer);
const [obatForm, setObatForm] = useState({
nama_obat: "",
kemasan: "",
harga: "",
});
const [obatEditForm, setObatEditForm] = useState({
nama_obat: "",
kemasan: "",
harga: "",
});
const [idObat, setIdObat] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
dispatch(storeObat(obatForm));
setObatForm({
nama_obat: "",
kemasan: "",
harga: "",
});
};
const handleEdit = (id) => {
setEditObat(true);
setIdObat(id);
};
const handleDelete = (id) => {
dispatch(deleteObat(id));
};
const handleSumbitEdit = (e) => {
e.preventDefault();
dispatch(updateObat(idObat, obatEditForm));
setEditObat(false);
};
const formatPriceInRupiah = (price) => {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
}).format(price);
};
useEffect(() => {
dispatch(getObat());
}, [dispatch]);
useEffect(() => {
if (idObat) {
dispatch(getObatById(idObat));
}
}, [idObat, dispatch]);
useEffect(() => {
setObatEditForm({
nama_obat: obatById.nama_obat,
kemasan: obatById.kemasan,
harga: obatById.harga,
});
}, [obatById]);
return (
<>
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Obat</h1>
<h1>{pathName}</h1>
</div>
<form className="mt-5" onSubmit={handleSubmit}>
<Input
label="Nama Obat"
type="text"
placeholder="Nama Obat"
name={"nama_obat"}
value={obatForm.nama_obat}
onChange={(e) =>
setObatForm({ ...obatForm, nama_obat: e.target.value })
}
/>
<Input
label="Kemasan"
type="text"
placeholder="Kemasan"
name={"kemasan"}
value={obatForm.kemasan}
onChange={(e) =>
setObatForm({ ...obatForm, kemasan: e.target.value })
}
/>
<Input
label="Harga"
type="number"
placeholder="Harga"
name={"harga"}
value={obatForm.harga}
onChange={(e) =>
setObatForm({ ...obatForm, harga: e.target.value })
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-slate-500 p-2 px-3 text-sm rounded-md text-white"
type="reset"
>
Reset Form
</button>
<button
className="bg-[#1B4242] p-2 px-3 text-sm rounded-md text-white mx-2"
type="submit"
>
Tambah
</button>
</div>
</form>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Poli</h1>
</div>
<div className="overflow-auto mt-4 max-h-[40vh]">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell width="40%">Nama Obat</Table.HeadCell>
<Table.HeadCell>Kemasan</Table.HeadCell>
<Table.HeadCell>Harga</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{obat.map((item, index) => (
<Table.Row key={item.id}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.nama_obat}</Table.Cell>
<Table.Cell>{item.kemasan}</Table.Cell>
<Table.Cell>{formatPriceInRupiah(item.harga)}</Table.Cell>
<Table.Cell>
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
onClick={() => handleEdit(item.id)}
>
Edit
</button>
<button
className="bg-red-500 p-2 rounded text-white mx-2"
onClick={() => handleDelete(item.id)}
>
Hapus
</button>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Modals
openModal={editObat}
setOpenModal={() => setEditObat(false)}
title={"Edit Obat"}
buttonClose={false}
body={
<form className="flex flex-col" onSubmit={handleSumbitEdit}>
<Input
label="Nama Obat"
type="text"
placeholder="Nama Obat"
name={"nama_obat"}
value={obatEditForm.nama_obat}
onChange={(e) =>
setObatEditForm({
...obatEditForm,
nama_obat: e.target.value,
})
}
/>
<Input
label="Kemasan"
type="text"
placeholder="Kemasan"
name={"kemasan"}
value={obatEditForm.kemasan}
onChange={(e) =>
setObatEditForm({
...obatEditForm,
kemasan: e.target.value,
})
}
/>
<Input
label="Harga"
type="number"
placeholder="Harga"
name={"harga"}
value={obatEditForm.harga}
onChange={(e) =>
setObatEditForm({
...obatEditForm,
harga: e.target.value,
})
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-[#1B4242] p-2 px-3 rounded text-white mx-2"
type="submit"
>
Update
</button>
</div>
</form>
}
/>
</div>
</div>
</div>
</div>
</>
);
};
export default Obat;

298
src/pages/Admin/Pasien.jsx Normal file
View File

@ -0,0 +1,298 @@
import { useEffect, useState } from "react";
import Input from "../../components/Input";
import TextArea from "../../components/TextArea";
import { Spinner, Table } from "flowbite-react";
import { useLocation, useOutletContext } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import {
getAllPasien,
getPasienById,
registerPasien,
updatePasien,
} from "../../config/Redux/action/pasienAction";
import Modals from "../../components/Modals";
const Pasien = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const dispatch = useDispatch();
const { pasienAll, pasienById } = useSelector((state) => state.pasienReducer);
const [pasienForm, setPasienForm] = useState({
nama: "",
alamat: "",
no_ktp: "",
no_hp: "",
username: "",
password: "",
});
const [editPasienForm, setEditPasienForm] = useState({
nama: pasienById?.nama,
alamat: pasienById?.alamat,
no_ktp: pasienById?.no_ktp,
no_hp: pasienById?.no_hp,
username: pasienById?.username,
});
const [editPasien, setEditPasien] = useState(false);
const [idPasien, setIdPasien] = useState("");
const [loading, setLoading] = useState(false);
const handleSumit = (e) => {
e.preventDefault();
dispatch(registerPasien(pasienForm));
};
const handleEdit = (id) => {
setEditPasien(true);
setIdPasien(id);
};
const handleUpdate = (e) => {
e.preventDefault();
dispatch(updatePasien(idPasien, editPasienForm, setLoading, setEditPasien));
};
useEffect(() => {
if (idPasien) {
dispatch(getPasienById(idPasien));
}
}, [dispatch, idPasien]);
useEffect(() => {
dispatch(getAllPasien());
}, [dispatch]);
useEffect(() => {
setEditPasienForm({
nama: pasienById?.nama,
alamat: pasienById?.alamat,
no_ktp: pasienById?.no_ktp,
no_hp: pasienById?.no_hp,
username: pasienById?.username,
});
}, [pasienById]);
useEffect(() => {
if (role !== "admin") {
window.location.href = "/";
}
}, [role]);
return (
<>
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Pasien</h1>
<h1>{pathName}</h1>
</div>
<form onSubmit={handleSumit} className="mt-5">
<Input
label="Nama Pasien"
type="text"
placeholder="Nama Pasien"
onChange={(e) =>
setPasienForm({ ...pasienForm, nama: e.target.value })
}
/>
<TextArea
label="Alamat"
type="text"
placeholder="Alamat Pasien"
onChange={(e) =>
setPasienForm({ ...pasienForm, alamat: e.target.value })
}
/>
<Input
label="No. KTP Pasien"
type="text"
placeholder="No. KTP Pasien"
onChange={(e) =>
setPasienForm({ ...pasienForm, no_ktp: e.target.value })
}
/>
<Input
label="No. HP Pasien"
type="text"
placeholder="No. HP Pasien"
onChange={(e) =>
setPasienForm({ ...pasienForm, no_hp: e.target.value })
}
/>
<Input
label="Username"
type="text"
placeholder="Username"
onChange={(e) =>
setPasienForm({ ...pasienForm, username: e.target.value })
}
/>
<Input
label="Password"
type="password"
placeholder="Password"
onChange={(e) =>
setPasienForm({ ...pasienForm, password: e.target.value })
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-slate-500 p-2 rounded text-white px-3 text-sm"
type="reset"
>
Reset Form
</button>
<button
className="bg-[#1B4242] p-2 rounded text-white mx-2 px-3 text-sm"
type="submit"
>
Tambah
</button>
</div>
</form>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Pasien</h1>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama</Table.HeadCell>
<Table.HeadCell width="30%">Alamat</Table.HeadCell>
<Table.HeadCell>No. KTP</Table.HeadCell>
<Table.HeadCell>No. HP</Table.HeadCell>
<Table.HeadCell>No. RM</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{pasienAll.map((pasien, index) => (
<>
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{pasien.nama}</Table.Cell>
<Table.Cell>{pasien.alamat}</Table.Cell>
<Table.Cell>{pasien.no_ktp}</Table.Cell>
<Table.Cell>{pasien.no_hp}</Table.Cell>
<Table.Cell>{pasien.no_rm}</Table.Cell>
<Table.Cell>
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
onClick={() => handleEdit(pasien.id)}
>
Edit
</button>
</Table.Cell>
</Table.Row>
</>
))}
</Table.Body>
</Table>
<Modals
openModal={editPasien}
setOpenModal={setEditPasien}
title="Edit Pasien"
buttonClose={false}
body={
<form onSubmit={handleUpdate}>
<Input
label="No. RM Pasien"
type="text"
placeholder="No. RM Pasien"
value={pasienById?.no_rm}
disabled
/>
<Input
label="Nama Pasien"
type="text"
placeholder="Nama Pasien"
onChange={(e) =>
setEditPasienForm({
...editPasienForm,
nama: e.target.value,
})
}
value={editPasienForm.nama}
/>
<TextArea
label="Alamat"
type="text"
placeholder="Alamat Pasien"
onChange={(e) =>
setEditPasienForm({
...editPasienForm,
alamat: e.target.value,
})
}
value={editPasienForm.alamat}
/>
<Input
label="No. KTP Pasien"
type="text"
placeholder="No. KTP Pasien"
onChange={(e) =>
setEditPasienForm({
...editPasienForm,
no_ktp: e.target.value,
})
}
value={editPasienForm.no_ktp}
/>
<Input
label="No. HP Pasien"
type="text"
placeholder="No. HP Pasien"
onChange={(e) =>
setEditPasienForm({
...editPasienForm,
no_hp: e.target.value,
})
}
value={editPasienForm.no_hp}
/>
<Input
label="Username"
type="text"
placeholder="Username"
onChange={(e) =>
setEditPasienForm({
...editPasienForm,
username: e.target.value,
})
}
value={editPasienForm.username}
/>
<div className="flex justify-end mt-4">
{loading ? (
<button
className="bg-[#1B4242] p-2 rounded text-white px-3 text-sm"
type="button"
disabled
>
<Spinner
color="success"
aria-label="Success spinner example"
/>
</button>
) : (
<button
className="bg-[#1B4242] p-2 rounded text-white px-3 text-sm"
type="submit"
>
Edit
</button>
)}
</div>
</form>
}
/>
</div>
</div>
</div>
</div>
</>
);
};
export default Pasien;

214
src/pages/Admin/Poli.jsx Normal file
View File

@ -0,0 +1,214 @@
import TextArea from "../../components/TextArea";
import { useLocation, useOutletContext } from "react-router-dom";
import { Table } from "flowbite-react";
import Input from "../../components/Input";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
addPoli,
deletePoli,
getPoli,
getPoliById,
updatePoli,
} from "../../config/Redux/Action/poliAction";
import Modals from "../../components/Modals";
const Poli = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const dispatch = useDispatch();
const { poli, poliById } = useSelector((state) => state.poliReducer);
const [edit, setEdit] = useState(false);
const [poliForm, setPoliForm] = useState({
nama_poli: "",
keterangan: "",
});
const [poliFormEdit, setPoliFormEdit] = useState({
nama_poli: "",
keterangan: "",
});
const [idPoli, setIdPoli] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addPoli(poliForm));
setPoliForm({
nama_poli: "",
keterangan: "",
});
};
const handleDelete = (id) => {
dispatch(deletePoli(id));
};
const handleEdit = (id) => {
setEdit(true);
setIdPoli(id);
};
const handleSumbitEdit = (e) => {
e.preventDefault();
dispatch(updatePoli(idPoli, poliFormEdit));
setEdit(false);
};
useEffect(() => {
dispatch(getPoli());
}, [dispatch]);
useEffect(() => {
if (poliById) {
setPoliFormEdit({
nama_poli: poliById.nama_poli,
keterangan: poliById.keterangan,
});
}
}, [poliById]);
useEffect(() => {
if (idPoli) {
dispatch(getPoliById(idPoli));
}
}, [idPoli, dispatch]);
useEffect(() => {
if (role !== "admin") {
window.location.href = "/";
}
}, [role]);
return (
<>
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Poli</h1>
<h1>{pathName}</h1>
</div>
<form action="" className="mt-5" onSubmit={handleSubmit}>
<Input
label="Nama Poli"
type="text"
placeholder="Nama Poli"
name="nama_poli"
value={poliForm.nama_poli}
onChange={(e) =>
setPoliForm({ ...poliForm, nama_poli: e.target.value })
}
/>
<TextArea
label="Keterangan"
type="text"
placeholder="Keterangan"
name="keterangan"
value={poliForm.keterangan}
onChange={(e) =>
setPoliForm({ ...poliForm, keterangan: e.target.value })
}
/>
<div className="flex justify-end mt-4">
<button className="bg-slate-500 p-2 px-3 text-sm rounded-md text-white">
Reset Form
</button>
<button
className="bg-[#1B4242] p-2 px-3 text-sm rounded-md text-white mx-2"
type="submit"
>
Tambah
</button>
</div>
</form>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Poli</h1>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama Poli</Table.HeadCell>
<Table.HeadCell width="40%">Keterangan</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{poli.map((item, index) => (
<>
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.nama_poli}</Table.Cell>
<Table.Cell>{item.keterangan}</Table.Cell>
<Table.Cell>
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
onClick={() => handleEdit(item.id)}
>
Edit
</button>
<button
className="bg-red-500 p-2 rounded text-white mx-2"
onClick={() => handleDelete(item.id)}
>
Hapus
</button>
</Table.Cell>
</Table.Row>
</>
))}
</Table.Body>
</Table>
<Modals
openModal={edit}
setOpenModal={setEdit}
title="Edit Poli"
buttonClose={false}
body={
<form className="mt-[-1.5rem]" onSubmit={handleSumbitEdit}>
<Input
label="Nama Poli"
type="text"
placeholder="Nama Poli"
name="nama_poli"
value={poliFormEdit.nama_poli}
onChange={(e) =>
setPoliFormEdit({
...poliFormEdit,
nama_poli: e.target.value,
})
}
/>
<TextArea
label="Keterangan"
type="text"
placeholder="Keterangan"
name="keterangan"
value={poliFormEdit.keterangan}
onChange={(e) =>
setPoliFormEdit({
...poliFormEdit,
keterangan: e.target.value,
})
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-[#1B4242] p-2 px-3 text-sm rounded-md text-white mx-2"
type="submit"
>
Edit
</button>
</div>
</form>
}
/>
</div>
</div>
</div>
</div>
</>
);
};
export default Poli;

View File

@ -0,0 +1,513 @@
import { FaAddressCard, FaRegAddressCard, FaUserAlt } from "react-icons/fa";
import { FaLocationDot } from "react-icons/fa6";
import { MdOutlineArrowRightAlt } from "react-icons/md";
import Modals from "../../components/Modals";
import { useState } from "react";
import { Checkbox, Label, Spinner, TextInput } from "flowbite-react";
import { HiLockClosed, HiMail } from "react-icons/hi";
import { useDispatch } from "react-redux";
import { loginAdmin } from "../../config/Redux/Action/adminAction";
import { useNavigate } from "react-router-dom";
import { loginDokter } from "../../config/Redux/Action/dokterAction";
import {
loginPasien,
registerPasien,
} from "../../config/Redux/Action/pasienAction";
import { ToastContainer } from "react-toastify";
const LoginPortal = () => {
const dispatch = useDispatch();
const nav = useNavigate();
const [pasien, setPasien] = useState(false);
const [dokter, setDokter] = useState(false);
const [admin, setAdmin] = useState(false);
const [register, setRegister] = useState(false);
const [adminForm, setAdminForm] = useState({
username: "",
password: "",
});
const [dokterForm, setDokterForm] = useState({
username: "",
password: "",
});
const [pasienRegister, setPasienRegister] = useState({
nama: "",
no_ktp: "",
no_hp: "",
username: "",
password: "",
alamat: "",
});
const [pasienForm, setPasienForm] = useState({
username: "",
password: "",
});
const [loading, setLoading] = useState(false);
const handleLoginAdmin = (e) => {
e.preventDefault();
dispatch(loginAdmin(adminForm, nav, setLoading, setAdmin));
};
const handleLoginDokter = (e) => {
e.preventDefault();
dispatch(loginDokter(dokterForm, nav, setLoading, setDokter));
};
const handleRegisterPasien = (e) => {
e.preventDefault();
dispatch(registerPasien(pasienRegister, setRegister, setLoading));
setPasienRegister({
nama: "",
no_ktp: "",
no_hp: "",
username: "",
password: "",
alamat: "",
});
};
const handleLoginPasien = (e) => {
e.preventDefault();
dispatch(loginPasien(pasienForm, nav, setLoading, setPasien));
setPasienForm({
username: "",
password: "",
});
setRegister(false);
};
return (
<>
<ToastContainer />
<div className=" bg-[#00008B]">
<div className="flex items-center justify-center flex-col container mx-auto py-[10rem] ">
<h1 className="text-4xl font-bold text-white mb-[5rem]">
{" "}
Login Portal{" "}
</h1>
<div className="w-full flex flex-col items-center">
{/* Login Admin */}
<div className="flex flex-col justify-center items-start bg-white text-black p-5 rounded shadow-lg w-[50rem]">
<div className="bg-[#00008B] p-4 rounded">
<FaUserAlt size={25} color="white" />
</div>
<h1 className=" text-2xl font-bold mt-2">Login Sebagai Pasien</h1>
<p className=" text-lg">
Apabila Anda adalah pasien, silahkan login disini.
</p>
<button
className="bg-white text-[#12486B] rounded-full py-2 mt-3 flex items-center hover:underline"
onClick={() => setPasien(true)}
>
Klik Disini <MdOutlineArrowRightAlt className="ml-2" />
</button>
</div>
<div className="flex flex-col justify-center items-start bg-white text-black p-5 rounded shadow-lg w-[50rem] my-3">
<div className="bg-[#00008B] p-4 rounded">
<FaUserAlt size={25} color="white" />
</div>
<h1 className=" text-2xl font-bold mt-2">Login Sebagai Dokter</h1>
<p className=" text-lg">
Apabila Anda adalah dokter, silahkan login disini.
</p>
<button
className="bg-white text-[#12486B] rounded-full py-2 mt-3 flex items-center hover:underline"
onClick={() => setDokter(true)}
>
Klik Disini <MdOutlineArrowRightAlt className="ml-2" />
</button>
</div>
<div className="flex flex-col justify-center items-start bg-white text-black p-5 rounded shadow-lg w-[50rem]">
<div className="bg-[#00008B] p-4 rounded">
<FaUserAlt size={25} color="white" />
</div>
<h1 className=" text-2xl font-bold mt-2">Login Sebagai Admin</h1>
<p className=" text-lg">
Apabila Anda adalah admin, silahkan login disini.
</p>
<button
className="bg-white text-[#12486B] rounded-full py-2 mt-3 flex items-center hover:underline"
onClick={() => setAdmin(true)}
>
Klik Disini <MdOutlineArrowRightAlt className="ml-2" />
</button>
</div>
</div>
</div>
<Modals
openModal={pasien}
setOpenModal={setPasien}
size="lg"
title="Login Pasien"
buttonClose={false}
body={
<>
<form className="flex flex-col" onSubmit={handleLoginPasien}>
<div className="w-full">
<TextInput
id="username"
type="text"
icon={HiMail}
placeholder="Username"
name="username"
onChange={(e) => {
setPasienForm({
...pasienForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full my-3">
<TextInput
id="password"
type="password"
icon={HiLockClosed}
placeholder="Password"
name="password"
onChange={(e) => {
setPasienForm({
...pasienForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="flex justify-end text-sm text-[#132043] hover:underline my-1">
<button onClick={() => setRegister(true)}>
Belum punya akun?
</button>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Checkbox id="accept" />
<Label htmlFor="accept" className="flex">
Remember Me
</Label>
</div>
{loading ? (
<button
className="bg-[#12486B] text-white p-2 px-4 rounded-lg w-fit"
type="button"
>
<Spinner
color="success"
aria-label="Success spinner example"
/>
</button>
) : (
<button
className="bg-[#1E90FF] text-white p-2 px-4 rounded-lg w-fit"
type="submit"
>
Login
</button>
)}
</div>
</form>
</>
}
/>
<Modals
openModal={register}
setOpenModal={setRegister}
size="lg"
title="Register Pasien"
buttonClose={false}
body={
<>
<form className="flex flex-col" onSubmit={handleRegisterPasien}>
<div className="w-full">
<TextInput
id="nama"
type="text"
icon={HiMail}
placeholder="Full Name"
name="nama"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full mt-3">
<TextInput
id="alamat"
type="text"
icon={FaLocationDot}
placeholder="Alamat"
name="alamat"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full mt-3">
<TextInput
id="no_ktp"
type="number"
icon={FaAddressCard}
placeholder="No. KTP"
name="no_ktp"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full mt-3">
<TextInput
id="no_hp"
type="number"
icon={FaRegAddressCard}
placeholder="No. HP"
name="no_hp"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full mt-3">
<TextInput
id="username"
type="text"
icon={HiMail}
placeholder="Username"
name="username"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full my-3">
<TextInput
id="password"
type="password"
icon={HiLockClosed}
placeholder="Password"
name="password"
onChange={(e) => {
setPasienRegister({
...pasienRegister,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Checkbox id="accept" />
<Label htmlFor="accept" className="flex">
I agree with the&nbsp;
<a
href="#"
className="text-cyan-600 hover:underline dark:text-cyan-500"
>
terms and conditions
</a>
</Label>
</div>
{loading ? (
<button
className="bg-[#12486B] text-white p-2 px-4 rounded-lg w-fit"
type="button"
>
<Spinner
color="success"
aria-label="Success spinner example"
/>
</button>
) : (
<button
className="bg-[#1E90FF ] text-white p-2 px-4 rounded-lg w-fit"
type="submit"
>
Register
</button>
)}
</div>
<div className="flex justify-end text-sm text-[#132043] hover:underline my-1">
<button
onClick={() => setRegister(false)}
className="text-[#132043]"
type="button"
>
Sudah memiliki akun?
</button>
</div>
</form>
</>
}
/>
<Modals
openModal={dokter}
setOpenModal={setDokter}
size="lg"
title="Login Dokter"
buttonClose={false}
body={
<>
<form className="flex flex-col" onSubmit={handleLoginDokter}>
<div className="w-full">
<TextInput
id="nama"
type="text"
icon={HiMail}
placeholder="Username"
name="username"
onChange={(e) => {
setDokterForm({
...dokterForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full my-3">
<TextInput
id="nama"
type="password"
icon={HiLockClosed}
placeholder="Password"
name="password"
onChange={(e) => {
setDokterForm({
...dokterForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Checkbox id="accept" />
<Label htmlFor="accept" className="flex">
Remember Me
</Label>
</div>
{loading ? (
<button
className="bg-[#12486B] text-white p-2 px-4 rounded-lg w-fit"
type="button"
>
<Spinner
color="success"
aria-label="Success spinner example"
/>
</button>
) : (
<button
className="bg-[#1E90FF] text-white p-2 px-4 rounded-lg w-fit"
type="submit"
>
Login
</button>
)}
</div>
</form>
</>
}
/>
<Modals
openModal={admin}
setOpenModal={setAdmin}
size="lg"
title="Login Admin"
buttonClose={false}
body={
<>
<form className="flex flex-col" onSubmit={handleLoginAdmin}>
<div className="w-full">
<TextInput
id="nama"
type="text"
icon={HiMail}
placeholder="Username"
name="username"
onChange={(e) => {
setAdminForm({
...adminForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="w-full my-3">
<TextInput
id="nama"
type="password"
icon={HiLockClosed}
placeholder="Password"
name="password"
onChange={(e) => {
setAdminForm({
...adminForm,
[e.target.name]: e.target.value,
});
}}
required
/>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Checkbox id="accept" />
<Label htmlFor="accept" className="flex">
Remember Me
</Label>
</div>
{loading ? (
<button
className="bg-[#12486B] text-white p-2 px-4 rounded-lg w-fit"
type="button"
>
<Spinner
color="success"
aria-label="Success spinner example"
/>
</button>
) : (
<button
className="bg-[#1E90FF] text-white p-2 px-4 rounded-lg w-fit"
type="submit"
>
Login
</button>
)}
</div>
</form>
</>
}
/>
</div>
</>
);
};
export default LoginPortal;

View File

@ -0,0 +1,11 @@
import transition from "../../transition";
const DashboardDokter = () => {
return (
<div className="flex justify-center items-center w-full h-screen">
<h1>Dashboard Dokter</h1>
</div>
);
};
export default DashboardDokter;

View File

@ -0,0 +1,129 @@
import { Table } from "flowbite-react";
import { useEffect, useState } from "react";
import { FaPlus } from "react-icons/fa";
import { useDispatch, useSelector } from "react-redux";
import {
Link,
useLocation,
useOutletContext,
useParams,
} from "react-router-dom";
import { getJadwalPeriksa } from "../../../config/Redux/Action/jadwalPeriksaAction";
const JadwalPeriksa = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const { id } = useParams();
const dispatch = useDispatch();
const { jadwalPeriksa } = useSelector((state) => state.jadwalPeriksaReducer);
const [jadwal, setJadwal] = useState([]);
const timeFormat = (time) => {
const date = new Date(`1970-01-01T${time}`);
if (isNaN(date)) {
return "Invalid time";
}
return date.toLocaleTimeString("id-ID", {
hour: "2-digit",
minute: "2-digit",
});
};
const dateFormat = (date) => {
const dateObj = new Date(date);
const month = dateObj.toLocaleString("default", { month: "long" });
const day = dateObj.getDate();
const year = dateObj.getFullYear();
return `${day} ${month} ${year}`;
};
useEffect(() => {
const updatedJadwal = jadwalPeriksa.map((item) => {
if (item.id_dokter === id) {
return item;
}
return item;
});
setJadwal(updatedJadwal);
}, [jadwalPeriksa, id]);
useEffect(() => {
dispatch(getJadwalPeriksa());
}, [dispatch]);
useEffect(() => {
if (role !== "dokter") {
window.location.href = "/";
}
}, [role]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Jadwal Periksa</h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Daftar Jadwal Periksa</h1>
<Link
className="bg-[#1B4242] p-2 rounded text-white mx-2 flex items-center"
to={"/dokter/jadwal-periksa/kelola-jadwal/" + id}
>
<FaPlus className="mr-2" /> Tambah
</Link>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama Dokter</Table.HeadCell>
<Table.HeadCell>Hari</Table.HeadCell>
<Table.HeadCell>Jam Mulai</Table.HeadCell>
<Table.HeadCell>Jam Selesai</Table.HeadCell>
<Table.HeadCell>Tanggal</Table.HeadCell>
<Table.HeadCell>Status</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{jadwal
.filter((item) => item.id_dokter === id)
.map((item, index) => {
return (
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.dokter.nama}</Table.Cell>
<Table.Cell>{item.hari}</Table.Cell>
<Table.Cell>{timeFormat(item.jam_mulai)}</Table.Cell>
<Table.Cell>{timeFormat(item.jam_selesai)}</Table.Cell>
<Table.Cell>{dateFormat(item.tanggal)}</Table.Cell>
<Table.Cell>
{item.status === "Y" ? "Aktif" : "Tidak Aktif"}
</Table.Cell>
<Table.Cell>
<Link
className={`bg-[#5C8374] p-2 rounded text-white mx-2 `}
to={`/dokter/jadwal-periksa/kelola-jadwal/${item.id}/${id}`}
>
Edit
</Link>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
</div>
</div>
</div>
</div>
);
};
export default JadwalPeriksa;

View File

@ -0,0 +1,173 @@
import { useLocation, useNavigate, useParams } from "react-router-dom";
import Input from "../../../components/Input";
import { useDispatch, useSelector } from "react-redux";
import Select from "react-select";
import { useEffect, useState } from "react";
import {
addJadwalPeriksa,
getJadwalPeriksaById,
updateJadwalPeriksa,
} from "../../../config/Redux/Action/jadwalPeriksaAction";
import ReactSelect from "../../../components/ReactSelect";
const KelolaJadwalPeriksa = () => {
const pathName = useLocation().pathname;
const { id, idJadwal } = useParams();
const { dokter } = useSelector((state) => state.dokterReducer);
const { jadwalPeriksaById } = useSelector(
(state) => state.jadwalPeriksaReducer
);
const dispatch = useDispatch();
const nav = useNavigate();
const [addForm, setAddForm] = useState({
id_dokter: id,
hari: "",
jam_mulai: "",
jam_selesai: "",
tanggal: "",
status: "",
});
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addJadwalPeriksa(addForm, nav));
};
const handleSubmitEdit = (e) => {
e.preventDefault();
dispatch(updateJadwalPeriksa(idJadwal, addForm, nav));
};
const dateFormat = (date) => {
return date?.split(" ")[0];
};
useEffect(() => {
if (idJadwal !== undefined && idJadwal !== null) {
// Lakukan sesuatu ketika idJadwal memiliki nilai yang valid
dispatch(getJadwalPeriksaById(idJadwal));
} else {
// Jika idJadwal adalah null atau undefined, atur kembali addForm
setAddForm({
id_dokter: id,
hari: "",
jam_mulai: "",
jam_selesai: "",
tanggal: "",
status: "",
});
}
}, [dispatch, id, idJadwal]);
useEffect(() => {
if (jadwalPeriksaById && idJadwal !== undefined) {
setAddForm({
id_dokter: id,
hari: jadwalPeriksaById.hari,
jam_mulai: jadwalPeriksaById.jam_mulai,
jam_selesai: jadwalPeriksaById.jam_selesai,
tanggal: jadwalPeriksaById.tanggal,
status: jadwalPeriksaById.status,
});
}
}, [id, jadwalPeriksaById, idJadwal]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem] ">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Jadwal Periksa</h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white mt-5 rounded-t-md">
<div className="header p-3 bg-[#1B4242] text-white rounded-t-md">
<h2>{id ? "Edit" : "Tambah"} Jadwal Periksa</h2>
</div>
<div className="p-5">
<form
onSubmit={idJadwal !== undefined ? handleSubmitEdit : handleSubmit}
>
<Input
label="Nama"
type="text"
placeholder="Nama Dokter"
value={dokter.nama}
disabled
/>
<div className="my-3">
<label className="text-black text-sm font-normal mb-5">
Hari
</label>
<Select
options={[
{ value: "1", label: "Senin" },
{ value: "2", label: "Selasa" },
{ value: "3", label: "Rabu" },
{ value: "4", label: "Kamis" },
{ value: "5", label: "Jumat" },
{ value: "6", label: "Sabtu" },
{ value: "7", label: "Minggu" },
]}
name="hari"
onChange={(e) => setAddForm({ ...addForm, hari: e.label })}
value={{ label: addForm.hari }}
/>
</div>
<Input
label="Jam Mulai"
type="time"
placeholder="Jam Mulai"
name="jam_mulai"
value={addForm.jam_mulai}
onChange={(e) =>
setAddForm({ ...addForm, jam_mulai: e.target.value })
}
/>
<Input
label="Jam Berakhir"
type="time"
placeholder="Jam Berakhir"
name="jam_selesai"
value={addForm.jam_selesai}
onChange={(e) =>
setAddForm({ ...addForm, jam_selesai: e.target.value })
}
/>
<Input
label="Tanggal"
type="date"
placeholder="tanggal"
name="tanggal"
value={dateFormat(addForm.tanggal)}
onChange={(e) =>
setAddForm({ ...addForm, tanggal: e.target.value })
}
/>
<ReactSelect
title="Status"
value={{
value: addForm.status,
label: addForm.status === "Y" ? "Aktif" : "Tidak Aktif",
}}
onChange={(e) => setAddForm({ ...addForm, status: e.value })}
data={[
{ value: "Y", label: "Aktif" },
{ value: "N", label: "Tidak Aktif" },
]}
/>
<div className="flex justify-end mt-4">
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
type="submit"
>
{idJadwal ? "Edit" : "Tambah"}
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default KelolaJadwalPeriksa;

View File

@ -0,0 +1,267 @@
import { Table } from "flowbite-react";
import { useEffect, useState } from "react";
import { FaEdit, FaStethoscope } from "react-icons/fa";
import { useDispatch, useSelector } from "react-redux";
import {
Link,
useLocation,
useOutletContext,
useParams,
} from "react-router-dom";
import {
getDaftarPoli,
getDetailPriksaByPeriksaId,
getObat,
getPeriksa,
getPeriksaByDafPol,
updatePeriksa,
} from "../../../config/Redux/Action";
import Modals from "../../../components/Modals";
import Input from "../../../components/Input";
import ReactSelect from "../../../components/ReactSelect";
const DaftarPeriksa = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const { id } = useParams();
const { daftarPoli } = useSelector((state) => state.daftarPoliReducer);
const { periksa, periksaByDafPolId } = useSelector(
(state) => state.periksaReducer
);
const { detailPriksaByPriksaId } = useSelector(
(state) => state.detailPeriksaReducer
);
const { obat } = useSelector((state) => state.obatReducer);
const dispatch = useDispatch();
const [edit, setEdit] = useState(false);
const [idPeriksa, setIdPeriksa] = useState("");
const [obatValue, setObatValue] = useState([]);
const [editForm, setEditForm] = useState({
id_daftar_poli: periksaByDafPolId?.id_daftar_poli,
tgl_periksa: periksaByDafPolId?.tanggal,
catatan: periksaByDafPolId?.catatan,
biaya_periksa: obatValue.reduce((acc, curr) => acc + curr.harga, 150000),
});
const handleEdit = (id) => {
setEdit(true);
setIdPeriksa(id);
};
const handleUpdate = (e) => {
e.preventDefault();
const data = {
...editForm,
};
dispatch(updatePeriksa(periksaByDafPolId?.id, data, obatValue));
setEdit(false);
};
const formatPriceInRupiah = (price) => {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
}).format(price);
};
useEffect(() => {
dispatch(getDaftarPoli());
}, [dispatch]);
useEffect(() => {
dispatch(getPeriksa());
}, [dispatch]);
useEffect(() => {
if (idPeriksa !== "") {
dispatch(getPeriksaByDafPol(idPeriksa));
dispatch(getObat());
}
}, [dispatch, idPeriksa]);
useEffect(() => {
if (periksaByDafPolId?.id) {
dispatch(getDetailPriksaByPeriksaId(periksaByDafPolId?.id));
}
}, [dispatch, periksaByDafPolId?.id]);
useEffect(() => {
if (periksaByDafPolId) {
setEditForm({
id_daftar_poli: periksaByDafPolId.id_daftar_poli,
tgl_periksa: periksaByDafPolId.tanggal,
catatan: periksaByDafPolId.catatan,
biaya_periksa: obatValue.reduce(
(acc, curr) => acc + curr.harga,
150000
),
});
}
}, [periksaByDafPolId, obatValue]);
useEffect(() => {
if (detailPriksaByPriksaId?.length > 0) {
setObatValue([]); // Reset array obat menjadi kosong sebelum membangun kembali
detailPriksaByPriksaId?.forEach((item) => {
setObatValue((obat) => [
...obat,
{
value: item.obat.id,
label: item.obat.nama_obat,
harga: item.obat.harga,
},
]);
});
} else {
setObatValue([]); // Jika detailPriksaByPriksaId kosong, reset obat menjadi array kosong
}
}, [detailPriksaByPriksaId]);
useEffect(() => {
if (role !== "dokter") {
window.location.href = "/";
}
}, [role]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem] ">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Periksa</h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Daftar Periksa</h1>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama Pasien</Table.HeadCell>
<Table.HeadCell>Keluhan</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{daftarPoli.map((item, index) => {
if (item.jadwal_periksa.id_dokter === id) {
index = 0;
return (
<>
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.pasien.nama}</Table.Cell>
<Table.Cell>{item.keluhan}</Table.Cell>
<Table.Cell width={150}>
{periksa?.find(
(periksa) => periksa.id_daftar_poli === item.id
) ? (
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2 flex items-center"
onClick={() => handleEdit(item.id)}
>
<FaEdit className="mr-2" /> Edit
</button>
) : (
<Link
to={
"/dokter/daftar-periksa/periksa/" +
item.id +
"/" +
id
}
className="bg-[#1B4242] p-2 rounded text-white mx-2 flex items-center w-fit"
>
<FaStethoscope className="mr-2" /> Periksa
</Link>
)}
</Table.Cell>
</Table.Row>
</>
);
}
})}
<Modals
openModal={edit}
setOpenModal={() => setEdit(!edit)}
title={"Edit Periksa"}
buttonClose={false}
body={
<form className="flex flex-col" onSubmit={handleUpdate}>
<Input
label="Nama Pasien"
disabled
value={periksaByDafPolId?.daftar_poli?.pasien?.nama}
onChange={(e) =>
setEditForm({ ...editForm, nama: e.target.value })
}
/>
<Input
label="Tanggal Periksa"
type="datetime-local"
value={editForm.tgl_periksa}
onChange={(e) =>
setEditForm({
...editForm,
tgl_periksa: e.target.value,
})
}
/>
<Input
label="Catatan"
value={editForm.catatan}
onChange={(e) =>
setEditForm({ ...editForm, catatan: e.target.value })
}
/>
<ReactSelect
title="Obat"
value={obatValue}
isMulti
onChange={(e) => {
// Membuat objek baru dengan value, label, dan harga
const updatedObatValue = e.map((item) => ({
value: item.value,
label: item.label,
harga:
obat.find(
(obatItem) => obatItem.id === item.value
)?.harga || 0, // Mendapatkan harga dari obat yang cocok dengan value
}));
setObatValue(updatedObatValue); // Menetapkan nilai yang telah diperbarui ke dalam state obatValue
}}
data={obat.map((item) => ({
value: item.id,
label: item.nama_obat,
}))}
/>
<div className="my-3">
<label className="text-black text-sm font-normal mb-5">
Biaya Periksa
</label>
<h2 className="text-black text-md font-normal mb-5">
{formatPriceInRupiah(editForm.biaya_periksa)}
</h2>
</div>
<div className="flex justify-end mt-4">
<button
className="bg-[#1B4242] p-2 px-3 text-sm rounded-md text-white mx-2"
type="submit"
>
Simpan
</button>
</div>
</form>
}
/>
</Table.Body>
</Table>
</div>
</div>
</div>
</div>
);
};
export default DaftarPeriksa;

View File

@ -0,0 +1,155 @@
import {
useLocation,
useNavigate,
useOutletContext,
useParams,
} from "react-router-dom";
import Input from "../../../components/Input";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
getDaftarPoliById,
getObat,
storePeriksa,
} from "../../../config/Redux/Action";
import ReactSelect from "../../../components/ReactSelect";
const PeriksaPasien = () => {
const pathName = useLocation().pathname;
const { id, idPasien } = useParams();
const [role] = useOutletContext();
const { daftarPoliById } = useSelector((state) => state.daftarPoliReducer);
const { obat } = useSelector((state) => state.obatReducer);
const dispatch = useDispatch();
const [addForm, setAddForm] = useState({
id_daftar_poli: idPasien,
tanggal: "",
cataan: "",
});
const [obatOption, setObatOption] = useState([]);
const [obatSelected, setObatSelected] = useState([]);
const [biayaPeriksa, setBiayaPeriksa] = useState(150000);
const nav = useNavigate();
const formatPriceInRupiah = (price) => {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
}).format(price);
};
const handleSubmit = (e) => {
e.preventDefault();
const data = {
...addForm,
biaya_periksa: biayaPeriksa,
};
dispatch(storePeriksa(data, nav, id, obatSelected));
};
useEffect(() => {
if (idPasien !== undefined) {
dispatch(getDaftarPoliById(idPasien));
}
}, [dispatch, idPasien]);
useEffect(() => {
dispatch(getObat());
}, [dispatch]);
useEffect(() => {
if (obat.length > 0) {
const newObat = obat.map((item) => {
return {
value: item.id,
label: item.nama_obat,
harga: item.harga,
};
});
setObatOption(newObat);
}
}, [obat]);
useEffect(() => {
if (obatSelected.length > 0) {
const newBiayaPeriksa = obatSelected.reduce((acc, item) => {
return acc + item.harga;
}, 150000);
setBiayaPeriksa(newBiayaPeriksa);
} else {
setBiayaPeriksa(150000);
}
}, [obatSelected]);
useEffect(() => {
if (role !== "dokter") {
window.location.href = "/";
}
}, [role]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Periksa </h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white mt-5 rounded-t-md">
<div className="header p-3 bg-[#1B4242] text-white rounded-t-md">
<h2>Periksa Pasien</h2>
</div>
<div className="p-5">
<form onSubmit={handleSubmit}>
<Input
label="Nama Pasien"
type="text"
placeholder="Nama Pasien"
value={daftarPoliById?.pasien?.nama}
disabled
/>
<Input
label="Tanggal Periksa"
type="datetime-local"
placeholder="Tanggal Periksa"
value={addForm.tanggal}
onChange={(e) =>
setAddForm({ ...addForm, tanggal: e.target.value })
}
/>
<Input
label="Catatan"
type="text"
placeholder="Catatan"
value={addForm.catatan}
onChange={(e) =>
setAddForm({ ...addForm, catatan: e.target.value })
}
/>
<ReactSelect
title="Obat"
isMulti={true}
data={obatOption}
onChange={(e) => setObatSelected(e)}
/>
<div className="my-3">
<label className="text-black text-sm font-normal mb-5">
Biaya Periksa
</label>
<h2 className="text-black text-md font-normal mb-5">
{formatPriceInRupiah(biayaPeriksa)}
</h2>
</div>
<div className="flex justify-end mt-4">
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
type="submit"
>
Simpan
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default PeriksaPasien;

View File

@ -0,0 +1,93 @@
import { useLocation, useOutletContext } from "react-router-dom";
import Input from "../../components/Input";
import TextArea from "../../components/TextArea";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateDokter } from "../../config/Redux/Action";
const ProfileDokter = () => {
const pathName = useLocation().pathname;
const [role] = useOutletContext();
const dispatch = useDispatch();
const { dokter } = useSelector((state) => state.dokterReducer);
const [dokterForm, setDokterForm] = useState({
nama: dokter.nama,
alamat: dokter.alamat,
no_hp: dokter.no_hp,
});
const handleUpdate = (e) => {
e.preventDefault();
dispatch(updateDokter(dokter.id, dokterForm));
};
useEffect(() => {
setDokterForm({
nama: dokter.nama,
alamat: dokter.alamat,
no_hp: dokter.no_hp,
});
}, [dokter]);
useEffect(() => {
if (role !== "dokter") {
window.location.href = "/";
}
}, [role]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Dokter</h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white mt-5 rounded-t-md">
<div className="header p-3 bg-[#1B4242] text-white rounded-t-md">
<h2>Profile Dokter</h2>
</div>
<div className="p-5">
<form onSubmit={handleUpdate}>
<Input
label="Nama"
type="text"
placeholder="Nama Dokter"
value={dokterForm.nama}
onChange={(e) =>
setDokterForm({ ...dokterForm, nama: e.target.value })
}
/>
<TextArea
label="Alamat"
type="text"
placeholder="Alamat Dokter"
value={dokterForm.alamat}
onChange={(e) =>
setDokterForm({ ...dokterForm, alamat: e.target.value })
}
/>
<Input
label="No. HP"
type="number"
placeholder="No. HP Dokter"
value={dokterForm.no_hp}
onChange={(e) =>
setDokterForm({ ...dokterForm, no_hp: e.target.value })
}
/>
<div className="flex justify-end mt-4">
<button
className="bg-[#5C8374] p-2 rounded text-white mx-2"
type="submit"
>
Simpan
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default ProfileDokter;

View File

@ -0,0 +1,224 @@
import { Table } from "flowbite-react";
import { useLocation, useOutletContext, useParams } from "react-router-dom";
import Modals from "../../components/Modals";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPeriksa } from "../../config/Redux/Action/periksaAction";
import {
getAllPasien,
getDaftarPoli,
getDaftarPoliByPasienId,
getDetailPriksa,
getPasienById,
} from "../../config/Redux/Action";
const RiwayatPasien = () => {
const pathName = useLocation().pathname;
const [openModal, setOpenModal] = useState(false);
const [role] = useOutletContext();
const dispatch = useDispatch();
const { periksa } = useSelector((state) => state.periksaReducer);
const { id } = useParams();
const { daftarPoli, daftarPoliByPasienId } = useSelector(
(state) => state.daftarPoliReducer
);
const { detailPriksa } = useSelector((state) => state.detailPeriksaReducer);
const { pasienAll, pasienById } = useSelector((state) => state.pasienReducer);
const [riwayat, setRiwayat] = useState([]);
const [pasien, setPasien] = useState([]);
const [idPasien, setIdPasien] = useState("");
const handleOpenModal = (id) => {
setOpenModal(true);
setIdPasien(id);
};
const formatPriceInRupiah = (price) => {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
}).format(price);
};
useEffect(() => {
dispatch(getPeriksa());
dispatch(getDaftarPoli());
}, [dispatch]);
useEffect(() => {
dispatch(getAllPasien());
}, [dispatch]);
useEffect(() => {
if (idPasien) {
dispatch(getDaftarPoliByPasienId(idPasien));
}
}, [dispatch, idPasien]);
useEffect(() => {
const matchedPatients = periksa
.map((item) => {
return daftarPoli.find(
(item2) =>
item.id_daftar_poli === item2.id &&
item2.jadwal_periksa.id_dokter === id
);
})
.filter((matchedItem) => matchedItem !== undefined);
const matchedPatients2 = matchedPatients
.map((item) => {
return pasienAll.find((item2) => item2.id === item.id_pasien);
})
.filter((matchedItem) => matchedItem !== undefined);
const uniquePatients = [
...new Map(matchedPatients2.map((item) => [item.id, item])).values(),
];
setPasien(uniquePatients || []);
return () => {
setPasien([]);
};
}, [pasienAll, daftarPoli, id, periksa]);
useEffect(() => {
if (daftarPoliByPasienId.length > 0) {
const filteredRiwayat = periksa.filter((item2) =>
daftarPoliByPasienId.some(
(item) =>
item.id === item2.id_daftar_poli &&
item2?.daftar_poli?.jadwal_periksa?.id_dokter === id &&
item.id_pasien === idPasien
)
);
setRiwayat(filteredRiwayat);
}
}, [daftarPoliByPasienId, periksa, id, idPasien]);
useEffect(() => {
if (riwayat.length > 0) {
dispatch(getDetailPriksa());
}
}, [riwayat, dispatch]);
useEffect(() => {
if (idPasien) {
dispatch(getPasienById(idPasien));
}
}, [dispatch, idPasien]);
useEffect(() => {
if (role !== "dokter") {
window.location.href = "/";
}
}, [role]);
return (
<div className="container min-h-[90vh] m-5 my-[3rem]">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Riwayat Pasien</h1>
<h1>{pathName}</h1>
</div>
<div className="card bg-white p-5 mt-[3rem]">
<div className="card-body">
<div className="flex justify-between">
<h1 className="text-xl font-medium">Daftar Riwayat Pasien</h1>
</div>
<div className="overflow-x-auto mt-4">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Nama</Table.HeadCell>
<Table.HeadCell width="30%">Alamat</Table.HeadCell>
<Table.HeadCell>No. KTP</Table.HeadCell>
<Table.HeadCell>No. HP</Table.HeadCell>
<Table.HeadCell>No. RM</Table.HeadCell>
<Table.HeadCell>Aksi</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{pasien.map((item, index) => {
return (
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.nama}</Table.Cell>
<Table.Cell>{item.alamat}</Table.Cell>
<Table.Cell>{item.no_ktp}</Table.Cell>
<Table.Cell>{item.no_hp}</Table.Cell>
<Table.Cell>{item.no_rm}</Table.Cell>
<Table.Cell>
<button
className="bg-[#ff8d9a] p-2 rounded text-white mx-2"
onClick={() => handleOpenModal(item.id)}
>
Detail Riwayat Periksa
</button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
<Modals
openModal={openModal}
setOpenModal={setOpenModal}
size="xxl"
title={"Riwayat " + pasienById?.nama}
body={
<div className="overflow-x-auto">
<Table striped>
<Table.Head>
<Table.HeadCell>No</Table.HeadCell>
<Table.HeadCell>Tanggal Periksa</Table.HeadCell>
<Table.HeadCell>Nama Pasien</Table.HeadCell>
<Table.HeadCell>Nama Dokter</Table.HeadCell>
<Table.HeadCell>Keluhan</Table.HeadCell>
<Table.HeadCell>Catatan</Table.HeadCell>
<Table.HeadCell>Obat</Table.HeadCell>
<Table.HeadCell>Biaya Priksa</Table.HeadCell>
<Table.HeadCell>Total Biaya</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
{riwayat?.map((item, index) => (
<Table.Row key={index}>
<Table.Cell>{index + 1}</Table.Cell>
<Table.Cell>{item.tanggal}</Table.Cell>
<Table.Cell>
{item.daftar_poli?.pasien?.nama}
</Table.Cell>
<Table.Cell>
Dr. {item.daftar_poli?.jadwal_periksa?.dokter?.nama}
</Table.Cell>
<Table.Cell>{item?.daftar_poli?.keluhan}</Table.Cell>
<Table.Cell>{item?.catatan}</Table.Cell>
<Table.Cell>
{detailPriksa?.map((item2, index) => {
if (item2.id_periksa === item.id) {
return (
<div key={index}>
{item2?.obat?.nama_obat} -{" "}
{formatPriceInRupiah(item2?.obat?.harga)}
</div>
);
}
})}
</Table.Cell>
<Table.Cell>{formatPriceInRupiah(150000)}</Table.Cell>
<Table.Cell>
{formatPriceInRupiah(item.biaya_periksa)}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</div>
}
/>
</div>
</div>
</div>
</div>
);
};
export default RiwayatPasien;

449
src/pages/Home.jsx Normal file
View File

@ -0,0 +1,449 @@
import { Dropdown, Footer, Navbar } from "flowbite-react";
import { Link, useNavigate, useParams } from "react-router-dom";
import udinus from "../assets/logo/udinus.png";
import {
BsDribbble,
BsFacebook,
BsFillPersonLinesFill,
BsGithub,
BsInstagram,
BsTwitter,
} from "react-icons/bs";
import { MdOutlineArrowRightAlt } from "react-icons/md";
import SwiperCoverflow from "../components/SwiperCoverflow";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import {
getAdmin,
logoutAdmin,
getDokter,
logoutDokter,
addDaftarPoli,
getJadwalPeriksa,
getPasien,
getPoli,
logoutPasien,
} from "../config/Redux/Action";
import Modals from "../components/Modals";
import Input from "../components/Input";
import ReactSelect from "../components/ReactSelect";
import TextArea from "../components/TextArea";
import { FaAngleDown } from "react-icons/fa6";
import { HiLogout } from "react-icons/hi";
import { ToastContainer } from "react-toastify";
import transition from "../transition";
const Home = () => {
const { id } = useParams();
const dispatch = useDispatch();
const nav = useNavigate();
const { admin, isLogin } = useSelector((state) => state.adminReducer);
const { dokter, isLoginDokter } = useSelector((state) => state.dokterReducer);
const { pasien, isLoginPasien } = useSelector((state) => state.pasienReducer);
const { poli } = useSelector((state) => state.poliReducer);
const { jadwalPeriksa } = useSelector((state) => state.jadwalPeriksaReducer);
const token = localStorage.getItem("token");
const role = localStorage.getItem("role");
const [daftarPoli, setDaftarPoli] = useState(false);
const [poliOption, setPoliOption] = useState([]);
const [selectedPoli, setSelectedPoli] = useState();
const [jadwal, setJadwal] = useState([]);
const [daftarPoliForm, setDaftarPoliForm] = useState({
id_pasien: pasien.id,
id_jadwal: "",
keluhan: "",
});
const handleLogoutAdmin = () => {
dispatch(logoutAdmin(token, nav));
};
const handleLogoutDokter = () => {
dispatch(logoutDokter(token, nav));
};
const handleLogoutPasien = () => {
dispatch(logoutPasien(token, nav));
};
const handleOpenDaftarPoli = () => {
setDaftarPoli(true);
dispatch(getPoli());
};
const handleCloseDaftarPoli = () => {
setDaftarPoli(false);
};
const handleDaftarPoli = (e) => {
e.preventDefault();
dispatch(addDaftarPoli(daftarPoliForm));
setDaftarPoli(false);
};
const dateFormat = (date) => {
return date?.split(" ")[0];
};
const timeFormat = (time) => {
const date = new Date(`1970-01-01T${time}`);
if (isNaN(date)) {
return "Invalid time";
}
return date.toLocaleTimeString("id-ID", {
hour: "2-digit",
minute: "2-digit",
});
};
useEffect(() => {
if (role === "admin") {
if (!token || id === undefined) {
dispatch(getAdmin(token));
if (isLogin && admin.id !== undefined) {
nav("/" + admin.id);
}
} else {
dispatch(getAdmin(token));
}
}
}, [dispatch, id, isLogin, nav, token, admin.id, role]);
useEffect(() => {
if (role === "dokter") {
if (!token || id === undefined) {
dispatch(getDokter(token));
if (isLoginDokter && dokter.id !== undefined) {
nav("/" + dokter.id);
}
} else {
dispatch(getDokter(token));
}
}
}, [dispatch, id, isLoginDokter, nav, token, dokter.id, role]);
useEffect(() => {
if (role === "pasien") {
if (!token || id === undefined) {
dispatch(getPasien(token));
if (isLoginPasien && pasien.id !== undefined) {
nav("/" + pasien.id);
}
} else {
dispatch(getPasien(token));
}
}
}, [dispatch, id, isLoginPasien, nav, token, pasien.id, role]);
useEffect(() => {
if (poli) {
const data = poli.map((item) => {
return {
value: item.id,
label: item.nama_poli,
};
});
setPoliOption(data);
}
}, [poli]);
useEffect(() => {
if (selectedPoli) {
dispatch(getJadwalPeriksa());
}
}, [dispatch, selectedPoli]);
useEffect(() => {
if (jadwalPeriksa) {
const currentDate = new Date();
const data = jadwalPeriksa
.map((item) => {
const date = item.tanggal.split(" ")[0];
const time = new Date(`${date}T${item.jam_mulai}`);
if (
item.dokter.poli.nama_poli === selectedPoli &&
time >= currentDate &&
item.status === "Y"
) {
return {
value: item.id,
label: `Dr. ${item.dokter.nama}, ${item.hari}, ${dateFormat(
item.tanggal
)}, jam ${timeFormat(item.jam_mulai)} sampai ${timeFormat(
item.jam_selesai
)}`,
};
}
// Jika kondisi tidak terpenuhi, kembalikan null atau objek kosong
return null; // atau return {};
})
.filter((item) => item !== null); // Filter elemen yang bernilai null
setJadwal(data);
}
}, [jadwalPeriksa, selectedPoli]);
return (
<>
<ToastContainer />
<Navbar className="shadow-md py-5 bg-[#00008B]">
<Navbar.Brand as={Link} href="https://flowbite-react.com">
<img
src={udinus}
className="mr-3 h-6 sm:h-9"
alt="Flowbite React Logo"
/>
<span className="self-center whitespace-nowrap text-xl font-semibold text-white">
Poliklinik BK
</span>
</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse className="flex items-center">
<Navbar.Link
href="#"
className="flex items-center h-full text-white"
as={Link}
>
Home
</Navbar.Link>
<Navbar.Link
as={Link}
href="/dashboard"
className="flex items-center h-full text-white"
>
About
</Navbar.Link>
{id && token ? (
<>
{role !== "pasien" && (
<Link
to={role == "admin" ? `/admin/${id}` : `/dokter/${id}`}
className="flex items-center h-100 text-white"
>
Dashboard
</Link>
)}
<div className="flex items-center">
<Dropdown
renderTrigger={() => (
<div className="flex items-center cursor-pointer">
<div className="flex flex-col justify-center items-end">
<h3 className=" font-bold text-white">
{admin.username
? admin.username
: dokter.username
? dokter.username
: pasien.username}
</h3>
<span className=" text-[12px] text-white">
{admin.role
? admin.role
: dokter.role
? dokter.role
: pasien.role}
</span>
</div>
<FaAngleDown className="text-white mr-2 ml-1" />
</div>
)}
dismissOnClick={false}
className="flex flex-col items-center py-3 px-5"
>
<button
className="flex items-center bg-red-500 hover:bg-red-600 p-2 px-3 text-white rounded-md w-full"
onClick={
role === "admin"
? () => handleLogoutAdmin()
: role === "dokter"
? () => handleLogoutDokter()
: () => handleLogoutPasien()
}
>
<HiLogout className="mr-2" />
Logout
</button>
</Dropdown>
<img
src="https://i.pravatar.cc/150?img=3"
alt="user"
className="w-10 h-10 rounded-full"
/>
</div>
</>
) : (
<Link to={"/login"}>
<button className="bg-[#1E90FF] text-black p-2 px-3 rounded-lg">
Login
</button>
</Link>
)}
</Navbar.Collapse>
</Navbar>
<main className="container mx-auto mt-8 px-4 sm:px-6 lg:px-8">
{/* Hero Section */}
<section className="relative h-[600px] lg:h-[800px] overflow-hidden">
<img
src="https://mmc.tirto.id/image/2020/04/14/ilustrasi-dokter-istock--1_ratio-16x9.jpg"
alt="hero"
className="object-cover w-full h-full"
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center text-white w-full px-4 sm:px-6 lg:px-8">
<h1 className="text-4xl lg:text-6xl font-bold leading-tight mb-4">
The Best Poliklinik in Semarang
</h1>
<p className="text-lg lg:text-xl mb-6">
There are many expert doctors here.
</p>
<div className="flex justify-center items-center">
<p className="mt-4">
{role === "pasien" ? (
<button
className="bg-blue-900 p-2 px-3 mt-4 text-white flex w-fit items-center rounded-md"
onClick={() => handleOpenDaftarPoli()}
>
Daftar Poli
<MdOutlineArrowRightAlt color="white" className="ml-2" />
</button>
) : role === "admin" || role === "dokter" ? (
<Link
to={`/${role}/${role === "admin" ? admin.id : dokter.id}`}
className="bg-blue-900 p-2 px-3 mt-4 text-white flex w-fit items-center rounded-md"
>
Dashboard{" "}
<MdOutlineArrowRightAlt color="white" className="ml-2" />
</Link>
) : (
<Link
to={`/login`}
className="bg-blue-900 p-2 px-3 mt-4 text-white flex w-fit items-center rounded-md"
>
Register Now{" "}
<MdOutlineArrowRightAlt color="white" className="ml-2" />
</Link>
)}
</p>
</div>
</div>
<Modals
openModal={daftarPoli}
setOpenModal={handleCloseDaftarPoli}
title="Daftar Poli"
buttonClose={false}
body={
<form className="mt-[-1.5rem]" onSubmit={handleDaftarPoli}>
<Input
label="Nomor Rekam Medis"
type="text"
placeholder="Nomor Rekam Medis"
value={pasien.no_rm}
disabled={true}
/>
<ReactSelect
data={poliOption}
title="Poli"
onChange={(e) => setSelectedPoli(e.label)}
/>
<ReactSelect
data={jadwal}
title="Pilih Jadwal"
disabled={selectedPoli === "" ? true : false}
onChange={(e) =>
setDaftarPoliForm({ ...daftarPoliForm, id_jadwal: e.value })
}
/>
<TextArea
label="Keluhan"
placeholder="Keluhan"
onChange={(e) =>
setDaftarPoliForm({
...daftarPoliForm,
keluhan: e.target.value,
})
}
/>
<div className="flex justify-end">
<button
type="submit"
className="bg-blue-900 p-2 px-3 mt-4 text-white w-fit rounded-md text-sm"
>
Daftar Poli
</button>
</div>
</form>
}
/>
</section>
{/* Testimonial Section */}
<section className="mb-[5rem] mt-8">
<div className="text-center">
<h1 className="text-4xl font-bold leading-tight text-gray-900">
Patient Testimonal
</h1>
<p className="text-xl text-gray-700 mt-2">
Testimoni dari pasien yang telah menggunakan layanan kami.
</p>
<div className="my-[8rem]">
<SwiperCoverflow />
</div>
</div>
</section>
</main>
<Footer container className="bg-[#00008B] rounded-none">
<div className="container mx-auto">
<div className="grid w-full justify-between sm:flex sm:justify-between md:flex md:grid-cols-1">
<div>
<Footer.Brand
src={udinus}
alt="Udinus Logo"
name="Udinus"
className="h-[4rem] sm:h-[5rem] text-white"
/>
</div>
<div className="grid grid-cols-2 gap-8 sm:mt-4 sm:grid-cols-3 sm:gap-6">
<div>
<Footer.Title title="about" />
<Footer.LinkGroup col>
<Footer.Link href="#">Flowbite</Footer.Link>
<Footer.Link href="#">Tailwind CSS</Footer.Link>
</Footer.LinkGroup>
</div>
<div>
<Footer.Title title="Follow us" />
<Footer.LinkGroup col>
<Footer.Link href="#">Github</Footer.Link>
<Footer.Link href="#">Discord</Footer.Link>
</Footer.LinkGroup>
</div>
<div>
<Footer.Title title="Legal" />
<Footer.LinkGroup col>
<Footer.Link href="#">Privacy Policy</Footer.Link>
<Footer.Link href="#">Terms &amp; Conditions</Footer.Link>
</Footer.LinkGroup>
</div>
</div>
</div>
<Footer.Divider />
<div className="w-full sm:flex sm:items-center sm:justify-between">
<Footer.Copyright href="#" by="Flowbite™" year={2022} />
<div className="mt-4 flex space-x-6 sm:mt-0 sm:justify-center">
<Footer.Icon href="#" icon={BsFacebook} />
<Footer.Icon href="#" icon={BsInstagram} />
<Footer.Icon href="#" icon={BsTwitter} />
<Footer.Icon href="#" icon={BsGithub} />
<Footer.Icon href="#" icon={BsDribbble} />
</div>
</div>
</div>
</Footer>
</>
);
};
export default Home;

32
src/transition.jsx Normal file
View File

@ -0,0 +1,32 @@
import { motion } from "framer-motion";
import udinus from "./assets/logo/udinus.png";
const transition = (OgComponent) => {
return () => {
return (
<>
<OgComponent />
<motion.div
className="fixed top-0 left-0 w-full h-screen origin-right bg-[#092635] z-50"
initial={{ scaleX: 0 }}
animate={{ scaleX: 0 }}
exit={{ scaleX: 1 }}
transition={{ duration: 1, ease: [0.22, 1, 0.36, 1] }}
>
<div className="flex justify-center items-center h-full">
<img src={udinus} alt="udinus" className="w-40" />
</div>
</motion.div>
<motion.div
className="fixed top-0 left-0 w-full h-screen origin-left bg-[#092635] z-50"
initial={{ scaleX: 1 }}
animate={{ scaleX: 0 }}
exit={{ scaleX: 0 }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
/>
</>
);
};
};
export default transition;

17
tailwind.config.js Normal file
View File

@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"node_modules/flowbite-react/lib/esm/**/*.js",
],
theme: {
extend: {
fontFamily: {
poppins: ["Poppins", "sans-serif"],
},
},
},
// eslint-disable-next-line no-undef
plugins: [require("flowbite/plugin")],
};

7
vite.config.js Normal file
View File

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