| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- // Multi-select component for groups
- import { useMemo } from 'react'
- import useGroups from '@/hooks/useGroups'
- interface GroupMultiSelectProps {
- workspaceId: string
- selectedGroups: string[]
- onChange: (groups: string[]) => void
- disabled?: boolean
- includeSystem?: boolean
- }
- export function GroupMultiSelect({
- workspaceId,
- selectedGroups,
- onChange,
- disabled = false,
- includeSystem = true,
- }: GroupMultiSelectProps) {
- const { groups, isLoading } = useGroups({
- workspaceId,
- includeSystem,
- })
- const handleToggle = (groupId: string) => {
- if (selectedGroups.includes(groupId)) {
- onChange(selectedGroups.filter((id) => id !== groupId))
- } else {
- onChange([...selectedGroups, groupId])
- }
- }
- const handleClear = () => {
- onChange([])
- }
- const selectedGroupNames = useMemo(() => {
- return selectedGroups
- .map((id) => groups.find((g) => g.id === id)?.name)
- .filter(Boolean) as string[]
- }, [selectedGroups, groups])
- if (isLoading) {
- return (
- <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">
- <svg
- className="h-4 w-4 animate-spin"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- 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"
- />
- </svg>
- Loading groups...
- </div>
- )
- }
- return (
- <div className="space-y-2">
- {/* Selected groups display */}
- {selectedGroups.length > 0 && (
- <div className="flex flex-wrap gap-1">
- {selectedGroupNames.map((name, index) => (
- <span
- key={selectedGroups[index]}
- 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"
- >
- {name}
- <button
- type="button"
- onClick={() => handleToggle(selectedGroups[index])}
- disabled={disabled}
- 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"
- >
- <svg className="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
- <path strokeLinecap="round" strokeWidth="1.5" d="M1 1l6 6m0-6L1 7" />
- </svg>
- </button>
- </span>
- ))}
- {selectedGroups.length > 1 && (
- <button
- type="button"
- onClick={handleClear}
- disabled={disabled}
- className="text-xs text-gray-500 hover:text-gray-700"
- >
- Clear all
- </button>
- )}
- </div>
- )}
- {/* Group selection dropdown */}
- <div className="rounded-md border border-gray-300 bg-white">
- <div className="max-h-48 overflow-y-auto">
- {groups.length === 0 ? (
- <div className="px-3 py-2 text-sm text-gray-500">No groups available</div>
- ) : (
- groups.map((group) => (
- <label
- key={group.id}
- className={`flex cursor-pointer items-center gap-2 px-3 py-2 hover:bg-gray-50 ${
- disabled ? 'cursor-not-allowed opacity-50' : ''
- }`}
- >
- <input
- type="checkbox"
- checked={selectedGroups.includes(group.id)}
- onChange={() => handleToggle(group.id)}
- disabled={disabled}
- className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
- />
- <span className="text-sm text-gray-700">{group.name}</span>
- {group.is_system && (
- <span className="rounded bg-purple-100 px-1.5 py-0.5 text-xs text-purple-700">
- System
- </span>
- )}
- </label>
- ))
- )}
- </div>
- </div>
- </div>
- )
- }
- export default GroupMultiSelect
|