School Commit Init
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs', 'node_modules', '.prettierrc.json'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh', '@typescript-eslint', 'import'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'prefer-const': 'error',
|
||||
'import/prefer-default-export': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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?
|
||||
coverage
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"jsxSingleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!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>Silk Road</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "silk-road-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"lint-staged": "lint-staged",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest --coverage ",
|
||||
"ui": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/x-charts": "^6.19.8",
|
||||
"axios": "^1.6.8",
|
||||
"husky": "^9.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-use-websockets": "^2.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mui/icons-material": "^5.15.13",
|
||||
"@mui/material": "^5.15.13",
|
||||
"@reduxjs/toolkit": "^2.2.2",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^18.2.64",
|
||||
"@types/react-dom": "^18.2.21",
|
||||
"@types/styled-components": "^5.1.34",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"@vitest/coverage-v8": "^1.4.0",
|
||||
"@vitest/ui": "^1.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"jest": "^29.7.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"styled-components": "^6.1.8",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.6",
|
||||
"vitest": "^1.4.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
"eslint --report-unused-disable-directives --max-warnings 0"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -0,0 +1,42 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import routes from './constants/routes';
|
||||
|
||||
function App() {
|
||||
return <RouterProvider router={routes} />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -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 |
+131
@@ -0,0 +1,131 @@
|
||||
import Card from '@mui/material/Card';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getDrugById } from '../../redux/selectors';
|
||||
import { RootState } from '../../redux/store';
|
||||
import axios from 'axios';
|
||||
import Drug from '../../interfaces/Drug';
|
||||
|
||||
export default function DrugDetails() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const id = parseInt(useParams().id ?? '0');
|
||||
|
||||
const [drug, setDrug] = useState<Drug | null>(null);
|
||||
const potentialDrug = useSelector((state: RootState) =>
|
||||
getDrugById(state, id)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDrugById = async (id: number) => {
|
||||
try {
|
||||
const drug = await axios.get<Drug>(
|
||||
`https://silkroadapi.azurewebsites.net/Drug/get-drug/${id}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setDrug(drug.data);
|
||||
console.log(drug);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
navigate('/error');
|
||||
}
|
||||
};
|
||||
if (potentialDrug) setDrug(potentialDrug);
|
||||
else fetchDrugById(id);
|
||||
}, [id, navigate, potentialDrug]);
|
||||
|
||||
const handleBackButton = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
!!drug && (
|
||||
<div style={{ marginLeft: '15%', marginRight: '15%' }}>
|
||||
<Card sx={{ maxWidth: 1600 }}>
|
||||
<CardContent>
|
||||
<Typography variant='h3' gutterBottom>
|
||||
{drug?.name}
|
||||
</Typography>
|
||||
<Typography variant='h4' component='div'>
|
||||
${drug?.price}
|
||||
</Typography>
|
||||
<Typography sx={{ mb: 1.5 }} color='text.secondary'>
|
||||
{drug?.description}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Manufacturer:
|
||||
<br />
|
||||
{drug?.manufacturer}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Side Effects:
|
||||
<br />
|
||||
{drug?.sideEffects}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Interactions:
|
||||
<br />
|
||||
{drug?.interactions}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Dosage:
|
||||
<br />
|
||||
{drug?.dosage}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Contraindications:
|
||||
<br />
|
||||
{drug?.contraindications}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Warnings:
|
||||
<br />
|
||||
{drug?.warnings}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Storage:
|
||||
<br />
|
||||
{drug?.storage}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Pregnacy:
|
||||
<br />
|
||||
{drug?.pregnancy}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Breastfeeding:
|
||||
<br />
|
||||
{drug?.breastfeeding}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Overdose:
|
||||
<br />
|
||||
{drug?.overdose}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size='small' onClick={handleBackButton}>
|
||||
Back
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import Card from '@mui/material/Card';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import axios from 'axios';
|
||||
import Manufacturer from '../../interfaces/Manufacturer';
|
||||
|
||||
export default function DrugDetails() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const id = parseInt(useParams().id ?? '0');
|
||||
|
||||
const [drug, setDrug] = useState<Manufacturer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDrugById = async (id: number) => {
|
||||
try {
|
||||
const drug = await axios.get<Manufacturer>(
|
||||
`https://silkroadapi.azurewebsites.net/Manufacturer/get-manufacturer/${id}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setDrug(drug.data);
|
||||
console.log(drug);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
navigate('/error');
|
||||
}
|
||||
};
|
||||
fetchDrugById(id);
|
||||
}, [id, navigate]);
|
||||
|
||||
const handleBackButton = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
!!drug && (
|
||||
<div style={{ marginLeft: '15%', marginRight: '15%' }}>
|
||||
<Card sx={{ maxWidth: 1600 }}>
|
||||
<CardContent>
|
||||
<Typography variant='h3' gutterBottom>
|
||||
{drug?.name}
|
||||
</Typography>
|
||||
<Typography variant='h4' component='div'>
|
||||
{drug?.address}
|
||||
</Typography>
|
||||
<Typography sx={{ mb: 1.5 }} color='text.secondary'>
|
||||
{drug?.contact}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant='body1'>
|
||||
Total Count:
|
||||
<br />
|
||||
{drug?.totalCount}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size='small' onClick={handleBackButton}>
|
||||
Back
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import * as React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableFooter from '@mui/material/TableFooter';
|
||||
import TablePagination from '@mui/material/TablePagination';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
||||
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
|
||||
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
||||
import LastPageIcon from '@mui/icons-material/LastPage';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
TableHead
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DeleteDialog from '../Shared/DeleteDialog';
|
||||
import Drug from '../../interfaces/Drug';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import axios from 'axios';
|
||||
import { loadDrugs } from '../../redux/slices/drugSlice';
|
||||
import { getWebSocket } from '../../redux/selectors';
|
||||
|
||||
interface TablePaginationActionsProps {
|
||||
count: number;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
onPageChange: (
|
||||
event: React.MouseEvent<HTMLButtonElement>,
|
||||
newPage: number
|
||||
) => void;
|
||||
}
|
||||
|
||||
function TablePaginationActions(props: TablePaginationActionsProps) {
|
||||
const theme = useTheme();
|
||||
const { count, page, rowsPerPage, onPageChange } = props;
|
||||
|
||||
const handleFirstPageButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, 0);
|
||||
};
|
||||
|
||||
const handleBackButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, page - 1);
|
||||
};
|
||||
|
||||
const handleNextButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, page + 1);
|
||||
};
|
||||
|
||||
const handleLastPageButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
|
||||
<IconButton
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label='first page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label='previous page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label='next page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label='last page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const DrugTable = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [page, setPage] = useState(
|
||||
sessionStorage.getItem('page')
|
||||
? parseInt(sessionStorage.getItem('page') as string)
|
||||
: 0
|
||||
);
|
||||
const ws = useSelector(getWebSocket);
|
||||
|
||||
const [sort, setSort] = useState('');
|
||||
const [open, setOpen] = useState(-1);
|
||||
const [drugs, setDrugs] = useState<Drug[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleViewClick = (id: number) => {
|
||||
navigate(`/details/${id}`);
|
||||
};
|
||||
const handleUpdateClick = (id: number) => {
|
||||
navigate(`/update/${id}`);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id: number) => {
|
||||
setOpen(id);
|
||||
};
|
||||
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(
|
||||
sessionStorage.getItem('rowsPerPage')
|
||||
? parseInt(sessionStorage.getItem('rowsPerPage') as string, 10)
|
||||
: 5
|
||||
);
|
||||
|
||||
const emptyRows =
|
||||
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - total) : 0;
|
||||
|
||||
const handleChangePage = (
|
||||
_event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
newPage: number
|
||||
) => {
|
||||
setPage(newPage);
|
||||
sessionStorage.setItem('page', newPage.toString());
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
sessionStorage.setItem('rowsPerPage', event.target.value);
|
||||
};
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://silkroadapi.azurewebsites.net/Drug/get-drugs?Page=${
|
||||
page + 1
|
||||
}&PageSize=${rowsPerPage}&sorting=${sort}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
dispatch(loadDrugs(response.data.items));
|
||||
setDrugs(response.data.items);
|
||||
setTotal(response.data.total);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(true);
|
||||
}
|
||||
}, [dispatch, page, rowsPerPage, sort]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, sort, dispatch, rowsPerPage, fetchData]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(ws);
|
||||
ws?.addEventListener('message', (event) => {
|
||||
console.log(event.data);
|
||||
fetchData();
|
||||
});
|
||||
}, [fetchData, ws]);
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 500 }} aria-label='custom pagination table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
data-asc={'false'}
|
||||
onClick={(e) => {
|
||||
setSort(
|
||||
e.currentTarget.dataset.asc === 'false' ? 'name' : 'name_desc'
|
||||
);
|
||||
e.currentTarget.dataset.asc =
|
||||
e.currentTarget.dataset.asc === 'false' ? 'true' : 'false';
|
||||
}}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align='right'
|
||||
onClick={(e) => {
|
||||
setSort(
|
||||
e.currentTarget.dataset.asc === 'false'
|
||||
? 'manufacturer'
|
||||
: 'manufacturer_desc'
|
||||
);
|
||||
e.currentTarget.dataset.asc =
|
||||
e.currentTarget.dataset.asc === 'false' ? 'true' : 'false';
|
||||
}}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
Manufacturer
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align='right'
|
||||
onClick={(e) => {
|
||||
setSort(
|
||||
e.currentTarget.dataset.asc === 'false'
|
||||
? 'price'
|
||||
: 'price_desc'
|
||||
);
|
||||
e.currentTarget.dataset.asc =
|
||||
e.currentTarget.dataset.asc === 'false' ? 'true' : 'false';
|
||||
}}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
Price (USD)
|
||||
</TableCell>
|
||||
<TableCell align='right'>View</TableCell>
|
||||
<TableCell align='right'>Update</TableCell>
|
||||
<TableCell align='right'>Delete</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{drugs.map((drug) => (
|
||||
<TableRow key={drug.id}>
|
||||
<TableCell component='th' scope='row'>
|
||||
{drug.name}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
{drug.manufacturer}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
${drug.price}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleViewClick(drug.id);
|
||||
}}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleUpdateClick(drug.id);
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleDeleteClick(drug.id);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 53 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={total}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
slotProps={{
|
||||
select: {
|
||||
inputProps: {
|
||||
'aria-label': 'rows per page'
|
||||
},
|
||||
native: true
|
||||
}
|
||||
}}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<DeleteDialog
|
||||
id={open}
|
||||
handleClose={() => {
|
||||
setOpen(-1);
|
||||
}}
|
||||
handleAgree={async () => {
|
||||
await axios.delete(
|
||||
`https://silkroadapi.azurewebsites.net/Drug/delete-drug/${open}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
await fetchData();
|
||||
setOpen(-1);
|
||||
}}
|
||||
/>
|
||||
<Dialog
|
||||
open={error}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle id='alert-dialog-title'>{'Error'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id='alert-dialog-description'>
|
||||
Error while connecting
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setError(false);
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrugTable;
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
import * as React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableFooter from '@mui/material/TableFooter';
|
||||
import TablePagination from '@mui/material/TablePagination';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
||||
import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
|
||||
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
||||
import LastPageIcon from '@mui/icons-material/LastPage';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
TableHead
|
||||
} from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DeleteDialog from '../Shared/DeleteDialog';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import axios from 'axios';
|
||||
import Manufacturer from '../../interfaces/Manufacturer';
|
||||
|
||||
interface TablePaginationActionsProps {
|
||||
count: number;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
onPageChange: (
|
||||
event: React.MouseEvent<HTMLButtonElement>,
|
||||
newPage: number
|
||||
) => void;
|
||||
}
|
||||
|
||||
function TablePaginationActions(props: TablePaginationActionsProps) {
|
||||
const theme = useTheme();
|
||||
const { count, page, rowsPerPage, onPageChange } = props;
|
||||
|
||||
const handleFirstPageButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, 0);
|
||||
};
|
||||
|
||||
const handleBackButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, page - 1);
|
||||
};
|
||||
|
||||
const handleNextButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, page + 1);
|
||||
};
|
||||
|
||||
const handleLastPageButtonClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
|
||||
<IconButton
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label='first page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label='previous page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label='next page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label='last page'
|
||||
>
|
||||
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const ManufacturerTable = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [page, setPage] = useState(
|
||||
sessionStorage.getItem('page')
|
||||
? parseInt(sessionStorage.getItem('page') as string)
|
||||
: 0
|
||||
);
|
||||
|
||||
const [sort] = useState('');
|
||||
const [open, setOpen] = useState(-1);
|
||||
const [manufacturers, setDrugs] = useState<Manufacturer[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(
|
||||
sessionStorage.getItem('rowsPerPage')
|
||||
? parseInt(sessionStorage.getItem('rowsPerPage') as string, 10)
|
||||
: 5
|
||||
);
|
||||
|
||||
const emptyRows =
|
||||
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - total) : 0;
|
||||
|
||||
const handleChangePage = (
|
||||
_event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
newPage: number
|
||||
) => {
|
||||
setPage(newPage);
|
||||
sessionStorage.setItem('page', newPage.toString());
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
sessionStorage.setItem('rowsPerPage', event.target.value);
|
||||
};
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://silkroadapi.azurewebsites.net/Manufacturer/get-manufacturer?Page=${
|
||||
page + 1
|
||||
}&PageSize=${rowsPerPage}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setDrugs(response.data.items);
|
||||
setTotal(response.data.total);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(true);
|
||||
}
|
||||
}, [page, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, sort, dispatch, rowsPerPage, fetchData]);
|
||||
|
||||
const handleViewClick = (id: number) => {
|
||||
navigate(`/manufacturer/details/${id}`);
|
||||
};
|
||||
const handleUpdateClick = (id: number) => {
|
||||
navigate(`/manufacturer/update/${id}`);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id: number) => {
|
||||
setOpen(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 500 }} aria-label='custom pagination table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align='right'>Andress</TableCell>
|
||||
<TableCell align='right'>Contact</TableCell>
|
||||
<TableCell align='right'>Total Drugs</TableCell>
|
||||
<TableCell align='right'>View</TableCell>
|
||||
<TableCell align='right'>Update</TableCell>
|
||||
<TableCell align='right'>Delete</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{manufacturers.map((manufacturer) => (
|
||||
<TableRow key={manufacturer.id}>
|
||||
<TableCell component='th' scope='row'>
|
||||
{manufacturer.name}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
{manufacturer.address}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
{manufacturer.contact}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
{manufacturer.totalCount}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleViewClick(manufacturer.id ?? 0);
|
||||
}}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleUpdateClick(manufacturer.id ?? 0);
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell style={{ width: 160 }} align='right'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
handleDeleteClick(manufacturer.id ?? 0);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 53 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={total}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
slotProps={{
|
||||
select: {
|
||||
inputProps: {
|
||||
'aria-label': 'rows per page'
|
||||
},
|
||||
native: true
|
||||
}
|
||||
}}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<DeleteDialog
|
||||
id={open}
|
||||
handleClose={() => {
|
||||
setOpen(-1);
|
||||
}}
|
||||
handleAgree={async () => {
|
||||
await axios.delete(
|
||||
`https://silkroadapi.azurewebsites.net/Manufacturer/delete-manufacturer/${open}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
await fetchData();
|
||||
setOpen(-1);
|
||||
}}
|
||||
/>
|
||||
<Dialog
|
||||
open={error}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle id='alert-dialog-title'>{'Error'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id='alert-dialog-description'>
|
||||
Error while connecting
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setError(false);
|
||||
}}
|
||||
color='primary'
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManufacturerTable;
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
|
||||
const DeleteDialog = (props: {
|
||||
id: number;
|
||||
handleClose: () => void;
|
||||
handleAgree: () => void;
|
||||
}) => {
|
||||
const { id, handleClose, handleAgree } = props;
|
||||
return (
|
||||
<Dialog
|
||||
open={id >= 0}
|
||||
onClose={handleClose}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle id='alert-dialog-title'>{'Delete?'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id='alert-dialog-description'>
|
||||
Are you sure you want to delete this item?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color='primary'>
|
||||
No
|
||||
</Button>
|
||||
<Button onClick={handleAgree} color='primary' autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteDialog;
|
||||
@@ -0,0 +1,216 @@
|
||||
import { Button, FormLabel } from '@mui/material';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { ChangeEvent, FormEvent, useState } from 'react';
|
||||
import Drug from '../../interfaces/Drug';
|
||||
import getEmptyDrug from '../../utils/DrugUtils';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
export default function DrugFrom(props: { updateDrug?: Drug }) {
|
||||
const [drug, setDrug] = useState(props.updateDrug ?? getEmptyDrug());
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setDrug({
|
||||
...drug,
|
||||
[e.target.name]: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
if (drug.id === undefined || drug.id === -1) {
|
||||
addDrug(drug);
|
||||
} else {
|
||||
updateDrug(drug);
|
||||
}
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const addDrug = async (drug: Drug) => {
|
||||
try {
|
||||
axios.post('https://silkroadapi.azurewebsites.net/Drug/add-drug', drug, {
|
||||
withCredentials: true
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateDrug = async (drug: Drug) => {
|
||||
try {
|
||||
axios.put(
|
||||
'https://silkroadapi.azurewebsites.net/Drug/update-drug',
|
||||
drug,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FormControl
|
||||
sx={{
|
||||
width: '70%',
|
||||
marginLeft: '15%',
|
||||
marginRight: '15%',
|
||||
marginBottom: '30px',
|
||||
marginTop: '30px'
|
||||
}}
|
||||
>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='name'
|
||||
title='Name'
|
||||
value={drug.name}
|
||||
/>
|
||||
<FormLabel>Stock</FormLabel>
|
||||
<TextField
|
||||
type='number'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='stock'
|
||||
title='Stock'
|
||||
value={drug.stock}
|
||||
/>
|
||||
<FormLabel>Price</FormLabel>
|
||||
<TextField
|
||||
type='number'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='price'
|
||||
title='Price'
|
||||
value={drug.price}
|
||||
/>
|
||||
<FormLabel>Manufacturer</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='manufacturer'
|
||||
title='Manufacturer'
|
||||
value={drug.manufacturer}
|
||||
/>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='description'
|
||||
title='Description'
|
||||
value={drug.description}
|
||||
/>
|
||||
<FormLabel>Side Effects</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='sideEffects'
|
||||
title='Side Effects'
|
||||
value={drug.sideEffects}
|
||||
/>
|
||||
<FormLabel>Interactions</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='interactions'
|
||||
title='Interactions'
|
||||
value={drug.interactions}
|
||||
/>
|
||||
<FormLabel>Dosage</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='dosage'
|
||||
title='Dosage'
|
||||
value={drug.dosage}
|
||||
/>
|
||||
<FormLabel>Contraindications</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='contraindications'
|
||||
title='Contraindications'
|
||||
value={drug.contraindications}
|
||||
/>
|
||||
<FormLabel>Warnings</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='warnings'
|
||||
title='Warnings'
|
||||
value={drug.warnings}
|
||||
/>
|
||||
<FormLabel>Storage</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='storage'
|
||||
title='Storage'
|
||||
value={drug.storage}
|
||||
/>
|
||||
<FormLabel>Pregnancy</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='pregnancy'
|
||||
title='Pregnancy'
|
||||
value={drug.pregnancy}
|
||||
/>
|
||||
<FormLabel>Breastfeeding</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='breastfeeding'
|
||||
title='Breastfeeding'
|
||||
value={drug.breastfeeding}
|
||||
/>
|
||||
<FormLabel>Overdose</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
multiline
|
||||
rows={4}
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='overdose'
|
||||
title='Overdose'
|
||||
value={drug.overdose}
|
||||
/>
|
||||
<Button variant='outlined' color='secondary' type='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
</FormControl>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import * as React from 'react';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import Container from '@mui/material/Container';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Button from '@mui/material/Button';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import AdbIcon from '@mui/icons-material/Adb';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getWebSocket } from '../../redux/selectors';
|
||||
import { setWebSocket } from '../../redux/slices/websocketSlice';
|
||||
|
||||
function Header() {
|
||||
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const ws = useSelector(getWebSocket);
|
||||
const setWebSocketClick = () => {
|
||||
if (ws === null || ws.readyState === ws.CLOSED) {
|
||||
dispatch(setWebSocket(new WebSocket('ws://localhost:5176/ws')));
|
||||
console.log('WebSocket created');
|
||||
} else {
|
||||
ws.close();
|
||||
console.log('WebSocket closed');
|
||||
}
|
||||
};
|
||||
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElNav(event.currentTarget);
|
||||
};
|
||||
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseNavMenu = () => {
|
||||
setAnchorElNav(null);
|
||||
};
|
||||
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
const handleAddDrug = () => {
|
||||
navigate('/add');
|
||||
};
|
||||
|
||||
const handleLogoClick = () => {
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
function handleStatistics(): void {
|
||||
navigate('/stats');
|
||||
}
|
||||
|
||||
function handleAddManu(): void {
|
||||
navigate('/manufacturer/add');
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBar position='static'>
|
||||
<Container maxWidth='xl'>
|
||||
<Toolbar disableGutters>
|
||||
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
|
||||
<Typography
|
||||
variant='h6'
|
||||
noWrap
|
||||
component='a'
|
||||
onClick={handleLogoClick}
|
||||
sx={{
|
||||
mr: 2,
|
||||
display: { xs: 'none', md: 'flex' },
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '.3rem',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Silk Road
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||
<IconButton
|
||||
size='large'
|
||||
aria-label='account of current user'
|
||||
aria-controls='menu-appbar'
|
||||
aria-haspopup='true'
|
||||
onClick={handleOpenNavMenu}
|
||||
color='inherit'
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id='menu-appbar'
|
||||
anchorEl={anchorElNav}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
open={Boolean(anchorElNav)}
|
||||
onClose={handleCloseNavMenu}
|
||||
sx={{
|
||||
display: { xs: 'block', md: 'none' }
|
||||
}}
|
||||
>
|
||||
<MenuItem key='add-drug' onClick={handleAddDrug}>
|
||||
<Typography textAlign='center'>Add Drug</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem key='add-manu' onClick={handleAddManu}>
|
||||
<Typography textAlign='center'>Add Manufacturer</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem key='statistics' onClick={handleStatistics}>
|
||||
<Typography textAlign='center'>Statistics</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem key='create-ws' onClick={setWebSocketClick}>
|
||||
<Typography textAlign='center'>Start Generating</Typography>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
|
||||
<Typography
|
||||
variant='h5'
|
||||
noWrap
|
||||
component='a'
|
||||
onClick={handleLogoClick}
|
||||
sx={{
|
||||
mr: 2,
|
||||
display: { xs: 'flex', md: 'none' },
|
||||
flexGrow: 1,
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '.3rem',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none'
|
||||
}}
|
||||
>
|
||||
Silk Road
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
<Button
|
||||
key='add-drug'
|
||||
onClick={handleAddDrug}
|
||||
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||
>
|
||||
Add Drug
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
<Button
|
||||
key='add-manu'
|
||||
onClick={handleAddManu}
|
||||
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||
>
|
||||
Add Manufacturer
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
<Button
|
||||
key='statistics'
|
||||
onClick={handleStatistics}
|
||||
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||
>
|
||||
Statistics
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
<Button
|
||||
key='create-ws'
|
||||
onClick={setWebSocketClick}
|
||||
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||
>
|
||||
Start Generating
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title='Open settings'>
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar alt='Login' />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
sx={{ mt: '45px' }}
|
||||
id='menu-appbar'
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
<MenuItem key='add-drug' onClick={handleCloseUserMenu}>
|
||||
<Typography textAlign='center'>Login</Typography>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
export default Header;
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Button, FormLabel } from '@mui/material';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { ChangeEvent, FormEvent, useState } from 'react';
|
||||
import { getEmptyManufacturer } from '../../utils/DrugUtils';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import Manufacturer from '../../interfaces/Manufacturer';
|
||||
|
||||
export default function DrugFrom(props: { updateDrug?: Manufacturer }) {
|
||||
const [drug, setDrug] = useState(props.updateDrug ?? getEmptyManufacturer());
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setDrug({
|
||||
...drug,
|
||||
[e.target.name]: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
if (drug.id === undefined || drug.id === -1) {
|
||||
addDrug(drug);
|
||||
} else {
|
||||
updateDrug(drug);
|
||||
}
|
||||
};
|
||||
|
||||
const addDrug = async (drug: Manufacturer) => {
|
||||
try {
|
||||
drug.id = 0;
|
||||
axios.post(
|
||||
'https://silkroadapi.azurewebsites.net/Manufacturer/add-manufacturer',
|
||||
drug,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
navigate('/');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateDrug = async (drug: Manufacturer) => {
|
||||
try {
|
||||
axios.put(
|
||||
'https://silkroadapi.azurewebsites.net/Manufacturer/update-manufacturer',
|
||||
drug,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
navigate('/');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FormControl
|
||||
sx={{
|
||||
width: '70%',
|
||||
marginLeft: '15%',
|
||||
marginRight: '15%',
|
||||
marginBottom: '30px',
|
||||
marginTop: '30px'
|
||||
}}
|
||||
>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='name'
|
||||
title='Name'
|
||||
value={drug.name}
|
||||
/>
|
||||
<FormLabel>Address</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='address'
|
||||
title='Address'
|
||||
value={drug.address}
|
||||
/>
|
||||
<FormLabel>Contact</FormLabel>
|
||||
<TextField
|
||||
type='text'
|
||||
required
|
||||
onChange={handleChange}
|
||||
name='contact'
|
||||
title='Contact'
|
||||
value={drug.contact}
|
||||
/>
|
||||
<Button variant='outlined' color='secondary' type='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
</FormControl>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import * as React from 'react';
|
||||
import { BarChart } from '@mui/x-charts/BarChart';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getWebSocket } from '../../redux/selectors';
|
||||
import axios from 'axios';
|
||||
|
||||
const chartSetting = {
|
||||
xAxis: [
|
||||
{
|
||||
label: 'Drugs By Company'
|
||||
}
|
||||
],
|
||||
width: 500,
|
||||
height: 400
|
||||
};
|
||||
type data = { manufacturer: string; count: number };
|
||||
|
||||
// const dataset: data[] = [];
|
||||
// drugs.forEach((drug) => {
|
||||
// const manufacturer = drug.manufacturer;
|
||||
// const index = dataset.findIndex((data) => data.manufacturer === manufacturer);
|
||||
// if (index === -1) {
|
||||
// dataset.push({ manufacturer, count: 1 });
|
||||
// } else {
|
||||
// dataset[index].count++;
|
||||
// }
|
||||
// });
|
||||
|
||||
const valueFormatter = (value: number) => `${value} #`;
|
||||
|
||||
export default function DrugBarChart() {
|
||||
const ws = useSelector(getWebSocket);
|
||||
const [data, setData] = React.useState<data[]>([]);
|
||||
const [newitems, setNewItems] = React.useState<number>(1);
|
||||
|
||||
const fetchData = async () => {
|
||||
const data = await axios.get(
|
||||
'https://silkroadapi.azurewebsites.net/Drug/get-chart-data',
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setData(data.data);
|
||||
setNewItems(0);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
ws?.addEventListener('message', (event) => {
|
||||
console.log(event.data);
|
||||
setNewItems(1);
|
||||
fetchData();
|
||||
});
|
||||
}, [ws]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (newitems === 0) return;
|
||||
fetchData();
|
||||
console.log(data);
|
||||
}, [data, newitems]);
|
||||
|
||||
// const groupBy = () => {
|
||||
// const dataset: data[] = [];
|
||||
// const arrayDrug = [...drugs];
|
||||
// arrayDrug.forEach((drug: Drug) => {
|
||||
// const manufacturer = drug.manufacturer;
|
||||
// const index = dataset.findIndex(
|
||||
// (data) => data.manufacturer === manufacturer
|
||||
// );
|
||||
// if (index === -1) {
|
||||
// dataset.push({ manufacturer, count: 1 });
|
||||
// } else {
|
||||
// dataset[index].count++;
|
||||
// }
|
||||
// });
|
||||
// return dataset;
|
||||
// };
|
||||
|
||||
// const [dataset] = React.useState<data[]>(groupBy());
|
||||
console.log(data);
|
||||
return (
|
||||
data.length > 0 && (
|
||||
<BarChart
|
||||
dataset={data}
|
||||
yAxis={[{ scaleType: 'band', dataKey: 'manufacturer' }]}
|
||||
series={[{ dataKey: 'count', label: 'Total Drugs', valueFormatter }]}
|
||||
layout='horizontal'
|
||||
{...chartSetting}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
import Drug from "../interfaces/Drug";
|
||||
|
||||
const drugs: Drug[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Paracetamol',
|
||||
price: 5,
|
||||
stock: 100,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Paracetamol is a painkiller and can be used to relieve mild to moderate pain. It can also reduce high temperatures (fever).',
|
||||
sideEffects: 'Paracetamol is a very safe medicine when used correctly. Side effects are rare if you take it at the right dosage.',
|
||||
interactions: 'Paracetamol can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose of paracetamol is one or two 500mg tablets at a time. Do not take more than 4 doses in 24 hours.',
|
||||
contraindications: 'Do not take paracetamol if you are allergic to it.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep paracetamol in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Paracetamol is safe to take in pregnancy and while breastfeeding, at recommended doses.',
|
||||
breastfeeding: 'Paracetamol is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of paracetamol can cause serious harm. The maximum amount of paracetamol for adults is 1 gram (1000 mg) per dose and 4 grams (4000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Ibuprofen',
|
||||
price: 10,
|
||||
stock: 50,
|
||||
manufacturer: 'Pfizer',
|
||||
description: 'Ibuprofen is a painkiller available over the counter without a prescription. It\'s one of a group of painkillers called non-steroidal anti-inflammatory drugs (NSAIDs) and can be used to ease mild to moderate pain, such as period pain, toothache, and migraines.',
|
||||
sideEffects: 'The most common side effects of ibuprofen are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Ibuprofen can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 200mg tablets 3 times a day. Do not take more than 6 tablets (1200mg) in 24 hours.',
|
||||
contraindications: 'Do not take ibuprofen if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep ibuprofen in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Ibuprofen is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Ibuprofen is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of ibuprofen can cause serious harm. The maximum amount of ibuprofen for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Aspirin',
|
||||
price: 15,
|
||||
stock: 25,
|
||||
manufacturer: 'Bayer',
|
||||
description: 'Aspirin is a painkiller and anti-inflammatory medicine. It can be used to relieve pain and inflammation caused by many conditions such as headaches, toothache, period pain, and joint pain.',
|
||||
sideEffects: 'The most common side effects of aspirin are indigestion and stomach irritation.',
|
||||
interactions: 'Aspirin can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 or 4 times a day. Do not take more than 12 tablets (3600mg) in 24 hours.',
|
||||
contraindications: 'Do not take aspirin if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep aspirin in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Aspirin is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Aspirin is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of aspirin can cause serious harm. The maximum amount of aspirin for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Codeine',
|
||||
price: 20,
|
||||
stock: 10,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Codeine is a strong painkiller and is part of a group of medicines called opioids. Opioids are derived from the poppy plant, and are known for their pain-relieving properties.',
|
||||
sideEffects: 'The most common side effects of codeine are constipation, feeling sick, and drowsiness.',
|
||||
interactions: 'Codeine can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 30mg tablets 3 or 4 times a day. Do not take more than 12 tablets (360mg) in 24 hours.',
|
||||
contraindications: 'Do not take codeine if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep codeine in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Codeine is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Codeine is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of codeine can cause serious harm. The maximum amount of codeine for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Morphine',
|
||||
price: 25,
|
||||
stock: 5,
|
||||
manufacturer: 'Pfizer',
|
||||
description: 'Morphine is a strong painkiller and is part of a group of medicines called opioids. Opioids are derived from the poppy plant, and are known for their pain-relieving properties.',
|
||||
sideEffects: 'The most common side effects of morphine are constipation, feeling sick, and drowsiness.',
|
||||
interactions: 'Morphine can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 10mg tablets 3 or 4 times a day. Do not take more than 12 tablets (120mg) in 24 hours.',
|
||||
contraindications: 'Do not take morphine if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep morphine in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Morphine is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Morphine is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of morphine can cause serious harm. The maximum amount of morphine for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Tramadol',
|
||||
price: 30,
|
||||
stock: 2,
|
||||
manufacturer: 'Bayer',
|
||||
description: 'Tramadol is a strong painkiller and is part of a group of medicines called opioids. Opioids are derived from the poppy plant, and are known for their pain-relieving properties.',
|
||||
sideEffects: 'The most common side effects of tramadol are constipation, feeling sick, and drowsiness.',
|
||||
interactions: 'Tramadol can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 50mg tablets 3 or 4 times a day. Do not take more than 12 tablets (600mg) in 24 hours.',
|
||||
contraindications: 'Do not take tramadol if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep tramadol in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Tramadol is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Tramadol is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of tramadol can cause serious harm. The maximum amount of tramadol for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Cocodamol',
|
||||
price: 35,
|
||||
stock: 1,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Cocodamol is a strong painkiller and is part of a group of medicines called opioids. Opioids are derived from the poppy plant, and are known for their pain-relieving properties.',
|
||||
sideEffects: 'The most common side effects of cocodamol are constipation, feeling sick, and drowsiness.',
|
||||
interactions: 'Cocodamol can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 30mg tablets 3 or 4 times a day. Do not take more than 12 tablets (360mg) in 24 hours.',
|
||||
contraindications: 'Do not take cocodamol if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep cocodamol in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Cocodamol is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Cocodamol is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of cocodamol can cause serious harm. The maximum amount of cocodamol for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Diclofenac',
|
||||
price: 40,
|
||||
stock: 1,
|
||||
manufacturer: 'Pfizer',
|
||||
description: 'Diclofenac is a painkiller available over the counter without a prescription. It\'s one of a group of painkillers called non-steroidal anti-inflammatory drugs (NSAIDs) and can be used to ease mild to moderate pain, such as period pain, toothache, and migraines.',
|
||||
sideEffects: 'The most common side effects of diclofenac are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Diclofenac can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 50mg tablets 3 times a day. Do not take more than 6 tablets (300mg) in 24 hours.',
|
||||
contraindications: 'Do not take diclofenac if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep diclofenac in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Diclofenac is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Diclofenac is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of diclofenac can cause serious harm. The maximum amount of diclofenac for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'Naproxen',
|
||||
price: 45,
|
||||
stock: 1,
|
||||
manufacturer: 'Bayer',
|
||||
description: 'Naproxen is a painkiller available over the counter without a prescription. It\'s one of a group of painkillers called non-steroidal anti-inflammatory drugs (NSAIDs) and can be used to ease mild to moderate pain, such as period pain, toothache, and migraines.',
|
||||
sideEffects: 'The most common side effects of naproxen are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Naproxen can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 250mg tablets 3 times a day. Do not take more than 6 tablets (1500mg) in 24 hours.',
|
||||
contraindications: 'Do not take naproxen if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep naproxen in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Naproxen is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Naproxen is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of naproxen can cause serious harm. The maximum amount of naproxen for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Mefenamic Acid',
|
||||
price: 50,
|
||||
stock: 1,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Mefenamic Acid is a painkiller available over the counter without a prescription. It\'s one of a group of painkillers called non-steroidal anti-inflammatory drugs (NSAIDs) and can be used to ease mild to moderate pain, such as period pain, toothache, and migraines.',
|
||||
sideEffects: 'The most common side effects of mefenamic acid are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Mefenamic acid can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 250mg tablets 3 times a day. Do not take more than 6 tablets (1500mg) in 24 hours.',
|
||||
contraindications: 'Do not take mefenamic acid if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep mefenamic acid in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Mefenamic acid is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Mefenamic acid is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of mefenamic acid can cause serious harm. The maximum amount of mefenamic acid for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Gabapentin',
|
||||
price: 55,
|
||||
stock: 1,
|
||||
manufacturer: 'Pfizer',
|
||||
description: 'Gabapentin is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of gabapentin are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Gabapentin can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take gabapentin if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep gabapentin in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Gabapentin is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Gabapentin is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of gabapentin can cause serious harm. The maximum amount of gabapentin for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'Pregabalin',
|
||||
price: 60,
|
||||
stock: 1,
|
||||
manufacturer: 'Bayer',
|
||||
description: 'Pregabalin is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of pregabalin are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Pregabalin can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take pregabalin if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep pregabalin in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Pregabalin is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Pregabalin is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of pregabalin can cause serious harm. The maximum amount of pregabalin for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'Amitriptyline',
|
||||
price: 65,
|
||||
stock: 1,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Amitriptyline is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of amitriptyline are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Amitriptyline can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take amitriptyline if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep amitriptyline in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Amitriptyline is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Amitriptyline is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of amitriptyline can cause serious harm. The maximum amount of amitriptyline for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: 'Duloxetine',
|
||||
price: 70,
|
||||
stock: 1,
|
||||
manufacturer: 'Pfizer',
|
||||
description: 'Duloxetine is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of duloxetine are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Duloxetine can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take duloxetine if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep duloxetine in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Duloxetine is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Duloxetine is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of duloxetine can cause serious harm. The maximum amount of duloxetine for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: 'Venlafaxine',
|
||||
price: 75,
|
||||
stock: 1,
|
||||
manufacturer: 'Bayer',
|
||||
description: 'Venlafaxine is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of venlafaxine are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Venlafaxine can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take venlafaxine if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep venlafaxine in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Venlafaxine is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Venlafaxine is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of venlafaxine can cause serious harm. The maximum amount of venlafaxine for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: 'Sertraline',
|
||||
price: 80,
|
||||
stock: 1,
|
||||
manufacturer: 'GSK',
|
||||
description: 'Sertraline is a medicine used to treat nerve pain, such as epilepsy, and can also be used to treat anxiety.',
|
||||
sideEffects: 'The most common side effects of sertraline are headaches, dizziness, and drowsiness.',
|
||||
interactions: 'Sertraline can be taken with other medicines, including prescription medicines such as antibiotics.',
|
||||
dosage: 'The usual dose for adults is one or two 300mg tablets 3 times a day. Do not take more than 6 tablets (1800mg) in 24 hours.',
|
||||
contraindications: 'Do not take sertraline if you have a stomach ulcer, or have had one in the past.',
|
||||
warnings: 'Do not take more than the recommended dose.',
|
||||
storage: 'Keep sertraline in a cool, dry place. Do not store it above 25C.',
|
||||
pregnancy: 'Sertraline is not recommended in pregnancy, especially in the third trimester.',
|
||||
breastfeeding: 'Sertraline is safe to take while breastfeeding, at recommended doses.',
|
||||
overdose: 'An overdose of sertraline can cause serious harm. The maximum amount of sertraline for adults is 1 gram (1000 mg) per dose and 3 grams (3000 mg) per day.'
|
||||
},
|
||||
];
|
||||
|
||||
export default drugs;
|
||||
@@ -0,0 +1,55 @@
|
||||
import Add from '../views/Add';
|
||||
import Update from '../views/Update';
|
||||
import Details from '../views/Details';
|
||||
import Search from '../views/Search';
|
||||
import Error from '../views/Error';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import Statistics from '../views/Statistics';
|
||||
import DetailsManu from '../views/DetailsManu';
|
||||
import AddManu from '../views/AddManu';
|
||||
import UpdateManu from '../views/UpdateManu';
|
||||
|
||||
const routes = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <Search />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/details/:id',
|
||||
element: <Details />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/add',
|
||||
element: <Add />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/update/:id',
|
||||
element: <Update />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: 'stats',
|
||||
element: <Statistics />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/manufacturer/details/:id',
|
||||
element: <DetailsManu />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/manufacturer/add',
|
||||
element: <AddManu />,
|
||||
errorElement: <Error />
|
||||
},
|
||||
{
|
||||
path: '/manufacturer/update/:id',
|
||||
element: <UpdateManu />,
|
||||
errorElement: <Error />
|
||||
}
|
||||
]);
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,20 @@
|
||||
type Drug = {
|
||||
id: number;
|
||||
name: string;
|
||||
price: number;
|
||||
stock: number;
|
||||
manufacturer: string;
|
||||
description: string;
|
||||
sideEffects: string;
|
||||
interactions: string;
|
||||
dosage: string;
|
||||
contraindications: string;
|
||||
warnings: string;
|
||||
storage: string;
|
||||
pregnancy: string;
|
||||
breastfeeding: string;
|
||||
overdose: string;
|
||||
|
||||
};
|
||||
|
||||
export default Drug;
|
||||
@@ -0,0 +1,9 @@
|
||||
type Manufacturer = {
|
||||
id: number | null;
|
||||
name: string;
|
||||
address: string;
|
||||
contact: string;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export default Manufacturer;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import GlobalStyle from './styled/global.ts';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './redux/store.ts';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<GlobalStyle />
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { RootState } from "./store";
|
||||
|
||||
export const getDrugs = (state: RootState) => state.drugs.drugs;
|
||||
export const getDrugById = (state: RootState, id: number) =>
|
||||
state.drugs.drugs.find((drug) => drug.id === id);
|
||||
export const getWebSocket = (state: RootState) => state.websocket.ws;
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||
import Drug from '../../interfaces/Drug'
|
||||
|
||||
export interface DrugState {
|
||||
drugs: Drug[]
|
||||
}
|
||||
|
||||
const initialState: DrugState = {
|
||||
drugs: [],
|
||||
}
|
||||
|
||||
export const drugsSlice = createSlice({
|
||||
name: 'drugs',
|
||||
initialState,
|
||||
reducers: {
|
||||
loadDrugs: (state, action: PayloadAction<Drug[]>) => {
|
||||
state.drugs = action.payload
|
||||
},
|
||||
addDrug: (state, action: PayloadAction<Drug>) => {
|
||||
state.drugs.push(action.payload)
|
||||
},
|
||||
removeDrug: (state, action: PayloadAction<number>) => {
|
||||
state.drugs = state.drugs.filter((drug) => drug.id !== action.payload)
|
||||
},
|
||||
updateDrug: (state, action: PayloadAction<Drug>) => {
|
||||
const index = state.drugs.findIndex((drug) => drug.id === action.payload.id)
|
||||
if (index !== -1) {
|
||||
state.drugs[index] = action.payload
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { loadDrugs, addDrug, removeDrug, updateDrug } = drugsSlice.actions
|
||||
|
||||
export default drugsSlice.reducer
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
export interface WebSocketState {
|
||||
ws: WebSocket | null
|
||||
}
|
||||
|
||||
const initialState: WebSocketState = {
|
||||
ws: null,
|
||||
}
|
||||
|
||||
export const websocketSlice = createSlice({
|
||||
name: 'websocket',
|
||||
initialState,
|
||||
reducers: {
|
||||
setWebSocket: (state, action: PayloadAction<WebSocket>) => {
|
||||
state.ws = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { setWebSocket } = websocketSlice.actions
|
||||
|
||||
export default websocketSlice.reducer
|
||||
@@ -0,0 +1,18 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import drugSlice from './slices/drugSlice'
|
||||
import websocketSlice from './slices/websocketSlice'
|
||||
|
||||
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
drugs: drugSlice,
|
||||
websocket: websocketSlice
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
})
|
||||
})
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
html {
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default GlobalStyle;
|
||||
@@ -0,0 +1,34 @@
|
||||
import Drug from "../interfaces/Drug";
|
||||
import Manufacturer from "../interfaces/Manufacturer";
|
||||
|
||||
export const getEmptyDrug = (): Drug => {
|
||||
return {
|
||||
id: -1,
|
||||
name: '',
|
||||
stock: 0,
|
||||
price: 0,
|
||||
manufacturer: '',
|
||||
description: '',
|
||||
sideEffects: '',
|
||||
interactions: '',
|
||||
dosage: '',
|
||||
contraindications: '',
|
||||
warnings: '',
|
||||
storage: '',
|
||||
pregnancy: '',
|
||||
breastfeeding: '',
|
||||
overdose: '',
|
||||
} as Drug;
|
||||
}
|
||||
|
||||
export const getEmptyManufacturer = (): Manufacturer => {
|
||||
return {
|
||||
id: -1,
|
||||
name: '',
|
||||
address: '',
|
||||
contact: '',
|
||||
totalCount: 0,
|
||||
} as Manufacturer;
|
||||
}
|
||||
|
||||
export default getEmptyDrug;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DrugForm from '../components/Shared/DrugForm';
|
||||
import Header from '../components/Shared/Header';
|
||||
|
||||
const Add = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<DrugForm />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Add;
|
||||
@@ -0,0 +1,13 @@
|
||||
import ManuForm from '../components/Shared/ManuForm';
|
||||
import Header from '../components/Shared/Header';
|
||||
|
||||
const AddManu = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<ManuForm />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddManu;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DrugDetails from '../components/Details/DrugDetails';
|
||||
import Header from '../components/Shared/Header';
|
||||
|
||||
const Details = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<DrugDetails />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
||||
@@ -0,0 +1,13 @@
|
||||
import ManuDetails from '../components/Details/ManuDetails';
|
||||
import Header from '../components/Shared/Header';
|
||||
|
||||
const DetailsManu = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<ManuDetails />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailsManu;
|
||||
@@ -0,0 +1,9 @@
|
||||
const Error = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Error</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Error;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Divider } from '@mui/material';
|
||||
import DrugTable from '../components/Search/DrugTable';
|
||||
import Header from '../components/Shared/Header';
|
||||
import ManufacturerTable from '../components/Search/ManufacturerTable';
|
||||
|
||||
const Search = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<DrugTable />
|
||||
<Divider sx={{ padding: 10 }} />
|
||||
<ManufacturerTable />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
@@ -0,0 +1,13 @@
|
||||
import Header from '../components/Shared/Header';
|
||||
import DrugBarChart from '../components/Statistics/DrugBarChart';
|
||||
|
||||
const Statistics = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<DrugBarChart />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Statistics;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import DrugForm from '../components/Shared/DrugForm';
|
||||
import Header from '../components/Shared/Header';
|
||||
import axios from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Drug from '../interfaces/Drug';
|
||||
import { getDrugById } from '../redux/selectors';
|
||||
import { RootState } from '../redux/store';
|
||||
|
||||
const Update = () => {
|
||||
const id = parseInt(useParams().id ?? '-1');
|
||||
const [drug, setDrug] = useState<Drug | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const potentialDrug = useSelector((state: RootState) =>
|
||||
getDrugById(state, id)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDrugById = async (id: number) => {
|
||||
try {
|
||||
const drug = await axios.get<Drug>(
|
||||
`https://silkroadapi.azurewebsites.net/Drug/get-drug/${id}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setDrug(drug.data);
|
||||
console.log(drug);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
navigate('/error');
|
||||
}
|
||||
};
|
||||
if (potentialDrug) setDrug(potentialDrug);
|
||||
else fetchDrugById(id);
|
||||
}, [id, navigate, potentialDrug]);
|
||||
return (
|
||||
drug && (
|
||||
<>
|
||||
<Header />
|
||||
<DrugForm updateDrug={drug} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Update;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import ManuForm from '../components/Shared/ManuForm';
|
||||
import Header from '../components/Shared/Header';
|
||||
import axios from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Manufacturer from '../interfaces/Manufacturer';
|
||||
|
||||
const UpdateManu = () => {
|
||||
const id = parseInt(useParams().id ?? '-1');
|
||||
const [drug, setDrug] = useState<Manufacturer | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDrugById = async (id: number) => {
|
||||
try {
|
||||
const drugcox = await axios.get<Manufacturer>(
|
||||
`https://silkroadapi.azurewebsites.net/Manufacturer/get-manufacturer/${id}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setDrug(drugcox.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
navigate('/error');
|
||||
}
|
||||
};
|
||||
fetchDrugById(id);
|
||||
}, [id, navigate]);
|
||||
return (
|
||||
drug && (
|
||||
<>
|
||||
<Header />
|
||||
<ManuForm updateDrug={drug} />
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateManu;
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import DrugDetails from '../src/components/Details/DrugDetails';
|
||||
import React from 'react';
|
||||
import routes from '../src/constants/routes';
|
||||
import drugs from '../src/constants/mock_data';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from '../src/redux/store';
|
||||
|
||||
const mockedUseNavigate = vi.fn(() => {
|
||||
return (path: string) => {
|
||||
return routes[path];
|
||||
};
|
||||
});
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-router-dom')>(
|
||||
'react-router-dom'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useNavigate: () => mockedUseNavigate,
|
||||
useParams: () => ({ id: '1' })
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('react-redux', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-redux')>(
|
||||
'react-redux'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useSelector: () => drugs[0]
|
||||
};
|
||||
});
|
||||
|
||||
test('DrugDetails renders correctly', () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<DrugDetails />
|
||||
</Provider>
|
||||
);
|
||||
expect(screen.getByText('Paracetamol')).toBeTruthy();
|
||||
expect(screen.getByText('$5')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Paracetamol is a painkiller and can be used to relieve mild to moderate pain. It can also reduce high temperatures (fever).'
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('DrugDetails back button works correctly', () => {
|
||||
render(<DrugDetails />);
|
||||
fireEvent.click(screen.getByText('Back'));
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith(-1);
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import drugs from '../src/constants/mock_data';
|
||||
import React from 'react';
|
||||
import routes from '../src/constants/routes';
|
||||
import DrugFrom from '../src/components/Shared/DrugForm';
|
||||
import axios from 'axios';
|
||||
import Drug from '../src/interfaces/Drug';
|
||||
|
||||
const mockedUseNavigate = vi.fn(() => {
|
||||
return (path: string) => {
|
||||
return routes[path];
|
||||
};
|
||||
});
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-router-dom')>(
|
||||
'react-router-dom'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useNavigate: () => mockedUseNavigate
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('react-redux', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-redux')>(
|
||||
'react-redux'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useDispatch: () => vi.fn()
|
||||
};
|
||||
});
|
||||
|
||||
test('DrugForm renders correctly - Empty', () => {
|
||||
render(<DrugFrom />);
|
||||
expect(screen.getByTitle('Name')).toBeTruthy();
|
||||
expect(screen.getByTitle('Stock')).toBeTruthy();
|
||||
expect(screen.getByTitle('Price')).toBeTruthy();
|
||||
|
||||
expect(
|
||||
screen.getByTitle('Name').getElementsByTagName('input')[0].value
|
||||
).not.toBeTruthy();
|
||||
expect(screen.getByTitle('Stock').getElementsByTagName('input')[0].value).eq(
|
||||
'0'
|
||||
);
|
||||
expect(screen.getByTitle('Price').getElementsByTagName('input')[0].value).eq(
|
||||
'0'
|
||||
);
|
||||
});
|
||||
|
||||
test('DrugForm renders correctly - Update', () => {
|
||||
render(<DrugFrom updateDrug={drugs[0]} />);
|
||||
expect(screen.getByTitle('Name')).toBeTruthy();
|
||||
expect(screen.getByTitle('Stock')).toBeTruthy();
|
||||
expect(screen.getByTitle('Price')).toBeTruthy();
|
||||
|
||||
expect(screen.getByTitle('Name').getElementsByTagName('input')[0].value).eq(
|
||||
drugs[0].name
|
||||
);
|
||||
expect(screen.getByTitle('Stock').getElementsByTagName('input')[0].value).eq(
|
||||
drugs[0].stock.toString()
|
||||
);
|
||||
expect(screen.getByTitle('Price').getElementsByTagName('input')[0].value).eq(
|
||||
drugs[0].price.toString()
|
||||
);
|
||||
});
|
||||
|
||||
test('DrugForm renders correctly - Create', () => {
|
||||
render(<DrugFrom />);
|
||||
expect(drugs.length).eq(16);
|
||||
fireEvent.change(screen.getByTitle('Name').getElementsByTagName('input')[0], {
|
||||
target: { value: 'Test' }
|
||||
});
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Stock').getElementsByTagName('input')[0],
|
||||
{
|
||||
target: { value: '10' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Price').getElementsByTagName('input')[0],
|
||||
{
|
||||
target: { value: '100' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Manufacturer').getElementsByTagName('input')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Description').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Side Effects').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Interactions').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Dosage').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Contraindications').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Warnings').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Storage').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Pregnancy').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Breastfeeding').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
fireEvent.change(
|
||||
screen.getByTitle('Overdose').getElementsByTagName('textarea')[0],
|
||||
{
|
||||
target: { value: 'Test' }
|
||||
}
|
||||
);
|
||||
vi.spyOn(axios, 'post').mockImplementation((url: string, data?: unknown) => {
|
||||
drugs.push(data as Drug);
|
||||
return Promise.resolve({ data: data });
|
||||
});
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
expect(drugs.length).eq(17);
|
||||
expect(drugs[16].name).eq('Test');
|
||||
});
|
||||
|
||||
test('DrugForm renders correctly - Update', () => {
|
||||
render(<DrugFrom updateDrug={drugs[0]} />);
|
||||
expect(screen.getByTitle('Name')).toBeTruthy();
|
||||
expect(screen.getByTitle('Stock')).toBeTruthy();
|
||||
expect(screen.getByTitle('Price')).toBeTruthy();
|
||||
|
||||
expect(screen.getByTitle('Name').getElementsByTagName('input')[0].value).eq(
|
||||
drugs[0].name
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByTitle('Name').getElementsByTagName('input')[0], {
|
||||
target: { value: 'cox' }
|
||||
});
|
||||
vi.spyOn(axios, 'put').mockImplementation((url: string, data?: unknown) => {
|
||||
const index = drugs.findIndex((d) => d.id === (data as Drug).id);
|
||||
drugs[index] = data as Drug;
|
||||
return Promise.resolve({ data: data });
|
||||
});
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
expect(drugs[0].name).eq('cox');
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import DrugTable from '../src/components/Search/DrugTable';
|
||||
import drugs from '../src/constants/mock_data';
|
||||
import React from 'react';
|
||||
import routes from '../src/constants/routes';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from '../src/redux/store';
|
||||
import axios from 'axios';
|
||||
|
||||
const mockedUseNavigate = vi.fn(() => {
|
||||
return (path: string) => {
|
||||
return routes[path];
|
||||
};
|
||||
});
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-router-dom')>(
|
||||
'react-router-dom'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useNavigate: () => mockedUseNavigate
|
||||
};
|
||||
});
|
||||
|
||||
test('DrugTable renders correctly', () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<DrugTable />
|
||||
</Provider>
|
||||
);
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { Page: 1, PageSize: 10, Total: 16, Items: drugs }
|
||||
});
|
||||
expect(screen.getByText('Name')).toBeTruthy();
|
||||
expect(screen.getByText('Price (USD)')).toBeTruthy();
|
||||
expect(screen.getByText('Manufacturer')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('DrugTable renders correct number of rows', async () => {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { Page: 1, PageSize: 10, total: 16, items: drugs.slice(0, 10) }
|
||||
});
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<DrugTable />
|
||||
</Provider>
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
// Header row + 10 data rows + 1 row for pagination
|
||||
expect(screen.getAllByRole('row')).toHaveLength(12);
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { Page: 2, PageSize: 10, total: 16, items: drugs.slice(9, 16) }
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText('next page'));
|
||||
// Header row + 6 data rows + 1 empty row to keep style + 1 row for pagination
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
expect(screen.getAllByRole('row')).toHaveLength(9);
|
||||
});
|
||||
|
||||
test('DrugTable buttons work correctly', async () => {
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
data: { Page: 1, PageSize: 10, total: 16, items: drugs.slice(0, 10) }
|
||||
});
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<DrugTable />
|
||||
</Provider>
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
fireEvent.click(screen.getAllByText('View').pop()!);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/details/10');
|
||||
fireEvent.click(screen.getAllByText('Update').pop()!);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/update/10');
|
||||
fireEvent.click(screen.getAllByText('Delete').pop()!);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/update/10');
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import routes from '../src/constants/routes';
|
||||
import Header from '../src/components/Shared/Header';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from '../src/redux/store';
|
||||
|
||||
const mockedUseNavigate = vi.fn(() => {
|
||||
return (path: string) => {
|
||||
return routes[path];
|
||||
};
|
||||
});
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const mod = await vi.importActual<typeof import('react-router-dom')>(
|
||||
'react-router-dom'
|
||||
);
|
||||
return {
|
||||
...mod,
|
||||
useNavigate: () => mockedUseNavigate
|
||||
};
|
||||
});
|
||||
|
||||
test('Header - test links', () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<Header />
|
||||
</Provider>
|
||||
);
|
||||
const homeLink = screen.getAllByText('Silk Road');
|
||||
const firstLink = homeLink[0];
|
||||
expect(firstLink).toBeTruthy();
|
||||
fireEvent.click(firstLink);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/');
|
||||
const secondLink = homeLink[1];
|
||||
expect(secondLink).toBeTruthy();
|
||||
fireEvent.click(secondLink);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/');
|
||||
const addLink = screen.getAllByText('Add Drug');
|
||||
const add = addLink[0];
|
||||
expect(add).toBeTruthy();
|
||||
fireEvent.click(add);
|
||||
expect(mockedUseNavigate).toHaveBeenCalledWith('/add');
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user