mirror of
https://github.com/RoboSats/robosats.git
synced 2025-09-13 00:56:22 +00: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:
@ -1,38 +1,27 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
|
||||||
Stack,
|
|
||||||
Paper,
|
|
||||||
Button,
|
Button,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ListItemButton,
|
|
||||||
Typography,
|
Typography,
|
||||||
Grid,
|
Grid,
|
||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
ListItemText,
|
|
||||||
ListItemAvatar,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { DataGrid } from '@mui/x-data-grid';
|
|
||||||
import currencyDict from '../../static/assets/currencies.json';
|
import currencyDict from '../../static/assets/currencies.json';
|
||||||
|
|
||||||
import MediaQuery from 'react-responsive';
|
|
||||||
import FlagWithProps from './FlagWithProps';
|
import FlagWithProps from './FlagWithProps';
|
||||||
import { pn, amountToString } from '../utils/prettyNumbers';
|
|
||||||
import PaymentText from './PaymentText';
|
|
||||||
import DepthChart from './Charts/DepthChart';
|
import DepthChart from './Charts/DepthChart';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
|
||||||
import { apiClient } from '../services/api/index';
|
import { apiClient } from '../services/api/index';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material';
|
import { BarChart, FormatListBulleted, Refresh } from '@mui/icons-material';
|
||||||
|
import BookTable from './BookTable';
|
||||||
|
|
||||||
class BookPage extends Component {
|
class BookPage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -43,11 +32,11 @@ class BookPage extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount = () => {
|
||||||
this.getOrderDetails(2, 0);
|
this.getOrderDetails();
|
||||||
}
|
};
|
||||||
|
|
||||||
getOrderDetails(type, currency) {
|
getOrderDetails() {
|
||||||
this.props.setAppState({ bookLoading: true });
|
this.props.setAppState({ bookLoading: true });
|
||||||
apiClient.get('/api/book/').then((data) =>
|
apiClient.get('/api/book/').then((data) =>
|
||||||
this.props.setAppState({
|
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) => {
|
handleTypeChange = (mouseEvent, val) => {
|
||||||
this.props.setAppState({ type: val });
|
this.props.setAppState({ type: val });
|
||||||
};
|
};
|
||||||
@ -495,45 +112,32 @@ class BookPage extends Component {
|
|||||||
return this.NoOrdersFound();
|
return this.NoOrdersFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const components =
|
if (this.state.view === 'depth') {
|
||||||
this.state.view == 'depth'
|
return (
|
||||||
? [
|
<DepthChart
|
||||||
<DepthChart
|
bookLoading={this.props.bookLoading}
|
||||||
bookLoading={this.props.bookLoading}
|
orders={this.props.bookOrders}
|
||||||
orders={this.props.bookOrders}
|
lastDayPremium={this.props.lastDayPremium}
|
||||||
lastDayPremium={this.props.lastDayPremium}
|
currency={this.props.currency}
|
||||||
currency={this.props.currency}
|
compact={true}
|
||||||
setAppState={this.props.setAppState}
|
setAppState={this.props.setAppState}
|
||||||
limits={this.props.limits}
|
limits={this.props.limits}
|
||||||
/>,
|
maxWidth={(this.props.windowWidth / this.props.theme.typography.fontSize) * 0.8} // EM units
|
||||||
<DepthChart
|
maxHeight={(this.props.windowHeight / this.props.theme.typography.fontSize) * 0.8 - 11} // EM units
|
||||||
bookLoading={this.props.bookLoading}
|
/>
|
||||||
orders={this.props.bookOrders}
|
);
|
||||||
lastDayPremium={this.props.lastDayPremium}
|
} else {
|
||||||
currency={this.props.currency}
|
return (
|
||||||
compact={true}
|
<BookTable
|
||||||
setAppState={this.props.setAppState}
|
loading={this.props.bookLoading}
|
||||||
limits={this.props.limits}
|
orders={this.props.bookOrders}
|
||||||
/>,
|
type={this.props.type}
|
||||||
]
|
currency={this.props.currency}
|
||||||
: [this.bookListTableDesktop(), this.bookListTablePhone()];
|
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
|
||||||
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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getTitle = () => {
|
getTitle = () => {
|
||||||
@ -562,7 +166,7 @@ class BookPage extends Component {
|
|||||||
<Grid className='orderBook' container spacing={1} sx={{ minWidth: 400 }}>
|
<Grid className='orderBook' container spacing={1} sx={{ minWidth: 400 }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{ position: 'fixed', right: '0px', top: '30px' }}
|
sx={{ position: 'fixed', right: '0px', top: '30px' }}
|
||||||
onClick={() => this.setState({ loading: true }) & this.getOrderDetails(2, 0)}
|
onClick={() => this.setState({ loading: true }) & this.getOrderDetails()}
|
||||||
>
|
>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -631,15 +235,11 @@ class BookPage extends Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
{this.props.bookNotFound ? (
|
<Grid item xs={12} align='center'>
|
||||||
<></>
|
<Typography component='h5' variant='h5'>
|
||||||
) : (
|
{this.getTitle()}
|
||||||
<Grid item xs={12} align='center'>
|
</Typography>
|
||||||
<Typography component='h5' variant='h5'>
|
</Grid>
|
||||||
{this.getTitle()}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
<Grid item xs={12} align='center'>
|
<Grid item xs={12} align='center'>
|
||||||
{this.mainView()}
|
{this.mainView()}
|
||||||
</Grid>
|
</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 getNivoScheme from '../NivoScheme';
|
||||||
import median from '../../../utils/match';
|
import median from '../../../utils/match';
|
||||||
import { apiClient } from '../../../services/api/index';
|
import { apiClient } from '../../../services/api/index';
|
||||||
|
import statusBadgeColor from '../../../utils/statusBadgeColor';
|
||||||
|
|
||||||
interface DepthChartProps {
|
interface DepthChartProps {
|
||||||
bookLoading: boolean;
|
bookLoading: boolean;
|
||||||
@ -38,7 +39,8 @@ interface DepthChartProps {
|
|||||||
currency: number;
|
currency: number;
|
||||||
setAppState: (state: object) => void;
|
setAppState: (state: object) => void;
|
||||||
limits: LimitList;
|
limits: LimitList;
|
||||||
compact?: boolean;
|
maxWidth: number;
|
||||||
|
maxHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DepthChart: React.FC<DepthChartProps> = ({
|
const DepthChart: React.FC<DepthChartProps> = ({
|
||||||
@ -48,7 +50,8 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
currency,
|
currency,
|
||||||
setAppState,
|
setAppState,
|
||||||
limits,
|
limits,
|
||||||
compact,
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -61,6 +64,9 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
const [currencyCode, setCurrencyCode] = useState<number>(1);
|
const [currencyCode, setCurrencyCode] = useState<number>(1);
|
||||||
const [center, setCenter] = useState<number>();
|
const [center, setCenter] = useState<number>();
|
||||||
|
|
||||||
|
const height = maxHeight < 20 ? 20 : maxHeight;
|
||||||
|
const width = maxWidth < 20 ? 20 : maxWidth > 72.8 ? 72.8 : maxWidth;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(limits).length === 0) {
|
if (Object.keys(limits).length === 0) {
|
||||||
apiClient.get('/api/limits/').then((data) => {
|
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> = (
|
const generateTooltip: React.FunctionComponent<PointTooltipProps> = (
|
||||||
pointTooltip: PointTooltipProps,
|
pointTooltip: PointTooltipProps,
|
||||||
) => {
|
) => {
|
||||||
@ -293,94 +288,109 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
history.push('/order/' + point.data?.order?.id);
|
history.push('/order/' + point.data?.order?.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return bookLoading || center == undefined || enrichedOrders.length < 1 ? (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: 200, height: 420 }}>
|
<Paper style={{ width: `${width}em`, maxHeight: `${height}em` }}>
|
||||||
<CircularProgress />
|
<Paper variant='outlined'>
|
||||||
</div>
|
{bookLoading || center == undefined || enrichedOrders.length < 1 ? (
|
||||||
) : (
|
<div
|
||||||
<Grid container style={{ paddingTop: 15 }}>
|
style={{
|
||||||
<Grid
|
display: 'flex',
|
||||||
container
|
justifyContent: 'center',
|
||||||
direction='row'
|
paddingTop: `${(height - 3) / 2 - 1}em`,
|
||||||
justifyContent='space-around'
|
height: `${height - 3}em`,
|
||||||
alignItems='flex-start'
|
}}
|
||||||
style={{ position: 'absolute' }}
|
>
|
||||||
>
|
<CircularProgress />
|
||||||
<Grid
|
</div>
|
||||||
container
|
) : (
|
||||||
justifyContent='flex-start'
|
<Grid container style={{ paddingTop: 15 }}>
|
||||||
alignItems='flex-start'
|
<Grid
|
||||||
style={{ paddingLeft: 20 }}
|
container
|
||||||
>
|
direction='row'
|
||||||
<Select variant='standard' value={xType} onChange={(e) => setXType(e.target.value)}>
|
justifyContent='space-around'
|
||||||
<MenuItem value={'premium'}>
|
alignItems='flex-start'
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
style={{ position: 'absolute' }}
|
||||||
{t('Premium')}
|
>
|
||||||
</div>
|
<Grid
|
||||||
</MenuItem>
|
container
|
||||||
<MenuItem value={'base_amount'}>
|
justifyContent='flex-start'
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
alignItems='flex-start'
|
||||||
{t('Price')}
|
style={{ paddingLeft: 20 }}
|
||||||
</div>
|
>
|
||||||
</MenuItem>
|
<Select variant='standard' value={xType} onChange={(e) => setXType(e.target.value)}>
|
||||||
</Select>
|
<MenuItem value={'premium'}>
|
||||||
</Grid>
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
</Grid>
|
{t('Premium')}
|
||||||
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
</div>
|
||||||
<Grid container justifyContent='center' alignItems='center'>
|
</MenuItem>
|
||||||
<Grid item>
|
<MenuItem value={'base_amount'}>
|
||||||
<IconButton onClick={() => setXRange(xRange + rangeSteps)}>
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
<RemoveCircleOutline />
|
{t('Price')}
|
||||||
</IconButton>
|
</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>
|
||||||
<Grid item>
|
)}
|
||||||
<Box justifyContent='center'>
|
</Paper>
|
||||||
{xType === 'base_amount' ? `${center} ${currencyDict[currencyCode]}` : `${center}%`}
|
</Paper>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const UpdateClientDialog = ({
|
|||||||
<Typography>
|
<Typography>
|
||||||
{t(
|
{t(
|
||||||
'The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.',
|
'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>
|
</Typography>
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ const UpdateClientDialog = ({
|
|||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
secondary={t('Download RoboSats {{coordinatorVersion}} APK from Github releases', {
|
secondary={t('Download RoboSats {{coordinatorVersion}} APK from Github releases', {
|
||||||
coordinatorVersion: coordinatorVersion,
|
coordinatorVersion,
|
||||||
})}
|
})}
|
||||||
primary={t('On Android RoboSats app ')}
|
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) => {
|
setAppState = (newState) => {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
};
|
};
|
||||||
@ -106,7 +123,7 @@ export default class HomePage extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='bottomBar'
|
className='bottomBar'
|
||||||
style={{ height: `${40 * fontSizeFactor}px`, width: window.innerWidth }}
|
style={{ height: `${40 * fontSizeFactor}px`, width: this.state.windowWidth }}
|
||||||
>
|
>
|
||||||
<BottomBar
|
<BottomBar
|
||||||
redirectTo={this.redirectTo}
|
redirectTo={this.redirectTo}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import { copyToClipboard } from '../utils/clipboard';
|
|||||||
import { getWebln } from '../utils/webln';
|
import { getWebln } from '../utils/webln';
|
||||||
import { apiClient } from '../services/api';
|
import { apiClient } from '../services/api';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
import RobotAvatar from './Robots/RobotAvatar';
|
||||||
|
import statusBadgeColor from '../utils/statusBadgeColor';
|
||||||
|
|
||||||
class OrderPage extends Component {
|
class OrderPage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -666,19 +667,6 @@ class OrderPage extends Component {
|
|||||||
return null;
|
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 = () => {
|
orderBox = () => {
|
||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
@ -694,7 +682,7 @@ class OrderPage extends Component {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar sx={{ width: 56, height: 56 }}>
|
<ListItemAvatar sx={{ width: 56, height: 56 }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
statusColor={this.statusBadgeColor(this.state.maker_status)}
|
statusColor={statusBadgeColor(this.state.maker_status)}
|
||||||
nickname={this.state.maker_nick}
|
nickname={this.state.maker_nick}
|
||||||
tooltip={t(this.state.maker_status)}
|
tooltip={t(this.state.maker_status)}
|
||||||
orderType={this.state.type}
|
orderType={this.state.type}
|
||||||
@ -726,7 +714,7 @@ class OrderPage extends Component {
|
|||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
avatarClass='smallAvatar'
|
avatarClass='smallAvatar'
|
||||||
statusColor={this.statusBadgeColor(this.state.taker_status)}
|
statusColor={statusBadgeColor(this.state.taker_status)}
|
||||||
nickname={this.state.taker_nick}
|
nickname={this.state.taker_nick}
|
||||||
tooltip={t(this.state.taker_status)}
|
tooltip={t(this.state.taker_status)}
|
||||||
orderType={this.state.type === 0 ? 1 : 0}
|
orderType={this.state.type === 0 ? 1 : 0}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Avatar, Badge, Tooltip } from '@mui/material';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SendReceiveIcon } from '../../Icons';
|
import { SendReceiveIcon } from '../../Icons';
|
||||||
|
|
||||||
interface DepthChartProps {
|
interface Props {
|
||||||
nickname: string;
|
nickname: string;
|
||||||
smooth?: boolean;
|
smooth?: boolean;
|
||||||
style?: object;
|
style?: object;
|
||||||
@ -15,7 +15,7 @@ interface DepthChartProps {
|
|||||||
onLoad?: () => void;
|
onLoad?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RobotAvatar: React.FC<DepthChartProps> = ({
|
const RobotAvatar: React.FC<Props> = ({
|
||||||
nickname,
|
nickname,
|
||||||
orderType,
|
orderType,
|
||||||
statusColor,
|
statusColor,
|
||||||
|
|||||||
@ -15,8 +15,8 @@ export const checkVer: (
|
|||||||
const patchAvailable = !updateAvailable && patch > Number(semver[2]);
|
const patchAvailable = !updateAvailable && patch > Number(semver[2]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateAvailable: updateAvailable,
|
updateAvailable,
|
||||||
patchAvailable: patchAvailable,
|
patchAvailable,
|
||||||
coordinatorVersion: `v${major}.${minor}.${patch}`,
|
coordinatorVersion: `v${major}.${minor}.${patch}`,
|
||||||
clientVersion: `v${semver[0]}.${semver[1]}.${semver[2]}`,
|
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;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 929px) {
|
@media (max-height: 725px) {
|
||||||
.appCenter:has(> div.MuiGrid-root:first-child, > div.MuiBox-root:first-child) {
|
.appCenter:has(> div.MuiGrid-root:first-child, > div.MuiBox-root:first-child) {
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
margin-top: 12px;
|
margin-top: 1em;
|
||||||
padding-bottom: 25px;
|
padding-bottom: 3em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user