Ver Fonte

Scrolling instead of paging, plus export to excel.

vtugulan há 3 dias atrás
pai
commit
ab6e0cf041
1 ficheiros alterados com 302 adições e 102 exclusões
  1. 302 102
      app/components/terratech/TerraTechSummaryDialog.tsx

+ 302 - 102
app/components/terratech/TerraTechSummaryDialog.tsx

@@ -1,6 +1,15 @@
 "use client";
 
 import { useEffect, useState } from "react";
+import {
+    ColumnDef,
+    flexRender,
+    getCoreRowModel,
+    getSortedRowModel,
+    useReactTable,
+    SortingState,
+} from "@tanstack/react-table";
+import * as XLSX from "xlsx";
 import {
     Dialog,
     DialogContent,
@@ -16,8 +25,9 @@ import {
     TableRow,
 } from "@/components/ui/table";
 import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
 import { getTerraTechFacilitySummary, TerraTechSummaryRow } from "@/app/actions/imports";
-import { Fuel, AlertTriangle } from "lucide-react";
+import { Fuel, AlertTriangle, Download } from "lucide-react";
 
 interface TerraTechSummaryDialogProps {
     open: boolean;
@@ -32,6 +42,296 @@ interface SummaryData {
     rows: TerraTechSummaryRow[];
 }
 
+function formatNumber(value: number | string | null | undefined, decimals: number = 2): string {
+    if (value === null || value === undefined) return '-';
+    const num = typeof value === 'string' ? parseFloat(value) : value;
+    if (isNaN(num)) return '-';
+    return num.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
+}
+
+function formatPercent(value: number | string | null | undefined): string {
+    if (value === null || value === undefined) return '-';
+    const num = typeof value === 'string' ? parseFloat(value) : value;
+    if (isNaN(num)) return '-';
+    return `${num.toFixed(1)}%`;
+}
+
+const columns: ColumnDef<TerraTechSummaryRow>[] = [
+    {
+        accessorKey: "wellName",
+        header: "Well Name",
+        cell: ({ row }) => (
+            <div className="font-medium">{row.getValue("wellName") || '-'}</div>
+        ),
+    },
+    {
+        accessorKey: "corpId",
+        header: "CorpID",
+        cell: ({ row }) => row.getValue("corpId") || '-',
+    },
+    {
+        accessorKey: "facilityId",
+        header: "Facility ID",
+        cell: ({ row }) => {
+            const value = row.getValue("facilityId") as string | null;
+            return value || <span className="text-amber-500">-</span>;
+        },
+    },
+    {
+        accessorKey: "gas",
+        header: "Gas",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("gas"))}</div>
+        ),
+    },
+    {
+        accessorKey: "oil",
+        header: "Oil",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("oil"))}</div>
+        ),
+    },
+    {
+        accessorKey: "water",
+        header: "Water",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("water"))}</div>
+        ),
+    },
+    {
+        accessorKey: "state",
+        header: "State",
+        cell: ({ row }) => row.getValue("state") || '-',
+    },
+    {
+        accessorKey: "county",
+        header: "County",
+        cell: ({ row }) => {
+            const value = row.getValue("county") as string | null;
+            return value || <span className="text-amber-500">-</span>;
+        },
+    },
+    {
+        accessorKey: "daysQ1",
+        header: "Days Q1",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("daysQ1"), 0)}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ2",
+        header: "Days Q2",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("daysQ2"), 0)}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ3",
+        header: "Days Q3",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("daysQ3"), 0)}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ4",
+        header: "Days Q4",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("daysQ4"), 0)}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ1Pct",
+        header: "Q1 %",
+        cell: ({ row }) => (
+            <div className="text-right">{formatPercent(row.getValue("daysQ1Pct"))}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ2Pct",
+        header: "Q2 %",
+        cell: ({ row }) => (
+            <div className="text-right">{formatPercent(row.getValue("daysQ2Pct"))}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ3Pct",
+        header: "Q3 %",
+        cell: ({ row }) => (
+            <div className="text-right">{formatPercent(row.getValue("daysQ3Pct"))}</div>
+        ),
+    },
+    {
+        accessorKey: "daysQ4Pct",
+        header: "Q4 %",
+        cell: ({ row }) => (
+            <div className="text-right">{formatPercent(row.getValue("daysQ4Pct"))}</div>
+        ),
+    },
+    {
+        accessorKey: "gasCorr",
+        header: "Gas-corr",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("gasCorr"))}</div>
+        ),
+    },
+    {
+        accessorKey: "oilCorr",
+        header: "Oil-corr",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("oilCorr"))}</div>
+        ),
+    },
+    {
+        accessorKey: "waterCorr",
+        header: "Water-corr",
+        cell: ({ row }) => (
+            <div className="text-right">{formatNumber(row.getValue("waterCorr"))}</div>
+        ),
+    },
+    {
+        accessorKey: "isMissingFacilityId",
+        header: "Missing Facility ID",
+        cell: ({ row }) => {
+            const value = row.getValue("isMissingFacilityId");
+            return value ? <Badge variant="destructive" className="text-xs">Yes</Badge> : null;
+        },
+    },
+    {
+        accessorKey: "isMissingCounty",
+        header: "Missing County",
+        cell: ({ row }) => {
+            const value = row.getValue("isMissingCounty");
+            return value ? <Badge variant="destructive" className="text-xs">Yes</Badge> : null;
+        },
+    },
+];
+
+function SummaryTable({ data, importName }: { data: TerraTechSummaryRow[]; importName: string }) {
+    const [sorting, setSorting] = useState<SortingState>([
+        { id: "wellName", desc: false },
+    ]);
+
+    const table = useReactTable({
+        data,
+        columns,
+        state: {
+            sorting,
+        },
+        onSortingChange: setSorting,
+        getCoreRowModel: getCoreRowModel(),
+        getSortedRowModel: getSortedRowModel(),
+    });
+
+    function exportToExcel() {
+        // Prepare data for Excel export
+        const exportData = data.map(row => ({
+            "Well Name": row.wellName || '',
+            "CorpID": row.corpId || '',
+            "Facility ID": row.facilityId || '',
+            "Gas": row.gas,
+            "Oil": row.oil,
+            "Water": row.water,
+            "State": row.state || '',
+            "County": row.county || '',
+            "Days Q1": row.daysQ1,
+            "Days Q2": row.daysQ2,
+            "Days Q3": row.daysQ3,
+            "Days Q4": row.daysQ4,
+            "Q1 %": row.daysQ1Pct,
+            "Q2 %": row.daysQ2Pct,
+            "Q3 %": row.daysQ3Pct,
+            "Q4 %": row.daysQ4Pct,
+            "Gas-corr": row.gasCorr,
+            "Oil-corr": row.oilCorr,
+            "Water-corr": row.waterCorr,
+            "Missing Facility ID": row.isMissingFacilityId || '',
+            "Missing County": row.isMissingCounty || '',
+        }));
+
+        const worksheet = XLSX.utils.json_to_sheet(exportData);
+        const workbook = XLSX.utils.book_new();
+        XLSX.utils.book_append_sheet(workbook, worksheet, "Facility Summary");
+
+        // Generate filename with import name and date
+        const date = new Date().toISOString().split('T')[0];
+        const filename = `${importName.replace(/[^a-z0-9]/gi, '_')}_Summary_${date}.xlsx`;
+
+        XLSX.writeFile(workbook, filename);
+    }
+
+    return (
+        <div className="space-y-4">
+            <div className="flex justify-between items-center">
+                <div className="text-sm text-muted-foreground">
+                    {data.length} records
+                </div>
+                <Button
+                    variant="outline"
+                    size="sm"
+                    onClick={exportToExcel}
+                    className="bg-green-50 hover:bg-green-100 text-green-700 border-green-300"
+                >
+                    <Download className="h-4 w-4 mr-2" />
+                    Export to Excel
+                </Button>
+            </div>
+
+            <div className="rounded-md border bg-white dark:bg-gray-800 dark:border-gray-700 max-h-[60vh] overflow-auto">
+                <Table>
+                    <TableHeader className="sticky top-0 bg-white dark:bg-gray-800 z-10">
+                        {table.getHeaderGroups().map((headerGroup) => (
+                            <TableRow key={headerGroup.id} className="dark:border-gray-700">
+                                {headerGroup.headers.map((header) => (
+                                    <TableHead
+                                        key={header.id}
+                                        onClick={header.column.getToggleSortingHandler()}
+                                        className={header.column.getCanSort() ? "cursor-pointer select-none whitespace-nowrap bg-gray-50 dark:bg-gray-900" : "whitespace-nowrap bg-gray-50 dark:bg-gray-900"}
+                                    >
+                                        <div className="flex items-center gap-1">
+                                            {flexRender(
+                                                header.column.columnDef.header,
+                                                header.getContext()
+                                            )}
+                                            {header.column.getIsSorted() && (
+                                                <span>
+                                                    {header.column.getIsSorted() === "asc" ? "↑" : "↓"}
+                                                </span>
+                                            )}
+                                        </div>
+                                    </TableHead>
+                                ))}
+                            </TableRow>
+                        ))}
+                    </TableHeader>
+                    <TableBody>
+                        {table.getRowModel().rows?.length ? (
+                            table.getRowModel().rows.map((row) => (
+                                <TableRow
+                                    key={row.id}
+                                    data-state={row.getIsSelected() && "selected"}
+                                    className="dark:border-gray-700"
+                                >
+                                    {row.getVisibleCells().map((cell) => (
+                                        <TableCell key={cell.id} className="whitespace-nowrap">
+                                            {flexRender(cell.column.columnDef.cell, cell.getContext())}
+                                        </TableCell>
+                                    ))}
+                                </TableRow>
+                            ))
+                        ) : (
+                            <TableRow>
+                                <TableCell colSpan={columns.length} className="h-24 text-center">
+                                    No summary data available for this import.
+                                </TableCell>
+                            </TableRow>
+                        )}
+                    </TableBody>
+                </Table>
+            </div>
+        </div>
+    );
+}
+
 export function TerraTechSummaryDialog({ open, onOpenChange, importId }: TerraTechSummaryDialogProps) {
     const [loading, setLoading] = useState(false);
     const [error, setError] = useState<string | null>(null);
@@ -61,20 +361,6 @@ export function TerraTechSummaryDialog({ open, onOpenChange, importId }: TerraTe
         }
     }
 
-    function formatNumber(value: number | string | null | undefined, decimals: number = 2): string {
-        if (value === null || value === undefined) return '-';
-        const num = typeof value === 'string' ? parseFloat(value) : value;
-        if (isNaN(num)) return '-';
-        return num.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
-    }
-
-    function formatPercent(value: number | string | null | undefined): string {
-        if (value === null || value === undefined) return '-';
-        const num = typeof value === 'string' ? parseFloat(value) : value;
-        if (isNaN(num)) return '-';
-        return `${num.toFixed(1)}%`;
-    }
-
     return (
         <Dialog open={open} onOpenChange={onOpenChange}>
             <DialogContent className="max-w-[95vw] max-h-[90vh] dark:bg-gray-800 dark:border-gray-700">
@@ -106,93 +392,7 @@ export function TerraTechSummaryDialog({ open, onOpenChange, importId }: TerraTe
                 )}
 
                 {!loading && !error && summaryData && (
-                    <div className="h-[70vh] overflow-auto">
-                        <div className="pr-4">
-                            <div className="mb-4 text-sm text-gray-600 dark:text-gray-300">
-                                Total records: <strong>{summaryData.rows.length}</strong>
-                            </div>
-                            <Table>
-                                <TableHeader>
-                                    <TableRow className="dark:border-gray-700">
-                                        <TableHead className="sticky left-0 bg-white dark:bg-gray-800 z-10">Well Name</TableHead>
-                                        <TableHead>CorpID</TableHead>
-                                        <TableHead>Facility ID</TableHead>
-                                        <TableHead className="text-right">Gas</TableHead>
-                                        <TableHead className="text-right">Oil</TableHead>
-                                        <TableHead className="text-right">Water</TableHead>
-                                        <TableHead>State</TableHead>
-                                        <TableHead>County</TableHead>
-                                        <TableHead className="text-right">Days Q1</TableHead>
-                                        <TableHead className="text-right">Days Q2</TableHead>
-                                        <TableHead className="text-right">Days Q3</TableHead>
-                                        <TableHead className="text-right">Days Q4</TableHead>
-                                        <TableHead className="text-right">Q1 %</TableHead>
-                                        <TableHead className="text-right">Q2 %</TableHead>
-                                        <TableHead className="text-right">Q3 %</TableHead>
-                                        <TableHead className="text-right">Q4 %</TableHead>
-                                        <TableHead className="text-right">Gas-corr</TableHead>
-                                        <TableHead className="text-right">Oil-corr</TableHead>
-                                        <TableHead className="text-right">Water-corr</TableHead>
-                                        <TableHead>Missing Facility ID</TableHead>
-                                        <TableHead>Missing County</TableHead>
-                                    </TableRow>
-                                </TableHeader>
-                                <TableBody>
-                                    {summaryData.rows.length === 0 ? (
-                                        <TableRow>
-                                            <TableCell colSpan={21} className="h-24 text-center">
-                                                No summary data available for this import.
-                                            </TableCell>
-                                        </TableRow>
-                                    ) : (
-                                        summaryData.rows.map((row, index) => (
-                                            <TableRow key={index} className="dark:border-gray-700">
-                                                <TableCell className="sticky left-0 bg-white dark:bg-gray-800 font-medium">
-                                                    {row.wellName || '-'}
-                                                </TableCell>
-                                                <TableCell>{row.corpId || '-'}</TableCell>
-                                                <TableCell>
-                                                    {row.facilityId || (
-                                                        <span className="text-amber-500">-</span>
-                                                    )}
-                                                </TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.gas)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.oil)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.water)}</TableCell>
-                                                <TableCell>{row.state || '-'}</TableCell>
-                                                <TableCell>
-                                                    {row.county || (
-                                                        <span className="text-amber-500">-</span>
-                                                    )}
-                                                </TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.daysQ1, 0)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.daysQ2, 0)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.daysQ3, 0)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.daysQ4, 0)}</TableCell>
-                                                <TableCell className="text-right">{formatPercent(row.daysQ1Pct)}</TableCell>
-                                                <TableCell className="text-right">{formatPercent(row.daysQ2Pct)}</TableCell>
-                                                <TableCell className="text-right">{formatPercent(row.daysQ3Pct)}</TableCell>
-                                                <TableCell className="text-right">{formatPercent(row.daysQ4Pct)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.gasCorr)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.oilCorr)}</TableCell>
-                                                <TableCell className="text-right">{formatNumber(row.waterCorr)}</TableCell>
-                                                <TableCell>
-                                                    {row.isMissingFacilityId && (
-                                                        <Badge variant="destructive" className="text-xs">Yes</Badge>
-                                                    )}
-                                                </TableCell>
-                                                <TableCell>
-                                                    {row.isMissingCounty && (
-                                                        <Badge variant="destructive" className="text-xs">Yes</Badge>
-                                                    )}
-                                                </TableCell>
-                                            </TableRow>
-                                        ))
-                                    )}
-                                </TableBody>
-                            </Table>
-                        </div>
-                    </div>
+                    <SummaryTable data={summaryData.rows} importName={summaryData.importName} />
                 )}
             </DialogContent>
         </Dialog>