Files
thrilltrack-explorer/src-old/components/lists/ListItemEditor.tsx

231 lines
6.9 KiB
TypeScript

import { useState, useEffect } from "react";
import { UserTopList, UserTopListItem } from "@/types/database";
import { supabase } from "@/lib/supabaseClient";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { GripVertical, Trash2, Plus } from "lucide-react";
import { toast } from "sonner";
import { ListSearch } from "./ListSearch";
import { getErrorMessage } from "@/lib/errorHandler";
interface ListItemEditorProps {
list: UserTopList;
onUpdate: () => void;
onClose: () => void;
}
export function ListItemEditor({ list, onUpdate, onClose }: ListItemEditorProps) {
const [items, setItems] = useState<UserTopListItem[]>([]);
const [loading, setLoading] = useState(true);
const [showSearch, setShowSearch] = useState(false);
useEffect(() => {
fetchItems();
}, [list.id]);
const fetchItems = async () => {
setLoading(true);
const { data, error } = await supabase
.from("user_top_list_items")
.select("*")
.eq("list_id", list.id)
.order("position", { ascending: true });
if (error) {
const errorMessage = getErrorMessage(error);
toast.error("Failed to load list items", {
description: errorMessage
});
} else {
setItems(data as UserTopListItem[]);
}
setLoading(false);
};
const handleAddItem = async (entityType: string, entityId: string, entityName: string) => {
const newPosition = items.length + 1;
const { error } = await supabase
.from("user_top_list_items")
.insert({
list_id: list.id,
entity_type: entityType,
entity_id: entityId,
position: newPosition,
});
if (error) {
if (error.code === "23505") {
toast.error("This item is already in your list");
} else {
const errorMessage = getErrorMessage(error);
toast.error("Failed to add item", {
description: errorMessage
});
}
} else {
toast.success(`Added ${entityName} to list`);
fetchItems();
onUpdate();
setShowSearch(false);
}
};
const handleRemoveItem = async (itemId: string) => {
const { error } = await supabase
.from("user_top_list_items")
.delete()
.eq("id", itemId);
if (error) {
const errorMessage = getErrorMessage(error);
toast.error("Failed to remove item", {
description: errorMessage
});
} else {
toast.success("Item removed");
// Reorder remaining items
const remainingItems = items.filter(i => i.id !== itemId);
await reorderItems(remainingItems);
fetchItems();
onUpdate();
}
};
const handleUpdateNotes = async (itemId: string, notes: string) => {
const { error } = await supabase
.from("user_top_list_items")
.update({ notes })
.eq("id", itemId);
if (error) {
const errorMessage = getErrorMessage(error);
toast.error("Failed to update notes", {
description: errorMessage
});
} else {
setItems(items.map(i => i.id === itemId ? { ...i, notes } : i));
}
};
const handleMoveItem = async (itemId: string, direction: "up" | "down") => {
const currentIndex = items.findIndex(i => i.id === itemId);
if (
(direction === "up" && currentIndex === 0) ||
(direction === "down" && currentIndex === items.length - 1)
) {
return;
}
const newItems = [...items];
const swapIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1;
[newItems[currentIndex], newItems[swapIndex]] = [newItems[swapIndex], newItems[currentIndex]];
await reorderItems(newItems);
setItems(newItems);
onUpdate();
};
const reorderItems = async (orderedItems: UserTopListItem[]) => {
const updates = orderedItems.map((item, index) => ({
id: item.id,
position: index + 1,
}));
for (const update of updates) {
await supabase
.from("user_top_list_items")
.update({ position: update.position })
.eq("id", update.id);
}
};
if (loading) {
return <div className="text-center py-4">Loading items...</div>;
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="font-semibold">Edit List Items</h3>
<div className="space-x-2">
<Button size="sm" onClick={() => setShowSearch(!showSearch)}>
<Plus className="h-4 w-4 mr-2" />
Add Item
</Button>
<Button size="sm" variant="outline" onClick={onClose}>
Done
</Button>
</div>
</div>
{showSearch && (
<ListSearch
listType={list.list_type}
onSelect={handleAddItem}
onClose={() => setShowSearch(false)}
/>
)}
{items.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
No items in this list yet. Click "Add Item" to get started.
</div>
) : (
<div className="space-y-2">
{items.map((item, index) => (
<div
key={item.id}
className="flex items-start gap-2 p-3 border rounded-lg bg-card"
>
<div className="flex flex-col gap-1 mt-1">
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => handleMoveItem(item.id, "up")}
disabled={index === 0}
>
<GripVertical className="h-4 w-4" />
</Button>
<span className="text-xs text-muted-foreground text-center">
{index + 1}
</span>
</div>
<div className="flex-1 space-y-2">
<div className="flex justify-between items-start">
<div>
<p className="font-medium">{item.entity_type} - {item.entity_id}</p>
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleRemoveItem(item.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div>
<Label htmlFor={`notes-${item.id}`} className="text-xs">
Notes (optional)
</Label>
<Textarea
id={`notes-${item.id}`}
value={item.notes || ""}
onChange={(e) => handleUpdateNotes(item.id, e.target.value)}
placeholder="Add personal notes about this item..."
className="h-16 text-sm"
/>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
}