page.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. "use client";
  2. import { useState, useEffect, useCallback } from "react";
  3. import {
  4. FileText,
  5. Fuel,
  6. Upload,
  7. Database,
  8. CheckCircle,
  9. Loader2,
  10. History,
  11. PlusCircle,
  12. } from "lucide-react";
  13. import {
  14. Card,
  15. CardContent,
  16. CardDescription,
  17. CardHeader,
  18. CardTitle,
  19. } from "@/components/ui/card";
  20. import { Button } from "@/components/ui/button";
  21. import { UploadForm } from "@/app/components/uploadForm";
  22. import {
  23. createTerraTechImportRecord,
  24. processTerraTechImportData,
  25. } from "@/app/actions/terratech-workflow";
  26. import { getImportsByLayoutName } from "@/app/actions/imports";
  27. import { TerraTechImportsTable } from "@/app/components/terratech/TerraTechImportsTable";
  28. import { TerraTechSummaryDialog } from "@/app/components/terratech/TerraTechSummaryDialog";
  29. import { useToast } from "@/hooks/use-toast";
  30. interface FileData {
  31. id: string;
  32. filename: string;
  33. mimetype: string;
  34. size: number;
  35. createdAt: string;
  36. updatedAt: string;
  37. }
  38. interface Import {
  39. id: number;
  40. name: string;
  41. importDate: string;
  42. layoutId: number;
  43. layout: {
  44. id: number;
  45. name: string;
  46. };
  47. }
  48. interface RawImportData {
  49. id: number;
  50. name: string;
  51. importDate: Date | string;
  52. layoutId: number;
  53. layout: {
  54. id: number;
  55. name: string;
  56. };
  57. }
  58. const LAYOUT_NAME = "TerraTech - GasOilWater Summary";
  59. export default function TerraTechFacilitySummariesPage() {
  60. const [imports, setImports] = useState<Import[]>([]);
  61. const [loading, setLoading] = useState(true);
  62. const [summaryDialogOpen, setSummaryDialogOpen] = useState(false);
  63. const [selectedImportId, setSelectedImportId] = useState<number | null>(null);
  64. const { toast } = useToast();
  65. // Workflow state
  66. const [viewMode, setViewMode] = useState<"imports" | "new-import">("imports");
  67. const [currentStep, setCurrentStep] = useState(1);
  68. const [uploadedFile, setUploadedFile] = useState<FileData | null>(null);
  69. const [isProcessing, setIsProcessing] = useState(false);
  70. const [importRecord, setImportRecord] = useState<Import | null>(null);
  71. const [error, setError] = useState<string | null>(null);
  72. const loadImports = useCallback(async () => {
  73. try {
  74. setLoading(true);
  75. setError(null);
  76. const result = await getImportsByLayoutName(LAYOUT_NAME);
  77. if (result.success && result.data) {
  78. const transformedImports = result.data.map((item: RawImportData) => ({
  79. id: item.id,
  80. name: item.name,
  81. importDate:
  82. item.importDate instanceof Date
  83. ? item.importDate.toISOString()
  84. : String(item.importDate),
  85. layoutId: item.layoutId,
  86. layout: {
  87. id: item.layout.id,
  88. name: item.layout.name,
  89. },
  90. }));
  91. setImports(transformedImports);
  92. } else {
  93. toast({
  94. title: "Error",
  95. description: result.error || "Failed to load imports",
  96. variant: "destructive",
  97. });
  98. setImports([]);
  99. }
  100. } catch {
  101. toast({
  102. title: "Error",
  103. description: "Failed to load imports",
  104. variant: "destructive",
  105. });
  106. } finally {
  107. setLoading(false);
  108. }
  109. }, [toast]);
  110. useEffect(() => {
  111. loadImports();
  112. }, [loadImports]);
  113. // Workflow handlers
  114. const handleFileUploaded = (file: FileData) => {
  115. setUploadedFile(file);
  116. setCurrentStep(2);
  117. setError(null);
  118. };
  119. const handleCreateImportRecord = async () => {
  120. if (!uploadedFile) return;
  121. setIsProcessing(true);
  122. setError(null);
  123. try {
  124. const result = await createTerraTechImportRecord(
  125. uploadedFile.id,
  126. uploadedFile.filename,
  127. );
  128. if (result.success && result.data) {
  129. const importData = result.data;
  130. const importRecordData: Import = {
  131. id: importData.id,
  132. name: importData.name,
  133. importDate:
  134. importData.importDate instanceof Date
  135. ? importData.importDate.toISOString()
  136. : String(importData.importDate),
  137. layoutId: importData.layoutId,
  138. layout: {
  139. id: importData.layout?.id || 0,
  140. name: importData.layout?.name || LAYOUT_NAME,
  141. },
  142. };
  143. setImportRecord(importRecordData);
  144. setCurrentStep(3);
  145. } else {
  146. setError(result.error || "Failed to create import record");
  147. }
  148. } catch (err) {
  149. setError(err instanceof Error ? err.message : "Unknown error occurred");
  150. } finally {
  151. setIsProcessing(false);
  152. }
  153. };
  154. const handleProcessImportData = async () => {
  155. if (!importRecord) return;
  156. setIsProcessing(true);
  157. setError(null);
  158. try {
  159. const result = await processTerraTechImportData(importRecord.id);
  160. if (result.success) {
  161. // After processing, go back to imports view
  162. handleBackToImports();
  163. } else {
  164. setError(result.error || "Failed to process import data");
  165. }
  166. } catch (err) {
  167. setError(err instanceof Error ? err.message : "Unknown error occurred");
  168. } finally {
  169. setIsProcessing(false);
  170. }
  171. };
  172. const handleStartNewImport = () => {
  173. setViewMode("new-import");
  174. setCurrentStep(1);
  175. setUploadedFile(null);
  176. setImportRecord(null);
  177. setError(null);
  178. };
  179. const handleBackToImports = () => {
  180. if (viewMode !== "imports") {
  181. setViewMode("imports");
  182. }
  183. };
  184. function handleViewSummary(importRecord: Import) {
  185. setSelectedImportId(importRecord.id);
  186. setSummaryDialogOpen(true);
  187. }
  188. if (loading && viewMode === "imports") {
  189. return (
  190. <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
  191. <div className="container mx-auto px-4 py-8">
  192. <div className="flex justify-center">
  193. <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
  194. </div>
  195. </div>
  196. </div>
  197. );
  198. }
  199. return (
  200. <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
  201. <div className="container mx-auto px-4 py-8">
  202. <div className="flex justify-between items-center mb-8">
  203. <div className="flex items-center gap-4">
  204. <div className="bg-amber-500 w-12 h-12 rounded-full flex items-center justify-center text-white">
  205. <Fuel className="w-6 h-6" />
  206. </div>
  207. <div>
  208. <h1 className="text-3xl font-bold text-gray-900 dark:text-white">
  209. TerraTech Facility Summaries
  210. </h1>
  211. <p className="text-gray-600 dark:text-gray-300">
  212. View Gas, Oil, and Water production summaries for imported data
  213. </p>
  214. </div>
  215. </div>
  216. </div>
  217. {error && (
  218. <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
  219. <p className="text-sm text-red-800">{error}</p>
  220. </div>
  221. )}
  222. {/* Navigation Buttons */}
  223. <div className="mb-6 flex gap-4">
  224. <Button
  225. variant={viewMode === "imports" ? "default" : "outline"}
  226. onClick={handleBackToImports}
  227. className="flex items-center gap-2"
  228. >
  229. <History className="h-4 w-4" />
  230. Prior Imports
  231. </Button>
  232. <Button
  233. variant={viewMode === "new-import" ? "default" : "outline"}
  234. onClick={handleStartNewImport}
  235. className="flex items-center gap-2"
  236. >
  237. <PlusCircle className="h-4 w-4" />
  238. New Import
  239. </Button>
  240. </div>
  241. {/* Table Header with Refresh Button */}
  242. {viewMode === "imports" && (
  243. <div className="flex justify-between items-center mb-4">
  244. <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
  245. TerraTech Imports
  246. </h2>
  247. <Button
  248. onClick={loadImports}
  249. disabled={loading}
  250. variant="outline"
  251. size="sm"
  252. className="flex items-center gap-2"
  253. title="Refresh imports"
  254. >
  255. <Loader2
  256. className={`h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}`}
  257. />
  258. Refresh
  259. </Button>
  260. </div>
  261. )}
  262. {/* Prior Imports View */}
  263. {viewMode === "imports" && (
  264. <>
  265. {imports.length === 0 ? (
  266. <Card className="dark:bg-gray-800 dark:border-gray-700">
  267. <CardContent className="flex flex-col items-center justify-center py-12">
  268. <FileText className="h-12 w-12 text-muted-foreground mb-4" />
  269. <h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">
  270. No imports found
  271. </h3>
  272. <p className="text-muted-foreground mb-4 dark:text-gray-300 text-center">
  273. No imports using the &quot;{LAYOUT_NAME}&quot; layout
  274. configuration were found.
  275. <br />
  276. Import data using this layout to see it here.
  277. </p>
  278. <Button onClick={handleStartNewImport}>
  279. Create First Import
  280. </Button>
  281. </CardContent>
  282. </Card>
  283. ) : (
  284. <div className="w-full">
  285. <TerraTechImportsTable
  286. data={imports}
  287. onViewSummary={handleViewSummary}
  288. />
  289. </div>
  290. )}
  291. </>
  292. )}
  293. {/* New Import Workflow */}
  294. {viewMode === "new-import" && (
  295. <div className="space-y-6">
  296. {/* Workflow Steps */}
  297. <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
  298. {[
  299. {
  300. id: 1,
  301. title: "Upload Excel File",
  302. description: "Upload the TerraTech GasOilWater Excel file",
  303. icon: Upload,
  304. status:
  305. currentStep >= 1
  306. ? uploadedFile
  307. ? "completed"
  308. : "pending"
  309. : "pending",
  310. },
  311. {
  312. id: 2,
  313. title: "Create Import Record",
  314. description: "Create import record with layout configuration",
  315. icon: FileText,
  316. status:
  317. currentStep >= 2
  318. ? importRecord
  319. ? "completed"
  320. : "pending"
  321. : "disabled",
  322. },
  323. {
  324. id: 3,
  325. title: "Import Data",
  326. description: "Process Excel file and import data",
  327. icon: Database,
  328. status:
  329. currentStep > 3
  330. ? "completed"
  331. : currentStep === 3
  332. ? "pending"
  333. : "disabled",
  334. },
  335. ].map((step) => {
  336. const Icon = step.icon;
  337. const getStatusColor = (status: string) => {
  338. switch (status) {
  339. case "completed":
  340. return "text-green-600 bg-green-50 border-green-200";
  341. case "pending":
  342. return "text-amber-600 bg-amber-50 border-amber-200";
  343. case "disabled":
  344. return "text-gray-400 bg-gray-50 border-gray-200";
  345. default:
  346. return "text-gray-600 bg-gray-50 border-gray-200";
  347. }
  348. };
  349. return (
  350. <Card
  351. key={step.id}
  352. className={`border-2 ${getStatusColor(step.status)}`}
  353. >
  354. <CardHeader className="pb-3">
  355. <div className="flex items-center space-x-2">
  356. <Icon className="h-5 w-5" />
  357. <div>
  358. <CardTitle className="text-sm font-medium">
  359. Step {step.id}: {step.title}
  360. </CardTitle>
  361. </div>
  362. </div>
  363. </CardHeader>
  364. <CardContent>
  365. <CardDescription className="text-xs">
  366. {step.description}
  367. </CardDescription>
  368. </CardContent>
  369. </Card>
  370. );
  371. })}
  372. </div>
  373. {/* Step Content */}
  374. <div className="space-y-6">
  375. {currentStep === 1 && (
  376. <Card>
  377. <CardHeader>
  378. <CardTitle>Step 1: Upload Excel File</CardTitle>
  379. <CardDescription>
  380. Upload your TerraTech GasOilWater Excel file to begin
  381. processing
  382. </CardDescription>
  383. </CardHeader>
  384. <CardContent>
  385. <div className="space-y-4">
  386. <UploadForm onFileUploaded={handleFileUploaded} />
  387. {uploadedFile && (
  388. <div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg">
  389. <div className="flex items-center space-x-2">
  390. <CheckCircle className="h-5 w-5 text-green-600" />
  391. <div>
  392. <p className="text-sm font-medium text-green-800">
  393. File uploaded successfully!
  394. </p>
  395. <p className="text-sm text-green-600">
  396. {uploadedFile.filename} (
  397. {(uploadedFile.size / 1024 / 1024).toFixed(2)}{" "}
  398. MB)
  399. </p>
  400. </div>
  401. </div>
  402. </div>
  403. )}
  404. </div>
  405. </CardContent>
  406. </Card>
  407. )}
  408. {currentStep === 2 && (
  409. <Card>
  410. <CardHeader>
  411. <CardTitle>Step 2: Create Import Record</CardTitle>
  412. <CardDescription>
  413. Creating import record with TerraTech - GasOilWater
  414. Summary layout configuration
  415. </CardDescription>
  416. </CardHeader>
  417. <CardContent>
  418. <div className="space-y-4">
  419. <p className="text-sm text-muted-foreground">
  420. File: {uploadedFile?.filename}
  421. </p>
  422. <p className="text-sm text-muted-foreground">
  423. Layout: {LAYOUT_NAME}
  424. </p>
  425. <Button
  426. onClick={handleCreateImportRecord}
  427. disabled={isProcessing}
  428. className="w-full bg-amber-500 hover:bg-amber-600"
  429. >
  430. {isProcessing ? (
  431. <>
  432. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  433. Creating Import Record...
  434. </>
  435. ) : (
  436. "Create Import Record"
  437. )}
  438. </Button>
  439. </div>
  440. </CardContent>
  441. </Card>
  442. )}
  443. {currentStep === 3 && (
  444. <Card>
  445. <CardHeader>
  446. <CardTitle>Step 3: Import Data</CardTitle>
  447. <CardDescription>
  448. Processing Excel file and importing data into database
  449. </CardDescription>
  450. </CardHeader>
  451. <CardContent>
  452. <div className="space-y-4">
  453. <p className="text-sm text-muted-foreground">
  454. Import ID: {importRecord?.id}
  455. </p>
  456. <p className="text-sm text-muted-foreground">
  457. Import Name: {importRecord?.name}
  458. </p>
  459. <Button
  460. onClick={handleProcessImportData}
  461. disabled={isProcessing}
  462. className="w-full bg-amber-500 hover:bg-amber-600"
  463. >
  464. {isProcessing ? (
  465. <>
  466. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  467. Processing Import...
  468. </>
  469. ) : (
  470. "Process Import"
  471. )}
  472. </Button>
  473. </div>
  474. </CardContent>
  475. </Card>
  476. )}
  477. </div>
  478. </div>
  479. )}
  480. <TerraTechSummaryDialog
  481. open={summaryDialogOpen}
  482. onOpenChange={setSummaryDialogOpen}
  483. importId={selectedImportId || 0}
  484. />
  485. </div>
  486. </div>
  487. );
  488. }