TaskProcessingCard.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. "use client";
  2. import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
  3. import toast from "react-hot-toast";
  4. import { get, post } from "../../lib/api";
  5. import Card from "./Card";
  6. export default function TaskProcessingCard() {
  7. const queryClient = useQueryClient();
  8. const { data: filesSuccessful, isLoading: filesSuccessfulLoading } = useQuery(
  9. {
  10. queryKey: ["files-stats-successful"],
  11. queryFn: () => get("/files/stats/successful"),
  12. }
  13. );
  14. const { data: filesProcessedTotal, isLoading: filesProcessedLoading } =
  15. useQuery({
  16. queryKey: ["files-stats-processed"],
  17. queryFn: () => get("/files/stats/processed"),
  18. });
  19. const {
  20. data: taskProcessingStatus,
  21. isLoading: taskProcessingLoading,
  22. refetch: refetchTaskProcessingStatus,
  23. } = useQuery({
  24. queryKey: ["tasks", "processing-status"],
  25. queryFn: () => get("/tasks/processing-status"),
  26. staleTime: 0,
  27. gcTime: 0,
  28. });
  29. const { data: queueStatus } = useQuery({
  30. queryKey: ["tasks", "queue", "status"],
  31. queryFn: () => get("/tasks/queue/status"),
  32. });
  33. const startTaskProcessingMutation = useMutation({
  34. mutationFn: () => post("/tasks/start-processing"),
  35. onSuccess: async () => {
  36. // Small delay to let backend update state
  37. await new Promise((resolve) => setTimeout(resolve, 200));
  38. await refetchTaskProcessingStatus();
  39. queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] });
  40. toast.success("Task processing started");
  41. },
  42. onError: () => {
  43. toast.error("Failed to start task processing");
  44. },
  45. });
  46. const stopTaskProcessingMutation = useMutation({
  47. mutationFn: () => post("/tasks/stop-processing"),
  48. onSuccess: async () => {
  49. // Small delay to let backend update state
  50. await new Promise((resolve) => setTimeout(resolve, 200));
  51. await refetchTaskProcessingStatus();
  52. queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] });
  53. toast.success("Task processing stopped");
  54. },
  55. onError: () => {
  56. toast.error("Failed to stop task processing");
  57. },
  58. });
  59. const filesProcessed = filesSuccessful || 0;
  60. const totalProcessed = filesProcessedTotal || 0;
  61. const successRate =
  62. totalProcessed > 0
  63. ? Math.round((filesProcessed / totalProcessed) * 100)
  64. : 0;
  65. const isTaskProcessingActive = taskProcessingStatus?.isProcessing;
  66. return (
  67. <Card>
  68. <div className="flex items-center justify-between">
  69. <div className="flex items-center gap-x-3">
  70. <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-emerald-500/20 ring-1 ring-emerald-500/30">
  71. <svg
  72. className="h-6 w-6 text-emerald-400"
  73. fill="none"
  74. viewBox="0 0 24 24"
  75. strokeWidth="1.5"
  76. stroke="currentColor"
  77. >
  78. <path
  79. strokeLinecap="round"
  80. strokeLinejoin="round"
  81. d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
  82. />
  83. </svg>
  84. </div>
  85. <div>
  86. <div className="text-2xl font-bold text-white">
  87. {taskProcessingLoading ? (
  88. <div className="flex justify-center">
  89. <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
  90. </div>
  91. ) : (
  92. <span
  93. className={
  94. isTaskProcessingActive ? "text-green-400" : "text-red-400"
  95. }
  96. >
  97. {isTaskProcessingActive ? "Active" : "Idle"}
  98. </span>
  99. )}
  100. </div>
  101. <div className="text-sm font-medium text-gray-400">
  102. Task Processing
  103. </div>
  104. <div className="text-xs text-gray-500 mt-1">
  105. {filesSuccessfulLoading || filesProcessedLoading
  106. ? "..."
  107. : `${successRate}% success`}
  108. </div>
  109. </div>
  110. </div>
  111. <div className="flex gap-2">
  112. <button
  113. onClick={() =>
  114. isTaskProcessingActive
  115. ? stopTaskProcessingMutation.mutate()
  116. : startTaskProcessingMutation.mutate()
  117. }
  118. disabled={
  119. taskProcessingLoading ||
  120. startTaskProcessingMutation.isPending ||
  121. stopTaskProcessingMutation.isPending
  122. }
  123. className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
  124. isTaskProcessingActive
  125. ? "bg-red-600 hover:bg-red-700 text-white"
  126. : "bg-green-600 hover:bg-green-700 text-white"
  127. } disabled:opacity-50`}
  128. >
  129. {startTaskProcessingMutation.isPending ||
  130. stopTaskProcessingMutation.isPending
  131. ? "..."
  132. : isTaskProcessingActive
  133. ? "Stop"
  134. : "Start"}
  135. </button>
  136. </div>
  137. </div>
  138. {queueStatus && (
  139. <div className="mt-4 pt-4 border-t border-white/10">
  140. <div className="grid grid-cols-4 gap-2 text-xs">
  141. <div className="text-center">
  142. <div className="text-gray-400">Pending</div>
  143. <div className="text-white font-medium">
  144. {queueStatus.pending || 0}
  145. </div>
  146. </div>
  147. <div className="text-center">
  148. <div className="text-gray-400">Active</div>
  149. <div className="text-white font-medium">
  150. {queueStatus.processing || 0}
  151. </div>
  152. </div>
  153. <div className="text-center">
  154. <div className="text-gray-400">Done</div>
  155. <div className="text-white font-medium">
  156. {queueStatus.completed || 0}
  157. </div>
  158. </div>
  159. <div className="text-center">
  160. <div className="text-gray-400">Failed</div>
  161. <div className="text-white font-medium">
  162. {queueStatus.failed || 0}
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. )}
  168. </Card>
  169. );
  170. }