132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
|
|
import { NextRequest, NextResponse } from 'next/server';
|
||
|
|
import { nocodbClient } from '@/api/nocodbClient';
|
||
|
|
import type { OrderFilters } from '@/api/nocodbClient';
|
||
|
|
import * as XLSX from 'xlsx';
|
||
|
|
import Papa from 'papaparse';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/admin/orders/export
|
||
|
|
* Export orders to CSV or Excel
|
||
|
|
* Query params:
|
||
|
|
* - format: 'csv' | 'xlsx' (default: csv)
|
||
|
|
* - status: Filter by status
|
||
|
|
* - dateFrom, dateTo: Date range filter
|
||
|
|
*/
|
||
|
|
export async function GET(request: NextRequest) {
|
||
|
|
try {
|
||
|
|
const searchParams = request.nextUrl.searchParams;
|
||
|
|
const format = searchParams.get('format') || 'csv';
|
||
|
|
|
||
|
|
// Build filters (same as orders list)
|
||
|
|
const filters: OrderFilters = {};
|
||
|
|
if (searchParams.get('status')) filters.status = searchParams.get('status')!;
|
||
|
|
if (searchParams.get('dateFrom')) filters.dateFrom = searchParams.get('dateFrom')!;
|
||
|
|
if (searchParams.get('dateTo')) filters.dateTo = searchParams.get('dateTo')!;
|
||
|
|
|
||
|
|
// Get all matching orders (no pagination for export)
|
||
|
|
const response = await nocodbClient.getOrders(filters, { limit: 10000 });
|
||
|
|
const orders = response.list;
|
||
|
|
|
||
|
|
// Transform data for export
|
||
|
|
const exportData = orders.map((order) => ({
|
||
|
|
'Order ID': order.orderId,
|
||
|
|
'Status': order.status,
|
||
|
|
'Created Date': order.CreatedAt ? new Date(order.CreatedAt).toLocaleDateString() : '',
|
||
|
|
'Vessel Name': order.vesselName,
|
||
|
|
'IMO Number': order.imoNumber || '',
|
||
|
|
'Distance (NM)': order.distance || '',
|
||
|
|
'Avg Speed (kn)': order.avgSpeed || '',
|
||
|
|
'CO2 Tons': order.co2Tons || '',
|
||
|
|
'Total Amount': order.totalAmount || '',
|
||
|
|
'Currency': order.currency || '',
|
||
|
|
'Customer Name': order.customerName || '',
|
||
|
|
'Customer Email': order.customerEmail || '',
|
||
|
|
'Customer Company': order.customerCompany || '',
|
||
|
|
'Departure Port': order.departurePort || '',
|
||
|
|
'Arrival Port': order.arrivalPort || '',
|
||
|
|
'Payment Method': order.paymentMethod || '',
|
||
|
|
'Payment Reference': order.paymentReference || '',
|
||
|
|
'Wren Order ID': order.wrenOrderId || '',
|
||
|
|
'Certificate URL': order.certificateUrl || '',
|
||
|
|
'Fulfilled At': order.fulfilledAt ? new Date(order.fulfilledAt).toLocaleDateString() : '',
|
||
|
|
'Notes': order.notes || '',
|
||
|
|
}));
|
||
|
|
|
||
|
|
if (format === 'xlsx') {
|
||
|
|
// Generate Excel file
|
||
|
|
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
||
|
|
const workbook = XLSX.utils.book_new();
|
||
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'Orders');
|
||
|
|
|
||
|
|
// Style headers (make them bold)
|
||
|
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
||
|
|
for (let col = range.s.c; col <= range.e.c; col++) {
|
||
|
|
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: col });
|
||
|
|
if (!worksheet[cellAddress]) continue;
|
||
|
|
worksheet[cellAddress].s = {
|
||
|
|
font: { bold: true },
|
||
|
|
fill: { fgColor: { rgb: 'D3D3D3' } },
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set column widths
|
||
|
|
worksheet['!cols'] = [
|
||
|
|
{ wch: 20 }, // Order ID
|
||
|
|
{ wch: 12 }, // Status
|
||
|
|
{ wch: 15 }, // Created Date
|
||
|
|
{ wch: 25 }, // Vessel Name
|
||
|
|
{ wch: 12 }, // IMO Number
|
||
|
|
{ wch: 12 }, // Distance
|
||
|
|
{ wch: 12 }, // Avg Speed
|
||
|
|
{ wch: 10 }, // CO2 Tons
|
||
|
|
{ wch: 12 }, // Total Amount
|
||
|
|
{ wch: 10 }, // Currency
|
||
|
|
{ wch: 20 }, // Customer Name
|
||
|
|
{ wch: 25 }, // Customer Email
|
||
|
|
{ wch: 25 }, // Customer Company
|
||
|
|
{ wch: 20 }, // Departure Port
|
||
|
|
{ wch: 20 }, // Arrival Port
|
||
|
|
{ wch: 15 }, // Payment Method
|
||
|
|
{ wch: 20 }, // Payment Reference
|
||
|
|
{ wch: 20 }, // Wren Order ID
|
||
|
|
{ wch: 30 }, // Certificate URL
|
||
|
|
{ wch: 15 }, // Fulfilled At
|
||
|
|
{ wch: 40 }, // Notes
|
||
|
|
];
|
||
|
|
|
||
|
|
// Generate buffer
|
||
|
|
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
||
|
|
|
||
|
|
// Return Excel file
|
||
|
|
return new NextResponse(buffer, {
|
||
|
|
status: 200,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
|
|
'Content-Disposition': `attachment; filename="puffin-orders-${Date.now()}.xlsx"`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// Generate CSV
|
||
|
|
const csv = Papa.unparse(exportData);
|
||
|
|
|
||
|
|
// Return CSV file
|
||
|
|
return new NextResponse(csv, {
|
||
|
|
status: 200,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'text/csv',
|
||
|
|
'Content-Disposition': `attachment; filename="puffin-orders-${Date.now()}.csv"`,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error exporting orders:', error);
|
||
|
|
return NextResponse.json(
|
||
|
|
{
|
||
|
|
success: false,
|
||
|
|
error: error instanceof Error ? error.message : 'Failed to export orders',
|
||
|
|
},
|
||
|
|
{ status: 500 }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|