Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"next": "16.0.10",
"next-auth": "^4.24.13",
"nodemailer": "^7.0.10",
"papaparse": "^5.5.3",
"rate-limiter-flexible": "^5.0.3",
"react": "19.2.1",
"react-dom": "19.2.1",
Expand All @@ -47,6 +48,7 @@
"@types/eslint": "^8.56.10",
"@types/ldapjs": "^3.0.6",
"@types/node": "^20.12.11",
"@types/papaparse": "^5.5.2",
"@types/prettier": "^2.7.3",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
Expand Down
39 changes: 39 additions & 0 deletions src/components/General/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ChevronLeft, ChevronRight } from "lucide-react"

export function Pagination(props: {
page: number
maxPage: number | undefined
setPage: React.Dispatch<React.SetStateAction<number>>
fetchNextPage?: () => Promise<unknown>
changePageAction?: (direction: "next" | "previous") => void,
}) {
const { page, maxPage, setPage, changePageAction, fetchNextPage } = props

const nextDisabled = maxPage === undefined || page >= maxPage
return (
<div className="join grid grid-cols-3 mt-2">
<button
className={`btn join-item ${page < 1 ? "btn-disabled" : "btn-outline"} border-r-0`}
onClick={() => {
changePageAction?.("previous")
setPage((prev) => prev - 1)
}}
disabled={page < 1}
>
<ChevronLeft className="h-4 w-4" />
</button>
<button className="btn join-item btn-active pointer-events-none border-1 border-base-content">
Seite {page + 1}
</button>
<button
className={`btn join-item ${nextDisabled ? "btn-disabled" : "btn-outline"} border-l-0`}
onClick={() => {
void fetchNextPage?.()
changePageAction?.("next")
setPage((prev) => prev + 1)
}}
disabled={nextDisabled}>
<ChevronRight className="h-4 w-4" />
</button>
</div>)
}
50 changes: 49 additions & 1 deletion src/components/PageComponents/TransactionList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { RouterOutputs } from "~/utils/api"
import { api, RouterInputs, RouterOutputs } from "~/utils/api"
import { Receipt, User, Target, Wallet, Calendar, Ban, AlertCircle } from "lucide-react"
import { localStringOptions } from "~/helper/globalTypes"
import CSVParser from 'papaparse';
import { useState } from "react";
import { toFlatPropertyMap } from "~/helper/generalFunctions";

type TransactionData = RouterOutputs["transaction"]["getAllInfinite"]["items"]
type Timespans = RouterInputs["transaction"]["getAllFixedTimespan"]["timespan"]
type Props = {
transactions: TransactionData | undefined
}
Expand All @@ -26,6 +30,7 @@ const getTransactionTypeLabel = (type: number): string => {
}
}


const TransactionList = (props: Props) => {
const Legend = () => (
<tr>
Expand All @@ -38,6 +43,34 @@ const TransactionList = (props: Props) => {
</tr>
)



const [queryDuration, setQueryDuration] = useState<Timespans>()
const [exportStatus, setExportStatus] = useState<'idle' | 'loading'>('idle')
api.transaction.getAllFixedTimespan.useQuery({ timespan: queryDuration! }, {
onSuccess: (data) => {
console.log("converting to csv")
const flatData = data.map((item) => toFlatPropertyMap(item))
const csv = CSVParser.unparse(flatData, { delimiter: ';', skipEmptyLines: true })
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const url = URL.createObjectURL(blob)
window.open(url, "_blank");
setTimeout(() => {
URL.revokeObjectURL(url)
}, 1000)
},
onSettled: () => {
setExportStatus('idle')
setQueryDuration(undefined)
},
enabled: !!queryDuration,
})

const startExport = (duration: Timespans) => {
setQueryDuration(duration)
setExportStatus('loading')
}

if (props.transactions === undefined) {
return (
<div className="card border border-base-300 bg-base-100 shadow-xl">
Expand Down Expand Up @@ -73,6 +106,21 @@ const TransactionList = (props: Props) => {
<div className="mb-6 flex items-center gap-3">
<Receipt className="h-6 w-6 text-primary" />
<h2 className="text-2xl font-bold text-base-content">Transaktionen</h2>
<div className="grow" />
<div>
<div className="dropdown dropdown-end dropdown-hover">
{exportStatus !== 'loading' && <>
<div tabIndex={0} role="button" className="btn btn-sm">Export...</div>
<ul tabIndex={-1} className="dropdown-content menu bg-base-100 rounded-box z-10 w-32 shadow-sm">
<li><a onClick={() => startExport('week')}>1 Woche</a></li>
<li><a onClick={() => startExport('month')}>1 Monat</a></li>
<li><a onClick={() => startExport('quarter')}>3 Monate</a></li>
<li><a onClick={() => startExport('year')}>1 Jahr</a></li>
</ul>
</>}
{exportStatus === 'loading' && <div role="button" className="btn btn-sm btn-disabled">Export...</div>}
</div>
</div>
</div>

{/* Desktop Table View */}
Expand Down
20 changes: 19 additions & 1 deletion src/helper/generalFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,22 @@ export const toggleElementInArray = <T>(array: T[], value: T) => {
} else {
return [...array, value]
}
}
}

export function toFlatPropertyMap(obj: object, keySeparator = '.') {
const flattenRecursive = (obj: object, parentProperty?: string, propertyMap: Record<string, unknown> = {}) => {
for (const [key, value] of Object.entries(obj)) {
const property = parentProperty ? `${parentProperty}${keySeparator}${key}` : key;
if (value && typeof value === 'object' && value instanceof Date) {
propertyMap[property] = value.toISOString();
}
else if (value && typeof value === 'object') {
flattenRecursive(value, property, propertyMap);
} else {
propertyMap[property] = value;
}
}
return propertyMap;
};
return flattenRecursive(obj);
}
30 changes: 4 additions & 26 deletions src/pages/account.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Transaction } from "@prisma/client"
import { ChevronLeft, ChevronRight, ClipboardList, TrendingUp, Undo } from "lucide-react"
import { ClipboardList, TrendingUp, Undo } from "lucide-react"
import { type NextPage } from "next"
import { useSession } from "next-auth/react"
import Link from "next/link"
Expand All @@ -8,6 +8,7 @@ import ActionResponsePopup, {
AnimationHandle,
animate,
} from "~/components/General/ActionResponsePopup"
import { Pagination } from "~/components/General/Pagination"
import CenteredPage from "~/components/Layout/CenteredPage"
import { getUsernameLetters } from "~/helper/generalFunctions"
import { Tid } from "~/helper/zodTypes"
Expand Down Expand Up @@ -262,31 +263,8 @@ const AccountPage: NextPage = () => {
</div>

{/* Pagination */}
<div className="mt-6 flex justify-center">
<div className="join grid grid-cols-3">
<button
className={`btn join-item ${page < 1 ? "btn-disabled" : "btn-outline"} border-r-0`}
onClick={() => setPage((prev) => prev - 1)}
disabled={page < 1}
>
<ChevronLeft className="h-4 w-4" />
Zurück
</button>
<button className="btn join-item btn-active pointer-events-none border-1 border-base-content">
Seite {page + 1}
</button>
<button
className={`btn join-item ${page >= maxPage ? "btn-disabled" : "btn-outline"} border-l-0`}
onClick={() => {
void fetchNextPage()
setPage((prev) => prev + 1)
return
}}
disabled={page >= maxPage}>
Weiter
<ChevronRight className="h-4 w-4" />
</button>
</div>
<div className="flex justify-center">
<Pagination page={page} maxPage={maxPage} setPage={setPage} fetchNextPage={fetchNextPage} />
</div>
</div>
</div>
Expand Down
22 changes: 2 additions & 20 deletions src/pages/admin/allTransactions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react"
import { Pagination } from "~/components/General/Pagination"
import CenteredPage from "~/components/Layout/CenteredPage"
import TransactionList from "~/components/PageComponents/TransactionList"
import { api } from "~/utils/api"
Expand Down Expand Up @@ -44,26 +45,7 @@ const AdminTransactionPage = () => {
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>
<Pagination page={page} maxPage={maxPage} setPage={setPage} fetchNextPage={fetchNextPage} />
</CenteredPage>
)
}
Expand Down
35 changes: 9 additions & 26 deletions src/pages/admin/grouporders.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React, { useEffect, useState } from "react"
import {
Plus,
Users,
Repeat,
Calendar,
TrendingUp,
CheckCircle,
XCircle,
DollarSign,
Plus,
Repeat,
Users,
XCircle
} from "lucide-react"
import React, { useEffect, useState } from "react"
import GrouporderForm from "~/components/Forms/GrouporderForm"
import GrouporderTemplateForm from "~/components/Forms/GrouporderTemplateForm"
import { Pagination } from "~/components/General/Pagination"
import Modal from "~/components/Layout/Modal"
import RegularPage from "~/components/Layout/RegularPage"
import StatCard from "~/components/PageComponents/StatsCard"
import { localStringOptions, weekdays } from "~/helper/globalTypes"
import { Tid } from "~/helper/zodTypes"
import { api } from "~/utils/api"
import StatCard from "~/components/PageComponents/StatsCard"

const GroupOrdersPage = () => {
const allOrderTemplateRequest = api.groupOrders.getAllTemplates.useQuery()
Expand Down Expand Up @@ -351,25 +351,8 @@ const GroupOrdersPage = () => {

{/* Pagination */}
{totalOrders > 0 && (
<div className="mt-6 flex justify-center">
<div className="join">
<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)
}}
>
»
</button>
</div>
<div className="flex justify-center">
<Pagination page={page} maxPage={maxPage} setPage={setPage} fetchNextPage={fetchNextPage} />
</div>
)}
</div>
Expand Down
Loading