Merge branch 'feature/caledarAjust' into fix/avatar
This commit is contained in:
commit
1088e66f55
14
next.config.mjs
Normal file
14
next.config.mjs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
// Proxy local → Supabase (bypass CORS no navegador)
|
||||||
|
{
|
||||||
|
source: '/proxy/supabase/:path*',
|
||||||
|
destination: 'https://yuanqfswhberkoevtmfr.supabase.co/:path*',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.fc-media-screen {
|
.fc-media-screen {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 74vh;
|
height: 74vh;
|
||||||
@ -38,4 +37,47 @@
|
|||||||
.fc-toolbar-title {
|
.fc-toolbar-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-gray-900);
|
color: var(--color-gray-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Compact mode for embedded EventManager */
|
||||||
|
.compact-event-manager {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.compact-event-manager h2 {
|
||||||
|
font-size: 1rem; /* menor título */
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.compact-event-manager .sm\\:flex { /* reduz grupo de botões */
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.compact-event-manager .button,
|
||||||
|
.compact-event-manager .btn,
|
||||||
|
.compact-event-manager .chakra-button {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs dentro do EventManager compactos */
|
||||||
|
.compact-event-manager input,
|
||||||
|
.compact-event-manager .input {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reduzir padding dos cards e dos toolbars internos */
|
||||||
|
.compact-event-manager .p-4 { padding: 0.5rem; }
|
||||||
|
.compact-event-manager .p-3 { padding: 0.4rem; }
|
||||||
|
|
||||||
|
/* reduzir altura das linhas na vista semana/dia custom */
|
||||||
|
.compact-event-manager .min-h-16 { min-height: 3.2rem; }
|
||||||
|
.compact-event-manager .min-h-20 { min-height: 3.6rem; }
|
||||||
|
|
||||||
|
/* tornar os botões de filtro menores */
|
||||||
|
.compact-event-manager .dropdown-trigger,
|
||||||
|
.compact-event-manager .dropdown-menu-trigger {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* melhorar harmonia: menos margem entre header e calendário */
|
||||||
|
.compact-event-manager { margin-top: 0.25rem; margin-bottom: 0.25rem; }
|
||||||
@ -1,26 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
// Imports mantidos
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import pt_br_locale from "@fullcalendar/core/locales/pt-br";
|
import Link from "next/link";
|
||||||
import FullCalendar from "@fullcalendar/react";
|
|
||||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
// --- Imports do EventManager (NOVO) - MANTIDOS ---
|
||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import { EventManager, type Event } from "@/components/event-manager";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import { v4 as uuidv4 } from 'uuid'; // Usado para IDs de fallback
|
||||||
import { EventInput } from "@fullcalendar/core/index.js";
|
|
||||||
|
// Imports mantidos
|
||||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||||
import { PagesHeader } from "@/components/dashboard/header";
|
import { PagesHeader } from "@/components/dashboard/header";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { mockWaitingList } from "@/lib/mocks/appointment-mocks";
|
import { mockWaitingList } from "@/lib/mocks/appointment-mocks";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import Link from "next/link";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { ThreeDWallCalendar, CalendarEvent } from "@/components/ui/three-dwall-calendar";
|
import { ThreeDWallCalendar, CalendarEvent } from "@/components/ui/three-dwall-calendar"; // Calendário 3D mantido
|
||||||
|
|
||||||
const ListaEspera = dynamic(
|
const ListaEspera = dynamic(
|
||||||
() => import("@/components/agendamento/ListaEspera"),
|
() => import("@/components/agendamento/ListaEspera"),
|
||||||
@ -31,9 +32,14 @@ export default function AgendamentoPage() {
|
|||||||
const [appointments, setAppointments] = useState<any[]>([]);
|
const [appointments, setAppointments] = useState<any[]>([]);
|
||||||
const [waitingList, setWaitingList] = useState(mockWaitingList);
|
const [waitingList, setWaitingList] = useState(mockWaitingList);
|
||||||
const [activeTab, setActiveTab] = useState<"calendar" | "espera" | "3d">("calendar");
|
const [activeTab, setActiveTab] = useState<"calendar" | "espera" | "3d">("calendar");
|
||||||
const [requestsList, setRequestsList] = useState<EventInput[]>();
|
|
||||||
const [threeDEvents, setThreeDEvents] = useState<CalendarEvent[]>([]);
|
const [threeDEvents, setThreeDEvents] = useState<CalendarEvent[]>([]);
|
||||||
|
|
||||||
|
// --- NOVO ESTADO ---
|
||||||
|
// Estado para alimentar o NOVO EventManager com dados da API
|
||||||
|
const [managerEvents, setManagerEvents] = useState<Event[]>([]);
|
||||||
|
const [managerLoading, setManagerLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "c") {
|
if (event.key === "c") {
|
||||||
@ -49,22 +55,21 @@ export default function AgendamentoPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch real appointments and map to calendar events
|
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
// listarAgendamentos accepts a query string; request a reasonable limit and order
|
setManagerLoading(true);
|
||||||
const api = await import('@/lib/api');
|
const api = await import('@/lib/api');
|
||||||
const arr = await api.listarAgendamentos('select=*&order=scheduled_at.desc&limit=500').catch(() => []);
|
const arr = await api.listarAgendamentos('select=*&order=scheduled_at.desc&limit=500').catch(() => []);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (!arr || !arr.length) {
|
if (!arr || !arr.length) {
|
||||||
setAppointments([]);
|
setAppointments([]);
|
||||||
setRequestsList([]);
|
|
||||||
setThreeDEvents([]);
|
setThreeDEvents([]);
|
||||||
|
setManagerEvents([]); // Limpa o novo calendário
|
||||||
|
setManagerLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch-fetch patient names for display
|
|
||||||
const patientIds = Array.from(new Set(arr.map((a: any) => a.patient_id).filter(Boolean)));
|
const patientIds = Array.from(new Set(arr.map((a: any) => a.patient_id).filter(Boolean)));
|
||||||
const patients = (patientIds && patientIds.length) ? await api.buscarPacientesPorIds(patientIds) : [];
|
const patients = (patientIds && patientIds.length) ? await api.buscarPacientesPorIds(patientIds) : [];
|
||||||
const patientsById: Record<string, any> = {};
|
const patientsById: Record<string, any> = {};
|
||||||
@ -72,24 +77,34 @@ export default function AgendamentoPage() {
|
|||||||
|
|
||||||
setAppointments(arr || []);
|
setAppointments(arr || []);
|
||||||
|
|
||||||
const events: EventInput[] = (arr || []).map((obj: any) => {
|
// --- LÓGICA DE TRANSFORMAÇÃO PARA O NOVO EVENTMANAGER ---
|
||||||
|
const newManagerEvents: Event[] = (arr || []).map((obj: any) => {
|
||||||
const scheduled = obj.scheduled_at || obj.scheduledAt || obj.time || null;
|
const scheduled = obj.scheduled_at || obj.scheduledAt || obj.time || null;
|
||||||
const start = scheduled ? new Date(scheduled) : null;
|
const start = scheduled ? new Date(scheduled) : new Date();
|
||||||
const duration = Number(obj.duration_minutes ?? obj.duration ?? 30) || 30;
|
const duration = Number(obj.duration_minutes ?? obj.duration ?? 30) || 30;
|
||||||
|
const end = new Date(start.getTime() + duration * 60 * 1000);
|
||||||
|
|
||||||
const patient = (patientsById[String(obj.patient_id)]?.full_name) || obj.patient_name || obj.patient_full_name || obj.patient || 'Paciente';
|
const patient = (patientsById[String(obj.patient_id)]?.full_name) || obj.patient_name || obj.patient_full_name || obj.patient || 'Paciente';
|
||||||
const title = `${patient}: ${obj.appointment_type ?? obj.type ?? ''}`.trim();
|
const title = `${patient}: ${obj.appointment_type ?? obj.type ?? ''}`.trim();
|
||||||
const color = obj.status === 'confirmed' ? '#68d68a' : obj.status === 'pending' ? '#ffe55f' : '#ff5f5fff';
|
|
||||||
return {
|
let color = "gray"; // Cor padrão
|
||||||
title,
|
if (obj.status === 'confirmed') color = 'green';
|
||||||
start: start || new Date(),
|
if (obj.status === 'pending') color = 'orange';
|
||||||
end: start ? new Date(start.getTime() + duration * 60 * 1000) : undefined,
|
|
||||||
color,
|
|
||||||
extendedProps: { raw: obj },
|
|
||||||
} as EventInput;
|
|
||||||
});
|
|
||||||
setRequestsList(events || []);
|
|
||||||
|
|
||||||
// Convert to 3D calendar events
|
return {
|
||||||
|
id: obj.id || uuidv4(), // Usa ID da API ou gera um
|
||||||
|
title: title,
|
||||||
|
description: `Agendamento para ${patient}. Status: ${obj.status || 'N/A'}.`,
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
color: color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setManagerEvents(newManagerEvents);
|
||||||
|
setManagerLoading(false);
|
||||||
|
// --- FIM DA LÓGICA ---
|
||||||
|
|
||||||
|
// Convert to 3D calendar events (MANTIDO 100%)
|
||||||
const threeDEvents: CalendarEvent[] = (arr || []).map((obj: any) => {
|
const threeDEvents: CalendarEvent[] = (arr || []).map((obj: any) => {
|
||||||
const scheduled = obj.scheduled_at || obj.scheduledAt || obj.time || null;
|
const scheduled = obj.scheduled_at || obj.scheduledAt || obj.time || null;
|
||||||
const patient = (patientsById[String(obj.patient_id)]?.full_name) || obj.patient_name || obj.patient_full_name || obj.patient || 'Paciente';
|
const patient = (patientsById[String(obj.patient_id)]?.full_name) || obj.patient_name || obj.patient_full_name || obj.patient || 'Paciente';
|
||||||
@ -108,14 +123,15 @@ export default function AgendamentoPage() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[AgendamentoPage] falha ao carregar agendamentos', err);
|
console.warn('[AgendamentoPage] falha ao carregar agendamentos', err);
|
||||||
setAppointments([]);
|
setAppointments([]);
|
||||||
setRequestsList([]);
|
|
||||||
setThreeDEvents([]);
|
setThreeDEvents([]);
|
||||||
|
setManagerEvents([]); // Limpa o novo calendário
|
||||||
|
setManagerLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
return () => { mounted = false; };
|
return () => { mounted = false; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// mantive para caso a lógica de salvar consulta passe a funcionar
|
// Handlers mantidos
|
||||||
const handleSaveAppointment = (appointment: any) => {
|
const handleSaveAppointment = (appointment: any) => {
|
||||||
if (appointment.id) {
|
if (appointment.id) {
|
||||||
setAppointments((prev) =>
|
setAppointments((prev) =>
|
||||||
@ -147,6 +163,7 @@ export default function AgendamentoPage() {
|
|||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<div className="flex w-full flex-col gap-10 p-6">
|
<div className="flex w-full flex-col gap-10 p-6">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
{/* Todo o cabeçalho foi mantido */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
{activeTab === "calendar" ? "Calendário" : activeTab === "3d" ? "Calendário 3D" : "Lista de Espera"}
|
{activeTab === "calendar" ? "Calendário" : activeTab === "3d" ? "Calendário 3D" : "Lista de Espera"}
|
||||||
@ -156,11 +173,6 @@ export default function AgendamentoPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{/* <Link href={"/agenda"}>
|
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700">
|
|
||||||
Agenda
|
|
||||||
</Button>
|
|
||||||
</Link> */}
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="bg-primary hover:bg-primary/90 px-5 py-1 text-primary-foreground rounded-sm">
|
<DropdownMenuTrigger className="bg-primary hover:bg-primary/90 px-5 py-1 text-primary-foreground rounded-sm">
|
||||||
Opções »
|
Opções »
|
||||||
@ -206,29 +218,25 @@ export default function AgendamentoPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* --- AQUI ESTÁ A SUBSTITUIÇÃO --- */}
|
||||||
{activeTab === "calendar" ? (
|
{activeTab === "calendar" ? (
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
<FullCalendar
|
{/* mostra loading até managerEvents ser preenchido (API integrada desde a entrada) */}
|
||||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
<div className="w-full">
|
||||||
initialView="dayGridMonth"
|
{managerLoading ? (
|
||||||
locale={pt_br_locale}
|
<div className="flex items-center justify-center w-full min-h-[70vh]">
|
||||||
timeZone={"America/Sao_Paulo"}
|
<div className="text-sm text-muted-foreground">Conectando ao calendário — carregando agendamentos...</div>
|
||||||
events={requestsList}
|
</div>
|
||||||
headerToolbar={{
|
) : (
|
||||||
left: "prev,next today",
|
// EventManager ocupa a área principal e já recebe events da API
|
||||||
center: "title",
|
<div className="w-full min-h-[70vh]">
|
||||||
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
<EventManager events={managerEvents} className="compact-event-manager" />
|
||||||
}}
|
</div>
|
||||||
dateClick={(info) => {
|
)}
|
||||||
info.view.calendar.changeView("timeGridDay", info.dateStr);
|
</div>
|
||||||
}}
|
|
||||||
selectable={true}
|
|
||||||
selectMirror={true}
|
|
||||||
dayMaxEvents={true}
|
|
||||||
dayMaxEventRows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : activeTab === "3d" ? (
|
) : activeTab === "3d" ? (
|
||||||
|
// O calendário 3D (ThreeDWallCalendar) foi MANTIDO 100%
|
||||||
<div className="flex w-full justify-center">
|
<div className="flex w-full justify-center">
|
||||||
<ThreeDWallCalendar
|
<ThreeDWallCalendar
|
||||||
events={threeDEvents}
|
events={threeDEvents}
|
||||||
@ -237,6 +245,7 @@ export default function AgendamentoPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
// A Lista de Espera foi MANTIDA
|
||||||
<ListaEspera
|
<ListaEspera
|
||||||
patients={waitingList}
|
patients={waitingList}
|
||||||
onNotify={handleNotifyPatient}
|
onNotify={handleNotifyPatient}
|
||||||
@ -247,4 +256,4 @@ export default function AgendamentoPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
1493
susconecta/app/calendarComponente/page.tsx
Normal file
1493
susconecta/app/calendarComponente/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
118
susconecta/components/Calendario/Calendar.tsx
Normal file
118
susconecta/components/Calendario/Calendar.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import React, { useState, useCallback, useMemo } from "react";
|
||||||
|
import { EventCard } from "./EventCard";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Event } from "@/components/event-manager";
|
||||||
|
|
||||||
|
// Week View Component
|
||||||
|
export function WeekView({
|
||||||
|
currentDate,
|
||||||
|
events,
|
||||||
|
onEventClick,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
onDrop,
|
||||||
|
getColorClasses,
|
||||||
|
}: {
|
||||||
|
currentDate: Date;
|
||||||
|
events: Event[];
|
||||||
|
onEventClick: (event: Event) => void;
|
||||||
|
onDragStart: (event: Event) => void;
|
||||||
|
onDragEnd: () => void;
|
||||||
|
onDrop: (date: Date, hour: number) => void;
|
||||||
|
getColorClasses: (color: string) => { bg: string; text: string };
|
||||||
|
}) {
|
||||||
|
const startOfWeek = new Date(currentDate);
|
||||||
|
startOfWeek.setDate(currentDate.getDay());
|
||||||
|
|
||||||
|
const weekDays = Array.from({ length: 7 }, (_, i) => {
|
||||||
|
const day = new Date(startOfWeek);
|
||||||
|
day.setDate(startOfWeek.getDate() + i);
|
||||||
|
return day;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||||
|
|
||||||
|
const getEventsForDayAndHour = (date: Date, hour: number) => {
|
||||||
|
return events.filter((event) => {
|
||||||
|
const eventDate = new Date(event.startTime);
|
||||||
|
const eventHour = eventDate.getHours();
|
||||||
|
return (
|
||||||
|
eventDate.getDate() === date.getDate() &&
|
||||||
|
eventDate.getMonth() === date.getMonth() &&
|
||||||
|
eventDate.getFullYear() === date.getFullYear() &&
|
||||||
|
eventHour === hour
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// dias da semana em pt-BR (abreviações)
|
||||||
|
const weekDayNames = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="overflow-auto">
|
||||||
|
<div className="grid grid-cols-8 border-b">
|
||||||
|
<div className="border-r p-2 text-center text-xs font-medium sm:text-sm">
|
||||||
|
Hora
|
||||||
|
</div>
|
||||||
|
{weekDays.map((day) => (
|
||||||
|
<div
|
||||||
|
key={day.toISOString()}
|
||||||
|
className="border-r p-2 text-center text-xs font-medium last:border-r-0 sm:text-sm"
|
||||||
|
>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
{day.toLocaleDateString("pt-BR", { weekday: "short" })}
|
||||||
|
</div>
|
||||||
|
<div className="sm:hidden">
|
||||||
|
{day.toLocaleDateString("pt-BR", { weekday: "narrow" })}
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-muted-foreground sm:text-xs">
|
||||||
|
{day.toLocaleDateString("pt-BR", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-8">
|
||||||
|
{hours.map((hour) => (
|
||||||
|
<React.Fragment key={`hour-${hour}`}>
|
||||||
|
<div
|
||||||
|
key={`time-${hour}`}
|
||||||
|
className="border-b border-r p-1 text-[10px] text-muted-foreground sm:p-2 sm:text-xs"
|
||||||
|
>
|
||||||
|
{hour.toString().padStart(2, "0")}:00
|
||||||
|
</div>
|
||||||
|
{weekDays.map((day) => {
|
||||||
|
const dayEvents = getEventsForDayAndHour(day, hour);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`${day.toISOString()}-${hour}`}
|
||||||
|
className="min-h-12 border-b border-r p-0.5 transition-colors hover:bg-accent/50 last:border-r-0 sm:min-h-16 sm:p-1"
|
||||||
|
onDragOver={(e) => e.preventDefault()}
|
||||||
|
onDrop={() => onDrop(day, hour)}
|
||||||
|
>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{dayEvents.map((event) => (
|
||||||
|
<EventCard
|
||||||
|
key={event.id}
|
||||||
|
event={event}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
getColorClasses={getColorClasses}
|
||||||
|
variant="default"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
susconecta/components/Calendario/EventCard.tsx
Normal file
103
susconecta/components/Calendario/EventCard.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Event } from "@/components/event-manager";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Componente leve para representar um evento no calendário.
|
||||||
|
Compatível com o uso em Calendar.tsx (WeekView / DayView).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function EventCard({
|
||||||
|
event,
|
||||||
|
onEventClick,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
getColorClasses,
|
||||||
|
variant = "default",
|
||||||
|
}: {
|
||||||
|
event: Event;
|
||||||
|
onEventClick: (e: Event) => void;
|
||||||
|
onDragStart: (e: Event) => void;
|
||||||
|
onDragEnd: () => void;
|
||||||
|
getColorClasses: (color: string) => { bg: string; text: string };
|
||||||
|
variant?: "default" | "compact" | "detailed";
|
||||||
|
}) {
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
const color = getColorClasses?.(event.color) ?? { bg: "bg-slate-400", text: "text-white" };
|
||||||
|
|
||||||
|
const handleDragStart = (e: React.DragEvent) => {
|
||||||
|
e.dataTransfer.setData("text/plain", event.id);
|
||||||
|
onDragStart && onDragStart(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
onEventClick && onEventClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (variant === "compact") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onDragEnd={() => onDragEnd && onDragEnd()}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
className={cn(
|
||||||
|
"rounded px-2 py-0.5 text-xs font-medium truncate",
|
||||||
|
color.bg,
|
||||||
|
color.text,
|
||||||
|
"cursor-pointer transition-all",
|
||||||
|
hover && "shadow-md scale-105"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === "detailed") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onDragEnd={() => onDragEnd && onDragEnd()}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg p-2 text-sm cursor-pointer transition-all",
|
||||||
|
color.bg,
|
||||||
|
color.text,
|
||||||
|
hover && "shadow-lg scale-[1.02]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="font-semibold">{event.title}</div>
|
||||||
|
{event.description && <div className="text-xs opacity-90 mt-1 line-clamp-2">{event.description}</div>}
|
||||||
|
<div className="mt-1 text-[11px] opacity-80">
|
||||||
|
{event.startTime?.toLocaleTimeString?.("pt-BR", { hour: "2-digit", minute: "2-digit" }) ?? ""} - {event.endTime?.toLocaleTimeString?.("pt-BR", { hour: "2-digit", minute: "2-digit" }) ?? ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onDragEnd={() => onDragEnd && onDragEnd()}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
className={cn(
|
||||||
|
"relative rounded px-2 py-1 text-xs font-medium cursor-pointer transition-all",
|
||||||
|
color.bg,
|
||||||
|
color.text,
|
||||||
|
hover && "shadow-md scale-105"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="truncate">{event.title}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1485
susconecta/components/event-manager.tsx
Normal file
1485
susconecta/components/event-manager.tsx
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user