page.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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. setViewMode("imports");
  181. loadImports();
  182. };
  183. function handleViewSummary(importRecord: Import) {
  184. setSelectedImportId(importRecord.id);
  185. setSummaryDialogOpen(true);
  186. }
  187. if (loading && viewMode === "imports") {
  188. return (
  189. <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
  190. <div className="container mx-auto px-4 py-8">
  191. <div className="flex justify-center">
  192. <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
  193. </div>
  194. </div>
  195. </div>
  196. );
  197. }
  198. return (
  199. <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
  200. <div className="container mx-auto px-4 py-8">
  201. <div className="flex justify-between items-center mb-8">
  202. <div className="flex items-center gap-4">
  203. <div className="bg-amber-500 w-12 h-12 rounded-full flex items-center justify-center text-white">
  204. <Fuel className="w-6 h-6" />
  205. </div>
  206. <div>
  207. <h1 className="text-3xl font-bold text-gray-900 dark:text-white">
  208. TerraTech Facility Summaries
  209. </h1>
  210. <p className="text-gray-600 dark:text-gray-300">
  211. View Gas, Oil, and Water production summaries for imported data
  212. </p>
  213. </div>
  214. </div>
  215. </div>
  216. {error && (
  217. <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
  218. <p className="text-sm text-red-800">{error}</p>
  219. </div>
  220. )}
  221. {/* Navigation Buttons */}
  222. <div className="mb-6 flex gap-4">
  223. <Button
  224. variant={viewMode === "imports" ? "default" : "outline"}
  225. onClick={handleBackToImports}
  226. className="flex items-center gap-2"
  227. >
  228. <History className="h-4 w-4" />
  229. Prior Imports
  230. </Button>
  231. <Button
  232. variant={viewMode === "new-import" ? "default" : "outline"}
  233. onClick={handleStartNewImport}
  234. className="flex items-center gap-2"
  235. >
  236. <PlusCircle className="h-4 w-4" />
  237. New Import
  238. </Button>
  239. </div>
  240. {/* Prior Imports View */}
  241. {viewMode === "imports" && (
  242. <>
  243. {imports.length === 0 ? (
  244. <Card className="dark:bg-gray-800 dark:border-gray-700">
  245. <CardContent className="flex flex-col items-center justify-center py-12">
  246. <FileText className="h-12 w-12 text-muted-foreground mb-4" />
  247. <h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">
  248. No imports found
  249. </h3>
  250. <p className="text-muted-foreground mb-4 dark:text-gray-300 text-center">
  251. No imports using the &quot;{LAYOUT_NAME}&quot; layout
  252. configuration were found.
  253. <br />
  254. Import data using this layout to see it here.
  255. </p>
  256. <Button onClick={handleStartNewImport}>
  257. Create First Import
  258. </Button>
  259. </CardContent>
  260. </Card>
  261. ) : (
  262. <div className="w-full">
  263. <TerraTechImportsTable
  264. data={imports}
  265. onViewSummary={handleViewSummary}
  266. />
  267. </div>
  268. )}
  269. </>
  270. )}
  271. {/* New Import Workflow */}
  272. {viewMode === "new-import" && (
  273. <div className="space-y-6">
  274. {/* Workflow Steps */}
  275. <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
  276. {[
  277. {
  278. id: 1,
  279. title: "Upload Excel File",
  280. description: "Upload the TerraTech GasOilWater Excel file",
  281. icon: Upload,
  282. status:
  283. currentStep >= 1
  284. ? uploadedFile
  285. ? "completed"
  286. : "pending"
  287. : "pending",
  288. },
  289. {
  290. id: 2,
  291. title: "Create Import Record",
  292. description: "Create import record with layout configuration",
  293. icon: FileText,
  294. status:
  295. currentStep >= 2
  296. ? importRecord
  297. ? "completed"
  298. : "pending"
  299. : "disabled",
  300. },
  301. {
  302. id: 3,
  303. title: "Import Data",
  304. description: "Process Excel file and import data",
  305. icon: Database,
  306. status:
  307. currentStep > 3
  308. ? "completed"
  309. : currentStep === 3
  310. ? "pending"
  311. : "disabled",
  312. },
  313. ].map((step) => {
  314. const Icon = step.icon;
  315. const getStatusColor = (status: string) => {
  316. switch (status) {
  317. case "completed":
  318. return "text-green-600 bg-green-50 border-green-200";
  319. case "pending":
  320. return "text-amber-600 bg-amber-50 border-amber-200";
  321. case "disabled":
  322. return "text-gray-400 bg-gray-50 border-gray-200";
  323. default:
  324. return "text-gray-600 bg-gray-50 border-gray-200";
  325. }
  326. };
  327. return (
  328. <Card
  329. key={step.id}
  330. className={`border-2 ${getStatusColor(step.status)}`}
  331. >
  332. <CardHeader className="pb-3">
  333. <div className="flex items-center space-x-2">
  334. <Icon className="h-5 w-5" />
  335. <div>
  336. <CardTitle className="text-sm font-medium">
  337. Step {step.id}: {step.title}
  338. </CardTitle>
  339. </div>
  340. </div>
  341. </CardHeader>
  342. <CardContent>
  343. <CardDescription className="text-xs">
  344. {step.description}
  345. </CardDescription>
  346. </CardContent>
  347. </Card>
  348. );
  349. })}
  350. </div>
  351. {/* Step Content */}
  352. <div className="space-y-6">
  353. {currentStep === 1 && (
  354. <Card>
  355. <CardHeader>
  356. <CardTitle>Step 1: Upload Excel File</CardTitle>
  357. <CardDescription>
  358. Upload your TerraTech GasOilWater Excel file to begin
  359. processing
  360. </CardDescription>
  361. </CardHeader>
  362. <CardContent>
  363. <div className="space-y-4">
  364. <UploadForm onFileUploaded={handleFileUploaded} />
  365. {uploadedFile && (
  366. <div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg">
  367. <div className="flex items-center space-x-2">
  368. <CheckCircle className="h-5 w-5 text-green-600" />
  369. <div>
  370. <p className="text-sm font-medium text-green-800">
  371. File uploaded successfully!
  372. </p>
  373. <p className="text-sm text-green-600">
  374. {uploadedFile.filename} (
  375. {(uploadedFile.size / 1024 / 1024).toFixed(2)}{" "}
  376. MB)
  377. </p>
  378. </div>
  379. </div>
  380. </div>
  381. )}
  382. </div>
  383. </CardContent>
  384. </Card>
  385. )}
  386. {currentStep === 2 && (
  387. <Card>
  388. <CardHeader>
  389. <CardTitle>Step 2: Create Import Record</CardTitle>
  390. <CardDescription>
  391. Creating import record with TerraTech - GasOilWater
  392. Summary layout configuration
  393. </CardDescription>
  394. </CardHeader>
  395. <CardContent>
  396. <div className="space-y-4">
  397. <p className="text-sm text-muted-foreground">
  398. File: {uploadedFile?.filename}
  399. </p>
  400. <p className="text-sm text-muted-foreground">
  401. Layout: {LAYOUT_NAME}
  402. </p>
  403. <Button
  404. onClick={handleCreateImportRecord}
  405. disabled={isProcessing}
  406. className="w-full bg-amber-500 hover:bg-amber-600"
  407. >
  408. {isProcessing ? (
  409. <>
  410. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  411. Creating Import Record...
  412. </>
  413. ) : (
  414. "Create Import Record"
  415. )}
  416. </Button>
  417. </div>
  418. </CardContent>
  419. </Card>
  420. )}
  421. {currentStep === 3 && (
  422. <Card>
  423. <CardHeader>
  424. <CardTitle>Step 3: Import Data</CardTitle>
  425. <CardDescription>
  426. Processing Excel file and importing data into database
  427. </CardDescription>
  428. </CardHeader>
  429. <CardContent>
  430. <div className="space-y-4">
  431. <p className="text-sm text-muted-foreground">
  432. Import ID: {importRecord?.id}
  433. </p>
  434. <p className="text-sm text-muted-foreground">
  435. Import Name: {importRecord?.name}
  436. </p>
  437. <Button
  438. onClick={handleProcessImportData}
  439. disabled={isProcessing}
  440. className="w-full bg-amber-500 hover:bg-amber-600"
  441. >
  442. {isProcessing ? (
  443. <>
  444. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  445. Processing Import...
  446. </>
  447. ) : (
  448. "Process Import"
  449. )}
  450. </Button>
  451. </div>
  452. </CardContent>
  453. </Card>
  454. )}
  455. </div>
  456. </div>
  457. )}
  458. <TerraTechSummaryDialog
  459. open={summaryDialogOpen}
  460. onOpenChange={setSummaryDialogOpen}
  461. importId={selectedImportId || 0}
  462. />
  463. </div>
  464. </div>
  465. );
  466. }