This commit is contained in:
cupcakearmy
2019-05-22 20:34:58 +02:00
parent 7b81984ce7
commit dcbc4bcdd7
73 changed files with 4138 additions and 0 deletions

105
www/components/chart.js Normal file
View File

@@ -0,0 +1,105 @@
import React, { useEffect, useRef } from 'react'
import ChartJS from 'chart.js'
import { capitalize } from '../utils/misc'
const Chart = ({ stats }) => {
const canvas = useRef(undefined)
const { _error, ...users } = stats
const chartColors = [
'rgb(255,74,109)',
'rgb(255,194,81)',
'rgb(63,197,255)',
'rgb(46,192,37)',
]
const formatData = (data) => {
const sorted = Object.entries(data).sort((a, b) => a[1] < b[1] ? 1 : -1)
const positive = sorted.filter(([name, amount]) => amount >= 0).reverse()
const negative = sorted.filter(([name, amount]) => amount < 0)
const getProgressiveValues = (arr) => {
if (arr.length === 0) return []
const tmp = [arr[0]]
let highest = arr[0][1]
for (const cur of arr.slice(1)) {
const delta = cur[1] - highest
tmp.push([cur[0], delta])
highest += delta
}
return tmp
}
return [
...getProgressiveValues(positive),
...getProgressiveValues(negative),
]
}
useEffect(() => {
console.log(`Error margin: ${_error}`)
// TODO: Consistent color scheme
const data = {
labels: ['Current'],
datasets: formatData(users).map(([name, amount], i) => ({
label: name,
backgroundColor: chartColors[i],
data: [amount],
})),
}
const chart = new ChartJS(canvas.current, {
type: 'horizontalBar',
data,
options: {
tooltips: {
enabled: false,
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
display: false,
stacked: true,
}],
},
},
})
}, [canvas])
return <React.Fragment>
<div className={'chart-container'}>
<canvas ref={canvas}/>
</div>
<br/>
<div className={'text-center'}>
{Object.entries(users).map(([user, value], i) => <span key={i} className={'label m-1'}>
{capitalize(user)} <b>{value}</b>
</span>)}
{/*<div className={'mt-2'}><small>Error margin:{_error}</small></div>*/}
</div>
{/* language=CSS */}
<style jsx>{`
.chart-container {
width: 100%;
height: 8em;
position: relative;
}
`}</style>
</React.Fragment>
}
export default Chart

View File

@@ -0,0 +1,14 @@
import React from 'react'
import { Duration } from 'uhrwerk'
export const timePassedSinceTimestamp = since => new Duration(Date.now() - since, 'milliseconds').humanize() + ' ago'
export const formatPrice = price => {
const [int, float] = price.toFixed(2).split('.')
return <span className={'mr-1'}>
<b>{int}
<small>,{float}</small>
</b>
</span>
}

110
www/components/layout.js Executable file
View File

@@ -0,0 +1,110 @@
import React from 'react'
import Head from 'next/head'
import Link from 'next/link'
import { logout } from '../utils/auth'
import { getRandomSlogan } from '../utils/misc'
const Layout = ({ children }) => {
const title = getRandomSlogan()
return <React.Fragment>
<Head>
<title>{title}</title>
{/* https://www.flaticon.com/packs/zoo-20 */}
<link rel="stylesheet" href="/static/css/spectre.min.css"/>
<link rel="stylesheet" href="/static/css/spectre-exp.min.css"/>
<link rel="stylesheet" href="/static/css/spectre-icons.min.css"/>
<link rel="stylesheet" href="/static/css/main.css"/>
<meta name="viewport" content="initial-scale=1.0, width=device-width"/>
</Head>
<div id='navbar-container'>
<header className="navbar">
<section className="navbar-section">
<Link href='/'>
<a className="btn btn-link">
<img src={'/static/icons/ui/stats.svg'} alt={'overview'}/>
<span className={'hide-sm'}> Overview</span>
</a>
</Link>
<Link href='/new'>
<a className="btn btn-link">
<img src={'/static/icons/ui/add.svg'} alt={'add'}/>
<span className={'hide-sm'}> New</span>
</a>
</Link>
</section>
<section className="navbar-center hide-sm">
<b>{title}</b>
</section>
<section className="navbar-section">
<Link href='/me'>
<a className="btn btn-link">
<img src={'/static/icons/ui/me.svg'} alt={'profile'}/>
<span className={'hide-sm'}> Me</span>
</a>
</Link>
<a onClick={logout} className="btn btn-link">
<img src={'/static/icons/ui/logout.svg'} alt={'add'}/>
<span className={'hide-sm'}> Logout</span>
</a>
</section>
</header>
</div>
<main>
<div className="container">
<div id='content'>
{children}
</div>
</div>
</main>
{/* language=CSS */}
<style jsx>{`
main {
padding: 6em 0;
}
#navbar-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
padding: 1em;
box-shadow: 0 -0.4em 1em -0.5em;
background-color: var(--clr-white);
z-index: 10;
}
#navbar-container header {
max-width: 50em;
margin: auto;
}
#content {
width: 100%;
max-width: 32em;
margin: auto;
}
.navbar a {
color: inherit;
}
.navbar a img {
height: 1.5em;
width: 1.5em;
vertical-align: bottom;
}
`}</style>
</React.Fragment>
}
export default Layout

View File

@@ -0,0 +1,60 @@
import { callAPI } from '../utils/api'
import { formatPrice, timePassedSinceTimestamp } from './humanize'
import { capitalize, getAvatarOfFallback } from '../utils/misc'
import React from 'react'
const deletePurchase = id => callAPI(null, {
url: `/api/purchases/${id}`,
method: 'delete',
}).then(() => location.reload()).catch(() => alert('There was a problem deleting the purchase'))
const Purchase = ({ purchase, me }) => <div className="purchases-item tile tile-centered">
<div className="tile-icon">
<figure className="avatar avatar-lg">
<img alt="avatar-icon" src={`/static/icons/animals/${getAvatarOfFallback(purchase.payer.avatar)}.svg`}/>
</figure>
</div>
<div className="tile-content">
<div className="tile-title">
{formatPrice(purchase.price)}
<small className={'float-right'}>{timePassedSinceTimestamp(purchase.when)}</small>
</div>
<small className="tile-subtitle text-gray">
<b>{capitalize(purchase.payer.name)}</b>
<span className={'float-right'}>
{purchase.debtors.map(debtor => debtor.name).map(capitalize).join(' · ')}
</span>
</small>
</div>
<div className="tile-action">
<div className="dropdown dropdown-right">
<a className="btn btn-link dropdown-toggle" tabIndex="0">
<i className="icon icon-more-vert"/>
</a>
<ul className="menu">
<li className="menu-item">
{purchase.payer.id === me.id
? <a onClick={() => deletePurchase(purchase.id)}>
<i className="icon icon-delete"/> Delete
</a>
: <span>Only the payer can cancel the purchase</span>
}
</li>
</ul>
</div>
</div>
{/* language=CSS */}
<style jsx>{`
.purchases-item {
margin-bottom: 1.5em;
}
.purchases-item .tile-subtitle {
display: block;
}
`}</style>
</div>
export default Purchase