mirror of
https://github.com/lemeow125/EquipmentTracker-Frontend.git
synced 2024-11-17 06:09:25 +08:00
commit
70fb033658
35 changed files with 5235 additions and 114 deletions
|
@ -1,10 +1,10 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>CITC Equipment Tracker</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
1173
package-lock.json
generated
1173
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
@ -10,8 +10,21 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@mui/icons-material": "^5.14.16",
|
||||||
|
"@mui/material": "^5.14.17",
|
||||||
|
"@mui/styled-engine": "^5.14.17",
|
||||||
|
"@mui/styled-engine-sc": "^6.0.0-alpha.5",
|
||||||
|
"@reduxjs/toolkit": "^1.9.7",
|
||||||
|
"@tanstack/react-query": "^5.8.3",
|
||||||
|
"axios": "^1.6.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-redux": "^8.1.3",
|
||||||
|
"react-router-dom": "^6.18.0",
|
||||||
|
"react-toastify": "^9.1.3",
|
||||||
|
"reactjs-popup": "^2.0.6",
|
||||||
|
"styled-components": "^6.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
|
|
36
src/App.css
36
src/App.css
|
@ -4,39 +4,3 @@
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
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;
|
|
||||||
}
|
|
||||||
|
|
145
src/App.tsx
145
src/App.tsx
|
@ -1,35 +1,118 @@
|
||||||
import { useState } from 'react'
|
import LandingPage from "./Pages/LandingPage/LandingPage";
|
||||||
import reactLogo from './assets/react.svg'
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||||
import viteLogo from '/vite.svg'
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import './App.css'
|
import { Provider } from "react-redux";
|
||||||
|
import "./App.css";
|
||||||
|
import store from "./Components/Plugins/Redux/Store/Store";
|
||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import ErrorPage from "./Pages/ErrorPage/ErrorPage";
|
||||||
|
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
||||||
|
import Revalidator from "./Components/Revalidator/Revalidator";
|
||||||
|
import ActivationPage from "./Pages/ActivationPage/ActivationPage";
|
||||||
|
import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage";
|
||||||
|
import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage";
|
||||||
|
import EquipmentListPage from "./Pages/EquipmentListPage/EquipmentListPage";
|
||||||
|
import EquipmentLogsPage from "./Pages/EquipmentLogsPage/EquipmentLogsPage";
|
||||||
|
import EquipmentInstanceLogsPage from "./Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage";
|
||||||
|
|
||||||
function App() {
|
const queryClient = new QueryClient();
|
||||||
const [count, setCount] = useState(0)
|
const router = createHashRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<LandingPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/dashboard",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<DashboardPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/view/equipment_instances",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<EquipmentInstancesListPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/view/equipment_instances/logs",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<EquipmentInstanceLogsPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/view/equipments",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<EquipmentListPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/view/equipments/logs",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<Revalidator />
|
||||||
|
<EquipmentLogsPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/activation/:uid/:token",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<ActivationPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/reset_password_confirm/:uid/:token",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<ResetPasswordPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<Provider store={store}>
|
||||||
<div>
|
<QueryClientProvider client={queryClient}>
|
||||||
<a href="https://vitejs.dev" target="_blank">
|
<RouterProvider router={router} />
|
||||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
</QueryClientProvider>
|
||||||
</a>
|
<ToastContainer
|
||||||
<a href="https://react.dev" target="_blank">
|
position={"top-right"}
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
autoClose={1500}
|
||||||
</a>
|
closeOnClick
|
||||||
</div>
|
pauseOnHover
|
||||||
<h1>Vite + React</h1>
|
draggable
|
||||||
<div className="card">
|
theme={"light"}
|
||||||
<button onClick={() => setCount((count) => count + 1)}>
|
limit={3}
|
||||||
count is {count}
|
/>
|
||||||
</button>
|
</Provider>
|
||||||
<p>
|
);
|
||||||
Edit <code>src/App.tsx</code> and save to test HMR
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p className="read-the-docs">
|
|
||||||
Click on the Vite and React logos to learn more
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
|
||||||
|
|
342
src/Components/API/API.tsx
Normal file
342
src/Components/API/API.tsx
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
|
import axios from "axios";
|
||||||
|
import {
|
||||||
|
ActivationType,
|
||||||
|
EquipmentListType,
|
||||||
|
LoginType,
|
||||||
|
RegisterType,
|
||||||
|
ResetPasswordConfirmType,
|
||||||
|
EquipmentInstanceListType,
|
||||||
|
EquipmentType,
|
||||||
|
AddEquipmentType,
|
||||||
|
AddEquipmentInstanceType,
|
||||||
|
EquipmentInstanceType,
|
||||||
|
PatchEquipmentInstanceType,
|
||||||
|
PatchEquipmentType,
|
||||||
|
EquipmentLogListType,
|
||||||
|
EquipmentInstanceLogListType,
|
||||||
|
} from "../Types/Types";
|
||||||
|
|
||||||
|
const debug = false;
|
||||||
|
let backendURL;
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
backendURL = "http://localhost:8000/";
|
||||||
|
} else {
|
||||||
|
backendURL = "https://equipment-tracker-backend.keannu1.duckdns.org/";
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: backendURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Token Handling
|
||||||
|
export async function getAccessToken() {
|
||||||
|
const accessToken = await localStorage.getItem("access_token");
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRefreshToken() {
|
||||||
|
const refreshToken = await localStorage.getItem("refresh_token");
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAccessToken(access: string) {
|
||||||
|
await localStorage.setItem("access_token", access);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setRefreshToken(refresh: string) {
|
||||||
|
await localStorage.setItem("refresh_token", refresh);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header Config Template for REST
|
||||||
|
export async function GetConfig() {
|
||||||
|
const accessToken = await getAccessToken();
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ParseError(error: { response: { data: string } }) {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
if (error.response.data.length > 50) {
|
||||||
|
return "Error truncated (too long)";
|
||||||
|
}
|
||||||
|
return JSON.stringify(error.response.data)
|
||||||
|
.replace(/[{}]/g, " ")
|
||||||
|
.replace(/\(/g, " ")
|
||||||
|
.replace(/\)/g, " ")
|
||||||
|
.replace(/"/g, " ")
|
||||||
|
.replace(/,/g, ",")
|
||||||
|
.replace(/\[/g, "")
|
||||||
|
.replace(/\]/g, "")
|
||||||
|
.replace(/\./g, "")
|
||||||
|
.replace(/non_field_errors/g, "")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
return "Unable to reach server";
|
||||||
|
}
|
||||||
|
// User APIs
|
||||||
|
|
||||||
|
export function RegisterAPI(info: RegisterType) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/", info)
|
||||||
|
.then(async (response) => {
|
||||||
|
console.log(response.data);
|
||||||
|
return [true, 0];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Registration failed");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoginAPI(user: LoginType, remember_session: boolean) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/jwt/create/", user)
|
||||||
|
.then(async (response) => {
|
||||||
|
console.log(response.data);
|
||||||
|
setAccessToken(response.data.access);
|
||||||
|
if (remember_session) {
|
||||||
|
setRefreshToken(response.data.refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Login Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Login Failed", error.response.data);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function JWTRefreshAPI() {
|
||||||
|
const refresh = await getRefreshToken();
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/jwt/refresh/", {
|
||||||
|
refresh: refresh,
|
||||||
|
})
|
||||||
|
.then(async (response) => {
|
||||||
|
setAccessToken(response.data.access);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error refreshing token");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function UserAPI() {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get("api/v1/accounts/users/me/", config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving user data");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActivationAPI(activation: ActivationType) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/activation/", activation)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Activation Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Activation failed");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function ResetPasswordAPI(email: string) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/reset_password/", { email: email })
|
||||||
|
.then(() => {
|
||||||
|
console.log("Activation Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Activation failed");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/reset_password_confirm/", info)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Reset Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Reset failed");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equipment APIs
|
||||||
|
|
||||||
|
export async function EquipmentAPI(id: number) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get(`api/v1/equipments/equipments/${id}/`, config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipment");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentUpdateAPI(
|
||||||
|
equipment: PatchEquipmentType,
|
||||||
|
id: number
|
||||||
|
) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.patch(`api/v1/equipments/equipments/${id}/`, equipment, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data as EquipmentType];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error updating equipment instance");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentRemoveAPI(id: number) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.delete(`api/v1/equipments/equipments/${id}/`, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data as EquipmentType];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error deleting equipment instance");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentsAPI() {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get("api/v1/equipments/equipments/", config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentListType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipments");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentCreateAPI(equipment: AddEquipmentType) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.post("api/v1/equipments/equipments/", equipment, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data as EquipmentType];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error creating equipment");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentLogsAPI() {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get("api/v1/equipments/equipments/logs", config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentLogListType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipment logs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equipment Instances APIs
|
||||||
|
|
||||||
|
export async function EquipmentInstanceLogsAPI() {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get("api/v1/equipments/equipment_instances/logs", config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentInstanceLogListType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipment logs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export async function EquipmentInstanceAPI(id: number) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get(`api/v1/equipments/equipment_instances/${id}/`, config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentInstanceType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipment");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentInstanceUpdateAPI(
|
||||||
|
item: PatchEquipmentInstanceType,
|
||||||
|
id: number
|
||||||
|
) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.patch(`api/v1/equipments/equipment_instances/${id}/`, item, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data as EquipmentInstanceType];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error updating equipment instance");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentInstanceRemoveAPI(id: number) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.delete(`api/v1/equipments/equipment_instances/${id}/`, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error deleting equipment instance");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentInstancesAPI() {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.get("api/v1/equipments/equipment_instances/", config)
|
||||||
|
.then((response) => {
|
||||||
|
return response.data as EquipmentInstanceListType;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Error retrieving equipments");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function EquipmentInstanceCreateAPI(
|
||||||
|
equipment_instance: AddEquipmentInstanceType
|
||||||
|
) {
|
||||||
|
const config = await GetConfig();
|
||||||
|
return instance
|
||||||
|
.post("api/v1/equipments/equipment_instances/", equipment_instance, config)
|
||||||
|
.then((response) => {
|
||||||
|
return [true, response.data as EquipmentInstanceType];
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error creating equipment instance");
|
||||||
|
return [false, ParseError(error)];
|
||||||
|
});
|
||||||
|
}
|
235
src/Components/AddItemModal/AddItemModal.tsx
Normal file
235
src/Components/AddItemModal/AddItemModal.tsx
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import AddToQueueIcon from "@mui/icons-material/AddToQueue";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { EquipmentInstanceCreateAPI, EquipmentsAPI } from "../API/API";
|
||||||
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import FormControl from "@mui/material/FormControl";
|
||||||
|
import FormLabel from "@mui/material/FormLabel";
|
||||||
|
import Radio from "@mui/material/Radio";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function AddItemModal() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [item, setItem] = useState({
|
||||||
|
equipment: 0,
|
||||||
|
remarks: "",
|
||||||
|
status: "WORKING",
|
||||||
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const equipments = useQuery({
|
||||||
|
queryKey: ["equipments"],
|
||||||
|
queryFn: EquipmentsAPI,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (equipments.data) {
|
||||||
|
setItem({ ...item, equipment: equipments.data[0].id });
|
||||||
|
}
|
||||||
|
}, [equipments.data, item]);
|
||||||
|
if (equipments.isLoading) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddToQueueIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Add Item</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControl style={{ marginTop: "8px" }}>
|
||||||
|
<FormLabel style={styles.text_dark} id="associated-equipment-group">
|
||||||
|
Select Associated SKU
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="demo-radio-buttons-group-label"
|
||||||
|
name="radio-buttons-group"
|
||||||
|
value={item.equipment}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, equipment: Number(e.target.value) });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ overflowY: "scroll", maxHeight: "8rem" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{equipments.data ? (
|
||||||
|
equipments.data.map((equipment) => (
|
||||||
|
<React.Fragment key={equipment.id}>
|
||||||
|
<FormControlLabel
|
||||||
|
value={equipment.id}
|
||||||
|
control={<Radio />}
|
||||||
|
label={equipment.name}
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl style={{ marginTop: "8px" }}>
|
||||||
|
<FormLabel style={styles.text_dark} id="status-selection">
|
||||||
|
Item Status
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="demo-radio-buttons-group-label"
|
||||||
|
value={item.status}
|
||||||
|
defaultValue="WORKING"
|
||||||
|
name="radio-buttons-group"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, status: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ overflowY: "scroll", maxHeight: "8rem" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value="WORKING"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Working"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="BROKEN"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Broken"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="MAINTENANCE"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Under Maintenance"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="DECOMISSIONED"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Decomissioned"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Remarks"
|
||||||
|
multiline
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, remarks: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={item.remarks}
|
||||||
|
placeholder={"Optionally add a brief description of the item"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Add Item"}
|
||||||
|
onClick={async () => {
|
||||||
|
let data;
|
||||||
|
if (item.remarks == "") {
|
||||||
|
data = await EquipmentInstanceCreateAPI({
|
||||||
|
equipment: item.equipment,
|
||||||
|
status: item.status,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data = await EquipmentInstanceCreateAPI(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[0]) {
|
||||||
|
setError("Added successfully");
|
||||||
|
toast(
|
||||||
|
`New item added successfuly, ${
|
||||||
|
typeof data[1] == "object" ? "ID:" + data[1].id : ""
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["equipment_instances"],
|
||||||
|
});
|
||||||
|
setItem({ ...item, status: "WORKING", remarks: "" });
|
||||||
|
} else {
|
||||||
|
setError(JSON.stringify(data[1]));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
172
src/Components/AddSKUModal/AddSKUModal.tsx
Normal file
172
src/Components/AddSKUModal/AddSKUModal.tsx
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import NoteAddIcon from "@mui/icons-material/NoteAdd";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { EquipmentCreateAPI } from "../API/API";
|
||||||
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import FormControl from "@mui/material/FormControl";
|
||||||
|
import FormLabel from "@mui/material/FormLabel";
|
||||||
|
import Radio from "@mui/material/Radio";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export default function AddSKUModal() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [sku, setSKU] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
category: "MISC",
|
||||||
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NoteAddIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Add SKU</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="SKU Name"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSKU({ ...sku, name: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={sku.name}
|
||||||
|
placeholder={"Enter SKU name"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Description"
|
||||||
|
multiline
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setSKU({ ...sku, description: e.target.value })
|
||||||
|
}
|
||||||
|
value={sku.description}
|
||||||
|
placeholder={"Give a brief description of the SKU"}
|
||||||
|
/>
|
||||||
|
<FormControl style={{ marginTop: "8px" }}>
|
||||||
|
<FormLabel
|
||||||
|
style={styles.text_dark}
|
||||||
|
id="demo-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
Category
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="demo-radio-buttons-group-label"
|
||||||
|
defaultValue="MISC"
|
||||||
|
name="radio-buttons-group"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSKU({ ...sku, category: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={styles.flex_row}>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="PC"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Workstation"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="NETWORKING"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Networking"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="CCTV"
|
||||||
|
control={<Radio />}
|
||||||
|
label="CCTV"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="FURNITURE"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Furniture"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="PERIPHERALS"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Peripherals"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="MISC"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Miscellaneous"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Add SKU"}
|
||||||
|
onClick={async () => {
|
||||||
|
const data = await EquipmentCreateAPI(sku);
|
||||||
|
if (data[0]) {
|
||||||
|
setError("Added successfully");
|
||||||
|
toast(
|
||||||
|
`New SKU added successfuly, ${
|
||||||
|
typeof data[1] == "object" ? "ID:" + data[1].id : ""
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["equipments"] });
|
||||||
|
setSKU({ name: "", description: "", category: "MISC" });
|
||||||
|
} else {
|
||||||
|
setError(JSON.stringify(data[1]));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
65
src/Components/Button/Button.tsx
Normal file
65
src/Components/Button/Button.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
|
||||||
|
export interface props {
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
label?: string;
|
||||||
|
type: "light" | "dark";
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
export default function Button(props: props) {
|
||||||
|
const [clicked, setClicked] = useState(false);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={props.onClick}
|
||||||
|
onMouseDown={() => {
|
||||||
|
if (!clicked) {
|
||||||
|
setClicked(!clicked);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseUp={() => setClicked(false)}
|
||||||
|
onMouseLeave={() => setClicked(false)}
|
||||||
|
style={{
|
||||||
|
borderRadius: 24,
|
||||||
|
minWidth: "128px",
|
||||||
|
maxWidth: "128px",
|
||||||
|
borderColor: colors.button_border,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: "2px",
|
||||||
|
paddingBottom: 0,
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingRight: "4px",
|
||||||
|
paddingLeft: "4px",
|
||||||
|
marginBottom: "4px",
|
||||||
|
marginTop: "4px",
|
||||||
|
backgroundColor:
|
||||||
|
props.type == "light"
|
||||||
|
? clicked
|
||||||
|
? colors.button_dark
|
||||||
|
: colors.button_light
|
||||||
|
: clicked
|
||||||
|
? colors.button_light
|
||||||
|
: colors.button_dark,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...(props.type == "light"
|
||||||
|
? clicked
|
||||||
|
? styles.text_light
|
||||||
|
: styles.text_dark
|
||||||
|
: clicked
|
||||||
|
? styles.text_dark
|
||||||
|
: styles.text_light),
|
||||||
|
...styles.text_S,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</p>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
110
src/Components/Drawer/Drawer.tsx
Normal file
110
src/Components/Drawer/Drawer.tsx
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import styles, { colors } from "../../styles";
|
||||||
|
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||||
|
import HomeIcon from "@mui/icons-material/Home";
|
||||||
|
import LogoutIcon from "@mui/icons-material/Logout";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { UserAPI, setAccessToken, setRefreshToken } from "../API/API";
|
||||||
|
import DrawerButton from "../DrawerButton/DrawerButton";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
export default function Drawer() {
|
||||||
|
const user = useQuery({ queryKey: ["user"], queryFn: UserAPI });
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "256px",
|
||||||
|
height: "100%",
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: colors.header_color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={styles.flex_row}>
|
||||||
|
<AccountCircleIcon
|
||||||
|
style={{
|
||||||
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
color: "white",
|
||||||
|
marginRight: "4px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_light,
|
||||||
|
...styles.text_S,
|
||||||
|
...{ alignSelf: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.data
|
||||||
|
? user.data.username
|
||||||
|
: user.isError
|
||||||
|
? "Error loading user"
|
||||||
|
: "Loading user..."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "white",
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DrawerButton
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/dashboard");
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
<HomeIcon
|
||||||
|
style={{
|
||||||
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
color: "white",
|
||||||
|
marginRight: "2px",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifySelf: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Dashboard"}
|
||||||
|
/>
|
||||||
|
<DrawerButton
|
||||||
|
onClick={async () => {
|
||||||
|
navigate("/");
|
||||||
|
await dispatch(auth_toggle());
|
||||||
|
await setAccessToken("");
|
||||||
|
await setRefreshToken("");
|
||||||
|
toast("Logged out", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
<LogoutIcon
|
||||||
|
style={{
|
||||||
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
color: "white",
|
||||||
|
marginRight: "2px",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifySelf: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Log out"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
55
src/Components/DrawerButton/DrawerButton.tsx
Normal file
55
src/Components/DrawerButton/DrawerButton.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
|
||||||
|
export interface props {
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
export default function DrawerButton(props: props) {
|
||||||
|
const [clicked, setClicked] = useState(false);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={props.onClick}
|
||||||
|
onMouseDown={() => {
|
||||||
|
if (!clicked) {
|
||||||
|
setClicked(!clicked);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseUp={() => setClicked(false)}
|
||||||
|
onMouseLeave={() => setClicked(false)}
|
||||||
|
style={{
|
||||||
|
borderRadius: 24,
|
||||||
|
minWidth: "128px",
|
||||||
|
maxWidth: "128px",
|
||||||
|
borderColor: colors.button_border,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: "2px",
|
||||||
|
paddingBottom: 0,
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingRight: "4px",
|
||||||
|
paddingLeft: "4px",
|
||||||
|
marginBottom: "4px",
|
||||||
|
marginTop: "4px",
|
||||||
|
backgroundColor: clicked ? colors.button_light : colors.button_dark,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={styles.flex_row}>
|
||||||
|
{clicked ? <></> : props.icon}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...(clicked ? styles.text_dark : styles.text_light),
|
||||||
|
...styles.text_M,
|
||||||
|
...{ marginLeft: "4px" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
298
src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx
Normal file
298
src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import {
|
||||||
|
EquipmentInstanceAPI,
|
||||||
|
EquipmentInstanceRemoveAPI,
|
||||||
|
EquipmentInstanceUpdateAPI,
|
||||||
|
} from "../API/API";
|
||||||
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import FormControl from "@mui/material/FormControl";
|
||||||
|
import FormLabel from "@mui/material/FormLabel";
|
||||||
|
import Radio from "@mui/material/Radio";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function EditItemInstanceModal(props: {
|
||||||
|
id: number;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [item, setItem] = useState({
|
||||||
|
remarks: "",
|
||||||
|
status: "",
|
||||||
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const equipment = useQuery({
|
||||||
|
queryKey: ["equipment_instance", props.id],
|
||||||
|
queryFn: () => EquipmentInstanceAPI(Number(props.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (equipment.data) {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
remarks: equipment.data.remarks,
|
||||||
|
status: equipment.data.status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [equipment.data]);
|
||||||
|
|
||||||
|
const update_mutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const data = await EquipmentInstanceUpdateAPI(item, props.id);
|
||||||
|
if (data[0] != true) {
|
||||||
|
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["equipment_instances"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["equipment_instance", props.id],
|
||||||
|
});
|
||||||
|
setError("Updated successfully");
|
||||||
|
toast(
|
||||||
|
`Item updated successfuly, ${
|
||||||
|
typeof data[1] == "object" ? "ID:" + data[1].id : ""
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (typeof data[1] == "object") {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
remarks: data[1].remarks,
|
||||||
|
status: data[1].status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(JSON.stringify(error));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delete_mutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const data = await EquipmentInstanceRemoveAPI(props.id);
|
||||||
|
if (data[0] != true) {
|
||||||
|
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["equipment_instances"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["equipment_instance", props.id],
|
||||||
|
});
|
||||||
|
setError("Deleted successfully");
|
||||||
|
toast("Item deleted successfuly", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
props.setOpen(false);
|
||||||
|
if (typeof data[1] == "object") {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
remarks: data[1].remarks,
|
||||||
|
status: data[1].status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(JSON.stringify(error));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (equipment.isLoading) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Edit Item</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControl style={{ marginTop: "8px" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
verticalAlign: "center",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
...{ marginRight: "8px" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Associated SKU:
|
||||||
|
</p>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>
|
||||||
|
{equipment.data?.equipment_name}
|
||||||
|
{" (SKU #" + equipment.data?.equipment + ")"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormLabel style={styles.text_dark} id="status-selection">
|
||||||
|
Item Status
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="demo-radio-buttons-group-label"
|
||||||
|
value={item.status}
|
||||||
|
defaultValue="WORKING"
|
||||||
|
name="radio-buttons-group"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, status: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ overflowY: "scroll", maxHeight: "8rem" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value="WORKING"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Working"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="BROKEN"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Broken"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="MAINTENANCE"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Under Maintenance"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="DECOMISSIONED"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Decomissioned"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Remarks"
|
||||||
|
multiline
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, remarks: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={item.remarks}
|
||||||
|
placeholder={"Optionally add a brief description of the item"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{ justifyContent: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Update Item"}
|
||||||
|
onClick={async () => {
|
||||||
|
await update_mutation.mutate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ margin: "8px" }}></div>
|
||||||
|
<Button
|
||||||
|
type={"light"}
|
||||||
|
label={"Delete Item"}
|
||||||
|
onClick={async () => {
|
||||||
|
await delete_mutation.mutate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
300
src/Components/EditSKUModal/EditSKUModal.tsx
Normal file
300
src/Components/EditSKUModal/EditSKUModal.tsx
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import {
|
||||||
|
EquipmentAPI,
|
||||||
|
EquipmentRemoveAPI,
|
||||||
|
EquipmentUpdateAPI,
|
||||||
|
} from "../API/API";
|
||||||
|
import RadioGroup from "@mui/material/RadioGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import FormControl from "@mui/material/FormControl";
|
||||||
|
import FormLabel from "@mui/material/FormLabel";
|
||||||
|
import Radio from "@mui/material/Radio";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function EditSKUModal(props: {
|
||||||
|
id: number;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [item, setItem] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
category: "",
|
||||||
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const equipment = useQuery({
|
||||||
|
queryKey: ["equipment", props.id],
|
||||||
|
queryFn: () => EquipmentAPI(Number(props.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (equipment.data) {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
name: equipment.data.name,
|
||||||
|
description: equipment.data.description,
|
||||||
|
category: equipment.data.category,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [equipment.data]);
|
||||||
|
|
||||||
|
const update_mutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const data = await EquipmentUpdateAPI(item, props.id);
|
||||||
|
if (data[0] != true) {
|
||||||
|
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["equipments"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["equipment", props.id],
|
||||||
|
});
|
||||||
|
setError("Updated successfully");
|
||||||
|
toast(
|
||||||
|
`Item updated successfuly, ${
|
||||||
|
typeof data[1] == "object" ? "ID:" + data[1].id : ""
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (typeof data[1] == "object") {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
name: data[1].name,
|
||||||
|
description: data[1].description,
|
||||||
|
category: data[1].category,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(JSON.stringify(error));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delete_mutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const data = await EquipmentRemoveAPI(props.id);
|
||||||
|
if (data[0] != true) {
|
||||||
|
return Promise.reject(new Error(JSON.stringify(data[1])));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["equipments"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["equipment", props.id],
|
||||||
|
});
|
||||||
|
setError("Deleted successfully");
|
||||||
|
toast("SKU deleted successfuly", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
props.setOpen(false);
|
||||||
|
if (typeof data[1] == "object") {
|
||||||
|
setItem({
|
||||||
|
...item,
|
||||||
|
name: data[1].name,
|
||||||
|
description: data[1].description,
|
||||||
|
category: data[1].category,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(JSON.stringify(error));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (equipment.isLoading) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Edit SKU</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControl style={{ marginTop: "8px" }}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="SKU Name"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, name: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={item.name}
|
||||||
|
placeholder={"Enter SKU name"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Description"
|
||||||
|
multiline
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setItem({ ...item, description: e.target.value })
|
||||||
|
}
|
||||||
|
value={item.description}
|
||||||
|
placeholder={"Give a brief description of the SKU"}
|
||||||
|
/>
|
||||||
|
<FormLabel
|
||||||
|
style={styles.text_dark}
|
||||||
|
id="demo-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
Category
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="demo-radio-buttons-group-label"
|
||||||
|
name="radio-buttons-group"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setItem({ ...item, category: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={item.category}
|
||||||
|
>
|
||||||
|
<div style={styles.flex_row}>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="PC"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Workstation"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="NETWORKING"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Networking"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="CCTV"
|
||||||
|
control={<Radio />}
|
||||||
|
label="CCTV"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="FURNITURE"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Furniture"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="PERIPHERALS"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Peripherals"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="MISC"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Miscellaneous"
|
||||||
|
style={styles.text_dark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{ justifyContent: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Update Item"}
|
||||||
|
onClick={async () => {
|
||||||
|
await update_mutation.mutate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ margin: "8px" }}></div>
|
||||||
|
<Button
|
||||||
|
type={"light"}
|
||||||
|
label={"Delete Item"}
|
||||||
|
onClick={async () => {
|
||||||
|
await delete_mutation.mutate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
50
src/Components/Header/Header.tsx
Normal file
50
src/Components/Header/Header.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles, { colors } from "../../styles";
|
||||||
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
|
import SidebarModal from "../Drawer/Drawer";
|
||||||
|
import { Drawer } from "@mui/material";
|
||||||
|
export interface props {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Header(props: props) {
|
||||||
|
const [SidebarOpen, SetSidebarOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundColor: colors.header_color,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
alignSelf: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuIcon
|
||||||
|
style={{
|
||||||
|
height: "64px",
|
||||||
|
width: "64px",
|
||||||
|
float: "left",
|
||||||
|
marginLeft: "8px",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
SetSidebarOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_light, ...styles.text_L, ...{ flex: 1 } }}>
|
||||||
|
{props.label}
|
||||||
|
</p>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Drawer open={SidebarOpen} onClose={() => SetSidebarOpen(false)}>
|
||||||
|
<SidebarModal />
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
145
src/Components/LoginModal/LoginModal.tsx
Normal file
145
src/Components/LoginModal/LoginModal.tsx
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import InputAdornment from "@mui/material/InputAdornment";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
|
import LoginIcon from "@mui/icons-material/Login";
|
||||||
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { LoginAPI } from "../API/API";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
export default function LoginModal() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [remember_session, setRememberSession] = useState(true);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoginIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Welcome back!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Username"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setUser({ ...user, username: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={user.username}
|
||||||
|
placeholder={"Enter username"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="Password"
|
||||||
|
placeholder={"Enter password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.password}
|
||||||
|
/>
|
||||||
|
<div style={styles.flex_row}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{ flex: 1, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
inputProps={{ "aria-label": "Checkbox demo" }}
|
||||||
|
defaultChecked
|
||||||
|
sx={{
|
||||||
|
color: colors.button_dark,
|
||||||
|
"&.Mui-checked": {
|
||||||
|
color: colors.button_dark,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={remember_session}
|
||||||
|
onChange={() => setRememberSession(!remember_session)}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>Remember me</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>{error}</p>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Login"}
|
||||||
|
onClick={async () => {
|
||||||
|
const status = await LoginAPI(user, remember_session);
|
||||||
|
if (status === true) {
|
||||||
|
await dispatch(auth_toggle());
|
||||||
|
navigate("/dashboard");
|
||||||
|
toast("Logged in", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError("Invalid login");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
19
src/Components/Plugins/Redux/Slices/AuthSlice/AuthSlice.tsx
Normal file
19
src/Components/Plugins/Redux/Slices/AuthSlice/AuthSlice.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export const AuthSlice = createSlice({
|
||||||
|
name: "auth",
|
||||||
|
initialState: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
auth_toggle: (state) => {
|
||||||
|
state.value = !state.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { auth_toggle } = AuthSlice.actions;
|
||||||
|
|
||||||
|
export default AuthSlice.reducer;
|
15
src/Components/Plugins/Redux/Store/Store.tsx
Normal file
15
src/Components/Plugins/Redux/Store/Store.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import AuthReducer from "../Slices/AuthSlice/AuthSlice";
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
auth: AuthReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store;
|
||||||
|
|
||||||
|
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
193
src/Components/RegisterModal/RegisterModal.tsx
Normal file
193
src/Components/RegisterModal/RegisterModal.tsx
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import InputAdornment from "@mui/material/InputAdornment";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import Visibility from "@mui/icons-material/Visibility";
|
||||||
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||||
|
import { AppRegistration } from "@mui/icons-material";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { RegisterAPI } from "../API/API";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
export default function RegisterModal() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
first_name: "",
|
||||||
|
last_name: "",
|
||||||
|
username: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
confirm_password: "",
|
||||||
|
});
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppRegistration
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Get Started</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="First Name"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setUser({ ...user, first_name: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={user.first_name}
|
||||||
|
placeholder={"Enter your first name"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Last Name"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, last_name: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.last_name}
|
||||||
|
placeholder={"Enter your last name"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Email"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, email: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.email}
|
||||||
|
placeholder={"Enter your email"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Username"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setUser({ ...user, username: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={user.username}
|
||||||
|
placeholder={"Enter username"}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="Password"
|
||||||
|
placeholder={"Enter password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.password}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder={"Re-enter password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setUser({ ...user, confirm_password: e.target.value });
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={user.confirm_password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Register"}
|
||||||
|
onClick={async () => {
|
||||||
|
if (user.password !== user.confirm_password) {
|
||||||
|
setError("Passwords do not match");
|
||||||
|
} else {
|
||||||
|
const status = await RegisterAPI(user);
|
||||||
|
if (status[0]) {
|
||||||
|
setError(
|
||||||
|
"Registration successful. Please activate your account using the email provided"
|
||||||
|
);
|
||||||
|
toast("Registration successful", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate(0);
|
||||||
|
}, 3000);
|
||||||
|
setUser({
|
||||||
|
first_name: "",
|
||||||
|
last_name: "",
|
||||||
|
username: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
confirm_password: "",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError(JSON.stringify(status[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
91
src/Components/ResetPasswordModal/ResetPasswordModal.tsx
Normal file
91
src/Components/ResetPasswordModal/ResetPasswordModal.tsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import NewReleasesIcon from "@mui/icons-material/NewReleases";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { ResetPasswordAPI } from "../API/API";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
export default function ResetPasswordModal() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NewReleasesIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Forgot Password</p>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>
|
||||||
|
Enter your email to request a password reset
|
||||||
|
</p>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Email"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setEmail(e.target.value);
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={email}
|
||||||
|
placeholder={"Enter email associated with account"}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Confirm"}
|
||||||
|
onClick={async () => {
|
||||||
|
const status = await ResetPasswordAPI(email);
|
||||||
|
if (status === true) {
|
||||||
|
await dispatch(auth_toggle());
|
||||||
|
navigate("/");
|
||||||
|
toast("Reset request sent", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setError(
|
||||||
|
"Password reset request sent. Please follow your email for further instructions"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setError("Invalid email specified");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
60
src/Components/Revalidator/Revalidator.tsx
Normal file
60
src/Components/Revalidator/Revalidator.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
|
import { JWTRefreshAPI, setAccessToken, setRefreshToken } from "../API/API";
|
||||||
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { RootState } from "../Plugins/Redux/Store/Store";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
export default function Revalidator() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const authenticated = useSelector((state: RootState) => state.auth.value);
|
||||||
|
const [rechecked, setRechecked] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authenticated && rechecked) {
|
||||||
|
if (location.pathname !== "/") {
|
||||||
|
navigate("/");
|
||||||
|
toast("Please log in to continue", {
|
||||||
|
position: "bottom-center",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [authenticated, location.pathname, navigate, rechecked]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authenticated) {
|
||||||
|
JWTRefreshAPI().then(async (response) => {
|
||||||
|
if (response) {
|
||||||
|
await dispatch(auth_toggle());
|
||||||
|
toast("User session restored", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await setRefreshToken("");
|
||||||
|
await setAccessToken("");
|
||||||
|
}
|
||||||
|
setRechecked(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
98
src/Components/Types/Types.tsx
Normal file
98
src/Components/Types/Types.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
export type RegisterType = {
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginType = {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActivationType = {
|
||||||
|
uid: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResetPasswordConfirmType = {
|
||||||
|
uid: string;
|
||||||
|
token: string;
|
||||||
|
new_password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddEquipmentType = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PatchEquipmentType = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentType = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
last_updated: string;
|
||||||
|
last_updated_by: string;
|
||||||
|
date_added: string;
|
||||||
|
category: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentListType = Array<EquipmentType>;
|
||||||
|
|
||||||
|
export type EquipmentLogType = {
|
||||||
|
history_id: number;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
description: string;
|
||||||
|
history_date: string;
|
||||||
|
history_user: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentLogListType = Array<EquipmentLogType>;
|
||||||
|
|
||||||
|
export type AddEquipmentInstanceType = {
|
||||||
|
equipment: number;
|
||||||
|
status: string;
|
||||||
|
remarks?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PatchEquipmentInstanceType = {
|
||||||
|
status: string;
|
||||||
|
remarks?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentInstanceType = {
|
||||||
|
id: number;
|
||||||
|
equipment: string;
|
||||||
|
equipment_name: string;
|
||||||
|
status: string;
|
||||||
|
remarks: string;
|
||||||
|
last_updated: string;
|
||||||
|
last_updated_by: string;
|
||||||
|
date_added: string;
|
||||||
|
category: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentInstanceListType = Array<EquipmentInstanceType>;
|
||||||
|
|
||||||
|
export type EquipmentInstanceLogType = {
|
||||||
|
history_id: number;
|
||||||
|
id: number;
|
||||||
|
equipment: number;
|
||||||
|
equipment_name: string;
|
||||||
|
category: string;
|
||||||
|
status: string;
|
||||||
|
remarks: string;
|
||||||
|
history_date: string;
|
||||||
|
history_user: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EquipmentInstanceLogListType = Array<EquipmentInstanceLogType>;
|
98
src/Pages/ActivationPage/ActivationPage.tsx
Normal file
98
src/Pages/ActivationPage/ActivationPage.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import styles, { colors } from "../../styles";
|
||||||
|
import { ActivationAPI } from "../../Components/API/API";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
|
||||||
|
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
export default function ActivationPage() {
|
||||||
|
const { uid, token } = useParams();
|
||||||
|
const [feedback, setFeedback] = useState("");
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
if (uid && token && feedback == "") {
|
||||||
|
ActivationAPI({ uid, token }).then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setFeedback("Activation successful");
|
||||||
|
toast("Activation successful", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
toast("Please login to continue", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFeedback("Invalid activation link");
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!uid || !token) {
|
||||||
|
setFeedback("Missing uid or token");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}, [uid, token, feedback, navigate]);
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{error && !loading ? (
|
||||||
|
<ErrorOutlineIcon
|
||||||
|
style={{ height: "128px", width: "128px", color: colors.red }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CheckCircleOutlineIcon
|
||||||
|
style={{ height: "128px", width: "128px", color: colors.green }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>{feedback}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.header_color,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "30%",
|
||||||
|
height: "4px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>
|
||||||
|
Activating your CITC Equipment Tracker Account
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
616
src/Pages/DashboardPage/DashboardPage.tsx
Normal file
616
src/Pages/DashboardPage/DashboardPage.tsx
Normal file
|
@ -0,0 +1,616 @@
|
||||||
|
import Header from "../../Components/Header/Header";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { useQueries } from "@tanstack/react-query";
|
||||||
|
import { EquipmentsAPI, EquipmentInstancesAPI } from "../../Components/API/API";
|
||||||
|
import { Button, CircularProgress } from "@mui/material";
|
||||||
|
import ComputerIcon from "@mui/icons-material/Computer";
|
||||||
|
import RouterIcon from "@mui/icons-material/Router";
|
||||||
|
import CameraOutdoorIcon from "@mui/icons-material/CameraOutdoor";
|
||||||
|
import ChairIcon from "@mui/icons-material/Chair";
|
||||||
|
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
||||||
|
import AddToQueueIcon from "@mui/icons-material/AddToQueue";
|
||||||
|
import NoteAddIcon from "@mui/icons-material/NoteAdd";
|
||||||
|
import NoteIcon from "@mui/icons-material/Note";
|
||||||
|
import ManageSearchIcon from "@mui/icons-material/ManageSearch";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useState } from "react";
|
||||||
|
import AddSKUModal from "../../Components/AddSKUModal/AddSKUModal";
|
||||||
|
import Popup from "reactjs-popup";
|
||||||
|
import AddItemModal from "../../Components/AddItemModal/AddItemModal";
|
||||||
|
export default function Dashboard() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const queries = useQueries({
|
||||||
|
queries: [
|
||||||
|
{
|
||||||
|
queryKey: ["equipments"],
|
||||||
|
queryFn: EquipmentsAPI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: ["equipment_instances"],
|
||||||
|
queryFn: EquipmentInstancesAPI,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const isLoading = queries.some((result) => result.isLoading);
|
||||||
|
|
||||||
|
const [addSKUmodalOpen, SetAddSKUModalOpen] = useState(false);
|
||||||
|
const [additemmodalOpen, SetAddItemModalOpen] = useState(false);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
margin: "16px",
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#a6a6a6",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "16rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
...{ float: "left", position: "absolute" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
SKUs in Database
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{queries[0].data ? queries[0].data.length : 0}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
margin: "16px",
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#a6a6a6",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "16rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
...{ float: "left", position: "absolute" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Items in Database
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{queries[1].data ? queries[1].data.length : 0}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
margin: "16px",
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#a6a6a6",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "16rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
...{ float: "left", position: "absolute" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Functional Items
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{queries[1].data
|
||||||
|
? queries[1].data.filter(
|
||||||
|
(equipment) => equipment.status == "WORKING"
|
||||||
|
).length
|
||||||
|
: 0}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
margin: "16px",
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#a6a6a6",
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "16rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
...{ float: "left", position: "absolute" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Broken Items
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{queries[1].data
|
||||||
|
? queries[1].data.filter(
|
||||||
|
(equipment) => equipment.status == "BROKEN"
|
||||||
|
).length
|
||||||
|
: 0}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Equipments
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/view/equipment_instances");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormatListBulletedIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View All
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
SetAddItemModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddToQueueIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Item
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
SetAddSKUModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NoteAddIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add SKU
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/view/equipments");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NoteIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View SKUs
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ComputerIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Workstations
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RouterIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Networking
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CameraOutdoorIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
CCTVs
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChairIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Furniture
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logs
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/view/equipments/logs");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ManageSearchIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
SKU Logs
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignSelf: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/view/equipment_instances/logs");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ManageSearchIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
marginLeft: "1rem",
|
||||||
|
marginRight: "1rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_M,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Item Logs
|
||||||
|
</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Popup
|
||||||
|
open={addSKUmodalOpen}
|
||||||
|
onClose={() => SetAddSKUModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "32rem",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddSKUModal />
|
||||||
|
</Popup>
|
||||||
|
<Popup
|
||||||
|
open={additemmodalOpen}
|
||||||
|
onClose={() => SetAddItemModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "32rem",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddItemModal />
|
||||||
|
</Popup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import Header from "../../Components/Header/Header";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { EquipmentInstanceLogsAPI } from "../../Components/API/API";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
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 TableHead from "@mui/material/TableHead";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
|
||||||
|
export default function EquipmentInstanceLogsPage() {
|
||||||
|
const equipment_instance_logs = useQuery({
|
||||||
|
queryKey: ["equipment_instance_logs"],
|
||||||
|
queryFn: EquipmentInstanceLogsAPI,
|
||||||
|
});
|
||||||
|
if (equipment_instance_logs.isLoading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Item History"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: "90%", overflowY: "scroll", marginTop: "2rem" }}>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: "32rem" }} size="medium">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow style={{ backgroundColor: colors.header_color }}>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Transaction ID
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Item ID
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
SKU
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Remarks
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Status
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Date Modified
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{equipment_instance_logs.data ? (
|
||||||
|
equipment_instance_logs.data.map((equipment_instance_log) => (
|
||||||
|
<TableRow
|
||||||
|
key={equipment_instance_log.history_id}
|
||||||
|
sx={{
|
||||||
|
"&:last-child td, &:last-child th": { border: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_instance_log.history_id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_instance_log.id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{`SKU #${equipment_instance_log.equipment} - ${equipment_instance_log.equipment_name}`}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_instance_log.remarks}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_instance_log.status}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ alignItems: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{equipment_instance_log.history_date}</div>
|
||||||
|
<div>
|
||||||
|
{equipment_instance_log.history_user
|
||||||
|
? "by " + equipment_instance_log.history_user
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import Header from "../../Components/Header/Header";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { EquipmentInstancesAPI } from "../../Components/API/API";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
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 TableHead from "@mui/material/TableHead";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import EditItemModal from "../../Components/EditItemInstanceModal/EditItemInstanceModal";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Popup from "reactjs-popup";
|
||||||
|
|
||||||
|
export default function EquipmentInstancesListPage() {
|
||||||
|
const [editmodalOpen, SetEditModalOpen] = useState(false);
|
||||||
|
const [selectedItem, SetSelectedItem] = useState(0);
|
||||||
|
const equipment_instances = useQuery({
|
||||||
|
queryKey: ["equipment_instances"],
|
||||||
|
queryFn: EquipmentInstancesAPI,
|
||||||
|
});
|
||||||
|
if (equipment_instances.isLoading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Items List"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: "90%", overflowY: "scroll", marginTop: "2rem" }}>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: "32rem" }} size="medium">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow style={{ backgroundColor: colors.header_color }}>
|
||||||
|
<TableCell style={styles.text_light}>ID</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Status
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Category
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Last Modified
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{equipment_instances.data ? (
|
||||||
|
equipment_instances.data.map((equipment) => (
|
||||||
|
<TableRow
|
||||||
|
key={equipment.id}
|
||||||
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
onClick={() => {
|
||||||
|
SetSelectedItem(equipment.id);
|
||||||
|
SetEditModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.equipment_name}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.status}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.category}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ alignItems: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{equipment.last_updated}</div>
|
||||||
|
<div>
|
||||||
|
{equipment.last_updated_by
|
||||||
|
? "by " + equipment.last_updated_by
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Popup
|
||||||
|
open={editmodalOpen}
|
||||||
|
onClose={() => SetEditModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={styles.popup_center}
|
||||||
|
>
|
||||||
|
<EditItemModal id={selectedItem} setOpen={SetEditModalOpen} />
|
||||||
|
</Popup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
148
src/Pages/EquipmentListPage/EquipmentListPage.tsx
Normal file
148
src/Pages/EquipmentListPage/EquipmentListPage.tsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import Header from "../../Components/Header/Header";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { EquipmentsAPI } from "../../Components/API/API";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
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 TableHead from "@mui/material/TableHead";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import EditSKUModal from "../../Components/EditSKUModal/EditSKUModal";
|
||||||
|
import Popup from "reactjs-popup";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function EquipmentListPage() {
|
||||||
|
const [editmodalOpen, SetEditModalOpen] = useState(false);
|
||||||
|
const [selectedItem, SetSelectedItem] = useState(0);
|
||||||
|
const equipments = useQuery({
|
||||||
|
queryKey: ["equipments"],
|
||||||
|
queryFn: EquipmentsAPI,
|
||||||
|
});
|
||||||
|
if (equipments.isLoading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"SKU List"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: "90%", overflowY: "scroll", marginTop: "2rem" }}>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: "32rem" }} size="medium">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow style={{ backgroundColor: colors.header_color }}>
|
||||||
|
<TableCell style={styles.text_light}>ID</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Description
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Category
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Last Modified
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{equipments.data ? (
|
||||||
|
equipments.data.map((equipment) => (
|
||||||
|
<TableRow
|
||||||
|
key={equipment.id}
|
||||||
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
onClick={() => {
|
||||||
|
SetSelectedItem(equipment.id);
|
||||||
|
SetEditModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.name}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.description}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment.category}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ alignItems: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{equipment.last_updated}</div>
|
||||||
|
<div>
|
||||||
|
{equipment.last_updated_by
|
||||||
|
? "by " + equipment.last_updated_by
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Popup
|
||||||
|
open={editmodalOpen}
|
||||||
|
onClose={() => SetEditModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={styles.popup_center}
|
||||||
|
>
|
||||||
|
<EditSKUModal id={selectedItem} setOpen={SetEditModalOpen} />
|
||||||
|
</Popup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
138
src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx
Normal file
138
src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import Header from "../../Components/Header/Header";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { EquipmentLogsAPI } from "../../Components/API/API";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
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 TableHead from "@mui/material/TableHead";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
|
||||||
|
export default function EquipmentLogsPage() {
|
||||||
|
const equipment_logs = useQuery({
|
||||||
|
queryKey: ["equipment_logs"],
|
||||||
|
queryFn: EquipmentLogsAPI,
|
||||||
|
});
|
||||||
|
if (equipment_logs.isLoading) {
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"Dashboard"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingTop: "64px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress style={{ height: "128px", width: "128px" }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
...styles.text_dark,
|
||||||
|
...styles.text_L,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<Header label={"SKU History"} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: "90%", overflowY: "scroll", marginTop: "2rem" }}>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: "32rem" }} size="medium">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow style={{ backgroundColor: colors.header_color }}>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Transaction ID
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
SKU ID
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Description
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Category
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" style={styles.text_light}>
|
||||||
|
Date Modified
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{equipment_logs.data ? (
|
||||||
|
equipment_logs.data.map((equipment_log) => (
|
||||||
|
<TableRow
|
||||||
|
key={equipment_log.history_id}
|
||||||
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_log.history_id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_log.id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_log.name}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_log.description}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" component="th" scope="row">
|
||||||
|
{equipment_log.category}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{ alignItems: "center" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{equipment_log.history_date}</div>
|
||||||
|
<div>
|
||||||
|
{equipment_log.history_user
|
||||||
|
? "by " + equipment_log.history_user
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
3
src/Pages/ErrorPage/ErrorPage.tsx
Normal file
3
src/Pages/ErrorPage/ErrorPage.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function ErrorPage() {
|
||||||
|
return <div>{"ErrorPage"}</div>;
|
||||||
|
}
|
161
src/Pages/LandingPage/LandingPage.tsx
Normal file
161
src/Pages/LandingPage/LandingPage.tsx
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import Button from "../../Components/Button/Button";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import citc_logo from "../../assets/citc_logo.jpg";
|
||||||
|
import Popup from "reactjs-popup";
|
||||||
|
import "reactjs-popup/dist/index.css";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import LoginModal from "../../Components/LoginModal/LoginModal";
|
||||||
|
import RegisterModal from "../../Components/RegisterModal/RegisterModal";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { RootState } from "../../Components/Plugins/Redux/Store/Store";
|
||||||
|
import ResetPasswordModal from "../../Components/ResetPasswordModal/ResetPasswordModal";
|
||||||
|
|
||||||
|
export default function LandingPage() {
|
||||||
|
const [loginmodalOpen, SetloginmodalOpen] = useState(false);
|
||||||
|
const [registermodalOpen, SetRegisterModalOpen] = useState(false);
|
||||||
|
const [resetmodalOpen, SetResetModalOpen] = useState(false);
|
||||||
|
const authenticated = useSelector((state: RootState) => state.auth.value);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authenticated) {
|
||||||
|
navigate("/dashboard");
|
||||||
|
console.log("Already logged in. Redirecting to dashboard page");
|
||||||
|
}
|
||||||
|
}, [authenticated, navigate]);
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ height: "auto", flex: 1, flexWrap: "wrap" }}>
|
||||||
|
<img style={{ width: "16rem", height: "auto" }} src={citc_logo} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "auto",
|
||||||
|
flex: 1,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minWidth: "30vw",
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
margin: 64,
|
||||||
|
paddingBottom: "16vh",
|
||||||
|
paddingTop: "16vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>
|
||||||
|
CITC EQUIPMENT
|
||||||
|
<br />
|
||||||
|
TRACKER
|
||||||
|
</p>
|
||||||
|
<div style={{ ...styles.flex_column }}>
|
||||||
|
<Button
|
||||||
|
type={"light"}
|
||||||
|
label={"Login"}
|
||||||
|
onClick={() => {
|
||||||
|
SetloginmodalOpen(true);
|
||||||
|
SetRegisterModalOpen(false);
|
||||||
|
SetResetModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Register"}
|
||||||
|
onClick={() => {
|
||||||
|
SetRegisterModalOpen(true);
|
||||||
|
SetloginmodalOpen(false);
|
||||||
|
SetResetModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"light"}
|
||||||
|
label={"Forgot Password"}
|
||||||
|
onClick={() => {
|
||||||
|
SetResetModalOpen(true);
|
||||||
|
SetRegisterModalOpen(false);
|
||||||
|
SetloginmodalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popup
|
||||||
|
open={loginmodalOpen}
|
||||||
|
onClose={() => SetloginmodalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "512px",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoginModal />
|
||||||
|
</Popup>
|
||||||
|
<Popup
|
||||||
|
open={registermodalOpen}
|
||||||
|
onClose={() => SetRegisterModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "512px",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RegisterModal />
|
||||||
|
</Popup>
|
||||||
|
<Popup
|
||||||
|
open={resetmodalOpen}
|
||||||
|
onClose={() => SetResetModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "512px",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResetPasswordModal />
|
||||||
|
</Popup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
148
src/Pages/ResetPasswordPage/ResetPasswordPage.tsx
Normal file
148
src/Pages/ResetPasswordPage/ResetPasswordPage.tsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import styles, { colors } from "../../styles";
|
||||||
|
import { ResetPasswordConfirmAPI } from "../../Components/API/API";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { VisibilityOff, Visibility } from "@mui/icons-material";
|
||||||
|
import { TextField, InputAdornment, IconButton } from "@mui/material";
|
||||||
|
import Button from "../../Components/Button/Button";
|
||||||
|
export default function ResetPasswordPage() {
|
||||||
|
const { uid, token } = useParams();
|
||||||
|
const [feedback, setFeedback] = useState("");
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
password: "",
|
||||||
|
confirm_password: "",
|
||||||
|
});
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
justifyContent: "center",
|
||||||
|
verticalAlign: "center",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>
|
||||||
|
Confirm Password Reset
|
||||||
|
</p>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
setFeedback("");
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="New Password"
|
||||||
|
placeholder={"Enter new password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.password}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
setFeedback("");
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder={"Re-enter password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, confirm_password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.confirm_password}
|
||||||
|
/>
|
||||||
|
<div style={{ justifyContent: "center", display: "flex" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.header_color,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "80%",
|
||||||
|
height: "4px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{feedback}</p>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Confirm"}
|
||||||
|
onClick={() => {
|
||||||
|
if (uid && token && feedback == "") {
|
||||||
|
ResetPasswordConfirmAPI({
|
||||||
|
uid,
|
||||||
|
token,
|
||||||
|
new_password: user.password,
|
||||||
|
}).then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setFeedback("Reset successful");
|
||||||
|
toast("Reset successful", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
toast("Please login to continue", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFeedback("Invalid token specified for password reset");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!uid || !token) {
|
||||||
|
setFeedback("Missing token for password reset");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
BIN
src/assets/citc_logo.jpg
Normal file
BIN
src/assets/citc_logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
13
src/main.tsx
13
src/main.tsx
|
@ -1,10 +1,5 @@
|
||||||
import React from 'react'
|
import ReactDOM from "react-dom/client";
|
||||||
import ReactDOM from 'react-dom/client'
|
import App from "./App.tsx";
|
||||||
import App from './App.tsx'
|
import "./index.css";
|
||||||
import './index.css'
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>,
|
|
||||||
)
|
|
||||||
|
|
92
src/styles.tsx
Normal file
92
src/styles.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
export const colors = {
|
||||||
|
background: "#FFFFFF",
|
||||||
|
header_color: "#141762",
|
||||||
|
font_dark: "#141762",
|
||||||
|
font_light: "#FFFFFF",
|
||||||
|
button_dark: "#141762",
|
||||||
|
button_light: "#FFFFFF",
|
||||||
|
button_border: "#141762",
|
||||||
|
red: "#a44141",
|
||||||
|
orange: "#c57331",
|
||||||
|
green: "#80b28a",
|
||||||
|
};
|
||||||
|
const styles: { [key: string]: React.CSSProperties } = {
|
||||||
|
background: {
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
minWidth: "100%",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
text_dark: {
|
||||||
|
color: colors.font_dark,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
text_light: {
|
||||||
|
color: colors.font_light,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
text_red: {
|
||||||
|
color: colors.red,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
text_orange: {
|
||||||
|
color: colors.orange,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
text_green: {
|
||||||
|
color: colors.green,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
text_XL: {
|
||||||
|
fontSize: "clamp(2rem, 3rem, 8rem)",
|
||||||
|
},
|
||||||
|
text_L: {
|
||||||
|
fontSize: "clamp(1.5rem, 2rem, 6rem)",
|
||||||
|
},
|
||||||
|
text_M: {
|
||||||
|
fontSize: "clamp(1rem, 1rem, 4rem)",
|
||||||
|
},
|
||||||
|
text_S: {
|
||||||
|
fontSize: "clamp(0.6rem, 0.8rem, 1rem)",
|
||||||
|
},
|
||||||
|
text_XS: {
|
||||||
|
fontSize: "clamp(0.5rem, 0.6rem, 0.8rem)",
|
||||||
|
},
|
||||||
|
flex_row: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
},
|
||||||
|
flex_column: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
input_form: {
|
||||||
|
color: colors.font_dark,
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "clamp(1vw, 1rem, 2vw)",
|
||||||
|
background: "none",
|
||||||
|
borderRadius: 8,
|
||||||
|
maxWidth: "128px",
|
||||||
|
minWidth: "100%",
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
|
popup_center: {
|
||||||
|
width: "32rem",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default styles;
|
|
@ -18,7 +18,11 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
"paths":{
|
||||||
|
"@mui/styled-engine": ["./node_modules/@mui/styled-engine-sc"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|
17
woodpecker.yml
Normal file
17
woodpecker.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
pipeline:
|
||||||
|
- name: build
|
||||||
|
image: node:14
|
||||||
|
commands:
|
||||||
|
- npm install
|
||||||
|
- npm run build
|
||||||
|
- name: copy
|
||||||
|
image: alpine
|
||||||
|
environment:
|
||||||
|
- SSH_KEY:
|
||||||
|
from_secret: ssh_key
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache openssh-client
|
||||||
|
- echo "$SSH_KEY" | tr -d '\r' > /root/.ssh/id_rsa
|
||||||
|
- chmod 600 /root/.ssh/id_rsa
|
||||||
|
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > /root/.ssh/config
|
||||||
|
- scp -r dist/* username@10.0.10.4:/mnt/sda1/projects/equipment_tracker_frontend
|
Loading…
Reference in a new issue