|
@@ -2,436 +2,502 @@
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useEffect, useState } from "react";
|
|
|
import {
|
|
import {
|
|
|
- ColumnDef,
|
|
|
|
|
- flexRender,
|
|
|
|
|
- getCoreRowModel,
|
|
|
|
|
- getPaginationRowModel,
|
|
|
|
|
- getSortedRowModel,
|
|
|
|
|
- useReactTable,
|
|
|
|
|
- SortingState,
|
|
|
|
|
|
|
+ ColumnDef,
|
|
|
|
|
+ flexRender,
|
|
|
|
|
+ getCoreRowModel,
|
|
|
|
|
+ getPaginationRowModel,
|
|
|
|
|
+ getSortedRowModel,
|
|
|
|
|
+ useReactTable,
|
|
|
|
|
+ SortingState,
|
|
|
} from "@tanstack/react-table";
|
|
} from "@tanstack/react-table";
|
|
|
import * as XLSX from "xlsx";
|
|
import * as XLSX from "xlsx";
|
|
|
import {
|
|
import {
|
|
|
- Dialog,
|
|
|
|
|
- DialogContent,
|
|
|
|
|
- DialogHeader,
|
|
|
|
|
- DialogTitle,
|
|
|
|
|
|
|
+ Dialog,
|
|
|
|
|
+ DialogContent,
|
|
|
|
|
+ DialogHeader,
|
|
|
|
|
+ DialogTitle,
|
|
|
} from "@/components/ui/dialog";
|
|
} from "@/components/ui/dialog";
|
|
|
import {
|
|
import {
|
|
|
- Table,
|
|
|
|
|
- TableBody,
|
|
|
|
|
- TableCell,
|
|
|
|
|
- TableHead,
|
|
|
|
|
- TableHeader,
|
|
|
|
|
- TableRow,
|
|
|
|
|
|
|
+ Table,
|
|
|
|
|
+ TableBody,
|
|
|
|
|
+ TableCell,
|
|
|
|
|
+ TableHead,
|
|
|
|
|
+ TableHeader,
|
|
|
|
|
+ TableRow,
|
|
|
} from "@/components/ui/table";
|
|
} from "@/components/ui/table";
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Button } from "@/components/ui/button";
|
|
|
-import { getTerraTechFacilitySummary, TerraTechSummaryRow } from "@/app/actions/imports";
|
|
|
|
|
-import { Fuel, AlertTriangle, Download, ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ getTerraTechFacilitySummary,
|
|
|
|
|
+ TerraTechSummaryRow,
|
|
|
|
|
+} from "@/app/actions/imports";
|
|
|
|
|
+import {
|
|
|
|
|
+ Fuel,
|
|
|
|
|
+ AlertTriangle,
|
|
|
|
|
+ Download,
|
|
|
|
|
+ ChevronLeft,
|
|
|
|
|
+ ChevronRight,
|
|
|
|
|
+} from "lucide-react";
|
|
|
|
|
|
|
|
interface TerraTechSummaryDialogProps {
|
|
interface TerraTechSummaryDialogProps {
|
|
|
- open: boolean;
|
|
|
|
|
- onOpenChange: (open: boolean) => void;
|
|
|
|
|
- importId: number;
|
|
|
|
|
|
|
+ open: boolean;
|
|
|
|
|
+ onOpenChange: (open: boolean) => void;
|
|
|
|
|
+ importId: number;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface SummaryData {
|
|
interface SummaryData {
|
|
|
- importId: number;
|
|
|
|
|
- importName: string;
|
|
|
|
|
- layoutName: string;
|
|
|
|
|
- rows: TerraTechSummaryRow[];
|
|
|
|
|
|
|
+ importId: number;
|
|
|
|
|
+ importName: string;
|
|
|
|
|
+ layoutName: string;
|
|
|
|
|
+ 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 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 {
|
|
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)}%`;
|
|
|
|
|
|
|
+ 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>[] = [
|
|
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: "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: "oilCorr",
|
|
|
|
|
- header: "Oil-corr",
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <div className="text-right">{formatNumber(row.getValue("oilCorr"))}</div>
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ 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: "waterCorr",
|
|
|
|
|
- header: "Water-corr",
|
|
|
|
|
- cell: ({ row }) => (
|
|
|
|
|
- <div className="text-right">{formatNumber(row.getValue("waterCorr"))}</div>
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ 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: "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;
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ 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 },
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+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(),
|
|
|
|
|
- getPaginationRowModel: getPaginationRowModel(),
|
|
|
|
|
- getSortedRowModel: getSortedRowModel(),
|
|
|
|
|
- initialState: {
|
|
|
|
|
- pagination: {
|
|
|
|
|
- pageSize: 30,
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const table = useReactTable({
|
|
|
|
|
+ data,
|
|
|
|
|
+ columns,
|
|
|
|
|
+ state: {
|
|
|
|
|
+ sorting,
|
|
|
|
|
+ },
|
|
|
|
|
+ onSortingChange: setSorting,
|
|
|
|
|
+ getCoreRowModel: getCoreRowModel(),
|
|
|
|
|
+ getPaginationRowModel: getPaginationRowModel(),
|
|
|
|
|
+ getSortedRowModel: getSortedRowModel(),
|
|
|
|
|
+ initialState: {
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ pageSize: 30,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- 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 || '',
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ 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");
|
|
|
|
|
|
|
+ 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`;
|
|
|
|
|
|
|
+ // 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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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>
|
|
|
|
|
|
|
+ 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>
|
|
|
|
|
|
|
+ <div className="rounded-md border bg-white dark:bg-gray-800 dark:border-gray-700 overflow-auto flex flex-col max-h-[60vh]">
|
|
|
|
|
+ <div className="overflow-x-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(),
|
|
|
)}
|
|
)}
|
|
|
- </TableBody>
|
|
|
|
|
- </Table>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {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>
|
|
|
|
|
|
|
|
- <div className="flex items-center justify-between">
|
|
|
|
|
- <div className="text-sm text-muted-foreground">
|
|
|
|
|
- Showing {table.getRowModel().rows.length} of {data.length} records
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="flex items-center space-x-2">
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="outline"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- onClick={() => table.previousPage()}
|
|
|
|
|
- disabled={!table.getCanPreviousPage()}
|
|
|
|
|
- >
|
|
|
|
|
- <ChevronLeft className="h-4 w-4 mr-1" />
|
|
|
|
|
- Previous
|
|
|
|
|
- </Button>
|
|
|
|
|
- <div className="text-sm font-medium">
|
|
|
|
|
- Page {table.getState().pagination.pageIndex + 1} of{" "}
|
|
|
|
|
- {table.getPageCount()}
|
|
|
|
|
- </div>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="outline"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- onClick={() => table.nextPage()}
|
|
|
|
|
- disabled={!table.getCanNextPage()}
|
|
|
|
|
- >
|
|
|
|
|
- Next
|
|
|
|
|
- <ChevronRight className="h-4 w-4 ml-1" />
|
|
|
|
|
- </Button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className="flex items-center justify-between px-4 pb-4">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
|
|
+ Showing {table.getRowModel().rows.length} of {data.length} records
|
|
|
</div>
|
|
</div>
|
|
|
- );
|
|
|
|
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => table.previousPage()}
|
|
|
|
|
+ disabled={!table.getCanPreviousPage()}
|
|
|
|
|
+ >
|
|
|
|
|
+ <ChevronLeft className="h-4 w-4 mr-1" />
|
|
|
|
|
+ Previous
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <div className="text-sm font-medium">
|
|
|
|
|
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
|
|
|
|
|
+ {table.getPageCount()}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => table.nextPage()}
|
|
|
|
|
+ disabled={!table.getCanNextPage()}
|
|
|
|
|
+ >
|
|
|
|
|
+ Next
|
|
|
|
|
+ <ChevronRight className="h-4 w-4 ml-1" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export function TerraTechSummaryDialog({ open, onOpenChange, importId }: TerraTechSummaryDialogProps) {
|
|
|
|
|
- const [loading, setLoading] = useState(false);
|
|
|
|
|
- const [error, setError] = useState<string | null>(null);
|
|
|
|
|
- const [summaryData, setSummaryData] = useState<SummaryData | null>(null);
|
|
|
|
|
|
|
+export function TerraTechSummaryDialog({
|
|
|
|
|
+ open,
|
|
|
|
|
+ onOpenChange,
|
|
|
|
|
+ importId,
|
|
|
|
|
+}: TerraTechSummaryDialogProps) {
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+ const [error, setError] = useState<string | null>(null);
|
|
|
|
|
+ const [summaryData, setSummaryData] = useState<SummaryData | null>(null);
|
|
|
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- if (open && importId) {
|
|
|
|
|
- loadSummary();
|
|
|
|
|
- }
|
|
|
|
|
- // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
- }, [open, importId]);
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (open && importId) {
|
|
|
|
|
+ loadSummary();
|
|
|
|
|
+ }
|
|
|
|
|
+ // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
+ }, [open, importId]);
|
|
|
|
|
|
|
|
- async function loadSummary() {
|
|
|
|
|
- setLoading(true);
|
|
|
|
|
- setError(null);
|
|
|
|
|
- try {
|
|
|
|
|
- const result = await getTerraTechFacilitySummary(importId);
|
|
|
|
|
- if (result.success && result.data) {
|
|
|
|
|
- setSummaryData(result.data);
|
|
|
|
|
- } else {
|
|
|
|
|
- setError(result.error || 'Failed to load summary');
|
|
|
|
|
- }
|
|
|
|
|
- } catch {
|
|
|
|
|
- setError('An unexpected error occurred');
|
|
|
|
|
- } finally {
|
|
|
|
|
- setLoading(false);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ async function loadSummary() {
|
|
|
|
|
+ setLoading(true);
|
|
|
|
|
+ setError(null);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await getTerraTechFacilitySummary(importId);
|
|
|
|
|
+ if (result.success && result.data) {
|
|
|
|
|
+ setSummaryData(result.data);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setError(result.error || "Failed to load summary");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ setError("An unexpected error occurred");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return (
|
|
|
|
|
- <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
- <DialogContent className="max-w-[95vw] max-h-[90vh] dark:bg-gray-800 dark:border-gray-700">
|
|
|
|
|
- <DialogHeader>
|
|
|
|
|
- <DialogTitle className="flex items-center gap-2 text-gray-900 dark:text-white">
|
|
|
|
|
- <div className="bg-amber-500 w-8 h-8 rounded-full flex items-center justify-center text-white">
|
|
|
|
|
- <Fuel className="w-4 h-4" />
|
|
|
|
|
- </div>
|
|
|
|
|
- Facility Summary
|
|
|
|
|
- {summaryData && (
|
|
|
|
|
- <Badge variant="outline" className="ml-2">
|
|
|
|
|
- {summaryData.importName}
|
|
|
|
|
- </Badge>
|
|
|
|
|
- )}
|
|
|
|
|
- </DialogTitle>
|
|
|
|
|
- </DialogHeader>
|
|
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
+ <DialogContent className="max-w-[95vw] max-h-[90vh] w-full overflow-y-auto dark:bg-gray-800 dark:border-gray-700">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle className="flex items-center gap-2 text-gray-900 dark:text-white">
|
|
|
|
|
+ <div className="bg-amber-500 w-8 h-8 rounded-full flex items-center justify-center text-white">
|
|
|
|
|
+ <Fuel className="w-4 h-4" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ Facility Summary
|
|
|
|
|
+ {summaryData && (
|
|
|
|
|
+ <Badge variant="outline" className="ml-2">
|
|
|
|
|
+ {summaryData.importName}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </DialogTitle>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
|
|
|
- {loading && (
|
|
|
|
|
- <div className="flex justify-center py-12">
|
|
|
|
|
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-amber-500"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ {loading && (
|
|
|
|
|
+ <div className="flex justify-center py-12">
|
|
|
|
|
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-amber-500"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
|
|
|
- {error && (
|
|
|
|
|
- <div className="flex flex-col items-center justify-center py-12 text-red-500">
|
|
|
|
|
- <AlertTriangle className="h-8 w-8 mb-2" />
|
|
|
|
|
- <p>{error}</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ {error && (
|
|
|
|
|
+ <div className="flex flex-col items-center justify-center py-12 text-red-500">
|
|
|
|
|
+ <AlertTriangle className="h-8 w-8 mb-2" />
|
|
|
|
|
+ <p>{error}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
|
|
|
- {!loading && !error && summaryData && (
|
|
|
|
|
- <SummaryTable data={summaryData.rows} importName={summaryData.importName} />
|
|
|
|
|
- )}
|
|
|
|
|
- </DialogContent>
|
|
|
|
|
- </Dialog>
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ {!loading && !error && summaryData && (
|
|
|
|
|
+ <SummaryTable
|
|
|
|
|
+ data={summaryData.rows}
|
|
|
|
|
+ importName={summaryData.importName}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|