LayoutConfigurationsTable.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /* eslint-disable @typescript-eslint/no-explicit-any */
  2. "use client";
  3. import { useEffect, useState } from "react";
  4. import { useRouter } from "next/navigation";
  5. import { getLayoutConfigurations, deleteLayoutConfiguration } from "@/app/actions/layout-configurations";
  6. import { Button } from "@/components/ui/button";
  7. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
  8. import { Badge } from "@/components/ui/badge";
  9. import { Skeleton } from "@/components/ui/skeleton";
  10. import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
  11. import { toast } from "@/hooks/use-toast";
  12. import { Trash2, Eye, Edit, RefreshCw } from "lucide-react";
  13. interface LayoutConfiguration {
  14. id: number;
  15. name: string;
  16. sections: LayoutSection[];
  17. createdAt: string;
  18. updatedAt: string;
  19. }
  20. interface LayoutSection {
  21. id: number;
  22. name: string;
  23. type: string;
  24. sheetName: string;
  25. fields: LayoutSectionField[];
  26. }
  27. interface LayoutSectionField {
  28. id: number;
  29. name: string;
  30. dataType: string;
  31. cellPosition: string;
  32. }
  33. export function LayoutConfigurationsTable() {
  34. const [configurations, setConfigurations] = useState<LayoutConfiguration[]>([]);
  35. const [loading, setLoading] = useState(true);
  36. const [isRefreshing, setIsRefreshing] = useState(false);
  37. const router = useRouter();
  38. useEffect(() => {
  39. loadConfigurations();
  40. }, []);
  41. async function loadConfigurations() {
  42. try {
  43. const result = await getLayoutConfigurations();
  44. if (result.success) {
  45. const configurations = (result.data || []).map((config: any) => ({
  46. ...config,
  47. createdAt: config.createdAt instanceof Date ? config.createdAt.toISOString() : String(config.createdAt),
  48. updatedAt: config.updatedAt instanceof Date ? config.updatedAt.toISOString() : String(config.updatedAt),
  49. }));
  50. setConfigurations(configurations);
  51. } else {
  52. toast({
  53. title: "Error",
  54. description: result.error,
  55. variant: "destructive",
  56. });
  57. }
  58. } catch {
  59. toast({
  60. title: "Error",
  61. description: "Failed to load configurations",
  62. variant: "destructive",
  63. });
  64. } finally {
  65. setLoading(false);
  66. setIsRefreshing(false);
  67. }
  68. }
  69. async function refreshConfigurations() {
  70. setIsRefreshing(true);
  71. await loadConfigurations();
  72. }
  73. async function handleDelete(id: number) {
  74. try {
  75. const result = await deleteLayoutConfiguration(id);
  76. if (result.success) {
  77. toast({
  78. title: "Success",
  79. description: "Configuration deleted successfully",
  80. });
  81. loadConfigurations();
  82. } else {
  83. toast({
  84. title: "Error",
  85. description: result.error,
  86. variant: "destructive",
  87. });
  88. }
  89. } catch {
  90. toast({
  91. title: "Error",
  92. description: "Failed to delete configuration",
  93. variant: "destructive",
  94. });
  95. }
  96. }
  97. if (loading) {
  98. return (
  99. <div className="p-4 space-y-4">
  100. {[...Array(3)].map((_, i) => (
  101. <Card key={i}>
  102. <CardHeader>
  103. <Skeleton className="h-6 w-48" />
  104. <Skeleton className="h-4 w-32" />
  105. </CardHeader>
  106. <CardContent>
  107. <Skeleton className="h-4 w-full" />
  108. </CardContent>
  109. </Card>
  110. ))}
  111. </div>
  112. );
  113. }
  114. if (configurations.length === 0) {
  115. return (
  116. <div className="p-8 text-center">
  117. <p className="text-muted-foreground mb-4">No layout configurations found</p>
  118. <Button
  119. variant="outline"
  120. size="sm"
  121. onClick={refreshConfigurations}
  122. disabled={isRefreshing}
  123. className="flex items-center gap-2 mx-auto"
  124. >
  125. <RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
  126. {isRefreshing ? "Refreshing..." : "Refresh"}
  127. </Button>
  128. </div>
  129. );
  130. }
  131. return (
  132. <div className="p-4 space-y-4">
  133. <div className="flex justify-end">
  134. <Button
  135. variant="outline"
  136. size="sm"
  137. onClick={refreshConfigurations}
  138. disabled={isRefreshing}
  139. className="flex items-center gap-2"
  140. >
  141. <RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
  142. {isRefreshing ? "Refreshing..." : "Refresh"}
  143. </Button>
  144. </div>
  145. {configurations.map((config) => (
  146. <Card key={config.id} className="hover:shadow-md transition-shadow">
  147. <CardHeader>
  148. <div className="flex justify-between items-start">
  149. <div>
  150. <CardTitle className="text-xl">{config.name}</CardTitle>
  151. <CardDescription>
  152. Created: {new Date(config.createdAt).toLocaleDateString()}
  153. </CardDescription>
  154. </div>
  155. <div className="flex gap-2">
  156. <Button
  157. variant="ghost"
  158. size="sm"
  159. onClick={() => router.push(`/layout-configurations/${config.id}`)}
  160. >
  161. <Eye className="h-4 w-4" />
  162. </Button>
  163. <Button
  164. variant="ghost"
  165. size="sm"
  166. onClick={() => router.push(`/layout-configurations/${config.id}/edit`)}
  167. >
  168. <Edit className="h-4 w-4" />
  169. </Button>
  170. <AlertDialog>
  171. <AlertDialogTrigger asChild>
  172. <Button variant="ghost" size="sm" className="text-destructive">
  173. <Trash2 className="h-4 w-4" />
  174. </Button>
  175. </AlertDialogTrigger>
  176. <AlertDialogContent>
  177. <AlertDialogHeader>
  178. <AlertDialogTitle>Delete Configuration</AlertDialogTitle>
  179. <AlertDialogDescription>
  180. Are you sure you want to delete {config.name}? This action cannot be undone.
  181. </AlertDialogDescription>
  182. </AlertDialogHeader>
  183. <AlertDialogFooter>
  184. <AlertDialogCancel>Cancel</AlertDialogCancel>
  185. <AlertDialogAction
  186. onClick={() => handleDelete(config.id)}
  187. className="bg-destructive text-destructive-foreground"
  188. >
  189. Delete
  190. </AlertDialogAction>
  191. </AlertDialogFooter>
  192. </AlertDialogContent>
  193. </AlertDialog>
  194. </div>
  195. </div>
  196. </CardHeader>
  197. <CardContent>
  198. <div className="flex items-center gap-4">
  199. <Badge variant="secondary">
  200. {config.sections.length} sections
  201. </Badge>
  202. <Badge variant="outline">
  203. {config.sections.reduce((total, section) => total + section.fields.length, 0)} fields
  204. </Badge>
  205. </div>
  206. </CardContent>
  207. </Card>
  208. ))}
  209. </div>
  210. );
  211. }