chore: update components config

This commit is contained in:
Jonas Francisco 2025-09-11 22:09:50 -03:00
parent a5d89b3fff
commit a7c9c90ebb
15 changed files with 77 additions and 81 deletions

View File

@ -1,10 +1,10 @@
// app/agendamento/page.tsx
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
// Importação dinâmica para evitar erros de SSR
const AgendaCalendar = dynamic(() => import('@/components/agendamento/AgendaCalendar'), {
ssr: false,
loading: () => (
@ -24,7 +24,7 @@ const AgendaCalendar = dynamic(() => import('@/components/agendamento/AgendaCale
const AppointmentModal = dynamic(() => import('@/components/agendamento/AppointmentModal'), { ssr: false });
const ListaEspera = dynamic(() => import('@/components/agendamento/ListaEspera'), { ssr: false });
// Dados mockados
const mockAppointments = [
{ id: '1', patient: 'Ana Costa', time: '2025-09-10T09:00', duration: 30, type: 'consulta' as const, status: 'confirmed' as const, professional: '1', notes: '' },
{ id: '2', patient: 'Pedro Alves', time: '2025-09-10T10:30', duration: 45, type: 'retorno' as const, status: 'pending' as const, professional: '2', notes: '' },

View File

@ -1,4 +1,4 @@
/* src/app/dashboard/pacientes/page.tsx */
"use client";
import { useEffect, useMemo, useState } from "react";
@ -11,7 +11,7 @@ import { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "luci
import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
import { PatientRegistrationForm } from "@/components/forms/patient-registration-form";
// Converte qualquer formato que vier do mock para o shape do nosso tipo Paciente
function normalizePaciente(p: any): Paciente {
const endereco: Endereco = {
cep: p.endereco?.cep ?? p.cep ?? "",
@ -114,7 +114,7 @@ export default function PacientesPage() {
const q = search.trim();
if (!q) return loadAll();
// Se for apenas números, tentamos como ID no servidor
if (/^\d+$/.test(q)) {
try {
setLoading(true);
@ -130,7 +130,7 @@ export default function PacientesPage() {
return;
}
// Senão, recarrega e filtra local (o mock nem sempre filtra por nome/CPF)
await loadAll();
setTimeout(() => setSearch(q), 0);
}
@ -161,7 +161,7 @@ export default function PacientesPage() {
return (
<div className="space-y-6">
{/* Cabeçalho + Busca (um único input no padrão do print) */}
{}
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold">Pacientes</h1>

View File

@ -31,7 +31,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
// Importações do FullCalendar
import dynamic from "next/dynamic";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
@ -54,7 +54,7 @@ const medico = {
fotoUrl: "",
}
// Tipos de consulta com cores
const colorsByType = {
Rotina: "#4dabf7",
Cardiologia: "#f76c6c",
@ -116,7 +116,7 @@ const ProfissionalPage = () => {
setPacienteSelecionado(null);
};
// Clicar em um dia -> abrir popup 3 etapas
const handleDateClick = (arg: any) => {
setSelectedDate(arg.dateStr);
setNewEvent({ title: "", type: "", time: "", pacienteId: "" });
@ -125,7 +125,7 @@ const ProfissionalPage = () => {
setShowPopup(true);
};
// Adicionar nova consulta
const handleAddEvent = () => {
const paciente = pacientes.find(p => p.nome === newEvent.title);
const eventToAdd = {
@ -141,7 +141,7 @@ const ProfissionalPage = () => {
setShowPopup(false);
};
// Editar consulta existente
const handleEditEvent = () => {
setEvents((prevEvents) =>
prevEvents.map((ev) =>
@ -161,19 +161,19 @@ const ProfissionalPage = () => {
setShowActionModal(false);
};
// Próxima etapa no popup
const handleNextStep = () => {
if (step < 3) setStep(step + 1);
else editingEvent ? handleEditEvent() : handleAddEvent();
};
// Clicar em uma consulta -> abre modal de ação (Editar/Apagar)
const handleEventClick = (clickInfo: any) => {
setSelectedEvent(clickInfo.event);
setShowActionModal(true);
};
// Apagar consulta
const handleDeleteEvent = () => {
if (!selectedEvent) return;
setEvents((prevEvents) =>
@ -182,7 +182,7 @@ const ProfissionalPage = () => {
setShowActionModal(false);
};
// Começar a editar
const handleStartEdit = () => {
if (!selectedEvent) return;
setEditingEvent(selectedEvent);
@ -197,7 +197,7 @@ const ProfissionalPage = () => {
setShowPopup(true);
};
// Aparência da consulta dentro do calendário
const renderEventContent = (eventInfo: any) => {
const bg = eventInfo.event.backgroundColor || eventInfo.event.extendedProps?.color || "#4dabf7";
@ -241,7 +241,7 @@ const ProfissionalPage = () => {
</header>
<div className="grid grid-cols-1 md:grid-cols-[220px_1fr] gap-6">
{/* Sidebar */}
{}
<aside className="md:sticky md:top-8 h-fit">
<nav className="bg-white shadow-md rounded-lg p-3 space-y-1">
<Button asChild variant="ghost" className="w-full justify-start hover:bg-primary hover:text-primary-foreground">
@ -602,7 +602,7 @@ const ProfissionalPage = () => {
</main>
</div>
{/* POPUP 3 etapas (Adicionar/Editar) */}
{}
{showPopup && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50">
@ -713,7 +713,7 @@ const ProfissionalPage = () => {
</div>
)}
{/* MODAL de ação ao clicar em consulta */}
{}
{showActionModal && selectedEvent && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
<div className="bg-white p-6 rounded-lg w-96">

View File

@ -8,9 +8,9 @@ export function AboutSection() {
<section className="py-16 lg:py-24 bg-muted/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left Content */}
{}
<div className="space-y-8">
{/* Professional Image */}
{}
<div className="relative">
<img
src="/Screenshot 2025-09-11 121911.png"
@ -19,7 +19,7 @@ export function AboutSection() {
/>
</div>
{/* Objective Card */}
{}
<Card className="bg-primary text-primary-foreground p-8 rounded-2xl">
<div className="flex items-start space-x-4">
<div className="flex-shrink-0 w-12 h-12 bg-primary-foreground/20 rounded-full flex items-center justify-center">
@ -36,7 +36,7 @@ export function AboutSection() {
</Card>
</div>
{/* Right Content */}
{}
<div className="space-y-8">
<div className="space-y-4">
<div className="inline-block px-4 py-2 bg-primary/10 text-primary rounded-full text-sm font-medium uppercase tracking-wide">

View File

@ -1,10 +1,10 @@
// app/agenda/page.tsx
'use client';
import { useState } from 'react';
import { AgendaCalendar, AppointmentModal, ListaEspera } from '@/components/agendamento';
// Dados mockados - substitua pelos seus dados reais
const mockAppointments = [
{ id: '1', patient: 'Ana Costa', time: '2025-09-10T09:00', duration: 30, type: 'consulta' as const, status: 'confirmed' as const, professional: '1', notes: '' },
{ id: '2', patient: 'Pedro Alves', time: '2025-09-10T10:30', duration: 45, type: 'retorno' as const, status: 'pending' as const, professional: '2', notes: '' },
@ -32,10 +32,10 @@ export default function AgendaPage() {
const handleSaveAppointment = (appointment: any) => {
if (appointment.id) {
// Editar agendamento existente
setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
} else {
// Novo agendamento
const newAppointment = {
...appointment,
id: Date.now().toString(),
@ -60,7 +60,7 @@ export default function AgendaPage() {
};
const handleNotifyPatient = (patientId: string) => {
// Lógica para notificar paciente
console.log(`Notificando paciente ${patientId}`);
};

View File

@ -1,4 +1,4 @@
// components/agendamento/AgendaCalendar.tsx (atualizado)
'use client';
import { useState } from 'react';
@ -86,7 +86,7 @@ export default function AgendaCalendar({
setCurrentDate(new Date());
};
// Filtra os agendamentos por profissional selecionado
const filteredAppointments = selectedProfessional === 'all'
? appointments
: appointments.filter(app => app.professional === selectedProfessional);
@ -187,7 +187,7 @@ export default function AgendaCalendar({
</div>
</div>
{/* Visualização de Dia/Semana (calendário) */}
{}
{view !== 'month' && (
<div className="overflow-auto">
<div className="min-w-full">
@ -256,7 +256,7 @@ export default function AgendaCalendar({
</div>
)}
{/* Visualização de Mês (lista) */}
{}
{view === 'month' && (
<div className="p-4">
<div className="space-y-4">

View File

@ -1,4 +1,4 @@
// components/agendamento/ListaEspera.tsx
'use client';
import { useState } from 'react';

View File

@ -12,10 +12,10 @@ export function Footer() {
<footer className="bg-background border-t border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex flex-col md:flex-row items-center justify-between space-y-4 md:space-y-0">
{/* Copyright */}
{}
<div className="text-muted-foreground text-sm">© 2025 SUS Conecta</div>
{/* Footer Links */}
{}
<nav className="flex items-center space-x-8">
<a href="#" className="text-muted-foreground hover:text-primary transition-colors text-sm">
Termos
@ -28,7 +28,7 @@ export function Footer() {
</a>
</nav>
{/* Back to Top Button */}
{}
<Button
variant="outline"
size="sm"

View File

@ -1,4 +1,4 @@
/* src/components/forms/patient-registration-form.tsx */
"use client";
import { useEffect, useMemo, useState } from "react";
@ -101,7 +101,7 @@ export function PatientRegistrationForm({
const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]);
// Edição
useEffect(() => {
async function load() {
if (mode !== "edit" || patientId == null) return;
@ -129,7 +129,7 @@ export function PatientRegistrationForm({
const ax = await listarAnexos(String(patientId)).catch(() => []);
setServerAnexos(Array.isArray(ax) ? ax : []);
} catch {
// ignora
}
}
load();
@ -208,7 +208,7 @@ export function PatientRegistrationForm({
ev.preventDefault();
if (!validateLocal()) return;
// validação externa do CPF (mock → pode falhar, tratamos erro legível)
try {
const { valido, existe } = await validarCPF(form.cpf);
if (!valido) {
@ -220,7 +220,7 @@ export function PatientRegistrationForm({
return;
}
} catch {
// se o mock der 404/timeout, seguimos sem bloquear
}
setSubmitting(true);
@ -318,7 +318,7 @@ export function PatientRegistrationForm({
)}
<form onSubmit={handleSubmit} className="space-y-6">
{/* DADOS PESSOAIS */}
{}
<Collapsible open={expanded.dados} onOpenChange={() => setExpanded((s) => ({ ...s, dados: !s.dados }))}>
<Card>
<CollapsibleTrigger asChild>
@ -337,7 +337,7 @@ export function PatientRegistrationForm({
<div className="flex items-center gap-4">
<div className="w-24 h-24 border-2 border-dashed border-muted-foreground rounded-lg flex items-center justify-center overflow-hidden">
{photoPreview ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={photoPreview} alt="Preview" className="w-full h-full object-cover" />
) : (
<FileImage className="h-8 w-8 text-muted-foreground" />
@ -420,7 +420,7 @@ export function PatientRegistrationForm({
</Card>
</Collapsible>
{/* CONTATO */}
{}
<Collapsible open={expanded.contato} onOpenChange={() => setExpanded((s) => ({ ...s, contato: !s.contato }))}>
<Card>
<CollapsibleTrigger asChild>
@ -448,7 +448,7 @@ export function PatientRegistrationForm({
</Card>
</Collapsible>
{/* ENDEREÇO */}
{}
<Collapsible open={expanded.endereco} onOpenChange={() => setExpanded((s) => ({ ...s, endereco: !s.endereco }))}>
<Card>
<CollapsibleTrigger asChild>
@ -517,7 +517,7 @@ export function PatientRegistrationForm({
</Card>
</Collapsible>
{/* OBSERVAÇÕES & ANEXOS */}
{}
<Collapsible open={expanded.obs} onOpenChange={() => setExpanded((s) => ({ ...s, obs: !s.obs }))}>
<Card>
<CollapsibleTrigger asChild>
@ -584,7 +584,7 @@ export function PatientRegistrationForm({
</Card>
</Collapsible>
{/* AÇÕES */}
{}
<div className="flex justify-end gap-4 pt-6 border-t">
<Button type="button" variant="outline" onClick={() => (inline ? onClose?.() : onOpenChange?.(false))} disabled={isSubmitting}>
<XCircle className="mr-2 h-4 w-4" />

View File

@ -19,7 +19,7 @@ export function Header() {
</span>
</Link>
{/* Desktop Navigation */}
{}
<nav className="hidden md:flex items-center space-x-8">
<Link
href="/"
@ -32,7 +32,7 @@ export function Header() {
</Link>
</nav>
{/* Desktop Action Buttons */}
{}
<div className="hidden md:flex items-center space-x-3">
<Button
variant="outline"
@ -53,13 +53,13 @@ export function Header() {
</Link>
</div>
{/* Mobile Menu Button */}
{}
<button className="md:hidden p-2" onClick={() => setIsMenuOpen(!isMenuOpen)} aria-label="Toggle menu">
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
{/* Mobile Menu */}
{}
{isMenuOpen && (
<div className="md:hidden py-4 border-t border-border">
<nav className="flex flex-col space-y-4">

View File

@ -7,7 +7,7 @@ export function HeroSection() {
<section className="py-16 lg:py-24 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Content */}
{}
<div className="space-y-8">
<div className="space-y-4">
<div className="inline-block px-4 py-2 bg-accent/10 text-accent rounded-full text-sm font-medium">
@ -23,7 +23,7 @@ export function HeroSection() {
</div>
</div>
{/* Action Buttons */}
{}
<div className="flex flex-col sm:flex-row gap-4">
<Button size="lg" className="bg-primary hover:bg-primary/90 text-primary-foreground">
Sou Paciente
@ -38,7 +38,7 @@ export function HeroSection() {
</div>
</div>
{/* Hero Image */}
{}
<div className="relative">
<div className="relative rounded-2xl overflow-hidden bg-gradient-to-br from-accent/20 to-primary/20 p-8">
<img
@ -50,7 +50,7 @@ export function HeroSection() {
</div>
</div>
{/* Features */}
{}
<div className="mt-16 grid md:grid-cols-3 gap-8">
<div className="flex items-start space-x-3">
<div className="flex-shrink-0 w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center">

View File

@ -1,4 +1,4 @@
// hooks/useAgenda.ts
import { useState } from 'react';
export interface Appointment {
@ -42,10 +42,10 @@ export const useAgenda = () => {
const handleSaveAppointment = (appointment: Appointment) => {
if (appointment.id) {
// Editar agendamento existente
setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
} else {
// Novo agendamento
const newAppointment = {
...appointment,
id: Date.now().toString(),
@ -70,7 +70,7 @@ export const useAgenda = () => {
};
const handleNotifyPatient = (patientId: string) => {
// Lógica para notificar paciente
console.log(`Notificando paciente ${patientId}`);
};

View File

@ -1,6 +1,6 @@
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
@ -93,8 +93,7 @@ export const reducer = (state: State, action: Action): State => {
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {

View File

@ -1,4 +1,4 @@
/* src/lib/api.ts */
export type ApiOk<T = any> = {
success: boolean;
@ -58,9 +58,7 @@ export type PacienteInput = {
observacoes?: string | null;
};
// -----------------------------------------------------------------------------
// Config & helpers
// -----------------------------------------------------------------------------
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://mock.apidog.com/m1/1053378-0-default";
@ -97,21 +95,20 @@ async function parse<T>(res: Response): Promise<T> {
try {
json = await res.json();
} catch {
// ignore
}
if (!res.ok) {
const code = json?.apidogError?.code ?? res.status;
const msg = json?.apidogError?.message ?? res.statusText;
throw new Error(`${code}: ${msg}`);
}
// muitos endpoints do mock respondem { success, data }
return (json?.data ?? json) as T;
}
// -----------------------------------------------------------------------------
//
// Pacientes (CRUD)
// -----------------------------------------------------------------------------
//
export async function listarPacientes(params?: { page?: number; limit?: number; q?: string }): Promise<Paciente[]> {
const query = new URLSearchParams();
if (params?.page) query.set("page", String(params.page));
@ -156,9 +153,9 @@ export async function excluirPaciente(id: string | number): Promise<void> {
logAPI("excluirPaciente", { url, result: { ok: true } });
}
// -----------------------------------------------------------------------------
//
// Foto
// -----------------------------------------------------------------------------
//
export async function uploadFotoPaciente(id: string | number, file: File): Promise<{ foto_url?: string; thumbnail_url?: string }> {
const url = `${API_BASE}${PATHS.foto(id)}`;
@ -178,9 +175,9 @@ export async function removerFotoPaciente(id: string | number): Promise<void> {
logAPI("removerFotoPaciente", { url, result: { ok: true } });
}
// -----------------------------------------------------------------------------
//
// Anexos
// -----------------------------------------------------------------------------
//
export async function listarAnexos(id: string | number): Promise<any[]> {
const url = `${API_BASE}${PATHS.anexos(id)}`;
@ -193,7 +190,7 @@ export async function listarAnexos(id: string | number): Promise<any[]> {
export async function adicionarAnexo(id: string | number, file: File): Promise<any> {
const url = `${API_BASE}${PATHS.anexos(id)}`;
const fd = new FormData();
// alguns mocks usam "arquivo" e outros "file"; tentamos ambos
fd.append("arquivo", file);
const res = await fetch(url, { method: "POST", body: fd, headers: headers("form") });
const data = await parse<ApiOk<any>>(res);
@ -208,9 +205,9 @@ export async function removerAnexo(id: string | number, anexoId: string | number
logAPI("removerAnexo", { url, result: { ok: true } });
}
// -----------------------------------------------------------------------------
//
// Validações
// -----------------------------------------------------------------------------
//
export async function validarCPF(cpf: string): Promise<{ valido: boolean; existe: boolean; paciente_id: string | null }> {
const url = `${API_BASE}${PATHS.validarCPF}`;

View File

@ -1,6 +1,6 @@
@import 'tailwindcss';
@import 'tw-animate-css';
/* Removed invalid @custom-variant at-rule */
:root {
--background: var(--primary)