add sum of balance
This commit is contained in:
parent
692bebdaf7
commit
380084b675
@ -24,6 +24,7 @@ interface AdminUser {
|
||||
interface AdminUsersApiResponse {
|
||||
users: AdminUser[];
|
||||
totalUsers: number;
|
||||
totalBalance: string; // Gesamtsaldo
|
||||
}
|
||||
|
||||
interface AdminTransaction {
|
||||
@ -49,11 +50,11 @@ interface AdminUserTransactionsApiResponse {
|
||||
totalTransactions: number;
|
||||
}
|
||||
|
||||
interface SetApprovalStatusApiResponse { // Typ für die Antwort der /set-approval-status API
|
||||
interface SetApprovalStatusApiResponse {
|
||||
message: string;
|
||||
userId: string;
|
||||
isApproved: boolean;
|
||||
approvedAt: Date | null; // API liefert Date-Objekt, im Frontend ggf. als String behandeln
|
||||
approvedAt: Date | null;
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +66,7 @@ const AdminPanelPage: NextPage = () => {
|
||||
const [isLoadingUsers, setIsLoadingUsers] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
const [totalBalance, setTotalBalance] = useState<string | null>(null); // State für Gesamtsaldo
|
||||
|
||||
const [selectedUserForAdjustment, setSelectedUserForAdjustment] = useState<AdminUser | null>(null);
|
||||
const [adjustmentAmount, setAdjustmentAmount] = useState<string>('');
|
||||
@ -103,10 +105,10 @@ const AdminPanelPage: NextPage = () => {
|
||||
const data: AdminUsersApiResponse = await res.json();
|
||||
const formattedUsers = data.users.map(user => ({
|
||||
...user,
|
||||
// approvedAt von der API kommt als ISO-String oder null
|
||||
approvedAt: user.approvedAt ? new Date(user.approvedAt).toLocaleString('de-DE') : null
|
||||
}));
|
||||
setUsers(formattedUsers);
|
||||
setTotalBalance(data.totalBalance); // Gesamtsaldo setzen
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
@ -218,7 +220,6 @@ const AdminPanelPage: NextPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// NEU: Benutzer Freigabestatus umschalten
|
||||
const handleToggleApproval = async (userId: string, currentApprovalStatus: boolean) => {
|
||||
setError(null);
|
||||
setSuccessMessage(null);
|
||||
@ -230,14 +231,14 @@ const AdminPanelPage: NextPage = () => {
|
||||
const response = await fetch(`/api/admin/users/${userId}/set-approval-status`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ isApproved: !currentApprovalStatus }), // Den entgegengesetzten Status senden
|
||||
body: JSON.stringify({ isApproved: !currentApprovalStatus }),
|
||||
});
|
||||
const data: SetApprovalStatusApiResponse = await response.json(); // Typ für die Antwort verwenden
|
||||
const data: SetApprovalStatusApiResponse = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || `Fehler beim ${actionText} des Benutzers.`);
|
||||
}
|
||||
setSuccessMessage(data.message);
|
||||
await fetchUsers(); // Benutzerliste neu laden, um den aktualisierten Status anzuzeigen
|
||||
await fetchUsers();
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
@ -270,7 +271,7 @@ const AdminPanelPage: NextPage = () => {
|
||||
<header className="mb-8 flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800">Admin Panel</h1>
|
||||
<p className="text-gray-600">Benutzerverwaltung</p>
|
||||
<p className="text-gray-600">Benutzerverwaltung & Übersicht</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
@ -291,6 +292,29 @@ const AdminPanelPage: NextPage = () => {
|
||||
{error && <div className="mb-4 p-3 bg-red-100 text-red-700 border border-red-300 rounded-md">{error}</div>}
|
||||
{successMessage && <div className="mb-4 p-3 bg-green-100 text-green-700 border border-green-300 rounded-md">{successMessage}</div>}
|
||||
|
||||
{/* Gesamtübersicht Sektion */}
|
||||
<section className="mb-8 p-6 bg-white shadow-lg rounded-lg">
|
||||
<h2 className="text-2xl font-semibold text-gray-700 mb-4">Gesamtübersicht</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-indigo-50 rounded-lg">
|
||||
<h3 className="text-sm font-medium text-indigo-700">Anzahl Benutzer</h3>
|
||||
<p className="mt-1 text-3xl font-semibold text-indigo-600">{users.length}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<h3 className="text-sm font-medium text-green-700">Gesamtsaldo</h3>
|
||||
{isLoadingUsers ? (
|
||||
<p className="mt-1 text-3xl font-semibold text-green-600">Lade...</p>
|
||||
) : (
|
||||
<p className={`mt-1 text-3xl font-semibold ${totalBalance && parseFloat(totalBalance) < 0 ? 'text-red-600' : 'text-green-600'}`}>
|
||||
{totalBalance !== null ? `${totalBalance} €` : 'N/A'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* Hier könnten weitere Kacheln für andere Statistiken hinzukommen */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section className="p-6 bg-white shadow-lg rounded-lg">
|
||||
<h2 className="text-2xl font-semibold text-gray-700 mb-4">Benutzerübersicht</h2>
|
||||
{isLoadingUsers ? (
|
||||
@ -334,7 +358,7 @@ const AdminPanelPage: NextPage = () => {
|
||||
<button
|
||||
onClick={() => handleToggleApproval(user.id, user.isApproved)}
|
||||
className="text-yellow-600 hover:text-yellow-900"
|
||||
disabled={user.id === session?.user?.id && users.filter(u => u.role === 'admin' && u.isApproved).length <= 1 && user.role === 'admin'} // Verhindere Selbstsperrung des letzten Admins
|
||||
disabled={user.id === session?.user?.id && users.filter(u => u.role === 'admin' && u.isApproved).length <= 1 && user.role === 'admin'}
|
||||
>
|
||||
Sperren
|
||||
</button>
|
||||
@ -355,7 +379,7 @@ const AdminPanelPage: NextPage = () => {
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Modal für Saldo anpassen */}
|
||||
{/* Modals (bleiben unverändert) */}
|
||||
<Transition appear show={isAdjustModalOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={() => setIsAdjustModalOpen(false)}>
|
||||
<Transition.Child
|
||||
@ -448,7 +472,6 @@ const AdminPanelPage: NextPage = () => {
|
||||
</Dialog>
|
||||
</Transition>
|
||||
|
||||
{/* Modal für Transaktionsverlauf */}
|
||||
<Transition appear show={isTransactionsModalOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={() => setIsTransactionsModalOpen(false)}>
|
||||
<Transition.Child
|
||||
@ -528,7 +551,6 @@ const AdminPanelPage: NextPage = () => {
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,11 +3,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import { authOptions } from '../auth/[...nextauth]'; // Pfad zu deiner authOptions anpassen
|
||||
import prisma from '../../../lib/prisma'; // Pfad zu deinem Prisma Client Singleton anpassen
|
||||
// User-Typ von Prisma wird hier nicht mehr direkt für ApiAdminUser benötigt,
|
||||
// da wir den Typ explizit definieren.
|
||||
import { Decimal } from '@prisma/client/runtime/library'; // Import Decimal
|
||||
|
||||
// Definiere den Typ für die Benutzerdaten, wie sie von dieser API gesendet werden.
|
||||
// Dieser Typ enthält genau die Felder, die wir auswählen und transformieren.
|
||||
type ApiAdminUser = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
@ -19,7 +17,7 @@ type ApiAdminUser = {
|
||||
updatedAt: string; // Datum als ISO-String
|
||||
isApproved: boolean; // Freigabestatus
|
||||
approvedAt: string | null; // Freigabedatum als ISO-String oder null
|
||||
_count?: { // _count ist optional und enthält die optionale Anzahl der Transaktionen
|
||||
_count?: {
|
||||
transactions?: number;
|
||||
};
|
||||
};
|
||||
@ -27,6 +25,7 @@ type ApiAdminUser = {
|
||||
interface AdminUsersApiResponse {
|
||||
users: ApiAdminUser[];
|
||||
totalUsers: number;
|
||||
totalBalance: string; // NEU: Gesamtsumme aller Salden als String
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
@ -49,6 +48,7 @@ export default async function handler(
|
||||
}
|
||||
|
||||
try {
|
||||
// Benutzerdaten abrufen
|
||||
const usersFromDb = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
@ -70,8 +70,14 @@ export default async function handler(
|
||||
},
|
||||
});
|
||||
|
||||
// Konvertiere Decimal-Saldo und DateTime-Objekte in Strings für die JSON-Antwort
|
||||
// und stelle sicher, dass die Struktur dem ApiAdminUser-Typ entspricht.
|
||||
// NEU: Gesamtsaldo berechnen
|
||||
const totalBalanceResult = await prisma.user.aggregate({
|
||||
_sum: {
|
||||
balance: true, // Summiere das 'balance'-Feld
|
||||
},
|
||||
});
|
||||
const totalBalanceDecimal = totalBalanceResult._sum.balance ?? new Decimal(0);
|
||||
|
||||
const apiAdminUsers: ApiAdminUser[] = usersFromDb.map(user => ({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
@ -83,14 +89,15 @@ export default async function handler(
|
||||
updatedAt: user.updatedAt.toISOString(),
|
||||
isApproved: user.isApproved,
|
||||
approvedAt: user.approvedAt ? user.approvedAt.toISOString() : null,
|
||||
_count: { // Stelle sicher, dass _count immer ein Objekt ist, auch wenn transactions 0 ist
|
||||
transactions: user._count?.transactions ?? 0 // Fallback auf 0, falls undefined
|
||||
_count: {
|
||||
transactions: user._count?.transactions ?? 0
|
||||
}
|
||||
}));
|
||||
|
||||
return res.status(200).json({
|
||||
users: apiAdminUsers,
|
||||
totalUsers: apiAdminUsers.length,
|
||||
totalBalance: totalBalanceDecimal.toFixed(2), // Gesamtsaldo als String hinzufügen
|
||||
});
|
||||
|
||||
} catch (error: unknown) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user