core/websrc/components/Statusbar.tsx
2020-12-24 16:52:42 +00:00

293 lines
10 KiB
TypeScript

import React, {Component} from "react";
import {EventName, GetProfitLossMessage, NewTickMessage, PutProfitLossMessage} from "../types";
import {socket} from "../index";
import {DateTime} from "luxon";
type QuoteStatusProps = {
percentage?: boolean,
quote_symbol?: string,
amount: number,
subtitle: string,
sign?: boolean
}
export class QuoteStatus extends Component<QuoteStatusProps> {
private whole: number;
private decimal: number;
private sign: string;
private signClass: string;
constructor(props) {
super(props);
this.deriveProps()
}
deriveProps() {
this.whole = Math.abs(Math.trunc(this.props.amount))
this.decimal = Math.trunc(this.props.amount % 1 * 100)
this.sign = this.props.amount > 0 ? "+" : "-"
this.signClass = this.props.amount > 0 ? "text-green-500" : "text-red-500"
}
renderSign() {
if (this.props.sign) {
return (
<span
className={this.signClass}>{this.sign}</span>
)
}
return null
}
symbolOrPercentageRender() {
if (this.props.percentage) {
return (
<>
<span className="text-4xl text-bold align-top">
{this.renderSign()}</span>
<span className="text-5xl">{Math.abs(this.props.amount).toFixed(2)}</span>
<span className="text-3xl align-top">%</span>
</>
)
} else {
return (
<>
<span className="text-4xl text-bold align-top">{this.renderSign()}{this.props.quote_symbol}</span>
<span className="text-5xl">{this.whole.toLocaleString()}</span>
<span className="text-3xl align-top">.{Math.abs(this.decimal)}</span>
</>
)
}
}
render() {
return (
<>
<div className="text-gray-700 mb-2">
{this.symbolOrPercentageRender()}
</div>
<div className="text-sm uppercase text-gray-300 tracking-wide">
{this.props.subtitle}
</div>
</>
)
}
}
type DateButtonProps = {
label: string,
onClick: any,
selected_default?: boolean
}
type DateButtonState = {
selected: boolean,
}
class DateButton extends Component<DateButtonProps, DateButtonState> {
private classSelected: string = "appearance-none py-4 text-blue-600 border-b border-blue-600 mr-3";
private classNotSelected: string = "appearance-none py-4 text-gray-600 border-b border-transparent hover:border-gray-800 mr-3";
private currentClass: string;
state = {
selected: this.props.selected_default
}
constructor(props) {
super(props);
this.updateClass()
}
onClick() {
this.setState({selected: !this.state.selected}, this.updateClass)
this.props.onClick()
}
updateClass() {
this.currentClass = this.state.selected ? this.classSelected : this.classNotSelected
}
render() {
return (
<button key={this.props.label} type="button"
className={this.currentClass} onClick={this.onClick.bind(this)}>
{this.props.label}
</button>
)
}
}
const PeriodUnit = {
SECOND: "second",
MINUTE: "minute",
HOUR: "hour",
DAY: "day",
WEEK: "week",
MONTH: "month",
YEAR: "year"
}
type StatusBarState = {
pl_period_unit: string,
pl_period_amount: number,
pl: number,
pl_perc: number
}
export class Statusbar extends Component<NewTickMessage, StatusBarState> {
constructor(props) {
super(props);
this.state = {
pl_period_unit: PeriodUnit.WEEK,
pl_period_amount: 1,
pl: 0.0,
pl_perc: 0.0
}
this.emitGetProfitLoss = this.emitGetProfitLoss.bind(this)
}
componentDidMount() {
socket.on(EventName.PutProfitLoss, (data: PutProfitLossMessage) => {
this.setState({
pl: data.pl,
pl_perc: data.pl_perc
})
})
socket.on(EventName.FirstConnect, this.emitGetProfitLoss)
socket.on(EventName.NewTick, this.emitGetProfitLoss)
}
durationObjectfromStr(str: string) {
switch (str) {
case PeriodUnit.MINUTE:
return {
minutes: this.state.pl_period_amount
}
case PeriodUnit.HOUR:
return {
hours: this.state.pl_period_amount
}
case PeriodUnit.DAY:
return {
days: this.state.pl_period_amount
}
case PeriodUnit.WEEK:
return {
weeks: this.state.pl_period_amount
}
case PeriodUnit.MONTH:
return {
months: this.state.pl_period_amount
}
case PeriodUnit.YEAR:
return {
years: this.state.pl_period_amount
}
default:
return {}
}
}
emitGetProfitLoss() {
const message: GetProfitLossMessage = {
start: DateTime.local().minus(this.durationObjectfromStr(this.state.pl_period_unit)).toMillis(),
end: DateTime.local().toMillis()
}
socket.emit(EventName.GetProfitLoss, message)
}
changeProfitLossPeriod(amount: number, unit: string) {
this.setState({
pl_period_amount: amount,
pl_period_unit: unit
}, this.emitGetProfitLoss)
}
render() {
return (
<div className="bg-white border-t border-b sm:border-l sm:border-r sm:rounded-lg shadow flex-grow mb-6">
<div className="bg-gray-50 rounded-tl-lg rounded-tr-lg border-b px-6">
<div className="flex justify-between -mb-px">
<div className="lg:hidden text-blue-600 py-4 text-lg">
Price Charts
</div>
<div className="hidden lg:flex">
<button type="button"
className="appearance-none py-4 text-blue-600 border-b border-blue-dark mr-6">
Bitcoin
</button>
</div>
<div className="flex text-sm">
<DateButton key={PeriodUnit.MINUTE} label={"1m"}
onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/>
<DateButton key={PeriodUnit.DAY} label={"1D"}
onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.DAY)}/>
<DateButton key={PeriodUnit.WEEK} label={"1W"} selected_default={true}
onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.WEEK)}/>
<DateButton key={PeriodUnit.MONTH} label={"1M"}
onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MONTH)}/>
<DateButton key={PeriodUnit.YEAR} label={"1Y"}
onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.YEAR)}/>
{/*<DateButton label={"ALL"} onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/>*/}
</div>
</div>
</div>
<div className="flex items-center px-6 lg:hidden">
<div className="flex-grow flex-no-shrink py-6">
<div className="text-gray-700 mb-2">
<span className="text-3xl align-top">CA$</span>
<span className="text-5xl">21,404</span>
<span className="text-3xl align-top">.74</span>
</div>
<div className="text-green-300 text-sm">
&uarr; CA$12,955.35 (154.16%)
</div>
</div>
<div className="flex-shrink w-32 inline-block relative">
<select
className="block appearance-none w-full bg-white border border-grey-light px-4 py-2 pr-8 rounded">
<option>BTC</option>
</select>
<div className="pointer-events-none absolute pin-y pin-r flex items-center px-2 text-gray-300">
<svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</div>
</div>
<div className="hidden lg:flex">
<div className="w-1/3 text-center py-8">
<div className="border-r">
<QuoteStatus key={this.props.price} quote_symbol={"USD"} amount={this.props.price}
subtitle={"Bitcoin price"}/>
</div>
</div>
<div className="w-1/3 text-center py-8">
<div className="border-r">
<QuoteStatus key={this.state.pl} quote_symbol={"USD"} sign={true} amount={this.state.pl}
subtitle={"since last ".concat(this.state.pl_period_unit)}/>
</div>
</div>
<div className="w-1/3 text-center py-8">
<div>
<QuoteStatus key={this.state.pl_perc} percentage={true} sign={true}
amount={this.state.pl_perc}
subtitle={"since last ".concat(this.state.pl_period_unit)}/>
</div>
</div>
</div>
</div>
)
}
}