import { store } from '@/actions/App/Http/Controllers/Settings/LabelController'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Command, CommandEmpty, CommandInput, CommandItem, CommandList, } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; import { getLabelColorClasses, LABEL_COLORS, type Label } from '@/types/label'; import { Check, ChevronsUpDown, Plus, Tag, X } from 'lucide-react'; import { useState } from 'react'; interface LabelComboboxProps { value: string[]; onValueChange: (value: string[]) => void; labels: Label[]; disabled?: boolean; placeholder?: string; triggerClassName?: string; allowCreate?: boolean; allowRemoveAll?: boolean; onLabelCreated?: (label: Label) => void; } export function LabelCombobox({ value, onValueChange, labels = [], disabled = false, placeholder = 'Add labels...', triggerClassName, allowCreate = true, allowRemoveAll = false, onLabelCreated, }: LabelComboboxProps) { const [open, setOpen] = useState(false); const [inputValue, setInputValue] = useState(''); const [isCreating, setIsCreating] = useState(false); const selectedLabels = labels.filter((l) => value.includes(l.id)); const sortedLabels = [...labels].sort((a, b) => a.name.localeCompare(b.name), ); const handleSelect = (labelId: string) => { if (value.includes(labelId)) { onValueChange(value.filter((id) => id !== labelId)); } else { onValueChange([...value, labelId]); } }; const handleRemove = (labelId: string, e: React.MouseEvent) => { e.stopPropagation(); onValueChange(value.filter((id) => id !== labelId)); }; const handleCreate = async () => { if (!inputValue.trim() || isCreating) return; const existingLabel = labels.find( (l) => l.name.toLowerCase() === inputValue.toLowerCase(), ); if (existingLabel) { handleSelect(existingLabel.id); setInputValue(''); return; } setIsCreating(true); try { const randomColor = LABEL_COLORS[Math.floor(Math.random() * LABEL_COLORS.length)]; const response = await fetch(store.url(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': decodeURIComponent( document.cookie .split('; ') .find((row) => row.startsWith('XSRF-TOKEN=')) ?.split('=')[1] || '', ), Accept: 'application/json', }, body: JSON.stringify({ name: inputValue.trim(), color: randomColor, }), }); if (!response.ok) { throw new Error('Failed to create label'); } const data = await response.json(); const newLabel = data.data || data; if (newLabel) { onValueChange([...value, newLabel.id]); setInputValue(''); onLabelCreated?.(newLabel); } } catch (error) { console.error('Failed to create label:', error); } finally { setIsCreating(false); } }; const showCreateOption = allowCreate && inputValue.trim() && !labels.some((l) => l.name.toLowerCase() === inputValue.toLowerCase()); return ( {sortedLabels.length === 0 && !showCreateOption && ( No labels found. )} {allowRemoveAll && ( { onValueChange([]); setOpen(false); }} className="gap-2" > Remove all labels )} {showCreateOption && ( {isCreating ? 'Creating...' : `Create "${inputValue.trim()}"`} )} {sortedLabels .filter( (label) => !inputValue || label.name .toLowerCase() .includes(inputValue.toLowerCase()), ) .map((label) => { const colorClasses = getLabelColorClasses( label.color, ); const isSelected = value.includes(label.id); return ( handleSelect(label.id)} >
{label.name}
); })}
); } export function LabelBadge({ label }: { label: Label }) { const colorClasses = getLabelColorClasses(label.color); return ( {label.name} ); } export function LabelBadges({ labels, max = 3, }: { labels: Label[]; max?: number; }) { if (!labels || labels.length === 0) return null; const displayLabels = labels.slice(0, max); const remainingCount = labels.length - max; return (
{displayLabels.map((label) => ( ))} {remainingCount > 0 && ( +{remainingCount} )}
); }