import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import {
Box,
Grid,
Dialog,
Typography,
Paper,
Stack,
ListItemButton,
ListItemText,
ListItemAvatar,
useTheme,
CircularProgress,
LinearProgress,
IconButton,
} from '@mui/material';
import { DataGrid, GridPagination } 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';
// Icons
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
interface Props {
loading: boolean;
refreshing: boolean;
clickRefresh: () => void;
orders: Order[];
type: number;
currency: number;
maxWidth: number;
maxHeight: number;
fullWidth: number;
fullHeight: number;
defaultFullscreen: boolean;
}
const BookTable = ({
loading,
refreshing,
clickRefresh,
orders,
type,
currency,
maxWidth,
maxHeight,
fullWidth,
fullHeight,
defaultFullscreen,
}: Props): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const history = useHistory();
const [pageSize, setPageSize] = useState(0);
const [fullscreen, setFullscreen] = useState(defaultFullscreen);
// all sizes in 'em'
const fontSize = theme.typography.fontSize;
const verticalHeightFrame = 6.9075;
const verticalHeightRow = 3.25;
const defaultPageSize = Math.max(
Math.floor(
((fullscreen ? fullHeight * 0.875 : maxHeight) - verticalHeightFrame) / verticalHeightRow,
),
1,
);
const height = defaultPageSize * verticalHeightRow + verticalHeightFrame;
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 (
);
},
};
};
const robotSmallObj = function (width: number, hide: boolean) {
return {
hide,
field: 'maker_nick',
headerName: t('Robot'),
width: width * fontSize,
renderCell: (params) => {
return (
);
},
};
};
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 (
{amountToString(
params.row.amount,
params.row.has_range,
params.row.min_amount,
params.row.max_amount,
)}
);
},
};
};
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 (
{currencyCode + ' '}
);
},
};
};
const paymentObj = function (width: number, hide: boolean) {
return {
hide,
field: 'payment_method',
headerName: t('Payment Method'),
width: width * fontSize,
renderCell: (params) => {
return (
);
},
};
};
const paymentSmallObj = function (width: number, hide: boolean) {
return {
hide,
field: 'payment_icons',
headerName: t('Pay'),
width: width * fontSize,
renderCell: (params) => {
return (
);
},
};
};
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 (
{`${pn(params.row.price)} ${currencyCode}/BTC`}
);
},
};
};
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 (
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
);
},
};
};
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 {hours > 0 ? `${hours}h` : `${minutes}m`}
;
},
};
};
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 (
{hours > 0 ? `${hours}h` : `${minutes}m`}
);
},
};
};
const satoshisObj = function (width: number, hide: boolean) {
return {
hide,
field: 'satoshis_now',
headerName: t('Sats now'),
type: 'number',
width: width * fontSize,
renderCell: (params) => {
return (
{params.row.satoshis_now > 1000000
? `${pn(Math.round(params.row.satoshis_now / 10000) / 100)} M`
: `${pn(Math.round(params.row.satoshis_now / 1000))} K`}
);
},
};
};
const idObj = function (width: number, hide: boolean) {
return {
hide,
field: 'id',
headerName: 'Order ID',
width: width * fontSize,
renderCell: (params) => {
return (
{`#${params.row.id}`}
);
},
};
};
const bondObj = function (width: number, hide: boolean) {
return {
hide,
field: 'bond_size',
headerName: t('Bond'),
type: 'number',
width: width * fontSize,
renderCell: (params: any) => {
return {`${Number(params.row.bond_size)}%`}
;
},
};
};
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,
},
},
paymentMethod: {
priority: 4,
order: 6,
normal: {
width: 12.85,
object: paymentObj,
},
small: {
width: 4.4,
object: paymentSmallObj,
},
},
robot: {
priority: 5,
order: 1,
normal: {
width: 17.14,
object: robotObj,
},
small: {
width: 4.1,
object: robotSmallObj,
},
},
price: {
priority: 6,
order: 10,
normal: {
width: 10,
object: priceObj,
},
},
expires_at: {
priority: 7,
order: 7,
normal: {
width: 5,
object: expiryObj,
},
},
escrow_duration: {
priority: 8,
order: 8,
normal: {
width: 4.8,
object: timerObj,
},
},
satoshisNow: {
priority: 9,
order: 9,
normal: {
width: 6,
object: satoshisObj,
},
},
type: {
priority: 10,
order: 2,
normal: {
width: 4.3,
object: typeObj,
},
},
bond: {
priority: 11,
order: 10,
normal: {
width: 4.2,
object: bondObj,
},
},
id: {
priority: 12,
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(fullscreen ? fullWidth : maxWidth);
const gridComponents = {
LoadingOverlay: LinearProgress,
NoResultsOverlay: () => (
{t('Filter has no results')}
),
Footer: () => (
setFullscreen(!fullscreen)}>
{fullscreen ? : }
),
};
if (!fullscreen) {
return (
(order.type == type || type == null) && (order.currency == currency || currency == 0),
)}
loading={loading || refreshing}
columns={columns}
components={gridComponents}
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.
/>
);
} else {
return (
);
}
};
export default BookTable;