diff --git a/src/lib/exportCSV.ts b/src/lib/exportCSV.ts new file mode 100644 index 0000000..859fb13 --- /dev/null +++ b/src/lib/exportCSV.ts @@ -0,0 +1,62 @@ +/** + * Export utility functions for CSV export + */ + +/** + * Convert data array to CSV format + */ +export const convertToCSV = (data: Record[], headers: string[]): string => { + // Add headers + const csvRows = [headers.join(',')]; + + // Add data rows + data.forEach((row) => { + const values = headers.map((header) => { + const value = row[header]; + // Escape values that contain commas, quotes, or newlines + if (value === null || value === undefined) { + return ''; + } + const stringValue = String(value); + if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) { + return `"${stringValue.replace(/"/g, '""')}"`; + } + return stringValue; + }); + csvRows.push(values.join(',')); + }); + + return csvRows.join('\n'); +}; + +/** + * Trigger CSV download in browser + */ +export const downloadCSV = (csv: string, filename: string) => { + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + +/** + * Format date for export (YYYY-MM-DD HH:mm:ss) + */ +export const formatExportDate = (date: string | Date): string => { + const d = typeof date === 'string' ? new Date(date) : date; + return d.toISOString().replace('T', ' ').substring(0, 19); +}; + +/** + * Format IDR for export (without "Rp" prefix for easier Excel processing) + */ +export const formatExportIDR = (amount: number): string => { + return (amount / 100).toLocaleString('id-ID'); +}; diff --git a/src/pages/admin/AdminOrders.tsx b/src/pages/admin/AdminOrders.tsx index 5eabdeb..3a733c8 100644 --- a/src/pages/admin/AdminOrders.tsx +++ b/src/pages/admin/AdminOrders.tsx @@ -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(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 ( @@ -294,8 +352,21 @@ export default function AdminOrders() { return (
-

Manajemen Order

-

Kelola semua pesanan

+
+
+

Manajemen Order

+

Kelola semua pesanan

+
+ +