tag management

This commit is contained in:
2025-12-04 00:07:26 +01:00
parent 0c5c01a2d2
commit ad0c04bd20

View File

@@ -1,6 +1,6 @@
import type { FaceRegion, Media, MediaMetadata } from "@/domain/types"; import type { FaceRegion, Media, MediaMetadata } from "@/domain/types";
import { useGetMediaDetails } from "@/features/media/use-media"; import { useGetMediaDetails } from "@/features/media/use-media";
import { useListMediaTags } from "@/features/tags/use-tags"; import { useListMediaTags, useAddMediaTags, useRemoveMediaTag } from "@/features/tags/use-tags";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { import {
Accordion, Accordion,
@@ -13,6 +13,10 @@ import { PersonFaceBadge } from "@/components/people/person-face-badge";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { format, parseISO } from "date-fns"; import { format, parseISO } from "date-fns";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Plus, X } from "lucide-react";
type MediaDetailsSidebarProps = { type MediaDetailsSidebarProps = {
media: Media; media: Media;
@@ -42,6 +46,23 @@ export function MediaDetailsSidebar({
media.id media.id
); );
const { data: tags, isLoading: isLoadingTags } = useListMediaTags(media.id); const { data: tags, isLoading: isLoadingTags } = useListMediaTags(media.id);
const addTags = useAddMediaTags(media.id);
const removeTag = useRemoveMediaTag(media.id);
const [newTag, setNewTag] = useState("");
const handleAddTag = () => {
if (!newTag.trim()) return;
addTags.mutate(
{ tags: [newTag.trim()] },
{
onSuccess: () => setNewTag(""),
}
);
};
const handleRemoveTag = (tagName: string) => {
removeTag.mutate(tagName);
};
const displayDate = media.date_taken const displayDate = media.date_taken
? format(parseISO(media.date_taken), "MMMM d, yyyy 'at' h:mm a") ? format(parseISO(media.date_taken), "MMMM d, yyyy 'at' h:mm a")
@@ -102,14 +123,35 @@ export function MediaDetailsSidebar({
<AccordionItem value="tags"> <AccordionItem value="tags">
<AccordionTrigger>Tags</AccordionTrigger> <AccordionTrigger>Tags</AccordionTrigger>
<AccordionContent> <AccordionContent className="space-y-4">
{/* TODO: Add input to add tags */} <div className="flex gap-2">
<Input
placeholder="Add tag..."
value={newTag}
onChange={(e) => setNewTag(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleAddTag();
}}
className="h-8"
/>
<Button size="sm" variant="outline" onClick={handleAddTag} disabled={!newTag.trim() || addTags.isPending} className="h-8 w-8 p-0">
<Plus size={16} />
</Button>
</div>
{isLoadingTags && <Skeleton className="h-8 w-full" />} {isLoadingTags && <Skeleton className="h-8 w-full" />}
{tags && tags.length > 0 && ( {tags && tags.length > 0 && (
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{tags.map((tag) => ( {tags.map((tag) => (
<Badge key={tag.id} variant="secondary"> <Badge key={tag.id} variant="secondary" className="pr-1 gap-1">
{tag.name} {tag.name}
<button
onClick={() => handleRemoveTag(tag.name)}
className="hover:bg-muted rounded-full p-0.5 transition-colors"
disabled={removeTag.isPending}
>
<X size={12} />
</button>
</Badge> </Badge>
))} ))}
</div> </div>