Skip to content

Commit

Permalink
Add crude transaction-overview for admins
Browse files Browse the repository at this point in the history
  • Loading branch information
13Bytes committed Jan 7, 2025
1 parent da8584a commit 5ebd01e
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 2 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ Create DB Migrations (for production)

### Open ToDos
- Bestellung zusammenrechnen aller gleichen Pizzen
- Übersicht alle Transaktionen für Admins
- Automatisches abmelden aller Accounts nach update (via changes )
- Android Zahlentastatur Komma ausgeblendet
#### Nice Improvements:
Expand Down
3 changes: 3 additions & 0 deletions src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export default function Header() {
<li>
<Link href="/admin/users">User-Verwaltung</Link>
</li>
<li>
<Link href="/admin/allTransactions">Alle Transaktionen</Link>
</li>
</ul>
</details>
</li>
Expand Down
73 changes: 73 additions & 0 deletions src/components/PageComponents/TransactionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { RouterOutputs } from "~/utils/api"

import { localStringOptions } from "~/helper/globalTypes"

type TransactionData = RouterOutputs["transaction"]["getAllInfinite"]["items"]
type Props = {
transactions: TransactionData | undefined
}
const TransactionList = (props: Props) => {
const Legend = () => (
<tr>
<th>Transaction</th>
<td>Type</td>
<td>Amount (€)</td>
<td>User (auführend)</td>
<td>Ziel</td>
<td>Verrechnungskonto</td>
</tr>
)

if (props.transactions === undefined) {
return (
<>
<span className="loading loading-spinner loading-lg m-6"></span>
</>
)
} else {
return (
<>
<div className="w-dvw max-w-5xl flex-col md:px-5">
<div className="mr-2 grow flex-row items-center justify-center overflow-x-auto">
<table className="table">
{/* head */}
<thead>
<Legend />
</thead>
<tbody>
{props.transactions.map((row) => (
<tr key={row.id} className={`${row.canceled ? "line-through" : ""}`}>
<td>
<div className="flex flex-col">
<p>{row.createdAt.toLocaleString("de", localStringOptions)}</p>
<p className="text-xs font-extralight">{row.id}</p>
{row.canceledBy && (
<p className="text-red-500 no-underline">
canceled by {row.canceledBy.name}
</p>
)}
</div>
</td>
<td>{row.type}</td>
<td>
{row.totalAmount?.toFixed(2)}{" "}
{!!row.amountWithoutFees && "(" + row.amountWithoutFees?.toFixed(2) + ")"}
</td>
<td>{row.user.name}</td>
<td>{row.moneyDestination?.name}</td>
<td>{row.clearingAccount?.name}</td>
</tr>
))}
</tbody>
<tfoot>
<Legend />
</tfoot>
</table>
</div>
</div>
</>
)
}
}

export default TransactionList
71 changes: 71 additions & 0 deletions src/pages/admin/allTransactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useEffect, useState } from "react"
import CenteredPage from "~/components/Layout/CenteredPage"
import TransactionList from "~/components/PageComponents/TransactionList"
import { api } from "~/utils/api"

const AdminTransactionPage = () => {
const [page, setPage] = useState(0)
const [maxPage, setMaxPage] = useState(Infinity)

const {
data: transactionData,
fetchNextPage,
hasNextPage,
} = api.transaction.getAllInfinite.useInfiniteQuery(
{},
{
keepPreviousData: true,
getNextPageParam: (lastPage) => {
if (lastPage.nextPageExists) {
return lastPage.pageNumber + 1
} else {
return undefined
}
},
},
)

useEffect(() => {
if (!hasNextPage) {
setMaxPage(page)
} else {
setMaxPage(Infinity)
}
}, [setMaxPage, hasNextPage])

useEffect(() => {
if (!hasNextPage) {
setMaxPage(page)
} else {
setMaxPage(Infinity)
}
}, [setMaxPage, hasNextPage])

return (
<CenteredPage>
<TransactionList transactions={transactionData?.pages[page]?.items} />

<div className="join mt-2">
<button
className={`btn join-item ${page < 1 ? "btn-disabled" : ""}`}
onClick={() => setPage((prev) => prev - 1)}
>
«
</button>
<button className="btn join-item pointer-events-none">Seite {page + 1}</button>
<button
className={`btn join-item ${page >= maxPage ? "btn-disabled" : ""}`}
onClick={() => {
void fetchNextPage()
setPage((prev) => prev + 1)
return
}}
>
»
</button>
</div>
</CenteredPage>
)
}

export default AdminTransactionPage
41 changes: 40 additions & 1 deletion src/server/api/routers/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { calculateFeesPerCategory } from "~/helper/dataProcessing"
import { id } from "~/helper/zodTypes"

import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
adminProcedure,
} from "~/server/api/trpc"
import { prisma } from "~/server/db"
import { checkAccountBacking } from "~/server/helper/dbCallHelper"
Expand Down Expand Up @@ -65,6 +65,45 @@ export const transactionRouter = createTRPCRouter({
}
}),

getAllInfinite: adminProcedure
.input(
z.object({
cursor: z.number().min(1).optional().default(1),
})
)
.query(async ({ ctx, input }) => {
const page = input.cursor ?? 1
const items = await ctx.prisma.transaction.findMany({
take: pageSize + 1, // get an extra item at the end which we'll use as next cursor
where: {
},
include: {
items: { include: { item: { include: { categories: true } } } },
procurementItems: { include: { item: { include: { categories: true } } } },
moneyDestination: { select: { name: true } },
user: { select: { name: true } },
canceledBy: { select: { name: true } },
clearingAccount: { select: { name: true } },
},
skip: (page - 1) * pageSize,
orderBy: {
createdAt: "desc",
},
})

// Last element is to check if list extends past current page
const nextPageExists = items.length > pageSize
if (nextPageExists) {
items.pop()
}

return {
items,
pageNumber: page,
nextPageExists,
}
}),

sendMoney: protectedProcedure
.input(z.object({ destinationUserId: id, amount: z.number(), note: z.string().optional() }))
.mutation(async ({ ctx, input }) => {
Expand Down

0 comments on commit 5ebd01e

Please sign in to comment.