GroupCard.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { useState } from 'react'
  2. import { Folder, MoreVertical, Pencil, Trash2, FolderInput } from 'lucide-react'
  3. import { WorkflowGroup } from '../api/workflowGroups'
  4. import { formatDistanceToNow } from 'date-fns'
  5. interface GroupCardProps {
  6. group: WorkflowGroup
  7. onClick: () => void
  8. onRename: () => void
  9. onMove: () => void
  10. onDelete: () => void
  11. }
  12. // Helper to safely format timestamps
  13. function safeFormatDistanceToNow(timestamp: number): string {
  14. if (!timestamp || timestamp < 0 || timestamp > 8640000000000000) {
  15. return 'unknown'
  16. }
  17. try {
  18. return formatDistanceToNow(timestamp, { addSuffix: true })
  19. } catch {
  20. return 'unknown'
  21. }
  22. }
  23. export function GroupCard({ group, onClick, onRename, onMove, onDelete }: GroupCardProps) {
  24. const [showMenu, setShowMenu] = useState(false)
  25. return (
  26. <div className="bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700 p-4 hover:shadow-md transition-shadow">
  27. <div className="flex items-start justify-between mb-3">
  28. <button
  29. onClick={onClick}
  30. className="flex-1 flex items-start gap-3 text-left"
  31. >
  32. <div className="p-2 bg-amber-100 dark:bg-amber-900/30 rounded-lg">
  33. <Folder className="w-5 h-5 text-amber-600 dark:text-amber-400" />
  34. </div>
  35. <div className="flex-1 min-w-0">
  36. <h3 className="font-semibold text-gray-900 dark:text-gray-100 hover:text-primary-600 dark:hover:text-primary-400 truncate">
  37. {group.name}
  38. </h3>
  39. {group.description && (
  40. <p className="text-sm text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">
  41. {group.description}
  42. </p>
  43. )}
  44. </div>
  45. </button>
  46. <div className="relative flex-shrink-0">
  47. <button
  48. onClick={(e) => {
  49. e.stopPropagation()
  50. setShowMenu(!showMenu)
  51. }}
  52. className="p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 rounded"
  53. >
  54. <MoreVertical className="w-5 h-5" />
  55. </button>
  56. {showMenu && (
  57. <>
  58. <div
  59. className="fixed inset-0 z-10"
  60. onClick={() => setShowMenu(false)}
  61. />
  62. <div className="absolute right-0 mt-1 w-40 bg-white dark:bg-slate-800 rounded-lg shadow-lg border border-gray-200 dark:border-slate-700 z-20">
  63. <button
  64. onClick={(e) => {
  65. e.stopPropagation()
  66. onRename()
  67. setShowMenu(false)
  68. }}
  69. className="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-slate-700 flex items-center gap-2"
  70. >
  71. <Pencil className="w-4 h-4" /> Rename
  72. </button>
  73. <button
  74. onClick={(e) => {
  75. e.stopPropagation()
  76. onMove()
  77. setShowMenu(false)
  78. }}
  79. className="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-slate-700 flex items-center gap-2"
  80. >
  81. <FolderInput className="w-4 h-4" /> Move
  82. </button>
  83. <button
  84. onClick={(e) => {
  85. e.stopPropagation()
  86. onDelete()
  87. setShowMenu(false)
  88. }}
  89. className="w-full px-4 py-2 text-left text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 flex items-center gap-2"
  90. >
  91. <Trash2 className="w-4 h-4" /> Delete
  92. </button>
  93. </div>
  94. </>
  95. )}
  96. </div>
  97. </div>
  98. <div className="flex items-center justify-end text-sm">
  99. <span className="text-gray-400 dark:text-gray-500">
  100. {safeFormatDistanceToNow(group.updatedAt)}
  101. </span>
  102. </div>
  103. </div>
  104. )
  105. }