ImportDetailDialog.tsx 7.7 KB

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