diff --git a/web/src/features/search/context/index.tsx b/web/src/features/search/context/index.tsx index e2609e3..b2bb4e0 100644 --- a/web/src/features/search/context/index.tsx +++ b/web/src/features/search/context/index.tsx @@ -42,6 +42,8 @@ interface SearchContextType { filter: Record setFilter: React.Dispatch>> handleTagToggle: (tag: string) => void + editTagsOpen: boolean + setEditTagsOpen: (open: boolean) => void } const SearchContext = React.createContext(null) diff --git a/web/src/features/search/edit-tag-dialog.tsx b/web/src/features/search/edit-tag-dialog.tsx index 832dfef..3258a4e 100644 --- a/web/src/features/search/edit-tag-dialog.tsx +++ b/web/src/features/search/edit-tag-dialog.tsx @@ -19,8 +19,8 @@ import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; -import { Command, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; -import { Plus, Tag as TagIcon, X, Loader2, Check } from 'lucide-react'; +import { Input } from '@/components/ui/input'; +import { Plus, Tag as TagIcon, X, Loader2, Check, Search } from 'lucide-react'; import { useState, useEffect } from 'react'; import { useAvailableTags } from '@/hooks/use-available-tags'; import { useUpdateTags } from '@/hooks/use-update-tags'; @@ -29,6 +29,7 @@ import { validateTag } from '@/lib/utils'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from '@tanstack/react-query'; import { useSearchContext } from './context'; +import { ScrollArea } from '@/components/ui/scroll-area'; interface Props { open: boolean @@ -44,7 +45,7 @@ export function EditTagsDialog({ open, onOpenChange }: Props) { const [commandOpen, setCommandOpen] = useState(false); const { t } = useTranslation(); - const { currentEnvelope } = useSearchContext() + const { currentEnvelope, setCurrentEnvelope } = useSearchContext() useEffect(() => { if (open && currentEnvelope) { @@ -111,6 +112,10 @@ export function EditTagsDialog({ open, onOpenChange }: Props) { }, { onSuccess: () => { + const finalTags = inputValue.trim() + ? [...selectedTags, inputValue.toLowerCase().trim()] + : selectedTags; + setCurrentEnvelope(prev => prev ? { ...prev, tags: finalTags } : prev); toast({ title: t('search.addTags.updatedTitle'), description: ( @@ -149,7 +154,7 @@ export function EditTagsDialog({ open, onOpenChange }: Props) {
-
+
{selectedTags.length === 0 ? (

{t('search.addTags.none')}

) : ( @@ -166,60 +171,62 @@ export function EditTagsDialog({ open, onOpenChange }: Props) { )) )}
- e.stopPropagation()}> -
-
- setCommandOpen(true)} - className="h-9 pr-10" - onKeyDown={(e) => { - if (e.key === 'Enter' && inputValue.trim()) { - e.preventDefault(); - e.stopPropagation(); - handleAddTag(inputValue); - } - }} - /> - {inputValue.trim() && ( - - )} -
- {inputValue.trim() && filteredSuggestions.length === 0 && ( -
- {t('search.addTags.createHint', { tag: inputValue })} -
- )} - {commandOpen && inputValue && filteredSuggestions.length > 0 && ( - - - {filteredSuggestions.map(tag => ( - handleAddTag(tag)} - className="cursor-pointer" - > - - {tag} - - ))} - - +
+
+ + { + setInputValue(e.target.value); + setCommandOpen(true); + }} + onFocus={() => setCommandOpen(true)} + className="h-9 pl-9 pr-10" + onKeyDown={(e) => { + if (e.key === 'Enter' && inputValue.trim()) { + e.preventDefault(); + handleAddTag(inputValue); + } + }} + /> + {inputValue.trim() && ( + )}
- + {inputValue.trim() && filteredSuggestions.length === 0 && ( +
+ {t('search.addTags.createHint', { tag: inputValue })} +
+ )} + {commandOpen && inputValue && filteredSuggestions.length > 0 && ( + +
+ {filteredSuggestions.map(tag => ( + + ))} +
+
+ )} +
-
+

{t('search.addTags.selectedCount', { count: selectedTags.length })}

diff --git a/web/src/features/search/index.tsx b/web/src/features/search/index.tsx index 5fad1e5..d954b3d 100644 --- a/web/src/features/search/index.tsx +++ b/web/src/features/search/index.tsx @@ -46,6 +46,7 @@ export default function EmailSearch() { const [sorting, setSorting] = React.useState([{ id: "date", desc: true }]); const [deleteMailboxId, setDeleteMailboxId] = React.useState(undefined); const [selectedAccountId, setSelectedAccountId] = React.useState(undefined); + const [editTagsOpen, setEditTagsOpen] = React.useState(false); const { emails, @@ -98,7 +99,9 @@ export default function EmailSearch() { setDeleteMailboxId, selectedAccountId, setSelectedAccountId, - handleTagToggle + handleTagToggle, + editTagsOpen, + setEditTagsOpen, }} >
@@ -151,8 +154,8 @@ export default function EmailSearch() { setOpen('edit-tags')} + open={editTagsOpen} + onOpenChange={setEditTagsOpen} /> (null); const [contentType, setContentType] = useState<'Plain' | 'Html' | null>(null); const [attachments, setAttachments] = useState(null); @@ -260,6 +262,28 @@ export function MailMessageView({ {formatTimestamp(envelope.internal_date)}
)} +
+ {t('mail.tags')}: +
+ {envelope.tags && envelope.tags.length > 0 ? ( + envelope.tags.map((tag) => ( + + {tag} + + )) + ) : ( + {t('mail.noTagsYet')} + )} + +
+
} {showActions && ( @@ -268,7 +292,7 @@ export function MailMessageView({
- +