Add CSV export functionality for admin orders

- Created exportCSV utility with convertToCSV, downloadCSV, formatExportDate, formatExportIDR
- Added export button to AdminOrders page with loading state
- Export includes all order fields: ID, email, total, status, payment method, date, refund info
- CSV format compatible with Excel and Google Sheets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-26 18:25:43 +07:00
parent 5a05203f2b
commit bf212fb973
2 changed files with 136 additions and 3 deletions

View File

@@ -13,9 +13,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { formatIDR, formatDateTime } from "@/lib/format";
import { Eye, CheckCircle, XCircle, Video, ExternalLink, Trash2, AlertTriangle, RefreshCw, Link as LinkIcon } from "lucide-react";
import { Eye, CheckCircle, XCircle, Video, ExternalLink, Trash2, AlertTriangle, RefreshCw, Link as LinkIcon, Download } from "lucide-react";
import { toast } from "@/hooks/use-toast";
import { getPaymentStatusLabel, getPaymentStatusColor, canRefundOrder, canCancelOrder, canMarkAsPaid } from "@/lib/statusHelpers";
import { convertToCSV, downloadCSV, formatExportDate, formatExportIDR } from "@/lib/exportCSV";
interface Order {
id: string;
@@ -64,6 +65,7 @@ export default function AdminOrders() {
const [selectedSlotId, setSelectedSlotId] = useState<string | null>(null);
const [newMeetLink, setNewMeetLink] = useState("");
const [creatingMeetLink, setCreatingMeetLink] = useState(false);
const [exporting, setExporting] = useState(false);
useEffect(() => {
if (!authLoading) {
@@ -280,6 +282,62 @@ export default function AdminOrders() {
);
};
const handleExportOrders = async () => {
setExporting(true);
try {
// Fetch all orders with full details
const { data: ordersData, error } = await supabase
.from("orders")
.select("*, profile:profiles(email)")
.order("created_at", { ascending: false });
if (error) throw error;
// Transform data for CSV export
const csvData = (ordersData as Order[]).map((order) => ({
"Order ID": order.id,
"Email": order.profile?.email || "",
"Total": formatExportIDR(order.total_amount),
"Status": getPaymentStatusLabel(order.payment_status),
"Metode Pembayaran": order.payment_method || "",
"Referensi": order.payment_reference || "",
"Tanggal": formatExportDate(order.created_at),
"Refund Amount": order.refunded_amount ? formatExportIDR(order.refunded_amount) : "",
"Refund Reason": order.refund_reason || "",
"Refunded At": order.refunded_at ? formatExportDate(order.refunded_at) : "",
}));
// Convert to CSV
const headers = [
"Order ID",
"Email",
"Total",
"Status",
"Metode Pembayaran",
"Referensi",
"Tanggal",
"Refund Amount",
"Refund Reason",
"Refunded At",
];
const csv = convertToCSV(csvData, headers);
// Download CSV
const filename = `orders-${new Date().toISOString().split('T')[0]}.csv`;
downloadCSV(csv, filename);
toast({ title: "Berhasil", description: "Data order berhasil di-export" });
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Gagal men-export data order",
variant: "destructive",
});
} finally {
setExporting(false);
}
};
if (authLoading || loading) {
return (
<AppLayout>
@@ -294,8 +352,21 @@ export default function AdminOrders() {
return (
<AppLayout>
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-2">Manajemen Order</h1>
<p className="text-muted-foreground mb-8">Kelola semua pesanan</p>
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-4xl font-bold mb-2">Manajemen Order</h1>
<p className="text-muted-foreground">Kelola semua pesanan</p>
</div>
<Button
onClick={handleExportOrders}
variant="outline"
className="gap-2 border-2"
disabled={exporting || orders.length === 0}
>
<Download className="w-4 h-4" />
{exporting ? "Men-export..." : "Export Orders"}
</Button>
</div>
<Card className="border-2 border-border hidden md:block">
<CardContent className="p-0">