feat: implement label management with color filtering
This commit is contained in:
@@ -11,28 +11,31 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Filter, X } from 'lucide-react'
|
||||
import { getAllLabelColors, getLabelColor } from '@/lib/label-storage'
|
||||
import { Filter } from 'lucide-react'
|
||||
import { LABEL_COLORS } from '@/lib/types'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useLabels } from '@/context/LabelContext'
|
||||
import { LabelBadge } from './label-badge'
|
||||
|
||||
interface LabelFilterProps {
|
||||
selectedLabels: string[]
|
||||
selectedColor?: string | null
|
||||
onFilterChange: (labels: string[]) => void
|
||||
onColorChange?: (color: string | null) => void
|
||||
}
|
||||
|
||||
export function LabelFilter({ selectedLabels, onFilterChange }: LabelFilterProps) {
|
||||
const [allLabels, setAllLabels] = useState<string[]>([])
|
||||
export function LabelFilter({ selectedLabels, selectedColor, onFilterChange, onColorChange }: LabelFilterProps) {
|
||||
const { labels, loading, getLabelColor } = useLabels()
|
||||
const [allLabelNames, setAllLabelNames] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
// Load all labels from localStorage
|
||||
const labelColors = getAllLabelColors()
|
||||
setAllLabels(Object.keys(labelColors).sort())
|
||||
}, [])
|
||||
// Extract label names from labels array
|
||||
setAllLabelNames(labels.map((l: any) => l.name).sort())
|
||||
}, [labels])
|
||||
|
||||
const handleToggleLabel = (label: string) => {
|
||||
if (selectedLabels.includes(label)) {
|
||||
onFilterChange(selectedLabels.filter(l => l !== label))
|
||||
onFilterChange(selectedLabels.filter((l: string) => l !== label))
|
||||
} else {
|
||||
onFilterChange([...selectedLabels, label])
|
||||
}
|
||||
@@ -40,9 +43,18 @@ export function LabelFilter({ selectedLabels, onFilterChange }: LabelFilterProps
|
||||
|
||||
const handleClearAll = () => {
|
||||
onFilterChange([])
|
||||
onColorChange?.(null)
|
||||
}
|
||||
|
||||
if (allLabels.length === 0) return null
|
||||
const handleColorFilter = (color: string) => {
|
||||
if (selectedColor === color) {
|
||||
onColorChange?.(null)
|
||||
} else {
|
||||
onColorChange?.(color)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || allLabelNames.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -58,9 +70,9 @@ export function LabelFilter({ selectedLabels, onFilterChange }: LabelFilterProps
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-64">
|
||||
<DropdownMenuContent align="end" className="w-80">
|
||||
<DropdownMenuLabel className="flex items-center justify-between">
|
||||
Filter by Labels
|
||||
<span>Filter by Labels</span>
|
||||
{selectedLabels.length > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -73,27 +85,54 @@ export function LabelFilter({ selectedLabels, onFilterChange }: LabelFilterProps
|
||||
)}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{allLabels.map((label) => {
|
||||
const colorName = getLabelColor(label)
|
||||
const colorClasses = LABEL_COLORS[colorName]
|
||||
const isSelected = selectedLabels.includes(label)
|
||||
|
||||
{/* Color Filter */}
|
||||
<div className="p-2">
|
||||
<p className="text-xs font-medium mb-2 text-gray-600 dark:text-gray-400">Filter by Color</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Object.entries(LABEL_COLORS).map(([colorName, colorClasses]) => {
|
||||
const isSelected = selectedColor === colorName
|
||||
const labelCount = labels.filter((l: any) => l.color === colorName).length
|
||||
|
||||
return (
|
||||
<button
|
||||
key={colorName}
|
||||
onClick={() => handleColorFilter(colorName)}
|
||||
className={cn(
|
||||
'flex items-center gap-2 p-2 rounded-lg border-2 transition-all hover:scale-105',
|
||||
isSelected ? 'ring-2 ring-blue-500' : 'border-gray-300 dark:border-gray-600'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'w-6 h-6 rounded-full border-2',
|
||||
colorClasses.bg,
|
||||
isSelected ? 'border-blue-500 dark:border-blue-400' : colorClasses.border
|
||||
)}
|
||||
/>
|
||||
<span className="text-xs ml-2">{labelCount}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{/* Label Filters */}
|
||||
{!loading && allLabelNames.map((labelName: string) => {
|
||||
const isSelected = selectedLabels.includes(labelName)
|
||||
const isColorFiltered = selectedColor && selectedColor !== 'gray'
|
||||
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={label}
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => handleToggleLabel(label)}
|
||||
key={labelName}
|
||||
checked={isSelected && !isColorFiltered}
|
||||
onCheckedChange={() => handleToggleLabel(labelName)}
|
||||
>
|
||||
<Badge
|
||||
className={cn(
|
||||
'text-xs border mr-2',
|
||||
colorClasses.bg,
|
||||
colorClasses.text,
|
||||
colorClasses.border
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Badge>
|
||||
<LabelBadge
|
||||
label={labelName}
|
||||
isDisabled={!!isColorFiltered}
|
||||
/>
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
})}
|
||||
@@ -101,28 +140,16 @@ export function LabelFilter({ selectedLabels, onFilterChange }: LabelFilterProps
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Active filters display */}
|
||||
{selectedLabels.length > 0 && (
|
||||
{!loading && selectedLabels.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedLabels.map((label) => {
|
||||
const colorName = getLabelColor(label)
|
||||
const colorClasses = LABEL_COLORS[colorName]
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={label}
|
||||
className={cn(
|
||||
'text-xs border cursor-pointer pr-1',
|
||||
colorClasses.bg,
|
||||
colorClasses.text,
|
||||
colorClasses.border
|
||||
)}
|
||||
onClick={() => handleToggleLabel(label)}
|
||||
>
|
||||
{label}
|
||||
<X className="h-3 w-3 ml-1" />
|
||||
</Badge>
|
||||
)
|
||||
})}
|
||||
{selectedLabels.map((labelName: string) => (
|
||||
<LabelBadge
|
||||
key={labelName}
|
||||
label={labelName}
|
||||
variant="filter"
|
||||
onClick={() => handleToggleLabel(labelName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user