refactor events list as shadcn cards
This commit is contained in:
163
src/app/page.tsx
163
src/app/page.tsx
@@ -5,7 +5,10 @@ import { nanoid } from 'nanoid'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { LucideMapPin, Clock, MoreHorizontal, Calendar1Icon } from 'lucide-react'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { RecurrencePicker } from '@/components/recurrence-picker'
|
||||
import { IcsFilePicker } from '@/components/ics-file-picker'
|
||||
|
||||
@@ -16,6 +19,111 @@ import { Textarea } from '@/components/ui/textarea'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
// Individual event card component
|
||||
const EventCard = ({ event, onEdit, onDelete }: { event: CalendarEvent, onEdit: (event: CalendarEvent) => void, onDelete: (eventId: string) => void }) => {
|
||||
const formatDateTime = (dateStr: string, allDay: boolean | undefined) => {
|
||||
return allDay
|
||||
? new Date(dateStr).toLocaleDateString()
|
||||
: new Date(dateStr).toLocaleString()
|
||||
}
|
||||
|
||||
const handleEdit = () => {
|
||||
onEdit({
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
description: event.description || '',
|
||||
location: event.location || '',
|
||||
url: event.url || '',
|
||||
start: event.start,
|
||||
end: event.end || '',
|
||||
allDay: event.allDay || false
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1 flex-1">
|
||||
<h3 className="font-semibold leading-none tracking-tight">
|
||||
{event.title}
|
||||
</h3>
|
||||
{event.recurrenceRule && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Repeats: {event.recurrenceRule}
|
||||
</Badge>
|
||||
)}
|
||||
{event.description && (
|
||||
<p className="text-sm text-muted-foreground mt-2 break-words">
|
||||
{event.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={handleEdit}>
|
||||
Edit event
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(event.id)}
|
||||
className="text-destructive"
|
||||
>
|
||||
Delete event
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<Clock className="mr-2 h-4 w-4" />
|
||||
{formatDateTime(event.start, event.allDay)}
|
||||
</div>
|
||||
|
||||
{event.location && (
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<LucideMapPin className="mr-2 h-4 w-4" />
|
||||
{event.location}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const EventsList = ({ events, onEdit, onDelete }: { events: CalendarEvent[], onEdit: (event: CalendarEvent) => void, onDelete: (eventId: string) => void }) => {
|
||||
if (events.length === 0) {
|
||||
|
||||
return (<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||
<Calendar1Icon className='h-12 w-12 text-muted-foreground mb-4' />
|
||||
<h3 className="text-lg font-medium text-muted-foreground">No events yet</h3>
|
||||
<p className="text-sm text-muted-foreground">Create your first event to get started</p>
|
||||
</div>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{events.map(event => (
|
||||
<EventCard
|
||||
key={event.id}
|
||||
event={event}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const [events, setEvents] = useState<CalendarEvent[]>([])
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
@@ -321,41 +429,24 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
{/* Event List */}
|
||||
{events.length === 0 && <p className="text-gray-500 italic">No events yet</p>}
|
||||
<ul className="space-y-2">
|
||||
{events.map(ev => (
|
||||
<li key={ev.id} className="p-3 border rounded flex justify-between items-start">
|
||||
<div>
|
||||
<div className="font-semibold">{ev.title}</div>
|
||||
{ev.recurrenceRule && (
|
||||
<div className="text-xs text-blue-600 mt-1">
|
||||
Repeats: {ev.recurrenceRule}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm text-gray-500">
|
||||
{ev.allDay ? ev.start.split('T')[0] : new Date(ev.start).toLocaleString()}
|
||||
{ev.location && <span> @ {ev.location}</span>}
|
||||
</div>
|
||||
{ev.description && <div className="text-sm mt-1 wrap-anywhere">{ev.description}</div>}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onClick={() => {
|
||||
setTitle(ev.title)
|
||||
setDescription(ev.description || '')
|
||||
setLocation(ev.location || '')
|
||||
setUrl(ev.url || '')
|
||||
setStart(ev.start)
|
||||
setEnd(ev.end || '')
|
||||
setAllDay(ev.allDay || false)
|
||||
setEditingId(ev.id)
|
||||
setDialogOpen(true)
|
||||
}}>Edit</Button>
|
||||
<Button variant="secondary" size="sm" onClick={() => handleDelete(ev.id)}>Delete</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{events.length === 0 && (
|
||||
<>
|
||||
</>)}
|
||||
<EventsList
|
||||
events={events}
|
||||
onEdit={(eventData) => {
|
||||
setTitle(eventData.title)
|
||||
setDescription(eventData.description || "")
|
||||
setLocation(eventData.location || "")
|
||||
setUrl(eventData.url || "")
|
||||
setStart(eventData.start)
|
||||
setEnd(eventData.end || "")
|
||||
setAllDay(eventData.allDay || false)
|
||||
setEditingId(eventData.id)
|
||||
setDialogOpen(true)
|
||||
}}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
|
||||
{/* Add/Edit Dialog */}
|
||||
<Dialog open={dialogOpen} onOpenChange={val => { if (!val) resetForm(); setDialogOpen(val) }}>
|
||||
|
||||
Reference in New Issue
Block a user