page.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. "use client";
  2. import { useState } from 'react';
  3. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
  4. import { Button } from '@/components/ui/button';
  5. import { Upload, FileText, Database, BarChart3, CheckCircle, Loader2 } from 'lucide-react';
  6. import { UploadForm } from '@/app/components/uploadForm';
  7. import { createCintasImportRecord } from '@/app/actions/cintas-workflow';
  8. interface FileData {
  9. id: string;
  10. filename: string;
  11. mimetype: string;
  12. size: number;
  13. createdAt: string;
  14. updatedAt: string;
  15. }
  16. interface CintasSummary {
  17. id: number;
  18. week: string;
  19. trrTotal: number;
  20. fourWkAverages: number;
  21. trrPlus4Wk: number;
  22. powerAdds: number;
  23. weekId: number;
  24. }
  25. export default function CintasCalendarSummaryPage() {
  26. const [currentStep, setCurrentStep] = useState(1);
  27. const [uploadedFile, setUploadedFile] = useState<FileData | null>(null);
  28. const [isProcessing, setIsProcessing] = useState(false);
  29. const [importRecord, setImportRecord] = useState<any>(null);
  30. const [summaryData, setSummaryData] = useState<CintasSummary[]>([]);
  31. const [error, setError] = useState<string | null>(null);
  32. const handleFileUploaded = (file: FileData) => {
  33. setUploadedFile(file);
  34. setCurrentStep(2);
  35. setError(null);
  36. };
  37. const handleCreateImportRecord = async () => {
  38. if (!uploadedFile) return;
  39. setIsProcessing(true);
  40. setError(null);
  41. try {
  42. const result = await createCintasImportRecord(uploadedFile.id, uploadedFile.filename);
  43. if (result.success) {
  44. setImportRecord(result.data);
  45. setCurrentStep(3);
  46. } else {
  47. setError(result.error || 'Failed to create import record');
  48. }
  49. } catch (err) {
  50. setError(err instanceof Error ? err.message : 'Unknown error occurred');
  51. } finally {
  52. setIsProcessing(false);
  53. }
  54. };
  55. const steps = [
  56. {
  57. id: 1,
  58. title: 'Upload Excel File',
  59. description: 'Upload the Cintas Install Calendar Excel file to blob storage',
  60. icon: Upload,
  61. status: currentStep >= 1 ? (uploadedFile ? 'completed' : 'pending') : 'pending',
  62. },
  63. {
  64. id: 2,
  65. title: 'Create Import Record',
  66. description: 'Create an import record with Cintas Install Calendar layout configuration',
  67. icon: FileText,
  68. status: currentStep >= 2 ? (importRecord ? 'completed' : 'pending') : 'disabled',
  69. },
  70. {
  71. id: 3,
  72. title: 'Import Data',
  73. description: 'Read the Excel file and import data into PostgreSQL database',
  74. icon: Database,
  75. status: currentStep >= 3 ? 'pending' : 'disabled',
  76. },
  77. {
  78. id: 4,
  79. title: 'Generate Summary',
  80. description: 'Run summary calculations and display results',
  81. icon: BarChart3,
  82. status: currentStep >= 4 ? 'pending' : 'disabled',
  83. },
  84. ];
  85. const getStepStatusColor = (status: string) => {
  86. switch (status) {
  87. case 'completed':
  88. return 'text-green-600 bg-green-50 border-green-200';
  89. case 'pending':
  90. return 'text-blue-600 bg-blue-50 border-blue-200';
  91. case 'disabled':
  92. return 'text-gray-400 bg-gray-50 border-gray-200';
  93. default:
  94. return 'text-gray-600 bg-gray-50 border-gray-200';
  95. }
  96. };
  97. return (
  98. <div className="container mx-auto py-6 px-4 max-w-6xl">
  99. <div className="mb-8">
  100. <h1 className="text-3xl font-bold tracking-tight">Cintas Install Calendar Summary</h1>
  101. <p className="text-muted-foreground">
  102. Follow the workflow steps to upload and process the installation calendar data
  103. </p>
  104. </div>
  105. {error && (
  106. <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
  107. <p className="text-sm text-red-800">{error}</p>
  108. </div>
  109. )}
  110. {/* Workflow Steps */}
  111. <div className="mb-8">
  112. <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
  113. {steps.map((step) => {
  114. const Icon = step.icon;
  115. return (
  116. <Card
  117. key={step.id}
  118. className={`border-2 ${getStepStatusColor(step.status)}`}
  119. >
  120. <CardHeader className="pb-3">
  121. <div className="flex items-center space-x-2">
  122. <Icon className="h-5 w-5" />
  123. <div>
  124. <CardTitle className="text-sm font-medium">
  125. Step {step.id}: {step.title}
  126. </CardTitle>
  127. </div>
  128. </div>
  129. </CardHeader>
  130. <CardContent>
  131. <CardDescription className="text-xs">
  132. {step.description}
  133. </CardDescription>
  134. </CardContent>
  135. </Card>
  136. );
  137. })}
  138. </div>
  139. </div>
  140. {/* Step Content */}
  141. <div className="grid gap-6">
  142. {currentStep === 1 && (
  143. <Card>
  144. <CardHeader>
  145. <CardTitle>Step 1: Upload Excel File</CardTitle>
  146. <CardDescription>
  147. Upload your Cintas Install Calendar Excel file to begin processing
  148. </CardDescription>
  149. </CardHeader>
  150. <CardContent>
  151. <div className="space-y-4">
  152. <UploadForm onFileUploaded={handleFileUploaded} />
  153. {uploadedFile && (
  154. <div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg">
  155. <div className="flex items-center space-x-2">
  156. <CheckCircle className="h-5 w-5 text-green-600" />
  157. <div>
  158. <p className="text-sm font-medium text-green-800">File uploaded successfully!</p>
  159. <p className="text-sm text-green-600">
  160. {uploadedFile.filename} ({(uploadedFile.size / 1024 / 1024).toFixed(2)} MB)
  161. </p>
  162. </div>
  163. </div>
  164. </div>
  165. )}
  166. </div>
  167. </CardContent>
  168. </Card>
  169. )}
  170. {currentStep === 2 && (
  171. <Card>
  172. <CardHeader>
  173. <CardTitle>Step 2: Create Import Record</CardTitle>
  174. <CardDescription>
  175. Creating import record with Cintas Install Calendar layout configuration
  176. </CardDescription>
  177. </CardHeader>
  178. <CardContent>
  179. <div className="space-y-4">
  180. <p className="text-sm text-muted-foreground">
  181. File: {uploadedFile?.filename}
  182. </p>
  183. <Button
  184. onClick={handleCreateImportRecord}
  185. disabled={isProcessing}
  186. className="w-full"
  187. >
  188. {isProcessing ? (
  189. <>
  190. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  191. Creating Import Record...
  192. </>
  193. ) : (
  194. 'Create Import Record'
  195. )}
  196. </Button>
  197. </div>
  198. </CardContent>
  199. </Card>
  200. )}
  201. {currentStep === 3 && (
  202. <Card>
  203. <CardHeader>
  204. <CardTitle>Step 3: Import Data</CardTitle>
  205. <CardDescription>
  206. Processing Excel file and importing data into database
  207. </CardDescription>
  208. </CardHeader>
  209. <CardContent>
  210. <div className="space-y-4">
  211. <p className="text-sm text-muted-foreground">
  212. Import ID: {importRecord?.id}
  213. </p>
  214. <Button
  215. onClick={() => setCurrentStep(4)}
  216. disabled={isProcessing}
  217. className="w-full"
  218. >
  219. {isProcessing ? (
  220. <>
  221. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  222. Processing Import...
  223. </>
  224. ) : (
  225. 'Process Import'
  226. )}
  227. </Button>
  228. </div>
  229. </CardContent>
  230. </Card>
  231. )}
  232. {currentStep === 4 && (
  233. <Card>
  234. <CardHeader>
  235. <CardTitle>Step 4: Generate Summary</CardTitle>
  236. <CardDescription>
  237. Running summary calculations and displaying results
  238. </CardDescription>
  239. </CardHeader>
  240. <CardContent>
  241. <div className="space-y-4">
  242. <Button
  243. onClick={() => setCurrentStep(4)}
  244. disabled={isProcessing}
  245. className="w-full"
  246. >
  247. {isProcessing ? (
  248. <>
  249. <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  250. Generating Summary...
  251. </>
  252. ) : (
  253. 'Generate Summary'
  254. )}
  255. </Button>
  256. {summaryData.length > 0 && (
  257. <div className="mt-6">
  258. <h3 className="text-lg font-semibold mb-4">Summary Results</h3>
  259. <div className="overflow-x-auto">
  260. <table className="min-w-full divide-y divide-gray-200">
  261. <thead className="bg-gray-50">
  262. <tr>
  263. <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Week</th>
  264. <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">TRR Total</th>
  265. <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">4 Week Avg</th>
  266. <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">TRR + 4Wk</th>
  267. <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Power Adds</th>
  268. </tr>
  269. </thead>
  270. <tbody className="bg-white divide-y divide-gray-200">
  271. {summaryData.map((item) => (
  272. <tr key={item.id}>
  273. <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{item.week}</td>
  274. <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{item.trrTotal}</td>
  275. <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{item.fourWkAverages}</td>
  276. <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{item.trrPlus4Wk}</td>
  277. <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{item.powerAdds}</td>
  278. </tr>
  279. ))}
  280. </tbody>
  281. </table>
  282. </div>
  283. </div>
  284. )}
  285. </div>
  286. </CardContent>
  287. </Card>
  288. )}
  289. {/* Navigation */}
  290. <div className="flex justify-between">
  291. <Button
  292. variant="outline"
  293. onClick={() => setCurrentStep(Math.max(1, currentStep - 1))}
  294. disabled={currentStep === 1}
  295. >
  296. Previous
  297. </Button>
  298. <Button
  299. onClick={() => setCurrentStep(Math.min(4, currentStep + 1))}
  300. disabled={currentStep === 4 || (currentStep === 1 && !uploadedFile)}
  301. >
  302. {currentStep === 4 ? 'Complete' : 'Next'}
  303. </Button>
  304. </div>
  305. </div>
  306. </div>
  307. );
  308. }