satistics done

This commit is contained in:
keyan 2021-12-16 14:02:17 -06:00
parent d92f58aaf4
commit bbc34edf51
8 changed files with 119 additions and 22 deletions

View File

@ -3,6 +3,7 @@ import { UserInputError, AuthenticationError } from 'apollo-server-micro'
import serialize from './serial' import serialize from './serial'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import lnpr from 'bolt11' import lnpr from 'bolt11'
import { SELECT } from './item'
export default { export default {
Query: { Query: {
@ -86,9 +87,29 @@ export default {
AND created_at <= $2)`) AND created_at <= $2)`)
} }
// TODO if (include.has('stacked')) {
// 1. union invoices and withdrawals (check) queries.push(
// 2. add to union spending and receiving `(SELECT ('stacked' || "Item".id) as id, "Item".id as "factId", NULL as bolt11,
MAX("ItemAct".created_at) as "createdAt", sum("ItemAct".sats) * 1000 as msats,
0 as "msatsFee", NULL as status, 'stacked' as type
FROM "ItemAct"
JOIN "Item" on "ItemAct"."itemId" = "Item".id
WHERE "ItemAct"."userId" <> $1 AND "ItemAct".act <> 'BOOST'
AND "Item"."userId" = $1 AND "ItemAct".created_at <= $2
GROUP BY "Item".id)`)
}
if (include.has('spent')) {
queries.push(
`(SELECT ('spent' || "Item".id) as id, "Item".id as "factId", NULL as bolt11,
MAX("ItemAct".created_at) as "createdAt", sum("ItemAct".sats) * 1000 as msats,
0 as "msatsFee", NULL as status, 'spent' as type
FROM "ItemAct"
JOIN "Item" on "ItemAct"."itemId" = "Item".id
WHERE "ItemAct"."userId" = $1
AND "ItemAct".created_at <= $2
GROUP BY "Item".id)`)
}
if (queries.length === 0) { if (queries.length === 0) {
return { return {
@ -103,8 +124,6 @@ export default {
OFFSET $3 OFFSET $3
LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset) LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset)
console.log(history)
history = history.map(f => { history = history.map(f => {
if (f.bolt11) { if (f.bolt11) {
const inv = lnpr.decode(f.bolt11) const inv = lnpr.decode(f.bolt11)
@ -120,6 +139,9 @@ export default {
} }
switch (f.type) { switch (f.type) {
case 'withdrawal': case 'withdrawal':
f.msats = (-1 * f.msats) - f.msatsFee
break
case 'spent':
f.msats *= -1 f.msats *= -1
break break
default: default:
@ -129,8 +151,6 @@ export default {
return f return f
}) })
console.log(history)
return { return {
cursor: history.length === LIMIT ? nextCursorEncoded(decodedCursor) : null, cursor: history.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
facts: history facts: history
@ -251,5 +271,19 @@ export default {
satsPaid: w => Math.floor(w.msatsPaid / 1000), satsPaid: w => Math.floor(w.msatsPaid / 1000),
satsFeePaying: w => Math.floor(w.msatsFeePaying / 1000), satsFeePaying: w => Math.floor(w.msatsFeePaying / 1000),
satsFeePaid: w => Math.floor(w.msatsFeePaid / 1000) satsFeePaid: w => Math.floor(w.msatsFeePaid / 1000)
},
Fact: {
item: async (fact, args, { models }) => {
if (fact.type !== 'spent' && fact.type !== 'stacked') {
return null
}
const [item] = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = $1`, Number(fact.factId))
return item
}
} }
} }

View File

@ -46,9 +46,10 @@ export default gql`
createdAt: String! createdAt: String!
msats: Int! msats: Int!
msatsFee: Int msatsFee: Int
status: String! status: String
type: String! type: String!
description: String description: String
item: Item
} }
type History { type History {

View File

@ -38,9 +38,14 @@ function Parent ({ item, rootText }) {
) )
} }
const truncateString = (string = '', maxLength = 140) =>
string.length > maxLength
? `${string.substring(0, maxLength)} […]`
: string
export default function Comment ({ export default function Comment ({
item, children, replyOpen, includeParent, item, children, replyOpen, includeParent,
rootText, noComments, noReply rootText, noComments, noReply, truncate
}) { }) {
const [edit, setEdit] = useState() const [edit, setEdit] = useState()
const [collapse, setCollapse] = useState(false) const [collapse, setCollapse] = useState(false)
@ -129,7 +134,9 @@ export default function Comment ({
) )
: ( : (
<div className={styles.text}> <div className={styles.text}>
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text> <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>
{truncate ? truncateString(item.text) : item.text}
</Text>
</div> </div>
)} )}
</div> </div>

View File

@ -132,7 +132,7 @@ export default function UserHeader ({ user }) {
</Nav.Item> </Nav.Item>
{isMe && {isMe &&
<Nav.Item> <Nav.Item>
<Link href='/satistics?inc=invoice,withdrawal,stacked,spent' passHref> <Link href='/satistics?inc=invoice,withdrawal' passHref>
<Nav.Link eventKey='/satistics'>satistics</Nav.Link> <Nav.Link eventKey='/satistics'>satistics</Nav.Link>
</Link> </Link>
</Nav.Item>} </Nav.Item>}

View File

@ -1,4 +1,5 @@
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import { ITEM_FIELDS } from './items'
export const INVOICE = gql` export const INVOICE = gql`
query Invoice($id: ID!) { query Invoice($id: ID!) {
@ -25,6 +26,8 @@ export const WITHDRAWL = gql`
}` }`
export const WALLET_HISTORY = gql` export const WALLET_HISTORY = gql`
${ITEM_FIELDS}
query WalletHistory($cursor: String, $inc: String) { query WalletHistory($cursor: String, $inc: String) {
walletHistory(cursor: $cursor, inc: $inc) { walletHistory(cursor: $cursor, inc: $inc) {
facts { facts {
@ -37,6 +40,10 @@ export const WALLET_HISTORY = gql`
status status
type type
description description
item {
...ItemFields
text
}
} }
cursor cursor
} }

View File

@ -1,7 +1,6 @@
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Link from 'next/link' import Link from 'next/link'
import { Table } from 'react-bootstrap' import { Table } from 'react-bootstrap'
import useDarkMode from 'use-dark-mode'
import { getGetServerSideProps } from '../api/ssrApollo' import { getGetServerSideProps } from '../api/ssrApollo'
import Layout from '../components/layout' import Layout from '../components/layout'
import { useMe } from '../components/me' import { useMe } from '../components/me'
@ -14,10 +13,16 @@ import Check from '../svgs/check-double-line.svg'
import ThumbDown from '../svgs/thumb-down-fill.svg' import ThumbDown from '../svgs/thumb-down-fill.svg'
import { Checkbox, Form } from '../components/form' import { Checkbox, Form } from '../components/form'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Item from '../components/item'
import Comment from '../components/comment'
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY) export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
function satusClass (status) { function satusClass (status) {
if (!status) {
return ''
}
switch (status) { switch (status) {
case 'CONFIRMED': case 'CONFIRMED':
return '' return ''
@ -82,14 +87,30 @@ function Satus ({ status }) {
) )
} }
function Detail ({ fact }) {
if (!fact.item) {
return (
<>
<div className={satusClass(fact.status)}>
{fact.description || 'no description'}
</div>
<Satus status={fact.status} />
</>
)
}
if (fact.item.title) {
return <div className={styles.itemWrapper}><Item item={fact.item} /></div>
}
return <div className={styles.commentWrapper}><Comment item={fact.item} includeParent noReply truncate /></div>
}
export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) { export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) {
const me = useMe() const me = useMe()
const { value: darkMode } = useDarkMode()
const router = useRouter() const router = useRouter()
const { data, fetchMore } = useQuery(WALLET_HISTORY, { variables: { inc: router.query.inc } }) const { data, fetchMore } = useQuery(WALLET_HISTORY, { variables: { inc: router.query.inc } })
console.log(router.query.inc, data)
function filterRoutePush (filter, add) { function filterRoutePush (filter, add) {
const inc = new Set(router.query.inc.split(',')) const inc = new Set(router.query.inc.split(','))
inc.delete('') inc.delete('')
@ -109,6 +130,16 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
return inc.has(filter) return inc.has(filter)
} }
function href (fact) {
switch (fact.type) {
case 'withdrawal':
case 'invoice':
return `/${fact.type}s/${fact.factId}`
default:
return `/items/${fact.factId}`
}
}
if (data) { if (data) {
({ walletHistory: { facts, cursor } } = data) ({ walletHistory: { facts, cursor } } = data)
} }
@ -149,7 +180,7 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
/> />
</div> </div>
</Form> </Form>
<Table className='mt-3 mb-0' bordered hover size='sm' variant={darkMode ? 'dark' : undefined}> <Table className='mt-3 mb-0' bordered hover size='sm'>
<thead> <thead>
<tr> <tr>
<th className={styles.type}>type</th> <th className={styles.type}>type</th>
@ -159,14 +190,11 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
</thead> </thead>
<tbody> <tbody>
{facts.map((f, i) => ( {facts.map((f, i) => (
<Link href={`${f.type}s/${f.factId}`} key={f.id}> <Link href={href(f)} key={f.id}>
<tr className={styles.row}> <tr className={styles.row}>
<td className={`${styles.type} ${satusClass(f.status)}`}>{f.type}</td> <td className={`${styles.type} ${satusClass(f.status)}`}>{f.type}</td>
<td className={styles.description}> <td className={styles.description}>
<div className={satusClass(f.status)}> <Detail fact={f} />
{f.description || 'no description'}
</div>
<Satus status={f.status} />
</td> </td>
<td className={`${styles.sats} ${satusClass(f.status)}`}>{f.msats / 1000}</td> <td className={`${styles.sats} ${satusClass(f.status)}`}>{f.msats / 1000}</td>
</tr> </tr>

View File

@ -59,8 +59,13 @@ $tooltip-bg: #5c8001;
src: url(/Lightningvolt-xoqm.ttf); src: url(/Lightningvolt-xoqm.ttf);
} }
@media screen and (min-width: 767px) {
.table-sm th, .table-sm td {
padding: .3rem .75rem;
}
}
.table-sm th, .table-sm td { .table-sm th, .table-sm td {
padding: .3rem .75rem;
line-height: 1.2rem; line-height: 1.2rem;
} }
@ -77,6 +82,11 @@ $tooltip-bg: #5c8001;
border-color: var(--theme-borderColor); border-color: var(--theme-borderColor);
} }
.table-hover tbody tr:hover {
color: var(--theme-color);
background-color: var(--theme-clickToContextColor);
}
body { body {
background: var(--theme-body); background: var(--theme-body);
color: var(--theme-color); color: var(--theme-color);

View File

@ -16,3 +16,13 @@
.row { .row {
cursor: pointer; cursor: pointer;
} }
.itemWrapper {
padding-top: .25rem;
margin-bottom: -.25rem;
}
.commentWrapper {
padding-top: .2rem;
padding-bottom: .4rem;
}