mirror of
https://github.com/Reckless-Satoshi/robosats.git
synced 2024-08-16 23:30:29 +03:00
Book functional component (#256)
* Add bookTable functional component * Implement responsive booktable * Fix unwanted scroll bar on chromium browsers * Add column self-organization and 3 new columns * Add responsive behaviour on depth chart * Run prettier * Add minimum pageSize (book must at least be 1 row height) * Adjust circular spinner div height * Add order ID column style * Refactor window resize event listener * Add depth chart outline * Review fixes
This commit is contained in:
parent
33941ce359
commit
e47d55b582
@ -1,38 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import {
|
||||
Tooltip,
|
||||
Stack,
|
||||
Paper,
|
||||
Button,
|
||||
ToggleButtonGroup,
|
||||
ToggleButton,
|
||||
ListItemButton,
|
||||
Typography,
|
||||
Grid,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
IconButton,
|
||||
ButtonGroup,
|
||||
} from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import currencyDict from '../../static/assets/currencies.json';
|
||||
|
||||
import MediaQuery from 'react-responsive';
|
||||
import FlagWithProps from './FlagWithProps';
|
||||
import { pn, amountToString } from '../utils/prettyNumbers';
|
||||
import PaymentText from './PaymentText';
|
||||
import DepthChart from './Charts/DepthChart';
|
||||
import RobotAvatar from './Robots/RobotAvatar';
|
||||
import { apiClient } from '../services/api/index';
|
||||
|
||||
// Icons
|
||||
import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material';
|
||||
import BookTable from './BookTable';
|
||||
|
||||
class BookPage extends Component {
|
||||
constructor(props) {
|
||||
@ -43,11 +32,11 @@ class BookPage extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getOrderDetails(2, 0);
|
||||
}
|
||||
componentDidMount = () => {
|
||||
this.getOrderDetails();
|
||||
};
|
||||
|
||||
getOrderDetails(type, currency) {
|
||||
getOrderDetails() {
|
||||
this.props.setAppState({ bookLoading: true });
|
||||
apiClient.get('/api/book/').then((data) =>
|
||||
this.props.setAppState({
|
||||
@ -79,378 +68,6 @@ class BookPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Colors for the status badges
|
||||
statusBadgeColor(status) {
|
||||
if (status === 'Active') {
|
||||
return 'success';
|
||||
}
|
||||
if (status === 'Seen recently') {
|
||||
return 'warning';
|
||||
}
|
||||
if (status === 'Inactive') {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
dataGridLocaleText = () => {
|
||||
const { t } = this.props;
|
||||
return {
|
||||
MuiTablePagination: { labelRowsPerPage: t('Orders per page:') },
|
||||
noRowsLabel: t('No rows'),
|
||||
noResultsOverlayLabel: t('No results found.'),
|
||||
errorOverlayDefaultLabel: t('An error occurred.'),
|
||||
toolbarColumns: t('Columns'),
|
||||
toolbarColumnsLabel: t('Select columns'),
|
||||
columnsPanelTextFieldLabel: t('Find column'),
|
||||
columnsPanelTextFieldPlaceholder: t('Column title'),
|
||||
columnsPanelDragIconLabel: t('Reorder column'),
|
||||
columnsPanelShowAllButton: t('Show all'),
|
||||
columnsPanelHideAllButton: t('Hide all'),
|
||||
filterPanelAddFilter: t('Add filter'),
|
||||
filterPanelDeleteIconLabel: t('Delete'),
|
||||
filterPanelLinkOperator: t('Logic operator'),
|
||||
filterPanelOperators: t('Operator'), // TODO v6: rename to filterPanelOperator
|
||||
filterPanelOperatorAnd: t('And'),
|
||||
filterPanelOperatorOr: t('Or'),
|
||||
filterPanelColumns: t('Columns'),
|
||||
filterPanelInputLabel: t('Value'),
|
||||
filterPanelInputPlaceholder: t('Filter value'),
|
||||
filterOperatorContains: t('contains'),
|
||||
filterOperatorEquals: t('equals'),
|
||||
filterOperatorStartsWith: t('starts with'),
|
||||
filterOperatorEndsWith: t('ends with'),
|
||||
filterOperatorIs: t('is'),
|
||||
filterOperatorNot: t('is not'),
|
||||
filterOperatorAfter: t('is after'),
|
||||
filterOperatorOnOrAfter: t('is on or after'),
|
||||
filterOperatorBefore: t('is before'),
|
||||
filterOperatorOnOrBefore: t('is on or before'),
|
||||
filterOperatorIsEmpty: t('is empty'),
|
||||
filterOperatorIsNotEmpty: t('is not empty'),
|
||||
filterOperatorIsAnyOf: t('is any of'),
|
||||
filterValueAny: t('any'),
|
||||
filterValueTrue: t('true'),
|
||||
filterValueFalse: t('false'),
|
||||
columnMenuLabel: t('Menu'),
|
||||
columnMenuShowColumns: t('Show columns'),
|
||||
columnMenuFilter: t('Filter'),
|
||||
columnMenuHideColumn: t('Hide'),
|
||||
columnMenuUnsort: t('Unsort'),
|
||||
columnMenuSortAsc: t('Sort by ASC'),
|
||||
columnMenuSortDesc: t('Sort by DESC'),
|
||||
columnHeaderFiltersLabel: t('Show filters'),
|
||||
columnHeaderSortIconLabel: t('Sort'),
|
||||
booleanCellTrueLabel: t('yes'),
|
||||
booleanCellFalseLabel: t('no'),
|
||||
};
|
||||
};
|
||||
|
||||
bookListTableDesktop = () => {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<div style={{ height: 424, width: '100%' }}>
|
||||
<DataGrid
|
||||
localeText={this.dataGridLocaleText()}
|
||||
rows={this.props.bookOrders.filter(
|
||||
(order) =>
|
||||
(order.type == this.props.type || this.props.type == null) &&
|
||||
(order.currency == this.props.currency || this.props.currency == 0),
|
||||
)}
|
||||
loading={this.props.bookLoading}
|
||||
columns={[
|
||||
// { field: 'id', headerName: 'ID', width: 40 },
|
||||
{
|
||||
field: 'maker_nick',
|
||||
headerName: t('Robot'),
|
||||
width: 240,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<ListItemButton style={{ cursor: 'pointer' }}>
|
||||
<ListItemAvatar>
|
||||
<RobotAvatar
|
||||
nickname={params.row.maker_nick}
|
||||
style={{ width: 45, height: 45 }}
|
||||
smooth={true}
|
||||
orderType={params.row.type}
|
||||
statusColor={this.statusBadgeColor(params.row.maker_status)}
|
||||
tooltip={t(params.row.maker_status)}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={params.row.maker_nick} />
|
||||
</ListItemButton>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
headerName: t('Is'),
|
||||
width: 60,
|
||||
renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')),
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
headerName: t('Amount'),
|
||||
type: 'number',
|
||||
width: 90,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{amountToString(
|
||||
params.row.amount,
|
||||
params.row.has_range,
|
||||
params.row.min_amount,
|
||||
params.row.max_amount,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'currency',
|
||||
headerName: t('Currency'),
|
||||
width: 100,
|
||||
renderCell: (params) => {
|
||||
const currencyCode = this.getCurrencyCode(params.row.currency);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{currencyCode + ' '}
|
||||
<FlagWithProps code={currencyCode} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'payment_method',
|
||||
headerName: t('Payment Method'),
|
||||
width: 180,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<PaymentText
|
||||
othersText={t('Others')}
|
||||
verbose={true}
|
||||
size={24}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
headerName: t('Price'),
|
||||
type: 'number',
|
||||
width: 140,
|
||||
renderCell: (params) => {
|
||||
const currencyCode = this.getCurrencyCode(params.row.currency);
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{pn(params.row.price) + ' ' + currencyCode + '/BTC'}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'premium',
|
||||
headerName: t('Premium'),
|
||||
type: 'number',
|
||||
width: 100,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
components={{
|
||||
NoRowsOverlay: () => (
|
||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
||||
<div style={{ height: '220px' }} />
|
||||
{this.NoOrdersFound()}
|
||||
</Stack>
|
||||
),
|
||||
NoResultsOverlay: () => (
|
||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
||||
{t('Filter has no results')}
|
||||
</Stack>
|
||||
),
|
||||
}}
|
||||
pageSize={this.props.bookLoading ? 0 : this.state.pageSize}
|
||||
rowsPerPageOptions={[0, 6, 20, 50]}
|
||||
onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })}
|
||||
onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
bookListTablePhone = () => {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<div style={{ height: 424, width: '100%' }}>
|
||||
<DataGrid
|
||||
localeText={this.dataGridLocaleText()}
|
||||
loading={this.props.bookLoading}
|
||||
rows={this.props.bookOrders.filter(
|
||||
(order) =>
|
||||
(order.type == this.props.type || this.props.type == null) &&
|
||||
(order.currency == this.props.currency || this.props.currency == 0),
|
||||
)}
|
||||
columns={[
|
||||
// { field: 'id', headerName: 'ID', width: 40 },
|
||||
{
|
||||
field: 'maker_nick',
|
||||
headerName: t('Robot'),
|
||||
width: 64,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ position: 'relative', left: '-5px' }}>
|
||||
<RobotAvatar
|
||||
nickname={params.row.maker_nick}
|
||||
smooth={true}
|
||||
style={{ width: 45, height: 45 }}
|
||||
orderType={params.row.type}
|
||||
statusColor={this.statusBadgeColor(params.row.maker_status)}
|
||||
tooltip={t(params.row.maker_status)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
headerName: t('Amount'),
|
||||
type: 'number',
|
||||
width: 84,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<Tooltip
|
||||
placement='right'
|
||||
enterTouchDelay={0}
|
||||
title={t(params.row.type ? 'Seller' : 'Buyer')}
|
||||
>
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{amountToString(
|
||||
params.row.amount,
|
||||
params.row.has_range,
|
||||
params.row.min_amount,
|
||||
params.row.max_amount,
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'currency',
|
||||
headerName: t('Currency'),
|
||||
width: 85,
|
||||
renderCell: (params) => {
|
||||
const currencyCode = this.getCurrencyCode(params.row.currency);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{currencyCode + ' '}
|
||||
<FlagWithProps code={currencyCode} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{ field: 'payment_method', headerName: t('Payment Method'), width: 180, hide: 'true' },
|
||||
{
|
||||
field: 'payment_icons',
|
||||
headerName: t('Pay'),
|
||||
width: 75,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
left: '-4px',
|
||||
cursor: 'pointer',
|
||||
align: 'center',
|
||||
}}
|
||||
>
|
||||
<PaymentText
|
||||
othersText={t('Others')}
|
||||
size={16}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
headerName: t('Price'),
|
||||
type: 'number',
|
||||
width: 140,
|
||||
hide: 'true',
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{pn(params.row.price) + ' ' + params.row.currency + '/BTC'}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'premium',
|
||||
headerName: t('Premium'),
|
||||
type: 'number',
|
||||
width: 85,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<Tooltip
|
||||
placement='left'
|
||||
enterTouchDelay={0}
|
||||
title={pn(params.row.price) + ' ' + params.row.currency + '/BTC'}
|
||||
>
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
components={{
|
||||
NoRowsOverlay: () => (
|
||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
||||
<div style={{ height: '220px' }} />
|
||||
{this.NoOrdersFound()}
|
||||
</Stack>
|
||||
),
|
||||
NoResultsOverlay: () => (
|
||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
||||
{t('Local filter returns no result')}
|
||||
</Stack>
|
||||
),
|
||||
}}
|
||||
pageSize={this.props.bookLoading ? 0 : this.state.pageSize}
|
||||
rowsPerPageOptions={[0, 6, 20, 50]}
|
||||
onPageSizeChange={(newPageSize) => this.setState({ pageSize: newPageSize })}
|
||||
onRowClick={(params) => this.handleRowClick(params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
handleTypeChange = (mouseEvent, val) => {
|
||||
this.props.setAppState({ type: val });
|
||||
};
|
||||
@ -495,45 +112,32 @@ class BookPage extends Component {
|
||||
return this.NoOrdersFound();
|
||||
}
|
||||
|
||||
const components =
|
||||
this.state.view == 'depth'
|
||||
? [
|
||||
<DepthChart
|
||||
bookLoading={this.props.bookLoading}
|
||||
orders={this.props.bookOrders}
|
||||
lastDayPremium={this.props.lastDayPremium}
|
||||
currency={this.props.currency}
|
||||
setAppState={this.props.setAppState}
|
||||
limits={this.props.limits}
|
||||
/>,
|
||||
<DepthChart
|
||||
bookLoading={this.props.bookLoading}
|
||||
orders={this.props.bookOrders}
|
||||
lastDayPremium={this.props.lastDayPremium}
|
||||
currency={this.props.currency}
|
||||
compact={true}
|
||||
setAppState={this.props.setAppState}
|
||||
limits={this.props.limits}
|
||||
/>,
|
||||
]
|
||||
: [this.bookListTableDesktop(), this.bookListTablePhone()];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop */}
|
||||
<MediaQuery minWidth={930}>
|
||||
<Paper elevation={0} style={{ width: 925, maxHeight: 510, overflow: 'auto' }}>
|
||||
<div style={{ height: 424, width: '100%' }}>{components[0]}</div>
|
||||
</Paper>
|
||||
</MediaQuery>
|
||||
{/* Smartphone */}
|
||||
<MediaQuery maxWidth={929}>
|
||||
<Paper elevation={0} style={{ width: 395, maxHeight: 460, overflow: 'auto' }}>
|
||||
<div style={{ height: 424, width: '100%' }}>{components[1]}</div>
|
||||
</Paper>
|
||||
</MediaQuery>
|
||||
</>
|
||||
);
|
||||
if (this.state.view === 'depth') {
|
||||
return (
|
||||
<DepthChart
|
||||
bookLoading={this.props.bookLoading}
|
||||
orders={this.props.bookOrders}
|
||||
lastDayPremium={this.props.lastDayPremium}
|
||||
currency={this.props.currency}
|
||||
compact={true}
|
||||
setAppState={this.props.setAppState}
|
||||
limits={this.props.limits}
|
||||
maxWidth={(this.props.windowWidth / this.props.theme.typography.fontSize) * 0.8} // EM units
|
||||
maxHeight={(this.props.windowHeight / this.props.theme.typography.fontSize) * 0.8 - 11} // EM units
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<BookTable
|
||||
loading={this.props.bookLoading}
|
||||
orders={this.props.bookOrders}
|
||||
type={this.props.type}
|
||||
currency={this.props.currency}
|
||||
maxWidth={(this.props.windowWidth / this.props.theme.typography.fontSize) * 0.97} // EM units
|
||||
maxHeight={(this.props.windowHeight / this.props.theme.typography.fontSize) * 0.8 - 11} // EM units
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getTitle = () => {
|
||||
@ -562,7 +166,7 @@ class BookPage extends Component {
|
||||
<Grid className='orderBook' container spacing={1} sx={{ minWidth: 400 }}>
|
||||
<IconButton
|
||||
sx={{ position: 'fixed', right: '0px', top: '30px' }}
|
||||
onClick={() => this.setState({ loading: true }) & this.getOrderDetails(2, 0)}
|
||||
onClick={() => this.setState({ loading: true }) & this.getOrderDetails()}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
@ -631,15 +235,11 @@ class BookPage extends Component {
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
{this.props.bookNotFound ? (
|
||||
<></>
|
||||
) : (
|
||||
<Grid item xs={12} align='center'>
|
||||
<Typography component='h5' variant='h5'>
|
||||
{this.getTitle()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} align='center'>
|
||||
<Typography component='h5' variant='h5'>
|
||||
{this.getTitle()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align='center'>
|
||||
{this.mainView()}
|
||||
</Grid>
|
||||
|
598
frontend/src/components/BookTable.tsx
Normal file
598
frontend/src/components/BookTable.tsx
Normal file
@ -0,0 +1,598 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
Stack,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
useTheme,
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import currencyDict from '../../static/assets/currencies.json';
|
||||
import { Order } from '../models/Order.model';
|
||||
|
||||
import FlagWithProps from './FlagWithProps';
|
||||
import { pn, amountToString } from '../utils/prettyNumbers';
|
||||
import PaymentText from './PaymentText';
|
||||
import RobotAvatar from './Robots/RobotAvatar';
|
||||
import hexToRgb from '../utils/hexToRgb';
|
||||
import statusBadgeColor from '../utils/statusBadgeColor';
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
orders: Order[];
|
||||
type: number;
|
||||
currency: number;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
}
|
||||
|
||||
const BookTable = ({
|
||||
loading,
|
||||
orders,
|
||||
type,
|
||||
currency,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
}: Props): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const history = useHistory();
|
||||
|
||||
const fontSize = theme.typography.fontSize;
|
||||
|
||||
// all sizes in 'em'
|
||||
const verticalHeightFrame = 6.9075;
|
||||
const verticalHeightRow = 3.25;
|
||||
const defaultPageSize = Math.max(
|
||||
Math.floor((maxHeight - verticalHeightFrame) / verticalHeightRow),
|
||||
1,
|
||||
);
|
||||
const height = defaultPageSize * verticalHeightRow + verticalHeightFrame;
|
||||
|
||||
const [pageSize, setPageSize] = useState(0);
|
||||
const [useDefaultPageSize, setUseDefaultPageSize] = useState(true);
|
||||
useEffect(() => {
|
||||
if (useDefaultPageSize) {
|
||||
setPageSize(defaultPageSize);
|
||||
}
|
||||
});
|
||||
|
||||
const premiumColor = function (baseColor: string, accentColor: string, point: number) {
|
||||
const baseRGB = hexToRgb(baseColor);
|
||||
const accentRGB = hexToRgb(accentColor);
|
||||
const redDiff = accentRGB[0] - baseRGB[0];
|
||||
const red = baseRGB[0] + redDiff * point;
|
||||
const greenDiff = accentRGB[1] - baseRGB[1];
|
||||
const green = baseRGB[1] + greenDiff * point;
|
||||
const blueDiff = accentRGB[2] - baseRGB[2];
|
||||
const blue = baseRGB[2] + blueDiff * point;
|
||||
return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)}, ${
|
||||
0.7 + point * 0.3
|
||||
})`;
|
||||
};
|
||||
|
||||
const localeText = {
|
||||
MuiTablePagination: { labelRowsPerPage: t('Orders per page:') },
|
||||
noRowsLabel: t('No rows'),
|
||||
noResultsOverlayLabel: t('No results found.'),
|
||||
errorOverlayDefaultLabel: t('An error occurred.'),
|
||||
toolbarColumns: t('Columns'),
|
||||
toolbarColumnsLabel: t('Select columns'),
|
||||
columnsPanelTextFieldLabel: t('Find column'),
|
||||
columnsPanelTextFieldPlaceholder: t('Column title'),
|
||||
columnsPanelDragIconLabel: t('Reorder column'),
|
||||
columnsPanelShowAllButton: t('Show all'),
|
||||
columnsPanelHideAllButton: t('Hide all'),
|
||||
filterPanelAddFilter: t('Add filter'),
|
||||
filterPanelDeleteIconLabel: t('Delete'),
|
||||
filterPanelLinkOperator: t('Logic operator'),
|
||||
filterPanelOperators: t('Operator'),
|
||||
filterPanelOperatorAnd: t('And'),
|
||||
filterPanelOperatorOr: t('Or'),
|
||||
filterPanelColumns: t('Columns'),
|
||||
filterPanelInputLabel: t('Value'),
|
||||
filterPanelInputPlaceholder: t('Filter value'),
|
||||
filterOperatorContains: t('contains'),
|
||||
filterOperatorEquals: t('equals'),
|
||||
filterOperatorStartsWith: t('starts with'),
|
||||
filterOperatorEndsWith: t('ends with'),
|
||||
filterOperatorIs: t('is'),
|
||||
filterOperatorNot: t('is not'),
|
||||
filterOperatorAfter: t('is after'),
|
||||
filterOperatorOnOrAfter: t('is on or after'),
|
||||
filterOperatorBefore: t('is before'),
|
||||
filterOperatorOnOrBefore: t('is on or before'),
|
||||
filterOperatorIsEmpty: t('is empty'),
|
||||
filterOperatorIsNotEmpty: t('is not empty'),
|
||||
filterOperatorIsAnyOf: t('is any of'),
|
||||
filterValueAny: t('any'),
|
||||
filterValueTrue: t('true'),
|
||||
filterValueFalse: t('false'),
|
||||
columnMenuLabel: t('Menu'),
|
||||
columnMenuShowColumns: t('Show columns'),
|
||||
columnMenuFilter: t('Filter'),
|
||||
columnMenuHideColumn: t('Hide'),
|
||||
columnMenuUnsort: t('Unsort'),
|
||||
columnMenuSortAsc: t('Sort by ASC'),
|
||||
columnMenuSortDesc: t('Sort by DESC'),
|
||||
columnHeaderFiltersLabel: t('Show filters'),
|
||||
columnHeaderSortIconLabel: t('Sort'),
|
||||
booleanCellTrueLabel: t('yes'),
|
||||
booleanCellFalseLabel: t('no'),
|
||||
};
|
||||
|
||||
const robotObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'maker_nick',
|
||||
headerName: t('Robot'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<ListItemButton style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}>
|
||||
<ListItemAvatar>
|
||||
<RobotAvatar
|
||||
nickname={params.row.maker_nick}
|
||||
style={{ width: '3.215em', height: '3.215em' }}
|
||||
smooth={true}
|
||||
orderType={params.row.type}
|
||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||
tooltip={t(params.row.maker_status)}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={params.row.maker_nick} />
|
||||
</ListItemButton>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const robotSmallObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'maker_nick',
|
||||
headerName: t('Robot'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ position: 'relative', left: '-1.5em' }}>
|
||||
<ListItemButton style={{ cursor: 'pointer' }}>
|
||||
<RobotAvatar
|
||||
nickname={params.row.maker_nick}
|
||||
smooth={true}
|
||||
style={{ width: '3.215em', height: '3.215em' }}
|
||||
orderType={params.row.type}
|
||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||
tooltip={t(params.row.maker_status)}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const typeObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'type',
|
||||
headerName: t('Is'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => (params.row.type ? t('Seller') : t('Buyer')),
|
||||
};
|
||||
};
|
||||
|
||||
const amountObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'amount',
|
||||
headerName: t('Amount'),
|
||||
type: 'number',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{amountToString(
|
||||
params.row.amount,
|
||||
params.row.has_range,
|
||||
params.row.min_amount,
|
||||
params.row.max_amount,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const currencyObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'currency',
|
||||
headerName: t('Currency'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{currencyCode + ' '}
|
||||
<FlagWithProps code={currencyCode} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const paymentObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'payment_method',
|
||||
headerName: t('Payment Method'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<PaymentText
|
||||
othersText={t('Others')}
|
||||
verbose={true}
|
||||
size={1.7 * fontSize}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const paymentSmallObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'payment_icons',
|
||||
headerName: t('Pay'),
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
left: '-4px',
|
||||
cursor: 'pointer',
|
||||
align: 'center',
|
||||
}}
|
||||
>
|
||||
<PaymentText
|
||||
othersText={t('Others')}
|
||||
size={1.3 * fontSize}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const priceObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'price',
|
||||
headerName: t('Price'),
|
||||
type: 'number',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>{`${pn(params.row.price)} ${currencyCode}/BTC`}</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const premiumObj = function (width: number, hide: boolean) {
|
||||
// coloring premium texts based on 4 params:
|
||||
// Hardcoded: a sell order at 0% is an outstanding premium
|
||||
// Hardcoded: a buy order at 10% is an outstanding premium
|
||||
const sellStandardPremium = 10;
|
||||
const buyOutstandingPremium = 10;
|
||||
return {
|
||||
hide,
|
||||
field: 'premium',
|
||||
headerName: t('Premium'),
|
||||
type: 'number',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
let fontColor = `rgb(0,0,0)`;
|
||||
if (params.row.type === 0) {
|
||||
var premiumPoint = params.row.premium / buyOutstandingPremium;
|
||||
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
||||
fontColor = premiumColor(
|
||||
theme.palette.text.primary,
|
||||
theme.palette.secondary.dark,
|
||||
premiumPoint,
|
||||
);
|
||||
} else {
|
||||
var premiumPoint = (sellStandardPremium - params.row.premium) / sellStandardPremium;
|
||||
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
||||
fontColor = premiumColor(
|
||||
theme.palette.text.primary,
|
||||
theme.palette.primary.dark,
|
||||
premiumPoint,
|
||||
);
|
||||
}
|
||||
const fontWeight = 400 + Math.round(premiumPoint * 5) * 100;
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<Typography variant='inherit' color={fontColor} sx={{ fontWeight }}>
|
||||
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const timerObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'escrow_duration',
|
||||
headerName: t('Timer'),
|
||||
type: 'number',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
const hours = Math.round(params.row.escrow_duration / 3600);
|
||||
const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60);
|
||||
return <div style={{ cursor: 'pointer' }}>{hours > 0 ? `${hours}h` : `${minutes}m`}</div>;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const expiryObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'expires_at',
|
||||
headerName: t('Expiry'),
|
||||
type: 'string',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
const expiresAt = new Date(params.row.expires_at);
|
||||
const timeToExpiry = Math.abs(expiresAt - new Date());
|
||||
const percent = Math.round((timeToExpiry / (24 * 60 * 60 * 1000)) * 100);
|
||||
const hours = Math.round(timeToExpiry / (3600 * 1000));
|
||||
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
||||
return (
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex', left: '0.3em' }}>
|
||||
<CircularProgress
|
||||
value={percent}
|
||||
color={percent < 15 ? 'error' : percent < 30 ? 'warning' : 'success'}
|
||||
thickness={0.35 * fontSize}
|
||||
size={2.5 * fontSize}
|
||||
variant='determinate'
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant='caption' component='div' color='text.secondary'>
|
||||
{hours > 0 ? `${hours}h` : `${minutes}m`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const satoshisObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'satoshis_now',
|
||||
headerName: t('Sats now'),
|
||||
type: 'number',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
{`${pn(Math.round(params.row.satoshis_now / 1000))}K`}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const idObj = function (width: number, hide: boolean) {
|
||||
return {
|
||||
hide,
|
||||
field: 'id',
|
||||
headerName: 'Order ID',
|
||||
width: width * fontSize,
|
||||
renderCell: (params) => {
|
||||
return (
|
||||
<div style={{ cursor: 'pointer' }}>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
{`#${params.row.id}`}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const columnSpecs = {
|
||||
amount: {
|
||||
priority: 1,
|
||||
order: 4,
|
||||
normal: {
|
||||
width: 6.5,
|
||||
object: amountObj,
|
||||
},
|
||||
},
|
||||
currency: {
|
||||
priority: 2,
|
||||
order: 5,
|
||||
normal: {
|
||||
width: 5.8,
|
||||
object: currencyObj,
|
||||
},
|
||||
},
|
||||
premium: {
|
||||
priority: 3,
|
||||
order: 11,
|
||||
normal: {
|
||||
width: 6,
|
||||
object: premiumObj,
|
||||
},
|
||||
},
|
||||
robot: {
|
||||
priority: 4,
|
||||
order: 1,
|
||||
normal: {
|
||||
width: 17.14,
|
||||
object: robotObj,
|
||||
},
|
||||
small: {
|
||||
width: 4.3,
|
||||
object: robotSmallObj,
|
||||
},
|
||||
},
|
||||
paymentMethod: {
|
||||
priority: 5,
|
||||
order: 6,
|
||||
normal: {
|
||||
width: 12.85,
|
||||
object: paymentObj,
|
||||
},
|
||||
small: {
|
||||
width: 5.8,
|
||||
object: paymentSmallObj,
|
||||
},
|
||||
},
|
||||
price: {
|
||||
priority: 6,
|
||||
order: 10,
|
||||
normal: {
|
||||
width: 10,
|
||||
object: priceObj,
|
||||
},
|
||||
},
|
||||
expires_at: {
|
||||
priority: 7,
|
||||
order: 7,
|
||||
normal: {
|
||||
width: 5.8,
|
||||
object: expiryObj,
|
||||
},
|
||||
},
|
||||
escrow_duration: {
|
||||
priority: 8,
|
||||
order: 8,
|
||||
normal: {
|
||||
width: 3.8,
|
||||
object: timerObj,
|
||||
},
|
||||
},
|
||||
satoshisNow: {
|
||||
priority: 9,
|
||||
order: 9,
|
||||
normal: {
|
||||
width: 6,
|
||||
object: satoshisObj,
|
||||
},
|
||||
},
|
||||
type: {
|
||||
priority: 10,
|
||||
order: 2,
|
||||
normal: {
|
||||
width: 4.3,
|
||||
object: typeObj,
|
||||
},
|
||||
},
|
||||
id: {
|
||||
priority: 11,
|
||||
order: 12,
|
||||
normal: {
|
||||
width: 4.8,
|
||||
object: idObj,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const filteredColumns = function (maxWidth: number) {
|
||||
const useSmall = maxWidth < 70;
|
||||
const selectedColumns: object[] = [];
|
||||
let width: number = 0;
|
||||
|
||||
for (const [key, value] of Object.entries(columnSpecs)) {
|
||||
const colWidth = useSmall && value.small ? value.small.width : value.normal.width;
|
||||
const colObject = useSmall && value.small ? value.small.object : value.normal.object;
|
||||
|
||||
if (width + colWidth < maxWidth || selectedColumns.length < 2) {
|
||||
width = width + colWidth;
|
||||
selectedColumns.push([colObject(colWidth, false), value.order]);
|
||||
} else {
|
||||
selectedColumns.push([colObject(colWidth, true), value.order]);
|
||||
}
|
||||
}
|
||||
|
||||
// sort columns by column.order value
|
||||
selectedColumns.sort(function (first, second) {
|
||||
return first[1] - second[1];
|
||||
});
|
||||
|
||||
const columns = selectedColumns.map(function (item) {
|
||||
return item[0];
|
||||
});
|
||||
|
||||
return [columns, width * 0.875 + 0.15];
|
||||
};
|
||||
|
||||
const [columns, width] = filteredColumns(maxWidth);
|
||||
|
||||
return (
|
||||
<Paper style={{ width: `${width}em`, height: `${height}em`, overflow: 'auto' }}>
|
||||
<DataGrid
|
||||
localeText={localeText}
|
||||
rows={orders.filter(
|
||||
(order) =>
|
||||
(order.type == type || type == null) && (order.currency == currency || currency == 0),
|
||||
)}
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
components={{
|
||||
NoResultsOverlay: () => (
|
||||
<Stack height='100%' alignItems='center' justifyContent='center'>
|
||||
{t('Filter has no results')}
|
||||
</Stack>
|
||||
),
|
||||
}}
|
||||
pageSize={loading ? 0 : pageSize}
|
||||
rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||
onPageSizeChange={(newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setUseDefaultPageSize(false);
|
||||
}}
|
||||
onRowClick={(params) => history.push('/order/' + params.row.id)} // Whole row is clickable, but the mouse only looks clickly in some places.
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookTable;
|
@ -30,6 +30,7 @@ import PaymentText from '../../PaymentText';
|
||||
import getNivoScheme from '../NivoScheme';
|
||||
import median from '../../../utils/match';
|
||||
import { apiClient } from '../../../services/api/index';
|
||||
import statusBadgeColor from '../../../utils/statusBadgeColor';
|
||||
|
||||
interface DepthChartProps {
|
||||
bookLoading: boolean;
|
||||
@ -38,7 +39,8 @@ interface DepthChartProps {
|
||||
currency: number;
|
||||
setAppState: (state: object) => void;
|
||||
limits: LimitList;
|
||||
compact?: boolean;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
}
|
||||
|
||||
const DepthChart: React.FC<DepthChartProps> = ({
|
||||
@ -48,7 +50,8 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
currency,
|
||||
setAppState,
|
||||
limits,
|
||||
compact,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
@ -61,6 +64,9 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
const [currencyCode, setCurrencyCode] = useState<number>(1);
|
||||
const [center, setCenter] = useState<number>();
|
||||
|
||||
const height = maxHeight < 20 ? 20 : maxHeight;
|
||||
const width = maxWidth < 20 ? 20 : maxWidth > 72.8 ? 72.8 : maxWidth;
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(limits).length === 0) {
|
||||
apiClient.get('/api/limits/').then((data) => {
|
||||
@ -216,17 +222,6 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
/>
|
||||
);
|
||||
|
||||
const statusBadgeColor = (status: string) => {
|
||||
if (status === 'Active') {
|
||||
return 'success';
|
||||
}
|
||||
if (status === 'Seen recently') {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'error';
|
||||
};
|
||||
|
||||
const generateTooltip: React.FunctionComponent<PointTooltipProps> = (
|
||||
pointTooltip: PointTooltipProps,
|
||||
) => {
|
||||
@ -293,94 +288,109 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
history.push('/order/' + point.data?.order?.id);
|
||||
};
|
||||
|
||||
return bookLoading || center == undefined || enrichedOrders.length < 1 ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 200, height: 420 }}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
) : (
|
||||
<Grid container style={{ paddingTop: 15 }}>
|
||||
<Grid
|
||||
container
|
||||
direction='row'
|
||||
justifyContent='space-around'
|
||||
alignItems='flex-start'
|
||||
style={{ position: 'absolute' }}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
justifyContent='flex-start'
|
||||
alignItems='flex-start'
|
||||
style={{ paddingLeft: 20 }}
|
||||
>
|
||||
<Select variant='standard' value={xType} onChange={(e) => setXType(e.target.value)}>
|
||||
<MenuItem value={'premium'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
{t('Premium')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem value={'base_amount'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
{t('Price')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
||||
<Grid container justifyContent='center' alignItems='center'>
|
||||
<Grid item>
|
||||
<IconButton onClick={() => setXRange(xRange + rangeSteps)}>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
return (
|
||||
<Paper style={{ width: `${width}em`, maxHeight: `${height}em` }}>
|
||||
<Paper variant='outlined'>
|
||||
{bookLoading || center == undefined || enrichedOrders.length < 1 ? (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: `${(height - 3) / 2 - 1}em`,
|
||||
height: `${height - 3}em`,
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
) : (
|
||||
<Grid container style={{ paddingTop: 15 }}>
|
||||
<Grid
|
||||
container
|
||||
direction='row'
|
||||
justifyContent='space-around'
|
||||
alignItems='flex-start'
|
||||
style={{ position: 'absolute' }}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
justifyContent='flex-start'
|
||||
alignItems='flex-start'
|
||||
style={{ paddingLeft: 20 }}
|
||||
>
|
||||
<Select variant='standard' value={xType} onChange={(e) => setXType(e.target.value)}>
|
||||
<MenuItem value={'premium'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
{t('Premium')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem value={'base_amount'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
{t('Price')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
||||
<Grid container justifyContent='center' alignItems='center'>
|
||||
<Grid item>
|
||||
<IconButton onClick={() => setXRange(xRange + rangeSteps)}>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box justifyContent='center'>
|
||||
{xType === 'base_amount'
|
||||
? `${center} ${currencyDict[currencyCode]}`
|
||||
: `${center}%`}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton onClick={() => setXRange(xRange - rangeSteps)} disabled={xRange <= 1}>
|
||||
<AddCircleOutline />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{ height: `${height - 7}em`, padding: 15 }}>
|
||||
<ResponsiveLine
|
||||
data={series}
|
||||
enableArea={true}
|
||||
useMesh={true}
|
||||
animate={false}
|
||||
crosshairType='cross'
|
||||
tooltip={generateTooltip}
|
||||
onClick={handleOnClick}
|
||||
axisRight={{
|
||||
tickSize: 5,
|
||||
format: formatAxisY,
|
||||
}}
|
||||
axisLeft={{
|
||||
tickSize: 5,
|
||||
format: formatAxisY,
|
||||
}}
|
||||
axisBottom={{
|
||||
tickSize: 5,
|
||||
tickRotation: xType === 'base_amount' && width < 40 ? 45 : 0,
|
||||
format: formatAxisX,
|
||||
}}
|
||||
margin={{ left: 65, right: 60, bottom: width < 40 ? 36 : 25, top: 10 }}
|
||||
xFormat={(value) => Number(value).toFixed(0)}
|
||||
lineWidth={3}
|
||||
theme={getNivoScheme(theme)}
|
||||
colors={[theme.palette.secondary.main, theme.palette.primary.main]}
|
||||
xScale={{
|
||||
type: 'linear',
|
||||
min: center - xRange,
|
||||
max: center + xRange,
|
||||
}}
|
||||
layers={['axes', 'areas', 'crosshair', 'lines', centerLine, 'slices', 'mesh']}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box justifyContent='center'>
|
||||
{xType === 'base_amount' ? `${center} ${currencyDict[currencyCode]}` : `${center}%`}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton onClick={() => setXRange(xRange - rangeSteps)} disabled={xRange <= 1}>
|
||||
<AddCircleOutline />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{ height: 357, padding: 15 }}>
|
||||
<ResponsiveLine
|
||||
data={series}
|
||||
enableArea={true}
|
||||
useMesh={true}
|
||||
animate={false}
|
||||
crosshairType='cross'
|
||||
tooltip={generateTooltip}
|
||||
onClick={handleOnClick}
|
||||
axisRight={{
|
||||
tickSize: 5,
|
||||
format: formatAxisY,
|
||||
}}
|
||||
axisLeft={{
|
||||
tickSize: 5,
|
||||
format: formatAxisY,
|
||||
}}
|
||||
axisBottom={{
|
||||
tickSize: 5,
|
||||
tickRotation: xType === 'base_amount' && compact ? 45 : 0,
|
||||
format: formatAxisX,
|
||||
}}
|
||||
margin={{ left: 65, right: 60, bottom: compact ? 36 : 25, top: 10 }}
|
||||
xFormat={(value) => Number(value).toFixed(0)}
|
||||
lineWidth={3}
|
||||
theme={getNivoScheme(theme)}
|
||||
colors={[theme.palette.secondary.main, theme.palette.primary.main]}
|
||||
xScale={{
|
||||
type: 'linear',
|
||||
min: center - xRange,
|
||||
max: center + xRange,
|
||||
}}
|
||||
layers={['axes', 'areas', 'crosshair', 'lines', centerLine, 'slices', 'mesh']}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Paper>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@ const UpdateClientDialog = ({
|
||||
<Typography>
|
||||
{t(
|
||||
'The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.',
|
||||
{ coordinatorVersion: coordinatorVersion, clientVersion: clientVersion },
|
||||
{ coordinatorVersion, clientVersion },
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
@ -63,7 +63,7 @@ const UpdateClientDialog = ({
|
||||
|
||||
<ListItemText
|
||||
secondary={t('Download RoboSats {{coordinatorVersion}} APK from Github releases', {
|
||||
coordinatorVersion: coordinatorVersion,
|
||||
coordinatorVersion,
|
||||
})}
|
||||
primary={t('On Android RoboSats app ')}
|
||||
/>
|
||||
|
@ -31,6 +31,23 @@ export default class HomePage extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
if (typeof window !== undefined) {
|
||||
this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight });
|
||||
window.addEventListener('resize', this.onResize);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
if (typeof window !== undefined) {
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
}
|
||||
};
|
||||
|
||||
onResize = () => {
|
||||
this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight });
|
||||
};
|
||||
|
||||
setAppState = (newState) => {
|
||||
this.setState(newState);
|
||||
};
|
||||
@ -106,7 +123,7 @@ export default class HomePage extends Component {
|
||||
</div>
|
||||
<div
|
||||
className='bottomBar'
|
||||
style={{ height: `${40 * fontSizeFactor}px`, width: window.innerWidth }}
|
||||
style={{ height: `${40 * fontSizeFactor}px`, width: this.state.windowWidth }}
|
||||
>
|
||||
<BottomBar
|
||||
redirectTo={this.redirectTo}
|
||||
|
@ -56,6 +56,7 @@ import { copyToClipboard } from '../utils/clipboard';
|
||||
import { getWebln } from '../utils/webln';
|
||||
import { apiClient } from '../services/api';
|
||||
import RobotAvatar from './Robots/RobotAvatar';
|
||||
import statusBadgeColor from '../utils/statusBadgeColor';
|
||||
|
||||
class OrderPage extends Component {
|
||||
constructor(props) {
|
||||
@ -666,19 +667,6 @@ class OrderPage extends Component {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Colors for the status badges
|
||||
statusBadgeColor(status) {
|
||||
if (status === 'Active') {
|
||||
return 'success';
|
||||
}
|
||||
if (status === 'Seen recently') {
|
||||
return 'warning';
|
||||
}
|
||||
if (status === 'Inactive') {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
orderBox = () => {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
@ -694,7 +682,7 @@ class OrderPage extends Component {
|
||||
<ListItem>
|
||||
<ListItemAvatar sx={{ width: 56, height: 56 }}>
|
||||
<RobotAvatar
|
||||
statusColor={this.statusBadgeColor(this.state.maker_status)}
|
||||
statusColor={statusBadgeColor(this.state.maker_status)}
|
||||
nickname={this.state.maker_nick}
|
||||
tooltip={t(this.state.maker_status)}
|
||||
orderType={this.state.type}
|
||||
@ -726,7 +714,7 @@ class OrderPage extends Component {
|
||||
<ListItemAvatar>
|
||||
<RobotAvatar
|
||||
avatarClass='smallAvatar'
|
||||
statusColor={this.statusBadgeColor(this.state.taker_status)}
|
||||
statusColor={statusBadgeColor(this.state.taker_status)}
|
||||
nickname={this.state.taker_nick}
|
||||
tooltip={t(this.state.taker_status)}
|
||||
orderType={this.state.type === 0 ? 1 : 0}
|
||||
|
@ -4,7 +4,7 @@ import { Avatar, Badge, Tooltip } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SendReceiveIcon } from '../../Icons';
|
||||
|
||||
interface DepthChartProps {
|
||||
interface Props {
|
||||
nickname: string;
|
||||
smooth?: boolean;
|
||||
style?: object;
|
||||
@ -15,7 +15,7 @@ interface DepthChartProps {
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
const RobotAvatar: React.FC<DepthChartProps> = ({
|
||||
const RobotAvatar: React.FC<Props> = ({
|
||||
nickname,
|
||||
orderType,
|
||||
statusColor,
|
||||
|
@ -15,8 +15,8 @@ export const checkVer: (
|
||||
const patchAvailable = !updateAvailable && patch > Number(semver[2]);
|
||||
|
||||
return {
|
||||
updateAvailable: updateAvailable,
|
||||
patchAvailable: patchAvailable,
|
||||
updateAvailable,
|
||||
patchAvailable,
|
||||
coordinatorVersion: `v${major}.${minor}.${patch}`,
|
||||
clientVersion: `v${semver[0]}.${semver[1]}.${semver[2]}`,
|
||||
};
|
||||
|
14
frontend/src/utils/hexToRgb.js
Normal file
14
frontend/src/utils/hexToRgb.js
Normal file
@ -0,0 +1,14 @@
|
||||
export default function hexToRgb(c) {
|
||||
if (c.includes('rgb')) {
|
||||
const vals = c.split('(')[1].split(')')[0];
|
||||
return vals.split(',');
|
||||
}
|
||||
if (/^#([a-f0-9]{3}){1,2}$/.test(c)) {
|
||||
if (c.length == 4) {
|
||||
c = '#' + [c[1], c[1], c[2], c[2], c[3], c[3]].join('');
|
||||
}
|
||||
c = '0x' + c.substring(1);
|
||||
return [(c >> 16) & 255, (c >> 8) & 255, c & 255];
|
||||
}
|
||||
return '';
|
||||
}
|
9
frontend/src/utils/statusBadgeColor.ts
Normal file
9
frontend/src/utils/statusBadgeColor.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default function statusBadgeColor(status: string) {
|
||||
if (status === 'Active') {
|
||||
return 'success';
|
||||
}
|
||||
if (status === 'Seen recently') {
|
||||
return 'warning';
|
||||
}
|
||||
return 'error';
|
||||
}
|
@ -169,11 +169,12 @@ input[type='number'] {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
@media (max-width: 929px) {
|
||||
@media (max-height: 725px) {
|
||||
.appCenter:has(> div.MuiGrid-root:first-child, > div.MuiBox-root:first-child) {
|
||||
overflow-y: scroll;
|
||||
margin-top: 12px;
|
||||
padding-bottom: 25px;
|
||||
overflow-y: auto;
|
||||
margin-top: 1em;
|
||||
padding-bottom: 3em;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user