import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { MonitoredFolder } from "@/types/monitored-folder"
import { Check, Command, Loader2, X } from "lucide-react"
import { useEffect, useMemo, useRef, useState } from "react"
interface TagDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
onSave: (tags: MonitoredFolder[]) => Promise<void>
tags: MonitoredFolder[]
defaultTags: MonitoredFolder[]
}
export function TagDialog({ open, onOpenChange, onSave, tags, defaultTags }: TagDialogProps) {
const [selectedTags, setSelectedTags] = useState<MonitoredFolder[]>(defaultTags)
const [inputValue, setInputValue] = useState("")
const [showDropdown, setShowDropdown] = useState(false)
const [isSaving, setIsSaving] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const dropdownRef = useRef<HTMLDivElement>(null)
const filteredTags = useMemo(() => {
return tags.filter((tag) => tag.folder_name.toLowerCase().includes(inputValue.toLowerCase()))
}, [tags, inputValue, selectedTags])
const handleSelectTag = (tag: MonitoredFolder) => {
if (selectedTags.some((t) => t.id === tag.id)) {
setSelectedTags(selectedTags.filter((t) => t.id !== tag.id))
} else {
setSelectedTags([...selectedTags, tag])
}
setInputValue("")
inputRef.current?.focus()
}
const handleRemoveTag = (tagToRemove: MonitoredFolder) => {
setSelectedTags(selectedTags.filter((tag) => tag.id !== tagToRemove.id))
}
const handleSave = async () => {
setIsSaving(true)
try {
await onSave(selectedTags)
onOpenChange(false)
} finally {
setIsSaving(false)
}
}
// 点击外部关闭下拉框
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setShowDropdown(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [])
useEffect(() => {
setSelectedTags(defaultTags)
}, [open])
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className="max-w-md"
onOpenAutoFocus={(e) => e.preventDefault()}
onPointerDownOutside={(e) => e.preventDefault()}
>
<DialogHeader>
<DialogTitle>Select Tags</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="relative">
<div
className="min-h-[40px] w-full border border-slate-200 rounded-md px-3 py-2 focus-within:border-slate-300 transition-colors duration-200"
onClick={() => inputRef.current?.focus()}
>
<div className="flex flex-wrap gap-1.5 items-center">
{selectedTags.map((tag) => (
<Badge key={tag.id} variant="secondary" className="gap-1 h-6 px-2">
{tag.folder_name}
<X
className="h-3 w-3 cursor-pointer hover:text-destructive"
onClick={(e) => {
e.stopPropagation()
handleRemoveTag(tag)
}}
/>
</Badge>
))}
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value)
setShowDropdown(true)
}}
onFocus={() => setShowDropdown(true)}
className="flex-1 outline-none min-w-[120px] bg-transparent"
placeholder={selectedTags.length === 0 ? "Please select tags..." : ""}
/>
</div>
</div>
{showDropdown && (
<div
ref={dropdownRef}
className="absolute z-50 w-full mt-1 bg-white border rounded-md shadow-lg max-h-[200px] overflow-y-auto"
>
{filteredTags.length > 0 ? (
filteredTags.map((tag) => {
const isSelected = selectedTags.includes(tag)
return (
<div
key={tag.id}
className={`
flex items-center justify-between px-3 py-2 cursor-pointer
hover:bg-slate-50 text-sm
${isSelected ? "text-blue-600" : ""}
`}
onClick={() => handleSelectTag(tag)}
>
<span>{tag.folder_name}</span>
{isSelected && <Check className="h-4 w-4 text-blue-600" />}
</div>
)
})
) : (
<div className="flex flex-col items-center justify-center py-6 text-sm text-slate-500">
<Command className="h-10 w-10 text-slate-300 mb-2" />
<p>No matching tags</p>
{inputValue && <p className="text-xs">Try other keywords</p>}
</div>
)}
</div>
)}
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={handleSave} disabled={isSaving}>
{isSaving ? (
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
Saving...
</div>
) : (
"Save"
)}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)
}