import SwiftUI struct OverviewTab: View { @ObservedObject var vm: HomeViewModel let onCreateQr: () -> Void let onSeePayments: () -> Void let onOpenWallet: () -> Void var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { let firstName = SessionStore.shared.name? .trimmingCharacters(in: .whitespaces) .split(separator: " ").first.map(String.init) Text(firstName.map { "Hello, \($0)" } ?? "Hello") .font(.ladill(size: 22, weight: .semibold)) .foregroundStyle(LadillColors.ink) Text("Here is today's takings, your payment QRs, and recent payments.") .font(.ladill(size: 14)) .foregroundStyle(LadillColors.muted) if vm.hasPayout == false { payoutNote } switch vm.overviewState { case .idle, .loading: LoadingView() .task { await vm.loadOverview() } case .failure(let message): ErrorView(message: message) { Task { await vm.loadOverview() } } case .success(let data): overviewContent(data) } } .padding(20) } .refreshable { await vm.refreshOverview() } } private var payoutNote: some View { Button(action: onOpenWallet) { HStack { Image(systemName: "exclamationmark.circle.fill") .foregroundStyle(LadillColors.indigo) Text("Set up your payout account to withdraw takings.") .font(.ladill(size: 13)) .foregroundStyle(LadillColors.indigoText) Spacer() } .padding(14) .background(LadillColors.indigoSoft) .clipShape(RoundedRectangle(cornerRadius: 14)) } .buttonStyle(.plain) } @ViewBuilder private func overviewContent(_ data: Overview) -> some View { let currency = data.currency ?? "GHS" HStack(spacing: 12) { StatCard(label: "Today", value: Formatters.money(minor: data.todayTakingsMinor ?? 0, currency: currency), accent: true) StatCard(label: "Payments", value: "\(data.todayCount ?? 0)") } HStack(spacing: 12) { StatCard(label: "Payment QRs", value: "\(data.paymentQrCount ?? 0)") StatCard(label: "Wallet", value: Formatters.money(minor: data.walletBalanceMinor ?? 0, currency: currency)) } LadillButton(title: "Create payment QR", action: onCreateQr) .padding(.top, 4) if let payments = data.recentPayments, !payments.isEmpty { HStack { Text("Recent payments") .font(.ladill(size: 16, weight: .semibold)) Spacer() Button("See all", action: onSeePayments) .font(.ladill(size: 13, weight: .semibold)) .foregroundStyle(LadillColors.indigo) } .padding(.top, 8) ForEach(payments) { payment in paymentRow(payment) } } } private func paymentRow(_ payment: Payment) -> some View { LadillCard { HStack { VStack(alignment: .leading, spacing: 4) { Text(payment.payerName ?? payment.qrLabel ?? "Payment") .font(.ladill(size: 15, weight: .semibold)) Text(Formatters.date(iso: payment.paidAt ?? payment.createdAt)) .font(.ladill(size: 12)) .foregroundStyle(LadillColors.muted) } Spacer() Text(Formatters.money(minor: payment.amountMinor ?? 0, currency: payment.currency ?? "GHS")) .font(.ladill(size: 15, weight: .semibold)) .foregroundStyle(LadillColors.money) } } } } struct PaymentQrsTab: View { @ObservedObject var vm: HomeViewModel let onCreateQr: () -> Void let onOpenQr: (Int64) -> Void var body: some View { ScrollView { VStack(spacing: 12) { LadillButton(title: "Create payment QR", action: onCreateQr) switch vm.qrsState { case .idle, .loading: LoadingView().task { await vm.loadQrs() } case .failure(let message): ErrorView(message: message) { Task { await vm.loadQrs() } } case .success(let qrs): if qrs.isEmpty { Text("No payment QRs yet. Create one to start collecting.") .font(.ladill(size: 14)) .foregroundStyle(LadillColors.muted) .multilineTextAlignment(.center) .padding(.vertical, 40) } else { ForEach(qrs) { qr in Button { onOpenQr(qr.id) } label: { LadillCard { HStack { Image(systemName: "qrcode") .font(.system(size: 28)) .foregroundStyle(LadillColors.indigo) VStack(alignment: .leading, spacing: 4) { Text(qr.label) .font(.ladill(size: 15, weight: .semibold)) .foregroundStyle(LadillColors.ink) Text(qr.businessName ?? "") .font(.ladill(size: 12)) .foregroundStyle(LadillColors.muted) } Spacer() if qr.isActive == false { Text("Paused") .font(.ladill(size: 11, weight: .medium)) .foregroundStyle(LadillColors.muted) } } } } .buttonStyle(.plain) } } } } .padding(20) } .refreshable { await vm.loadQrs() } } } struct PaymentsTab: View { @ObservedObject var vm: HomeViewModel var body: some View { ScrollView { VStack(spacing: 12) { switch vm.paymentsState { case .idle, .loading: LoadingView().task { await vm.loadPayments() } case .failure(let message): ErrorView(message: message) { Task { await vm.loadPayments() } } case .success(let payments): if payments.isEmpty { Text("No payments yet.") .font(.ladill(size: 14)) .foregroundStyle(LadillColors.muted) .padding(.vertical, 40) } else { ForEach(payments) { payment in LadillCard { HStack { VStack(alignment: .leading, spacing: 4) { Text(payment.payerName ?? payment.qrLabel ?? "Payment") .font(.ladill(size: 15, weight: .semibold)) Text(Formatters.date(iso: payment.paidAt ?? payment.createdAt)) .font(.ladill(size: 12)) .foregroundStyle(LadillColors.muted) } Spacer() Text(Formatters.money(minor: payment.amountMinor ?? 0, currency: payment.currency ?? "GHS")) .font(.ladill(size: 15, weight: .semibold)) .foregroundStyle(LadillColors.money) } } } } } } .padding(20) } .refreshable { await vm.loadPayments() } } } struct PayoutsTab: View { @ObservedObject var vm: HomeViewModel let onOpenWallet: () -> Void var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { Text("Payouts") .font(.ladill(size: 22, weight: .semibold)) Text("Takings settle into your Ladill wallet (5% fee), then withdraw to bank or MoMo.") .font(.ladill(size: 14)) .foregroundStyle(LadillColors.muted) switch vm.payoutsState { case .idle, .loading: LoadingView().task { await vm.loadPayouts() } case .failure(let message): ErrorView(message: message) { Task { await vm.loadPayouts() } } case .success(let data): let currency = data.currency ?? "GHS" StatCard(label: "Total revenue", value: Formatters.money(minor: data.totalRevenueMinor ?? 0, currency: currency), accent: true) StatCard(label: "Wallet balance", value: Formatters.money(minor: data.walletBalanceMinor ?? 0, currency: currency)) } LadillButton(title: "Open wallet", action: onOpenWallet) } .padding(20) } .refreshable { await vm.loadPayouts() } } }