Skip to content

Commit

Permalink
Merge pull request #47 from GEWIS/develop
Browse files Browse the repository at this point in the history
Upgrade
Yoronex authored Oct 23, 2023
2 parents 74cf789 + cd9ce97 commit 559f38b
Showing 80 changed files with 6,822 additions and 28,848 deletions.
21 changes: 12 additions & 9 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
plugins: ['@typescript-eslint', 'import', 'jsx-a11y', 'react', 'react-hooks'],
extends: ['airbnb-typescript'],
parserOptions: {
project: './tsconfig.json'
@@ -11,13 +11,16 @@ module.exports = {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'arrow-body-style': ['off'],
'react/destructuring-assignment': ['off'],
"no-plusplus": "off",
"import/prefer-default-export": "off",
"class-methods-use-this": "off",
"@typescript-eslint/no-unused-vars": ["warn"],
"react/prefer-stateless-function": "off",
"react/prop-types": "off",
"unicode-bom": "off",
"react/static-property-placement": ["off"],
'no-plusplus': 'off',
'import/prefer-default-export': 'off',
'default-param-last': 'off',
'@typescript-eslint/default-param-last': ['off'],
'class-methods-use-this': 'off',
'@typescript-eslint/no-unused-vars': ['warn'],
'react/prefer-stateless-function': 'off',
'react/prop-types': 'off',
'unicode-bom': 'off',
'react/static-property-placement': ['off'],
"react-hooks/rules-of-hooks": "error"
}
};
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# build environment
FROM node:16-alpine as build
FROM node:20 as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package*.json ./
RUN npm ci --silent
RUN npm install react-scripts@3.4.1 -g --silent
COPY . ./
RUN npm run build

3 changes: 0 additions & 3 deletions craco.config.js

This file was deleted.

5 changes: 3 additions & 2 deletions public/index.html → index.html
Original file line number Diff line number Diff line change
@@ -22,9 +22,9 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="/manifest.json" />
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="%PUBLIC_URL%/roboto-slab/robotoslab.css" rel="stylesheet">
<link href="/roboto-slab/robotoslab.css" rel="stylesheet">

<!--
Notice the use of %PUBLIC_URL% in the tags above.
@@ -39,6 +39,7 @@
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/src/index.tsx"></script>
<div id="root">Loading... please wait</div>
<!--
This HTML file is a template.
33,614 changes: 5,624 additions & 27,990 deletions package-lock.json

Large diffs are not rendered by default.

115 changes: 61 additions & 54 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
{
"name": "parelpracht-client",
"version": "0.1.0",
"version": "1.0410",
"private": true,
"proxy": "http://localhost:3001",
"author": "The 39th board of Study Association GEWIS",
"dependencies": {
"chart.js": "^2.9.4",
"chartjs-plugin-annotation": "^0.5.7",
"connected-react-router": "^6.9.1",
"@lagunovsky/redux-react-router": "^4.3.0",
"@redux-devtools/extension": "^3.2.5",
"@reduxjs/toolkit": "^1.9.7",
"@vitejs/plugin-react-swc": "^3.4.0",
"chart.js": "^4.4.0",
"chartjs-plugin-annotation": "^3.0.1",
"file-saver": "^2.0.5",
"history": "^4.10.1",
"i18next": "^20.3.5",
"i18next-browser-languagedetector": "^6.1.2",
"javascript-time-ago": "^2.3.8",
"jose": "^4.11.1",
"history": "^5.3.0",
"i18next": "^23.5.1",
"i18next-browser-languagedetector": "^7.1.0",
"javascript-time-ago": "^2.5.9",
"jose": "^4.15.2",
"lodash": "^4.17.21",
"marked": "^2.1.3",
"query-string": "^7.0.1",
"react": "^17.0.2",
"react-chartjs-2": "^2.11.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.11.4",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.1",
"marked": "^9.0.3",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.2.2",
"react-redux": "^8.1.3",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-router-redux": "^5.0.0-alpha.9",
"react-semantic-ui-datepickers": "^2.17.2",
"redux": "^4.1.1",
"redux-devtools-extension": "^2.13.9",
"redux-saga": "^1.1.3",
"semantic-ui-less": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"validator": "^13.6.0"
"redux": "^4.2.1",
"redux-saga": "^1.2.3",
"semantic-ui-less": "^2.5.0",
"semantic-ui-react": "^2.1.4",
"validator": "^13.11.0",
"vite": "^4.4.10",
"vite-plugin-svgr": "^4.1.0",
"vite-tsconfig-paths": "^4.2.1"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"eject": "craco eject",
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
@@ -58,32 +64,33 @@
]
},
"devDependencies": {
"@craco/craco": "^7.0.0",
"@semantic-ui-react/craco-less": "^3.0.0",
"@types/chartjs-plugin-annotation": "^0.5.2",
"@types/file-saver": "^2.0.3",
"@types/javascript-time-ago": "^2.0.3",
"@types/jest": "^26.0.24",
"@types/jsonwebtoken": "^8.5.4",
"@types/lodash": "^4.14.171",
"@types/marked": "^2.0.4",
"@types/node": "^14.17.6",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"@types/react-redux": "^7.1.18",
"@types/react-router-dom": "^5.1.8",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"eslint": "^7.31.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-plugin-flowtype": "^5.8.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"sass": "^1.37.5",
"sass-loader": "^12.1.0",
"typescript": "^4.3.5"
"@rollup/plugin-alias": "^5.0.0",
"@types/chart.js": "^2.9.38",
"@types/chartjs-plugin-annotation": "^0.5.3",
"@types/file-saver": "^2.0.5",
"@types/javascript-time-ago": "^2.0.4",
"@types/jest": "^29.5.5",
"@types/jsonwebtoken": "^9.0.3",
"@types/lodash": "^4.14.199",
"@types/marked": "^5.0.2",
"@types/node": "^20.8.2",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.10",
"@types/react-redux": "^7.1.27",
"@types/react-router-dom": "^5.3.3",
"@types/validator": "^13.11.2",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"eslint": "^8.50.0",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"less": "^4.2.0",
"redux-immutable-state-invariant": "^2.1.0",
"sass": "^1.68.0",
"sass-loader": "^13.3.2"
}
}
9 changes: 5 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { ReduxRouter, ReduxRouterSelector } from '@lagunovsky/redux-react-router';
import './App.scss';
import './Form.scss';

import store, { history } from './stores/store';
import store, { history, RootState } from './stores/store';
import Routes from './Routes';
import { showAlert } from './stores/alerts/actionCreators';
import AlertContainer from './components/alerts/AlertContainer';
@@ -42,11 +42,12 @@ class App extends React.Component<{}, State> {
<AlertContainer />
);
}
const routerSelector: ReduxRouterSelector<RootState> = (state) => state.router;
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<ReduxRouter history={history} routerSelector={routerSelector}>
<Routes />
</ConnectedRouter>
</ReduxRouter>
</Provider>
);
}
544 changes: 351 additions & 193 deletions src/Routes.tsx

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/WithRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Location as HLocation } from 'history';
import { NavigateFunction } from 'react-router/dist/lib/hooks';

export interface WithRouter {
router: {
location: HLocation,
navigate: NavigateFunction,
params: Readonly<any>,
}
}

// export function withRouter<ComponentProps>(Component: React.ComponentType<ComponentProps>) {
// function ComponentWithRouterProp(props: ComponentProps) {
// const location = useLocation();
// const navigate = useNavigate();
// const params = useParams();
//
// return <Component {...props} router={{ location, navigate, params }} />;
// }
//
// return ComponentWithRouterProp;
// }

export function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}

return ComponentWithRouterProp;
}
4 changes: 2 additions & 2 deletions src/components/Version.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Modal, Segment } from 'semantic-ui-react';
import marked from 'marked';
import { marked } from 'marked';
import releaseNotes from '../changelog.md';

export const version = 'v1.3.2';
@@ -20,7 +20,7 @@ class VersionModal extends React.Component<Props, State> {

async componentDidMount() {
const rawLog = await fetch(releaseNotes);
const changelog = await marked(await rawLog.text());
const changelog = marked(await rawLog.text());
this.setState({
changelog,
});
4 changes: 2 additions & 2 deletions src/components/activities/ActivitiesList.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import React from 'react';
import {
Button, Feed, Icon, Loader,
} from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { WithTranslation, withTranslation } from 'react-i18next';
import ActivityComponent from './ActivityComponent';
import { GeneralActivity } from './GeneralActivity';
@@ -11,8 +10,9 @@ import CreateCommentRow from './CreateCommentRow';
import ResourceStatus from '../../stores/resourceStatus';
import AuthorizationComponent from '../AuthorizationComponent';
import { Roles } from '../../clients/server.generated';
import { withRouter } from '../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation {
activities: GeneralActivity[];
componentId: number;
componentType: SingleEntities;
4 changes: 2 additions & 2 deletions src/components/activities/ActivityComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Feed, Popup, Segment,
} from 'semantic-ui-react';
@@ -22,8 +21,9 @@ import {
import UserAvatar from '../entities/user/UserAvatar';
import AuthorizationComponent from '../AuthorizationComponent';
import { getLanguage } from '../../localization';
import { withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
activity: GeneralActivity;
componentId: number;
componentType: SingleEntities;
4 changes: 2 additions & 2 deletions src/components/activities/DocumentStatusModal.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import * as React from 'react';
import {
Dimmer, Loader, Modal, Segment,
} from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
ContractStatus,
ContractStatusParams,
@@ -14,8 +13,9 @@ import AlertContainer from '../alerts/AlertContainer';
import { SingleEntities } from '../../stores/single/single';
import DocumentStatusProps from './DocumentStatusProps';
import { DocumentStatus } from './DocumentStatus';
import { withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
resourceStatus: ResourceStatus;
close: () => void;

4 changes: 2 additions & 2 deletions src/components/activities/FinancialDocumentProgress.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import {
Button, Grid, Icon, Popup, Step,
} from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import { GeneralActivity } from './GeneralActivity';
import FinancialDocumentStep from './FinancialDocumentStep';
@@ -24,8 +23,9 @@ import ResourceStatus from '../../stores/resourceStatus';
import { Roles } from '../../clients/server.generated';
import AuthorizationComponent from '../AuthorizationComponent';
import { getLanguage } from '../../localization';
import { withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
documentId: number;
// If the document is a ProductInstance, the parentId is the contract ID
parentId?: number;
4 changes: 2 additions & 2 deletions src/components/activities/FinancialDocumentStep.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { Step } from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
formatStatus,
} from '../../helpers/activity';
@@ -12,11 +11,12 @@ import ResourceStatus from '../../stores/resourceStatus';
import { Roles } from '../../clients/server.generated';
import { RootState } from '../../stores/store';
import { authedUserHasRole } from '../../stores/auth/selectors';
import { withRouter } from '../../WithRouter';

/**
* Definition of used variables
*/
interface Props extends RouteComponentProps {
interface Props {
documentId: number;
documentType: SingleEntities;
// If the document is a ProductInstance, the parentId is the contract ID
18 changes: 9 additions & 9 deletions src/components/alerts/AlertContainer.tsx
Original file line number Diff line number Diff line change
@@ -20,16 +20,16 @@ class AlertContainer extends React.Component<AlertsProps> {
public render() {
return (
<Rail
internal={this.props.internal}
position="right"
style={{ marginTop: this.props.internal ? '2em' : undefined, height: 'auto', maxHeight: 'calc(100% - 2em)' }}
internal={this.props.internal}
position="right"
style={{ marginTop: this.props.internal ? '2em' : undefined, height: 'auto', maxHeight: 'calc(100% - 2em)' }}
>
{this.props.alerts.map((alert) => (
<AlertItem
key={alert.id}
alert={alert}
/>
))}
{this.props.alerts.map((alert) => (
<AlertItem
key={alert.id}
alert={alert}
/>
))}
</Rail>
);
}
76 changes: 42 additions & 34 deletions src/components/chart/CategoryLineChart.tsx
Original file line number Diff line number Diff line change
@@ -3,12 +3,14 @@ import { Dropdown, Grid } from 'semantic-ui-react';
import { withTranslation, WithTranslation } from 'react-i18next';
import { Line } from 'react-chartjs-2';
import { connect } from 'react-redux';
import { ChartOptions } from 'chart.js';
import { ChartData, ChartOptions } from 'chart.js';
import { formatPriceFull } from '../../helpers/monetary';
import { randomColorSet } from '../../helpers/colors';
import { ProductsPerCategory } from '../../clients/server.generated';
import { RootState } from '../../stores/store';
import { getCategoryName } from '../../stores/productcategory/selectors';
import { TooltipItem } from 'chart.js/dist/types';
import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';

export enum DataSet {
VALUES,
@@ -31,7 +33,7 @@ class CategoryLineChart extends React.Component<Props, State> {
extraDropdown: undefined,
};

private chartReference: React.RefObject<Line>;
private chartReference: React.RefObject<ChartJSOrUndefined<'line'>>;

constructor(props: Props) {
super(props);
@@ -45,7 +47,7 @@ class CategoryLineChart extends React.Component<Props, State> {
this.setState({ dataSetSelection: newDataset });
};

createLineChartDataObject(): object {
createLineChartDataObject(): ChartData<'line'> {
const {
data, labels, getCatName,
} = this.props;
@@ -58,23 +60,22 @@ class CategoryLineChart extends React.Component<Props, State> {
default: throw new Error();
}

const result = {
const result: ChartData<'line'> = {
labels,
datasets: [] as object[],
datasets: [],
};

data.forEach((c, i) => {
// @ts-ignore
result.datasets.push({
label: getCatName(c.categoryId),
data: c[valueArray],
fill: false,
lineTension: 0,
backgroundColor: randomColorSet(i),
borderColor: randomColorSet(i),
pointBackgroundColor: randomColorSet(i),
});
});

return result;
}

@@ -84,26 +85,33 @@ class CategoryLineChart extends React.Component<Props, State> {
const { dataSetSelection } = this.state;
const chartData = this.createLineChartDataObject();

let options: ChartOptions = {};
let options: ChartOptions<'line'> = {};
switch (dataSetSelection) {
case DataSet.VALUES:
options = {
scales: {
yAxes: [{
y: {
beginAtZero: true,
ticks: {
beginAtZero: true,
callback(value: number) {
callback(value: number | string) {
// First case should never apply
if (typeof value === 'string') return formatPriceFull(parseInt(value));
return formatPriceFull(value);
},
},
}],
},
},
tooltips: {
callbacks: {
label(tooltipItem: any, data: any) {
const value = formatPriceFull(tooltipItem.yLabel);
const { label } = data.datasets[tooltipItem.datasetIndex];
return ` ${label}: ${value}`;
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label(tooltipItem: TooltipItem<'line'>) {
const value = formatPriceFull(tooltipItem.raw as number);
const { label } = tooltipItem.dataset;
return ` ${label}: ${value}`;
},
},
},
},
@@ -112,22 +120,27 @@ class CategoryLineChart extends React.Component<Props, State> {
case DataSet.AMOUNTS:
options = {
scales: {
yAxes: [{
y: {
beginAtZero: true,
ticks: {
beginAtZero: true,
callback(value: number) {
callback(value: number | string ) {
return value;
},
precision: 0,
},
}],
},
},
tooltips: {
callbacks: {
label(tooltipItem: any, data: any) {
const value = tooltipItem.yLabel;
const { label } = data.datasets[tooltipItem.datasetIndex];
return ` ${label}: ${value}`;
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label(tooltipItem: TooltipItem<'line'>) {
const value = tooltipItem.raw;
const { label } = tooltipItem.dataset;
return ` ${label}: ${value}`;
},
},
},
},
@@ -163,12 +176,7 @@ class CategoryLineChart extends React.Component<Props, State> {
<Line
ref={this.chartReference}
data={chartData}
options={{
legend: {
display: false,
},
...options,
}}
options={options}
redraw
/>
</div>
8 changes: 5 additions & 3 deletions src/components/dashboard/DashboardContractsRow.tsx
Original file line number Diff line number Diff line change
@@ -3,15 +3,16 @@ import { connect } from 'react-redux';
import {
Button, Header, Icon, Segment, Image, Table,
} from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import { RecentContract } from '../../clients/server.generated';
import { RootState } from '../../stores/store';
import { formatActivityDate } from '../../helpers/activity';
import { getUserName } from '../../stores/user/selectors';
import { getCompanyLogo, getCompanyName } from '../../stores/company/selectors';
import { useNavigate } from 'react-router-dom';
import { withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
contract: RecentContract;
company: string;
user: string;
@@ -41,7 +42,8 @@ class DashboardContractsRow extends React.Component<Props> {
horizontal
style={{ margin: 0, marginTop: '0.2em' }}
onClick={() => {
this.props.history.push(`./contract/${contract.id}`);
const history = useNavigate();
history(`./contract/${contract.id}`);
}}
>
<Segment
53 changes: 30 additions & 23 deletions src/components/dashboard/FinancialOverview.tsx
Original file line number Diff line number Diff line change
@@ -3,15 +3,17 @@ import {
Dropdown, Grid, Popup, Segment, Table,
} from 'semantic-ui-react';
import { WithTranslation, withTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Bar } from 'react-chartjs-2';
import { Client, DashboardProductInstanceStats } from '../../clients/server.generated';
import { dateToFinancialYear } from '../../helpers/timestamp';
import { formatPriceFull } from '../../helpers/monetary';
import './FinancialOverview.scss';
import { FinancialOverviewField } from './FinancialOverviewField';

interface Props extends RouteComponentProps, WithTranslation {}
import { ChartData } from 'chart.js';
import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
import { useNavigate } from 'react-router-dom';
import { withRouter } from '../../WithRouter';
interface Props extends WithTranslation {}

interface State {
data?: DashboardProductInstanceStats;
@@ -21,7 +23,8 @@ interface State {
}

class FinancialOverview extends React.Component<Props, State> {
private readonly chart: React.RefObject<Bar>;
private readonly chart: React.RefObject<ChartJSOrUndefined<'bar'>>;


constructor(props: Props) {
super(props);
@@ -44,7 +47,7 @@ class FinancialOverview extends React.Component<Props, State> {
}

componentWillUnmount() {
this.chart.current?.chartInstance.destroy();
this.chart.current?.destroy();
}

// goToInsightsTable = (e: MouseEvent | undefined, data: any[]) => {
@@ -63,7 +66,8 @@ class FinancialOverview extends React.Component<Props, State> {

goToInsightsTable = (status: 'suggested' | 'contracted' | 'delivered' | 'invoiced') => {
const { financialYear } = this.state;
this.props.history.push(`/insights#${status}&${financialYear}`);
const history = useNavigate();
history(`/insights#${status}&${financialYear}`);
};

async updateGraph(year: number) {
@@ -76,7 +80,7 @@ class FinancialOverview extends React.Component<Props, State> {
});
}

createBarChartDataObject(): object {
createBarChartDataObject(): ChartData<'bar'> {
const { t } = this.props;
const { data } = this.state;
return {
@@ -94,8 +98,8 @@ class FinancialOverview extends React.Component<Props, State> {
borderWidth: 1,
hoverBackgroundColor: 'rgba(255, 148, 128, 0.8)',
hoverBorderColor: 'rgba(41, 48, 101, 1)',
data: [data?.suggested.amount, data?.contracted.amount, data?.delivered.amount,
data?.invoiced.delivered.amount, data?.paid.amount],
data: data ? [data?.suggested.amount, data?.contracted.amount, data?.delivered.amount,
data?.invoiced.delivered.amount, data?.paid.amount] : [],
},
{
label: t('dashboard.financialOverview.delivered'),
@@ -104,7 +108,7 @@ class FinancialOverview extends React.Component<Props, State> {
borderWidth: 1,
hoverBackgroundColor: 'rgba(255, 148, 128, 0.8)',
hoverBorderColor: 'rgba(41, 48, 101, 1)',
data: [0, 0, 0, data?.invoiced.notDelivered.amount, 0],
data: data ? [0, 0, 0, data?.invoiced.notDelivered.amount, 0] : [],
},
],
};
@@ -154,27 +158,30 @@ class FinancialOverview extends React.Component<Props, State> {
ref={this.chart}
data={chartData}
options={{
legend: {
display: false,
},
scales: {
xAxes: [{
x: {
stacked: true,
}],
yAxes: [{
},
y : {
stacked: true,
beginAtZero: true,
ticks: {
beginAtZero: true,
callback(value: number) {
callback(value: number | string) {
if (typeof value === 'string') return 'TEMP';
return formatPriceFull(value);
},
},
}],
},
},
tooltips: {
callbacks: {
label(tooltipItem: any) {
return formatPriceFull(tooltipItem.yLabel);
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label(tooltipItem: any) {
return formatPriceFull(tooltipItem.raw);
},
},
},
},
8 changes: 5 additions & 3 deletions src/components/entities/EntitySummary.tsx
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ import {
Header, Icon, Loader, Placeholder, Segment, SemanticICONS,
} from 'semantic-ui-react';
import { SingleEntities } from '../../stores/single/single';
import { formatEntity } from '../../helpers/entity';
import './EntitySummary.scss';
import { useTranslation } from 'react-i18next';

interface Props {
loading: boolean;
@@ -18,13 +18,15 @@ interface Props {
}

export function EntitySummary(props: Props) {
const { t } = useTranslation();

if (props.loading) {
return (
<>
<Header as="h1" attached="top" style={{ backgroundColor: 'rgba(238, 238, 238, 0.98)' }}>
<Icon name={props.icon} />
<Header.Content>
<Header.Subheader>{formatEntity(props.entity)}</Header.Subheader>
<Header.Subheader>{t(`entity.${props.entity.toLowerCase()}`)}</Header.Subheader>
<Loader active inline />
</Header.Content>
</Header>
@@ -44,7 +46,7 @@ export function EntitySummary(props: Props) {
</div>
<div className="name">
<Header.Content style={{ paddingLeft: '1.25rem' }}>
<Header.Subheader>{formatEntity(props.entity)}</Header.Subheader>
<Header.Subheader>{t(`entity.${props.entity.toLowerCase()}`)}</Header.Subheader>
{props.title}
</Header.Content>
</div>
9 changes: 4 additions & 5 deletions src/components/entities/company/CompanyContact.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Header, Icon, Segment,
} from 'semantic-ui-react';
import { Contact, ContactFunction } from '../../../clients/server.generated';
import { formatContactName, formatFunction } from '../../../helpers/contact';
import './CompanyContact.scss';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps {
interface Props extends WithRouter {
contact: Contact;
}

function CompanyContact(props: Props) {
const { contact } = props;
const { navigate, location } = props.router;

return (
<Segment.Group
horizontal
className="company-contact"
style={{ margin: 0, marginTop: '0.2em' }}
onClick={() => {
props.history.push(
`${props.location.pathname}/contact/${contact.id}`,
);
navigate(`${location.pathname}/contact/${contact.id}`);
}}
>
<Segment
12 changes: 7 additions & 5 deletions src/components/entities/company/CompanyContactList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Button, Icon, Loader,
} from 'semantic-ui-react';
@@ -12,8 +12,9 @@ import { RootState } from '../../../stores/store';
import AuthorizationComponent from '../../AuthorizationComponent';
import CompanyContact from './CompanyContact';
import { sortContactsByFunction } from '../../../helpers/contact';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
company: Company | undefined;
}

@@ -28,6 +29,7 @@ class CompanyContactList extends React.Component<Props, State> {

public render() {
const { company, t } = this.props;
const { location } = this.props.router;

if (company === undefined) {
return (
@@ -54,7 +56,7 @@ class CompanyContactList extends React.Component<Props, State> {
style={{ marginTop: '-0.5em' }}
basic
as={NavLink}
to={`${this.props.location.pathname}/contact/new`}
to={`${location.pathname}/contact/new`}
>
<Icon name="plus" />
{t('pages.contacts.addContact')}
@@ -79,14 +81,14 @@ class CompanyContactList extends React.Component<Props, State> {
style={{ marginTop: '-0.5em' }}
basic
as={NavLink}
to={`${this.props.location.pathname}/contact/new`}
to={`${location.pathname}/contact/new`}
>
<Icon name="plus" />
{t('pages.contacts.addContact')}
</Button>
</h3>
{contacts.map((contact) => (
<CompanyContact key={contact.id} contact={contact} />
<CompanyContact key={contact.id} contact={contact}/>
))}
</>
);
11 changes: 5 additions & 6 deletions src/components/entities/company/CompanyProps.tsx
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import {
Checkbox, Form, Input,
} from 'semantic-ui-react';
import validator from 'validator';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
Company, CompanyParams, CompanyStatus, Roles,
@@ -20,8 +19,9 @@ import CountrySelector from './CountrySelector';
import AuthorizationComponent from '../../AuthorizationComponent';
import TextArea from '../../TextArea';
import { authedUserHasRole } from '../../../stores/auth/selectors';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCancel?: () => void;

@@ -71,7 +71,6 @@ class CompanyProps extends React.Component<Props, State> {
componentDidUpdate(prevProps: Props) {
if (prevProps.status === ResourceStatus.SAVING
&& this.props.status === ResourceStatus.FETCHED) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ editing: false });
}
}
@@ -134,7 +133,8 @@ class CompanyProps extends React.Component<Props, State> {

remove = () => {
if (!this.props.create && this.props.deleteCompany) {
this.props.history.push('/company');
const { navigate } = this.props.router;
navigate('/company');
this.props.deleteCompany(this.props.company.id);
}
};
@@ -412,6 +412,5 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
),
});

export default withTranslation()(
withRouter(connect(mapStateToProps, mapDispatchToProps)(CompanyProps)),
export default withTranslation()(withRouter(connect(mapStateToProps, mapDispatchToProps)(CompanyProps)),
);
9 changes: 5 additions & 4 deletions src/components/entities/contact/ContactProps.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import {
} from 'semantic-ui-react';
import { WithTranslation, withTranslation } from 'react-i18next';
import validator from 'validator';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Contact, ContactFunction, ContactParams, Gender, Roles,
} from '../../../clients/server.generated';
@@ -23,8 +22,9 @@ import { TransientAlert } from '../../../stores/alerts/actions';
import { showTransientAlert } from '../../../stores/alerts/actionCreators';

import AuthorizationComponent from '../../AuthorizationComponent';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCompanyPage: boolean;
onCancel?: () => void;
@@ -145,11 +145,12 @@ class ContactProps extends React.Component<Props, State> {
remove = () => {
if (!this.props.create) {
this.props.deleteContact(this.props.contact.id);
const { navigate } = this.props.router;
if (this.props.onCompanyPage) {
this.props.history.push(`/company/${this.props.contact.companyId}`);
navigate(`/company/${this.props.contact.companyId}`);
this.props.fetchCompany(this.props.contact.companyId);
} else {
this.props.history.push('/contact');
navigate('/contact');
}
}
};
10 changes: 6 additions & 4 deletions src/components/entities/contract/ContractList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Button, Icon, Loader, Table,
} from 'semantic-ui-react';
@@ -11,8 +11,9 @@ import { getSingle } from '../../../stores/single/selectors';
import { SingleEntities } from '../../../stores/single/single';
import { RootState } from '../../../stores/store';
import AuthorizationComponent from '../../AuthorizationComponent';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
company: Company | undefined;
}

@@ -27,6 +28,7 @@ class ContractList extends React.Component<Props, State> {

public render() {
const { company, t } = this.props;
const { location } = this.props.router;

if (company === undefined) {
return (
@@ -48,7 +50,7 @@ class ContractList extends React.Component<Props, State> {
style={{ marginTop: '-0.5em' }}
basic
as={NavLink}
to={`${this.props.location.pathname}/contract/new`}
to={`${location.pathname}/contract/new`}
>
<Icon name="plus" />
{t('pages.contracts.addContract')}
@@ -74,7 +76,7 @@ class ContractList extends React.Component<Props, State> {
style={{ marginTop: '-0.5em' }}
basic
as={NavLink}
to={`${this.props.location.pathname}/contract/new`}
to={`${location.pathname}/contract/new`}
>
<Icon name="plus" />
{t('pages.contracts.addContract')}
8 changes: 5 additions & 3 deletions src/components/entities/contract/ContractProductList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Button, Icon, Loader, Table,
} from 'semantic-ui-react';
@@ -13,8 +13,9 @@ import { formatPriceFull } from '../../../helpers/monetary';
import ContractInvoiceModal from '../../../pages/ContractInvoiceModal';
import AuthorizationComponent from '../../AuthorizationComponent';
import { getLastStatus } from '../../../helpers/activity';
import { WithRouter, withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
contract: Contract;
}

@@ -53,6 +54,7 @@ class ContractProductList extends React.Component<Props, State> {
public render() {
const { contract, t } = this.props;
const { selected } = this.state;
const { location } = this.props.router;

if (contract === undefined) {
return (
@@ -101,7 +103,7 @@ class ContractProductList extends React.Component<Props, State> {
style={{ marginTop: '-0.5em' }}
basic
as={NavLink}
to={`${this.props.location.pathname}/product/new`}
to={`${location.pathname}/product/new`}
disabled={!canChangeProducts}
>
<Icon name="plus" />
4 changes: 2 additions & 2 deletions src/components/entities/contract/ContractProductRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Checkbox, Table } from 'semantic-ui-react';
import { ActivityType, ProductInstance, ProductInstanceStatus } from '../../../clients/server.generated';
import './ContractComponent.scss';
@@ -8,8 +7,9 @@ import ProductInstanceLink from '../product/ProductInstanceLink';
import { SingleEntities } from '../../../stores/single/single';
import { formatStatus, getLastStatus } from '../../../helpers/activity';
import InvoiceLink from '../invoice/InvoiceLink';
import { withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
productInstance: ProductInstance;

selectFunction: (id: number) => void;
7 changes: 4 additions & 3 deletions src/components/entities/contract/ContractProps.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { Form, Input, TextArea } from 'semantic-ui-react';
import validator from 'validator';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
ActivityType, Contract, ContractParams, Roles,
@@ -21,8 +20,9 @@ import { TransientAlert } from '../../../stores/alerts/actions';
import { showTransientAlert } from '../../../stores/alerts/actionCreators';
import { formatDocumentIdTitle } from '../../../helpers/documents';
import AuthorizationComponent from '../../AuthorizationComponent';
import { WithRouter, withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
companyPredefined?: boolean;
onCancel?: () => void;
@@ -123,7 +123,8 @@ class ContractProps extends React.Component<Props, State> {

remove = () => {
if (!this.props.create) {
this.props.history.push('/contract');
const { navigate } = this.props.router;
navigate('/contract');
this.props.deleteContract(this.props.contract.id);
}
};
5 changes: 3 additions & 2 deletions src/components/entities/contract/ContractRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { Table } from 'semantic-ui-react';
import { Contract, ContractStatus } from '../../../clients/server.generated';
import { getCompanyName } from '../../../stores/company/selectors';
@@ -11,8 +11,9 @@ import { getContractStatus, getContractValue } from '../../../stores/contract/se
import { formatStatus } from '../../../helpers/activity';
import CompanyLink from '../company/CompanyLink';
import { formatPriceFull } from '../../../helpers/monetary';
import { withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
contract: Contract;
contactName: string;
contractStatus: ContractStatus;
4 changes: 2 additions & 2 deletions src/components/entities/invoice/InvoiceList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { withTranslation, WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Loader, Table,
} from 'semantic-ui-react';
@@ -10,8 +9,9 @@ import { getSingle } from '../../../stores/single/selectors';
import { SingleEntities } from '../../../stores/single/single';
import { RootState } from '../../../stores/store';
import InvoiceComponent from './InvoiceComponent';
import { withRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation {
company: Company | undefined;
}

4 changes: 2 additions & 2 deletions src/components/entities/invoice/InvoiceProductList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Table } from 'semantic-ui-react';
import { withTranslation, WithTranslation } from 'react-i18next';
import InvoiceProductRow from './InvoiceProductRow';
import {
Client, Invoice, InvoiceStatus, VAT,
} from '../../../clients/server.generated';
import { formatPriceFull } from '../../../helpers/monetary';
import { withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
invoice: Invoice;

fetchInvoice: (id: number) => void;
4 changes: 2 additions & 2 deletions src/components/entities/invoice/InvoiceProductRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Icon, Table } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
@@ -13,8 +12,9 @@ import ResourceStatus from '../../../stores/resourceStatus';
import { TransientAlert } from '../../../stores/alerts/actions';
import { showTransientAlert } from '../../../stores/alerts/actionCreators';
import AuthorizationComponent from '../../AuthorizationComponent';
import { withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
productInstance: ProductInstance;
removeProduct: (id: number) => void;
canDelete: boolean;
7 changes: 4 additions & 3 deletions src/components/entities/invoice/InvoiceProps.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import React, { ChangeEvent } from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { Form, Input, TextArea } from 'semantic-ui-react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import validator from 'validator';
import { withTranslation, WithTranslation } from 'react-i18next';
import SemanticDatepicker from 'react-semantic-ui-datepickers';
@@ -29,8 +28,9 @@ import { formatDocumentIdTitle } from '../../../helpers/documents';
import { TransientAlert } from '../../../stores/alerts/actions';
import { showTransientAlert } from '../../../stores/alerts/actionCreators';
import AuthorizationComponent from '../../AuthorizationComponent';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCancel?: () => void;

@@ -131,7 +131,8 @@ class InvoiceProps extends React.Component<Props, State> {

remove = () => {
if (!this.props.create) {
this.props.history.push('/invoice');
const { navigate } = this.props.router;
navigate('/invoice');
this.props.deleteInvoice(this.props.invoice.id);
}
};
17 changes: 5 additions & 12 deletions src/components/entities/invoice/InvoiceTable.tsx
Original file line number Diff line number Diff line change
@@ -52,18 +52,11 @@ function InvoicesTable({
}: Props) {
const { t } = useTranslation();

if ([Roles.FINANCIAL].some(hasRole) && ![Roles.ADMIN].some(hasRole)) {
useEffect(() => {
setSort('id', 'DESC');
setTableFilter({ column: 'activityStatus', values: ['SENT'] });
fetchInvoices();
}, []);
} else {
useEffect(() => {
setSort('id', 'DESC');
fetchInvoices();
}, []);
}
useEffect(() => {
setSort('id', 'DESC');
if (([Roles.FINANCIAL].some(hasRole) && ![Roles.ADMIN].some(hasRole))) setTableFilter({ column: 'activityStatus', values: ['SENT'] });
fetchInvoices();
}, []);

const table = (
<>
7 changes: 4 additions & 3 deletions src/components/entities/product/ProductProps.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import {
} from 'semantic-ui-react';
import { withTranslation, WithTranslation } from 'react-i18next';
import validator from 'validator';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Client, PaginationParams,
Product, ProductParams, ProductStatus, Roles,
@@ -22,8 +21,9 @@ import PropsButtons from '../../PropsButtons';
import TextArea from '../../TextArea';
import { authedUserHasRole } from '../../../stores/auth/selectors';
import ProductVatSelector from './ProductVatSelector';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCancel?: () => void;

@@ -142,8 +142,9 @@ class ProductProps extends React.Component<Props, State> {
};

remove = () => {
const { navigate } = this.props.router;
if (!this.props.create && this.props.deleteProduct) {
this.props.history.push('/product');
navigate('/product');
this.props.deleteProduct(this.props.product.id);
}
};
327 changes: 171 additions & 156 deletions src/components/entities/product/ProductsContractedGraph.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import {
} from 'semantic-ui-react';
import validator from 'validator';
import { WithTranslation, withTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { CategoryParams, ProductCategory, Roles } from '../../../clients/server.generated';
import { createSingle, deleteSingle, saveSingle } from '../../../stores/single/actionCreators';
import ResourceStatus from '../../../stores/resourceStatus';
@@ -15,8 +14,9 @@ import PropsButtons from '../../PropsButtons';
import { getSingle } from '../../../stores/single/selectors';
import { SingleEntities } from '../../../stores/single/single';
import AuthorizationComponent from '../../AuthorizationComponent';
import { withRouter, WithRouter } from '../../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCancel?: () => void;

@@ -93,7 +93,8 @@ class ProductCategoryProps extends React.Component<Props, State> {

remove = () => {
if (!this.props.create && this.props.deleteCategory) {
this.props.history.push('/category');
const { navigate } = this.props.router;
navigate('/category');
this.props.deleteCategory(this.props.category.id);
}
};
7 changes: 4 additions & 3 deletions src/components/entities/user/UserProps.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import {
} from 'semantic-ui-react';
import validator from 'validator';
import _ from 'lodash';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
Gender, LoginMethods, Roles, User, UserParams,
@@ -19,8 +18,9 @@ import { SingleEntities } from '../../../stores/single/single';
import { getSingle } from '../../../stores/single/selectors';
import TextArea from '../../TextArea';
import { authedUserHasRole } from '../../../stores/auth/selectors';
import { WithRouter, withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCancel?: () => void;

@@ -171,7 +171,8 @@ class UserProps extends React.Component<Props, State> {

remove = () => {
if (!this.props.create) {
this.props.history.push('/users');
const { navigate } = this.props.router;
navigate('/users');
this.props.deleteUser(this.props.user.id);
}
};
5 changes: 3 additions & 2 deletions src/components/entities/user/UserSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import { Redirect, RouteComponentProps, withRouter } from 'react-router-dom';
import { Navigate as Redirect } from 'react-router-dom';
import { Dispatch } from 'redux';
import { useTranslation } from 'react-i18next';
import { User } from '../../../clients/server.generated';
@@ -12,8 +12,9 @@ import { RootState } from '../../../stores/store';
import LogoAvatarModal from '../../files/LogoAvatarModal';
import { fetchSingle } from '../../../stores/single/actionCreators';
import { EntitySummary } from '../EntitySummary';
import { withRouter } from '../../../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
user: User | undefined;
status: ResourceStatus;
fetchUser: (id: number) => void;
4 changes: 2 additions & 2 deletions src/components/files/FilesList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Button, Icon, Table } from 'semantic-ui-react';
import { withTranslation, WithTranslation } from 'react-i18next';
import { SingleEntities } from '../../stores/single/single';
@@ -9,8 +8,9 @@ import { GeneralFile } from './GeneralFile';
import SingleFile from './SingleFile';
import AuthorizationComponent from '../AuthorizationComponent';
import './FilesList.scss';
import { withRouter } from '../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation {
files: GeneralFile[];
entityId: number;
entity: SingleEntities;
4 changes: 2 additions & 2 deletions src/components/files/SingleFile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Icon, Input, Popup, Table,
} from 'semantic-ui-react';
@@ -16,8 +15,9 @@ import { SingleEntities } from '../../stores/single/single';
import { GeneralFile } from './GeneralFile';
import ResourceStatus from '../../stores/resourceStatus';
import AuthorizationComponent from '../AuthorizationComponent';
import { withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
file: GeneralFile;
create: boolean;
closeCreate?: (shouldUpdate: boolean) => void;
7 changes: 4 additions & 3 deletions src/components/megatable/MegaTable.tsx
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import {
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { withTranslation, WithTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import MegaTableRow from './MegaTableRow';
import { RootState } from '../../stores/store';
import { countFetched, countTotal, getTable } from '../../stores/tables/selectors';
@@ -29,8 +28,9 @@ import { ContractStatus, ETCompany, ProductInstanceStatus } from '../../clients/
import { formatPriceFull } from '../../helpers/monetary';
import ContractStatusFilter from '../tablefilters/ContractStatusFilter';
import { dateToFinancialYear } from '../../helpers/timestamp';
import { withRouter, WithRouter } from '../../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation, WithRouter {
companies: ETCompany[];
nrOfProducts: number;
sumProducts: number;
@@ -60,7 +60,8 @@ class MegaTable extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

let { hash } = this.props.location;
const { location } = this.props.router;
let { hash } = location;
// If there is no hash, do not take the first (#) character
if (hash.length > 0) {
hash = hash.substr(1);
10 changes: 6 additions & 4 deletions src/components/navigation/AuthMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { Dispatch } from 'redux';
import {
Dropdown, Icon, Loader, Menu, Flag,
@@ -14,8 +14,9 @@ import { RootState } from '../../stores/store';
import UserAvatar from '../entities/user/UserAvatar';
import { authedUserHasRole } from '../../stores/auth/selectors';
import { changeLanguage, getLanguage } from '../../localization';
import { WithRouter, withRouter } from '../../WithRouter';

interface Props extends RouteComponentProps {
interface Props extends WithRouter {
authStatus: AuthStatus | undefined;
status: ResourceStatus;

@@ -27,6 +28,8 @@ interface Props extends RouteComponentProps {
}

function AuthMenu(props: Props) {
const { t } = useTranslation();
const { navigate } = props.router;
if (props.status !== ResourceStatus.FETCHED || props.authStatus === undefined
|| props.profileStatus !== ResourceStatus.FETCHED || props.profile === undefined) {
return (
@@ -36,10 +39,9 @@ function AuthMenu(props: Props) {
);
}

const { t } = useTranslation();

const logout = () => {
props.history.push('/login');
navigate('/login');
props.logout();
};

2 changes: 1 addition & 1 deletion src/components/navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ function Navigation() {
return (
<Menu fixed="top" inverted size="large" className="main-menu">
<Container>
<Menu.Item as={NavLink} header to="/" exact title="Home">
<Menu.Item as={NavLink} header to="/" title="Home">
<Image
className="logo"
src="/ParelPracht-whitesvg.svg"
2 changes: 1 addition & 1 deletion src/components/productpricing/CreatePricing.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { fetchSingle } from '../../stores/single/actionCreators';
import { SingleEntities } from '../../stores/single/single';
import { Client } from '../../clients/server.generated';

interface Props extends WithTranslation{
interface Props extends WithTranslation {
fetchProduct: (id: number) => void;
productId: number;
}
8 changes: 0 additions & 8 deletions src/helpers/entity.ts

This file was deleted.

14 changes: 7 additions & 7 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import i18n from './localization';
import './index.scss';
import App from './App';
import 'semantic-ui-less/semantic.less';
import TitleRenderer from './components/TitleContext';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
import 'semantic-ui-less/semantic.less';

// We have to disable the following "errors", because they are actually warnings.
// The components that throw them work fine, so to keep the console clean from errors,
@@ -21,13 +23,11 @@ import TitleRenderer from './components/TitleContext';
// }
// };

ReactDOM.render(
<React.StrictMode>
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<I18nextProvider i18n={i18n}>
<TitleRenderer>
<App />
</TitleRenderer>
</I18nextProvider>
</React.StrictMode>,
document.getElementById('root'),
</I18nextProvider>,
);
17 changes: 10 additions & 7 deletions src/pages/CompaniesCreatePage.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import {
} from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import { Company, CompanyStatus, Roles } from '../clients/server.generated';
import { clearSingle } from '../stores/single/actionCreators';
@@ -17,8 +16,9 @@ import { SingleEntities } from '../stores/single/single';
import { TransientAlert } from '../stores/alerts/actions';
import { showTransientAlert } from '../stores/alerts/actionCreators';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
status: ResourceStatus;

clearCompany: () => void;
@@ -29,13 +29,14 @@ class CompaniesCreatePage extends React.Component<Props> {
componentDidMount() {
const { clearCompany, t } = this.props;
clearCompany();
this.context.setTitle(t('entities.company.newCompany'));
document.title = t('entities.company.newCompany');
}

componentDidUpdate(prevProps: Props) {
if (prevProps.status === ResourceStatus.SAVING
&& this.props.status === ResourceStatus.FETCHED) {
this.props.history.push('/company');
const { navigate } = this.props.router;
navigate('/company');
this.props.showTransientAlert({
title: 'Success',
message: 'Company successfully created',
@@ -45,7 +46,10 @@ class CompaniesCreatePage extends React.Component<Props> {
}
}

close = () => { this.props.history.goBack(); };
close = () => {
const { navigate } = this.props.router;
navigate(-1);
};

public render() {
const company = {
@@ -99,5 +103,4 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({

CompaniesCreatePage.contextType = TitleContext;

export default withTranslation()(withRouter(connect(mapStateToProps,
mapDispatchToProps)(CompaniesCreatePage)));
export default withTranslation()(withRouter(connect(mapStateToProps, mapDispatchToProps)(CompaniesCreatePage)));
8 changes: 5 additions & 3 deletions src/pages/CompaniesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -9,10 +8,13 @@ import AuthorizationComponent from '../components/AuthorizationComponent';
import CompanyTable from '../components/entities/company/CompanyTable';
import CompanyTableControls from '../components/entities/company/CompanyTableControls';
import { useTitle } from '../components/TitleContext';
import { useNavigate } from 'react-router-dom';
import { withRouter } from '../WithRouter';

function CompaniesPage(props: RouteComponentProps) {
function CompaniesPage() {
const { t } = useTranslation();
const { setTitle } = useTitle();
const navigate = useNavigate();

React.useEffect(() => {
setTitle(t('entity.companies'));
@@ -40,7 +42,7 @@ function CompaniesPage(props: RouteComponentProps) {
roles={[Roles.ADMIN]}
notFound={false}
>
<Button icon labelPosition="left" primary floated="right" onClick={() => props.history.push('/company/new')}>
<Button icon labelPosition="left" primary floated="right" onClick={() => navigate('/company/new')}>
<Icon name="plus" />
{t('pages.companies.addCompany')}
</Button>
104 changes: 55 additions & 49 deletions src/pages/ContactModal.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import {
} from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
Contact, ContactFunction, ContractStatus, Gender,
@@ -26,9 +26,9 @@ import { formatStatus } from '../helpers/activity';
import { getContractStatus } from '../stores/contract/selectors';
import CompanyLink from '../components/entities/company/CompanyLink';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends RouteComponentProps<{ companyId: string, contactId?: string }>,
WithTranslation {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
onCompanyPage: boolean;
contact: Contact | undefined;
@@ -48,10 +48,9 @@ class ContactModal extends React.Component<Props> {

componentDidMount() {
this.props.clearContact();

const { contactId } = this.props.match.params;
if (!this.props.create && contactId !== undefined) {
this.props.fetchContact(parseInt(contactId, 10));
const { params } = this.props.router;
if (!this.props.create && params.contactId !== undefined) {
this.props.fetchContact(parseInt(params.contactId, 10));
}
}

@@ -81,35 +80,36 @@ class ContactModal extends React.Component<Props> {
}

closeWithPopupMessage = () => {
const { companyId } = this.props.match.params;
const { params, navigate } = this.props.router;

// If the modal is not opened on a company page, we cannot refresh the company information
if (companyId !== undefined) {
this.props.fetchCompany(parseInt(companyId, 10));
if (params.companyId !== undefined) {
this.props.fetchCompany(parseInt(params.companyId, 10));
}
if (companyId === undefined) {
this.props.history.push('/contact');
if (params.companyId === undefined) {
navigate('/contact');
} else {
this.props.history.push(`/company/${companyId}`);
navigate(`/company/${params.companyId}`);
}
};

close = () => {
const { companyId } = this.props.match.params;
const { params, navigate } = this.props.router;
// If the modal is not opened on a company page, we cannot refresh the company information
if (companyId !== undefined) {
this.props.fetchCompany(parseInt(companyId, 10));
if (params.companyId !== undefined) {
this.props.fetchCompany(parseInt(params.companyId, 10));
}
this.props.history.goBack();
navigate(-1);
};

public render() {
const { t } = this.props;
let contact: Contact | undefined;

if (this.props.create) {
this.context.setTitle(t('entities.contact.newContact'));
const { companyId } = this.props.match.params;
document.title = t('entities.contact.newContact');
const { params } = this.props.router;
const companyId = params.companyId;
contact = {
id: 0,
firstName: '',
@@ -127,7 +127,7 @@ class ContactModal extends React.Component<Props> {
}

if (contact === undefined) {
this.context.setTitle(t('entity.contact'));
document.title = t('entity.contact');
return (
<Modal
onClose={this.close}
@@ -137,16 +137,16 @@ class ContactModal extends React.Component<Props> {
size="tiny"
>
<Segment placeholder attached="bottom">
<AlertContainer />
<AlertContainer/>
<Dimmer active inverted>
<Loader />
<Loader/>
</Dimmer>
</Segment>
</Modal>
);
}

this.context.setTitle(formatContactName(contact.firstName, contact.lastName, contact.lastName));
document.title = formatContactName(contact.firstName, contact.lastName, contact.lastName);

let contractOverview;

@@ -160,31 +160,35 @@ class ContactModal extends React.Component<Props> {
<Header>{t('entity.contracts')}</Header>
<Table>
<Table.Header>
<Table.HeaderCell>
{t('entities.contract.props.title')}
</Table.HeaderCell>
<Table.HeaderCell>
{t('entity.company')}
</Table.HeaderCell>
<Table.HeaderCell>
{t('entities.generalProps.status')}
</Table.HeaderCell>
<Table.Row>
<Table.HeaderCell>
{t('entities.contract.props.title')}
</Table.HeaderCell>
<Table.HeaderCell>
{t('entity.company')}
</Table.HeaderCell>
<Table.HeaderCell>
{t('entities.generalProps.status')}
</Table.HeaderCell>
</Table.Row>
</Table.Header>
{contact.contracts.map((contract) => {
return (
<Table.Row>
<Table.Cell>
<NavLink to={`/contract/${contract.id}`}>{contract.title}</NavLink>
</Table.Cell>
<Table.Cell>
<CompanyLink id={contract.companyId} />
</Table.Cell>
<Table.Cell>
{formatStatus(this.props.getContractStatus(contract.id))}
</Table.Cell>
</Table.Row>
);
})}
<Table.Body>
{contact.contracts.map((contract) => {
return (
<Table.Row key={contract.id}>
<Table.Cell>
<NavLink to={`/contract/${contract.id}`}>{contract.title}</NavLink>
</Table.Cell>
<Table.Cell>
<CompanyLink id={contract.companyId}/>
</Table.Cell>
<Table.Cell>
{formatStatus(this.props.getContractStatus(contract.id))}
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
</Segment>
);
@@ -199,12 +203,14 @@ class ContactModal extends React.Component<Props> {
size="tiny"
>
<Segment attached="bottom">
<AlertContainer />
<AlertContainer/>
<ContactProps
onCompanyPage={this.props.onCompanyPage}
contact={contact}
create={this.props.create}
onCancel={() => { this.close(); }}
onCancel={() => {
this.close();
}}
/>
{contractOverview}
</Segment>
8 changes: 4 additions & 4 deletions src/pages/ContractInvoiceModal.tsx
Original file line number Diff line number Diff line change
@@ -3,19 +3,19 @@ import {
Button, Dropdown, Icon, Modal,
} from 'semantic-ui-react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { Dispatch } from 'redux';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
Body, Client, Contract, InvoiceCreateParams, InvoiceStatus, InvoiceSummary,
Body as SBody, Client, Contract, InvoiceCreateParams, InvoiceStatus, InvoiceSummary,
} from '../clients/server.generated';
import { RootState } from '../stores/store';
import { SummaryCollections } from '../stores/summaries/summaries';
import { getSummaryCollection } from '../stores/summaries/selectors';
import { createSingle, fetchSingle } from '../stores/single/actionCreators';
import { SingleEntities } from '../stores/single/single';
import { withRouter } from '../WithRouter';

interface SelfProps extends RouteComponentProps<{ contractId: string }>, WithTranslation {
interface SelfProps extends WithTranslation {
}

interface Props extends SelfProps {
@@ -58,7 +58,7 @@ class ContractInvoiceModal extends React.Component<Props, State> {
} else if (this.state.selectedInvoice !== undefined) {
const client = new Client();
await Promise.all(this.props.productInstanceIds.map((x) => {
return client.addProductToInvoice(this.state.selectedInvoice!, new Body({ productId: x }));
return client.addProductToInvoice(this.state.selectedInvoice!, new SBody({ productId: x }));
}));
fetchContract(contract.id);
}
22 changes: 10 additions & 12 deletions src/pages/ContractModal.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import * as React from 'react';
import { Modal } from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import { Contract } from '../clients/server.generated';
import { fetchSingle, clearSingle } from '../stores/single/actionCreators';
@@ -13,11 +12,9 @@ import AlertContainer from '../components/alerts/AlertContainer';
import { SingleEntities } from '../stores/single/single';
import { getSingle } from '../stores/single/selectors';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface SelfProps extends RouteComponentProps<{ companyId?: string }> {
}

interface Props extends SelfProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
status: ResourceStatus;

clearContract: () => void;
@@ -31,7 +28,7 @@ class ContractModal extends React.Component<Props> {

componentDidUpdate(prevProps: Props) {
const { t } = this.props;
this.context.setTitle(t('pages.contract.newContract'));
document.title = t('pages.contract.newContract');

if (prevProps.status === ResourceStatus.SAVING
&& this.props.status === ResourceStatus.FETCHED) {
@@ -40,19 +37,20 @@ class ContractModal extends React.Component<Props> {
}

close = () => {
const { companyId } = this.props.match.params;
const { params, navigate } = this.props.router;
// If the modal is not opened on a company page, we cannot refresh the company information
if (companyId !== undefined) {
this.props.fetchCompany(parseInt(companyId, 10));
if (params.companyId !== undefined) {
this.props.fetchCompany(parseInt(params.companyId, 10));
}
this.props.history.goBack();
navigate(-1);
};

public render() {
let compId = -1;
let companyPredefined: boolean = false;
if (this.props.match.params.companyId) {
compId = parseInt(this.props.match.params.companyId, 10);
const { params } = this.props.router;
if (params.companyId) {
compId = parseInt(params.companyId, 10);
companyPredefined = true;
}
const contract: Contract = {
39 changes: 20 additions & 19 deletions src/pages/ContractProductInstanceModal.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import {
} from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import {
Contract, ProductInstance, ProductInstanceParams, ProductInstanceStatus, Roles,
@@ -26,14 +25,12 @@ import {
} from '../stores/productinstance/actionCreator';
import { getProductName } from '../stores/product/selectors';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface SelfProps extends RouteComponentProps<{
contractId: string, productInstanceId?: string
}> {
interface SelfProps extends WithRouter {
create?: boolean;
}

interface Props extends SelfProps, WithTranslation {
interface Props extends WithTranslation, SelfProps {
productInstance: ProductInstance | undefined;
status: ResourceStatus;
contract?: Contract;
@@ -54,51 +51,54 @@ class ContractProductInstanceModal extends React.Component<Props> {
componentDidUpdate() {
const { productInstance, productName, t } = this.props;
if (!productInstance) {
this.context.setTitle(t('pages.contract.products.addProduct'));
document.title = t('pages.contract.products.addProduct');
} else {
this.context.setTitle(productName);
document.title = productName;
}
}

close = () => {
const { contractId } = this.props.match.params;
this.props.fetchContract(parseInt(contractId, 10));
this.props.history.goBack();
const { params, navigate } = this.props.router;
this.props.fetchContract(parseInt(params.contractId, 10));
navigate(-1);
};

saveProductInstance = async (productInstance: ProductInstanceParams) => {
const { params } = this.props.router;
this.props.saveProductInstance(
parseInt(this.props.match.params.contractId!, 10),
parseInt(this.props.match.params.productInstanceId!, 10),
parseInt(params.contractId, 10),
parseInt(params.productInstanceId, 10),
productInstance,
);
this.close();
};

createProductInstance = async (productInstance: ProductInstanceParams) => {
const { params } = this.props.router;
this.props.createProductInstance(
parseInt(this.props.match.params.contractId!, 10),
parseInt(params.contractId, 10),
productInstance,
);
this.close();
};

removeProductInstance = async () => {
const { params } = this.props.router;
this.props.removeProductInstance(
parseInt(this.props.match.params.contractId!, 10),
parseInt(this.props.match.params.productInstanceId!, 10),
parseInt(params.contractId, 10),
parseInt(params.productInstanceId, 10),
);
this.close();
};

public render() {
const { create, status, contract } = this.props;
const { params } = this.props.router;
let productInstance: ProductInstance | undefined;
if (create) {
const { contractId } = this.props.match.params;
productInstance = {
id: -1,
contractId: parseInt(contractId, 10),
contractId: parseInt(params.contractId, 10),
productId: -1,
basePrice: 0,
discount: 0,
@@ -182,9 +182,10 @@ class ContractProductInstanceModal extends React.Component<Props> {
}

const mapStateToProps = (state: RootState, props: SelfProps) => {
const { params } = props.router;
const prodInstance = !props.create
? getSingle<Contract>(state, SingleEntities.Contract).data?.products.find(
(p) => p.id === parseInt(props.match.params.productInstanceId!, 10),
(p) => p.id === parseInt(params.productInstanceId, 10),
)
: undefined;
let prodName = '';
8 changes: 5 additions & 3 deletions src/pages/ContractsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -9,10 +8,13 @@ import AuthorizationComponent from '../components/AuthorizationComponent';
import ContractsTable from '../components/entities/contract/ContractTable';
import ContractTableControls from '../components/entities/contract/ContractTableControls';
import { useTitle } from '../components/TitleContext';
import { useNavigate } from 'react-router-dom';
import { withRouter } from '../WithRouter';

function ContractsPage(props: RouteComponentProps) {
function ContractsPage() {
const { t } = useTranslation();
const { setTitle } = useTitle();
const history = useNavigate();

React.useEffect(() => {
setTitle(t('entity.contracts'));
@@ -37,7 +39,7 @@ function ContractsPage(props: RouteComponentProps) {
</Grid.Column>
<Grid.Column>
<AuthorizationComponent roles={[Roles.GENERAL, Roles.ADMIN]} notFound={false}>
<Button icon labelPosition="left" primary floated="right" onClick={() => props.history.push('/contract/new')}>
<Button icon labelPosition="left" primary floated="right" onClick={() => history('/contract/new')}>
<Icon name="plus" />
{t('pages.contracts.addContract')}
</Button>
6 changes: 3 additions & 3 deletions src/pages/CustomInvoicePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -20,8 +19,9 @@ import { FilesClient } from '../clients/filesClient';
import AuthorizationComponent from '../components/AuthorizationComponent';
import { isInvalidDate } from '../helpers/timestamp';
import { TitleContext } from '../components/TitleContext';
import { WithRouter, withRouter } from '../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {}
interface Props extends WithTranslation, WithRouter {}

interface State {
language: Language;
@@ -69,7 +69,7 @@ class CustomInvoicePage extends React.Component<Props, State> {

componentDidMount() {
const { t } = this.props;
this.context.setTitle(t('pages.customInvoice.title'));
document.title = t('pages.customInvoice.title');
}

setAttribute = (attribute: string, value: string) => {
4 changes: 2 additions & 2 deletions src/pages/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -12,8 +11,9 @@ import DashboardContracts from '../components/dashboard/DashboardContracts';
import FinancialOverview from '../components/dashboard/FinancialOverview';
import DashboardContractedCategoryGraph from '../components/dashboard/DashboardContractedCategoryGraph';
import { useTitle } from '../components/TitleContext';
import { withRouter } from '../WithRouter';

interface Props extends WithTranslation, RouteComponentProps {
interface Props extends WithTranslation {
// eslint-disable-next-line react/no-unused-prop-types
user: User | undefined;
}
4 changes: 2 additions & 2 deletions src/pages/Insights.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react';
import { withRouter } from 'react-router-dom';
import {
Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import ContractTableExtensive from '../components/megatable/MegaTable';
import ContractTableExtensiveControls from '../components/megatable/MegaTableControls';
import { useTitle } from '../components/TitleContext';
import { withRouter } from '../WithRouter';

function Insights() {
const { t } = useTranslation();
@@ -38,7 +38,7 @@ function Insights() {
</Container>
</Segment>
<Container style={{ marginTop: '20px' }}>
<ContractTableExtensive />
<ContractTableExtensive/>
</Container>
</>
);
6 changes: 3 additions & 3 deletions src/pages/InvoicesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button,
Container, Grid, Header, Icon, Popup, Segment,
@@ -14,15 +13,16 @@ import { fetchTable } from '../stores/tables/actionCreators';
import { Tables } from '../stores/tables/tables';
import AuthorizationComponent from '../components/AuthorizationComponent';
import { TitleContext } from '../components/TitleContext';
import { withRouter } from '../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation {
refresh: () => void;
}

class InvoicesPage extends React.Component<Props> {
componentDidMount() {
const { t } = this.props;
this.context.setTitle(t('entity.invoices'));
document.title = t('entity.invoices');
}

updateTreasurerLastSeen = async () => {
14 changes: 9 additions & 5 deletions src/pages/ProductCategoriesCreatePage.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import * as React from 'react';
import { Modal } from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { ProductCategory } from '../clients/server.generated';
import { clearSingle } from '../stores/single/actionCreators';
import { RootState } from '../stores/store';
@@ -13,8 +12,9 @@ import { SingleEntities } from '../stores/single/single';
import ProductCategoryProps from '../components/entities/productcategories/ProductCategoryProps';
import { TransientAlert } from '../stores/alerts/actions';
import { showTransientAlert } from '../stores/alerts/actionCreators';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends RouteComponentProps {
interface Props extends WithRouter {
status: ResourceStatus;

clearCategory: () => void;
@@ -40,10 +40,14 @@ class ProductCategoriesCreatePage extends React.Component<Props> {
}

closeWithPopupMessage = () => {
this.props.history.push('/category');
const { navigate } = this.props.router;
navigate('/category');
};

close = () => { this.props.history.goBack(); };
close = () => {
const { navigate } = this.props.router;
navigate(-1);
};

public render() {
const category = {
@@ -63,7 +67,7 @@ class ProductCategoriesCreatePage extends React.Component<Props> {
>
<Modal.Content>
<AlertContainer />
<ProductCategoryProps category={category} create onCancel={this.close} />
<ProductCategoryProps category={category} create onCancel={this.close} router={this.props.router} />
</Modal.Content>
</Modal>
);
8 changes: 5 additions & 3 deletions src/pages/ProductCategoriesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import {
Button, Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -9,10 +8,13 @@ import AuthorizationComponent from '../components/AuthorizationComponent';
import ProductCategoriesTable from '../components/entities/productcategories/ProductCategoriesTable';
import ProductCategoriesTableControls from '../components/entities/productcategories/ProductCategoriesTableControls';
import { useTitle } from '../components/TitleContext';
import { useNavigate } from 'react-router-dom';
import { withRouter } from '../WithRouter';

function ProductCategoriesPage(props: RouteComponentProps) {
function ProductCategoriesPage() {
const { t } = useTranslation();
const { setTitle } = useTitle();
const history = useNavigate();

React.useEffect(() => {
setTitle(t('entity.categories'));
@@ -34,7 +36,7 @@ function ProductCategoriesPage(props: RouteComponentProps) {
</Grid.Column>
<Grid.Column>
<AuthorizationComponent roles={[Roles.ADMIN]} notFound={false}>
<Button icon labelPosition="left" primary floated="right" onClick={() => props.history.push('/category/new')}>
<Button icon labelPosition="left" primary floated="right" onClick={() => history('/category/new')}>
<Icon name="plus" />
{t('entities.category.addCategory')}
</Button>
24 changes: 13 additions & 11 deletions src/pages/ProductCategoryModal.tsx
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import {
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { withTranslation, WithTranslation } from 'react-i18next';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { ProductCategory } from '../clients/server.generated';
import { clearSingle, fetchSingle } from '../stores/single/actionCreators';
import { RootState } from '../stores/store';
@@ -20,8 +20,9 @@ import { TransientAlert } from '../stores/alerts/actions';
import { showTransientAlert } from '../stores/alerts/actionCreators';
import ProductCategoryProps from '../components/entities/productcategories/ProductCategoryProps';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends WithTranslation, RouteComponentProps<{ categoryId: string }> {
interface Props extends WithTranslation, WithRouter {
create?: boolean;
category: ProductCategory | undefined;
status: ResourceStatus;
@@ -38,10 +39,9 @@ class ProductCategoryModal extends React.Component<Props> {

componentDidMount() {
this.props.clearCategory();

const { categoryId } = this.props.match.params;
if (!this.props.create && categoryId !== undefined) {
this.props.fetchCategory(parseInt(categoryId, 10));
const { params } = this.props.router;
if (!this.props.create && params.categoryId !== undefined) {
this.props.fetchCategory(parseInt(params.categoryId, 10));
}
}

@@ -51,11 +51,11 @@ class ProductCategoryModal extends React.Component<Props> {
} = this.props;

if (create) {
this.context.setTitle(t('entities.category.newCategory'));
document.title = t('entities.category.newCategory');
} else if (category === undefined) {
this.context.setTitle(t('entity.category'));
document.title = t('entity.category');
} else {
this.context.setTitle(category.name);
document.title = category.name;
}

if (status === ResourceStatus.FETCHED
@@ -84,11 +84,13 @@ class ProductCategoryModal extends React.Component<Props> {
}

closeWithPopupMessage = () => {
this.props.history.push('/category');
const { navigate } = this.props.router;
navigate('/category');
};

close = () => {
this.props.history.goBack();
const { navigate } = this.props.router;
navigate(-1);
};

public render() {
14 changes: 9 additions & 5 deletions src/pages/ProductCreatePage.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import * as React from 'react';
import { Modal } from 'semantic-ui-react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { withTranslation, WithTranslation } from 'react-i18next';
import { Product, ProductStatus, Roles } from '../clients/server.generated';
import { RootState } from '../stores/store';
@@ -15,8 +14,9 @@ import { clearSingle } from '../stores/single/actionCreators';
import { TransientAlert } from '../stores/alerts/actions';
import { showTransientAlert } from '../stores/alerts/actionCreators';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends RouteComponentProps, WithTranslation {
interface Props extends WithTranslation, WithRouter {
status: ResourceStatus;

clearProduct: () => void;
@@ -27,13 +27,14 @@ class ProductCreatePage extends React.Component<Props> {
componentDidMount() {
const { clearProduct, t } = this.props;
clearProduct();
this.context.setTitle(t('pages.product.newProduct'));
document.title = t('pages.product.newProduct');
}

componentDidUpdate(prevProps: Props) {
const { navigate } = this.props.router;
if (prevProps.status === ResourceStatus.SAVING
&& this.props.status === ResourceStatus.FETCHED) {
this.props.history.push('/product');
navigate('/product');
this.props.showTransientAlert({
title: 'Success',
message: 'Product successfully created',
@@ -43,7 +44,10 @@ class ProductCreatePage extends React.Component<Props> {
}
}

close = () => { this.props.history.goBack(); };
close = () => {
const { navigate } = this.props.router;
navigate(-1);
};

public render() {
const product: Product = {
9 changes: 6 additions & 3 deletions src/pages/ProductsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import {
Button, Container, Grid, Header, Icon, Segment,
} from 'semantic-ui-react';
@@ -9,15 +9,18 @@ import AuthorizationComponent from '../components/AuthorizationComponent';
import ProductsTable from '../components/entities/product/ProductTable';
import ProductTableControls from '../components/entities/product/ProductTableControls';
import { useTitle } from '../components/TitleContext';
import { withRouter } from '../WithRouter';

function ProductsPage(props: RouteComponentProps) {
function ProductsPage() {
const { t } = useTranslation();
const { setTitle } = useTitle();

React.useEffect(() => {
setTitle(t('entity.products'));
}, []);

const history = useNavigate();

return (
<AuthorizationComponent roles={[Roles.GENERAL, Roles.ADMIN]} notFound>
<Segment style={{ backgroundColor: 'rgba(237, 237, 237, 0.98)' }} vertical basic>
@@ -36,7 +39,7 @@ function ProductsPage(props: RouteComponentProps) {
</Grid.Column>
<Grid.Column>
<AuthorizationComponent roles={[Roles.ADMIN]} notFound={false}>
<Button icon labelPosition="left" primary floated="right" onClick={() => props.history.push('/product/new')}>
<Button icon labelPosition="left" primary floated="right" onClick={() => history('/product/new')}>
<Icon name="plus" />
{t('pages.products.addProduct')}
</Button>
30 changes: 16 additions & 14 deletions src/pages/ResetPasswordPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
NavLink, Redirect, RouteComponentProps, withRouter,
NavLink, Navigate as Redirect, useLocation,
} from 'react-router-dom';
import {
Button, Container, Header, Icon, Segment,
@@ -19,18 +19,31 @@ import { authRequestClear } from '../stores/auth/actionCreators';
import ResourceStatus from '../stores/resourceStatus';
import CenterInPage from '../components/CenterInPage';
import { useTitle } from '../components/TitleContext';
import { withRouter } from '../WithRouter';

interface Props extends RouteComponentProps {
interface Props {
status: ResourceStatus;

clearStatus: () => void;
}

function ResetPasswordPage(props: Props) {
const { token } = queryString.parse(props.location.search);
const location = useLocation();
const { token } = queryString.parse(location.search);
const { t } = useTranslation();
const { setTitle } = useTitle();

const [eightCharacters, changeEightCharacters] = useState(false);
const [lowerCase, changeLowerCase] = useState(false);
const [upperCase, changeUpperCase] = useState(false);
const [numbers, changeNumbers] = useState(false);
const [symbols, changeSymbols] = useState(false);

useEffect(() => {
props.clearStatus();
setTitle(t('pages.resetPassword.title'));
}, []);

if (typeof token !== 'string') {
return <Redirect to="/login" />;
}
@@ -40,12 +53,6 @@ function ResetPasswordPage(props: Props) {
return <Redirect to="/login" />;
}

const [eightCharacters, changeEightCharacters] = useState(false);
const [lowerCase, changeLowerCase] = useState(false);
const [upperCase, changeUpperCase] = useState(false);
const [numbers, changeNumbers] = useState(false);
const [symbols, changeSymbols] = useState(false);

const hasEightCharacters = (password: string) => {
changeEightCharacters(password.length >= 8);
};
@@ -78,11 +85,6 @@ function ResetPasswordPage(props: Props) {

const newUser = payload.type === 'PASSWORD_SET';

useEffect(() => {
props.clearStatus();
setTitle(t('pages.resetPassword.title'));
}, []);

if (props.status === ResourceStatus.FETCHED) {
return (
<>
28 changes: 16 additions & 12 deletions src/pages/SingleCompanyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Breadcrumb, Container, Grid, Loader, Segment, Tab,
} from 'semantic-ui-react';
@@ -27,8 +27,9 @@ import { authedUserHasRole } from '../stores/auth/selectors';
import AuthorizationComponent from '../components/AuthorizationComponent';
import NotFound from './NotFound';
import { TitleContext } from '../components/TitleContext';
import { WithRouter, withRouter } from '../WithRouter';

interface Props extends WithTranslation, RouteComponentProps<{ companyId: string }> {
interface Props extends WithTranslation, WithRouter {
company: Company | undefined;
status: ResourceStatus;

@@ -45,9 +46,10 @@ interface State {
class SingleCompanyPage extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { location, navigate } = this.props.router;

const panes = this.getPanes();
let { hash } = this.props.location;
let { hash } = location;
// If there is no hash, do not take the first (#) character
if (hash.length > 0) {
hash = hash.substr(1);
@@ -58,7 +60,7 @@ class SingleCompanyPage extends React.Component<Props, State> {
// select the first one by default
if (index < 0) {
index = 0;
this.props.history.replace(`#${panes[0].menuItem.toLowerCase()}`);
navigate(`#${panes[0].menuItem.toLowerCase()}`, { replace: true });
}

this.state = {
@@ -67,25 +69,26 @@ class SingleCompanyPage extends React.Component<Props, State> {
}

componentDidMount() {
const { companyId } = this.props.match.params;
const { params } = this.props.router;

this.props.clearCompany();
this.props.fetchCompany(Number.parseInt(companyId, 10));
this.props.fetchCompany(Number.parseInt(params.companyId, 10));
}

componentDidUpdate(prevProps: Readonly<Props>) {
const { company, status, t } = this.props;
const { navigate } = this.props.router;

if (company === undefined) {
this.context.setTitle(t('entity.company'));
document.title = t('entity.company');
} else {
this.context.setTitle(company.name);
document.title = company.name;
}

if (status === ResourceStatus.EMPTY
&& prevProps.status === ResourceStatus.DELETING
) {
this.props.history.push('/company');
navigate('/company');
this.props.showTransientAlert({
title: 'Success',
message: `Company ${prevProps.company?.name} successfully deleted`,
@@ -184,13 +187,14 @@ class SingleCompanyPage extends React.Component<Props, State> {
public render() {
const { company, status, t } = this.props;
const { paneIndex } = this.state;
const { navigate } = this.props.router;

if (status === ResourceStatus.NOTFOUND) {
return <NotFound />;
}

if (company === undefined) {
this.context.setTitle(t('entity.company'));
document.title = t('entity.company');
return (
<AuthorizationComponent
roles={[Roles.GENERAL, Roles.ADMIN, Roles.AUDIT]}
@@ -204,7 +208,7 @@ class SingleCompanyPage extends React.Component<Props, State> {
}

const panes = this.getPanes();
this.context.setTitle(company.name);
document.title = company.name;

return (
<AuthorizationComponent
@@ -231,7 +235,7 @@ class SingleCompanyPage extends React.Component<Props, State> {
menu={{ pointing: true, inverted: true }}
onTabChange={(e, data) => {
this.setState({ paneIndex: data.activeIndex! as number });
this.props.history.replace(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`);
navigate(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`, { replace: true });
}}
activeIndex={paneIndex}
/>
28 changes: 17 additions & 11 deletions src/pages/SingleContractPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Breadcrumb, Container, Grid, Loader, Segment, Tab,
} from 'semantic-ui-react';
@@ -25,8 +25,9 @@ import GenerateContractModal from '../components/files/GenerateContractModal';
import { authedUserHasRole } from '../stores/auth/selectors';
import NotFound from './NotFound';
import { TitleContext } from '../components/TitleContext';
import { withRouter, WithRouter } from '../WithRouter';

interface Props extends RouteComponentProps<{ contractId: string }>, WithTranslation {
interface Props extends WithTranslation, WithRouter {
contract: Contract | undefined;
status: ResourceStatus;

@@ -43,9 +44,10 @@ interface State {
class SingleContractPage extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { location, navigate } = this.props.router;

const panes = this.getPanes();
let { hash } = this.props.location;
let { hash } = location;
// If there is no hash, do not take the first (#) character
if (hash.length > 0) {
hash = hash.substr(1);
@@ -56,7 +58,7 @@ class SingleContractPage extends React.Component<Props, State> {
// select the first one by default
if (index < 0) {
index = 0;
this.props.history.replace(`#${panes[0].menuItem.toLowerCase()}`);
navigate(`#${panes[0].menuItem.toLowerCase()}`, { replace: true });
}

this.state = {
@@ -65,25 +67,26 @@ class SingleContractPage extends React.Component<Props, State> {
}

componentDidMount() {
const { contractId } = this.props.match.params;
const { params } = this.props.router;

this.props.clearContract();
this.props.fetchContract(Number.parseInt(contractId, 10));
this.props.fetchContract(Number.parseInt(params.contractId, 10));
}

public componentDidUpdate(prevProps: Readonly<Props>) {
const { contract, status, t } = this.props;
const { navigate } = this.props.router;

if (contract === undefined) {
this.context.setTitle(t('entity.contract'));
document.title = t('entity.contract');
} else {
this.context.setTitle(`C${contract.id} ${contract.title}`);
document.title = `C${contract.id} ${contract.title}`;
}

if (status === ResourceStatus.EMPTY
&& prevProps.status === ResourceStatus.DELETING
) {
this.props.history.push('/contract');
navigate('/contract');
this.props.showTransientAlert({
title: 'Success',
message: `Contract ${prevProps.contract?.title} successfully deleted`,
@@ -154,6 +157,7 @@ class SingleContractPage extends React.Component<Props, State> {
public render() {
const { contract, status, t } = this.props;
const { paneIndex } = this.state;
const { navigate } = this.props.router;

if (status === ResourceStatus.NOTFOUND) {
return <NotFound />;
@@ -207,14 +211,16 @@ class SingleContractPage extends React.Component<Props, State> {
menu={{ pointing: true, inverted: true }}
onTabChange={(e, data) => {
this.setState({ paneIndex: data.activeIndex! as number });
this.props.history.replace(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`);
navigate(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`, { replace: true });
}}
activeIndex={paneIndex}
/>
</Grid.Column>
<Grid.Column width={6}>
<Segment secondary style={{ backgroundColor: 'rgba(243, 244, 245, 0.98)' }}>
<ContractProps contract={contract} />
<ContractProps
contract={contract}
/>
</Segment>
</Grid.Column>
</Grid.Row>
26 changes: 16 additions & 10 deletions src/pages/SingleInvoicePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Breadcrumb,
Container, Grid, Loader, Segment, Tab,
@@ -25,8 +25,9 @@ import AuthorizationComponent from '../components/AuthorizationComponent';
import { authedUserHasRole } from '../stores/auth/selectors';
import NotFound from './NotFound';
import { TitleContext } from '../components/TitleContext';
import { WithRouter, withRouter } from '../WithRouter';

interface Props extends RouteComponentProps<{ invoiceId: string }>, WithTranslation {
interface Props extends WithTranslation, WithRouter {
invoice: Invoice | undefined;
status: ResourceStatus;

@@ -42,9 +43,10 @@ interface State {
class SingleInvoicePage extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { location, navigate } = props.router;

const panes = this.getPanes();
let { hash } = this.props.location;
let { hash } = location;
// If there is no hash, do not take the first (#) character
if (hash.length > 0) {
hash = hash.substr(1);
@@ -55,7 +57,8 @@ class SingleInvoicePage extends React.Component<Props, State> {
// select the first one by default
if (index < 0) {
index = 0;
this.props.history.replace(`#${panes[0].menuItem.toLowerCase()}`);

navigate(`#${panes[0].menuItem.toLowerCase()}`, { replace: true });
}

this.state = {
@@ -64,18 +67,18 @@ class SingleInvoicePage extends React.Component<Props, State> {
}

componentDidMount() {
const { invoiceId } = this.props.match.params;
const { params } = this.props.router;

this.props.clearInvoice();
this.props.fetchInvoice(Number.parseInt(invoiceId, 10));
this.props.fetchInvoice(Number.parseInt(params.invoiceId, 10));
}

componentDidUpdate() {
const { invoice, t } = this.props;
if (invoice === undefined) {
this.context.setTitle(t('entity.contract'));
document.title = t('entity.contract');
} else {
this.context.setTitle(`F${invoice.id} ${invoice.title}`);
document.title = `F${invoice.id} ${invoice.title}`;
}
}

@@ -141,6 +144,7 @@ class SingleInvoicePage extends React.Component<Props, State> {
public render() {
const { invoice, status, t } = this.props;
const { paneIndex } = this.state;
const { navigate } = this.props.router;

if (status === ResourceStatus.NOTFOUND) {
return <NotFound />;
@@ -194,14 +198,16 @@ class SingleInvoicePage extends React.Component<Props, State> {
menu={{ pointing: true, inverted: true }}
onTabChange={(e, data) => {
this.setState({ paneIndex: data.activeIndex! as number });
this.props.history.replace(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`);
navigate(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`, { replace: true });
}}
activeIndex={paneIndex}
/>
</Grid.Column>
<Grid.Column width={6}>
<Segment secondary style={{ backgroundColor: 'rgba(243, 244, 245, 0.98)' }}>
<InvoiceProps invoice={invoice} />
<InvoiceProps
invoice={invoice}
/>
</Segment>
</Grid.Column>
</Grid.Row>
26 changes: 15 additions & 11 deletions src/pages/SingleProductPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { NavLink, RouteComponentProps, withRouter } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import {
Breadcrumb, Container, Grid, Loader, Segment, Tab,
} from 'semantic-ui-react';
@@ -28,8 +28,9 @@ import NotFound from './NotFound';
import { getLanguage } from '../localization';
import { TitleContext } from '../components/TitleContext';
import CreatePricing from '../components/productpricing/CreatePricing';
import { WithRouter, withRouter } from '../WithRouter';

interface Props extends WithTranslation, RouteComponentProps<{ productId: string }> {
interface Props extends WithTranslation, WithRouter {
product: Product | undefined;
status: ResourceStatus;

@@ -45,9 +46,10 @@ interface State {
class SingleProductPage extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
const { location, navigate } = this.props.router;

const panes = this.getPanes();
let { hash } = this.props.location;
let { hash } = location;
// If there is no hash, do not take the first (#) character
if (hash.length > 0) {
hash = hash.substr(1);
@@ -58,7 +60,7 @@ class SingleProductPage extends React.Component<Props, State> {
// select the first one by default
if (index < 0) {
index = 0;
this.props.history.replace(`#${panes[0].menuItem.toLowerCase()}`);
navigate(`#${panes[0].menuItem.toLowerCase()}`, { replace: true });
}

this.state = {
@@ -67,27 +69,28 @@ class SingleProductPage extends React.Component<Props, State> {
}

componentDidMount() {
const { productId } = this.props.match.params;
const { params } = this.props.router;

this.props.clearProduct();
this.props.fetchProduct(Number.parseInt(productId, 10));
this.props.fetchProduct(Number.parseInt(params.productId, 10));
}

componentDidUpdate(prevProps: Readonly<Props>) {
const { product, status, t } = this.props;
const { navigate } = this.props.router;

if (product === undefined) {
this.context.setTitle(t('entity.product'));
document.title = t('entity.product');
} else if (getLanguage() === 'nl-NL') {
this.context.setTitle(product.nameDutch);
document.title = product.nameDutch;
} else {
this.context.setTitle(product.nameEnglish);
document.title = product.nameEnglish;
}

if (status === ResourceStatus.EMPTY
&& prevProps.status === ResourceStatus.DELETING
) {
this.props.history.push('/product');
navigate('/product');
this.props.showTransientAlert({
title: 'Success',
message: `Product ${prevProps.product?.nameEnglish} successfully deleted`,
@@ -187,6 +190,7 @@ class SingleProductPage extends React.Component<Props, State> {

public render() {
const { product, status, t } = this.props;
const { navigate } = this.props.router;
const { paneIndex } = this.state;
const useDutch = getLanguage() === 'nl-NL';

@@ -228,7 +232,7 @@ class SingleProductPage extends React.Component<Props, State> {
menu={{ pointing: true, inverted: true }}
onTabChange={(e, data) => {
this.setState({ paneIndex: data.activeIndex! as number });
this.props.history.replace(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`);
navigate(`#${data.panes![data.activeIndex! as number].menuItem.toLowerCase()}`, { replace: true });
}}
activeIndex={paneIndex}
/>
Loading

0 comments on commit 559f38b

Please sign in to comment.