From 316c4c6aeb4631b7de5cdf316a49f492978ff235 Mon Sep 17 00:00:00 2001 From: legend4tech Date: Sun, 1 Jun 2025 00:06:24 +0100 Subject: [PATCH 1/4] feat: Implement Transaction Page in Dashboard --- .../components/OutcomeStatistics.tsx | 18 -- .../transactions/components/ProgressBar.tsx | 29 -- .../components/ProgressCircle.tsx | 34 --- .../transactions/components/ProjectCard.tsx | 29 -- .../components/TransactionFilter.tsx | 23 -- .../components/TransactionTable.tsx | 197 ------------- .../transaction-dashboard-section.tsx | 65 +++++ .../components/transaction-filters.tsx | 71 +++++ .../components/transaction-pagination.tsx | 81 ++++++ .../components/transaction-stats.tsx | 76 +++++ .../components/transaction-table.tsx | 194 +++++++++++++ src/app/dashboard/transactions/page.tsx | 33 +-- src/app/hooks/use-transactions.ts | 263 ++++++++++++++++++ src/components/ui/skeleton.tsx | 15 + 14 files changed, 769 insertions(+), 359 deletions(-) delete mode 100644 src/app/dashboard/transactions/components/OutcomeStatistics.tsx delete mode 100644 src/app/dashboard/transactions/components/ProgressBar.tsx delete mode 100644 src/app/dashboard/transactions/components/ProgressCircle.tsx delete mode 100644 src/app/dashboard/transactions/components/ProjectCard.tsx delete mode 100644 src/app/dashboard/transactions/components/TransactionFilter.tsx delete mode 100644 src/app/dashboard/transactions/components/TransactionTable.tsx create mode 100644 src/app/dashboard/transactions/components/transaction-dashboard-section.tsx create mode 100644 src/app/dashboard/transactions/components/transaction-filters.tsx create mode 100644 src/app/dashboard/transactions/components/transaction-pagination.tsx create mode 100644 src/app/dashboard/transactions/components/transaction-stats.tsx create mode 100644 src/app/dashboard/transactions/components/transaction-table.tsx create mode 100644 src/app/hooks/use-transactions.ts create mode 100644 src/components/ui/skeleton.tsx 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..7da2b76 --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx @@ -0,0 +1,65 @@ +'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'; + +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, loading, 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..21d390f --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-filters.tsx @@ -0,0 +1,71 @@ +"use client" + +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..c8d482f --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-pagination.tsx @@ -0,0 +1,81 @@ +"use client" + +import { ChevronLeft, ChevronRight } from "lucide-react" +import { Button } from "@/components/ui/button" + +interface TransactionPaginationProps { + currentPage: number + totalPages: number + onPageChange: (page: number) => void +} + +export function TransactionPagination({ currentPage, totalPages, onPageChange }: 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..3cbc72b --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-stats.tsx @@ -0,0 +1,76 @@ +'use client'; + +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..5d46a8c --- /dev/null +++ b/src/app/dashboard/transactions/components/transaction-table.tsx @@ -0,0 +1,194 @@ +'use client'; + +import { ChevronUp, ChevronDown, FileX } from 'lucide-react'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +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'; +} + +export interface SortConfig { + key: string | null; + direction: 'asc' | 'desc'; +} + +interface TransactionTableProps { + transactions: Transaction[]; + loading: boolean; + error: string | null; + sortConfig: SortConfig; + onSort: (config: SortConfig) => void; +} + +export function TransactionTable({ + transactions, + loading, + error, + sortConfig, + onSort, +}: TransactionTableProps) { + const handleSort = (key: string) => { + const direction = + sortConfig.key === key && sortConfig.direction === 'asc' ? 'desc' : 'asc'; + onSort({ key, direction }); + }; + + const SortIcon = ({ column }: { column: string }) => { + if (sortConfig.key !== column) return null; + return sortConfig.direction === 'asc' ? ( + + ) : ( + + ); + }; + + const tableHeaders = [ + { key: 'sn', label: 'S/N' }, + { key: 'project', label: 'Project' }, + { key: 'currency', label: 'Currency' }, + { key: 'amount', label: 'Amount' }, + { key: 'address', label: 'Address/Account' }, + { key: 'note', label: 'Note' }, + { key: 'dateTime', label: 'Date/Time' }, + { key: 'status', label: 'Status' }, + ]; + + if (loading) { + return ( +
+ + + + {tableHeaders.map((header) => ( + + {header.label} + + ))} + + + + {[...Array(10)].map((_, i) => ( + + {[...Array(8)].map((_, j) => ( + + + + ))} + + ))} + +
+
+ ); + } + + if (error) { + return ( + + {error} + + ); + } + + return ( +
+ + + + {tableHeaders.map((header) => ( + handleSort(header.key)} + > +
+ {header.label} + +
+
+ ))} +
+
+ + {transactions.length > 0 ? ( + transactions.map((transaction) => ( + + + {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..7a8b140 --- /dev/null +++ b/src/app/hooks/use-transactions.ts @@ -0,0 +1,263 @@ +'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 [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchTransactions = async () => { + setLoading(true); + setError(null); + + try { + // Simulate API delay + await new Promise((resolve) => setTimeout(resolve, 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); + } catch (err) { + setError('Failed to fetch transactions. Please try again.'); + } finally { + setLoading(false); + } + }; + + fetchTransactions(); + }, [filters, currentPage, sortConfig]); + + return { transactions, stats, loading, 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 } From bc5cab6ac43735a5a24d129cd2f1fbce51af696b Mon Sep 17 00:00:00 2001 From: legend4tech Date: Sun, 1 Jun 2025 00:27:22 +0100 Subject: [PATCH 2/4] feat: Implement Transaction Page in Dashboard --- .../transaction-dashboard-section.tsx | 19 ++--- .../components/transaction-pagination.tsx | 72 ++++++++++------- .../components/transaction-table.tsx | 81 ++++++++++++++----- src/app/hooks/use-transactions.ts | 25 ++++-- 4 files changed, 133 insertions(+), 64 deletions(-) diff --git a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx index 7da2b76..39f3c07 100644 --- a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx +++ b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx @@ -7,6 +7,7 @@ import { TransactionTable } from './transaction-table'; import { TransactionPagination } from './transaction-pagination'; import { useTransactions } from '@/app/hooks/use-transactions'; +// Define the SortConfig type to match what TransactionTable expects type SortConfig = { key: string | null; direction: 'asc' | 'desc'; @@ -23,32 +24,31 @@ export function TransactionDashboardSection() { }); const [currentPage, setCurrentPage] = useState(1); - + // Fix the type definition to match the expected SortConfig type const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc', }); - const { transactions, stats, loading, error } = useTransactions( - filters, - currentPage, - sortConfig - ); + const { transactions, stats, isLoading, isInitialLoading, error } = + useTransactions(filters, currentPage, sortConfig); + // Create a handler function that matches the expected type const handleSortChange = (config: SortConfig) => { setSortConfig(config); }; return ( -
+
- +
diff --git a/src/app/dashboard/transactions/components/transaction-pagination.tsx b/src/app/dashboard/transactions/components/transaction-pagination.tsx index c8d482f..67f7f74 100644 --- a/src/app/dashboard/transactions/components/transaction-pagination.tsx +++ b/src/app/dashboard/transactions/components/transaction-pagination.tsx @@ -1,42 +1,52 @@ -"use client" +'use client'; -import { ChevronLeft, ChevronRight } from "lucide-react" -import { Button } from "@/components/ui/button" +import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; interface TransactionPaginationProps { - currentPage: number - totalPages: number - onPageChange: (page: number) => void + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + isLoading?: boolean; } -export function TransactionPagination({ currentPage, totalPages, onPageChange }: TransactionPaginationProps) { +export function TransactionPagination({ + currentPage, + totalPages, + onPageChange, + isLoading = false, +}: TransactionPaginationProps) { const getVisiblePages = () => { - const delta = 2 - const range = [] - const rangeWithDots = [] + 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) + 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, "...") + rangeWithDots.push(1, '...'); } else { - rangeWithDots.push(1) + rangeWithDots.push(1); } - rangeWithDots.push(...range) + rangeWithDots.push(...range); if (currentPage + delta < totalPages - 1) { - rangeWithDots.push("...", totalPages) + rangeWithDots.push('...', totalPages); } else if (totalPages > 1) { - rangeWithDots.push(totalPages) + rangeWithDots.push(totalPages); } - return rangeWithDots - } + return rangeWithDots; + }; - if (totalPages <= 1) return null + if (totalPages <= 1) return null; return (
@@ -44,7 +54,7 @@ export function TransactionPagination({ currentPage, totalPages, onPageChange }: variant="outline" size="sm" onClick={() => onPageChange(currentPage - 1)} - disabled={currentPage === 1} + disabled={currentPage === 1 || isLoading} className="bg-[#1E1E2A] border-[#2A2A3A] text-white hover:bg-[#2A2A3A] h-9 px-3" > @@ -53,17 +63,21 @@ export function TransactionPagination({ currentPage, totalPages, onPageChange }: {getVisiblePages().map((page, index) => ( ))} @@ -71,11 +85,11 @@ export function TransactionPagination({ currentPage, totalPages, onPageChange }: variant="outline" size="sm" onClick={() => onPageChange(currentPage + 1)} - disabled={currentPage === totalPages} + disabled={currentPage === totalPages || isLoading} className="bg-[#1E1E2A] border-[#2A2A3A] text-white hover:bg-[#2A2A3A] h-9 px-3" >
- ) + ); } diff --git a/src/app/dashboard/transactions/components/transaction-table.tsx b/src/app/dashboard/transactions/components/transaction-table.tsx index 5d46a8c..1d90014 100644 --- a/src/app/dashboard/transactions/components/transaction-table.tsx +++ b/src/app/dashboard/transactions/components/transaction-table.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ChevronUp, ChevronDown, FileX } from 'lucide-react'; +import { ChevronUp, ChevronDown, FileX, Loader2 } from 'lucide-react'; import { Table, TableBody, @@ -33,6 +33,7 @@ export interface SortConfig { interface TransactionTableProps { transactions: Transaction[]; loading: boolean; + isProcessing?: boolean; error: string | null; sortConfig: SortConfig; onSort: (config: SortConfig) => void; @@ -41,22 +42,50 @@ interface TransactionTableProps { export function TransactionTable({ transactions, loading, + isProcessing = false, error, sortConfig, onSort, }: TransactionTableProps) { - const handleSort = (key: string) => { - const direction = - sortConfig.key === key && sortConfig.direction === 'asc' ? 'desc' : 'asc'; + const handleSort = (key: string, direction: 'asc' | 'desc') => { onSort({ key, direction }); }; - const SortIcon = ({ column }: { column: string }) => { - if (sortConfig.key !== column) return null; - return sortConfig.direction === 'asc' ? ( - - ) : ( - + const SortButtons = ({ column }: { column: string }) => { + const isActiveAsc = + sortConfig.key === column && sortConfig.direction === 'asc'; + const isActiveDesc = + sortConfig.key === column && sortConfig.direction === 'desc'; + + return ( +
+ + +
); }; @@ -73,16 +102,22 @@ export function TransactionTable({ if (loading) { return ( -
+
- + {tableHeaders.map((header) => ( - {header.label} +
+ {header.label} +
+ + +
+
))}
@@ -112,19 +147,27 @@ export function TransactionTable({ } return ( -
+
+ {isProcessing && ( +
+
+ + Processing... +
+
+ )} +
{tableHeaders.map((header) => ( handleSort(header.key)} + className="text-gray-300 font-medium h-16 px-6" > -
- {header.label} - +
+ {header.label} +
))} diff --git a/src/app/hooks/use-transactions.ts b/src/app/hooks/use-transactions.ts index 7a8b140..db9c194 100644 --- a/src/app/hooks/use-transactions.ts +++ b/src/app/hooks/use-transactions.ts @@ -168,17 +168,26 @@ export function useTransactions( ) { const [transactions, setTransactions] = useState([]); const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(true); + const [isLoading, setIsLoading] = useState(true); + const [isInitialLoading, setIsInitialLoading] = useState(true); const [error, setError] = useState(null); + const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { const fetchTransactions = async () => { - setLoading(true); + // Only show full loading state on initial load + if (!isInitialized) { + setIsInitialLoading(true); + } + + setIsLoading(true); setError(null); try { - // Simulate API delay - await new Promise((resolve) => setTimeout(resolve, 1000)); + // Simulate API delay - shorter for subsequent operations + await new Promise((resolve) => + setTimeout(resolve, isInitialized ? 300 : 1000) + ); let filteredTransactions = [...mockTransactions]; @@ -249,15 +258,17 @@ export function useTransactions( ); setTransactions(paginatedTransactions); + setIsInitialized(true); } catch (err) { setError('Failed to fetch transactions. Please try again.'); } finally { - setLoading(false); + setIsLoading(false); + setIsInitialLoading(false); } }; fetchTransactions(); - }, [filters, currentPage, sortConfig]); + }, [filters, currentPage, sortConfig, isInitialized]); - return { transactions, stats, loading, error }; + return { transactions, stats, isLoading, isInitialLoading, error }; } From c9922636b9a6dd91551f98af15eb5794861e1904 Mon Sep 17 00:00:00 2001 From: legend4tech Date: Sun, 1 Jun 2025 00:33:23 +0100 Subject: [PATCH 3/4] feat: Implement Transaction Page in Dashboard --- .../components/transaction-dashboard-section.tsx | 5 +---- .../transactions/components/transaction-filters.tsx | 1 - .../transactions/components/transaction-pagination.tsx | 2 -- .../transactions/components/transaction-stats.tsx | 2 -- .../transactions/components/transaction-table.tsx | 9 +++++---- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx index 39f3c07..fcba7fb 100644 --- a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx +++ b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx @@ -7,7 +7,6 @@ import { TransactionTable } from './transaction-table'; import { TransactionPagination } from './transaction-pagination'; import { useTransactions } from '@/app/hooks/use-transactions'; -// Define the SortConfig type to match what TransactionTable expects type SortConfig = { key: string | null; direction: 'asc' | 'desc'; @@ -24,7 +23,6 @@ export function TransactionDashboardSection() { }); const [currentPage, setCurrentPage] = useState(1); - // Fix the type definition to match the expected SortConfig type const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc', @@ -33,13 +31,12 @@ export function TransactionDashboardSection() { const { transactions, stats, isLoading, isInitialLoading, error } = useTransactions(filters, currentPage, sortConfig); - // Create a handler function that matches the expected type 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 index 21d390f..f940f2a 100644 --- a/src/app/dashboard/transactions/components/transaction-filters.tsx +++ b/src/app/dashboard/transactions/components/transaction-filters.tsx @@ -1,4 +1,3 @@ -"use client" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { cn } from "@/lib/utils" diff --git a/src/app/dashboard/transactions/components/transaction-pagination.tsx b/src/app/dashboard/transactions/components/transaction-pagination.tsx index 67f7f74..e29e4ed 100644 --- a/src/app/dashboard/transactions/components/transaction-pagination.tsx +++ b/src/app/dashboard/transactions/components/transaction-pagination.tsx @@ -1,5 +1,3 @@ -'use client'; - import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/src/app/dashboard/transactions/components/transaction-stats.tsx b/src/app/dashboard/transactions/components/transaction-stats.tsx index 3cbc72b..d1dc126 100644 --- a/src/app/dashboard/transactions/components/transaction-stats.tsx +++ b/src/app/dashboard/transactions/components/transaction-stats.tsx @@ -1,5 +1,3 @@ -'use client'; - import { Card, CardContent } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; diff --git a/src/app/dashboard/transactions/components/transaction-table.tsx b/src/app/dashboard/transactions/components/transaction-table.tsx index 1d90014..905029d 100644 --- a/src/app/dashboard/transactions/components/transaction-table.tsx +++ b/src/app/dashboard/transactions/components/transaction-table.tsx @@ -1,5 +1,3 @@ -'use client'; - import { ChevronUp, ChevronDown, FileX, Loader2 } from 'lucide-react'; import { Table, @@ -124,7 +122,10 @@ export function TransactionTable({ {[...Array(10)].map((_, i) => ( - + {[...Array(8)].map((_, j) => ( @@ -216,7 +217,7 @@ export function TransactionTable({ )) ) : ( - +
From 0a2bcc418423a5f779dc35dacdb65801d00200f3 Mon Sep 17 00:00:00 2001 From: legend4tech Date: Tue, 3 Jun 2025 03:57:31 +0100 Subject: [PATCH 4/4] fix table layout --- src/app/dashboard/page.tsx | 2 +- .../transaction-dashboard-section.tsx | 15 +- .../components/transaction-table.tsx | 302 +++++++----------- 3 files changed, 125 insertions(+), 194 deletions(-) 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/transaction-dashboard-section.tsx b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx index fcba7fb..8d1135b 100644 --- a/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx +++ b/src/app/dashboard/transactions/components/transaction-dashboard-section.tsx @@ -6,6 +6,7 @@ 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; @@ -31,15 +32,14 @@ export function TransactionDashboardSection() { const { transactions, stats, isLoading, isInitialLoading, error } = useTransactions(filters, currentPage, sortConfig); - const handleSortChange = (config: SortConfig) => { - setSortConfig(config); - }; + // const handleSortChange = (config: SortConfig) => { + // setSortConfig(config); + // }; return (
-
+
- - void; } export function TransactionTable({ @@ -42,113 +26,72 @@ export function TransactionTable({ loading, isProcessing = false, error, - sortConfig, - onSort, }: TransactionTableProps) { - const handleSort = (key: string, direction: 'asc' | 'desc') => { - onSort({ key, direction }); - }; - - const SortButtons = ({ column }: { column: string }) => { - const isActiveAsc = - sortConfig.key === column && sortConfig.direction === 'asc'; - const isActiveDesc = - sortConfig.key === column && sortConfig.direction === 'desc'; - - return ( -
- - -
- ); - }; - - const tableHeaders = [ - { key: 'sn', label: 'S/N' }, - { key: 'project', label: 'Project' }, - { key: 'currency', label: 'Currency' }, - { key: 'amount', label: 'Amount' }, - { key: 'address', label: 'Address/Account' }, - { key: 'note', label: 'Note' }, - { key: 'dateTime', label: 'Date/Time' }, - { key: 'status', label: 'Status' }, - ]; - if (loading) { return ( -
-
- - - {tableHeaders.map((header) => ( - -
- {header.label} -
- - -
-
-
+
+
+
+ + + + + + + + + + + + + + {[...Array(10)].map((_, i) => ( + + + + + + + + + + ))} - - - - {[...Array(10)].map((_, i) => ( - - {[...Array(8)].map((_, j) => ( - - - - ))} - - ))} - -
S/NProjectsCurrencyAmount SentAddress/AccountNoteDate/timeStatus
+ + + + + + + + + + + + + + + +
+ + +
); } if (error) { return ( - - {error} - +
+ + {error} + +
); } return ( -
+
{isProcessing && (
@@ -158,81 +101,70 @@ export function TransactionTable({
)} - - - - {tableHeaders.map((header) => ( - -
- {header.label} - -
-
- ))} -
-
- - {transactions.length > 0 ? ( - transactions.map((transaction) => ( - - - {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 -

-
-
-
- )} -
-
+
+ + + + + + + + + + + + + + + + {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 +

+
+
+
); }