inv & with satistics + filtering

This commit is contained in:
keyan 2021-12-16 11:27:12 -06:00
parent 06f5ed731e
commit d92f58aaf4
9 changed files with 159 additions and 84 deletions

View File

@ -49,18 +49,18 @@ export default {
connectAddress: async (parent, args, { lnd }) => { connectAddress: async (parent, args, { lnd }) => {
return process.env.LND_CONNECT_ADDRESS return process.env.LND_CONNECT_ADDRESS
}, },
walletHistory: async (parent, { cursor }, { me, models, lnd }) => { walletHistory: async (parent, { cursor, inc }, { me, models, lnd }) => {
const decodedCursor = decodeCursor(cursor) const decodedCursor = decodeCursor(cursor)
if (!me) { if (!me) {
throw new AuthenticationError('you must be logged in') throw new AuthenticationError('you must be logged in')
} }
// TODO const include = new Set(inc.split(','))
// 1. union invoices and withdrawals (check) const queries = []
// 2. add to union spending and receiving
let history = await models.$queryRaw(` if (include.has('invoice')) {
(SELECT id, bolt11, created_at as "createdAt", queries.push(
`(SELECT ('invoice' || id) as id, id as "factId", bolt11, created_at as "createdAt",
COALESCE("msatsReceived", "msatsRequested") as msats, NULL as "msatsFee", COALESCE("msatsReceived", "msatsRequested") as msats, NULL as "msatsFee",
CASE WHEN "confirmedAt" IS NOT NULL THEN 'CONFIRMED' CASE WHEN "confirmedAt" IS NOT NULL THEN 'CONFIRMED'
WHEN "expiresAt" <= $2 THEN 'EXPIRED' WHEN "expiresAt" <= $2 THEN 'EXPIRED'
@ -69,11 +69,12 @@ export default {
'invoice' as type 'invoice' as type
FROM "Invoice" FROM "Invoice"
WHERE "userId" = $1 WHERE "userId" = $1
AND created_at <= $2 AND created_at <= $2)`)
ORDER BY created_at desc }
LIMIT ${LIMIT}+$3)
UNION ALL if (include.has('withdrawal')) {
(SELECT id, bolt11, created_at as "createdAt", queries.push(
`(SELECT ('withdrawal' || id) as id, id as "factId", bolt11, created_at as "createdAt",
CASE WHEN status = 'CONFIRMED' THEN "msatsPaid" CASE WHEN status = 'CONFIRMED' THEN "msatsPaid"
ELSE "msatsPaying" END as msats, ELSE "msatsPaying" END as msats,
CASE WHEN status = 'CONFIRMED' THEN "msatsFeePaid" CASE WHEN status = 'CONFIRMED' THEN "msatsFeePaid"
@ -82,13 +83,28 @@ export default {
'withdrawal' as type 'withdrawal' as type
FROM "Withdrawl" FROM "Withdrawl"
WHERE "userId" = $1 WHERE "userId" = $1
AND created_at <= $2 AND created_at <= $2)`)
ORDER BY created_at desc }
LIMIT ${LIMIT}+$3)
// TODO
// 1. union invoices and withdrawals (check)
// 2. add to union spending and receiving
if (queries.length === 0) {
return {
cursor: null,
facts: []
}
}
let history = await models.$queryRaw(`
${queries.join(' UNION ALL ')}
ORDER BY "createdAt" DESC ORDER BY "createdAt" DESC
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)
@ -113,6 +129,8 @@ 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

View File

@ -30,7 +30,7 @@ export default async function getSSRApolloClient (req, me = null) {
} }
export function getGetServerSideProps (query, variables = null, foundField) { export function getGetServerSideProps (query, variables = null, foundField) {
return async function ({ req, params }) { return async function ({ req, query: params }) {
const client = await getSSRApolloClient(req) const client = await getSSRApolloClient(req)
const { error, data } = await client.query({ const { error, data } = await client.query({
query, query,

View File

@ -5,7 +5,7 @@ export default gql`
invoice(id: ID!): Invoice! invoice(id: ID!): Invoice!
withdrawl(id: ID!): Withdrawl! withdrawl(id: ID!): Withdrawl!
connectAddress: String! connectAddress: String!
walletHistory(cursor: String): History walletHistory(cursor: String, inc: String): History
} }
extend type Mutation { extend type Mutation {
@ -41,6 +41,7 @@ export default gql`
type Fact { type Fact {
id: ID! id: ID!
factId: ID!
bolt11: String bolt11: String
createdAt: String! createdAt: String!
msats: Int! msats: Int!

View File

@ -175,16 +175,16 @@ export function Input ({ label, groupClassName, ...props }) {
) )
} }
export function Checkbox ({ children, label, extra, handleChange, ...props }) { export function Checkbox ({ children, label, extra, handleChange, inline, ...props }) {
// React treats radios and checkbox inputs differently other input types, select, and textarea. // React treats radios and checkbox inputs differently other input types, select, and textarea.
// Formik does this too! When you specify `type` to useField(), it will // Formik does this too! When you specify `type` to useField(), it will
// return the correct bag of props for you // return the correct bag of props for you
const [field, { value }] = useField({ ...props, type: 'checkbox' }) const [field] = useField({ ...props, type: 'checkbox' })
return ( return (
<div className={value ? styles.checkboxChecked : styles.checkboxUnchecked}>
<BootstrapForm.Check <BootstrapForm.Check
custom custom
id={props.id || props.name} id={props.id || props.name}
inline={inline}
> >
<BootstrapForm.Check.Input <BootstrapForm.Check.Input
{...field} {...props} type='checkbox' onChange={(e) => { {...field} {...props} type='checkbox' onChange={(e) => {
@ -200,8 +200,6 @@ export function Checkbox ({ children, label, extra, handleChange, ...props }) {
</div>} </div>}
</BootstrapForm.Check.Label> </BootstrapForm.Check.Label>
</BootstrapForm.Check> </BootstrapForm.Check>
{children}
</div>
) )
} }

View File

@ -113,7 +113,7 @@ export default function UserHeader ({ user }) {
</div> </div>
<Nav <Nav
className={styles.nav} className={styles.nav}
activeKey={router.asPath} activeKey={router.asPath.split('?')[0]}
> >
<Nav.Item> <Nav.Item>
<Link href={'/' + user.name} passHref> <Link href={'/' + user.name} passHref>
@ -132,8 +132,8 @@ export default function UserHeader ({ user }) {
</Nav.Item> </Nav.Item>
{isMe && {isMe &&
<Nav.Item> <Nav.Item>
<Link href='/satistics' passHref> <Link href='/satistics?inc=invoice,withdrawal,stacked,spent' passHref>
<Nav.Link>satistics</Nav.Link> <Nav.Link eventKey='/satistics'>satistics</Nav.Link>
</Link> </Link>
</Nav.Item>} </Nav.Item>}
</Nav> </Nav>

View File

@ -25,10 +25,11 @@ export const WITHDRAWL = gql`
}` }`
export const WALLET_HISTORY = gql` export const WALLET_HISTORY = gql`
query WalletHistory($cursor: String) { query WalletHistory($cursor: String, $inc: String) {
walletHistory(cursor: $cursor) { walletHistory(cursor: $cursor, inc: $inc) {
facts { facts {
id id
factId
type type
createdAt createdAt
msats msats

View File

@ -66,7 +66,7 @@ export default function getApolloClient () {
} }
}, },
walletHistory: { walletHistory: {
keyArgs: false, keyArgs: ['inc'],
merge (existing, incoming) { merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing?.facts)) { if (isFirstPage(incoming.cursor, existing?.facts)) {
return incoming return incoming

View File

@ -12,6 +12,8 @@ import styles from '../styles/satistics.module.css'
import Moon from '../svgs/moon-fill.svg' import Moon from '../svgs/moon-fill.svg'
import Check from '../svgs/check-double-line.svg' 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 { useRouter } from 'next/router'
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY) export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
@ -83,7 +85,29 @@ function Satus ({ status }) {
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 { value: darkMode } = useDarkMode()
const { data, fetchMore } = useQuery(WALLET_HISTORY) const router = useRouter()
const { data, fetchMore } = useQuery(WALLET_HISTORY, { variables: { inc: router.query.inc } })
console.log(router.query.inc, data)
function filterRoutePush (filter, add) {
const inc = new Set(router.query.inc.split(','))
inc.delete('')
// depending on addrem, add or remove filter
if (add) {
inc.add(filter)
} else {
inc.delete(filter)
}
const incstr = [...inc].join(',')
router.push(`/satistics?inc=${incstr}`)
}
function included (filter) {
const inc = new Set(router.query.inc.split(','))
return inc.has(filter)
}
if (data) { if (data) {
({ walletHistory: { facts, cursor } } = data) ({ walletHistory: { facts, cursor } } = data)
@ -97,6 +121,34 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
return ( return (
<Layout noSeo> <Layout noSeo>
<UserHeader user={me} /> <UserHeader user={me} />
<div className='mt-3'>
<Form
initial={{
invoice: included('invoice'),
withdrawal: included('withdrawal'),
stacked: included('stacked'),
spent: included('spent')
}}
>
<div className='d-flex justify-content-around flex-wrap'>
<Checkbox
label='invoice' name='invoice' inline
handleChange={c => filterRoutePush('invoice', c)}
/>
<Checkbox
label='withdrawal' name='withdrawal' inline
handleChange={c => filterRoutePush('withdrawal', c)}
/>
<Checkbox
label='stacked' name='stacked' inline
handleChange={c => filterRoutePush('stacked', c)}
/>
<Checkbox
label='spent' name='spent' inline
handleChange={c => filterRoutePush('spent', c)}
/>
</div>
</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' variant={darkMode ? 'dark' : undefined}>
<thead> <thead>
<tr> <tr>
@ -107,7 +159,7 @@ 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.id}`} key={`${f.type}-${f.id}`}> <Link href={`${f.type}s/${f.factId}`} 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}>
@ -123,6 +175,7 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
</tbody> </tbody>
</Table> </Table>
<MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={SatisticsSkeleton} /> <MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={SatisticsSkeleton} />
</div>
</Layout> </Layout>
) )
} }

View File

@ -64,6 +64,10 @@ $tooltip-bg: #5c8001;
line-height: 1.2rem; line-height: 1.2rem;
} }
.custom-checkbox.custom-control-inline {
margin-right: .5rem;
}
.table { .table {
color: var(--theme-color); color: var(--theme-color);
background-color: var(--theme-body); background-color: var(--theme-body);