AI Review Center
Commit Review Workspace ?
Select a commit on the left to inspect quality, security, business value, findings, and suggested code changes.
Project AI Score
?
72%
Quality Avg
?
77%
Security Avg
?
64%
Reviews
?
91
Review Result ?
solutionbowl/super-admin · 1af2bf90
The commit introduces a CsvExportButton component for downloading CSV files and integrates it into the MultiBet route. The implementation uses typed props and memoization for efficient CSV data generation. The new routes for user exposure details and bets are also added. Overall, the quality is good with proper React patterns and error handling. Business value is high given the export feature and new routes. Bug risk is low but could be improved by better validation and tests. Security score is good but depends on underlying exportCsv implementation. The changes appear genuine with meaningful feature additions.
Quality
?
85%
Security
?
80%
Business Value
?
90%
Maintainability
?
88%
Issues & Suggested Fixes
?
1AI detects the issue
2Shows file, line, or evidence
3Suggests the fix to apply
Handleclick Does Not Handle Exportcsv Erro...
Where
src/components/common/CsvExportButton.tsx:16
Issue / Evidence
handleClick does not handle exportCsv errors
Suggested Fix
add try-catch to handle potential export failures gracefully
Usememo Dependency On 'Data' Is Broad
Where
src/routes/MultiBet.tsx:52
Issue / Evidence
useMemo dependency on 'data' is broad
Suggested Fix
refine dependency to avoid unnecessary recalculations or add comments
Missing Validation
Where
src/routes/MultiBet.tsx:108
Issue / Evidence
input validation returns early after toast
Suggested Fix
consider disabling submit button to prevent invalid submits
Non Descriptive Commit Message
Where
commit message
Issue / Evidence
non-descriptive commit message
Suggested Fix
add description that highlights key additions such as CSV export and new routes to improve clarity
Code Change Preview · src/components/common/CsvExportButton.tsx
?
Removed / Before Commit
- diff --git a/src/components/common/CsvExportButton.tsx b/src/components/common/CsvExportButton.tsx - new file mode 100644 - index 0000000..36f2cf6
Added / After Commit
+ diff --git a/src/components/common/CsvExportButton.tsx b/src/components/common/CsvExportButton.tsx + new file mode 100644 + index 0000000..36f2cf6 + import { Download } from "lucide-react"; + import { Button } from "@/components/ui/button"; + import { exportCsv } from "@/utils/csv/exportCsv"; + import type { CsvExportButtonProps } from "@/utils/csv/types"; + + export default function CsvExportButton<TRow>({ + filename, + rows, + columns, + children = "Download CSV", + disabled, + className, + onExport, + ...buttonProps + }: CsvExportButtonProps<TRow>) {
Removed / Before Commit
- diff --git a/src/main.tsx b/src/main.tsx - index fd8c3ab..919f552 100644 - import { createRoot } from "react-dom/client"; - import { RouterProvider } from "@tanstack/react-router"; - - import { router } from "./router"; - import "./styles.css"; - - const rootElement = document.getElementById("root");
Added / After Commit
+ diff --git a/src/main.tsx b/src/main.tsx + index fd8c3ab..919f552 100644 + import { createRoot } from "react-dom/client"; + import { RouterProvider } from "@tanstack/react-router"; + + import { router } from "./router" + import "./styles.css"; + + const rootElement = document.getElementById("root");
Removed / Before Commit
- diff --git a/src/routes/agency-management.user-details.$userId.tsx b/src/routes/agency-management.user-details.$userId.tsx - index 643ee14..d948c69 100644 - import { useQuery } from "@tanstack/react-query"; - import { useMemo, useState } from "react"; - import { motion } from "motion/react"; - import { - ArrowLeft, - Calendar, - Download, - RefreshCw, - Search, - ShieldCheck, - UserRound, - } from "lucide-react"; - import { Card } from "@/components/ui/card"; - import { Button } from "@/components/ui/button"; - import { Input } from "@/components/ui/input"; - CreditTransactionUser,
Added / After Commit
+ diff --git a/src/routes/agency-management.user-details.$userId.tsx b/src/routes/agency-management.user-details.$userId.tsx + index 643ee14..d948c69 100644 + import { useQuery } from "@tanstack/react-query"; + import { useMemo, useState } from "react"; + import { motion } from "motion/react"; + import { ArrowLeft, Calendar, RefreshCw, Search, ShieldCheck, UserRound } from "lucide-react"; + import { Card } from "@/components/ui/card"; + import { Button } from "@/components/ui/button"; + import { Input } from "@/components/ui/input"; + CreditTransactionUser, + DownlineUser, + } from "@/services/api/internalTypes"; + import CsvExportButton from "@/components/common/CsvExportButton"; + + export const Route = createFileRoute("/agency-management/user-details/$userId")({ + component: UserDetailsPage, + <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> + <div>
Removed / Before Commit
- diff --git a/src/routes/balance.tsx b/src/routes/balance.tsx - index f0c2fee..3dfed93 100644 - import { createFileRoute } from "@tanstack/react-router"; - import { useQuery } from "@tanstack/react-query"; - import { motion } from "motion/react"; - import { - Activity, - ArrowDownRight, - /> - Refresh - </Button> - </div> - </div>
Added / After Commit
+ diff --git a/src/routes/balance.tsx b/src/routes/balance.tsx + index f0c2fee..3dfed93 100644 + import { createFileRoute } from "@tanstack/react-router"; + import { useQuery } from "@tanstack/react-query"; + import { motion } from "motion/react"; + import { Download} from "lucide-react"; + import { + Activity, + ArrowDownRight, + /> + Refresh + </Button> + + </div> + </div>
Removed / Before Commit
- diff --git a/src/routes/bet-list.tsx b/src/routes/bet-list.tsx - index 2defb98..c6d13a2 100644 - import { useQuery } from "@tanstack/react-query"; - import TablePagination from "@/components/common/TablePagination"; - import { format } from "date-fns"; - import { Download, Eye } from "lucide-react"; - import DateRangePicker from "@/components/common/DateRangePicker"; - import { useEffect, useMemo, useState } from "react"; - import type { DateRange } from "react-day-picker"; - import { Button } from "@/components/ui/button"; - import { Input } from "@/components/ui/input"; - import { - Select, - SelectContent, - } from "@/components/ui/select"; - import { internalServices } from "@/services/api/internalServices"; - import type { Bet, MultiBetLeg } from "@/services/api/betTypes"; - import { cn } from "@/lib/utils";
Added / After Commit
+ diff --git a/src/routes/bet-list.tsx b/src/routes/bet-list.tsx + index 2defb98..c6d13a2 100644 + import { useQuery } from "@tanstack/react-query"; + import TablePagination from "@/components/common/TablePagination"; + import { format } from "date-fns"; + import { Eye } from "lucide-react"; + import DateRangePicker from "@/components/common/DateRangePicker"; + import { useEffect, useMemo, useState } from "react"; + import type { DateRange } from "react-day-picker"; + import { Button } from "@/components/ui/button"; + import { Input } from "@/components/ui/input"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Select, + SelectContent, + } from "@/components/ui/select"; + import { internalServices } from "@/services/api/internalServices"; + import type { Bet, MultiBetLeg } from "@/services/api/betTypes";
Removed / Before Commit
- diff --git a/src/routes/bet-ticket.tsx b/src/routes/bet-ticket.tsx - index cb3010e..cacc2ea 100644 - import { createFileRoute ,useNavigate} from "@tanstack/react-router"; - import { useState } from "react"; - import TablePagination from "@/components/common/TablePagination"; - import { useQuery } from "@tanstack/react-query"; - import { motion } from "motion/react"; - import { cn } from "@/lib/utils"; - import { format } from "date-fns"; - import { useAutoRefresh } from "@/hooks/useAutoRefresh"; - - export const Route = createFileRoute("/bet-ticket")({ - component: BetTicketPage, - head: () => ({ meta: [{ title: "Bet Ticker - B2B-B2C Panel" }] }), - }); - - - interface BetTickerParams {
Added / After Commit
+ diff --git a/src/routes/bet-ticket.tsx b/src/routes/bet-ticket.tsx + index cb3010e..cacc2ea 100644 + import { createFileRoute, useNavigate } from "@tanstack/react-router"; + import { useMemo, useState } from "react"; + import TablePagination from "@/components/common/TablePagination"; + import { useQuery } from "@tanstack/react-query"; + import { motion } from "motion/react"; + import { cn } from "@/lib/utils"; + import { format } from "date-fns"; + import { useAutoRefresh } from "@/hooks/useAutoRefresh"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + + export const Route = createFileRoute("/bet-ticket")({ + component: BetTicketPage, + head: () => ({ meta: [{ title: "Bet Ticker - B2B-B2C Panel" }] }), + }); +
Removed / Before Commit
- diff --git a/src/routes/blocked-ips.tsx b/src/routes/blocked-ips.tsx - index a730d58..35e4a5a 100644 - import { createFileRoute } from "@tanstack/react-router"; - import { useState } from "react"; - import TablePagination from "@/components/common/TablePagination"; - import { motion } from "motion/react"; - import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; - import { internalServices } from "@/services/api/internalServices"; - import { cn } from "@/lib/utils"; - import { toast } from "sonner"; - - export const Route = createFileRoute("/blocked-ips")({ - component: BlockedIPsPage, - const queryClient = useQueryClient(); - - // Fetch blocked IPs - const { data: blockedIpsData, isLoading } = useQuery({ - queryKey: ["blocked-ips", filterType, searchQuery, currentPage, pageSize],
Added / After Commit
+ diff --git a/src/routes/blocked-ips.tsx b/src/routes/blocked-ips.tsx + index a730d58..35e4a5a 100644 + import { createFileRoute } from "@tanstack/react-router"; + import { useMemo, useState } from "react"; + import TablePagination from "@/components/common/TablePagination"; + import { motion } from "motion/react"; + import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; + import { internalServices } from "@/services/api/internalServices"; + import { cn } from "@/lib/utils"; + import { toast } from "sonner"; + import { RefreshCw } from "lucide-react"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + + export const Route = createFileRoute("/blocked-ips")({ + component: BlockedIPsPage, + const queryClient = useQueryClient(); +
Removed / Before Commit
- diff --git a/src/routes/credit-transactions.tsx b/src/routes/credit-transactions.tsx - index a51b0c2..201bc3b 100644 - import { useMemo, useState } from "react"; - import { motion } from "motion/react"; - import TablePagination from "@/components/common/TablePagination"; - import { - ArrowRight, - RefreshCw, - Search, - } from "lucide-react"; - import { Card } from "@/components/ui/card"; - import { Badge } from "@/components/ui/badge"; - import { Button } from "@/components/ui/button"; - import { Input } from "@/components/ui/input"; - import { - Table, - TableBody, - } from "@/components/ui/table";
Added / After Commit
+ diff --git a/src/routes/credit-transactions.tsx b/src/routes/credit-transactions.tsx + index a51b0c2..201bc3b 100644 + import { useMemo, useState } from "react"; + import { motion } from "motion/react"; + import TablePagination from "@/components/common/TablePagination"; + import { ArrowRight, RefreshCw, Search } from "lucide-react"; + import { Card } from "@/components/ui/card"; + import { Badge } from "@/components/ui/badge"; + import { Button } from "@/components/ui/button"; + import { Input } from "@/components/ui/input"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Table, + TableBody, + } from "@/components/ui/table"; + import { internalServices } from "@/services/api/internalServices"; + import type { CreditTransaction, CreditTransactionUser } from "@/services/api/internalTypes"; + import type { CsvColumn } from "@/utils/csv/types";
Removed / Before Commit
- diff --git a/src/routes/MultiBet.tsx b/src/routes/MultiBet.tsx - index 906822e..796fdbb 100644 - import React from "react"; - import { createFileRoute } from "@tanstack/react-router"; - import { useQuery } from "@tanstack/react-query"; - import { useEffect, useState } from "react"; - import { format } from "date-fns"; - import type { DateRange } from "react-day-picker"; - import TablePagination from "@/components/common/TablePagination"; - import DateRangePicker from "@/components/common/DateRangePicker"; - import { ChevronDown, ChevronUp, Download } from "lucide-react"; - import { toast } from "sonner"; - import { Button } from "@/components/ui/button"; - import { internalServices } from "@/services/api/internalServices"; - import type { MultiBetProfitLossReport } from "@/services/api/internalServices"; - import { cn } from "@/lib/utils"; - - export const Route = createFileRoute("/MultiBet")({
Added / After Commit
+ diff --git a/src/routes/MultiBet.tsx b/src/routes/MultiBet.tsx + index 906822e..796fdbb 100644 + import React from "react"; + import { createFileRoute } from "@tanstack/react-router"; + import { useQuery } from "@tanstack/react-query"; + import { useEffect, useMemo, useState } from "react"; + import { format } from "date-fns"; + import type { DateRange } from "react-day-picker"; + import TablePagination from "@/components/common/TablePagination"; + import DateRangePicker from "@/components/common/DateRangePicker"; + import { ChevronDown, ChevronUp } from "lucide-react"; + import { toast } from "sonner"; + import { Button } from "@/components/ui/button"; + import { internalServices } from "@/services/api/internalServices"; + import type { MultiBetProfitLossReport } from "@/services/api/internalServices"; + import { cn } from "@/lib/utils"; + import CsvExportButton from "@/components/common/CsvExportButton"; +
Removed / Before Commit
- diff --git a/src/routes/pl-by-agent.tsx b/src/routes/pl-by-agent.tsx - index 1682a5f..bc0a29f 100644 - import { useEffect, useMemo, useState } from "react"; - import { motion } from "motion/react"; - import { format } from "date-fns"; - import { Download } from "lucide-react"; - import { toast } from "sonner"; - import type { DateRange } from "react-day-picker"; - import { Card } from "@/components/ui/card"; - import { Button } from "@/components/ui/button"; - import DateRangePicker from "@/components/common/DateRangePicker"; - import { - Table, - TableBody, - refetchReport(); - setRefreshCountdown(refreshIntervalSeconds); - }; -
Added / After Commit
+ diff --git a/src/routes/pl-by-agent.tsx b/src/routes/pl-by-agent.tsx + index 1682a5f..bc0a29f 100644 + import { useEffect, useMemo, useState } from "react"; + import { motion } from "motion/react"; + import { format } from "date-fns"; + import { toast } from "sonner"; + import type { DateRange } from "react-day-picker"; + import { Card } from "@/components/ui/card"; + import { Button } from "@/components/ui/button"; + import DateRangePicker from "@/components/common/DateRangePicker"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Table, + TableBody, + refetchReport(); + setRefreshCountdown(refreshIntervalSeconds); + }; + const csvExportRows = useMemo(
Removed / Before Commit
- diff --git a/src/routes/pl-by-market.tsx b/src/routes/pl-by-market.tsx - index 3cec7b7..62c0f40 100644 - import { useEffect, useMemo, useState } from "react"; - import { motion } from "motion/react"; - import { format } from "date-fns"; - import { Download } from "lucide-react"; - import { toast } from "sonner"; - import type { DateRange } from "react-day-picker"; - import { Card } from "@/components/ui/card"; - import { Button } from "@/components/ui/button"; - import DateRangePicker from "@/components/common/DateRangePicker"; - import { - Table, - TableBody, - refetchReport(); - setRefreshCountdown(refreshIntervalSeconds); - }; -
Added / After Commit
+ diff --git a/src/routes/pl-by-market.tsx b/src/routes/pl-by-market.tsx + index 3cec7b7..62c0f40 100644 + import { useEffect, useMemo, useState } from "react"; + import { motion } from "motion/react"; + import { format } from "date-fns"; + import { toast } from "sonner"; + import type { DateRange } from "react-day-picker"; + import { Card } from "@/components/ui/card"; + import { Button } from "@/components/ui/button"; + import DateRangePicker from "@/components/common/DateRangePicker"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Table, + TableBody, + refetchReport(); + setRefreshCountdown(refreshIntervalSeconds); + }; + const csvExportRows = useMemo(
Removed / Before Commit
- diff --git a/src/routes/statement.tsx b/src/routes/statement.tsx - index 17f4d1a..e26a717 100644 - import { internalServices } from "@/services/api/internalServices"; - import type { CreditTransaction, CreditTransactionUser } from "@/services/api/internalTypes"; - import { cn } from "@/lib/utils"; - - export const Route = createFileRoute("/statement")({ - component: StatementPage, - () => filteredTransactions.slice((currentPage - 1) * pageSize, currentPage * pageSize), - [currentPage, filteredTransactions], - ); - - return ( - <div className="space-y-6"> - /> - Refresh - </Button> - </div>
Added / After Commit
+ diff --git a/src/routes/statement.tsx b/src/routes/statement.tsx + index 17f4d1a..e26a717 100644 + import { internalServices } from "@/services/api/internalServices"; + import type { CreditTransaction, CreditTransactionUser } from "@/services/api/internalTypes"; + import { cn } from "@/lib/utils"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + + export const Route = createFileRoute("/statement")({ + component: StatementPage, + () => filteredTransactions.slice((currentPage - 1) * pageSize, currentPage * pageSize), + [currentPage, filteredTransactions], + ); + const csvColumns = useMemo( + () => + [ + { header: "Date", accessor: (transaction) => formatDate(transaction.createdAt) }, + {
Removed / Before Commit
- diff --git a/src/routes/transfer-statement.tsx b/src/routes/transfer-statement.tsx - index 17c7804..c5fca50 100644 - import { useQuery } from "@tanstack/react-query"; - import { authApiClient } from "@/services/api/apiClient"; - import { toast } from "sonner"; - import { - Download, - RefreshCw, - } from "lucide-react"; - import { Card } from "@/components/ui/card"; - import { Button } from "@/components/ui/button"; - import { Input } from "@/components/ui/input"; - import { - Table, - TableBody, - if (data?.hasServerPagination) return filteredRows; - return filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize); - }, [currentPage, data?.hasServerPagination, filteredRows, pageSize]);
Added / After Commit
+ diff --git a/src/routes/transfer-statement.tsx b/src/routes/transfer-statement.tsx + index 17c7804..c5fca50 100644 + import { useQuery } from "@tanstack/react-query"; + import { authApiClient } from "@/services/api/apiClient"; + import { toast } from "sonner"; + import { RefreshCw } from "lucide-react"; + import { Card } from "@/components/ui/card"; + import { Button } from "@/components/ui/button"; + import { Input } from "@/components/ui/input"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + import { + Table, + TableBody, + if (data?.hasServerPagination) return filteredRows; + return filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize); + }, [currentPage, data?.hasServerPagination, filteredRows, pageSize]); + const csvColumns = useMemo(
Removed / Before Commit
- diff --git a/src/routes/transfer.tsx b/src/routes/transfer.tsx - index 73639d3..1c5baa2 100644 - import { createFileRoute } from "@tanstack/react-router"; - import { useEffect, useMemo, useState } from "react"; - import { useMutation, useQuery } from "@tanstack/react-query"; - import { motion } from "motion/react"; - import { toast } from "sonner"; - import { Label } from "@/components/ui/label"; - import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; - import TablePagination from "@/components/common/TablePagination"; - import { - Table, - TableBody, - TransferListProfile, - TransferListUser, - } from "@/services/api/internalTypes"; - - export const Route = createFileRoute("/transfer")({
Added / After Commit
+ diff --git a/src/routes/transfer.tsx b/src/routes/transfer.tsx + index 73639d3..1c5baa2 100644 + import { createFileRoute } from "@tanstack/react-router"; + import { useEffect, useMemo, useState, type ReactNode } from "react"; + import { useMutation, useQuery } from "@tanstack/react-query"; + import { motion } from "motion/react"; + import { toast } from "sonner"; + import { Label } from "@/components/ui/label"; + import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; + import TablePagination from "@/components/common/TablePagination"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Table, + TableBody, + TransferListProfile, + TransferListUser, + } from "@/services/api/internalTypes"; + import type { CsvColumn } from "@/utils/csv/types";
Removed / Before Commit
- diff --git a/src/routes/user-details.$userId.tsx b/src/routes/user-details.$userId.tsx - index e56e802..8e90c94 100644 - } from "@/components/ui/table"; - import { - Search, - Download, - // RefreshCw, - Filter, - Gauge, - import { format } from "date-fns"; - import { setExposureParams } from "@/lib/nav-params"; - import type { MultiBetLeg } from "@/services/api/betTypes"; - - export const Route = createFileRoute("/user-details/$userId")({ - component: UserDetailsPage, - const [betListDateRange, setBetListDateRange] = useState<DateRange | undefined>(() => - getDefaultStatementDateRange(), - );
Added / After Commit
+ diff --git a/src/routes/user-details.$userId.tsx b/src/routes/user-details.$userId.tsx + index e56e802..8e90c94 100644 + } from "@/components/ui/table"; + import { + Search, + // RefreshCw, + Filter, + Gauge, + import { format } from "date-fns"; + import { setExposureParams } from "@/lib/nav-params"; + import type { MultiBetLeg } from "@/services/api/betTypes"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + + export const Route = createFileRoute("/user-details/$userId")({ + component: UserDetailsPage, + const [betListDateRange, setBetListDateRange] = useState<DateRange | undefined>(() => + getDefaultStatementDateRange(),
Removed / Before Commit
- diff --git a/src/routes/user-exposure-detail.tsx b/src/routes/user-exposure-detail.tsx - new file mode 100644 - index 0000000..6e017de
Added / After Commit
+ diff --git a/src/routes/user-exposure-detail.tsx b/src/routes/user-exposure-detail.tsx + new file mode 100644 + index 0000000..6e017de + import { createFileRoute, useNavigate } from "@tanstack/react-router"; + import { useQuery } from "@tanstack/react-query"; + import { Fragment, useEffect, useState } from "react"; + import type React from "react"; + import { ArrowLeft, RefreshCw, ChevronDown, Network, Eye } from "lucide-react"; + import { Button } from "@/components/ui/button"; + import { cn } from "@/lib/utils"; + import { tenantApiClient } from "@/services/api/apiClient"; + import { getExposureParams } from "@/lib/nav-params"; + + export const Route = createFileRoute("/user-exposure-detail")({ + component: UserExposureDetailPage, + head: () => ({ meta: [{ title: "Net Exposure Detail - B2B-B2C Panel" }] }), + }); +
Removed / Before Commit
- diff --git a/src/routes/user-listing.tsx b/src/routes/user-listing.tsx - index d1abb4b..472568d 100644 - import { createFileRoute, Link } from "@tanstack/react-router"; - import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; - import { type ReactNode, useEffect, useMemo, useState } from "react"; - import type { DateRange } from "react-day-picker"; - } from "@/components/ui/table"; - import { - Calendar as CalendarIcon, - Download, - Eye, - Lock, - Minus, - DownlineUser, - UserBlockStatusResult, - } from "@/services/api/internalTypes"; - import { cn } from "@/lib/utils"; -
Added / After Commit
+ diff --git a/src/routes/user-listing.tsx b/src/routes/user-listing.tsx + index d1abb4b..472568d 100644 + import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; + import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + import { type ReactNode, useEffect, useMemo, useState } from "react"; + import type { DateRange } from "react-day-picker"; + } from "@/components/ui/table"; + import { + Calendar as CalendarIcon, + Eye, + Lock, + Minus, + DownlineUser, + UserBlockStatusResult, + } from "@/services/api/internalTypes"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import type { CsvColumn } from "@/utils/csv/types"; + import { cn } from "@/lib/utils";
Removed / Before Commit
- diff --git a/src/routes/users.tsx b/src/routes/users.tsx - index 975b040..9ce0323 100644 - import { motion } from "motion/react"; - import { - Calendar as CalendarIcon, - Download, - Plus, - Eye, - Lock, - TableRow, - } from "@/components/ui/table"; - import { cn } from "@/lib/utils"; - - export const Route = createFileRoute("/users")({ - component: UsersPage, - const fmt = (n: number) => - n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); -
Added / After Commit
+ diff --git a/src/routes/users.tsx b/src/routes/users.tsx + index 975b040..9ce0323 100644 + import { motion } from "motion/react"; + import { + Calendar as CalendarIcon, + Plus, + Eye, + Lock, + TableRow, + } from "@/components/ui/table"; + import { cn } from "@/lib/utils"; + import CsvExportButton from "@/components/common/CsvExportButton"; + + export const Route = createFileRoute("/users")({ + component: UsersPage, + const fmt = (n: number) => + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +
Removed / Before Commit
- diff --git a/src/routes/virtual-report.tsx b/src/routes/virtual-report.tsx - index 480d1c9..18403cd 100644 - import { useQuery } from "@tanstack/react-query"; - import { toast } from "sonner"; - // import { internalServices } from "@/services/api/internalServices"; - import { Calendar as CalendarIcon, Download } from "lucide-react"; - import { Calendar } from "@/components/ui/calendar"; - import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; - import { - Select, - SelectContent, - - function VirtualReportPage() { - const today = new Date(); - const [dateRange, setDateRange] = useState<DateRange | undefined>(() => ({ from: today, to: today })); - const formatLabel = (value?: DateRange) => { - const fmt = (d?: Date) => (d ? format(d, "dd/MM/yyyy") : ""); - if (!value || (!value.from && !value.to)) return `${fmt(today)} - ${fmt(today)}`;
Added / After Commit
+ diff --git a/src/routes/virtual-report.tsx b/src/routes/virtual-report.tsx + index 480d1c9..18403cd 100644 + import { useQuery } from "@tanstack/react-query"; + import { toast } from "sonner"; + // import { internalServices } from "@/services/api/internalServices"; + import { Calendar as CalendarIcon } from "lucide-react"; + import { Calendar } from "@/components/ui/calendar"; + import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; + import CsvExportButton from "@/components/common/CsvExportButton"; + import { + Select, + SelectContent, + + function VirtualReportPage() { + const today = new Date(); + const [dateRange, setDateRange] = useState<DateRange | undefined>(() => ({ + from: today, + to: today,
Removed / Before Commit
- diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts - index 6ada5c7..24cab2a 100644 - import { Route as VirtualReportRouteImport } from './routes/virtual-report' - import { Route as UsersRouteImport } from './routes/users' - import { Route as UserListingRouteImport } from './routes/user-listing' - import { Route as UploadFilesRouteImport } from './routes/upload-files' - import { Route as TransferStatementRouteImport } from './routes/transfer-statement' - import { Route as TransferRouteImport } from './routes/transfer' - path: '/user-listing', - getParentRoute: () => rootRouteImport, - } as any) - const UploadFilesRoute = UploadFilesRouteImport.update({ - id: '/upload-files', - path: '/upload-files', - '/transfer': typeof TransferRoute - '/transfer-statement': typeof TransferStatementRoute - '/upload-files': typeof UploadFilesRoute - '/user-listing': typeof UserListingRoute
Added / After Commit
+ diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts + index 6ada5c7..24cab2a 100644 + import { Route as VirtualReportRouteImport } from './routes/virtual-report' + import { Route as UsersRouteImport } from './routes/users' + import { Route as UserListingRouteImport } from './routes/user-listing' + import { Route as UserExposureDetailRouteImport } from './routes/user-exposure-detail' + import { Route as UserExposureBetRouteImport } from './routes/user-exposure-bet' + import { Route as UploadFilesRouteImport } from './routes/upload-files' + import { Route as TransferStatementRouteImport } from './routes/transfer-statement' + import { Route as TransferRouteImport } from './routes/transfer' + path: '/user-listing', + getParentRoute: () => rootRouteImport, + } as any) + const UserExposureDetailRoute = UserExposureDetailRouteImport.update({ + id: '/user-exposure-detail', + path: '/user-exposure-detail', + getParentRoute: () => rootRouteImport, + } as any)
Removed / Before Commit
- diff --git a/src/services/api/internalServices.ts b/src/services/api/internalServices.ts - index 748cb41..9d5d297 100644 - export type DownlineSearchParams = { - search_from?: string; - search?: string; - }; - export interface MultiBetProfitLossReport { - fromDate: string; - type: role, - search_from: search?.search_from, - search: search?.search, - }); - return unwrapData<DownlineUser[]>( - await authApiClient.get<unknown>(
Added / After Commit
+ diff --git a/src/services/api/internalServices.ts b/src/services/api/internalServices.ts + index 748cb41..9d5d297 100644 + export type DownlineSearchParams = { + search_from?: string; + search?: string; + status?: string; + }; + export interface MultiBetProfitLossReport { + fromDate: string; + type: role, + search_from: search?.search_from, + search: search?.search, + status: search?.status, + }); + return unwrapData<DownlineUser[]>( + await authApiClient.get<unknown>(
Removed / Before Commit
- diff --git a/src/utils/csv/csvHelpers.ts b/src/utils/csv/csvHelpers.ts - new file mode 100644 - index 0000000..430deda
Added / After Commit
+ diff --git a/src/utils/csv/csvHelpers.ts b/src/utils/csv/csvHelpers.ts + new file mode 100644 + index 0000000..430deda + import type { CsvCellValue } from "@/utils/csv/types"; + + export function normalizeCsvFilename(filename: string) { + const fallback = "export.csv"; + const cleaned = filename.trim() || fallback; + return cleaned.toLowerCase().endsWith(".csv") ? cleaned : `${cleaned}.csv`; + } + + export function formatCsvCell(value: CsvCellValue) { + if (value === null || value === undefined) return ""; + if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toISOString(); + return String(value); + } + + export function escapeCsvCell(value: CsvCellValue) {
Removed / Before Commit
- diff --git a/src/utils/csv/exportCsv.ts b/src/utils/csv/exportCsv.ts - new file mode 100644 - index 0000000..1282fb9
Added / After Commit
+ diff --git a/src/utils/csv/exportCsv.ts b/src/utils/csv/exportCsv.ts + new file mode 100644 + index 0000000..1282fb9 + import { downloadCsvFile, escapeCsvCell } from "@/utils/csv/csvHelpers"; + import type { CsvCellValue, CsvColumn, ExportCsvOptions } from "@/utils/csv/types"; + + function getObjectColumns<TRow>(rows: TRow[]): CsvColumn<TRow>[] { + const firstObject = rows.find( + (row): row is TRow & Record<string, CsvCellValue> => + Boolean(row) && typeof row === "object" && !Array.isArray(row), + ); + + if (!firstObject) return []; + + return Object.keys(firstObject).map((key) => ({ + header: key, + accessor: key as keyof TRow, + }));
Removed / Before Commit
- diff --git a/src/utils/csv/types.ts b/src/utils/csv/types.ts - new file mode 100644 - index 0000000..fe5453d
Added / After Commit
+ diff --git a/src/utils/csv/types.ts b/src/utils/csv/types.ts + new file mode 100644 + index 0000000..fe5453d + import type { ButtonProps } from "@/components/ui/button"; + + export type CsvCellValue = string | number | boolean | Date | null | undefined; + + export type CsvColumn<TRow> = { + header: string; + accessor: keyof TRow | ((row: TRow, index: number) => CsvCellValue); + }; + + export type ExportCsvOptions<TRow> = { + filename: string; + rows: TRow[]; + columns?: CsvColumn<TRow>[]; + }; +