GroupMultiSelect.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. // Multi-select component for groups
  2. import { useMemo } from 'react'
  3. import useGroups from '@/hooks/useGroups'
  4. interface GroupMultiSelectProps {
  5. workspaceId: string
  6. selectedGroups: string[]
  7. onChange: (groups: string[]) => void
  8. disabled?: boolean
  9. includeSystem?: boolean
  10. }
  11. export function GroupMultiSelect({
  12. workspaceId,
  13. selectedGroups,
  14. onChange,
  15. disabled = false,
  16. includeSystem = true,
  17. }: GroupMultiSelectProps) {
  18. const { groups, isLoading } = useGroups({
  19. workspaceId,
  20. includeSystem,
  21. })
  22. const handleToggle = (groupId: string) => {
  23. if (selectedGroups.includes(groupId)) {
  24. onChange(selectedGroups.filter((id) => id !== groupId))
  25. } else {
  26. onChange([...selectedGroups, groupId])
  27. }
  28. }
  29. const handleClear = () => {
  30. onChange([])
  31. }
  32. const selectedGroupNames = useMemo(() => {
  33. return selectedGroups
  34. .map((id) => groups.find((g) => g.id === id)?.name)
  35. .filter(Boolean) as string[]
  36. }, [selectedGroups, groups])
  37. if (isLoading) {
  38. return (
  39. <div className="flex items-center gap-2 rounded-md border border-gray-300 bg-gray-50 px-3 py-2 text-sm text-gray-500">
  40. <svg
  41. className="h-4 w-4 animate-spin"
  42. xmlns="http://www.w3.org/2000/svg"
  43. fill="none"
  44. viewBox="0 0 24 24"
  45. >
  46. <circle
  47. className="opacity-25"
  48. cx="12"
  49. cy="12"
  50. r="10"
  51. stroke="currentColor"
  52. strokeWidth="4"
  53. />
  54. <path
  55. className="opacity-75"
  56. fill="currentColor"
  57. d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
  58. />
  59. </svg>
  60. Loading groups...
  61. </div>
  62. )
  63. }
  64. return (
  65. <div className="space-y-2">
  66. {/* Selected groups display */}
  67. {selectedGroups.length > 0 && (
  68. <div className="flex flex-wrap gap-1">
  69. {selectedGroupNames.map((name, index) => (
  70. <span
  71. key={selectedGroups[index]}
  72. className="inline-flex items-center gap-1 rounded-full bg-indigo-100 px-2.5 py-0.5 text-xs font-medium text-indigo-800"
  73. >
  74. {name}
  75. <button
  76. type="button"
  77. onClick={() => handleToggle(selectedGroups[index])}
  78. disabled={disabled}
  79. className="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full text-indigo-600 hover:bg-indigo-200 hover:text-indigo-900 focus:bg-indigo-500 focus:text-white focus:outline-none"
  80. >
  81. <svg className="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
  82. <path strokeLinecap="round" strokeWidth="1.5" d="M1 1l6 6m0-6L1 7" />
  83. </svg>
  84. </button>
  85. </span>
  86. ))}
  87. {selectedGroups.length > 1 && (
  88. <button
  89. type="button"
  90. onClick={handleClear}
  91. disabled={disabled}
  92. className="text-xs text-gray-500 hover:text-gray-700"
  93. >
  94. Clear all
  95. </button>
  96. )}
  97. </div>
  98. )}
  99. {/* Group selection dropdown */}
  100. <div className="rounded-md border border-gray-300 bg-white">
  101. <div className="max-h-48 overflow-y-auto">
  102. {groups.length === 0 ? (
  103. <div className="px-3 py-2 text-sm text-gray-500">No groups available</div>
  104. ) : (
  105. groups.map((group) => (
  106. <label
  107. key={group.id}
  108. className={`flex cursor-pointer items-center gap-2 px-3 py-2 hover:bg-gray-50 ${
  109. disabled ? 'cursor-not-allowed opacity-50' : ''
  110. }`}
  111. >
  112. <input
  113. type="checkbox"
  114. checked={selectedGroups.includes(group.id)}
  115. onChange={() => handleToggle(group.id)}
  116. disabled={disabled}
  117. className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
  118. />
  119. <span className="text-sm text-gray-700">{group.name}</span>
  120. {group.is_system && (
  121. <span className="rounded bg-purple-100 px-1.5 py-0.5 text-xs text-purple-700">
  122. System
  123. </span>
  124. )}
  125. </label>
  126. ))
  127. )}
  128. </div>
  129. </div>
  130. </div>
  131. )
  132. }
  133. export default GroupMultiSelect