Showing AI reviews for super-admin
Project AI Score ?
72%
Quality Avg ?
77%
Security Avg ?
64%
Reviews ?
91

Review Result ?

solutionbowl/super-admin · a2657828
This commit adds a CsvExportButton component to facilitate CSV downloads and integrates it into the MultiBet route with appropriate bindings to the data. The implementation is clean and fits well with existing UI components and utilities. The feature adds clear business value by allowing users to export reports, improving usability. Some minor improvements in input validation and error handling could raise quality and robustness further.
Quality ?
85%
Security ?
80%
Business Value ?
85%
Maintainability ?
55%
Issues & Suggested Fixes ?
1AI detects the issue
2Shows file, line, or evidence
3Suggests the fix to apply
Consider Adding Error Handling Within Hand...
Where src/components/common/CsvExportButton.tsx:16
Issue / Evidence Consider adding error handling within handleClick to manage exportCsv failures to improve bug risk score
Suggested Fix Review and simplify this section.
Add Tooltip Or Aria Label To Disabled Butt...
Where src/components/common/CsvExportButton.tsx:25
Issue / Evidence Add tooltip or aria-label to disabled button to improve accessibility and overall quality
Suggested Fix Review and simplify this section.
Inform User With More Details Or Log When...
Where src/routes/MultiBet.tsx:109
Issue / Evidence Inform user with more details or log when 'Select both dates' error occurs for better user feedback
Suggested Fix Review and simplify this section.
Missing Validation
Where src/routes/MultiBet.tsx:113
Issue / Evidence Add validation or prevent users from inputting an invalid date range earlier to reduce user errors
Suggested Fix Review and simplify this section.
Consider Disabling The Export Button Also...
Where src/routes/MultiBet.tsx:185
Issue / Evidence Consider disabling the export button also if rows are empty to prevent exporting empty CSV files
Suggested Fix Review and simplify this section.
Introduce Memoization Or Caching For N() C...
Where src/routes/MultiBet.tsx:54-66
Issue / Evidence Introduce memoization or caching for n() calls to optimize performance for large datasets
Suggested Fix Review and simplify this section.
Expand To Describe The Feature Benefits, K...
Where commit message
Issue / Evidence Expand to describe the feature benefits, key changes, and potential impacts to increase business value visibility
Suggested Fix Review and simplify this section.
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>[];
+ };
+