- Created ItemForm component with fields for all metadata (priority, due date, tags, notes) - Created ItemDetail component to display all item information - Built /list/[id]/item/[itemId] page for viewing full item details - Built /list/[id]/item/[itemId]/edit page for editing items - Added form validation for item inputs (title required, length limits) - Created DeleteItemButton and ToggleItemButton helper components - Enhanced createItem action to support full metadata on creation - Added getItem action to fetch individual items with list data Requirements: 1.1, 1.2, 5.1, 5.2, 5.3, 5.4
164 lines
5.1 KiB
TypeScript
164 lines
5.1 KiB
TypeScript
import { Calendar, Tag, FileText, AlertCircle, CheckCircle2, Circle } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
|
|
interface ItemDetailProps {
|
|
item: {
|
|
id: string
|
|
title: string
|
|
description: string | null
|
|
completed: boolean
|
|
priority: string
|
|
dueDate: Date | null
|
|
tags: any
|
|
notes: string | null
|
|
createdAt: Date
|
|
updatedAt: Date
|
|
listId: string
|
|
}
|
|
listTitle: string
|
|
}
|
|
|
|
export default function ItemDetail({ item, listTitle }: ItemDetailProps) {
|
|
const tags = Array.isArray(item.tags) ? item.tags : []
|
|
const isOverdue = item.dueDate && new Date(item.dueDate) < new Date() && !item.completed
|
|
|
|
const priorityColors = {
|
|
low: 'bg-green-100 text-green-800',
|
|
medium: 'bg-yellow-100 text-yellow-800',
|
|
high: 'bg-red-100 text-red-800',
|
|
}
|
|
|
|
const priorityColor = priorityColors[item.priority as keyof typeof priorityColors] || priorityColors.medium
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div>
|
|
<div className="flex items-center gap-2 text-sm text-gray-500 mb-2">
|
|
<Link href={`/list/${item.listId}`} className="hover:text-blue-600">
|
|
{listTitle}
|
|
</Link>
|
|
<span>/</span>
|
|
<span>Task Details</span>
|
|
</div>
|
|
<div className="flex items-start gap-3">
|
|
<div className="pt-1">
|
|
{item.completed ? (
|
|
<CheckCircle2 className="text-green-500" size={24} />
|
|
) : (
|
|
<Circle className="text-gray-400" size={24} />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<h1 className={`text-2xl font-bold ${item.completed ? 'text-gray-500 line-through' : 'text-gray-900'}`}>
|
|
{item.title}
|
|
</h1>
|
|
{item.completed && (
|
|
<p className="text-sm text-green-600 mt-1">Completed</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Metadata Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Priority */}
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2 text-sm text-gray-600 mb-1">
|
|
<AlertCircle size={16} />
|
|
<span className="font-medium">Priority</span>
|
|
</div>
|
|
<span className={`inline-block px-3 py-1 rounded-full text-sm font-medium ${priorityColor}`}>
|
|
{item.priority.charAt(0).toUpperCase() + item.priority.slice(1)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Due Date */}
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2 text-sm text-gray-600 mb-1">
|
|
<Calendar size={16} />
|
|
<span className="font-medium">Due Date</span>
|
|
</div>
|
|
{item.dueDate ? (
|
|
<div>
|
|
<p className={`text-gray-900 ${isOverdue ? 'text-red-600 font-semibold' : ''}`}>
|
|
{new Date(item.dueDate).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})}
|
|
</p>
|
|
{isOverdue && (
|
|
<p className="text-sm text-red-600 mt-1">Overdue</p>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-500">No due date</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
{item.description && (
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<h2 className="text-sm font-medium text-gray-600 mb-2">Description</h2>
|
|
<p className="text-gray-900 whitespace-pre-wrap">{item.description}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tags */}
|
|
{tags.length > 0 && (
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2 text-sm font-medium text-gray-600 mb-3">
|
|
<Tag size={16} />
|
|
<span>Tags</span>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{tags.map((tag: string) => (
|
|
<span
|
|
key={tag}
|
|
className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notes */}
|
|
{item.notes && (
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2 text-sm font-medium text-gray-600 mb-3">
|
|
<FileText size={16} />
|
|
<span>Notes</span>
|
|
</div>
|
|
<p className="text-gray-900 whitespace-pre-wrap">{item.notes}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Timestamps */}
|
|
<div className="border-t pt-4 text-sm text-gray-500 space-y-1">
|
|
<p>
|
|
Created: {new Date(item.createdAt).toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})}
|
|
</p>
|
|
<p>
|
|
Updated: {new Date(item.updatedAt).toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|