LayoutConfigurationsTable.tsx 7.1 KB

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