T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.ca/public_html/src/components/payments/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/components/payments/FinancialDashboard.tsx
'use client';

import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';

interface FinancialData {
  user: {
    id: string;
    name: string;
    email: string;
    role: string;
    xpPoints: number;
    level: number;
  };
  societyInfo: {
    tier: string;
    platformFeeDiscount: number;
    discountedRate: number;
    benefits: string[];
  } | null;
  overview: {
    totalEarnings: number;
    totalSpent: number;
    thisMonthEarnings: number;
    thisMonthSpent: number;
    pendingPayouts: number;
    escrowBalance: number;
    societyDiscountsSaved: number;
    totalCommissionPaid: number;
  };
  monthlyComparison: {
    currentMonth: {
      earnings: number;
      spending: number;
      transactions: number;
    };
    lastMonth: {
      earnings: number;
      spending: number;
      transactions: number;
    };
  };
  recentTransactions: Array<{
    id: string;
    amount: number;
    type: string;
    status: string;
    description: string;
    caseTitle?: string;
    createdAt: string;
    societyDiscount: number;
    platformFeeAmount: number;
    lawyerPayoutAmount: number;
    xpEarned: number;
  }>;
  escrowAccounts: {
    asClient: Array<{
      id: string;
      caseId: string;
      caseTitle: string;
      lawyerName: string;
      totalAmount: number;
      availableAmount: number;
      releasedAmount: number;
      status: string;
      createdAt: string;
    }>;
    asLawyer: Array<{
      id: string;
      caseId: string;
      caseTitle: string;
      clientName: string;
      totalAmount: number;
      availableAmount: number;
      releasedAmount: number;
      status: string;
      createdAt: string;
    }>;
  };
  statistics: {
    paymentCount: number;
    refundCount: number;
    disputeCount: number;
    averageTransactionAmount: number;
    platformFeesSaved: number;
  };
  chartData: Array<{
    month: string;
    earnings: number;
    spending: number;
    transactions: number;
    societyDiscounts: number;
  }>;
  lastUpdated: string;
}

export default function FinancialDashboard() {
  const { data: session } = useSession();
  const [data, setData] = useState<FinancialData | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [activeTab, setActiveTab] = useState<'overview' | 'transactions' | 'escrow' | 'analytics'>('overview');

  useEffect(() => {
    if (session) {
      fetchFinancialData();
    }
  }, [session]);

  const fetchFinancialData = async () => {
    try {
      setIsLoading(true);
      const response = await fetch('/api/payments/financial-dashboard');
      
      if (!response.ok) {
        throw new Error('Failed to fetch financial data');
      }

      const financialData = await response.json();
      setData(financialData);
    } catch (error) {
      console.error('Error fetching financial data:', error);
      setError('Failed to load financial data. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  if (isLoading) {
    return (
      <div className="flex items-center justify-center min-h-[400px]">
        <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
        <span className="ml-3 text-gray-600">Loading financial dashboard...</span>
      </div>
    );
  }

  if (error) {
    return (
      <div className="bg-red-50 border border-red-200 rounded-md p-4">
        <div className="flex">
          <svg className="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
            <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
          </svg>
          <div className="ml-3">
            <h3 className="text-sm font-medium text-red-800">Error</h3>
            <p className="text-sm text-red-700 mt-1">{error}</p>
            <button
              onClick={fetchFinancialData}
              className="mt-2 text-sm bg-red-100 hover:bg-red-200 text-red-800 px-3 py-1 rounded"
            >
              Try Again
            </button>
          </div>
        </div>
      </div>
    );
  }

  if (!data) {
    return <div>No financial data available.</div>;
  }

  const isLawyer = ['LAWYER', 'ADMIN', 'SUPERADMIN'].includes(data.user.role);

  return (
    <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
      {/* Header */}
      <div className="mb-8">
        <h1 className="text-3xl font-bold text-gray-900">Financial Dashboard</h1>
        <p className="text-gray-600 mt-2">
          Track your {isLawyer ? 'earnings' : 'spending'}, society benefits, and payment history
        </p>
        
        {/* Society Tier Badge */}
        {data.societyInfo && (
          <div className="mt-4 inline-flex items-center px-4 py-2 rounded-full text-sm font-medium bg-gradient-to-r from-blue-500 to-purple-600 text-white">
            <svg className="h-4 w-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
              <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
            </svg>
            {data.societyInfo.tier} Member • {(data.societyInfo.platformFeeDiscount * 100).toFixed(0)}% Fee Discount
          </div>
        )}
      </div>

      {/* Tabs */}
      <div className="border-b border-gray-200 mb-6">
        <nav className="-mb-px flex space-x-8">
          {[
            { id: 'overview', name: 'Overview', icon: '📊' },
            { id: 'transactions', name: 'Transactions', icon: '💳' },
            { id: 'escrow', name: 'Escrow', icon: '🔒' },
            { id: 'analytics', name: 'Analytics', icon: '📈' }
          ].map((tab) => (
            <button
              key={tab.id}
              onClick={() => setActiveTab(tab.id as any)}
              className={`py-2 px-1 border-b-2 font-medium text-sm ${
                activeTab === tab.id
                  ? 'border-blue-500 text-blue-600'
                  : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
              }`}
            >
              <span className="mr-2">{tab.icon}</span>
              {tab.name}
            </button>
          ))}
        </nav>
      </div>

      {/* Tab Content */}
      {activeTab === 'overview' && (
        <OverviewTab data={data} isLawyer={isLawyer} />
      )}
      
      {activeTab === 'transactions' && (
        <TransactionsTab transactions={data.recentTransactions} />
      )}
      
      {activeTab === 'escrow' && (
        <EscrowTab escrowAccounts={data.escrowAccounts} isLawyer={isLawyer} />
      )}
      
      {activeTab === 'analytics' && (
        <AnalyticsTab data={data} isLawyer={isLawyer} />
      )}
    </div>
  );
}

// Overview Tab Component
function OverviewTab({ data, isLawyer }: { data: FinancialData; isLawyer: boolean }) {
  const { overview, monthlyComparison } = data;

  const statCards = [
    {
      title: isLawyer ? 'Total Earnings' : 'Total Spent',
      value: `$${(isLawyer ? overview.totalEarnings : overview.totalSpent).toFixed(2)}`,
      change: isLawyer ? 
        monthlyComparison.currentMonth.earnings - monthlyComparison.lastMonth.earnings :
        monthlyComparison.currentMonth.spending - monthlyComparison.lastMonth.spending,
      icon: isLawyer ? '💰' : '💸',
      color: 'blue'
    },
    {
      title: isLawyer ? 'This Month Earnings' : 'This Month Spent',
      value: `$${(isLawyer ? overview.thisMonthEarnings : overview.thisMonthSpent).toFixed(2)}`,
      change: 0,
      icon: '📅',
      color: 'green'
    },
    {
      title: 'Escrow Balance',
      value: `$${overview.escrowBalance.toFixed(2)}`,
      change: 0,
      icon: '🔒',
      color: 'yellow'
    },
    {
      title: 'Society Savings',
      value: `$${overview.societyDiscountsSaved.toFixed(2)}`,
      change: 0,
      icon: '⭐',
      color: 'purple'
    }
  ];

  if (isLawyer) {
    statCards.push({
      title: 'Pending Payouts',
      value: `$${overview.pendingPayouts.toFixed(2)}`,
      change: 0,
      icon: '⏳',
      color: 'orange'
    });
  }

  return (
    <div className="space-y-6">
      {/* Stat Cards */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
        {statCards.map((stat, index) => (
          <div key={index} className="bg-white rounded-lg shadow p-6">
            <div className="flex items-center">
              <div className="text-2xl mr-3">{stat.icon}</div>
              <div>
                <p className="text-sm font-medium text-gray-600">{stat.title}</p>
                <p className="text-2xl font-bold text-gray-900">{stat.value}</p>
                {stat.change !== 0 && (
                  <p className={`text-sm ${stat.change > 0 ? 'text-green-600' : 'text-red-600'}`}>
                    {stat.change > 0 ? '+' : ''}${stat.change.toFixed(2)} from last month
                  </p>
                )}
              </div>
            </div>
          </div>
        ))}
      </div>

      {/* Society Benefits */}
      {data.societyInfo && (
        <div className="bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg p-6">
          <h3 className="text-lg font-semibold text-gray-900 mb-4">
            🌟 {data.societyInfo.tier} Benefits
          </h3>
          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div>
              <h4 className="font-medium text-gray-700 mb-2">Active Benefits:</h4>
              <ul className="space-y-1">
                {data.societyInfo.benefits.map((benefit, index) => (
                  <li key={index} className="text-sm text-gray-600 flex items-center">
                    <svg className="h-4 w-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
                      <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
                    </svg>
                    {benefit}
                  </li>
                ))}
              </ul>
            </div>
            <div>
              <h4 className="font-medium text-gray-700 mb-2">Financial Impact:</h4>
              <div className="space-y-2 text-sm">
                <div className="flex justify-between">
                  <span>Platform Fee Rate:</span>
                  <span className="font-medium">{(data.societyInfo.discountedRate * 100).toFixed(1)}%</span>
                </div>
                <div className="flex justify-between">
                  <span>Total Savings:</span>
                  <span className="font-medium text-green-600">${overview.societyDiscountsSaved.toFixed(2)}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Transactions Tab Component
function TransactionsTab({ transactions }: { transactions: FinancialData['recentTransactions'] }) {
  const getStatusColor = (status: string) => {
    switch (status.toLowerCase()) {
      case 'completed': return 'text-green-600 bg-green-100';
      case 'pending': return 'text-yellow-600 bg-yellow-100';
      case 'failed': return 'text-red-600 bg-red-100';
      default: return 'text-gray-600 bg-gray-100';
    }
  };

  return (
    <div className="bg-white shadow rounded-lg overflow-hidden">
      <div className="px-6 py-4 border-b border-gray-200">
        <h3 className="text-lg font-medium text-gray-900">Recent Transactions</h3>
      </div>
      <div className="overflow-x-auto">
        <table className="min-w-full divide-y divide-gray-200">
          <thead className="bg-gray-50">
            <tr>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Amount</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">XP</th>
            </tr>
          </thead>
          <tbody className="bg-white divide-y divide-gray-200">
            {transactions.map((transaction) => (
              <tr key={transaction.id} className="hover:bg-gray-50">
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
                  {new Date(transaction.createdAt).toLocaleDateString()}
                </td>
                <td className="px-6 py-4 text-sm text-gray-900">
                  <div>{transaction.description}</div>
                  {transaction.caseTitle && (
                    <div className="text-xs text-gray-500">{transaction.caseTitle}</div>
                  )}
                </td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
                  <div>${transaction.amount.toFixed(2)}</div>
                  {transaction.societyDiscount > 0 && (
                    <div className="text-xs text-green-600">
                      -{(transaction.societyDiscount * 100).toFixed(1)}% discount
                    </div>
                  )}
                </td>
                <td className="px-6 py-4 whitespace-nowrap">
                  <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(transaction.status)}`}>
                    {transaction.status}
                  </span>
                </td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-blue-600">
                  {transaction.xpEarned > 0 && `+${transaction.xpEarned} XP`}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// Escrow Tab Component
function EscrowTab({ escrowAccounts, isLawyer }: { escrowAccounts: FinancialData['escrowAccounts']; isLawyer: boolean }) {
  const clientEscrows = escrowAccounts.asClient;
  const lawyerEscrows = escrowAccounts.asLawyer;

  const getStatusColor = (status: string) => {
    switch (status.toLowerCase()) {
      case 'active': return 'text-green-600 bg-green-100';
      case 'completed': return 'text-blue-600 bg-blue-100';
      case 'disputed': return 'text-red-600 bg-red-100';
      case 'cancelled': return 'text-gray-600 bg-gray-100';
      default: return 'text-yellow-600 bg-yellow-100';
    }
  };

  const EscrowTable = ({ escrows, title }: { escrows: any[]; title: string }) => (
    <div className="bg-white shadow rounded-lg overflow-hidden mb-6">
      <div className="px-6 py-4 border-b border-gray-200">
        <h3 className="text-lg font-medium text-gray-900">{title}</h3>
      </div>
      {escrows.length === 0 ? (
        <div className="px-6 py-8 text-center text-gray-500">
          No escrow accounts found.
        </div>
      ) : (
        <div className="overflow-x-auto">
          <table className="min-w-full divide-y divide-gray-200">
            <thead className="bg-gray-50">
              <tr>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Case</th>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
                  {title.includes('Client') ? 'Lawyer' : 'Client'}
                </th>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Total</th>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Available</th>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Released</th>
                <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
              </tr>
            </thead>
            <tbody className="bg-white divide-y divide-gray-200">
              {escrows.map((escrow) => (
                <tr key={escrow.id} className="hover:bg-gray-50">
                  <td className="px-6 py-4 text-sm text-gray-900">
                    <div className="font-medium">{escrow.caseTitle}</div>
                    <div className="text-xs text-gray-500">
                      {new Date(escrow.createdAt).toLocaleDateString()}
                    </div>
                  </td>
                  <td className="px-6 py-4 text-sm text-gray-900">
                    {title.includes('Client') ? escrow.lawyerName : escrow.clientName}
                  </td>
                  <td className="px-6 py-4 text-sm text-gray-900">
                    ${escrow.totalAmount.toFixed(2)}
                  </td>
                  <td className="px-6 py-4 text-sm text-gray-900">
                    ${escrow.availableAmount.toFixed(2)}
                  </td>
                  <td className="px-6 py-4 text-sm text-gray-900">
                    ${escrow.releasedAmount.toFixed(2)}
                  </td>
                  <td className="px-6 py-4 whitespace-nowrap">
                    <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(escrow.status)}`}>
                      {escrow.status}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );

  return (
    <div>
      <EscrowTable escrows={clientEscrows} title="As Client" />
      {isLawyer && <EscrowTable escrows={lawyerEscrows} title="As Lawyer" />}
    </div>
  );
}

// Analytics Tab Component
function AnalyticsTab({ data, isLawyer }: { data: FinancialData; isLawyer: boolean }) {
  return (
    <div className="space-y-6">
      {/* Chart placeholder */}
      <div className="bg-white rounded-lg shadow p-6">
        <h3 className="text-lg font-semibold text-gray-900 mb-4">6-Month Financial Trend</h3>
        <div className="h-64 bg-gray-100 rounded-lg flex items-center justify-center">
          <div className="text-center text-gray-500">
            <svg className="h-12 w-12 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" />
            </svg>
            <p>Chart visualization would go here</p>
            <p className="text-sm mt-1">Use Chart.js or similar library</p>
          </div>
        </div>
      </div>

      {/* Statistics Grid */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
        <div className="bg-white rounded-lg shadow p-6">
          <div className="text-sm font-medium text-gray-600">Total Transactions</div>
          <div className="text-2xl font-bold text-gray-900">{data.statistics.paymentCount}</div>
        </div>
        <div className="bg-white rounded-lg shadow p-6">
          <div className="text-sm font-medium text-gray-600">Average Transaction</div>
          <div className="text-2xl font-bold text-gray-900">
            ${data.statistics.averageTransactionAmount.toFixed(2)}
          </div>
        </div>
        <div className="bg-white rounded-lg shadow p-6">
          <div className="text-sm font-medium text-gray-600">Refunds</div>
          <div className="text-2xl font-bold text-gray-900">{data.statistics.refundCount}</div>
        </div>
        <div className="bg-white rounded-lg shadow p-6">
          <div className="text-sm font-medium text-gray-600">Platform Fees Saved</div>
          <div className="text-2xl font-bold text-green-600">
            ${data.statistics.platformFeesSaved.toFixed(2)}
          </div>
        </div>
      </div>
    </div>
  );
} 

CasperSecurity Mini