ImportDetailDialog.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. 'use client';
  2. import { useState, useEffect, useCallback } from 'react';
  3. import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
  4. import { Button } from '@/components/ui/button';
  5. import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
  6. import { format } from 'date-fns';
  7. import { useToast } from '@/hooks/use-toast';
  8. import { getImportById, calculateCintasSummaries } from '@/app/actions/imports';
  9. interface CintasSummary {
  10. id: number;
  11. week: string;
  12. trrTotal: number;
  13. fourWkAverages: number;
  14. trrPlus4Wk: number;
  15. powerAdds: number;
  16. weekId: number;
  17. importId: number;
  18. }
  19. interface ImportDetail {
  20. id: number;
  21. name: string;
  22. importDate: Date;
  23. layout: {
  24. id: number;
  25. name: string;
  26. sections: Array<{
  27. id: number;
  28. name: string;
  29. tableName: string;
  30. fields: Array<{
  31. id: number;
  32. name: string;
  33. createdAt: Date;
  34. updatedAt: Date;
  35. layoutSectionId: number;
  36. cellPosition: string;
  37. dataType: string;
  38. dataTypeFormat: string | null;
  39. importTableColumnName: string;
  40. importColumnOrderNumber: number;
  41. }>;
  42. }>;
  43. };
  44. cintasSummaries: CintasSummary[];
  45. }
  46. interface ImportDetailDialogProps {
  47. open: boolean;
  48. onOpenChange: (open: boolean) => void;
  49. importId: number;
  50. }
  51. export function ImportDetailDialog({ open, onOpenChange, importId }: ImportDetailDialogProps) {
  52. const [importDetail, setImportDetail] = useState<ImportDetail | null>(null);
  53. const [loading, setLoading] = useState(true);
  54. const [calculating, setCalculating] = useState(false);
  55. const { toast } = useToast();
  56. const loadImportDetail = useCallback(async () => {
  57. try {
  58. const result = await getImportById(importId);
  59. if (result.success && result.data) {
  60. setImportDetail(result.data);
  61. } else {
  62. toast({
  63. title: 'Error',
  64. description: result.error || 'Failed to load import details',
  65. variant: 'destructive',
  66. });
  67. setImportDetail(null);
  68. }
  69. } catch {
  70. toast({
  71. title: 'Error',
  72. description: 'Failed to load import details',
  73. variant: 'destructive',
  74. });
  75. setImportDetail(null);
  76. } finally {
  77. setLoading(false);
  78. }
  79. }, [importId, toast]);
  80. useEffect(() => {
  81. if (open && importId) {
  82. loadImportDetail();
  83. }
  84. }, [open, importId, loadImportDetail]);
  85. async function handleCalculateSummaries() {
  86. setCalculating(true);
  87. try {
  88. const result = await calculateCintasSummaries(importId);
  89. if (result.success) {
  90. toast({
  91. title: 'Success',
  92. description: 'Cintas summaries calculated successfully',
  93. });
  94. loadImportDetail();
  95. } else {
  96. toast({
  97. title: 'Error',
  98. description: result.error || 'Failed to calculate summaries',
  99. variant: 'destructive',
  100. });
  101. }
  102. } catch {
  103. toast({
  104. title: 'Error',
  105. description: 'Failed to calculate summaries',
  106. variant: 'destructive',
  107. });
  108. } finally {
  109. setCalculating(false);
  110. }
  111. }
  112. if (loading) {
  113. return (
  114. <Dialog open={open} onOpenChange={onOpenChange}>
  115. <DialogContent className="max-w-4xl max-h-[80vh]">
  116. <div className="flex justify-center py-8">
  117. <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
  118. </div>
  119. </DialogContent>
  120. </Dialog>
  121. );
  122. }
  123. if (!importDetail) return null;
  124. return (
  125. <Dialog open={open} onOpenChange={onOpenChange}>
  126. <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
  127. <DialogHeader>
  128. <DialogTitle>Import Details</DialogTitle>
  129. <DialogDescription>
  130. View detailed information about this import
  131. </DialogDescription>
  132. </DialogHeader>
  133. <div className="space-y-6">
  134. <Card>
  135. <CardHeader>
  136. <CardTitle>Import Information</CardTitle>
  137. </CardHeader>
  138. <CardContent className="space-y-2">
  139. <div className="flex justify-between">
  140. <span className="font-medium">Name:</span>
  141. <span>{importDetail.name}</span>
  142. </div>
  143. <div className="flex justify-between">
  144. <span className="font-medium">Layout:</span>
  145. <span>{importDetail.layout.name}</span>
  146. </div>
  147. <div className="flex justify-between">
  148. <span className="font-medium">Import Date:</span>
  149. <span>{format(importDetail.importDate, 'PPpp')}</span>
  150. </div>
  151. </CardContent>
  152. </Card>
  153. <Card>
  154. <CardHeader>
  155. <CardTitle>Layout Configuration</CardTitle>
  156. </CardHeader>
  157. <CardContent>
  158. <div className="space-y-4">
  159. {importDetail.layout.sections.map((section) => (
  160. <div key={section.id} className="border rounded-lg p-4">
  161. <h4 className="font-medium mb-2">{section.name}</h4>
  162. <p className="text-sm text-muted-foreground mb-2">
  163. Table: {section.tableName}
  164. </p>
  165. <div className="grid gap-1">
  166. {section.fields.map((field) => (
  167. <div key={field.importTableColumnName} className="text-sm">
  168. <span className="font-medium">{field.name}:</span>
  169. <span className="ml-2 text-muted-foreground">
  170. {field.importTableColumnName}
  171. </span>
  172. </div>
  173. ))}
  174. </div>
  175. </div>
  176. ))}
  177. </div>
  178. </CardContent>
  179. </Card>
  180. <Card>
  181. <CardHeader>
  182. <div className="flex justify-between items-center">
  183. <CardTitle>Cintas Summaries</CardTitle>
  184. <Button
  185. onClick={handleCalculateSummaries}
  186. disabled={calculating}
  187. size="sm"
  188. >
  189. {calculating ? 'Calculating...' : 'Calculate Summaries'}
  190. </Button>
  191. </div>
  192. </CardHeader>
  193. <CardContent>
  194. {importDetail.cintasSummaries.length === 0 ? (
  195. <p className="text-muted-foreground">No summaries calculated yet</p>
  196. ) : (
  197. <div className="overflow-x-auto">
  198. <table className="w-full text-sm">
  199. <thead>
  200. <tr className="border-b">
  201. <th className="text-left py-2">Week</th>
  202. <th className="text-right py-2">TRR Total</th>
  203. <th className="text-right py-2">4WK Averages</th>
  204. <th className="text-right py-2">TRR + 4WK</th>
  205. <th className="text-right py-2">Power Adds</th>
  206. </tr>
  207. </thead>
  208. <tbody>
  209. {importDetail.cintasSummaries.map((summary) => (
  210. <tr key={summary.id} className="border-b">
  211. <td className="py-2">{summary.week}</td>
  212. <td className="text-right py-2">{summary.trrTotal}</td>
  213. <td className="text-right py-2">{summary.fourWkAverages}</td>
  214. <td className="text-right py-2">{summary.trrPlus4Wk}</td>
  215. <td className="text-right py-2">{summary.powerAdds}</td>
  216. </tr>
  217. ))}
  218. </tbody>
  219. </table>
  220. </div>
  221. )}
  222. </CardContent>
  223. </Card>
  224. </div>
  225. <div className="flex justify-end gap-2 mt-6">
  226. <Button onClick={() => onOpenChange(false)}>Close</Button>
  227. </div>
  228. </DialogContent>
  229. </Dialog>
  230. );
  231. }