"use client" import React, { useState, useCallback, useMemo, useEffect } from "react" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Badge } from "@/components/ui/badge" import { ChevronLeft, ChevronRight, Plus, Calendar, Clock, Grid3x3, List, Search, X } from "lucide-react" import { cn } from "@/lib/utils" export interface Event { id: string title: string description?: string startTime: Date endTime: Date color: string category?: string attendees?: string[] tags?: string[] } export interface EventManagerProps { events?: Event[] onEventCreate?: (event: Omit) => void onEventUpdate?: (id: string, event: Partial) => void onEventDelete?: (id: string) => void categories?: string[] colors?: { name: string; value: string; bg: string; text: string }[] defaultView?: "month" | "week" | "day" | "list" className?: string availableTags?: string[] } const defaultColors = [ { name: "Blue", value: "blue", bg: "bg-blue-500", text: "text-blue-700" }, { name: "Green", value: "green", bg: "bg-green-500", text: "text-green-700" }, { name: "Purple", value: "purple", bg: "bg-purple-500", text: "text-purple-700" }, { name: "Orange", value: "orange", bg: "bg-orange-500", text: "text-orange-700" }, { name: "Pink", value: "pink", bg: "bg-pink-500", text: "text-pink-700" }, { name: "Red", value: "red", bg: "bg-red-500", text: "text-red-700" }, ] // Locale/timezone padrão BR const LOCALE = "pt-BR" const TIMEZONE = "America/Sao_Paulo" export function EventManager({ events: initialEvents = [], onEventCreate, onEventUpdate, onEventDelete, categories = ["Meeting", "Task", "Reminder", "Personal"], colors = defaultColors, defaultView = "month", className, availableTags = ["Important", "Urgent", "Work", "Personal", "Team", "Client"], }: EventManagerProps) { const [events, setEvents] = useState(initialEvents) const [currentDate, setCurrentDate] = useState(new Date()) const [view, setView] = useState<"month" | "week" | "day" | "list">(defaultView) const [selectedEvent, setSelectedEvent] = useState(null) const [isDialogOpen, setIsDialogOpen] = useState(false) const [isCreating, setIsCreating] = useState(false) const [draggedEvent, setDraggedEvent] = useState(null) const [newEvent, setNewEvent] = useState>({ title: "", description: "", color: colors[0].value, category: categories[0], tags: [], }) const [searchQuery, setSearchQuery] = useState("") // Dialog: lista completa de pacientes do dia const [dayDialogEvents, setDayDialogEvents] = useState(null) const [isDayDialogOpen, setIsDayDialogOpen] = useState(false) const openDayDialog = useCallback((eventsForDay: Event[]) => { // ordena por horário antes de abrir const ordered = [...eventsForDay].sort((a, b) => a.startTime.getTime() - b.startTime.getTime()) setDayDialogEvents(ordered) setIsDayDialogOpen(true) }, []) const filteredEvents = useMemo(() => { return events.filter((event) => { if (searchQuery) { const query = searchQuery.toLowerCase() const matchesSearch = event.title.toLowerCase().includes(query) || event.description?.toLowerCase().includes(query) || event.category?.toLowerCase().includes(query) || event.tags?.some((tag) => tag.toLowerCase().includes(query)) if (!matchesSearch) return false } return true }) }, [events, searchQuery]) const hasActiveFilters = false const clearFilters = () => { setSearchQuery("") } const handleCreateEvent = useCallback(() => { if (!newEvent.title || !newEvent.startTime || !newEvent.endTime) return const event: Event = { id: Math.random().toString(36).substr(2, 9), title: newEvent.title, description: newEvent.description, startTime: newEvent.startTime, endTime: newEvent.endTime, color: newEvent.color || colors[0].value, category: newEvent.category, attendees: newEvent.attendees, tags: newEvent.tags || [], } setEvents((prev) => [...prev, event]) onEventCreate?.(event) setIsDialogOpen(false) setIsCreating(false) setNewEvent({ title: "", description: "", color: colors[0].value, category: categories[0], tags: [], }) }, [newEvent, colors, categories, onEventCreate]) const handleUpdateEvent = useCallback(() => { if (!selectedEvent) return setEvents((prev) => prev.map((e) => (e.id === selectedEvent.id ? selectedEvent : e))) onEventUpdate?.(selectedEvent.id, selectedEvent) setIsDialogOpen(false) setSelectedEvent(null) }, [selectedEvent, onEventUpdate]) const handleDeleteEvent = useCallback( (id: string) => { setEvents((prev) => prev.filter((e) => e.id !== id)) onEventDelete?.(id) setIsDialogOpen(false) setSelectedEvent(null) }, [onEventDelete], ) const handleDragStart = useCallback((event: Event) => { setDraggedEvent(event) }, []) const handleDragEnd = useCallback(() => { setDraggedEvent(null) }, []) const handleDrop = useCallback( (date: Date, hour?: number) => { if (!draggedEvent) return const duration = draggedEvent.endTime.getTime() - draggedEvent.startTime.getTime() const newStartTime = new Date(date) if (hour !== undefined) { newStartTime.setHours(hour, 0, 0, 0) } const newEndTime = new Date(newStartTime.getTime() + duration) const updatedEvent = { ...draggedEvent, startTime: newStartTime, endTime: newEndTime, } setEvents((prev) => prev.map((e) => (e.id === draggedEvent.id ? updatedEvent : e))) onEventUpdate?.(draggedEvent.id, updatedEvent) setDraggedEvent(null) }, [draggedEvent, onEventUpdate], ) const navigateDate = useCallback( (direction: "prev" | "next") => { setCurrentDate((prev) => { const newDate = new Date(prev) if (view === "month") { newDate.setMonth(prev.getMonth() + (direction === "next" ? 1 : -1)) } else if (view === "week") { newDate.setDate(prev.getDate() + (direction === "next" ? 7 : -7)) } else if (view === "day") { newDate.setDate(prev.getDate() + (direction === "next" ? 1 : -1)) } return newDate }) }, [view], ) const getColorClasses = useCallback( (colorValue: string) => { const color = colors.find((c) => c.value === colorValue) return color || colors[0] }, [colors], ) // Força lang/cookie pt-BR no documento (reforço local) useEffect(() => { try { document.documentElement.lang = "pt-BR" document.documentElement.setAttribute("xml:lang", "pt-BR") document.documentElement.setAttribute("data-lang", "pt-BR") const oneYear = 60 * 60 * 24 * 365 document.cookie = `NEXT_LOCALE=pt-BR; Path=/; Max-Age=${oneYear}; SameSite=Lax` } catch {} }, []) return (
{/* Header */}

{view === "month" && currentDate.toLocaleDateString(LOCALE, { month: "long", year: "numeric", timeZone: TIMEZONE, })} {view === "week" && `Semana de ${currentDate.toLocaleDateString(LOCALE, { month: "short", day: "numeric", timeZone: TIMEZONE, })}`} {view === "day" && currentDate.toLocaleDateString(LOCALE, { weekday: "long", month: "long", day: "numeric", year: "numeric", timeZone: TIMEZONE, })} {view === "list" && "Todos os eventos"}

{/* Mobile: Select dropdown */}
{/* Desktop: Button group */}
{/* Lupa minimalista à esquerda (somente ícone) */} {/* Input central com altura consistente e foco visível */} setSearchQuery(e.target.value)} className={cn( "flex-1 h-10 px-3 border border-border focus:ring-2 focus:ring-primary/20 outline-none", searchQuery ? "rounded-l-md rounded-r-none" : "rounded-md" )} /> {/* Botão limpar discreto à direita (aparece somente com query) */} {searchQuery ? ( ) : null}
{/* Calendar Views - Pass filteredEvents instead of events */} {view === "month" && ( { setSelectedEvent(event) setIsDialogOpen(true) }} onDragStart={(event) => handleDragStart(event)} onDragEnd={() => handleDragEnd()} onDrop={handleDrop} getColorClasses={getColorClasses} openDayDialog={openDayDialog} /> )} {/* Dialog com todos os pacientes do dia */} Pacientes do dia Todos os agendamentos do dia selecionado.
{dayDialogEvents?.map((ev) => (
{ setSelectedEvent(ev) setIsDialogOpen(true) setIsDayDialogOpen(false) }} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { setSelectedEvent(ev) setIsDialogOpen(true) setIsDayDialogOpen(false) } }} className="flex items-start gap-3 p-2 border-b last:border-b-0 rounded-md cursor-pointer hover:bg-accent/40 focus:outline-none focus:ring-2 focus:ring-primary/30" >
{ev.title}
{ev.startTime.toLocaleTimeString(LOCALE,{hour:"2-digit",minute:"2-digit",hour12:false,timeZone:TIMEZONE})} {" - "} {ev.endTime.toLocaleTimeString(LOCALE,{hour:"2-digit",minute:"2-digit",hour12:false,timeZone:TIMEZONE})}
{ev.description && (
{ev.description}
)}
{ev.category && {ev.category}} {ev.tags?.map((t) => ( {t} ))}
))} {!dayDialogEvents?.length && (
Nenhum evento
)}
{view === "week" && ( { setSelectedEvent(event) setIsDialogOpen(true) }} onDragStart={(event) => handleDragStart(event)} onDragEnd={() => handleDragEnd()} onDrop={handleDrop} getColorClasses={getColorClasses} /> )} {view === "day" && ( { setSelectedEvent(event) setIsDialogOpen(true) }} onDragStart={(event) => handleDragStart(event)} onDragEnd={() => handleDragEnd()} onDrop={handleDrop} getColorClasses={getColorClasses} /> )} {view === "list" && ( { setSelectedEvent(event) setIsDialogOpen(true) }} getColorClasses={getColorClasses} /> )} {/* Event Dialog */} {isCreating ? "Criar Evento" : "Detalhes do Agendamento"} {isCreating ? "Adicione um novo evento ao seu calendário" : "Visualizar e editar detalhes do agendamento"}
isCreating ? setNewEvent((prev) => ({ ...prev, title: e.target.value })) : setSelectedEvent((prev) => (prev ? { ...prev, title: e.target.value } : null)) } placeholder="Título do evento" />