riseup-squad20/susconecta/eslint-report.json
M-Gabrielly fb578b2a7a feat(api): add server-side /api/create-user route (user creation not functional yet)
- What was done:
  - Added a server-side Next.js route at `src/app/api/create-user/route.ts` that validates the requester token, checks roles, generates a temporary password and forwards the creation to the Supabase Edge Function using the service role key.
  - Client wired to call the route via `lib/config.ts` (`FUNCTIONS_ENDPOINTS.CREATE_USER` -> `/api/create-user`) and the `criarUsuario()` wrapper in `lib/api.ts`.
- Status / missing work:
  - Important: user creation is NOT working yet (requests to `/api/create-user` return 404 in dev).
  - Next steps: restart dev server, ensure `SUPABASE_SERVICE_ROLE_KEY` is set in the environment, check server logs and run a test POST with a valid admin JWT.
2025-10-14 17:02:26 -03:00

1 line
198 KiB
JSON

[{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\calendar\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\consultas\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\dashboard\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\dashboard\\relatorios\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\doutores\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\layout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\pacientes\\loading.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\(main-routes)\\pacientes\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\agenda\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\financeiro\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\layout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\login-admin\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\login-paciente\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\login\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\paciente\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\procedimento\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\profissional\\page.tsx","messages":[{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array.","line":167,"column":6,"nodeType":"ArrayExpression","endLine":167,"endColumn":26,"suggestions":[{"desc":"Update the dependencies array to be: [user.id, doctorId, user]","fix":{"range":[6724,6744],"text":"[user.id, doctorId, user]"}}]},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array.","line":219,"column":6,"nodeType":"ArrayExpression","endLine":219,"endColumn":29,"suggestions":[{"desc":"Update the dependencies array to be: [user.id, user.email, user]","fix":{"range":[9697,9720],"text":"[user.id, user.email, user]"}}]},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'reports'. Either include it or remove the dependency array. Outer scope values like 'user.id' aren't valid dependencies because mutating them doesn't re-render the component.","line":988,"column":8,"nodeType":"ArrayExpression","endLine":988,"endColumn":18,"suggestions":[{"desc":"Update the dependencies array to be: [reports]","fix":{"range":[40874,40884],"text":"[reports]"}}]},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'laudos'. Either include it or remove the dependency array.","line":993,"column":8,"nodeType":"ArrayExpression","endLine":993,"endColumn":17,"suggestions":[{"desc":"Update the dependencies array to be: [laudos, reports]","fix":{"range":[41050,41059],"text":"[laudos, reports]"}}]},{"ruleId":"unicorn/prefer-module","severity":2,"message":"Do not use \"require\".","line":1457,"column":21,"nodeType":"Identifier","messageId":"error/identifier","endLine":1457,"endColumn":28},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has an unnecessary dependency: 'user'. Either exclude it or remove the dependency array. Outer scope values like 'user' aren't valid dependencies because mutating them doesn't re-render the component.","line":1606,"column":8,"nodeType":"ArrayExpression","endLine":1606,"endColumn":70,"suggestions":[{"desc":"Update the dependencies array to be: [laudo, isNewLaudo, pacienteSelecionado, listaPacientes]","fix":{"range":[71698,71760],"text":"[laudo, isNewLaudo, pacienteSelecionado, listaPacientes]"}}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":2038,"column":27,"nodeType":"JSXOpeningElement","endLine":2042,"endColumn":29},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":2205,"column":29,"nodeType":"JSXOpeningElement","endLine":2210,"endColumn":31},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":2220,"column":29,"nodeType":"JSXOpeningElement","endLine":2220,"endColumn":126},{"ruleId":"unicorn/no-lonely-if","severity":2,"message":"Unexpected `if` as the only statement in a `if` block without `else`.","line":2316,"column":31,"nodeType":"IfStatement","messageId":"no-lonely-if","endLine":2316,"endColumn":98,"fix":{"range":[109490,109657],"text":"(val !== undefined && val !== null && JSON.stringify(origVal) !== JSON.stringify(val)) diff[k] = val;"}},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`.","line":2414,"column":36,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&quot;"},"fix":{"range":[114977,115006],"text":"&quot;Ok, obrigado pelo lembrete!\""},"desc":"Replace with `&quot;`."},{"messageId":"replaceWithAlt","data":{"alt":"&ldquo;"},"fix":{"range":[114977,115006],"text":"&ldquo;Ok, obrigado pelo lembrete!\""},"desc":"Replace with `&ldquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#34;"},"fix":{"range":[114977,115006],"text":"&#34;Ok, obrigado pelo lembrete!\""},"desc":"Replace with `&#34;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rdquo;"},"fix":{"range":[114977,115006],"text":"&rdquo;Ok, obrigado pelo lembrete!\""},"desc":"Replace with `&rdquo;`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`.","line":2414,"column":64,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&quot;"},"fix":{"range":[114977,115006],"text":"\"Ok, obrigado pelo lembrete!&quot;"},"desc":"Replace with `&quot;`."},{"messageId":"replaceWithAlt","data":{"alt":"&ldquo;"},"fix":{"range":[114977,115006],"text":"\"Ok, obrigado pelo lembrete!&ldquo;"},"desc":"Replace with `&ldquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#34;"},"fix":{"range":[114977,115006],"text":"\"Ok, obrigado pelo lembrete!&#34;"},"desc":"Replace with `&#34;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rdquo;"},"fix":{"range":[114977,115006],"text":"\"Ok, obrigado pelo lembrete!&rdquo;"},"desc":"Replace with `&rdquo;`."}]}],"suppressedMessages":[{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has missing dependencies: 'history' and 'historyIndex'. Either include them or remove the dependency array.","line":1620,"column":8,"nodeType":"ArrayExpression","endLine":1620,"endColumn":17,"suggestions":[{"desc":"Update the dependencies array to be: [content, history, historyIndex]","fix":{"range":[72242,72251],"text":"[content, history, historyIndex]"}}],"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":4,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":1,"fixableWarningCount":0,"source":"\"use client\";\r\nimport React, { useState, useRef, useEffect } from \"react\";\r\nimport SignatureCanvas from \"react-signature-canvas\";\r\nimport Link from \"next/link\";\r\nimport ProtectedRoute from \"@/components/ProtectedRoute\";\r\nimport { useAuth } from \"@/hooks/useAuth\";\r\nimport { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, buscarMedicos, type Paciente, buscarRelatorioPorId, atualizarMedico } from \"@/lib/api\";\r\nimport { useReports } from \"@/hooks/useReports\";\r\nimport { CreateReportData } from \"@/types/report-types\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Input } from \"@/components/ui/input\";\r\nimport { Label } from \"@/components/ui/label\";\r\nimport { Textarea } from \"@/components/ui/textarea\";\r\nimport { SimpleThemeToggle } from \"@/components/simple-theme-toggle\";\r\nimport {\r\n Table,\r\n TableBody,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from \"@/components/ui/table\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\"\r\nimport { User, FolderOpen, X, Users, MessageSquare, ClipboardList, Plus, Edit, Trash2, ChevronLeft, ChevronRight, Clock, FileCheck, Upload, Download, Eye, History, Stethoscope, Pill, Activity, Search } from \"lucide-react\"\r\nimport { Calendar as CalendarIcon, FileText, Settings } from \"lucide-react\";\r\nimport {\r\n Tooltip,\r\n TooltipContent,\r\n TooltipProvider,\r\n TooltipTrigger,\r\n} from \"@/components/ui/tooltip\";\r\n\r\n\r\nimport dynamic from \"next/dynamic\";\r\nimport dayGridPlugin from \"@fullcalendar/daygrid\";\r\nimport timeGridPlugin from \"@fullcalendar/timegrid\";\r\nimport interactionPlugin from \"@fullcalendar/interaction\";\r\nimport ptBrLocale from \"@fullcalendar/core/locales/pt-br\";\r\n\r\nconst FullCalendar = dynamic(() => import(\"@fullcalendar/react\"), {\r\n ssr: false,\r\n});\r\n\r\n// pacientes will be loaded inside the component (hooks must run in component body)\r\n\r\n// removed static medico placeholder; will load real profile for logged-in user\r\n\r\n\r\nconst colorsByType = {\r\n Rotina: \"#4dabf7\",\r\n Cardiologia: \"#f76c6c\",\r\n Otorrino: \"#f7b84d\",\r\n Pediatria: \"#6cf78b\",\r\n Dermatologia: \"#9b59b6\",\r\n Oftalmologia: \"#2ecc71\"\r\n};\r\n\r\n // Helpers para normalizar dados de paciente (suporta schema antigo e novo)\r\n const getPatientName = (p: any) => p?.full_name ?? p?.nome ?? '';\r\n const getPatientCpf = (p: any) => p?.cpf ?? '';\r\n const getPatientSex = (p: any) => p?.sex ?? p?.sexo ?? '';\r\n const getPatientId = (p: any) => p?.id ?? '';\r\n const getPatientAge = (p: any) => {\r\n if (!p) return '';\r\n // Prefer birth_date (ISO) to calcular idade\r\n const bd = p?.birth_date ?? p?.data_nascimento ?? p?.birthDate;\r\n if (bd) {\r\n const d = new Date(bd);\r\n if (!isNaN(d.getTime())) {\r\n const age = Math.floor((Date.now() - d.getTime()) / (1000 * 60 * 60 * 24 * 365.25));\r\n return `${age}`;\r\n }\r\n }\r\n // Fallback para campo idade/idade_anterior\r\n return p?.idade ?? p?.age ?? '';\r\n };\r\n\r\n // Helpers para normalizar campos do laudo/relatório\r\n const getReportPatientName = (r: any) => r?.paciente?.full_name ?? r?.paciente?.nome ?? r?.patient?.full_name ?? r?.patient?.nome ?? r?.patient_name ?? r?.patient_full_name ?? '';\r\n const getReportPatientId = (r: any) => r?.paciente?.id ?? r?.patient?.id ?? r?.patient_id ?? r?.patientId ?? r?.patient_id_raw ?? r?.patient_id ?? r?.id ?? '';\r\n const getReportPatientCpf = (r: any) => r?.paciente?.cpf ?? r?.patient?.cpf ?? r?.patient_cpf ?? '';\r\n const getReportExecutor = (r: any) => r?.executante ?? r?.requested_by ?? r?.requestedBy ?? r?.created_by ?? r?.createdBy ?? r?.requested_by_name ?? r?.executor ?? '';\r\n const getReportExam = (r: any) => r?.exame ?? r?.exam ?? r?.especialidade ?? r?.cid_code ?? r?.report_type ?? '-';\r\n const getReportDate = (r: any) => r?.data ?? r?.created_at ?? r?.due_at ?? r?.report_date ?? '';\r\n const formatReportDate = (raw?: string) => {\r\n if (!raw) return '-';\r\n try {\r\n const d = new Date(raw);\r\n if (isNaN(d.getTime())) return raw;\r\n return d.toLocaleDateString('pt-BR');\r\n } catch (e) {\r\n return raw;\r\n }\r\n };\r\n\r\nconst ProfissionalPage = () => {\r\n const { logout, user } = useAuth();\r\n const [activeSection, setActiveSection] = useState('calendario');\r\n const [pacienteSelecionado, setPacienteSelecionado] = useState<any>(null);\r\n \r\n // Estados para edição de laudo\r\n const [isEditingLaudoForPatient, setIsEditingLaudoForPatient] = useState(false);\r\n const [patientForLaudo, setPatientForLaudo] = useState<any>(null);\r\n \r\n // Estados para o perfil do médico\r\n const [isEditingProfile, setIsEditingProfile] = useState(false);\r\n const [doctorId, setDoctorId] = useState<string | null>(null);\r\n // Removemos o placeholder extenso — inicializamos com valores minimalistas e vazios.\r\n const [profileData, setProfileData] = useState({\r\n nome: '',\r\n email: user?.email || '',\r\n telefone: '',\r\n endereco: '',\r\n cidade: '',\r\n cep: '',\r\n crm: '',\r\n especialidade: '',\r\n // biografia field removed — not present in Medico records\r\n fotoUrl: ''\r\n });\r\n\r\n // pacientes carregados dinamicamente (hooks devem ficar dentro do componente)\r\n const [pacientes, setPacientes] = useState<any[]>([]);\r\n useEffect(() => {\r\n let mounted = true;\r\n (async () => {\r\n try {\r\n if (!user || !user.id) {\r\n if (mounted) setPacientes([]);\r\n return;\r\n }\r\n\r\n const assignmentsMod = await import('@/lib/assignment');\r\n if (!assignmentsMod || typeof assignmentsMod.listAssignmentsForUser !== 'function') {\r\n if (mounted) setPacientes([]);\r\n return;\r\n }\r\n\r\n const assignments = await assignmentsMod.listAssignmentsForUser(user.id || '');\r\n const patientIds = Array.isArray(assignments) ? assignments.map((a:any) => String(a.patient_id)).filter(Boolean) : [];\r\n if (!patientIds.length) {\r\n if (mounted) setPacientes([]);\r\n return;\r\n }\r\n\r\n const patients = await buscarPacientesPorIds(patientIds);\r\n const normalized = (patients || []).map((p: any) => ({\r\n ...p,\r\n nome: p.full_name ?? (p as any).nome ?? '',\r\n cpf: p.cpf ?? '',\r\n idade: getPatientAge(p) // preencher idade para a tabela de pacientes\r\n }));\r\n if (mounted) setPacientes(normalized);\r\n } catch (err) {\r\n console.warn('[ProfissionalPage] falha ao carregar pacientes atribuídos:', err);\r\n if (mounted) setPacientes([]);\r\n }\r\n })();\r\n return () => { mounted = false; };\r\n }, [user?.id, doctorId]);\r\n\r\n // Carregar perfil do médico correspondente ao usuário logado\r\n useEffect(() => {\r\n let mounted = true;\r\n (async () => {\r\n try {\r\n if (!user || !user.email) return;\r\n // Tenta buscar médicos pelo email do usuário (buscarMedicos lida com queries por email)\r\n const docs = await buscarMedicos(user.email);\r\n if (!mounted) return;\r\n if (Array.isArray(docs) && docs.length > 0) {\r\n // preferir registro cujo user_id bate com user.id\r\n let chosen = docs.find(d => String((d as any).user_id) === String(user.id)) || docs[0];\r\n if (chosen) {\r\n // store the doctor's id so we can update it later\r\n try { setDoctorId((chosen as any).id ?? null); } catch {};\r\n // Especialidade pode vir como 'specialty' (inglês), 'especialidade' (pt),\r\n // ou até uma lista/array. Normalizamos para string.\r\n const rawSpecialty = (chosen as any).specialty ?? (chosen as any).especialidade ?? (chosen as any).especialidades ?? (chosen as any).especiality;\r\n let specialtyStr = '';\r\n if (Array.isArray(rawSpecialty)) {\r\n specialtyStr = rawSpecialty.join(', ');\r\n } else if (rawSpecialty) {\r\n specialtyStr = String(rawSpecialty);\r\n }\r\n\r\n // Foto pode vir como 'foto_url' ou 'fotoUrl' ou 'avatar_url'\r\n const foto = (chosen as any).foto_url || (chosen as any).fotoUrl || (chosen as any).avatar_url || '';\r\n\r\n setProfileData((prev) => ({\r\n ...prev,\r\n nome: (chosen as any).full_name || (chosen as any).nome_social || prev.nome || user?.email?.split('@')[0] || '',\r\n email: (chosen as any).email || user?.email || prev.email,\r\n telefone: (chosen as any).phone_mobile || (chosen as any).celular || (chosen as any).telefone || (chosen as any).phone || (chosen as any).mobile || (user as any)?.user_metadata?.phone || prev.telefone,\r\n endereco: (chosen as any).street || (chosen as any).endereco || prev.endereco,\r\n cidade: (chosen as any).city || (chosen as any).cidade || prev.cidade,\r\n cep: (chosen as any).cep || prev.cep,\r\n // store raw CRM (only the number) to avoid double-prefixing when rendering\r\n crm: (chosen as any).crm ? String((chosen as any).crm).replace(/^(?:CRM\\s*)+/i, '').trim() : (prev.crm || ''),\r\n especialidade: specialtyStr || prev.especialidade || '',\r\n // biografia removed: prefer to ignore observacoes/curriculo_url here\r\n // (if needed elsewhere, render directly from chosen.observacoes)\r\n fotoUrl: foto || prev.fotoUrl || ''\r\n }));\r\n }\r\n }\r\n } catch (e) {\r\n console.warn('[ProfissionalPage] falha ao carregar perfil do médico pelo email:', e);\r\n }\r\n })();\r\n return () => { mounted = false; };\r\n }, [user?.id, user?.email]);\r\n\r\n\r\n\r\n // Estados para campos principais da consulta\r\n const [consultaAtual, setConsultaAtual] = useState({\r\n patient_id: \"\",\r\n order_number: \"\",\r\n exam: \"\",\r\n diagnosis: \"\",\r\n conclusion: \"\",\r\n cid_code: \"\",\r\n content_html: \"\",\r\n content_json: {},\r\n status: \"draft\",\r\n requested_by: \"\",\r\n due_at: new Date().toISOString(),\r\n hide_date: true,\r\n hide_signature: true\r\n });\r\n\r\n \r\n \r\n const [events, setEvents] = useState<any[]>([\r\n \r\n {\r\n id: 1,\r\n title: \"Ana Souza\",\r\n type: \"Cardiologia\",\r\n time: \"09:00\",\r\n date: new Date().toISOString().split('T')[0], \r\n pacienteId: \"123.456.789-00\",\r\n color: colorsByType.Cardiologia\r\n },\r\n {\r\n id: 2,\r\n title: \"Bruno Lima\",\r\n type: \"Cardiologia\",\r\n time: \"10:30\",\r\n date: new Date().toISOString().split('T')[0], \r\n pacienteId: \"987.654.321-00\",\r\n color: colorsByType.Cardiologia\r\n },\r\n {\r\n id: 3,\r\n title: \"Carla Menezes\",\r\n type: \"Dermatologia\",\r\n time: \"14:00\",\r\n date: new Date().toISOString().split('T')[0], \r\n pacienteId: \"111.222.333-44\",\r\n color: colorsByType.Dermatologia\r\n }\r\n ]);\r\n const [editingEvent, setEditingEvent] = useState<any>(null);\r\n const [showPopup, setShowPopup] = useState(false);\r\n const [showActionModal, setShowActionModal] = useState(false);\r\n const [step, setStep] = useState(1);\r\n const [newEvent, setNewEvent] = useState({ \r\n title: \"\", \r\n type: \"\", \r\n time: \"\",\r\n pacienteId: \"\" \r\n });\r\n const [selectedDate, setSelectedDate] = useState<string | null>(null);\r\n const [selectedEvent, setSelectedEvent] = useState<any>(null);\r\n const [currentCalendarDate, setCurrentCalendarDate] = useState(new Date());\r\n\r\n const handleSave = (event: React.MouseEvent<HTMLButtonElement>) => {\r\n event.preventDefault();\r\n console.log(\"Laudo salvo!\");\r\n window.scrollTo({ top: 0, behavior: \"smooth\" });\r\n };\r\n\r\n \r\n\r\n const handleEditarLaudo = (paciente: any) => {\r\n setPatientForLaudo(paciente);\r\n setIsEditingLaudoForPatient(true);\r\n setActiveSection('laudos');\r\n };\r\n\r\n \r\n const navigateDate = (direction: 'prev' | 'next') => {\r\n const newDate = new Date(currentCalendarDate);\r\n newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1));\r\n setCurrentCalendarDate(newDate);\r\n };\r\n\r\n const goToToday = () => {\r\n setCurrentCalendarDate(new Date());\r\n };\r\n\r\n const formatDate = (date: Date) => {\r\n return date.toLocaleDateString('pt-BR', { \r\n weekday: 'long', \r\n day: 'numeric', \r\n month: 'long', \r\n year: 'numeric' \r\n });\r\n };\r\n\r\n // Filtrar eventos do dia atual\r\n const getTodayEvents = () => {\r\n const today = currentCalendarDate.toISOString().split('T')[0];\r\n return events\r\n .filter(event => event.date === today)\r\n .sort((a, b) => a.time.localeCompare(b.time));\r\n };\r\n\r\n const getStatusColor = (type: string) => {\r\n return colorsByType[type as keyof typeof colorsByType] || \"#4dabf7\";\r\n };\r\n\r\n // Funções para o perfil\r\n const handleProfileChange = (field: string, value: string) => {\r\n setProfileData(prev => ({\r\n ...prev,\r\n [field]: value\r\n }));\r\n };\r\n\r\n const handleSaveProfile = () => {\r\n (async () => {\r\n if (!doctorId) {\r\n alert('Não foi possível localizar o registro do médico para atualizar.');\r\n setIsEditingProfile(false);\r\n return;\r\n }\r\n\r\n // Build payload mapping UI fields to DB columns\r\n const payload: any = {};\r\n if (profileData.email) payload.email = profileData.email;\r\n if (profileData.telefone) payload.phone_mobile = profileData.telefone;\r\n if (profileData.endereco) payload.street = profileData.endereco;\r\n if (profileData.cidade) payload.city = profileData.cidade;\r\n if (profileData.cep) payload.cep = profileData.cep;\r\n if (profileData.especialidade) payload.specialty = profileData.especialidade || profileData.especialidade;\r\n if (profileData.fotoUrl) payload.foto_url = profileData.fotoUrl;\r\n\r\n // Don't allow updating full_name or crm from this UI\r\n\r\n try {\r\n const updated = await atualizarMedico(doctorId, payload as any);\r\n console.debug('[ProfissionalPage] médico atualizado:', updated);\r\n alert('Perfil atualizado com sucesso!');\r\n } catch (err: any) {\r\n console.error('[ProfissionalPage] falha ao atualizar médico:', err);\r\n // Mostrar mensagem amigável (o erro já é tratado em lib/api)\r\n alert(err?.message || 'Falha ao atualizar perfil. Verifique logs.');\r\n } finally {\r\n setIsEditingProfile(false);\r\n }\r\n })();\r\n };\r\n\r\n const handleCancelEdit = () => {\r\n setIsEditingProfile(false);\r\n };\r\n\r\n\r\n\r\n \r\n const handleDateClick = (arg: any) => {\r\n setSelectedDate(arg.dateStr);\r\n setNewEvent({ title: \"\", type: \"\", time: \"\", pacienteId: \"\" });\r\n setStep(1);\r\n setEditingEvent(null);\r\n setShowPopup(true);\r\n };\r\n\r\n \r\n const handleAddEvent = () => {\r\n const paciente = pacientes.find(p => p.nome === newEvent.title);\r\n const eventToAdd = {\r\n id: Date.now(),\r\n title: newEvent.title,\r\n type: newEvent.type,\r\n time: newEvent.time,\r\n date: selectedDate || currentCalendarDate.toISOString().split('T')[0],\r\n pacienteId: paciente ? paciente.cpf : \"\",\r\n color: colorsByType[newEvent.type as keyof typeof colorsByType] || \"#4dabf7\"\r\n };\r\n setEvents((prev) => [...prev, eventToAdd]);\r\n setShowPopup(false);\r\n };\r\n\r\n\r\n const handleEditEvent = () => {\r\n setEvents((prevEvents) =>\r\n prevEvents.map((ev) =>\r\n ev.id.toString() === editingEvent.id.toString()\r\n ? {\r\n ...ev,\r\n title: newEvent.title,\r\n type: newEvent.type,\r\n time: newEvent.time,\r\n color: colorsByType[newEvent.type as keyof typeof colorsByType] || \"#4dabf7\"\r\n }\r\n : ev\r\n )\r\n );\r\n setEditingEvent(null);\r\n setShowPopup(false);\r\n setShowActionModal(false);\r\n };\r\n\r\n \r\n const handleNextStep = () => {\r\n if (step < 3) setStep(step + 1);\r\n else editingEvent ? handleEditEvent() : handleAddEvent();\r\n };\r\n\r\n \r\n const handleEventClick = (clickInfo: any) => {\r\n setSelectedEvent(clickInfo.event);\r\n setShowActionModal(true);\r\n };\r\n\r\n\r\n const handleDeleteEvent = () => {\r\n if (!selectedEvent) return;\r\n setEvents((prevEvents) =>\r\n prevEvents.filter((ev: any) => ev.id.toString() !== selectedEvent.id.toString())\r\n );\r\n setShowActionModal(false);\r\n };\r\n\r\n \r\n const handleStartEdit = () => {\r\n if (!selectedEvent) return;\r\n setEditingEvent(selectedEvent);\r\n setNewEvent({\r\n title: selectedEvent.title,\r\n type: selectedEvent.extendedProps.type,\r\n time: selectedEvent.extendedProps.time,\r\n pacienteId: selectedEvent.extendedProps.pacienteId || \"\"\r\n });\r\n setStep(1);\r\n setShowActionModal(false);\r\n setShowPopup(true);\r\n };\r\n\r\n \r\n const renderEventContent = (eventInfo: any) => {\r\n const bg = eventInfo.event.backgroundColor || eventInfo.event.extendedProps?.color || \"#4dabf7\";\r\n\r\n return (\r\n <div\r\n className=\"flex items-center gap-1 text-xs p-1 rounded cursor-pointer\"\r\n style={{\r\n backgroundColor: bg,\r\n color: \"#fff\",\r\n maxWidth: \"100%\",\r\n whiteSpace: \"nowrap\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\"\r\n }}\r\n title={`${eventInfo.event.title} • ${eventInfo.event.extendedProps.type} • ${eventInfo.event.extendedProps.time}`}\r\n >\r\n <span className=\"truncate\">{eventInfo.event.title}</span>\r\n <span>•</span>\r\n <span className=\"truncate\">{eventInfo.event.extendedProps.type}</span>\r\n <span>•</span>\r\n <span>{eventInfo.event.extendedProps.time}</span>\r\n </div>\r\n );\r\n };\r\n\r\n \r\n const renderCalendarioSection = () => {\r\n const todayEvents = getTodayEvents();\r\n \r\n return (\r\n <section className=\"bg-card shadow-md rounded-lg border border-border p-6\">\r\n <div className=\"flex justify-between items-center mb-4\">\r\n <h2 className=\"text-2xl font-bold\">Agenda do Dia</h2>\r\n </div>\r\n \r\n {/* Navegação de Data */}\r\n <div className=\"flex items-center justify-between mb-6 p-4 bg-blue-50 rounded-lg dark:bg-muted\">\r\n <div className=\"flex items-center space-x-4\">\r\n <Button \r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => navigateDate('prev')}\r\n className=\"p-2 hover:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground\"\r\n >\r\n <ChevronLeft className=\"h-4 w-4 hover:!text-white\" />\r\n </Button>\r\n <h3 className=\"text-lg font-medium text-foreground\">\r\n {formatDate(currentCalendarDate)}\r\n </h3>\r\n <Button \r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => navigateDate('next')}\r\n className=\"p-2 hover:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground\"\r\n >\r\n <ChevronRight className=\"h-4 w-4 hover:!text-white\" />\r\n </Button>\r\n \r\n </div>\r\n <div className=\"text-sm text-gray-600 dark:text-muted-foreground\">\r\n {todayEvents.length} consulta{todayEvents.length !== 1 ? 's' : ''} agendada{todayEvents.length !== 1 ? 's' : ''}\r\n </div>\r\n </div>\r\n\r\n {/* Lista de Pacientes do Dia */}\r\n <div className=\"space-y-4\">\r\n {todayEvents.length === 0 ? (\r\n <div className=\"text-center py-8 text-gray-600 dark:text-muted-foreground\">\r\n <CalendarIcon className=\"h-12 w-12 mx-auto mb-4 text-gray-400 dark:text-muted-foreground/50\" />\r\n <p className=\"text-lg mb-2\">Nenhuma consulta agendada para este dia</p>\r\n <p className=\"text-sm\">Agenda livre para este dia</p>\r\n </div>\r\n ) : (\r\n todayEvents.map((appointment) => {\r\n const paciente = pacientes.find(p => p.nome === appointment.title);\r\n return (\r\n <div\r\n key={appointment.id}\r\n className=\"border-l-4 border-t border-r border-b p-4 rounded-lg shadow-sm bg-card border-border\"\r\n style={{ borderLeftColor: getStatusColor(appointment.type) }}\r\n >\r\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4 items-center\">\r\n <div className=\"flex items-center\">\r\n <div \r\n className=\"w-3 h-3 rounded-full mr-3\"\r\n style={{ backgroundColor: getStatusColor(appointment.type) }}\r\n ></div>\r\n <div>\r\n <div className=\"font-medium flex items-center\">\r\n <User className=\"h-4 w-4 mr-2 text-gray-500 dark:text-muted-foreground\" />\r\n {appointment.title}\r\n </div>\r\n {paciente && (\r\n <div className=\"text-sm text-gray-600 dark:text-muted-foreground\">\r\n CPF: {getPatientCpf(paciente)} • {getPatientAge(paciente)} anos\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n <div className=\"flex items-center\">\r\n <Clock className=\"h-4 w-4 mr-2 text-gray-500 dark:text-muted-foreground\" />\r\n <span className=\"font-medium\">{appointment.time}</span>\r\n </div>\r\n <div className=\"flex items-center\">\r\n <div \r\n className=\"px-3 py-1 rounded-full text-sm font-medium text-white\"\r\n style={{ backgroundColor: getStatusColor(appointment.type) }}\r\n >\r\n {appointment.type}\r\n </div>\r\n </div>\r\n <div className=\"flex items-center justify-end space-x-2\">\r\n <div className=\"relative group\">\r\n <div className=\"absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded-md opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none whitespace-nowrap z-50\">\r\n Ver informações do paciente\r\n <div className=\"absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900 dark:border-t-gray-100\"></div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n })\r\n )}\r\n </div>\r\n </section>\r\n );\r\n };\r\n\r\n \r\n \r\n \r\n const renderLaudosSection = () => (\r\n <div className=\"space-y-6\">\r\n <LaudoManager \r\n isEditingForPatient={isEditingLaudoForPatient}\r\n selectedPatientForLaudo={patientForLaudo}\r\n onClosePatientEditor={() => {\r\n setIsEditingLaudoForPatient(false);\r\n setPatientForLaudo(null);\r\n }}\r\n />\r\n </div>\r\n );\r\n\r\n // --- NOVO SISTEMA DE LAUDOS COMPLETO ---\r\n function LaudoManager({ isEditingForPatient, selectedPatientForLaudo, onClosePatientEditor }: { isEditingForPatient?: boolean; selectedPatientForLaudo?: any; onClosePatientEditor?: () => void }) {\r\n const [pacientesDisponiveis] = useState([\r\n { id: \"95170038\", nome: \"Ana Souza\", cpf: \"123.456.789-00\", idade: 42, sexo: \"Feminino\" },\r\n { id: \"93203056\", nome: \"Bruno Lima\", cpf: \"987.654.321-00\", idade: 33, sexo: \"Masculino\" },\r\n { id: \"92953542\", nome: \"Carla Menezes\", cpf: \"111.222.333-44\", idade: 67, sexo: \"Feminino\" },\r\n ]);\r\n\r\n const { reports, loadReports, loadReportById, loading: reportsLoading, createNewReport, updateExistingReport } = useReports();\r\n const [laudos, setLaudos] = useState<any[]>([]);\r\n const [selectedRange, setSelectedRange] = useState<'todos'|'semana'|'mes'|'custom'>('mes');\r\n const [startDate, setStartDate] = useState<string | null>(null);\r\n const [endDate, setEndDate] = useState<string | null>(null);\r\n\r\n // helper to check if a date string is in range\r\n const isInRange = (dateStr: string | undefined, range: 'todos'|'semana'|'mes'|'custom') => {\r\n if (range === 'todos') return true;\r\n if (!dateStr) return false;\r\n const d = new Date(dateStr);\r\n if (isNaN(d.getTime())) return false;\r\n const now = new Date();\r\n \r\n if (range === 'semana') {\r\n const start = new Date(now);\r\n start.setDate(now.getDate() - now.getDay()); // sunday start\r\n const end = new Date(start);\r\n end.setDate(start.getDate() + 6);\r\n return d >= start && d <= end;\r\n }\r\n // mes\r\n return d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth();\r\n };\r\n\r\n // helper: ensure report has paciente object populated (fetch by id if necessary)\r\n const ensurePaciente = async (report: any) => {\r\n if (!report) return report;\r\n try {\r\n if (!report.paciente) {\r\n const pid = report.patient_id ?? report.patient ?? report.paciente ?? null;\r\n if (pid) {\r\n try {\r\n const p = await buscarPacientePorId(String(pid));\r\n if (p) report.paciente = p;\r\n } catch (e) {\r\n // ignore\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n // ignore\r\n }\r\n return report;\r\n };\r\n\r\n // When selectedRange changes (and isn't custom), compute start/end dates\r\n useEffect(() => {\r\n const now = new Date();\r\n if (selectedRange === 'todos') {\r\n setStartDate(null);\r\n setEndDate(null);\r\n return;\r\n }\r\n \r\n if (selectedRange === 'semana') {\r\n const start = new Date(now);\r\n start.setDate(now.getDate() - now.getDay()); // sunday\r\n const end = new Date(start);\r\n end.setDate(start.getDate() + 6);\r\n setStartDate(start.toISOString().slice(0,10));\r\n setEndDate(end.toISOString().slice(0,10));\r\n return;\r\n }\r\n if (selectedRange === 'mes') {\r\n const start = new Date(now.getFullYear(), now.getMonth(), 1);\r\n const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);\r\n setStartDate(start.toISOString().slice(0,10));\r\n setEndDate(end.toISOString().slice(0,10));\r\n return;\r\n }\r\n // custom: leave startDate/endDate as-is\r\n }, [selectedRange]);\r\n\r\n const filteredLaudos = (laudos || []).filter(l => {\r\n // If a specific start/end date is set, use that range\r\n if (startDate && endDate) {\r\n const ds = getReportDate(l);\r\n if (!ds) return false;\r\n const d = new Date(ds);\r\n if (isNaN(d.getTime())) return false;\r\n const start = new Date(startDate + 'T00:00:00');\r\n const end = new Date(endDate + 'T23:59:59');\r\n return d >= start && d <= end;\r\n }\r\n // Fallback to selectedRange heuristics\r\n if (!selectedRange) return true;\r\n const ds = getReportDate(l);\r\n return isInRange(ds, selectedRange);\r\n });\r\n\r\n function DateRangeButtons() {\r\n return (\r\n <>\r\n <Button\r\n variant={selectedRange === 'todos' ? 'default' : 'outline'}\r\n size=\"sm\"\r\n onClick={() => setSelectedRange('todos')}\r\n className=\"hover:bg-blue-50\"\r\n >\r\n Todos\r\n </Button>\r\n <Button\r\n variant={selectedRange === 'semana' ? 'default' : 'outline'}\r\n size=\"sm\"\r\n onClick={() => setSelectedRange('semana')}\r\n className=\"hover:bg-blue-50\"\r\n >\r\n Semana\r\n </Button>\r\n <Button\r\n variant={selectedRange === 'mes' ? 'default' : 'outline'}\r\n size=\"sm\"\r\n onClick={() => setSelectedRange('mes')}\r\n className=\"hover:bg-blue-50\"\r\n >\r\n Mês\r\n </Button>\r\n </>\r\n );\r\n }\r\n\r\n // SearchBox inserido aqui para acessar reports, setLaudos e loadReports\r\n function SearchBox() {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n const [searching, setSearching] = useState(false);\r\n const { token } = useAuth();\r\n\r\n const isMaybeId = (s: string) => {\r\n const t = s.trim();\r\n if (!t) return false;\r\n if (t.includes('-') && t.length > 10) return true;\r\n if (t.toUpperCase().startsWith('REL-')) return true;\r\n const digits = t.replace(/\\D/g, '');\r\n if (digits.length >= 8) return true;\r\n return false;\r\n };\r\n\r\n const doSearch = async () => {\r\n const term = searchTerm.trim();\r\n if (!term) return;\r\n setSearching(true);\r\n try {\r\n if (isMaybeId(term)) {\r\n try {\r\n const r = await buscarRelatorioPorId(term);\r\n if (r) {\r\n // If token exists, attempt batch enrichment like useReports\r\n const enriched: any = { ...r };\r\n\r\n // Collect possible patient/doctor ids from payload\r\n const pidCandidates: string[] = [];\r\n const didCandidates: string[] = [];\r\n const pid = (r as any).patient_id ?? (r as any).patient ?? (r as any).paciente ?? null;\r\n if (pid) pidCandidates.push(String(pid));\r\n const possiblePatientName = (r as any).patient_name ?? (r as any).patient_full_name ?? (r as any).paciente?.full_name ?? (r as any).paciente?.nome ?? null;\r\n if (possiblePatientName) {\r\n enriched.paciente = enriched.paciente ?? {};\r\n enriched.paciente.full_name = possiblePatientName;\r\n }\r\n\r\n const did = (r as any).requested_by ?? (r as any).created_by ?? (r as any).executante ?? null;\r\n if (did) didCandidates.push(String(did));\r\n\r\n // If token available, perform batch fetch to get full patient/doctor objects\r\n if (token) {\r\n try {\r\n if (pidCandidates.length) {\r\n const patients = await buscarPacientesPorIds(pidCandidates);\r\n if (patients && patients.length) {\r\n const p = patients[0];\r\n enriched.paciente = enriched.paciente ?? {};\r\n enriched.paciente.full_name = enriched.paciente.full_name || p.full_name || (p as any).nome;\r\n enriched.paciente.id = enriched.paciente.id || p.id;\r\n enriched.paciente.cpf = enriched.paciente.cpf || p.cpf;\r\n }\r\n }\r\n if (didCandidates.length) {\r\n const doctors = await buscarMedicosPorIds(didCandidates);\r\n if (doctors && doctors.length) {\r\n const d = doctors[0];\r\n enriched.executante = enriched.executante || d.full_name || (d as any).nome;\r\n }\r\n }\r\n } catch (e) {\r\n // fallback: continue with payload-only enrichment\r\n console.warn('[SearchBox] batch enrichment failed, falling back to payload-only enrichment', e);\r\n }\r\n }\r\n\r\n // Final payload-only fallbacks (ensure id/cpf/order_number are populated)\r\n const possiblePatientId = (r as any).paciente?.id ?? (r as any).patient?.id ?? (r as any).patient_id ?? (r as any).patientId ?? (r as any).id ?? undefined;\r\n if (possiblePatientId && !enriched.paciente?.id) {\r\n enriched.paciente = enriched.paciente ?? {};\r\n enriched.paciente.id = possiblePatientId;\r\n }\r\n const possibleCpf = (r as any).patient_cpf ?? (r as any).paciente?.cpf ?? (r as any).patient?.cpf ?? null;\r\n if (possibleCpf) {\r\n enriched.paciente = enriched.paciente ?? {};\r\n enriched.paciente.cpf = possibleCpf;\r\n }\r\n const execName = (r as any).requested_by_name ?? (r as any).requester_name ?? (r as any).requestedByName ?? (r as any).executante_name ?? (r as any).created_by_name ?? (r as any).createdByName ?? (r as any).executante ?? (r as any).requested_by ?? (r as any).created_by ?? '';\r\n if (execName) enriched.executante = enriched.executante || execName;\r\n if ((r as any).order_number) enriched.order_number = (r as any).order_number;\r\n\r\n setLaudos([enriched]);\r\n return;\r\n }\r\n } catch (err: any) {\r\n console.warn('Relatório não encontrado por ID:', err);\r\n }\r\n }\r\n\r\n const lower = term.toLowerCase();\r\n const filtered = (reports || []).filter((x: any) => {\r\n const name = (x.paciente?.full_name || x.patient_name || x.patient_full_name || x.order_number || x.exame || x.exam || '').toString().toLowerCase();\r\n return name.includes(lower);\r\n });\r\n if (filtered.length) setLaudos(filtered);\r\n else setLaudos([]);\r\n } finally {\r\n setSearching(false);\r\n }\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLInputElement>) => {\r\n if (e.key === 'Enter') doSearch();\r\n };\r\n\r\n const handleClear = async () => {\r\n setSearchTerm('');\r\n await loadReports();\r\n setLaudos(reports || []);\r\n };\r\n\r\n return (\r\n <div>\r\n <div className=\"relative\">\r\n <Input\r\n placeholder=\"Buscar paciente / pedido / ID\"\r\n className=\"pl-10 h-10\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n onKeyDown={handleKey}\r\n />\r\n <svg className=\"absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\r\n </svg>\r\n </div>\r\n <div className=\"flex items-center gap-2 mt-2\">\r\n <Button size=\"sm\" onClick={doSearch} disabled={searching}>\r\n Buscar\r\n </Button>\r\n <Button size=\"sm\" variant=\"ghost\" onClick={handleClear}>\r\n Limpar\r\n </Button>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // carregar laudos ao montar - somente dos pacientes atribuídos ao médico logado\r\n useEffect(() => {\r\n let mounted = true;\r\n (async () => {\r\n try {\r\n // obter assignments para o usuário logado\r\n const assignments = await import('@/lib/assignment').then(m => m.listAssignmentsForUser(user?.id || ''));\r\n const patientIds = Array.isArray(assignments) ? assignments.map(a => String(a.patient_id)).filter(Boolean) : [];\r\n\r\n if (patientIds.length === 0) {\r\n if (mounted) setLaudos([]);\r\n return;\r\n }\r\n\r\n // Tentar carregar todos os relatórios em uma única chamada usando in.(...)\r\n try {\r\n const reportsMod = await import('@/lib/reports');\r\n if (typeof reportsMod.listarRelatoriosPorPacientes === 'function') {\r\n const batch = await reportsMod.listarRelatoriosPorPacientes(patientIds);\r\n // Filtrar apenas relatórios criados/solicitados por este usuário (evita mostrar laudos de outros médicos)\r\n const mineOnly = (batch || []).filter((r: any) => {\r\n const requester = ((r.requested_by ?? r.created_by ?? r.executante ?? r.requestedBy ?? r.createdBy) || '').toString();\r\n return user?.id && requester && requester === user.id;\r\n });\r\n // Enrich reports with paciente objects so UI shows name/cpf immediately\r\n const enriched = await (async (reportsArr: any[]) => {\r\n if (!reportsArr || !reportsArr.length) return reportsArr;\r\n const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean);\r\n if (!pids.length) return reportsArr;\r\n try {\r\n const patients = await buscarPacientesPorIds(pids);\r\n const map = new Map((patients || []).map((p: any) => [String(p.id), p]));\r\n return reportsArr.map(r => {\r\n const pid = String(getReportPatientId(r));\r\n return { ...r, paciente: r.paciente ?? map.get(pid) ?? r.paciente };\r\n });\r\n } catch (e) {\r\n return reportsArr;\r\n }\r\n })(mineOnly);\r\n if (mounted) setLaudos(enriched || []);\r\n } else {\r\n // fallback: 请求 por paciente individual\r\n const allReports: any[] = [];\r\n for (const pid of patientIds) {\r\n try {\r\n const rels = await import('@/lib/reports').then(m => m.listarRelatoriosPorPaciente(pid));\r\n if (Array.isArray(rels) && rels.length) {\r\n // filtrar por autor (requested_by / created_by / executante)\r\n const mine = rels.filter((r: any) => {\r\n const requester = ((r.requested_by ?? r.created_by ?? r.executante ?? r.requestedBy ?? r.createdBy) || '').toString();\r\n return user?.id && requester && requester === user.id;\r\n });\r\n if (mine.length) allReports.push(...mine);\r\n }\r\n } catch (err) {\r\n console.warn('[LaudoManager] falha ao carregar relatórios para paciente', pid, err);\r\n }\r\n }\r\n // enrich fallback results too\r\n const enrichedAll = await (async (reportsArr: any[]) => {\r\n if (!reportsArr || !reportsArr.length) return reportsArr;\r\n const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean);\r\n if (!pids.length) return reportsArr;\r\n try {\r\n const patients = await buscarPacientesPorIds(pids);\r\n const map = new Map((patients || []).map((p: any) => [String(p.id), p]));\r\n return reportsArr.map(r => ({ ...r, paciente: r.paciente ?? map.get(String(getReportPatientId(r))) ?? r.paciente }));\r\n } catch (e) {\r\n return reportsArr;\r\n }\r\n })(allReports);\r\n if (mounted) setLaudos(enrichedAll);\r\n }\r\n } catch (err) {\r\n console.warn('[LaudoManager] erro ao carregar relatórios em batch, tentando por paciente individual', err);\r\n const allReports: any[] = [];\r\n for (const pid of patientIds) {\r\n try {\r\n const rels = await import('@/lib/reports').then(m => m.listarRelatoriosPorPaciente(pid));\r\n if (Array.isArray(rels) && rels.length) {\r\n const mine = rels.filter((r: any) => {\r\n const requester = ((r.requested_by ?? r.created_by ?? r.executante ?? r.requestedBy ?? r.createdBy) || '').toString();\r\n return user?.id && requester && requester === user.id;\r\n });\r\n if (mine.length) allReports.push(...mine);\r\n }\r\n } catch (e) {\r\n console.warn('[LaudoManager] falha ao carregar relatórios para paciente', pid, e);\r\n }\r\n }\r\n const enrichedAll = await (async (reportsArr: any[]) => {\r\n if (!reportsArr || !reportsArr.length) return reportsArr;\r\n const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean);\r\n if (!pids.length) return reportsArr;\r\n try {\r\n const patients = await buscarPacientesPorIds(pids);\r\n const map = new Map((patients || []).map((p: any) => [String(p.id), p]));\r\n return reportsArr.map(r => ({ ...r, paciente: r.paciente ?? map.get(String(getReportPatientId(r))) ?? r.paciente }));\r\n } catch (e) {\r\n return reportsArr;\r\n }\r\n })(allReports);\r\n if (mounted) setLaudos(enrichedAll);\r\n }\r\n } catch (e) {\r\n console.warn('[LaudoManager] erro ao carregar laudos para pacientes atribuídos:', e);\r\n if (mounted) setLaudos(reports || []);\r\n }\r\n })();\r\n return () => { mounted = false; };\r\n }, [user?.id]);\r\n\r\n // sincroniza quando reports mudarem no hook (fallback)\r\n useEffect(() => {\r\n if (!laudos || laudos.length === 0) setLaudos(reports || []);\r\n }, [reports]);\r\n\r\n // Sort reports newest-first (more recent dates at the top)\r\n const sortedLaudos = React.useMemo(() => {\r\n const arr = (filteredLaudos || []).slice();\r\n arr.sort((a: any, b: any) => {\r\n try {\r\n const da = new Date(getReportDate(a) || 0).getTime() || 0;\r\n const db = new Date(getReportDate(b) || 0).getTime() || 0;\r\n return db - da;\r\n } catch (e) {\r\n return 0;\r\n }\r\n });\r\n return arr;\r\n }, [filteredLaudos]);\r\n\r\n const [activeTab, setActiveTab] = useState(\"descobrir\");\r\n const [laudoSelecionado, setLaudoSelecionado] = useState<any>(null);\r\n const [isViewing, setIsViewing] = useState(false);\r\n const [isCreatingNew, setIsCreatingNew] = useState(false);\r\n\r\n\r\n\r\n\r\n return (\r\n <div className=\"space-y-6\">\r\n {/* Header */}\r\n <div className=\"bg-card rounded-lg p-6 shadow-md\">\r\n <div className=\"flex items-center justify-between\">\r\n <div>\r\n <h1 className=\"text-2xl font-bold text-foreground mb-2\">Gerenciamento de Laudo</h1>\r\n <p className=\"text-muted-foreground\">Nesta seção você pode gerenciar todos os laudos gerados.</p>\r\n </div>\r\n <Button \r\n onClick={() => setIsCreatingNew(true)}\r\n className=\"flex items-center gap-2\"\r\n >\r\n <Plus className=\"w-4 h-4\" />\r\n Novo Laudo\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n {/* Tabs */}\r\n <div className=\"bg-card rounded-lg shadow-md\">\r\n <div className=\"flex border-b border-border\">\r\n <button\r\n onClick={() => setActiveTab(\"descobrir\")}\r\n className={`px-4 py-3 text-sm font-medium border-b-2 transition-colors ${\r\n activeTab === \"descobrir\"\r\n ? \"border-blue-500 text-blue-600\"\r\n : \"border-transparent text-muted-foreground hover:text-foreground\"\r\n }`}\r\n >\r\n A descobrir\r\n </button>\r\n </div>\r\n\r\n {/* Filtros */}\r\n <div className=\"p-4 border-b border-border\">\r\n <div className=\"flex flex-wrap items-center gap-4\">\r\n <div className=\"relative flex-1 min-w-[200px]\">\r\n {/* Search input integrado com busca por ID */}\r\n <SearchBox />\r\n </div>\r\n \r\n <div className=\"flex items-center gap-2\">\r\n <div className=\"flex items-center gap-1 text-sm\">\r\n <CalendarIcon className=\"w-4 h-4\" />\r\n <Input type=\"date\" value={startDate ?? ''} onChange={(e) => { setStartDate(e.target.value); setSelectedRange('custom'); }} className=\"p-1 text-sm h-10\" />\r\n <span className=\"inline-flex items-center px-1 text-sm\">-</span>\r\n <Input type=\"date\" value={endDate ?? ''} onChange={(e) => { setEndDate(e.target.value); setSelectedRange('custom'); }} className=\"p-1 text-sm h-10\" />\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex gap-2 items-center\">\r\n {/* date range buttons: Semana / Mês */}\r\n <DateRangeButtons />\r\n </div>\r\n\r\n {/* Filtros e pesquisa removidos por solicitação */}\r\n </div>\r\n </div>\r\n\r\n {/* Tabela para desktop e cards empilháveis para mobile */}\r\n <div>\r\n {/* Desktop / tablet (md+) - tabela com scroll horizontal */}\r\n <div className=\"hidden md:block overflow-x-auto\">\r\n <Table>\r\n <TableHeader>\r\n <TableRow>\r\n <TableHead>Pedido</TableHead>\r\n <TableHead>Data</TableHead>\r\n <TableHead>Prazo</TableHead>\r\n <TableHead>Paciente</TableHead>\r\n <TableHead>Executante/Solicitante</TableHead>\r\n <TableHead>Exame/Classificação</TableHead>\r\n <TableHead>Ação</TableHead>\r\n </TableRow>\r\n </TableHeader>\r\n <TableBody>\r\n {sortedLaudos.map((laudo, idx) => (\r\n <TableRow key={`${(laudo?.id ?? laudo?.order_number ?? getReportPatientId(laudo) ?? 'laudo')}-${idx}`}>\r\n <TableCell>\r\n <div className=\"flex items-center gap-2\">\r\n {laudo.urgente && (\r\n <div className=\"w-2 h-2 rounded-full bg-red-500\"></div>\r\n )}\r\n <span className=\"font-mono text-sm\">\r\n {getReportPatientName(laudo) || laudo.order_number || getShortId(laudo.id)}\r\n </span>\r\n </div>\r\n </TableCell>\r\n <TableCell>\r\n <div className=\"text-sm\">\r\n <div>{formatReportDate(getReportDate(laudo))}</div>\r\n <div className=\"text-xs text-muted-foreground\">{laudo?.hora || new Date(laudo?.data || laudo?.created_at || laudo?.due_at || Date.now()).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>\r\n </div>\r\n </TableCell>\r\n <TableCell>\r\n <div className=\"text-sm\">\r\n <div>{(laudo?.prazo ?? laudo?.due_at) ? formatReportDate(laudo?.due_at ?? laudo?.prazo) : '-'}</div>\r\n <div className=\"text-xs text-muted-foreground\">{\r\n (() => {\r\n // prefer explicit fields\r\n const explicit = laudo?.prazo_hora ?? laudo?.due_time ?? laudo?.hora ?? null;\r\n if (explicit) return explicit;\r\n // fallback: try to parse due_at / prazo datetime and extract time\r\n const due = laudo?.due_at ?? laudo?.prazo ?? laudo?.dueDate ?? laudo?.data ?? null;\r\n if (!due) return '-';\r\n try {\r\n const d = new Date(due);\r\n if (isNaN(d.getTime())) return '-';\r\n return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\r\n } catch (e) {\r\n return '-';\r\n }\r\n })()\r\n }</div>\r\n </div>\r\n </TableCell>\r\n <TableCell>\r\n <div className=\"text-sm\">\r\n <div className=\"flex items-center gap-1\">\r\n <User className=\"w-3 h-3\" />\r\n </div>\r\n <div className=\"font-medium\">{getReportPatientName(laudo) || '—'}</div>\r\n <div className=\"text-xs text-muted-foreground\">{getReportPatientCpf(laudo) ? `CPF: ${getReportPatientCpf(laudo)}` : ''}</div>\r\n </div>\r\n </TableCell>\r\n <TableCell className=\"text-sm\">{\r\n (() => {\r\n const possibleName = laudo.requested_by_name ?? laudo.requester_name ?? laudo.requestedByName ?? laudo.executante_name ?? laudo.executante ?? laudo.executante_name ?? laudo.executante;\r\n if (possibleName && typeof possibleName === 'string' && possibleName.trim().length) return possibleName;\r\n const possibleId = (laudo.requested_by ?? laudo.created_by ?? laudo.executante ?? laudo.requestedBy ?? laudo.createdBy) || '';\r\n if (possibleId && user?.id && possibleId === user.id) return (profileData as any)?.nome || user?.name || possibleId;\r\n return possibleName || possibleId || '-';\r\n })()\r\n }</TableCell>\r\n <TableCell className=\"text-sm\">{getReportExam(laudo) || \"-\"}</TableCell>\r\n <TableCell>\r\n <div className=\"flex items-center gap-2\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={async () => {\r\n try {\r\n const full = (laudo?.id || laudo?.order_number) ? await loadReportById(String(laudo?.id ?? laudo?.order_number)) : laudo;\r\n await ensurePaciente(full);\r\n setLaudoSelecionado(full);\r\n setIsViewing(true);\r\n } catch (e) {\r\n // fallback\r\n setLaudoSelecionado(laudo);\r\n setIsViewing(true);\r\n }\r\n }}\r\n className=\"flex items-center gap-1 hover:bg-blue-50 dark:hover:bg-accent dark:hover:text-accent-foreground\"\r\n >\r\n <Eye className=\"w-4 h-4\" />\r\n Ver Laudo\r\n </Button>\r\n <Button\r\n variant=\"default\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setPatientForLaudo(laudo);\r\n setIsEditingLaudoForPatient(true);\r\n }}\r\n className=\"flex items-center gap-1 bg-green-600 hover:bg-green-700 text-white\"\r\n title=\"Editar laudo para este paciente\"\r\n >\r\n <Edit className=\"w-4 h-4\" />\r\n Editar Laudo\r\n </Button>\r\n </div>\r\n </TableCell>\r\n </TableRow>\r\n ))}\r\n </TableBody>\r\n </Table>\r\n </div>\r\n\r\n {/* Mobile - cards empilháveis */}\r\n <div className=\"md:hidden space-y-3\">\r\n {sortedLaudos.map((laudo, idx) => (\r\n <div key={`${(laudo?.id ?? laudo?.order_number ?? getReportPatientId(laudo) ?? 'laudo-mobile')}-${idx}`} className=\"bg-card p-4 rounded-lg border border-border shadow-sm\">\r\n <div className=\"flex justify-between items-start\">\r\n <div className=\"flex-1\">\r\n <div className=\"flex items-center justify-between\">\r\n <div>\r\n <div className=\"text-sm font-medium\">{getReportExam(laudo) || '-'}</div>\r\n <div className=\"text-xs text-muted-foreground\">{formatReportDate(getReportDate(laudo))} {laudo?.hora ? `• ${laudo.hora}` : ''}</div>\r\n </div>\r\n <div className=\"ml-3 text-xs font-mono text-muted-foreground\">{getReportPatientName(laudo) ? getShortId(laudo.id) : ''}</div>\r\n </div>\r\n <div className=\"mt-2\">\r\n <div className=\"font-semibold\">{getReportPatientName(laudo) || '—'}</div>\r\n <div className=\"text-xs text-muted-foreground\">{getReportPatientCpf(laudo) ? `CPF: ${getReportPatientCpf(laudo)}` : ''}</div>\r\n </div>\r\n </div>\r\n <div className=\"flex flex-col items-end ml-4\">\r\n <div className=\"text-sm\">{\r\n (() => {\r\n const possibleName = laudo.requested_by_name ?? laudo.requester_name ?? laudo.requestedByName ?? laudo.executante_name ?? laudo.executante ?? laudo.executante_name ?? laudo.executante;\r\n if (possibleName && typeof possibleName === 'string' && possibleName.trim().length) return possibleName;\r\n const possibleId = (laudo.requested_by ?? laudo.created_by ?? laudo.executante ?? laudo.requestedBy ?? laudo.createdBy) || '';\r\n if (possibleId && user?.id && possibleId === user.id) return (profileData as any)?.nome || user?.name || possibleId;\r\n return possibleName || possibleId || '-';\r\n })()\r\n }</div>\r\n <div className=\"flex gap-2 mt-3\">\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setLaudoSelecionado(laudo);\r\n setIsViewing(true);\r\n }}\r\n className=\"flex items-center gap-1\"\r\n >\r\n <Eye className=\"w-4 h-4\" />\r\n </Button>\r\n <Button\r\n variant=\"default\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setPatientForLaudo(laudo);\r\n setIsEditingLaudoForPatient(true);\r\n }}\r\n className=\"flex items-center gap-1 bg-green-600 hover:bg-green-700 text-white\"\r\n title=\"Editar laudo\"\r\n >\r\n <Edit className=\"w-4 h-4\" />\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Visualizador de Laudo */}\r\n {isViewing && laudoSelecionado && (\r\n <LaudoViewer laudo={laudoSelecionado} onClose={() => setIsViewing(false)} />\r\n )}\r\n\r\n {/* Editor para Novo Laudo */}\r\n {isCreatingNew && (\r\n <LaudoEditor\r\n pacientes={pacientesDisponiveis}\r\n onClose={() => setIsCreatingNew(false)}\r\n isNewLaudo={true}\r\n createNewReport={createNewReport}\r\n updateExistingReport={updateExistingReport}\r\n reloadReports={loadReports}\r\n onSaved={async (r:any) => {\r\n try {\r\n // If report has an id, fetch full report and open viewer\r\n if (r && (r.id || r.order_number)) {\r\n const id = r.id ?? r.order_number;\r\n const full = await loadReportById(String(id));\r\n await ensurePaciente(full);\r\n // prepend to laudos list so it appears immediately\r\n setLaudos(prev => [full, ...(prev || [])]);\r\n setLaudoSelecionado(full);\r\n setIsViewing(true);\r\n } else {\r\n setLaudoSelecionado(r);\r\n setIsViewing(true);\r\n }\r\n // refresh global reports list too\r\n try { await loadReports(); } catch {}\r\n } catch (e) {\r\n // fallback: open what we have\r\n setLaudoSelecionado(r);\r\n setIsViewing(true);\r\n }\r\n }}\r\n />\r\n )}\r\n\r\n {/* Editor para Paciente Específico */}\r\n {isEditingForPatient && selectedPatientForLaudo && (\r\n <LaudoEditor\r\n pacientes={[selectedPatientForLaudo.paciente || selectedPatientForLaudo]}\r\n laudo={selectedPatientForLaudo}\r\n onClose={onClosePatientEditor || (() => {})}\r\n isNewLaudo={!selectedPatientForLaudo?.id}\r\n preSelectedPatient={selectedPatientForLaudo.paciente || selectedPatientForLaudo}\r\n createNewReport={createNewReport}\r\n updateExistingReport={updateExistingReport}\r\n reloadReports={loadReports}\r\n onSaved={async (r:any) => {\r\n try {\r\n if (r && (r.id || r.order_number)) {\r\n const id = r.id ?? r.order_number;\r\n const full = await loadReportById(String(id));\r\n await ensurePaciente(full);\r\n setLaudos(prev => [full, ...(prev || [])]);\r\n setLaudoSelecionado(full);\r\n setIsViewing(true);\r\n } else {\r\n setLaudoSelecionado(r);\r\n setIsViewing(true);\r\n }\r\n try { await loadReports(); } catch {}\r\n } catch (e) {\r\n setLaudoSelecionado(r);\r\n setIsViewing(true);\r\n }\r\n }}\r\n />\r\n )}\r\n </div>\r\n );\r\n }\r\n\r\n // Visualizador de Laudo (somente leitura)\r\n function LaudoViewer({ laudo, onClose }: { laudo: any; onClose: () => void }) {\r\n return (\r\n <div className=\"fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4\">\r\n <div className=\"bg-background rounded-lg shadow-xl w-full h-full md:h-auto md:rounded-lg md:max-w-4xl max-h-[90vh] overflow-hidden flex flex-col\">\r\n {/* Header */}\r\n <div className=\"flex items-center justify-between p-4 border-b border-border\">\r\n <div>\r\n <h2 className=\"text-xl font-bold text-foreground\">Visualizar Laudo</h2>\r\n <p className=\"text-sm text-muted-foreground\">\r\n Paciente: {getPatientName(laudo?.paciente) || getPatientName(laudo) || '—'} | CPF: {getReportPatientCpf(laudo) ?? laudo?.patient_cpf ?? '-'} | {laudo?.especialidade ?? laudo?.exame ?? '-'}\r\n </p>\r\n </div>\r\n <Button variant=\"ghost\" size=\"sm\" onClick={onClose}>\r\n <X className=\"w-4 h-4\" />\r\n </Button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 overflow-y-auto p-6\">\r\n <div className=\"max-w-2xl mx-auto bg-background border border-border rounded-lg p-6 shadow-sm\">\r\n {/* Header do Laudo */}\r\n <div className=\"text-center mb-6\">\r\n <h2 className=\"text-lg font-bold\">LAUDO MÉDICO - {(laudo.especialidade ?? laudo.exame ?? '').toString().toUpperCase()}</h2>\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n Data: {formatReportDate(getReportDate(laudo))}\r\n </p>\r\n </div>\r\n\r\n {/* Dados do Paciente */}\r\n <div className=\"mb-6 p-4 bg-muted rounded\">\r\n <h3 className=\"font-semibold mb-2\">Dados do Paciente:</h3>\r\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\r\n <p><strong>Nome:</strong> {getPatientName(laudo?.paciente) || getPatientName(laudo) || '-'}</p>\r\n <p><strong>CPF:</strong> {getPatientCpf(laudo?.paciente) ?? laudo?.patient_cpf ?? '-'}</p>\r\n <p><strong>Idade:</strong> {getPatientAge(laudo?.paciente) ? `${getPatientAge(laudo?.paciente)} anos` : (getPatientAge(laudo) ? `${getPatientAge(laudo)} anos` : '-')}</p>\r\n <p><strong>Sexo:</strong> {getPatientSex(laudo?.paciente) ?? getPatientSex(laudo) ?? '-'}</p>\r\n <p><strong>CID:</strong> {laudo?.cid ?? laudo?.cid_code ?? '-'}</p>\r\n </div>\r\n </div>\r\n\r\n {/* Conteúdo do Laudo */}\r\n <div className=\"mb-6\">\r\n <div \r\n className=\"prose prose-sm max-w-none\"\r\n dangerouslySetInnerHTML={{ \r\n __html: ((laudo.conteudo ?? laudo.content_html ?? laudo.contentHtml ?? laudo.content) || '').toString().replace(/\\n/g, '<br>') \r\n }}\r\n />\r\n </div>\r\n\r\n {/* Exame */}\r\n {((laudo.exame ?? laudo.exam ?? laudo.especialidade ?? laudo.report_type) || '').toString().length > 0 && (\r\n <div className=\"mb-4\">\r\n <h4 className=\"font-semibold mb-1\">Exame / Especialidade:</h4>\r\n <p className=\"text-sm\">{laudo.exame ?? laudo.exam ?? laudo.especialidade ?? laudo.report_type}</p>\r\n </div>\r\n )}\r\n\r\n {/* Diagnóstico */}\r\n {((laudo.diagnostico ?? laudo.diagnosis) || '').toString().length > 0 && (\r\n <div className=\"mb-4 p-3 bg-blue-50 dark:bg-blue-950/20 rounded\">\r\n <h4 className=\"font-semibold mb-1\">Diagnóstico:</h4>\r\n <p className=\"text-sm font-bold\">{laudo.diagnostico ?? laudo.diagnosis}</p>\r\n </div>\r\n )}\r\n\r\n {/* Conclusão */}\r\n {((laudo.conclusao ?? laudo.conclusion) || '').toString().length > 0 && (\r\n <div className=\"mb-6 p-3 bg-green-50 dark:bg-green-950/20 rounded\">\r\n <h4 className=\"font-semibold mb-1\">Conclusão:</h4>\r\n <p className=\"text-sm font-bold\">{laudo.conclusao ?? laudo.conclusion}</p>\r\n </div>\r\n )}\r\n\r\n {/* Diagnóstico e Conclusão */}\r\n {laudo.diagnostico && (\r\n <div className=\"mb-4 p-3 bg-blue-50 dark:bg-blue-950/20 rounded\">\r\n <h4 className=\"font-semibold mb-1\" style={{ color: '#EF4444' }}>Diagnóstico:</h4>\r\n <p className=\"text-sm font-bold\" style={{ color: '#EF4444' }}>{laudo.diagnostico}</p>\r\n </div>\r\n )}\r\n\r\n {laudo.conclusao && (\r\n <div className=\"mb-6 p-3 bg-green-50 dark:bg-green-950/20 rounded\">\r\n <h4 className=\"font-semibold mb-1\" style={{ color: '#EF4444' }}>Conclusão:</h4>\r\n <p className=\"text-sm font-bold\" style={{ color: '#EF4444' }}>{laudo.conclusao}</p>\r\n </div>\r\n )}\r\n\r\n {/* Assinatura */}\r\n <div className=\"mt-8 text-center border-t pt-4\">\r\n <div className=\"h-16 mb-2\"></div>\r\n {(() => {\r\n const signatureName = laudo?.created_by_name ?? laudo?.createdByName ?? ((laudo?.created_by && user?.id && laudo.created_by === user.id) ? profileData.nome : (laudo?.created_by_name || profileData.nome));\r\n return (\r\n <>\r\n <p className=\"text-sm font-semibold\">{signatureName}</p>\r\n <p className=\"text-xs text-muted-foreground\">{profileData.crm ? `CRM: ${String(profileData.crm).replace(/^(?:CRM\\s*)+/i, '').trim()}` : 'CRM não informado'}{laudo.especialidade ? ` - ${laudo.especialidade}` : ''}</p>\r\n <p className=\"text-xs text-muted-foreground mt-1\">Data: {formatReportDate(getReportDate(laudo))}</p>\r\n </>\r\n );\r\n })()}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Footer */}\r\n <div className=\"p-4 border-t border-border bg-muted/20\">\r\n <div className=\"flex items-center justify-end\">\r\n <Button onClick={onClose}>\r\n Fechar\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // Editor de Laudo Avançado (para novos laudos)\r\n function LaudoEditor({ pacientes, laudo, onClose, isNewLaudo, preSelectedPatient, createNewReport, updateExistingReport, reloadReports, onSaved }: { pacientes?: any[]; laudo?: any; onClose: () => void; isNewLaudo?: boolean; preSelectedPatient?: any; createNewReport?: (data: any) => Promise<any>; updateExistingReport?: (id: string, data: any) => Promise<any>; reloadReports?: () => Promise<void>; onSaved?: (r:any) => void }) {\r\n // Import useToast at the top level of the component\r\n const { toast } = require('@/hooks/use-toast').useToast();\r\n const [activeTab, setActiveTab] = useState(\"editor\");\r\n const [content, setContent] = useState(laudo?.conteudo || \"\");\r\n const [showPreview, setShowPreview] = useState(false);\r\n const [pacienteSelecionado, setPacienteSelecionado] = useState<any>(preSelectedPatient || null);\r\n const [listaPacientes, setListaPacientes] = useState<any[]>([]);\r\n // Novo: campos para solicitante e prazo\r\n // solicitanteId será enviado ao backend (sempre o id do usuário logado)\r\n const [solicitanteId, setSolicitanteId] = useState<string>(user?.id || \"\");\r\n // displaySolicitante é apenas para exibição (nome do usuário) e NÃO é enviado ao backend\r\n // Prefer profileData.nome (nome do médico carregado) — cai back para user.name ou email\r\n const displaySolicitante = ((profileData as any) && ((profileData as any).nome || (profileData as any).nome_social)) || user?.name || (user?.profile as any)?.full_name || user?.email || '';\r\n const [prazoDate, setPrazoDate] = useState<string>(\"\");\r\n const [prazoTime, setPrazoTime] = useState<string>(\"\");\r\n\r\n // Pega token do usuário logado (passado explicitamente para listarPacientes)\r\n const { token } = useAuth();\r\n\r\n // Carregar pacientes reais do Supabase ao abrir o modal ou quando o token mudar\r\n useEffect(() => {\r\n async function fetchPacientes() {\r\n try {\r\n if (!token) {\r\n setListaPacientes([]);\r\n return;\r\n }\r\n const pacientes = await listarPacientes();\r\n setListaPacientes(pacientes || []);\r\n } catch (err) {\r\n console.warn('Erro ao carregar pacientes:', err);\r\n setListaPacientes([]);\r\n }\r\n }\r\n fetchPacientes();\r\n }, [token]);\r\n const [campos, setCampos] = useState({\r\n cid: laudo?.cid || \"\",\r\n diagnostico: laudo?.diagnostico || \"\",\r\n conclusao: laudo?.conclusao || \"\",\r\n exame: laudo?.exame || \"\",\r\n especialidade: laudo?.especialidade || \"\",\r\n mostrarData: true,\r\n mostrarAssinatura: true\r\n });\r\n const [imagens, setImagens] = useState<any[]>([]);\r\n const [templates] = useState([\r\n \"Exame normal, sem alterações significativas\",\r\n \"Paciente em acompanhamento ambulatorial\",\r\n \"Recomenda-se retorno em 30 dias\",\r\n \"Alterações compatíveis com processo inflamatório\",\r\n \"Resultado dentro dos parâmetros de normalidade\",\r\n \"Recomendo seguimento com especialista\"\r\n ]);\r\n\r\n\r\n const sigCanvasRef = useRef<any>(null);\r\n\r\n // Estado para imagem da assinatura\r\n const [assinaturaImg, setAssinaturaImg] = useState<string | null>(null);\r\n\r\n useEffect(() => {\r\n if (!sigCanvasRef.current) return;\r\n const handleEnd = () => {\r\n const url = sigCanvasRef.current.getTrimmedCanvas().toDataURL('image/png');\r\n setAssinaturaImg(url);\r\n };\r\n const canvas = sigCanvasRef.current;\r\n if (canvas && canvas.canvas) {\r\n canvas.canvas.addEventListener('mouseup', handleEnd);\r\n canvas.canvas.addEventListener('touchend', handleEnd);\r\n }\r\n return () => {\r\n if (canvas && canvas.canvas) {\r\n canvas.canvas.removeEventListener('mouseup', handleEnd);\r\n canvas.canvas.removeEventListener('touchend', handleEnd);\r\n }\r\n };\r\n }, [sigCanvasRef]);\r\n\r\n const handleClearSignature = () => {\r\n if (sigCanvasRef.current) {\r\n sigCanvasRef.current.clear();\r\n }\r\n setAssinaturaImg(null);\r\n };\r\n\r\n // Carregar dados do laudo existente quando disponível (mais robusto: suporta vários nomes de campo)\r\n useEffect(() => {\r\n if (laudo && !isNewLaudo) {\r\n // Conteúdo: aceita 'conteudo', 'content_html', 'contentHtml', 'content'\r\n const contentValue = laudo.conteudo ?? laudo.content_html ?? laudo.contentHtml ?? laudo.content ?? \"\";\r\n setContent(contentValue);\r\n\r\n // Campos: use vários fallbacks\r\n const cidValue = laudo.cid ?? laudo.cid_code ?? '';\r\n const diagnosticoValue = laudo.diagnostico ?? laudo.diagnosis ?? '';\r\n const conclusaoValue = laudo.conclusao ?? laudo.conclusion ?? '';\r\n const exameValue = laudo.exame ?? laudo.exam ?? laudo.especialidade ?? '';\r\n const especialidadeValue = laudo.especialidade ?? laudo.exame ?? laudo.exam ?? '';\r\n const mostrarDataValue = typeof laudo.hide_date === 'boolean' ? !laudo.hide_date : true;\r\n const mostrarAssinaturaValue = typeof laudo.hide_signature === 'boolean' ? !laudo.hide_signature : true;\r\n\r\n setCampos({\r\n cid: cidValue,\r\n diagnostico: diagnosticoValue,\r\n conclusao: conclusaoValue,\r\n exame: exameValue,\r\n especialidade: especialidadeValue,\r\n mostrarData: mostrarDataValue,\r\n mostrarAssinatura: mostrarAssinaturaValue\r\n });\r\n\r\n // Paciente: não sobrescrever se já existe preSelectedPatient ou pacienteSelecionado\r\n if (!pacienteSelecionado) {\r\n const pacienteFromLaudo = laudo.paciente ?? laudo.patient ?? null;\r\n if (pacienteFromLaudo) {\r\n setPacienteSelecionado(pacienteFromLaudo);\r\n } else if (laudo.patient_id && listaPacientes && listaPacientes.length) {\r\n const found = listaPacientes.find(p => String(p.id) === String(laudo.patient_id));\r\n if (found) setPacienteSelecionado(found);\r\n }\r\n }\r\n\r\n // preencher solicitanteId/prazo quando existe laudo (edição)\r\n // preferimos manter o solicitanteId como o user id; se o laudo tiver requested_by que pareça um id, usamos ele\r\n const possibleRequestedById = laudo.requested_by ?? laudo.created_by ?? null;\r\n if (possibleRequestedById && typeof possibleRequestedById === 'string' && possibleRequestedById.length > 5) {\r\n setSolicitanteId(possibleRequestedById);\r\n } else {\r\n setSolicitanteId(user?.id || \"\");\r\n }\r\n\r\n const dueRaw = laudo.due_at ?? laudo.prazo ?? laudo.dueDate ?? laudo.data ?? null;\r\n if (dueRaw) {\r\n try {\r\n const d = new Date(dueRaw);\r\n if (!isNaN(d.getTime())) {\r\n setPrazoDate(d.toISOString().slice(0,10));\r\n setPrazoTime(d.toTimeString().slice(0,5));\r\n }\r\n } catch (e) {\r\n // ignore invalid date\r\n }\r\n }\r\n\r\n // assinatura: aceitar vários campos possíveis\r\n const sig = laudo.assinaturaImg ?? laudo.signature_image ?? laudo.signature ?? laudo.sign_image ?? null;\r\n if (sig) setAssinaturaImg(sig);\r\n }\r\n }, [laudo, isNewLaudo, pacienteSelecionado, listaPacientes, user]);\r\n\r\n // Histórico para desfazer/refazer\r\n const [history, setHistory] = useState<string[]>([]);\r\n const [historyIndex, setHistoryIndex] = useState(-1);\r\n\r\n // Atualiza histórico ao digitar\r\n useEffect(() => {\r\n if (history[historyIndex] !== content) {\r\n const newHistory = history.slice(0, historyIndex + 1);\r\n setHistory([...newHistory, content]);\r\n setHistoryIndex(newHistory.length);\r\n }\r\n // eslint-disable-next-line\r\n }, [content]);\r\n\r\n const handleUndo = () => {\r\n if (historyIndex > 0) {\r\n setContent(history[historyIndex - 1]);\r\n setHistoryIndex(historyIndex - 1);\r\n }\r\n };\r\n const handleRedo = () => {\r\n if (historyIndex < history.length - 1) {\r\n setContent(history[historyIndex + 1]);\r\n setHistoryIndex(historyIndex + 1);\r\n }\r\n };\r\n\r\n // Formatação avançada\r\n const formatText = (type: string, value?: any) => {\r\n const textarea = document.querySelector('textarea') as HTMLTextAreaElement;\r\n if (!textarea) return;\r\n const start = textarea.selectionStart;\r\n const end = textarea.selectionEnd;\r\n const selectedText = textarea.value.substring(start, end);\r\n let formattedText = \"\";\r\n switch(type) {\r\n case \"bold\":\r\n formattedText = selectedText ? `**${selectedText}**` : \"**texto em negrito**\";\r\n break;\r\n case \"italic\":\r\n formattedText = selectedText ? `*${selectedText}*` : \"*texto em itálico*\";\r\n break;\r\n case \"underline\":\r\n formattedText = selectedText ? `__${selectedText}__` : \"__texto sublinhado__\";\r\n break;\r\n case \"list-ul\":\r\n formattedText = selectedText ? selectedText.split('\\n').map(l => `• ${l}`).join('\\n') : \"• item da lista\";\r\n break;\r\n case \"list-ol\":\r\n formattedText = selectedText ? selectedText.split('\\n').map((l,i) => `${i+1}. ${l}`).join('\\n') : \"1. item da lista\";\r\n break;\r\n case \"indent\":\r\n formattedText = selectedText ? selectedText.split('\\n').map(l => ` ${l}`).join('\\n') : \" \";\r\n break;\r\n case \"outdent\":\r\n formattedText = selectedText ? selectedText.split('\\n').map(l => l.replace(/^\\s{1,4}/, \"\")).join('\\n') : \"\";\r\n break;\r\n case \"align-left\":\r\n formattedText = selectedText ? `[left]${selectedText}[/left]` : \"[left]Texto à esquerda[/left]\";\r\n break;\r\n case \"align-center\":\r\n formattedText = selectedText ? `[center]${selectedText}[/center]` : \"[center]Texto centralizado[/center]\";\r\n break;\r\n case \"align-right\":\r\n formattedText = selectedText ? `[right]${selectedText}[/right]` : \"[right]Texto à direita[/right]\";\r\n break;\r\n case \"align-justify\":\r\n formattedText = selectedText ? `[justify]${selectedText}[/justify]` : \"[justify]Texto justificado[/justify]\";\r\n break;\r\n case \"font-size\":\r\n formattedText = selectedText ? `[size=${value}]${selectedText}[/size]` : `[size=${value}]Texto tamanho ${value}[/size]`;\r\n break;\r\n case \"font-family\":\r\n formattedText = selectedText ? `[font=${value}]${selectedText}[/font]` : `[font=${value}]${value}[/font]`;\r\n break;\r\n case \"font-color\":\r\n formattedText = selectedText ? `[color=${value}]${selectedText}[/color]` : `[color=${value}]${value}[/color]`;\r\n break;\r\n default:\r\n return;\r\n }\r\n const newText = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end);\r\n setContent(newText);\r\n };\r\n\r\n const insertTemplate = (template: string) => {\r\n setContent((prev: string) => prev ? `${prev}\\n\\n${template}` : template);\r\n };\r\n\r\n const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = Array.from(e.target.files || []);\r\n files.forEach(file => {\r\n const reader = new FileReader();\r\n reader.onload = (e) => {\r\n setImagens(prev => [...prev, {\r\n id: Date.now() + Math.random(),\r\n name: file.name,\r\n url: e.target?.result,\r\n type: file.type\r\n }]);\r\n };\r\n reader.readAsDataURL(file);\r\n });\r\n };\r\n\r\n const processContent = (content: string) => {\r\n return content\r\n .replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>')\r\n .replace(/\\*(.*?)\\*/g, '<em>$1</em>')\r\n .replace(/__(.*?)__/g, '<u>$1</u>')\r\n .replace(/\\[left\\]([\\s\\S]*?)\\[\\/left\\]/g, '<div style=\"text-align:left\">$1</div>')\r\n .replace(/\\[center\\]([\\s\\S]*?)\\[\\/center\\]/g, '<div style=\"text-align:center\">$1</div>')\r\n .replace(/\\[right\\]([\\s\\S]*?)\\[\\/right\\]/g, '<div style=\"text-align:right\">$1</div>')\r\n .replace(/\\[justify\\]([\\s\\S]*?)\\[\\/justify\\]/g, '<div style=\"text-align:justify\">$1</div>')\r\n .replace(/\\[size=(\\d+)\\]([\\s\\S]*?)\\[\\/size\\]/g, '<span style=\"font-size:$1px\">$2</span>')\r\n .replace(/\\[font=([^\\]]+)\\]([\\s\\S]*?)\\[\\/font\\]/g, '<span style=\"font-family:$1\">$2</span>')\r\n .replace(/\\[color=([^\\]]+)\\]([\\s\\S]*?)\\[\\/color\\]/g, '<span style=\"color:$1\">$2</span>')\r\n .replace(/{{sexo_paciente}}/g, pacienteSelecionado?.sexo || laudo?.paciente?.sexo || '[SEXO]')\r\n .replace(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]')\r\n .replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]')\r\n .replace(/\\n/g, '<br>');\r\n };\r\n\r\n return (\r\n <div className=\"fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4\">\r\n <div className=\"bg-background rounded-none md:rounded-lg shadow-xl w-full h-full md:h-auto md:max-w-6xl max-h-[90vh] overflow-hidden flex flex-col\">\r\n {/* Header */}\r\n <div className=\"border-b border-border\">\r\n <div className=\"flex items-center justify-between p-4\">\r\n <div>\r\n <h2 className=\"text-xl font-bold text-foreground\">\r\n {isNewLaudo ? \"Novo Laudo Médico\" : \"Editar Laudo Existente\"}\r\n </h2>\r\n {isNewLaudo ? (\r\n <p className=\"text-sm text-muted-foreground\">\r\n Crie um novo laudo selecionando um paciente\r\n </p>\r\n ) : (\r\n <p className=\"text-sm text-muted-foreground\">\r\n Paciente: {getPatientName(pacienteSelecionado) || getPatientName(laudo?.paciente) || getPatientName(laudo) || '-'} | CPF: {getReportPatientCpf(laudo) ?? laudo?.patient_cpf ?? '-'} | {laudo?.especialidade}\r\n </p>\r\n )}\r\n </div>\r\n <Button variant=\"ghost\" size=\"sm\" onClick={onClose}>\r\n <X className=\"w-4 h-4\" />\r\n </Button>\r\n </div>\r\n\r\n {/* Seleção de Paciente (apenas para novos laudos) */}\r\n {isNewLaudo && (\r\n <div className=\"px-4 pb-4\">\r\n {!pacienteSelecionado ? (\r\n <div className=\"bg-muted border border-border rounded-lg p-4\">\r\n <Label htmlFor=\"select-paciente\" className=\"text-sm font-medium mb-2 block\">\r\n Selecionar Paciente *\r\n </Label>\r\n <Select onValueChange={(value) => {\r\n const paciente = listaPacientes.find(p => p.id === value);\r\n if (paciente) setPacienteSelecionado(paciente);\r\n }}>\r\n <SelectTrigger className=\"w-full\">\r\n <SelectValue placeholder=\"Escolha um paciente para criar o laudo\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {listaPacientes.map((paciente) => (\r\n <SelectItem key={paciente.id} value={paciente.id}>\r\n {paciente.full_name} {paciente.cpf ? `- CPF: ${paciente.cpf}` : ''}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n ) : (\r\n <div className=\"bg-primary/10 border border-primary/20 rounded-lg p-3 flex items-center justify-between\">\r\n <div>\r\n <div className=\"font-semibold text-primary\">{getPatientName(pacienteSelecionado)}</div>\r\n <div className=\"text-xs text-muted-foreground\">\r\n {getPatientCpf(pacienteSelecionado) ? `CPF: ${getPatientCpf(pacienteSelecionado)} | ` : ''}\r\n {pacienteSelecionado?.birth_date ? `Nascimento: ${pacienteSelecionado.birth_date}` : (getPatientAge(pacienteSelecionado) ? `Idade: ${getPatientAge(pacienteSelecionado)} anos` : '')}\r\n {getPatientSex(pacienteSelecionado) ? ` | Sexo: ${getPatientSex(pacienteSelecionado)}` : ''}\r\n </div>\r\n </div>\r\n {!preSelectedPatient && (\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => setPacienteSelecionado(null)}\r\n >\r\n Trocar Paciente\r\n </Button>\r\n )}\r\n </div>\r\n )}\r\n {/* Novos campos: Solicitante e Prazo */}\r\n <div className=\"mt-3 grid grid-cols-1 md:grid-cols-2 gap-3\">\r\n <div>\r\n <Label htmlFor=\"solicitante\">Solicitante</Label>\r\n {/* Mostrar o nome do usuário logado de forma estática (não editável) */}\r\n <Input id=\"solicitante\" value={displaySolicitante} readOnly disabled />\r\n \r\n </div>\r\n <div>\r\n <Label htmlFor=\"prazoDate\">Prazo do Laudo</Label>\r\n <div className=\"flex gap-2\">\r\n <Input id=\"prazoDate\" type=\"date\" value={prazoDate} onChange={(e) => setPrazoDate(e.target.value)} />\r\n <Input id=\"prazoTime\" type=\"time\" value={prazoTime} onChange={(e) => setPrazoTime(e.target.value)} />\r\n </div>\r\n <p className=\"text-xs text-muted-foreground mt-1\">Defina a data e hora do prazo (opcional).</p>\r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Tabs */}\r\n <div className=\"flex border-b border-border\">\r\n {/* Informações tab removed - only Editor/Imagens/Campos/Pré-visualização remain */}\r\n <button\r\n onClick={() => setActiveTab(\"editor\")}\r\n className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${\r\n activeTab === \"editor\"\r\n ? \"border-blue-500 text-blue-600\"\r\n : \"border-transparent text-gray-600 dark:text-muted-foreground dark:hover:text-foreground dark:hover:bg-blue-900\"\r\n }`}\r\n style={{\r\n backgroundColor: activeTab === \"editor\" ? undefined : \"transparent\"\r\n }}\r\n onMouseEnter={(e) => {\r\n if (activeTab !== \"editor\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n if (activeTab !== \"editor\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n >\r\n <FileText className=\"w-4 h-4 inline mr-1\" />\r\n Editor\r\n </button>\r\n <button\r\n onClick={() => setActiveTab(\"imagens\")}\r\n className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${\r\n activeTab === \"imagens\"\r\n ? \"border-blue-500 text-blue-600\"\r\n : \"border-transparent text-gray-600 dark:text-muted-foreground dark:hover:text-foreground dark:hover:bg-blue-900\"\r\n }`}\r\n style={{\r\n backgroundColor: activeTab === \"imagens\" ? undefined : \"transparent\"\r\n }}\r\n onMouseEnter={(e) => {\r\n if (activeTab !== \"imagens\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n if (activeTab !== \"imagens\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n >\r\n <Upload className=\"w-4 h-4 inline mr-1\" />\r\n Imagens ({imagens.length})\r\n </button>\r\n <button\r\n onClick={() => setActiveTab(\"campos\")}\r\n className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${\r\n activeTab === \"campos\"\r\n ? \"border-blue-500 text-blue-600\"\r\n : \"border-transparent text-gray-600 dark:text-muted-foreground dark:hover:text-foreground dark:hover:bg-blue-900\"\r\n }`}\r\n style={{\r\n backgroundColor: activeTab === \"campos\" ? undefined : \"transparent\"\r\n }}\r\n onMouseEnter={(e) => {\r\n if (activeTab !== \"campos\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n if (activeTab !== \"campos\") {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n >\r\n <Settings className=\"w-4 h-4 inline mr-1\" />\r\n Campos\r\n </button>\r\n <button\r\n onClick={() => setShowPreview(!showPreview)}\r\n className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${\r\n showPreview\r\n ? \"border-green-500 text-green-600\"\r\n : \"border-transparent text-gray-600 dark:text-muted-foreground dark:hover:text-foreground dark:hover:bg-blue-900\"\r\n }`}\r\n style={{\r\n backgroundColor: !showPreview ? \"transparent\" : undefined\r\n }}\r\n onMouseEnter={(e) => {\r\n if (!showPreview) {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n if (!showPreview) {\r\n e.currentTarget.style.backgroundColor = \"transparent\";\r\n e.currentTarget.style.color = \"#4B5563\";\r\n }\r\n }}\r\n >\r\n <Eye className=\"w-4 h-4 inline mr-1\" />\r\n {showPreview ? \"Ocultar\" : \"Pré-visualização\"}\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 overflow-hidden flex\">\r\n {/* Left Panel */}\r\n <div className=\"flex-1 flex flex-col\">\r\n {/* 'Informações' section removed to keep editor-only experience */}\r\n\r\n {activeTab === \"editor\" && (\r\n <div className=\"flex-1 flex flex-col\">\r\n {/* Toolbar */}\r\n <div className=\"p-3 border-b border-border\">\r\n <div className=\"flex flex-wrap gap-2 items-center\">\r\n {/* Tamanho da fonte */}\r\n <label className=\"text-xs mr-1\">Tamanho</label>\r\n <input\r\n type=\"number\"\r\n min={8}\r\n max={32}\r\n defaultValue={14}\r\n onBlur={e => formatText('font-size', e.target.value)}\r\n className=\"w-14 border rounded px-1 py-0.5 text-xs mr-2\"\r\n title=\"Tamanho da fonte\"\r\n />\r\n {/* Família da fonte */}\r\n <label className=\"text-xs mr-1\">Fonte</label>\r\n <select\r\n defaultValue={'Arial'}\r\n onBlur={e => formatText('font-family', e.target.value)}\r\n className=\"border rounded px-1 py-0.5 text-xs mr-2 bg-white text-gray-900 dark:bg-gray-800 dark:text-white\"\r\n style={{ minWidth: 140, fontWeight: 500 }}\r\n title=\"Família da fonte\"\r\n >\r\n <option value=\"Arial\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Arial</option>\r\n <option value=\"Helvetica\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Helvetica</option>\r\n <option value=\"Times New Roman\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Times New Roman</option>\r\n <option value=\"Courier New\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Courier New</option>\r\n <option value=\"Verdana\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Verdana</option>\r\n <option value=\"Georgia\" style={{ color: '#222', background: '#fff', fontWeight: 600 }}>Georgia</option>\r\n </select>\r\n {/* Cor da fonte */}\r\n <label className=\"text-xs mr-1\">Cor</label>\r\n <input\r\n type=\"color\"\r\n defaultValue=\"#222222\"\r\n onBlur={e => formatText('font-color', e.target.value)}\r\n className=\"w-6 h-6 border rounded mr-2\"\r\n title=\"Cor da fonte\"\r\n />\r\n {/* Alinhamento */}\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('align-left')} title=\"Alinhar à esquerda\" className=\"px-1\"><svg width=\"16\" height=\"16\" fill=\"none\"><rect x=\"2\" y=\"4\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"7\" width=\"8\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"10\" width=\"10\" height=\"2\" rx=\"1\" fill=\"currentColor\"/></svg></Button>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('align-center')} title=\"Centralizar\" className=\"px-1\"><svg width=\"16\" height=\"16\" fill=\"none\"><rect x=\"4\" y=\"4\" width=\"8\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"7\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"3\" y=\"10\" width=\"10\" height=\"2\" rx=\"1\" fill=\"currentColor\"/></svg></Button>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('align-right')} title=\"Alinhar à direita\" className=\"px-1\"><svg width=\"16\" height=\"16\" fill=\"none\"><rect x=\"6\" y=\"4\" width=\"8\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"7\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"4\" y=\"10\" width=\"10\" height=\"2\" rx=\"1\" fill=\"currentColor\"/></svg></Button>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('align-justify')} title=\"Justificar\" className=\"px-1\"><svg width=\"16\" height=\"16\" fill=\"none\"><rect x=\"2\" y=\"4\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"7\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/><rect x=\"2\" y=\"10\" width=\"12\" height=\"2\" rx=\"1\" fill=\"currentColor\"/></svg></Button>\r\n {/* Listas */}\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('list-ol')} title=\"Lista numerada\" className=\"px-1\">1.</Button>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('list-ul')} title=\"Lista com marcadores\" className=\"px-1\">•</Button>\r\n {/* Recuo */}\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('indent')} title=\"Aumentar recuo\" className=\"px-1\">→</Button>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => formatText('outdent')} title=\"Diminuir recuo\" className=\"px-1\">←</Button>\r\n {/* Desfazer/Refazer */}\r\n <Button variant=\"outline\" size=\"sm\" onClick={handleUndo} title=\"Desfazer\" className=\"px-1\">↺</Button>\r\n <div className=\"flex flex-wrap gap-1\">\r\n {templates.map((template, idx) => (\r\n <Button\r\n key={idx}\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n className=\"text-xs h-auto p-1 px-2\"\r\n onClick={() => insertTemplate(template)}\r\n >\r\n {template.substring(0, 30)}...\r\n </Button>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Editor */}\r\n <div className=\"flex-1 p-4 overflow-auto max-h-[500px]\">\r\n <Textarea\r\n value={content}\r\n onChange={(e) => setContent(e.target.value)}\r\n placeholder=\"Digite o conteúdo do laudo aqui. Use ** para negrito, * para itálico, <u></u> para sublinhado.\"\r\n className=\"h-full min-h-[400px] resize-none scrollbar-thin scrollbar-thumb-blue-400 scrollbar-track-blue-100\"\r\n style={{ maxHeight: 400, overflow: 'auto' }}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {activeTab === \"imagens\" && (\r\n <div className=\"flex-1 p-4\">\r\n <div className=\"mb-4\">\r\n <Label htmlFor=\"upload-images\">Upload de Imagens</Label>\r\n <Input\r\n id=\"upload-images\"\r\n type=\"file\"\r\n multiple\r\n accept=\"image/*,.pdf\"\r\n onChange={handleImageUpload}\r\n className=\"mt-1\"\r\n />\r\n </div>\r\n \r\n <div className=\"grid grid-cols-2 md:grid-cols-3 gap-4\">\r\n {imagens.map((img) => (\r\n <div key={img.id} className=\"border border-border rounded-lg p-2\">\r\n {img.type.startsWith('image/') ? (\r\n <img \r\n src={img.url} \r\n alt={img.name}\r\n className=\"w-full h-32 object-cover rounded\"\r\n />\r\n ) : (\r\n <div className=\"w-full h-32 bg-muted rounded flex items-center justify-center\">\r\n <FileText className=\"w-8 h-8 text-muted-foreground\" />\r\n </div>\r\n )}\r\n <p className=\"text-xs text-muted-foreground mt-1 truncate\">{img.name}</p>\r\n <Button\r\n variant=\"destructive\"\r\n size=\"sm\"\r\n className=\"w-full mt-1\"\r\n onClick={() => setImagens(prev => prev.filter(i => i.id !== img.id))}\r\n >\r\n Remover\r\n </Button>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {activeTab === \"campos\" && (\r\n <div className=\"flex-1 p-4 space-y-4 max-h-[500px] overflow-y-auto\">\r\n <div>\r\n <Label htmlFor=\"cid\">CID</Label>\r\n <Input\r\n id=\"cid\"\r\n value={campos.cid}\r\n onChange={(e) => setCampos(prev => ({ ...prev, cid: e.target.value }))}\r\n placeholder=\"Ex: M25.5, I10, etc.\"\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"exame\">Exame</Label>\r\n <Input\r\n id=\"exame\"\r\n value={campos.exame}\r\n onChange={(e) => setCampos(prev => ({ ...prev, exame: e.target.value }))}\r\n placeholder=\"Exame realizado\"\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"diagnostico\">Diagnóstico</Label>\r\n <Textarea\r\n id=\"diagnostico\"\r\n value={campos.diagnostico}\r\n onChange={(e) => setCampos(prev => ({ ...prev, diagnostico: e.target.value }))}\r\n placeholder=\"Diagnóstico principal\"\r\n rows={3}\r\n />\r\n </div>\r\n <div>\r\n <Label htmlFor=\"conclusao\">Conclusão</Label>\r\n <Textarea\r\n id=\"conclusao\"\r\n value={campos.conclusao}\r\n onChange={(e) => setCampos(prev => ({ ...prev, conclusao: e.target.value }))}\r\n placeholder=\"Conclusão do laudo\"\r\n rows={3}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <div className=\"flex items-center space-x-2\">\r\n <input\r\n type=\"checkbox\"\r\n id=\"mostrar-data\"\r\n checked={campos.mostrarData}\r\n onChange={(e) => setCampos(prev => ({ ...prev, mostrarData: e.target.checked }))}\r\n />\r\n <Label htmlFor=\"mostrar-data\">Mostrar data no laudo</Label>\r\n </div>\r\n <div className=\"flex items-center space-x-2\">\r\n <input\r\n type=\"checkbox\"\r\n id=\"mostrar-assinatura\"\r\n checked={campos.mostrarAssinatura}\r\n onChange={(e) => setCampos(prev => ({ ...prev, mostrarAssinatura: e.target.checked }))}\r\n />\r\n <Label htmlFor=\"mostrar-assinatura\">Mostrar assinatura no laudo</Label>\r\n </div>\r\n </div>\r\n {/* Assinatura Digital removida dos campos */}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Preview Panel */}\r\n {showPreview && (\r\n <div className=\"w-1/2 border-l border-border bg-muted/20\">\r\n <div className=\"p-4 border-b border-border\">\r\n <h3 className=\"font-semibold text-foreground\">Pré-visualização do Laudo</h3>\r\n </div>\r\n <div className=\"p-4 max-h-[600px] overflow-y-auto\">\r\n <div className=\"bg-background border border-border rounded-lg p-6 shadow-sm\">\r\n {/* Header do Laudo */}\r\n <div className=\"text-center mb-6\">\r\n <h2 className=\"text-lg font-bold\">\r\n LAUDO MÉDICO {campos.especialidade ? `- ${campos.especialidade.toUpperCase()}` : ''}\r\n </h2>\r\n {campos.exame && (\r\n <h3 className=\"text-md font-semibold mt-2\">{campos.exame}</h3>\r\n )}\r\n {campos.cid && (\r\n <h3 className=\"text-md font-semibold mt-2\">CID: {campos.cid}</h3>\r\n )}\r\n {campos.diagnostico && (\r\n <h3 className=\"text-md font-semibold mt-2\">Diagnóstico: {campos.diagnostico}</h3>\r\n )}\r\n {campos.conclusao && (\r\n <h3 className=\"text-md font-semibold mt-2\">Conclusão: {campos.conclusao}</h3>\r\n )}\r\n {campos.mostrarData && (\r\n <p className=\"text-sm text-muted-foreground mt-1\">\r\n Data: {new Date().toLocaleDateString('pt-BR')}\r\n </p>\r\n )}\r\n </div>\r\n\r\n {/* Dados do Paciente */}\r\n {(isNewLaudo ? pacienteSelecionado : laudo?.paciente) && (\r\n <div className=\"mb-4 p-3 bg-muted rounded\">\r\n <h3 className=\"font-semibold mb-2\">Dados do Paciente:</h3>\r\n {isNewLaudo && pacienteSelecionado ? (\r\n <>\r\n <p><strong>Nome:</strong> {getPatientName(pacienteSelecionado)}</p>\r\n <p><strong>ID:</strong> {getPatientId(pacienteSelecionado)}</p>\r\n <p><strong>CPF:</strong> {getPatientCpf(pacienteSelecionado)}</p>\r\n <p><strong>Idade:</strong> {getPatientAge(pacienteSelecionado)} anos</p>\r\n <p><strong>Sexo:</strong> {getPatientSex(pacienteSelecionado)}</p>\r\n <p><strong>CID:</strong> {campos.cid || '---'}</p>\r\n <p><strong>Diagnóstico:</strong> {campos.diagnostico || '---'}</p>\r\n <p><strong>Conclusão:</strong> {campos.conclusao || '---'}</p>\r\n </>\r\n ) : (\r\n <>\r\n <p><strong>Nome:</strong> {getPatientName(laudo?.paciente)}</p>\r\n <p><strong>ID:</strong> {getPatientId(laudo?.paciente)}</p>\r\n <p><strong>CPF:</strong> {getPatientCpf(laudo?.paciente)}</p>\r\n <p><strong>Idade:</strong> {getPatientAge(laudo?.paciente)} anos</p>\r\n <p><strong>Sexo:</strong> {getPatientSex(laudo?.paciente)}</p>\r\n <p><strong>CID:</strong> {campos.cid || laudo?.cid || '---'}</p>\r\n <p><strong>Diagnóstico:</strong> {campos.diagnostico || laudo?.diagnostico || '---'}</p>\r\n <p><strong>Conclusão:</strong> {campos.conclusao || laudo?.conclusao || '---'}</p>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Conteúdo */}\r\n <div className=\"mb-4\">\r\n <div \r\n dangerouslySetInnerHTML={{ \r\n __html: processContent(content) \r\n }}\r\n />\r\n </div>\r\n\r\n {/* Imagens */}\r\n {imagens.length > 0 && (\r\n <div className=\"mb-4\">\r\n <h3 className=\"font-semibold mb-2\">Imagens:</h3>\r\n <div className=\"grid grid-cols-2 gap-2\">\r\n {imagens.map((img) => (\r\n <img \r\n key={img.id}\r\n src={img.url} \r\n alt={img.name}\r\n className=\"w-full h-32 object-cover rounded border\"\r\n />\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Assinatura Digital em tempo real */}\r\n {campos.mostrarAssinatura && (\r\n <div className=\"mt-8 text-center\">\r\n {assinaturaImg && assinaturaImg.length > 30 ? (\r\n <img src={assinaturaImg} alt=\"Assinatura Digital\" className=\"mx-auto h-16 object-contain mb-2\" />\r\n ) : (\r\n <div className=\"h-16 mb-2 text-xs text-muted-foreground\">Assine no campo ao lado para visualizar aqui.</div>\r\n )}\r\n <div className=\"border-b border-border mb-2\"></div>\r\n <p className=\"text-sm\">{((profileData as any)?.nome || (profileData as any)?.nome_social) || user?.name || 'Squad-20'}</p>\r\n {(((profileData as any)?.crm) || ((user?.profile as any)?.crm)) ? (\r\n // Ensure we render a single 'CRM ' prefix followed by the raw number\r\n <p className=\"text-xs text-muted-foreground\">CRM {(((profileData as any)?.crm) || (user?.profile as any)?.crm).toString().replace(/^(?:CRM\\s*)+/i, '').trim()}</p>\r\n ) : null}\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Footer */}\r\n <div className=\"p-4 border-t border-border bg-muted/20\">\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"text-xs text-muted-foreground\">\r\n Este editor permite escrever relatórios de forma livre, com formatação de texto rica.\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Button variant=\"outline\" onClick={onClose} className=\"hover:bg-blue-50 dark:hover:bg-accent dark:hover:text-accent-foreground\">\r\n Cancelar\r\n </Button>\r\n {/* botão 'Salvar Rascunho' removido por não ser utilizado */}\r\n <Button\r\n variant=\"default\"\r\n onClick={async () => {\r\n try {\r\n const userId = user?.id || '00000000-0000-0000-0000-000000000001';\r\n // compor due_at a partir dos campos de data/hora, se fornecidos\r\n let composedDueAt = undefined;\r\n if (prazoDate) {\r\n // if time not provided, default to 23:59\r\n const t = prazoTime || '23:59';\r\n composedDueAt = new Date(`${prazoDate}T${t}:00`).toISOString();\r\n }\r\n\r\n const payload = {\r\n patient_id: pacienteSelecionado?.id,\r\n order_number: '',\r\n exam: campos.exame || '',\r\n diagnosis: campos.diagnostico || '',\r\n conclusion: campos.conclusao || '',\r\n cid_code: campos.cid || '',\r\n content_html: content,\r\n content_json: {},\r\n // status intentionally omitted — não enviar 'draft'\r\n requested_by: solicitanteId || userId,\r\n due_at: composedDueAt ?? new Date().toISOString(),\r\n hide_date: !campos.mostrarData,\r\n hide_signature: !campos.mostrarAssinatura,\r\n };\r\n\r\n if (isNewLaudo) {\r\n if (createNewReport) {\r\n const created = await createNewReport(payload as any);\r\n if (onSaved) onSaved(created);\r\n }\r\n } else {\r\n // Atualizar laudo existente: confirmar e enviar apenas diff\r\n const targetId = laudo?.id ?? laudo?.order_number ?? null;\r\n if (!targetId) throw new Error('ID do laudo ausente, não é possível atualizar');\r\n\r\n // Montar objeto contendo somente campos alterados\r\n const original = laudo || {};\r\n const candidate: any = {\r\n patient_id: payload.patient_id,\r\n order_number: payload.order_number,\r\n exam: payload.exam,\r\n diagnosis: payload.diagnosis,\r\n conclusion: payload.conclusion,\r\n cid_code: payload.cid_code,\r\n content_html: payload.content_html,\r\n // content_json intentionally left as full replacement if changed\r\n // status omitted on purpose\r\n requested_by: payload.requested_by,\r\n due_at: payload.due_at,\r\n hide_date: payload.hide_date,\r\n hide_signature: payload.hide_signature,\r\n };\r\n\r\n const diff: any = {};\r\n for (const k of Object.keys(candidate)) {\r\n const val = candidate[k];\r\n const origVal = original[k];\r\n // Considerar string/undefined equivalence\r\n if (typeof val === 'string') {\r\n if ((origVal ?? '') !== (val ?? '')) diff[k] = val;\r\n } else if (typeof val === 'boolean') {\r\n if (origVal !== val) diff[k] = val;\r\n } else if (val !== undefined && val !== null) {\r\n if (JSON.stringify(origVal) !== JSON.stringify(val)) diff[k] = val;\r\n }\r\n }\r\n\r\n if (Object.keys(diff).length === 0) {\r\n toast({ title: 'Nada a atualizar', description: 'Nenhuma alteração detectada.', variant: 'default' });\r\n } else {\r\n const ok = window.confirm('Deseja realmente atualizar este laudo? As alterações serão enviadas ao servidor.');\r\n if (!ok) return;\r\n if (updateExistingReport) {\r\n const updated = await updateExistingReport(String(targetId), diff as any);\r\n if (onSaved) onSaved(updated);\r\n }\r\n }\r\n }\r\n\r\n if (reloadReports) {\r\n await reloadReports();\r\n }\r\n\r\n toast({\r\n title: isNewLaudo ? 'Laudo criado com sucesso!' : 'Laudo atualizado com sucesso!',\r\n description: isNewLaudo ? 'O laudo foi liberado e salvo.' : 'As alterações foram salvas.',\r\n variant: 'default',\r\n });\r\n onClose();\r\n } catch (err) {\r\n toast({\r\n title: isNewLaudo ? 'Erro ao criar laudo' : 'Erro ao atualizar laudo',\r\n description: (err && typeof err === 'object' && 'message' in err) ? (err as any).message : String(err) || 'Tente novamente.',\r\n variant: 'destructive',\r\n });\r\n }\r\n }}\r\n >\r\n {isNewLaudo ? \"Liberar Laudo\" : \"Atualizar Laudo\"}\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n \r\n const renderComunicacaoSection = () => (\r\n <div className=\"bg-card shadow-md rounded-lg p-6\">\r\n <h2 className=\"text-2xl font-bold mb-4 text-foreground\">Comunicação com o Paciente</h2>\r\n <div className=\"space-y-6\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"destinatario\">Destinatário</Label>\r\n <Select>\r\n <SelectTrigger id=\"destinatario\" className=\"hover:border-primary focus:border-primary cursor-pointer\">\r\n <SelectValue placeholder=\"Selecione o paciente\" />\r\n </SelectTrigger>\r\n <SelectContent className=\"bg-popover border\">\r\n {pacientes.map((paciente) => (\r\n <SelectItem \r\n key={paciente.cpf} \r\n value={paciente.nome} \r\n className=\"hover:bg-blue-50 focus:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground dark:focus:bg-primary dark:focus:text-primary-foreground\"\r\n >\r\n {paciente.nome} - {paciente.cpf}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"tipoMensagem\">Tipo de mensagem</Label>\r\n <Select>\r\n <SelectTrigger id=\"tipoMensagem\" className=\"hover:border-primary focus:border-primary cursor-pointer\">\r\n <SelectValue placeholder=\"Selecione o tipo\" />\r\n </SelectTrigger>\r\n <SelectContent className=\"bg-popover border\">\r\n <SelectItem value=\"lembrete\" className=\"hover:bg-blue-50 focus:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground dark:focus:bg-primary dark:focus:text-primary-foreground\">Lembrete de Consulta</SelectItem>\r\n <SelectItem value=\"resultado\" className=\"hover:bg-blue-50 focus:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground dark:focus:bg-primary dark:focus:text-primary-foreground\">Resultado de Exame</SelectItem>\r\n <SelectItem value=\"instrucao\" className=\"hover:bg-blue-50 focus:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground dark:focus:bg-primary dark:focus:text-primary-foreground\">Instruções Pós-Consulta</SelectItem>\r\n <SelectItem value=\"outro\" className=\"hover:bg-blue-50 focus:bg-blue-50 cursor-pointer dark:hover:bg-primary dark:hover:text-primary-foreground dark:focus:bg-primary dark:focus:text-primary-foreground\">Outro</SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n </div>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n <div>\r\n <Label htmlFor=\"dataEnvio\">Data de envio</Label>\r\n <p id=\"dataEnvio\" className=\"text-sm text-muted-foreground\">03/09/2025</p>\r\n </div>\r\n <div>\r\n <Label htmlFor=\"statusEntrega\">Status da entrega</Label>\r\n <p id=\"statusEntrega\" className=\"text-sm text-muted-foreground\">Pendente</p>\r\n </div>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <Label>Resposta do paciente</Label>\r\n <div className=\"border rounded-md p-3 bg-muted/40 space-y-2\">\r\n <p className=\"text-sm\">\"Ok, obrigado pelo lembrete!\"</p>\r\n <p className=\"text-xs text-muted-foreground\">03/09/2025 14:30</p>\r\n </div>\r\n </div>\r\n <div className=\"flex justify-end mt-6\">\r\n <Button onClick={handleSave}>Registrar Comunicação</Button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n\r\n \r\n const renderPerfilSection = () => (\r\n <div className=\"space-y-6\">\r\n <div className=\"flex items-center justify-between\">\r\n <h2 className=\"text-2xl font-bold text-foreground\">Meu Perfil</h2>\r\n {!isEditingProfile ? (\r\n <Button onClick={() => setIsEditingProfile(true)} className=\"flex items-center gap-2\">\r\n <Edit className=\"h-4 w-4\" />\r\n Editar Perfil\r\n </Button>\r\n ) : (\r\n <div className=\"flex gap-2\">\r\n <Button onClick={handleSaveProfile} className=\"flex items-center gap-2\">\r\n Salvar\r\n </Button>\r\n <Button variant=\"outline\" onClick={handleCancelEdit} className=\"hover:bg-blue-50 dark:hover:bg-accent dark:hover:text-accent-foreground\">\r\n Cancelar\r\n </Button>\r\n </div>\r\n )}\r\n </div>\r\n\r\n <div className=\"grid gap-6 md:grid-cols-2\">\r\n {/* Informações Pessoais */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold border-b border-border text-foreground pb-2\">Informações Pessoais</h3>\r\n \r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"nome\">Nome Completo</Label>\r\n <p className=\"p-2 bg-muted rounded text-muted-foreground\">{profileData.nome}</p>\r\n <span className=\"text-xs text-muted-foreground\">Este campo não pode ser alterado</span>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"email\">Email</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"email\"\r\n type=\"email\"\r\n value={profileData.email}\r\n onChange={(e) => handleProfileChange('email', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.email}</p>\r\n )}\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"telefone\">Telefone</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"telefone\"\r\n value={profileData.telefone}\r\n onChange={(e) => handleProfileChange('telefone', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.telefone}</p>\r\n )}\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"crm\">CRM</Label>\r\n <p className=\"p-2 bg-muted rounded text-muted-foreground\">{profileData.crm}</p>\r\n <span className=\"text-xs text-muted-foreground\">Este campo não pode ser alterado</span>\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"especialidade\">Especialidade</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"especialidade\"\r\n value={profileData.especialidade}\r\n onChange={(e) => handleProfileChange('especialidade', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.especialidade}</p>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Endereço e Contato */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold border-b border-border text-foreground pb-2\">Endereço e Contato</h3>\r\n \r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"endereco\">Endereço</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"endereco\"\r\n value={profileData.endereco}\r\n onChange={(e) => handleProfileChange('endereco', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.endereco}</p>\r\n )}\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"cidade\">Cidade</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"cidade\"\r\n value={profileData.cidade}\r\n onChange={(e) => handleProfileChange('cidade', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.cidade}</p>\r\n )}\r\n </div>\r\n\r\n <div className=\"space-y-2\">\r\n <Label htmlFor=\"cep\">CEP</Label>\r\n {isEditingProfile ? (\r\n <Input\r\n id=\"cep\"\r\n value={profileData.cep}\r\n onChange={(e) => handleProfileChange('cep', e.target.value)}\r\n />\r\n ) : (\r\n <p className=\"p-2 bg-muted/50 rounded text-foreground\">{profileData.cep}</p>\r\n )}\r\n </div>\r\n\r\n {/* Biografia removida: não é um campo no registro de médico */}\r\n </div>\r\n </div>\r\n\r\n {/* Foto do Perfil */}\r\n <div className=\"border-t border-border pt-6\">\r\n <h3 className=\"text-lg font-semibold mb-4 text-foreground\">Foto do Perfil</h3>\r\n <div className=\"flex items-center gap-4\">\r\n <Avatar className=\"h-20 w-20\">\r\n <AvatarFallback className=\"text-lg\">\r\n {profileData.nome.split(' ').map(n => n[0]).join('').toUpperCase()}\r\n </AvatarFallback>\r\n </Avatar>\r\n {isEditingProfile && (\r\n <div className=\"space-y-2\">\r\n <Button variant=\"outline\" size=\"sm\">\r\n Alterar Foto\r\n </Button>\r\n <p className=\"text-xs text-muted-foreground\">\r\n Formatos aceitos: JPG, PNG (máx. 2MB)\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n\r\n \r\n const renderActiveSection = () => {\r\n switch (activeSection) {\r\n case 'calendario':\r\n return renderCalendarioSection();\r\n case 'pacientes':\r\n return (\r\n <section className=\"bg-card shadow-md rounded-lg border border-border p-6\">\r\n <h2 className=\"text-2xl font-bold mb-4\">Pacientes</h2>\r\n <div className=\"overflow-x-auto\">\r\n <Table>\r\n <TableHeader>\r\n <TableRow>\r\n <TableHead>Nome</TableHead>\r\n <TableHead>CPF</TableHead>\r\n <TableHead>Idade</TableHead>\r\n </TableRow>\r\n </TableHeader>\r\n <TableBody>\r\n {pacientes.map((paciente) => (\r\n <TableRow key={paciente.id ?? paciente.cpf}>\r\n <TableCell>{paciente.nome}</TableCell>\r\n <TableCell>{paciente.cpf}</TableCell>\r\n <TableCell>{getPatientAge(paciente) ? `${getPatientAge(paciente)} anos` : '-'}</TableCell>\r\n </TableRow>\r\n ))}\r\n </TableBody>\r\n </Table>\r\n </div>\r\n </section>\r\n );\r\n case 'laudos':\r\n return renderLaudosSection();\r\n case 'comunicacao':\r\n return renderComunicacaoSection();\r\n case 'perfil':\r\n return renderPerfilSection();\r\n default:\r\n return renderCalendarioSection();\r\n }\r\n };\r\n\r\n return (\r\n <ProtectedRoute requiredUserType={[\"profissional\"]}>\r\n <div className=\"container mx-auto px-4 py-8\">\r\n <header className=\"bg-card shadow-md rounded-lg border border-border p-4 mb-6 flex items-center justify-between\">\r\n <div className=\"flex items-center gap-4\">\r\n <Avatar className=\"h-12 w-12\">\r\n <AvatarImage src={(profileData as any).fotoUrl || undefined} alt={profileData.nome} />\r\n <AvatarFallback className=\"bg-muted\">\r\n <User className=\"h-5 w-5\" />\r\n </AvatarFallback>\r\n </Avatar>\r\n <div className=\"min-w-0\">\r\n <p className=\"text-sm text-muted-foreground truncate\">Conta do profissional</p>\r\n <h2 className=\"text-lg font-semibold leading-none truncate\">{profileData.nome}</h2>\r\n <p className=\"text-sm text-muted-foreground truncate\">{(profileData.crm ? `CRM: ${profileData.crm}` : '') + (profileData.especialidade ? ` • ${profileData.especialidade}` : '')}</p>\r\n {user?.email && (\r\n <p className=\"text-xs text-muted-foreground truncate\">Logado como: {user.email}</p>\r\n )}\r\n </div>\r\n </div>\r\n <div className=\"flex items-center gap-2\">\r\n <SimpleThemeToggle />\r\n <Button asChild variant=\"default\" className=\"mr-2 bg-primary hover:bg-primary/90 text-primary-foreground px-3 py-1 rounded shadow-sm shadow-blue-500/10 border border-primary\">\r\n <Link href=\"/\" aria-label=\"Início\">Início</Link>\r\n </Button>\r\n <Button \r\n variant=\"outline\" \r\n onClick={logout}\r\n className=\"text-red-600 border-red-600 hover:bg-red-50 cursor-pointer dark:hover:bg-red-600 dark:hover:text-white\"\r\n >\r\n Sair\r\n </Button>\r\n </div>\r\n </header>\r\n \r\n <div className=\"grid grid-cols-1 md:grid-cols-[220px_1fr] gap-6\">\r\n {}\r\n <aside className=\"md:sticky md:top-8 h-fit\">\r\n <nav className=\"bg-card shadow-md rounded-lg border border-border p-3 space-y-1\">\r\n <Button \r\n variant={activeSection === 'calendario' ? 'default' : 'ghost'} \r\n className=\"w-full justify-start transition-colors hover:bg-primary hover:text-white cursor-pointer\"\r\n onClick={() => setActiveSection('calendario')}\r\n >\r\n <CalendarIcon className=\"mr-2 h-4 w-4\" />\r\n Calendário\r\n </Button>\r\n <Button \r\n variant={activeSection === 'pacientes' ? 'default' : 'ghost'} \r\n className=\"w-full justify-start transition-colors hover:bg-primary hover:text-white cursor-pointer\"\r\n onClick={() => setActiveSection('pacientes')}\r\n >\r\n <Users className=\"mr-2 h-4 w-4\" />\r\n Pacientes\r\n </Button>\r\n <Button \r\n variant={activeSection === 'laudos' ? 'default' : 'ghost'} \r\n className=\"w-full justify-start transition-colors hover:bg-primary hover:text-white cursor-pointer\"\r\n onClick={() => setActiveSection('laudos')}\r\n >\r\n <FileText className=\"mr-2 h-4 w-4\" />\r\n Laudos\r\n </Button>\r\n <Button \r\n variant={activeSection === 'comunicacao' ? 'default' : 'ghost'} \r\n className=\"w-full justify-start transition-colors hover:bg-primary hover:text-white cursor-pointer\"\r\n onClick={() => setActiveSection('comunicacao')}\r\n >\r\n <MessageSquare className=\"mr-2 h-4 w-4\" />\r\n Comunicação\r\n </Button>\r\n <Button \r\n variant={activeSection === 'perfil' ? 'default' : 'ghost'} \r\n className=\"w-full justify-start transition-colors hover:bg-primary hover:text-white cursor-pointer\"\r\n onClick={() => setActiveSection('perfil')}\r\n >\r\n <Settings className=\"mr-2 h-4 w-4\" />\r\n Meu Perfil\r\n </Button>\r\n </nav>\r\n </aside>\r\n\r\n <main>\r\n <div className=\"flex justify-between items-center mb-4\">\r\n <h1 className=\"text-3xl font-bold\">Área do Profissional de Saúde</h1>\r\n </div>\r\n <p className=\"mb-8\">Bem-vindo à sua área exclusiva.</p>\r\n\r\n {renderActiveSection()}\r\n </main>\r\n </div>\r\n\r\n {}\r\n {showPopup && (\r\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50\">\r\n\r\n <div className=\"bg-card border border-border p-6 rounded-lg w-96\">\r\n\r\n {step === 1 && (\r\n <>\r\n <h3 className=\"text-lg font-semibold mb-2\">Selecionar Paciente</h3>\r\n <p className=\"text-sm text-gray-600 mb-4\">\r\n Data: {selectedDate ? new Date(selectedDate + 'T00:00:00').toLocaleDateString('pt-BR') : 'Não selecionada'}\r\n </p>\r\n <Select\r\n value={newEvent.title}\r\n onValueChange={(value) => setNewEvent({ ...newEvent, title: value })}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder=\"Selecione o paciente\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {pacientes && pacientes.map((paciente) => (\r\n <SelectItem key={paciente.cpf} value={paciente.nome}>\r\n {paciente.nome} - {paciente.cpf}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n <div className=\"flex gap-2 mt-4\">\r\n <Button\r\n onClick={() => setShowPopup(false)}\r\n variant=\"outline\"\r\n className=\"flex-1 hover:bg-blue-50 dark:hover:bg-accent dark:hover:text-accent-foreground\"\r\n >\r\n Cancelar\r\n </Button>\r\n <Button\r\n onClick={handleNextStep}\r\n disabled={!newEvent.title}\r\n className=\"flex-1\"\r\n >\r\n Próximo\r\n </Button>\r\n </div>\r\n </>\r\n )}\r\n\r\n {step === 2 && (\r\n <>\r\n <h3 className=\"text-lg font-semibold mb-4\">Tipo da Consulta</h3>\r\n <Select\r\n value={newEvent.type}\r\n onValueChange={(value) => setNewEvent({ ...newEvent, type: value })}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder=\"Selecione o tipo\" />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Object.keys(colorsByType).map((type) => (\r\n <SelectItem key={type} value={type}>\r\n {type}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n <div className=\"flex gap-2 mt-4\">\r\n <Button\r\n onClick={() => setStep(1)}\r\n variant=\"outline\"\r\n className=\"flex-1\"\r\n >\r\n Voltar\r\n </Button>\r\n <Button\r\n onClick={handleNextStep}\r\n disabled={!newEvent.type}\r\n className=\"flex-1\"\r\n >\r\n Próximo\r\n </Button>\r\n </div>\r\n </>\r\n )}\r\n\r\n {step === 3 && (\r\n <>\r\n <h3 className=\"text-lg font-semibold mb-4\">Horário da Consulta</h3>\r\n <Input\r\n type=\"time\"\r\n value={newEvent.time}\r\n onChange={(e) => setNewEvent({ ...newEvent, time: e.target.value })}\r\n className=\"mb-4\"\r\n />\r\n <div className=\"flex gap-2\">\r\n <Button\r\n onClick={() => setStep(2)}\r\n variant=\"outline\"\r\n className=\"flex-1\"\r\n >\r\n Voltar\r\n </Button>\r\n <Button\r\n onClick={handleNextStep}\r\n disabled={!newEvent.time}\r\n className=\"flex-1\"\r\n >\r\n {editingEvent ? \"Salvar\" : \"Agendar\"}\r\n </Button>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {}\r\n {showActionModal && selectedEvent && (\r\n <div className=\"fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50\">\r\n <div className=\"bg-card border border-border p-6 rounded-lg w-96\">\r\n <h3 className=\"text-lg font-semibold mb-2\">\r\n Consulta de {selectedEvent.title}\r\n </h3>\r\n <p className=\"text-sm text-gray-600 mb-4\">\r\n {selectedEvent.extendedProps.type} às {selectedEvent.extendedProps.time}\r\n </p>\r\n\r\n <div className=\"flex gap-2\">\r\n <Button\r\n onClick={handleStartEdit}\r\n className=\"flex-1 flex items-center gap-2\"\r\n >\r\n <Edit className=\"h-4 w-4\" />\r\n Editar\r\n </Button>\r\n <Button\r\n onClick={handleDeleteEvent}\r\n variant=\"destructive\"\r\n className=\"flex-1 flex items-center gap-2\"\r\n >\r\n <Trash2 className=\"h-4 w-4\" />\r\n Excluir\r\n </Button>\r\n </div>\r\n\r\n <Button\r\n onClick={() => setShowActionModal(false)}\r\n variant=\"outline\"\r\n className=\"w-full mt-2 hover:bg-blue-50 dark:hover:bg-accent dark:hover:text-accent-foreground\"\r\n >\r\n Cancelar\r\n </Button>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </ProtectedRoute>\r\n );\r\n};\r\n\r\nconst getShortId = (id?: string) => {\r\n if (!id) return '-';\r\n try {\r\n return id.length > 10 ? `${id.slice(0, 8)}...` : id;\r\n } catch (e) {\r\n return id;\r\n }\r\n};\r\n\r\nexport default ProfissionalPage;","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\resultados\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\app\\sobre\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ProtectedRoute.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\about-section.tsx","messages":[{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":15,"column":15,"nodeType":"JSXOpeningElement","endLine":19,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Card } from \"@/components/ui/card\"\r\nimport { Lightbulb, CheckCircle } from \"lucide-react\"\r\n\r\nexport function AboutSection() {\r\n const values = [\"Inovação\", \"Segurança\", \"Discrição\", \"Transparência\", \"Agilidade\"]\r\n\r\n return (\r\n <section className=\"py-16 lg:py-24 bg-muted/30\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"grid lg:grid-cols-2 gap-12 items-center\">\r\n {}\r\n <div className=\"space-y-8\">\r\n {}\r\n <div className=\"relative\">\r\n <img\r\n src=\"/Screenshot 2025-09-11 121911.png\"\r\n alt=\"Profissional trabalhando em laptop\"\r\n className=\"w-full h-auto rounded-2xl\"\r\n />\r\n </div>\r\n\r\n {}\r\n <Card className=\"bg-primary text-primary-foreground p-8 rounded-2xl\">\r\n <div className=\"flex items-start space-x-4\">\r\n <div className=\"flex-shrink-0 w-12 h-12 bg-primary-foreground/20 rounded-full flex items-center justify-center\">\r\n <Lightbulb className=\"w-6 h-6 text-primary-foreground\" />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <h3 className=\"text-sm font-semibold uppercase tracking-wide opacity-90\">NOSSO OBJETIVO</h3>\r\n <p className=\"text-lg leading-relaxed\">\r\n Nosso compromisso é garantir qualidade, segurança e sigilo em cada atendimento, unindo tecnologia à\r\n responsabilidade médica.\r\n </p>\r\n </div>\r\n </div>\r\n </Card>\r\n </div>\r\n\r\n {}\r\n <div className=\"space-y-8\">\r\n <div className=\"space-y-4\">\r\n <div className=\"inline-block px-4 py-2 bg-primary/10 text-primary rounded-full text-sm font-medium uppercase tracking-wide\">\r\n SOBRE NÓS\r\n </div>\r\n <h2 className=\"text-3xl lg:text-4xl font-bold text-foreground leading-tight text-balance\">\r\n Experimente o futuro do gerenciamento dos seus atendimentos médicos\r\n </h2>\r\n </div>\r\n\r\n <div className=\"space-y-6 text-muted-foreground leading-relaxed\">\r\n <p>\r\n Somos uma plataforma inovadora que conecta pacientes e médicos de forma prática, segura e humanizada.\r\n Nosso objetivo é simplificar o processo de emissão e acompanhamento de laudos médicos, oferecendo um\r\n ambiente online confiável e acessível.\r\n </p>\r\n <p>\r\n Aqui, os pacientes podem registrar suas informações de saúde e solicitar laudos de forma rápida,\r\n enquanto os médicos têm acesso a ferramentas que facilitam a análise, validação e emissão dos\r\n documentos.\r\n </p>\r\n </div>\r\n\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-xl font-semibold text-foreground\">Nossos valores</h3>\r\n <div className=\"grid grid-cols-2 gap-3\">\r\n {values.map((value, index) => (\r\n <div key={index} className=\"flex items-center space-x-2\">\r\n <CheckCircle className=\"w-5 h-5 text-primary flex-shrink-0\" />\r\n <span className=\"text-foreground font-medium\">{value}</span>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n )\r\n}\r\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\admin\\AssignmentForm.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agenda\\FooterAgenda.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agenda\\HeaderAgenda.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agendamento\\AgendaCalendar.tsx","messages":[{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.","line":185,"column":22,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&apos;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: &apos;C' para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&apos;`."},{"messageId":"replaceWithAlt","data":{"alt":"&lsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: &lsquo;C' para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&lsquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#39;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: &#39;C' para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&#39;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: &rsquo;C' para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&rsquo;`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.","line":185,"column":24,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&apos;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C&apos; para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&apos;`."},{"messageId":"replaceWithAlt","data":{"alt":"&lsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C&lsquo; para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&lsquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#39;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C&#39; para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&#39;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C&rsquo; para calendário, 'F' para fila de espera\r\n "},"desc":"Replace with `&rsquo;`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.","line":185,"column":43,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&apos;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, &apos;F' para fila de espera\r\n "},"desc":"Replace with `&apos;`."},{"messageId":"replaceWithAlt","data":{"alt":"&lsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, &lsquo;F' para fila de espera\r\n "},"desc":"Replace with `&lsquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#39;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, &#39;F' para fila de espera\r\n "},"desc":"Replace with `&#39;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, &rsquo;F' para fila de espera\r\n "},"desc":"Replace with `&rsquo;`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.","line":185,"column":45,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"&apos;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, 'F&apos; para fila de espera\r\n "},"desc":"Replace with `&apos;`."},{"messageId":"replaceWithAlt","data":{"alt":"&lsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, 'F&lsquo; para fila de espera\r\n "},"desc":"Replace with `&lsquo;`."},{"messageId":"replaceWithAlt","data":{"alt":"&#39;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, 'F&#39; para fila de espera\r\n "},"desc":"Replace with `&#39;`."},{"messageId":"replaceWithAlt","data":{"alt":"&rsquo;"},"fix":{"range":[6609,6688],"text":"\r\n Atalhos: 'C' para calendário, 'F&rsquo; para fila de espera\r\n "},"desc":"Replace with `&rsquo;`."}]}],"suppressedMessages":[],"errorCount":4,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\r\n'use client';\r\n\r\nimport { useState } from 'react';\r\nimport { ChevronLeft, ChevronRight, Plus, Clock, User, Calendar as CalendarIcon } from 'lucide-react';\r\n\r\ninterface Appointment {\r\n id: string;\r\n patient: string;\r\n time: string;\r\n duration: number;\r\n type: 'consulta' | 'exame' | 'retorno';\r\n status: 'confirmed' | 'pending' | 'absent';\r\n professional: string;\r\n notes: string;\r\n}\r\n\r\ninterface Professional {\r\n id: string;\r\n name: string;\r\n specialty: string;\r\n}\r\n\r\ninterface AgendaCalendarProps {\r\n professionals: Professional[];\r\n appointments: Appointment[];\r\n onAddAppointment: () => void;\r\n onEditAppointment: (appointment: Appointment) => void;\r\n}\r\n\r\nexport default function AgendaCalendar({ \r\n professionals, \r\n appointments, \r\n onAddAppointment, \r\n onEditAppointment \r\n}: AgendaCalendarProps) {\r\n const [view, setView] = useState<'day' | 'week' | 'month'>('week');\r\n const [selectedProfessional, setSelectedProfessional] = useState('all');\r\n const [currentDate, setCurrentDate] = useState(new Date());\r\n\r\n const timeSlots = Array.from({ length: 11 }, (_, i) => {\r\n const hour = i + 8; // Das 8h às 18h\r\n return [`${hour.toString().padStart(2, '0')}:00`, `${hour.toString().padStart(2, '0')}:30`];\r\n }).flat();\r\n\r\n const getStatusColor = (status: string) => {\r\n switch (status) {\r\n case 'confirmed': return 'bg-green-100 border-green-500 text-green-800';\r\n case 'pending': return 'bg-yellow-100 border-yellow-500 text-yellow-800';\r\n case 'absent': return 'bg-red-100 border-red-500 text-red-800';\r\n default: return 'bg-gray-100 border-gray-500 text-gray-800';\r\n }\r\n };\r\n\r\n const getTypeIcon = (type: string) => {\r\n switch (type) {\r\n case 'consulta': return '🩺';\r\n case 'exame': return '📋';\r\n case 'retorno': return '↩️';\r\n default: return '📅';\r\n }\r\n };\r\n\r\n const formatDate = (date: Date) => {\r\n return date.toLocaleDateString('pt-BR', { \r\n weekday: 'long', \r\n day: 'numeric', \r\n month: 'long', \r\n year: 'numeric' \r\n });\r\n };\r\n\r\n const navigateDate = (direction: 'prev' | 'next') => {\r\n const newDate = new Date(currentDate);\r\n if (view === 'day') {\r\n newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1));\r\n } else if (view === 'week') {\r\n newDate.setDate(newDate.getDate() + (direction === 'next' ? 7 : -7));\r\n } else {\r\n newDate.setMonth(newDate.getMonth() + (direction === 'next' ? 1 : -1));\r\n }\r\n setCurrentDate(newDate);\r\n };\r\n\r\n const goToToday = () => {\r\n setCurrentDate(new Date());\r\n };\r\n\r\n \r\n const filteredAppointments = selectedProfessional === 'all' \r\n ? appointments \r\n : appointments.filter(app => app.professional === selectedProfessional);\r\n\r\n return (\r\n <div className=\"bg-white rounded-lg shadow\">\r\n <div className=\"p-4 border-b border-gray-200\">\r\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between\">\r\n <h2 className=\"text-xl font-semibold text-gray-900 mb-4 sm:mb-0\">Agenda</h2>\r\n \r\n <div className=\"flex flex-wrap gap-2\">\r\n <select \r\n value={selectedProfessional}\r\n onChange={(e) => setSelectedProfessional(e.target.value)}\r\n className=\"px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500\"\r\n >\r\n <option value=\"all\">Todos os profissionais</option>\r\n {professionals.map(prof => (\r\n <option key={prof.id} value={prof.id}>{prof.name}</option>\r\n ))}\r\n </select>\r\n \r\n <div className=\"inline-flex rounded-md shadow-sm\">\r\n <button\r\n type=\"button\"\r\n onClick={() => setView('day')}\r\n className={`px-3 py-2 text-sm font-medium rounded-l-md ${\r\n view === 'day' \r\n ? 'bg-blue-100 text-blue-700 border border-blue-300' \r\n : 'bg-white text-gray-700 border border-gray-300'\r\n }`}\r\n >\r\n Dia\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() => setView('week')}\r\n className={`px-3 py-2 text-sm font-medium -ml-px ${\r\n view === 'week' \r\n ? 'bg-blue-100 text-blue-700 border border-blue-300' \r\n : 'bg-white text-gray-700 border border-gray-300'\r\n }`}\r\n >\r\n Semana\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() => setView('month')}\r\n className={`px-3 py-2 text-sm font-medium -ml-px rounded-r-md ${\r\n view === 'month' \r\n ? 'bg-blue-100 text-blue-700 border border-blue-300' \r\n : 'bg-white text-gray-700 border border-gray-300'\r\n }`}\r\n >\r\n Mês\r\n </button>\r\n </div>\r\n \r\n <button \r\n onClick={onAddAppointment}\r\n className=\"inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\r\n >\r\n <Plus className=\"h-4 w-4 mr-2\" />\r\n Novo Agendamento\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className=\"p-4 border-b border-gray-200\">\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center space-x-4\">\r\n <button \r\n onClick={() => navigateDate('prev')}\r\n className=\"p-1 rounded-md hover:bg-gray-100\"\r\n >\r\n <ChevronLeft className=\"h-5 w-5 text-gray-600\" />\r\n </button>\r\n <h3 className=\"text-lg font-medium text-gray-900\">\r\n {formatDate(currentDate)}\r\n </h3>\r\n <button \r\n onClick={() => navigateDate('next')}\r\n className=\"p-1 rounded-md hover:bg-gray-100\"\r\n >\r\n <ChevronRight className=\"h-5 w-5 text-gray-600\" />\r\n </button>\r\n <button \r\n onClick={goToToday}\r\n className=\"ml-4 px-3 py-1 text-sm border border-gray-300 rounded-md hover:bg-gray-100\"\r\n >\r\n Hoje\r\n </button>\r\n </div>\r\n <div className=\"text-sm text-gray-500\">\r\n Atalhos: 'C' para calendário, 'F' para fila de espera\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {}\r\n {view !== 'month' && (\r\n <div className=\"overflow-auto\">\r\n <div className=\"min-w-full\">\r\n <div className=\"flex\">\r\n <div className=\"w-20 flex-shrink-0 border-r border-gray-200\">\r\n <div className=\"h-12 border-b border-gray-200 flex items-center justify-center text-sm font-medium text-gray-500\">\r\n Hora\r\n </div>\r\n {timeSlots.map(time => (\r\n <div key={time} className=\"h-16 border-b border-gray-200 flex items-center justify-center text-sm text-gray-500\">\r\n {time}\r\n </div>\r\n ))}\r\n </div>\r\n \r\n <div className=\"flex-1\">\r\n <div className=\"h-12 border-b border-gray-200 flex items-center justify-center text-sm font-medium text-gray-500\">\r\n {currentDate.toLocaleDateString('pt-BR', { weekday: 'long' })}\r\n </div>\r\n <div className=\"relative\">\r\n {timeSlots.map(time => (\r\n <div key={time} className=\"h-16 border-b border-gray-200\"></div>\r\n ))}\r\n \r\n {filteredAppointments.map(app => {\r\n const [date, timeStr] = app.time.split('T');\r\n const [hours, minutes] = timeStr.split(':');\r\n const hour = parseInt(hours);\r\n const minute = parseInt(minutes);\r\n \r\n return (\r\n <div\r\n key={app.id}\r\n className={`absolute left-1 right-1 border-l-4 rounded p-2 shadow-sm cursor-pointer ${getStatusColor(app.status)}`}\r\n style={{\r\n top: `${((hour - 8) * 64 + (minute / 60) * 64) + 48}px`,\r\n height: `${(app.duration / 60) * 64}px`,\r\n }}\r\n onClick={() => onEditAppointment(app)}\r\n >\r\n <div className=\"flex justify-between items-start\">\r\n <div>\r\n <div className=\"font-medium flex items-center\">\r\n <User className=\"h-3 w-3 mr-1\" />\r\n {app.patient}\r\n </div>\r\n <div className=\"text-xs flex items-center mt-1\">\r\n <Clock className=\"h-3 w-3 mr-1\" />\r\n {hours}:{minutes} - {app.type} {getTypeIcon(app.type)}\r\n </div>\r\n <div className=\"text-xs mt-1\">\r\n {professionals.find(p => p.id === app.professional)?.name}\r\n </div>\r\n </div>\r\n <div className=\"text-xs capitalize\">\r\n {app.status === 'confirmed' ? 'confirmado' : app.status === 'pending' ? 'pendente' : 'ausente'}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {}\r\n {view === 'month' && (\r\n <div className=\"p-4\">\r\n <div className=\"space-y-4\">\r\n {filteredAppointments.map(app => {\r\n const [date, timeStr] = app.time.split('T');\r\n const [hours, minutes] = timeStr.split(':');\r\n \r\n return (\r\n <div key={app.id} className={`border-l-4 p-4 rounded-lg shadow-sm ${getStatusColor(app.status)}`}>\r\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 items-center\">\r\n <div className=\"flex items-center\">\r\n <User className=\"h-4 w-4 mr-2\" />\r\n <span className=\"font-medium\">{app.patient}</span>\r\n </div>\r\n <div className=\"flex items-center\">\r\n <Clock className=\"h-4 w-4 mr-2\" />\r\n <span>{hours}:{minutes} - {app.type} {getTypeIcon(app.type)}</span>\r\n </div>\r\n <div className=\"flex items-center\">\r\n <span className=\"text-sm\">{professionals.find(p => p.id === app.professional)?.name}</span>\r\n </div>\r\n </div>\r\n {app.notes && (\r\n <div className=\"mt-2 text-sm text-gray-600\">\r\n {app.notes}\r\n </div>\r\n )}\r\n <div className=\"mt-2 flex justify-end\">\r\n <button \r\n onClick={() => onEditAppointment(app)}\r\n className=\"text-blue-600 hover:text-blue-800 text-sm\"\r\n >\r\n Editar\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agendamento\\AppointmentModal.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agendamento\\ListaEspera.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\agendamento\\index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\credentials-dialog.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\dashboard\\header.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\dashboard\\sidebar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\footer.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\forms\\calendar-registration-form.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\forms\\doctor-registration-form.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\forms\\patient-registration-form.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\header.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\hero-section.tsx","messages":[{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":49,"column":15,"nodeType":"JSXOpeningElement","endLine":53,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Button } from \"@/components/ui/button\"\r\nimport { Shield, Clock, Users } from \"lucide-react\"\r\nimport Link from \"next/link\"\r\n\r\nexport function HeroSection() {\r\n return (\r\n <section className=\"py-8 lg:py-12 bg-background\">\r\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\r\n <div className=\"grid lg:grid-cols-2 gap-8 items-center\">\r\n {}\r\n <div className=\"space-y-6\">\r\n <div className=\"space-y-3\">\r\n <div className=\"inline-block px-4 py-2 bg-accent/10 text-accent rounded-full text-sm font-medium\">\r\n APROXIMANDO MÉDICOS E PACIENTES\r\n </div>\r\n <h1 className=\"text-3xl lg:text-4xl font-bold text-foreground leading-tight text-balance\">\r\n Segurança, <span className=\"text-primary\">Confiabilidade</span> e{\" \"}\r\n <span className=\"text-primary\">Rapidez</span>\r\n </h1>\r\n <div className=\"space-y-1 text-base text-muted-foreground\">\r\n <p>Experimente o futuro dos agendamentos.</p>\r\n <p>Encontre profissionais capacitados e marque já sua consulta.</p>\r\n </div>\r\n </div>\r\n\r\n {}\r\n <div className=\"flex flex-col sm:flex-row gap-4\">\r\n <Button \r\n size=\"lg\" \r\n className=\"bg-primary hover:bg-primary/90 text-primary-foreground cursor-pointer shadow-sm shadow-blue-500/10 border border-blue-200 dark:shadow-none dark:border-transparent\"\r\n asChild\r\n >\r\n <Link href=\"/login-paciente\">Portal do Paciente</Link>\r\n </Button>\r\n <Button\r\n size=\"lg\"\r\n variant=\"outline\"\r\n className=\"text-primary border-primary bg-transparent cursor-pointer shadow-sm shadow-blue-500/10 border border-blue-200 hover:bg-blue-50 dark:shadow-none dark:border-primary dark:hover:bg-primary dark:hover:text-primary-foreground\"\r\n asChild\r\n >\r\n <Link href=\"/login\">Sou Profissional de Saúde</Link>\r\n </Button>\r\n </div>\r\n </div>\r\n\r\n {}\r\n <div className=\"relative\">\r\n <div className=\"relative rounded-2xl overflow-hidden bg-gradient-to-br from-accent/20 to-primary/20 p-6\">\r\n <img\r\n src=\"/medico-sorridente-de-tiro-medio-vestindo-casaco.jpg\"\r\n alt=\"Médico profissional sorrindo\"\r\n className=\"w-full h-auto rounded-lg min-h-80 max-h-[500px] object-cover object-center\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {}\r\n <div className=\"mt-10 grid md:grid-cols-3 gap-6\">\r\n <div className=\"flex items-start space-x-3\">\r\n <div className=\"flex-shrink-0 w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <Shield className=\"w-4 h-4 text-primary\" />\r\n </div>\r\n <div>\r\n <h3 className=\"font-semibold text-foreground\">Laudos digitais e padronizados</h3>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start space-x-3\">\r\n <div className=\"flex-shrink-0 w-8 h-8 bg-accent/10 rounded-full flex items-center justify-center\">\r\n <Clock className=\"w-4 h-4 text-accent\" />\r\n </div>\r\n <div>\r\n <h3 className=\"font-semibold text-foreground\">Notificações automáticas ao paciente</h3>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-start space-x-3\">\r\n <div className=\"flex-shrink-0 w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center\">\r\n <Users className=\"w-4 h-4 text-primary\" />\r\n </div>\r\n <div>\r\n <h3 className=\"font-semibold text-foreground\">LGPD: controle de acesso e consentimento</h3>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </section>\r\n )\r\n}\r\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\simple-theme-toggle.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\theme-provider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\theme-toggle.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\accordion.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\alert-dialog.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\alert.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\aspect-ratio.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\avatar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\badge.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\breadcrumb.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\button.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\calendar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\card.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\carousel.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\chart.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\checkbox.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\collapsible.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\command.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\context-menu.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\dialog.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\drawer.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\dropdown-menu.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\form.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\hover-card.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\input-otp.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\input.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\label.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\menubar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\navigation-menu.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\pagination.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\popover.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\progress.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\radio-group.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\resizable.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\scroll-area.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\select.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\separator.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\sheet.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\sidebar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\skeleton.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\slider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\sonner.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\switch.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\table.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\tabs.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\textarea.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\toast.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\toaster.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\toggle-group.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\toggle.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\tooltip.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\use-mobile.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\components\\ui\\use-toast.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\eslint.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\UseAgenda.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\use-force-default-theme.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\use-mobile.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\use-toast.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\useAuth.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\hooks\\useReports.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\api.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\assignment.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\auth.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\debug-utils.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\env-config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\http.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\jwt.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\mocks\\appointment-mocks.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\reports.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\lib\\utils.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\next.config.mjs","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\postcss.config.mjs","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\public\\forward-client-logs.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\src\\app\\api\\assign-role\\route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\types\\auth.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\types\\react-signature-canvas.d.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Gabri\\Desktop\\riseup-squad20\\susconecta\\types\\report-types.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]