fix atendimentos.ts

Signed-off-by: Rodrigo Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2025-04-12 23:25:48 -03:00
1 parent ea0481e09e
commit ac4be2197a
3 files changed
+236 -267

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import React, { useState, useCallback } from 'react';
import { Calendar } from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import ptBrLocale from '@fullcalendar/core/locales/pt-br';
import { format } from 'date-fns';
import { api } from '../services/api';
import { toast } from 'react-toastify';
interface Agendamento {
id: string;
data: string;
observacoes?: string;
status: string;
cliente: {
id: string;
nome: string;
};
}
interface CalendarEvent {
id: string;
title: string;
start: string;
end: string;
backgroundColor?: string;
borderColor?: string;
extendedProps: {
agendamento: Agendamento;
};
}
interface AgendamentoCalendarioProps {
onEventClick?: (event: any) => void;
onDateSelect?: (selectInfo: any) => void;
}
export const AgendamentoCalendario: React.FC<AgendamentoCalendarioProps> = ({
onEventClick,
onDateSelect
}) => {
const [eventos, setEventos] = useState<CalendarEvent[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const formatEventos = useCallback((agendamentos: Agendamento[]): CalendarEvent[] => {
return agendamentos.map(ag => ({
id: ag.id,
title: `${ag.cliente.nome} ${ag.observacoes ? `- ${ag.observacoes}` : ''}`,
start: ag.data,
end: ag.data,
backgroundColor: ag.status === 'CANCELADO' ? '#FEE2E2' : '#E0F2FE',
borderColor: ag.status === 'CANCELADO' ? '#EF4444' : '#0EA5E9',
extendedProps: {
agendamento: ag
}
}));
}, []);
const loadAgendamentos = useCallback(async ({ start, end }: { start: Date, end: Date }) => {
try {
setLoading(true);
setError(null);
const response = await api.get('/atendimentos/agendamentos', {
params: {
inicio: format(start, 'yyyy-MM-dd'),
fim: format(end, 'yyyy-MM-dd')
}
});
setEventos(formatEventos(response.data));
} catch (err: any) {
const mensagem = err.response?.data?.error || 'Erro ao carregar agendamentos';
setError(mensagem);
toast.error(mensagem);
} finally {
setLoading(false);
}
}, [formatEventos]);
return (
<div className="relative h-full">
{loading && (
<div className="absolute inset-0 bg-white/50 flex items-center justify-center z-10">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
</div>
)}
{error && (
<div className="absolute top-0 left-0 right-0 p-4 bg-red-100 text-red-700">
{error}
</div>
)}
<Calendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
locale={ptBrLocale}
events={eventos}
editable={false}
selectable={true}
selectMirror={true}
dayMaxEvents={3}
weekends={true}
datesSet={({ start, end }) => loadAgendamentos({ start, end })}
eventClick={({ event }) => onEventClick?.(event)}
select={(selectInfo) => onDateSelect?.(selectInfo)}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
}}
slotMinTime="08:00:00"
slotMaxTime="18:00:00"
/>
</div>
);
};
+75
View File
@@ -0,0 +1,75 @@
import { Router, Request, Response, NextFunction } from "express";
import { startOfWeek, endOfWeek, isValid, parseISO } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import { prisma } from "../prismaClient";
const router = Router();
router.get("/", async (
req: Request<{}, any, any, { data?: string }>,
res: Response,
next: NextFunction
) => {
try {
const { data } = req.query;
console.log("[Agendamentos] Consultando data:", data);
// Validar e converter a data
let searchDate = new Date();
if (data) {
const parsedDate = parseISO(data);
if (isValid(parsedDate)) {
searchDate = parsedDate;
} else {
return res.status(400).json({
error: "Formato de data inválido",
details: "Use o formato ISO, exemplo: 2025-04-13",
});
}
}
const inicio = startOfWeek(searchDate, { locale: ptBR });
const fim = endOfWeek(searchDate, { locale: ptBR });
console.log("[Agendamentos] Buscando período:", {
inicio: inicio.toISOString(),
fim: fim.toISOString()
});
const agendamentos = await prisma.agendamentoAtendimento.findMany({
where: {
data: {
gte: inicio,
lte: fim,
},
},
include: {
cliente: {
select: {
id: true,
nome: true,
},
},
usuario: {
select: {
id: true,
nome: true,
},
},
},
orderBy: {
data: "asc",
},
});
return res.json(agendamentos);
} catch (error) {
console.error("[Agendamentos] Erro:", error);
return res.status(500).json({
error: "Erro ao buscar agendamentos",
details: error instanceof Error ? error.message : "Erro desconhecido",
});
}
});
export { router as agendamentosRouter };
+41 -267
View File
@@ -1,283 +1,75 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import multer from "multer"; import multer from "multer";
import { startOfWeek, endOfWeek, parseISO } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import { authMiddleware } from "../../middleware/auth"; import { authMiddleware } from "../../middleware/auth";
import { NextCloudService } from "../../services/nextcloud"; import { NextCloudService } from "../../services/nextcloud";
import { prisma } from "../prismaClient"; import { prisma } from "../prismaClient";
import { agendamentosRouter } from './agendamentos';
const router = Router(); const router = Router();
const upload = multer({ storage: multer.memoryStorage() }); const upload = multer({ storage: multer.memoryStorage() });
router.use(authMiddleware); router.use(authMiddleware);
// Rotas de agendamentos em subrouter separado
router.use('/agendamentos', agendamentosRouter);
// Middleware para logging com informações mais detalhadas
router.use((req, res, next) => {
const requestId = Math.random().toString(36).substring(7);
console.log(`[${new Date().toISOString()}][${requestId}] Request:`, {
method: req.method,
path: req.path,
originalUrl: req.originalUrl,
params: req.params,
query: req.query,
body: req.body
});
// Interceptar resposta para logging
const oldJson = res.json;
res.json = function(data) {
console.log(`[${new Date().toISOString()}][${requestId}] Response:`, {
statusCode: res.statusCode,
data: data
});
return oldJson.apply(res, arguments);
};
next();
});
// Função auxiliar para validar se uma string é um UUID válido // Função auxiliar para validar se uma string é um UUID válido
function isValidUUID(uuid: string): boolean { function isValidUUID(uuid: string): boolean {
if (!uuid) return false; if (!uuid) return false;
try { try {
// Validação principal com regex para UUID padrão
const regexUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const regexUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return regexUUID.test(uuid);
// Se passar na validação principal, retorna verdadeiro
if (regexUUID.test(uuid)) return true;
// Caso contrário, verifica se é um ID válido de alguma outra forma
// (como um ID numérico ou string que possa ser convertida em UUID)
console.log(`ID recebido em formato não-UUID padrão: ${uuid}`);
// Se for "agendamentos", é uma rota especial que não precisa de validação UUID
if (uuid === "agendamentos") return true;
// Se chegou aqui e não é um UUID válido, retorna falso
return false;
} catch (error) { } catch (error) {
console.error("Erro ao validar UUID:", error); console.error("Erro ao validar UUID:", error);
return false; return false;
} }
} }
// Rota para listar agendamentos // Desabilitar validação UUID para rotas específicas
router.get("/agendamentos", async (req: Request, res: Response) => { const SKIP_UUID_VALIDATION = ['/agendamentos', '/upload'];
try {
const { data } = req.query;
console.log("Requisição de agendamentos recebida, data:", data);
let dateFilter = {}; router.use((req, res, next) => {
// Pula validação para rotas específicas
if (data) { if (SKIP_UUID_VALIDATION.some(path => req.path.startsWith(path))) {
try { return next();
// Aceitar e validar o formato da data de forma mais flexível
const parsedDate = new Date(data as string);
// Verificar se a data é válida
if (isNaN(parsedDate.getTime())) {
console.error("Data inválida recebida:", data);
// Em vez de retornar erro, continue sem filtro
console.log("Continuando sem filtro de data devido a data inválida");
} else {
console.log("Data parseada:", parsedDate);
const weekStart = startOfWeek(parsedDate, { locale: ptBR });
const weekEnd = endOfWeek(parsedDate, { locale: ptBR });
console.log("Período de busca:", {
weekStart: weekStart.toISOString(),
weekEnd: weekEnd.toISOString()
});
dateFilter = {
data: {
gte: weekStart,
lte: weekEnd,
},
};
}
} catch (parseError) {
console.error("Erro ao analisar data:", parseError);
// Continuar sem filtro de data em caso de erro, em vez de falhar
console.log("Continuando sem filtro de data devido ao erro");
}
} }
console.log("Filtro de data:", JSON.stringify(dateFilter)); // Se tiver ID nos parâmetros, valida
// Usando o modelo correto e com tratamento de erro adequado
try {
const agendamentos = await prisma.agendamentoAtendimento.findMany({
where: dateFilter,
include: {
cliente: {
select: {
id: true,
nome: true,
},
},
usuario: {
select: {
id: true,
nome: true,
},
},
},
orderBy: {
data: 'asc',
},
});
console.log(`Encontrados ${agendamentos.length} agendamentos`);
return res.json(agendamentos);
} catch (dbError) {
console.error("Erro na consulta do banco de dados:", dbError);
return res.status(500).json({
error: "Erro ao buscar agendamentos no banco de dados",
details: dbError.message
});
}
} catch (error) {
console.error("Erro geral ao listar agendamentos:", error);
return res.status(500).json({
error: "Erro ao listar agendamentos",
details: error.message
});
}
});
// Rota para criar um novo agendamento
router.post("/agendamentos", async (req: Request, res: Response) => {
try {
const { clienteId, data, observacoes, tipo } = req.body;
console.log("Criando agendamento:", { clienteId, data, observacoes, tipo });
if (!clienteId || !data) {
return res.status(400).json({ error: "Cliente e data são obrigatórios" });
}
// Verificar se o cliente existe
const cliente = await prisma.cliente.findUnique({
where: { id: clienteId }
});
if (!cliente) {
return res.status(404).json({ error: "Cliente não encontrado" });
}
// Criar o agendamento - usando agendamentoAtendimento
const agendamento = await prisma.agendamentoAtendimento.create({
data: {
clienteId,
data: new Date(data),
observacoes,
tipo,
status: "AGENDADO",
usuarioId: req.user?.id // Adicionando usuarioId do middleware auth
},
include: {
cliente: {
select: {
id: true,
nome: true
}
}
}
});
console.log("Agendamento criado:", {
id: agendamento.id,
data: agendamento.data,
clienteNome: agendamento.cliente.nome
});
res.status(201).json(agendamento);
} catch (error) {
console.error("Erro ao criar agendamento:", error);
res.status(500).json({ error: "Erro ao criar agendamento", details: error.message });
}
});
// Rota para atualizar um agendamento
router.put("/agendamentos/:id", async (req: Request, res: Response) => {
try {
const { id } = req.params; const { id } = req.params;
const { clienteId, data, observacoes, tipo } = req.body; if (id && !isValidUUID(id)) {
// Validação do UUID antes de consultar o banco de dados
if (!isValidUUID(id)) {
console.error(`ID de agendamento inválido: ${id}. Não é um UUID válido.`);
return res.status(400).json({ return res.status(400).json({
error: "ID de agendamento inválido", error: "ID inválido",
details: "O formato do ID fornecido não é um UUID válido." details: "Formato de ID inválido para esta operação"
}); });
} }
console.log("Atualizando agendamento:", { id, clienteId, data, observacoes, tipo }); next();
// Verificar se o agendamento existe usando a tabela correta
const existingAgendamento = await prisma.agendamentoAtendimento.findUnique({
where: { id }
});
if (!existingAgendamento) {
return res.status(404).json({ error: "Agendamento não encontrado" });
}
// Atualizar o agendamento usando a tabela correta
const agendamento = await prisma.agendamentoAtendimento.update({
where: { id },
data: {
clienteId,
data: data ? new Date(data) : undefined,
observacoes,
tipo
},
include: {
cliente: {
select: {
id: true,
nome: true
}
}
}
});
console.log("Agendamento atualizado:", {
id: agendamento.id,
data: agendamento.data,
clienteNome: agendamento.cliente.nome
});
res.json(agendamento);
} catch (error) {
console.error("Erro ao atualizar agendamento:", error);
res.status(500).json({ error: "Erro ao atualizar agendamento", details: error.message });
}
});
// Rota para excluir um agendamento
router.delete("/agendamentos/:id", async (req: Request, res: Response) => {
try {
const { id } = req.params;
console.log("Excluindo agendamento:", id);
// Validação do UUID antes de consultar o banco de dados
if (!isValidUUID(id)) {
console.error(`ID de agendamento inválido: ${id}. Não é um UUID válido.`);
return res.status(400).json({
error: "ID de agendamento inválido",
details: "O formato do ID fornecido não é um UUID válido."
});
}
// Verificar se o agendamento existe usando a tabela correta
const agendamento = await prisma.agendamentoAtendimento.findUnique({
where: { id },
include: { cliente: true }
});
if (!agendamento) {
console.error("Agendamento não encontrado:", id);
return res.status(404).json({ error: "Agendamento não encontrado" });
}
console.log("Agendamento encontrado:", {
id: agendamento.id,
data: agendamento.data,
clienteNome: agendamento.cliente?.nome
});
// Excluir o agendamento usando a tabela correta
await prisma.agendamentoAtendimento.delete({
where: { id }
});
console.log("Agendamento excluído com sucesso");
res.status(200).json({ message: "Agendamento excluído com sucesso" });
} catch (error) {
console.error("Erro ao excluir agendamento:", error);
res.status(500).json({ error: "Erro ao excluir agendamento", details: error.message });
}
}); });
// Rota para atualizar atendimento com anexos // Rota para atualizar atendimento com anexos
@@ -286,15 +78,6 @@ router.patch("/:id", async (req: Request, res: Response) => {
const { id } = req.params; const { id } = req.params;
const { anexos, ...data } = req.body; const { anexos, ...data } = req.body;
// Validação do UUID antes de consultar o banco de dados
if (!isValidUUID(id)) {
console.error(`ID de atendimento inválido: ${id}. Não é um UUID válido.`);
return res.status(400).json({
error: "ID de atendimento inválido",
details: "O formato do ID fornecido não é um UUID válido."
});
}
console.log("Atualizando atendimento:", id, { anexos, data }); // Debug log console.log("Atualizando atendimento:", id, { anexos, data }); // Debug log
const atendimento = await prisma.atendimento.update({ const atendimento = await prisma.atendimento.update({
@@ -363,15 +146,6 @@ router.get("/:id", async (req: Request, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
// Validação do UUID antes de consultar o banco de dados
if (!isValidUUID(id)) {
console.error(`ID de atendimento inválido: ${id}. Não é um UUID válido.`);
return res.status(400).json({
error: "ID de atendimento inválido",
details: "O formato do ID fornecido não é um UUID válido."
});
}
const atendimento = await prisma.atendimento.findUnique({ const atendimento = await prisma.atendimento.findUnique({
where: { where: {
id: id, id: id,