mirror of
https://github.com/rjNemo/melon_frontend
synced 2026-06-06 02:16:45 +00:00
refactoring (#18)
* chore: update deps to fix CVEs * cleanup * use variable for app routes * refactor * ⬆️ react router * ⬆️ react * delete pem files
This commit is contained in:
parent
ed4ca135e7
commit
a9bf6936b5
24 changed files with 730 additions and 658 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -19,3 +19,4 @@ yarn-error.log*
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
*.pem
|
||||||
25
cert.pem
25
cert.pem
|
|
@ -1,25 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIENjCCAp6gAwIBAgIQDi8FrIt59I2QjzJ7mfvXlDANBgkqhkiG9w0BAQsFADBr
|
|
||||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExIDAeBgNVBAsMF3J1aWR5
|
|
||||||
QG1vb25zdG9uZSAoUnVpZHkpMScwJQYDVQQDDB5ta2NlcnQgcnVpZHlAbW9vbnN0
|
|
||||||
b25lIChSdWlkeSkwHhcNMjEwNzEzMTI0NzE1WhcNMjMxMDEzMTI0NzE1WjBLMScw
|
|
||||||
JQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxIDAeBgNVBAsM
|
|
||||||
F3J1aWR5QG1vb25zdG9uZSAoUnVpZHkpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
|
||||||
MIIBCgKCAQEAv6wTfi7TqOn54LB/pwF0T7CdRwdoo1Lokyee/olOBRsLAYpmeRSN
|
|
||||||
BPmrtPm8UB2z/xgpgo7UfZ2c9wjDP5rkOk8XA/LJtdpPTsLlex+kZPsD4TNwqO1J
|
|
||||||
JYjoOFI/25eCGu3SQ8L4J/JXxrpvgiNw/s/CR75sB8pk8mL9CD7WJtU07NM48A+1
|
|
||||||
ODTINoO73ZOqO4XTftHImyb3PxoyTos0LS2igfPysXRw/bmUMzkpaZyqX29ZfS+E
|
|
||||||
vK/vNaR01IMZ/aqGuwHm339E2EZHkJfM0ttiu4jOsS41CsjqxyLSWC3V8NW688T+
|
|
||||||
QUchi1XUR6eROKjmmEVT53B69S5rWhAPWQIDAQABo3YwdDAOBgNVHQ8BAf8EBAMC
|
|
||||||
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUz7wYgdxcbzkHFwRB
|
|
||||||
Nx6SdbPNcpYwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAA
|
|
||||||
AAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBgQBTuPUOJMPqaTplBB7q+CmMjSL1ZJjz
|
|
||||||
hz2BUZZOjmEBqedZYKxhhtAt4XJGAlcU5Koyf12j+stGRAEk/FSanE0z0xDbeZut
|
|
||||||
xDwptcVXMLOpBmokvC5f0jKfNwtB89uoDkTdIxAjo3ftLOORqyeJGmiwLjXnYSxg
|
|
||||||
VS5bET/91Nnkj1hLdiA8QzHYoC7hr25lipUwAL67gElrSzxdNduHPGjv5DrZFrbX
|
|
||||||
sklxgtGmLh0yDk42zinL6bJAVuuXIF+tiQ9fXxKhdnR90tPyQk4PM7Mvzvr00udZ
|
|
||||||
wuoKZ9L1Zd2G4lKu2Kg9UCCIP3RFi4Al6/5oCW1LS+ZvNLnbY/Rf11dUtHvl8ao4
|
|
||||||
yoLJ+viNALDbMk/9YPt6xEOq+gYgqiDXzJycdX8yw/J7K5iG99ov1dC7Q/F14yxd
|
|
||||||
3h3jwQ7uAoxPy1IADvnvSfmxKS8n7S+9wTKA37vPG1E/4bF0vAx+bNiJM7AF5Jy2
|
|
||||||
cR3PXtrjoU/ymIdky2nNkAOhdBxH4I0goU4=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
28
key.pem
28
key.pem
|
|
@ -1,28 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/rBN+LtOo6fng
|
|
||||||
sH+nAXRPsJ1HB2ijUuiTJ57+iU4FGwsBimZ5FI0E+au0+bxQHbP/GCmCjtR9nZz3
|
|
||||||
CMM/muQ6TxcD8sm12k9OwuV7H6Rk+wPhM3Co7UkliOg4Uj/bl4Ia7dJDwvgn8lfG
|
|
||||||
um+CI3D+z8JHvmwHymTyYv0IPtYm1TTs0zjwD7U4NMg2g7vdk6o7hdN+0cibJvc/
|
|
||||||
GjJOizQtLaKB8/KxdHD9uZQzOSlpnKpfb1l9L4S8r+81pHTUgxn9qoa7Aebff0TY
|
|
||||||
RkeQl8zS22K7iM6xLjUKyOrHItJYLdXw1brzxP5BRyGLVdRHp5E4qOaYRVPncHr1
|
|
||||||
LmtaEA9ZAgMBAAECggEAKuwJ4XPzChx7TIm5zFhTB5VxYvqRwCFM28c/n5h31ETe
|
|
||||||
DhZEJFEp0zdl8D0MopREPpfWk9oQU6D8CYX400GIYlZbvRKdgQ/GOskCQrQvSD29
|
|
||||||
/KxcSjf47WLfPd2NV0tyYz1mnK4IjEmExofxeJ8fKrCzvQUzg/sa5/yq1THdGwy3
|
|
||||||
7Sf+i1xugni0dHPzEFdBPv27GqVtIulKvzYZEP4hEH9r+J7kqfcWdWuR8X3dOMoJ
|
|
||||||
Ow3Dd9geZV8px3HvCtk7uYYeR0YtqJjlyns/xcfqCg84P91BNtdZmvzd0gYPcedw
|
|
||||||
gq1PfnTlfwA9vcvQ9xZyPaolNsKWY4dYtbFa2+FVQQKBgQDHRMney+YhFPRUGzSI
|
|
||||||
gj0B2PWgfzajnQPfxKJo+UrbBPmPl6vFKChOFDmE7OaiFAZxC6Wtf7l9amWryGfy
|
|
||||||
mHWOK5YvzqJktsl8M8bDfYMc5sT3CjJ8LmmRZBYmVilzTIs+KFQSm7CLQimK7st6
|
|
||||||
9jZ5Jr0TSJPgIF0oX/kD532rNQKBgQD2PaIFueoxAh94IiPhKIC0Izn99rxKdJIS
|
|
||||||
p7yCP9hHmRbCvo24TSggLpxUQy+YLF0LQfF3+wazSa5Dlyzn4Z8MpQsQJf+Nn49d
|
|
||||||
1GogNdVnz7zKdiSOBrNT2+SAgEGzeOuam2aeBYfbGc8ClfRbXFY1O8dacoOFZLvk
|
|
||||||
UngHRxl0FQKBgEXcF+nEEioG7qGN6Bzzi+/Z4EMkVkOGCJWqmkWzIEsos1VkFxRH
|
|
||||||
v9mfvYqcgz7PRnLbYnoxDRaq9noDCG5EhPIS1dRs3QHyEzE39bcTiaPYQ7VYHdrr
|
|
||||||
apdOP3WxP1bsvuRLvy3G513rFCVhEnVJXE+dBSx9hVpN/lrpSqPTxHsBAoGBAKWE
|
|
||||||
ACiBM14F40lJCX0LUJfFJfIDq8UQXi+T+C5E5AgcayQ0GebMVjBrpEEveAp9p30t
|
|
||||||
sjXH0lUpLa3ZLCIhkltZhPox0HlNy3xyj3NfPDcvd+NPTnr5Hgk8QXfuvjr5CMU/
|
|
||||||
t9Z4OT2ZN4CxczMF+hyGNhYh6y367CEpHe0maYGRAoGBAIJgOPxtPhk/uNd1wQ4U
|
|
||||||
UkBzRR+tGpTqeA24j9t1KITJBD+AMIfhR7/3Py8X+vQQq0z7TkfzHSnQdF9Zn9m9
|
|
||||||
DzihbA39nM+MVMRJijgYrl8U+b98G2QIE8V6Rd2NIPsMeNLxDX3sM8kJ45dev62Q
|
|
||||||
1S3db2Lkv0wtq1bGbMgRw+LI
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
36
package.json
36
package.json
|
|
@ -9,26 +9,26 @@
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^4.16.6",
|
"antd": "^4.23.1",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.27.2",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.8.0",
|
"react-hook-form": "^7.35.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "^5.0.0"
|
"react-scripts": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/jest": "^26.0.15",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^18.7.16",
|
||||||
"@types/react": "^17.0.11",
|
"@types/react": "^18.0.19",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.7.1",
|
||||||
"typescript": "^4.3.4",
|
"typescript": "^4.8.3",
|
||||||
"web-vitals": "^1.0.1"
|
"web-vitals": "^3.0.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,7 @@
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<meta name="theme-color" content="#000000"/>
|
<meta name="theme-color" content="#000000"/>
|
||||||
<meta
|
<meta name="description" content="Web site created using create-react-app"/>
|
||||||
name="description"
|
|
||||||
content="Web site created using create-react-app"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||||
<title>Melon | Property Management</title>
|
<title>Melon | Property Management</title>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from '@testing-library/react';
|
||||||
import App from "./App";
|
import App from './App';
|
||||||
|
|
||||||
test("renders learn react link", () => {
|
test('renders learn react link', () => {
|
||||||
render(<App />);
|
render(<App />);
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
expect(linkElement).toBeInTheDocument();
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Router from "./Router";
|
import Router from './Router';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return <Router />;
|
return <Router />;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import BillPage from './pages/bill';
|
import BillPage from './pages/bill';
|
||||||
import BillsPage from './pages/bills';
|
import BillsPage from './pages/bills';
|
||||||
import HomePage from './pages/home';
|
import HomePage from './pages/home';
|
||||||
|
|
@ -6,55 +6,57 @@ import NewBillPage from './pages/newBill';
|
||||||
import NotFoundPage from './pages/notFound';
|
import NotFoundPage from './pages/notFound';
|
||||||
import ReportPage from './pages/report';
|
import ReportPage from './pages/report';
|
||||||
|
|
||||||
type RouteConfig = {
|
type Params = {
|
||||||
path: string;
|
path: string;
|
||||||
component: ({ ...props }: any) => JSX.Element;
|
component: ({ ...props }: any) => JSX.Element;
|
||||||
exact?: boolean;
|
};
|
||||||
|
|
||||||
|
export const AppRoutes = {
|
||||||
|
home: '/',
|
||||||
|
bills: '/bills',
|
||||||
|
reports: '/reports',
|
||||||
|
catchAll: '*'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Router() {
|
export default function Router() {
|
||||||
const routes: RouteConfig[] = [
|
const routes: Params[] = [
|
||||||
// Home
|
// Home
|
||||||
{
|
{
|
||||||
path: '/',
|
path: AppRoutes.home,
|
||||||
component: HomePage,
|
component: HomePage
|
||||||
exact: true
|
|
||||||
},
|
},
|
||||||
// Bills
|
// Bills
|
||||||
{
|
{
|
||||||
path: '/bills/new',
|
path: `${AppRoutes.bills}/new`,
|
||||||
component: NewBillPage,
|
component: NewBillPage
|
||||||
exact: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/bills',
|
path: AppRoutes.bills,
|
||||||
component: BillsPage,
|
component: BillsPage
|
||||||
exact: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/bills/:id',
|
path: `${AppRoutes.bills}/:id`,
|
||||||
component: BillPage
|
component: BillPage
|
||||||
},
|
},
|
||||||
// Reports
|
// Reports
|
||||||
{
|
{
|
||||||
path: '/reports',
|
path: AppRoutes.reports,
|
||||||
component: ReportPage,
|
component: ReportPage
|
||||||
exact: true
|
|
||||||
},
|
},
|
||||||
// … rest
|
// … rest
|
||||||
{
|
{
|
||||||
path: '*',
|
path: AppRoutes.catchAll,
|
||||||
component: NotFoundPage
|
component: NotFoundPage
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<>
|
||||||
{routes.map(({ exact, path, component: Component }) => (
|
<Routes>
|
||||||
<Route key={path} exact={!!exact} path={path}>
|
{routes.map(({ path, component: Component }) => (
|
||||||
<Component />
|
<Route key={path} path={path} element={<Component />} />
|
||||||
</Route>
|
|
||||||
))}
|
))}
|
||||||
</Switch>
|
</Routes>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { client, Response } from '.';
|
import { client, Response } from '.';
|
||||||
import { Bill, BillFormType, billFrom } from '../types/bill';
|
import { Bill, BillFormType, billFrom } from '../types/bill';
|
||||||
|
|
||||||
|
export const billsURL = '/bills';
|
||||||
|
|
||||||
export const createBill = async (data: BillFormType) => {
|
export const createBill = async (data: BillFormType) => {
|
||||||
try {
|
try {
|
||||||
const { data: response } = await client.post<number>('/bills', data);
|
const { data: response } = await client.post<number>(billsURL, data);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -13,7 +15,7 @@ export const createBill = async (data: BillFormType) => {
|
||||||
|
|
||||||
export const updateBill = async (id: number, data: BillFormType): Promise<Response<void>> => {
|
export const updateBill = async (id: number, data: BillFormType): Promise<Response<void>> => {
|
||||||
try {
|
try {
|
||||||
const { data: response } = await client.put<void>(`/bills/${id}`, data);
|
const { data: response } = await client.put<void>(`${billsURL}/${id}`, data);
|
||||||
return { data: response };
|
return { data: response };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -23,7 +25,7 @@ export const updateBill = async (id: number, data: BillFormType): Promise<Respon
|
||||||
|
|
||||||
export const fetchOneBill = async (id: number): Promise<Response<Bill>> => {
|
export const fetchOneBill = async (id: number): Promise<Response<Bill>> => {
|
||||||
try {
|
try {
|
||||||
const { data } = await client.get<Bill>(`/bills/${id}`);
|
const { data } = await client.get<Bill>(`${billsURL}/${id}`);
|
||||||
return { data: billFrom(data) };
|
return { data: billFrom(data) };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -33,7 +35,7 @@ export const fetchOneBill = async (id: number): Promise<Response<Bill>> => {
|
||||||
|
|
||||||
export const fetchAllBills = async () => {
|
export const fetchAllBills = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await client.get<Bill[]>('/bills');
|
const { data } = await client.get<Bill[]>(billsURL);
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -43,7 +45,7 @@ export const fetchAllBills = async () => {
|
||||||
|
|
||||||
export const sendBillAsPDF = async (id: number) => {
|
export const sendBillAsPDF = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await client.post<boolean>(`/bills/${id}/send`);
|
const { data } = await client.post<boolean>(`${billsURL}/${id}/send`);
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const BASE_URL = process.env.REACT_APP_API_URL;
|
const baseURL = process.env.REACT_APP_API_URL;
|
||||||
|
|
||||||
export const client = axios.create({ baseURL: BASE_URL });
|
export const client = axios.create({ baseURL });
|
||||||
|
|
||||||
export type Response<T> = {
|
export type Response<T> = {
|
||||||
data?: T;
|
data?: T;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,27 @@
|
||||||
import { client, Response } from '.';
|
import { client, Response } from '.';
|
||||||
import { Report, ReportFormType, ReportType } from '../types/report';
|
import { Report, ReportFormType, ReportType } from '../types/report';
|
||||||
|
|
||||||
|
export const reportsURL = '/reports';
|
||||||
|
|
||||||
export const fetchReport = async ({
|
export const fetchReport = async ({
|
||||||
type,
|
type,
|
||||||
year,
|
year,
|
||||||
month
|
month
|
||||||
}: ReportFormType): Promise<Response<Report>> => {
|
}: ReportFormType): Promise<Response<Report>> => {
|
||||||
const baseQueryURL = `/reports/?report_type=${type}&year=${year}`;
|
const params =
|
||||||
const queryURL =
|
|
||||||
type === ReportType.monthly && month
|
type === ReportType.monthly && month
|
||||||
? baseQueryURL.concat(`&month=${month + 1}`)
|
? {
|
||||||
: baseQueryURL;
|
report_type: type,
|
||||||
|
month: month + 1,
|
||||||
|
year
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
report_type: type,
|
||||||
|
year
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await client.get<Report>(queryURL);
|
const { data } = await client.get<Report>(reportsURL, { params });
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Form, Select } from "antd";
|
import { Form, Select } from 'antd';
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from 'react-hook-form';
|
||||||
|
|
||||||
type InputSelectProps = {
|
type InputSelectProps = {
|
||||||
control: any;
|
control: any;
|
||||||
|
|
@ -20,11 +20,11 @@ export const InputSelect = ({ control, name, label, placeholder, options }: Inpu
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
optionFilterProp="children"
|
optionFilterProp="children"
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option?.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
option?.label!.toLowerCase().indexOf(input.toLowerCase())! >= 0
|
||||||
}
|
}
|
||||||
options={options.map((label, value) => ({
|
options={options.map((label, value) => ({
|
||||||
value,
|
value,
|
||||||
label,
|
label
|
||||||
}))}
|
}))}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
@import "antd/dist/antd.css";
|
@import 'antd/dist/antd.css';
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>
|
||||||
document.getElementById('root')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
reportWebVitals();
|
reportWebVitals();
|
||||||
export { enumToList } from './lib/enums';
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { Layout, Menu, Row } from 'antd';
|
import { Layout, Menu, Row } from 'antd';
|
||||||
import { Link, useRouteMatch } from 'react-router-dom';
|
import { Link, useMatch } from 'react-router-dom';
|
||||||
|
import { AppRoutes } from '../Router';
|
||||||
|
|
||||||
const { Header, Content, Footer } = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
|
|
||||||
export const withLayout =
|
export const withLayout =
|
||||||
(Component: (arg: any) => JSX.Element) =>
|
(Component: (arg: any) => JSX.Element) =>
|
||||||
({ ...props }: any) => {
|
({ ...props }: any) => {
|
||||||
const { url } = useRouteMatch();
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ label: 'Home', path: '/' },
|
{ label: 'Home', path: AppRoutes.home },
|
||||||
{ label: 'Bills', path: '/bills' }
|
{ label: 'Bills', path: AppRoutes.bills },
|
||||||
|
{ label: 'Reports', path: AppRoutes.reports }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -22,7 +23,7 @@ export const withLayout =
|
||||||
theme="dark"
|
theme="dark"
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
selectedKeys={[
|
selectedKeys={[
|
||||||
(menuItems.findIndex((item) => url.includes(item.path)) + 1).toString()
|
(menuItems.findIndex((item) => useMatch(item.path)) + 1).toString()
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{menuItems.map(({ label, path }, index) => (
|
{menuItems.map(({ label, path }, index) => (
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Button, Result } from 'antd';
|
import { Button, Result } from 'antd';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export function BillSent() {
|
export function BillSent() {
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status="success"
|
status="success"
|
||||||
title="Bill sent"
|
title="Bill sent"
|
||||||
subTitle="The bill is on its way to your customer"
|
subTitle="The bill is on its way to your customer"
|
||||||
extra={[
|
extra={[
|
||||||
<Button key="back" type="primary" onClick={() => history.push('/')}>
|
<Button key="back" type="primary" onClick={() => navigate('/')}>
|
||||||
Go Back Home
|
Go Back Home
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button, Col, Divider, message, PageHeader, Space, Typography } from 'antd';
|
import { Button, Col, Divider, message, PageHeader, Space, Typography } from 'antd';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { fetchOneBill, sendBillAsPDF, updateBill } from '../../api/bills';
|
import { fetchOneBill, sendBillAsPDF, updateBill } from '../../api/bills';
|
||||||
import { BillForm } from '../../components/billForm';
|
import { BillForm } from '../../components/billForm';
|
||||||
import { withLayout } from '../../layouts/main';
|
import { withLayout } from '../../layouts/main';
|
||||||
|
|
@ -13,7 +13,7 @@ export type QueryParams = { id: string };
|
||||||
const BillPage = () => {
|
const BillPage = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
let { id } = useParams<QueryParams>();
|
let { id } = useParams<QueryParams>();
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const { handleSubmit, control, reset } = useForm<BillFormType>();
|
const { handleSubmit, control, reset } = useForm<BillFormType>();
|
||||||
|
|
||||||
// Local State
|
// Local State
|
||||||
|
|
@ -36,7 +36,7 @@ const BillPage = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadBill(id);
|
loadBill(id!);
|
||||||
}, [id, loadBill, reset]);
|
}, [id, loadBill, reset]);
|
||||||
|
|
||||||
// Logic
|
// Logic
|
||||||
|
|
@ -47,12 +47,12 @@ const BillPage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
const { error } = await updateBill(parseInt(id), data);
|
const { error } = await updateBill(parseInt(id!), data);
|
||||||
if (error) {
|
if (error) {
|
||||||
message.error(`Update failed because of ${error}`);
|
message.error(`Update failed because of ${error}`);
|
||||||
}
|
}
|
||||||
setEdit(() => false);
|
setEdit(() => false);
|
||||||
loadBill(id);
|
loadBill(id!);
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = edit ? (
|
const content = edit ? (
|
||||||
|
|
@ -64,7 +64,9 @@ const BillPage = () => {
|
||||||
<Typography.Text>{bill.name}</Typography.Text>
|
<Typography.Text>{bill.name}</Typography.Text>
|
||||||
<Typography.Text>{bill.price} €</Typography.Text>
|
<Typography.Text>{bill.price} €</Typography.Text>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
|
<>
|
||||||
from {bill.start} to {bill.end}
|
from {bill.start} to {bill.end}
|
||||||
|
</>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
|
@ -72,7 +74,7 @@ const BillPage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
onBack={() => history.goBack()}
|
onBack={() => navigate(-1)}
|
||||||
title={`Facture #VFNI${bill.id?.toString().padStart(4, '0')}`}
|
title={`Facture #VFNI${bill.id?.toString().padStart(4, '0')}`}
|
||||||
subTitle={bill.name}
|
subTitle={bill.name}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import { Button, Row } from 'antd';
|
import { Button, Row } from 'antd';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { withLayout } from '../layouts/main';
|
import { withLayout } from '../layouts/main';
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
// Hooks
|
// Hooks
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Rent it Like a Pro</h1>
|
<h1>Rent it Like a Pro</h1>
|
||||||
<Row>
|
<Row>
|
||||||
<Button type="primary" size="large" onClick={() => history.push('/bills/new')}>
|
<Button type="primary" size="large" onClick={() => navigate('/bills/new')}>
|
||||||
Add Bill
|
Add Bill
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="default" size="large" onClick={() => history.push('/reports')}>
|
<Button type="default" size="large" onClick={() => navigate('/reports')}>
|
||||||
Edit Report
|
Edit Report
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { createBill } from '../../api/bills';
|
import { createBill } from '../../api/bills';
|
||||||
import { BillForm } from '../../components/billForm';
|
import { BillForm } from '../../components/billForm';
|
||||||
import { withLayout } from '../../layouts/main';
|
import { withLayout } from '../../layouts/main';
|
||||||
|
|
@ -8,12 +8,12 @@ import { BillFormType } from '../../types/bill';
|
||||||
const NewBillPage = () => {
|
const NewBillPage = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { handleSubmit, control } = useForm<BillFormType>();
|
const { handleSubmit, control } = useForm<BillFormType>();
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Logic
|
// Logic
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
const newId = await createBill(data);
|
const newId = await createBill(data);
|
||||||
history.push(`/bills/${newId}`);
|
navigate(`/bills/${newId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { Button, Image, Row } from 'antd';
|
import { Button, Image, Row } from 'antd';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import NotFoundImage from '../assets/notFound/404.png';
|
import NotFoundImage from '../assets/notFound/404.png';
|
||||||
import { withLayout } from '../layouts/main';
|
import { withLayout } from '../layouts/main';
|
||||||
|
|
||||||
function NotFoundPage() {
|
function NotFoundPage() {
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row justify="center">
|
<Row justify="center">
|
||||||
<Button size="large" type="link" onClick={() => history.push('/')}>
|
<Button size="large" type="link" onClick={() => navigate('/')}>
|
||||||
Go Back to Safety
|
Go Back to Safety
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ReportHandler } from "web-vitals";
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
if (onPerfEntry) {
|
if (onPerfEntry) {
|
||||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
getCLS(onPerfEntry);
|
getCLS(onPerfEntry);
|
||||||
getFID(onPerfEntry);
|
getFID(onPerfEntry);
|
||||||
getFCP(onPerfEntry);
|
getFCP(onPerfEntry);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1 @@
|
||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -23,10 +19,6 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src"
|
"exclude": ["node_modules"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue