diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 3051673..283cae6 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -9,4 +9,4 @@ const page = () => { ); }; -export default page; \ No newline at end of file +export default page; diff --git a/src/app/dashboard/transactions/components/OutcomeStatistics.tsx b/src/app/dashboard/transactions/components/OutcomeStatistics.tsx deleted file mode 100644 index 791d037..0000000 --- a/src/app/dashboard/transactions/components/OutcomeStatistics.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import ProgressBar from './ProgressBar'; - -const OutcomeStatistics = () => { - return ( -
-
-

Outcome Statistics

- -
- - - -
- ); -}; - -export default OutcomeStatistics; diff --git a/src/app/dashboard/transactions/components/ProgressBar.tsx b/src/app/dashboard/transactions/components/ProgressBar.tsx deleted file mode 100644 index b1d4082..0000000 --- a/src/app/dashboard/transactions/components/ProgressBar.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import React from 'react'; - -interface ProgressBarProps { - label: string; - value: number | string; - color: string; -} - -const ProgressBar: React.FC = ({ label, value, color }) => { - return ( -
-
- {label} - {value}% -
- -
-
-
-
- ); -}; - -export default ProgressBar; diff --git a/src/app/dashboard/transactions/components/ProgressCircle.tsx b/src/app/dashboard/transactions/components/ProgressCircle.tsx deleted file mode 100644 index 9ccef35..0000000 --- a/src/app/dashboard/transactions/components/ProgressCircle.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import { RadialBarChart, RadialBar, ResponsiveContainer } from 'recharts'; - -interface ProgressCircleProps { - progress: number; -} - -const ProgressCircle: React.FC = ({ progress }) => { - const data = [{ value: progress }]; - - return ( -
- - - - - -
- ); -}; - -export default ProgressCircle; diff --git a/src/app/dashboard/transactions/components/ProjectCard.tsx b/src/app/dashboard/transactions/components/ProjectCard.tsx deleted file mode 100644 index add2243..0000000 --- a/src/app/dashboard/transactions/components/ProjectCard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import React from 'react'; - -import ProgressCircle from './ProgressCircle'; - -const ProjectCard = () => { - return ( -
-
-

Ndida Project

- -
-
- - -
    -
  • • 80% Ready for Publishing
  • -
  • • 17 Successful Transactions
  • -
  • • 3 Canceled Transactions
  • -
  • • 5 Appeals
  • -
  • • 4 Weeks remaining time.
  • -
-
-
- ); -}; - -export default ProjectCard; diff --git a/src/app/dashboard/transactions/components/TransactionFilter.tsx b/src/app/dashboard/transactions/components/TransactionFilter.tsx deleted file mode 100644 index 7189569..0000000 --- a/src/app/dashboard/transactions/components/TransactionFilter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -const TransactionFilter = () => { - const [sortBy, setSortBy] = useState('Date Added'); - - return ( -
- -
- ); -}; - -export default TransactionFilter; \ No newline at end of file diff --git a/src/app/dashboard/transactions/components/TransactionTable.tsx b/src/app/dashboard/transactions/components/TransactionTable.tsx deleted file mode 100644 index c34a084..0000000 --- a/src/app/dashboard/transactions/components/TransactionTable.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React from 'react'; -import TransactionFilter from './TransactionFilter'; - -interface Transaction { - id: number; - currency: string; - address: string; - amount: string; - note: string; - date: string; - time: string; - status: 'SUCCESSFUL' | 'CANCELED'; -} - -const transactions: Transaction[] = [ - { - id: 1, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 2, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'CANCELED', - }, - { - id: 3, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '2,000 STRK ($1,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 4, - currency: 'USDC', - address: '0xcK4R....7G4F', - amount: '$1,200', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 5, - currency: 'Fiat', - address: '00234*****', - amount: '$1,200', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 6, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 7, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'CANCELED', - }, - { - id: 8, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '2,000 STRK ($1,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 9, - currency: 'USDC', - address: '0xcK4R....7G4F', - amount: '$1,200', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 10, - currency: 'Fiat', - address: '00234*****', - amount: '$1,200', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, - { - id: 11, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'CANCELED', - }, - { - id: 12, - currency: 'STRK', - address: '0xcK4R....7G4F', - amount: '20,000 STRK ($10,200)', - note: 'Lorem ipsum dolor sit amet, consectetur...', - date: '12-12-24', - time: '5:34 UTC', - status: 'SUCCESSFUL', - }, -]; - -const TransactionTable: React.FC = () => { - return ( -
-
-
-

Ndida’s Transactions

-
-

Filter by:

- -
-
- - - - - - - - - - - - - - - - - {transactions.map((tx) => ( - - - - - - - - - - - ))} - -
S/NCurrencyAddressAmount RequestedNoteDateTimeStatus
{tx.id}{tx.currency}{tx.address}{tx.amount}{tx.note}{tx.date}{tx.time} - - ● {tx.status} - -
-
-
- ); -}; - -export default TransactionTable; \ No newline at end of file diff --git a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx new file mode 100644 index 0000000..8d1135b --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useState } from 'react'; +import { TransactionStats } from './transaction-stats'; +import { TransactionFilters } from './transaction-filters'; +import { TransactionTable } from './transaction-table'; +import { TransactionPagination } from './transaction-pagination'; +import { useTransactions } from '@/app/hooks/use-transactions'; +// import TransactionTable from '../../home_dashboard/TransactionTable'; + +type SortConfig = { + key: string | null; + direction: 'asc' | 'desc'; +}; + +export function TransactionDashboardSection() { + const [filters, setFilters] = useState({ + dao: 'all', + project: 'all', + action: 'all', + dateRange: null, + status: 'all', + search: '', + }); + + const [currentPage, setCurrentPage] = useState(1); + const [sortConfig, setSortConfig] = useState({ + key: null, + direction: 'asc', + }); + + const { transactions, stats, isLoading, isInitialLoading, error } = + useTransactions(filters, currentPage, sortConfig); + + // const handleSortChange = (config: SortConfig) => { + // setSortConfig(config); + // }; + + return ( +
+
+ + + + + +
+
+ ); +} diff --git a/src/app/dashboard/transactions/components/transaction-filters.tsx b/src/app/dashboard/transactions/components/transaction-filters.tsx new file mode 100644 index 0000000..f940f2a --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-filters.tsx @@ -0,0 +1,70 @@ + +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { cn } from "@/lib/utils" + +interface FilterState { + dao: string + project: string + action: string + dateRange: any + status: string + search: string +} + +interface TransactionFiltersProps { + filters: FilterState + onFiltersChange: (filters: FilterState) => void +} + +export function TransactionFilters({ filters, onFiltersChange }: TransactionFiltersProps) { + const updateFilter = (key: keyof FilterState, value: any) => { + onFiltersChange({ ...filters, [key]: value }) + } + + const selectTriggerClass = + "bg-[#1E1E2A] border-[#2A2A3A] text-white hover:bg-[#2A2A3A] focus:ring-purple-500 focus:ring-opacity-50" + const selectContentClass = "bg-[#1E1E2A] border-[#2A2A3A] text-white" + + return ( +
+
+ + + +
+ +
+ Filter by: + +
+
+ ) +} diff --git a/src/app/dashboard/transactions/components/transaction-pagination.tsx b/src/app/dashboard/transactions/components/transaction-pagination.tsx new file mode 100644 index 0000000..e29e4ed --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-pagination.tsx @@ -0,0 +1,93 @@ +import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + +interface TransactionPaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + isLoading?: boolean; +} + +export function TransactionPagination({ + currentPage, + totalPages, + onPageChange, + isLoading = false, +}: TransactionPaginationProps) { + const getVisiblePages = () => { + const delta = 2; + const range = []; + const rangeWithDots = []; + + for ( + let i = Math.max(2, currentPage - delta); + i <= Math.min(totalPages - 1, currentPage + delta); + i++ + ) { + range.push(i); + } + + if (currentPage - delta > 2) { + rangeWithDots.push(1, '...'); + } else { + rangeWithDots.push(1); + } + + rangeWithDots.push(...range); + + if (currentPage + delta < totalPages - 1) { + rangeWithDots.push('...', totalPages); + } else if (totalPages > 1) { + rangeWithDots.push(totalPages); + } + + return rangeWithDots; + }; + + if (totalPages <= 1) return null; + + return ( +
+ + + {getVisiblePages().map((page, index) => ( + + ))} + + +
+ ); +} diff --git a/src/app/dashboard/transactions/components/transaction-stats.tsx b/src/app/dashboard/transactions/components/transaction-stats.tsx new file mode 100644 index 0000000..d1dc126 --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-stats.tsx @@ -0,0 +1,74 @@ +import { Card, CardContent } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; + +interface StatsData { + total: number; + canceled: number; + successful: number; +} + +interface TransactionStatsProps { + stats: StatsData | null; + loading: boolean; +} + +export function TransactionStats({ stats, loading }: TransactionStatsProps) { + if (loading) { + return ( +
+ {[1, 2, 3].map((i) => ( + + + + + + + ))} +
+ ); + } + + const statCards = [ + { + title: 'Total Transactions', + value: stats?.total || 0, + subtitle: 'All', + }, + { + title: 'Total Canceled', + value: stats?.canceled || 0, + subtitle: 'All', + }, + { + title: 'Total Successful', + value: stats?.successful || 0, + subtitle: 'All', + }, + ]; + + return ( +
+ {statCards.map((stat, index) => ( + + +
+

+ {stat.title} +

+ {stat.subtitle} ▾ +
+

+ {stat.value} +

+
+
+ ))} +
+ ); +} diff --git a/src/app/dashboard/transactions/components/transaction-table.tsx b/src/app/dashboard/transactions/components/transaction-table.tsx new file mode 100644 index 0000000..a2130ba --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-table.tsx @@ -0,0 +1,170 @@ +import { FileX, Loader2 } from 'lucide-react'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Alert, AlertDescription } from '@/components/ui/alert'; + +interface Transaction { + id: string; + sn: number; + project: string; + currency: string; + amount: string; + address: string; + note: string; + dateTime: string; + status: 'successful' | 'canceled'; +} + +interface TransactionTableProps { + transactions: Transaction[]; + loading: boolean; + isProcessing?: boolean; + error: string | null; +} + +export function TransactionTable({ + transactions, + loading, + isProcessing = false, + error, +}: TransactionTableProps) { + if (loading) { + return ( +
+
+ + + + + + + + + + + + + + + {[...Array(10)].map((_, i) => ( + + + + + + + + + + + ))} + +
S/NProjectsCurrencyAmount SentAddress/AccountNoteDate/timeStatus
+ + + + + + + + + + + + + + + +
+
+
+ ); + } + + if (error) { + return ( +
+ + {error} + +
+ ); + } + + return ( +
+ {isProcessing && ( +
+
+ + Processing... +
+
+ )} + +
+ + + + + + + + + + + + + + + + {transactions.length > 0 ? ( + transactions.map((transaction) => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
S/NProjectsCurrencyAmount SentAddress/AccountNoteDate/timeStatus
{transaction.sn}{transaction.project} + {transaction.currency} + {transaction.amount}{transaction.address}{transaction.note} +
{transaction.dateTime}
+
+ + ● {transaction.status.toUpperCase()} + +
+
+ +

+ No transactions found +

+

+ Try adjusting your filters or search criteria +

+
+
+
+
+ ); +} diff --git a/src/app/dashboard/transactions/page.tsx b/src/app/dashboard/transactions/page.tsx index 3948960..dfe71ad 100644 --- a/src/app/dashboard/transactions/page.tsx +++ b/src/app/dashboard/transactions/page.tsx @@ -1,30 +1,5 @@ -import React from 'react'; -import ProjectCard from './components/ProjectCard'; -import OutcomeStatistics from './components/OutcomeStatistics'; -import { ArrowLeft } from 'lucide-react'; -import TransactionTable from './components/TransactionTable'; +import { TransactionDashboardSection } from './components/transaction-dashboard-section'; -const Dashboard = () => { - return ( -
-
-

- Records -

- - Add New Project - -
- - -
- - -
- - -
- ); -}; - -export default Dashboard; +export default function TransactionsPage() { + return ; +} diff --git a/src/app/hooks/use-transactions.ts b/src/app/hooks/use-transactions.ts new file mode 100644 index 0000000..db9c194 --- /dev/null +++ b/src/app/hooks/use-transactions.ts @@ -0,0 +1,274 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +type SortConfig = { + key: string | null; + direction: 'asc' | 'desc'; +}; + +interface Transaction { + id: string; + sn: number; + project: string; + currency: string; + amount: string; + address: string; + note: string; + dateTime: string; + status: 'successful' | 'canceled'; +} + +interface StatsData { + total: number; + canceled: number; + successful: number; +} + +// Mock data - replace with actual API calls +const mockTransactions: Transaction[] = [ + { + id: '1', + sn: 1, + project: 'Fragma', + currency: 'STRK', + amount: '20,000 STRK', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '2', + sn: 2, + project: 'Fragma', + currency: 'STRK', + amount: '20,000 STRK', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'canceled', + }, + { + id: '3', + sn: 3, + project: 'Nulda', + currency: 'STRK', + amount: '2,000 STRK', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '4', + sn: 4, + project: 'Fragma', + currency: 'USDC', + amount: '$1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '5', + sn: 5, + project: 'Nulda', + currency: 'Fiat', + amount: '$1,200', + address: '0123*****', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '6', + sn: 6, + project: 'Starkz', + currency: 'STRK', + amount: '2,000 STRK $1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'canceled', + }, + { + id: '7', + sn: 7, + project: 'Nulda', + currency: 'Fiat', + amount: '$1,200', + address: '0256G*****', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '8', + sn: 8, + project: 'Fragma', + currency: 'USDC', + amount: '$1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '9', + sn: 9, + project: 'Fragma', + currency: 'STRK', + amount: '$1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '10', + sn: 10, + project: 'Nulda', + currency: 'STRK', + amount: '2,000 STRK', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '11', + sn: 11, + project: 'Nulda', + currency: 'USDC', + amount: '$1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'successful', + }, + { + id: '12', + sn: 12, + project: 'Nulda', + currency: 'USDC', + amount: '$1,200', + address: '0xeAc6...7CAF', + note: 'Lorem ipsum dolor sit amet, consectetur...', + dateTime: '5:54UTC', + status: 'canceled', + }, +]; + +export function useTransactions( + filters: any, + currentPage: number, + sortConfig: SortConfig +) { + const [transactions, setTransactions] = useState([]); + const [stats, setStats] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isInitialLoading, setIsInitialLoading] = useState(true); + const [error, setError] = useState(null); + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + const fetchTransactions = async () => { + // Only show full loading state on initial load + if (!isInitialized) { + setIsInitialLoading(true); + } + + setIsLoading(true); + setError(null); + + try { + // Simulate API delay - shorter for subsequent operations + await new Promise((resolve) => + setTimeout(resolve, isInitialized ? 300 : 1000) + ); + + let filteredTransactions = [...mockTransactions]; + + // Apply filters + if (filters.search) { + filteredTransactions = filteredTransactions.filter( + (t) => + t.project.toLowerCase().includes(filters.search.toLowerCase()) || + t.currency.toLowerCase().includes(filters.search.toLowerCase()) || + t.address.toLowerCase().includes(filters.search.toLowerCase()) || + t.amount.toLowerCase().includes(filters.search.toLowerCase()) + ); + } + + if (filters.dao !== 'all' && filters.dao) { + // In a real app, you'd filter by DAO ID + filteredTransactions = filteredTransactions.filter((t) => + filters.dao === 'eth2' + ? ['Fragma', 'Nulda'].includes(t.project) + : t.project === 'Starkz' + ); + } + + if (filters.project !== 'all' && filters.project) { + filteredTransactions = filteredTransactions.filter( + (t) => t.project.toLowerCase() === filters.project.toLowerCase() + ); + } + + if (filters.status !== 'all' && filters.status) { + filteredTransactions = filteredTransactions.filter( + (t) => t.status === filters.status + ); + } + + // Apply sorting + if (sortConfig.key) { + filteredTransactions.sort((a: any, b: any) => { + const aValue = a[sortConfig.key as keyof Transaction]; + const bValue = b[sortConfig.key as keyof Transaction]; + + if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1; + if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1; + return 0; + }); + } + + // Calculate stats + const totalTransactions = filteredTransactions.length; + const canceledTransactions = filteredTransactions.filter( + (t) => t.status === 'canceled' + ).length; + const successfulTransactions = filteredTransactions.filter( + (t) => t.status === 'successful' + ).length; + + setStats({ + total: totalTransactions, + canceled: canceledTransactions, + successful: successfulTransactions, + }); + + // Apply pagination + const startIndex = (currentPage - 1) * 10; + const paginatedTransactions = filteredTransactions.slice( + startIndex, + startIndex + 10 + ); + + setTransactions(paginatedTransactions); + setIsInitialized(true); + } catch (err) { + setError('Failed to fetch transactions. Please try again.'); + } finally { + setIsLoading(false); + setIsInitialLoading(false); + } + }; + + fetchTransactions(); + }, [filters, currentPage, sortConfig, isInitialized]); + + return { transactions, stats, isLoading, isInitialLoading, error }; +} diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..d7e45f7 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton }