diff --git a/src/app/(frontend)/logistics/banking/page.tsx b/src/app/(frontend)/logistics/banking/page.tsx index 516ca25..5aba39a 100644 --- a/src/app/(frontend)/logistics/banking/page.tsx +++ b/src/app/(frontend)/logistics/banking/page.tsx @@ -1,7 +1,7 @@ import config from "@payload-config"; import { getPayload } from "payload"; import { headers as nextHeaders } from "next/headers"; -import type { BankAccount, Faction } from "@/payload-types"; +import type { BankAccount, BankTransaction, Faction, Resource } from "@/payload-types"; import { LandmarkIcon } from "lucide-react"; import { BankingOverview } from "@/components/frontend/banking/BankingOverview"; import hasRoles from "@/utils/access-control/hasRoles"; @@ -26,6 +26,14 @@ export default async function BankingPage() { }); const accounts = accountsRes.docs as unknown as BankAccount[]; + const transactionsRes = await payload.find({ + collection: "bank-transactions", + sort: "-timestamp", + limit: 25, + depth: 1, + }); + const recentTransactions = transactionsRes.docs as unknown as BankTransaction[]; + const factionsRes = await payload.find({ collection: "factions", limit: 100, @@ -41,6 +49,21 @@ export default async function BankingPage() { (await hasLogisticsQualification(payload, user).catch(() => false)) : false; + const currentUser = user + ? { + id: user.id as number, + username: user.username, + displayName: user.displayName ?? user.username, + } + : null; + + // Currency is shared across all accounts (main currency set in Game Rules). + // Resolve from the first account whose currency populated at depth 1. + const currencyResource = + accounts.length > 0 && typeof accounts[0].currency === "object" + ? (accounts[0].currency as Resource) + : null; + return (
@@ -55,10 +78,12 @@ export default async function BankingPage() {
); -} +} \ No newline at end of file diff --git a/src/components/frontend/banking/BankTransactionDialog.tsx b/src/components/frontend/banking/BankTransactionDialog.tsx index 906f4cb..c8e1c55 100644 --- a/src/components/frontend/banking/BankTransactionDialog.tsx +++ b/src/components/frontend/banking/BankTransactionDialog.tsx @@ -124,6 +124,7 @@ export function BankTransactionDialog({ size="sm" variant={mode === m ? "default" : "outline"} onClick={() => openMode(m)} + className="font-mono text-xs uppercase tracking-wider" > {M.label} diff --git a/src/components/frontend/banking/BankingOverview.tsx b/src/components/frontend/banking/BankingOverview.tsx index 26e1731..d8f0ae0 100644 --- a/src/components/frontend/banking/BankingOverview.tsx +++ b/src/components/frontend/banking/BankingOverview.tsx @@ -1,18 +1,25 @@ "use client"; import type { ReactNode } from "react"; -import Link from "next/link"; -import type { BankAccount } from "@/payload-types"; -import { LandmarkIcon, WalletIcon } from "lucide-react"; -import { Item, ItemContent, ItemHeader, ItemMedia, ItemTitle } from "@/components/ui/item"; -import { formatAmount } from "@/lib/banking/format"; +import type { BankAccount, BankTransaction, Resource } from "@/payload-types"; +import { LandmarkIcon } from "lucide-react"; +import { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/ui/tabs"; import { AccountCard } from "@/components/frontend/banking/AccountCard"; import { CreateAccountDialog } from "@/components/frontend/banking/CreateAccountDialog"; -import { MyWalletCard } from "@/components/frontend/banking/MyWalletCard"; +import { WalletHero } from "@/components/frontend/banking/WalletHero"; +import { ActivityFeed } from "@/components/frontend/banking/ActivityFeed"; +import { currencyLabel, formatAmount } from "@/lib/banking/format"; interface BankingOverviewProps { accounts: BankAccount[]; - currentUserId: number; + recentTransactions: BankTransaction[]; + currentUser: { id: number; username: string; displayName?: string } | null; + currencyResource: Resource | null; isManager: boolean; factions: { id: number; name: string }[]; } @@ -29,10 +36,10 @@ function Section({ return (
-

+

{title} -

- + + {count} account{count !== 1 ? "s" : ""}
@@ -49,124 +56,148 @@ function Section({ export function BankingOverview({ accounts, - currentUserId, + recentTransactions, + currentUser, + currencyResource, isManager, factions, }: BankingOverviewProps) { const treasury = accounts.filter((a) => a.accountType === "treasury"); - const faction = accounts.filter((a) => a.accountType === "faction"); + const factionAccounts = accounts.filter((a) => a.accountType === "faction"); const personal = accounts.filter((a) => a.accountType === "personal"); - const myAccount = personal.find((a) => { - const ownerId = typeof a.ownerUser === "object" ? a.ownerUser?.id : a.ownerUser; - return ownerId === currentUserId; - }); + const myAccount = currentUser + ? personal.find((a) => { + if (!a.ownerUser) return false; + const ownerId = + typeof a.ownerUser === "object" ? a.ownerUser.id : a.ownerUser; + return ownerId === currentUser.id; + }) + : undefined; - const unitFunds = [...treasury, ...faction].reduce( + // Regular users see only their own personal account; managers see everything. + const visiblePersonal = isManager + ? personal + : myAccount + ? [myAccount] + : []; + + const unitFundsTotal = [...treasury, ...factionAccounts].reduce( (sum, a) => sum + (a.balance ?? 0), 0, ); - const currency = - accounts.length > 0 && typeof accounts[0].currency === "object" - ? accounts[0].currency - : null; - const accountTypeLabels: Record = { - treasury: "Treasury", - faction: "Faction", - personal: "Personal", - }; + const visibleAccountsCount = + (isManager ? treasury.length + factionAccounts.length : 0) + + visiblePersonal.length; + + const tickerCurrency = (label: string): string => + label.length > 8 ? `${label.slice(0, 8)}…` : label; return ( -
-
- - - - - - - Unit funds - - - {formatAmount(unitFunds, currency)} - - - +
+ - - - - - - - Accounts - - - {accounts.length} - - - + +
+ + + Activity + + + Accounts + + - {myAccount ? ( - - - - - - - - My wallet - - - {formatAmount(myAccount.balance ?? 0, currency)} - - - - - ) : ( - - )} -
- -
-
-

- Accounts -

+ {isManager && ( +

+ UNIT FUNDS  + {formatAmount(unitFundsTotal, currencyResource)} +

+ )}
- -
-
- {treasury.map((account) => ( - - ))} -
+ + + -
- {faction.map((account) => ( - - ))} -
+ +
+
+ +

+ Unit Accounts +

+ + {visibleAccountsCount} + +
+ +
-
- {personal.map((account) => ( - - ))} -
+ {visibleAccountsCount === 0 ? ( +

+ No accessible accounts.{" "} + {!isManager && "Ask logistics personnel to set up the unit treasury."} +

+ ) : ( + <> + {isManager && treasury.length > 0 && ( +
+ {treasury.map((account) => ( + + ))} +
+ )} + + {isManager && factionAccounts.length > 0 && ( +
+ {factionAccounts.map((account) => ( + + ))} +
+ )} + + {visiblePersonal.length > 0 && ( +
+ {visiblePersonal.map((account) => ( + + ))} +
+ )} + + )} +
+
{accounts.length === 0 && (

No accounts yet. Create your first account to get started. - {isManager ? "" : " (Ask logistics personnel to set up the unit treasury.)"}

)} -

- Account types: {Object.values(accountTypeLabels).join(" · ")}. - Balances are denominated in the main currency set in Game Rules. +

+ SYS.BANKING / currency={tickerCurrency(currencyLabel(currencyResource))}

); -} +} \ No newline at end of file