Some checks failed
Build and Push Docker Images / docker (push) Failing after 1m57s
Phase 2 Backend Integration Complete: Backend Infrastructure: - Created NocoDB client abstraction layer (src/api/nocodbClient.ts) - Clean TypeScript API hiding NocoDB query syntax complexity - Helper methods for orders, stats, search, timeline, and filtering - Automatic date range handling and pagination support API Routes: - POST /api/admin/stats - Dashboard statistics with time range filtering - GET /api/admin/orders - List orders with search, filter, sort, pagination - GET /api/admin/orders/[id] - Single order details - PATCH /api/admin/orders/[id] - Update order fields - DELETE /api/admin/orders/[id] - Cancel order (soft delete) - GET /api/admin/orders/export - CSV/Excel export with filters Dashboard Updates: - Real-time data fetching from NocoDB - Time range selector (7d, 30d, 90d, all time) - Recharts line chart for orders timeline - Recharts pie chart for status distribution - Loading states and error handling - Dynamic stat cards with real numbers Dependencies Added: - papaparse - CSV export - xlsx - Excel export with styling - @types/papaparse - TypeScript support Data Types: - OrderRecord interface for NocoDB data structure - DashboardStats, TimelineData, OrderFilters interfaces - Full type safety across API and UI Environment Configuration: - NOCODB_BASE_URL, NOCODB_BASE_ID configured - NOCODB_API_KEY, NOCODB_ORDERS_TABLE_ID configured - All credentials stored securely in .env.local Ready for testing with sample data in NocoDB! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
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 }
|
|
);
|
|
}
|
|
}
|