Compare commits
9 Commits
dfb70c6d6e
...
e53d7fb96e
| Author | SHA1 | Date | |
|---|---|---|---|
| e53d7fb96e | |||
| 7aadcefb86 | |||
| c6b18b7f34 | |||
| 945c6eafb6 | |||
| 9dfba10baa | |||
| f435ade0d6 | |||
| 9c7ce7d7d2 | |||
| bb4cc3895c | |||
| 953a4e7fa0 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules/
|
||||||
|
|
||||||
|
|||||||
41
susconecta/app/(main-routes)/calendar/index.css
Normal file
41
susconecta/app/(main-routes)/calendar/index.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
.fc-media-screen {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 74vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fc-prev-button,
|
||||||
|
.fc-next-button {
|
||||||
|
background-color: var(--color-blue-600) !important;
|
||||||
|
border: none !important;
|
||||||
|
transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-prev-button:hover,
|
||||||
|
.fc-next-button:hover {
|
||||||
|
background-color: var(--color-blue-700) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-timeGridWeek-button,
|
||||||
|
.fc-timeGridDay-button,
|
||||||
|
.fc-dayGridMonth-button {
|
||||||
|
border: none !important;
|
||||||
|
background-color: var(--color-blue-600) !important;
|
||||||
|
transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-timeGridWeek-button:hover,
|
||||||
|
.fc-timeGridDay-button:hover,
|
||||||
|
.fc-dayGridMonth-button:hover {
|
||||||
|
background-color: var(--color-blue-700) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-active {
|
||||||
|
background-color: var(--color-blue-500) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-toolbar-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-gray-900);
|
||||||
|
}
|
||||||
174
susconecta/app/(main-routes)/calendar/page.tsx
Normal file
174
susconecta/app/(main-routes)/calendar/page.tsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import pt_br_locale from "@fullcalendar/core/locales/pt-br";
|
||||||
|
import FullCalendar from "@fullcalendar/react";
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
|
import { EventInput } from "@fullcalendar/core/index.js";
|
||||||
|
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||||
|
import { PagesHeader } from "@/components/dashboard/header";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
mockAppointments,
|
||||||
|
mockWaitingList,
|
||||||
|
} from "@/lib/mocks/appointment-mocks";
|
||||||
|
import "./index.css";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
const ListaEspera = dynamic(
|
||||||
|
() => import("@/components/agendamento/ListaEspera"),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function AgendamentoPage() {
|
||||||
|
const [appointments, setAppointments] = useState(mockAppointments);
|
||||||
|
const [waitingList, setWaitingList] = useState(mockWaitingList);
|
||||||
|
const [activeTab, setActiveTab] = useState<"calendar" | "espera">("calendar");
|
||||||
|
const [requestsList, setRequestsList] = useState<EventInput[]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "c") {
|
||||||
|
setActiveTab("calendar");
|
||||||
|
}
|
||||||
|
if (event.key === "f") {
|
||||||
|
setActiveTab("espera");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let events: EventInput[] = [];
|
||||||
|
appointments.forEach((obj) => {
|
||||||
|
const event: EventInput = {
|
||||||
|
title: `${obj.patient}: ${obj.type}`,
|
||||||
|
start: new Date(obj.time),
|
||||||
|
end: new Date(new Date(obj.time).getTime() + obj.duration * 60 * 1000),
|
||||||
|
color:
|
||||||
|
obj.status === "confirmed"
|
||||||
|
? "#68d68a"
|
||||||
|
: obj.status === "pending"
|
||||||
|
? "#ffe55f"
|
||||||
|
: "#ff5f5fff",
|
||||||
|
};
|
||||||
|
events.push(event);
|
||||||
|
});
|
||||||
|
setRequestsList(events);
|
||||||
|
}, [appointments]);
|
||||||
|
|
||||||
|
// mantive para caso a lógica de salvar consulta passe a funcionar
|
||||||
|
const handleSaveAppointment = (appointment: any) => {
|
||||||
|
if (appointment.id) {
|
||||||
|
setAppointments((prev) =>
|
||||||
|
prev.map((a) => (a.id === appointment.id ? appointment : a))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const newAppointment = {
|
||||||
|
...appointment,
|
||||||
|
id: Date.now().toString(),
|
||||||
|
};
|
||||||
|
setAppointments((prev) => [...prev, newAppointment]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNotifyPatient = (patientId: string) => {
|
||||||
|
console.log(`Notificando paciente ${patientId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
<div className="flex w-full flex-col gap-10 p-6">
|
||||||
|
<div className="flex flex-row justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">{activeTab === "calendar" ? "Calendário" : "Lista de Espera"}</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Navegue através dos atalhos: Calendário (C) ou Fila de espera
|
||||||
|
(F).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{/* <Link href={"/agenda"}>
|
||||||
|
<Button className="bg-blue-600 hover:bg-blue-700">
|
||||||
|
Agenda
|
||||||
|
</Button>
|
||||||
|
</Link> */}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className="bg-blue-600 hover:bg-blue-700 px-5 py-1 text-white rounded-sm">
|
||||||
|
Opções »
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<Link href={"/agenda"}>
|
||||||
|
<DropdownMenuItem>Agendamento</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/procedimento"}>
|
||||||
|
<DropdownMenuItem>Procedimento</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/financeiro"}>
|
||||||
|
<DropdownMenuItem>Financeiro</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
className="bg-gray-100 hover:bg-gray-200 hover:text-foreground rounded-l-[100px] rounded-r-[0px]"
|
||||||
|
onClick={() => setActiveTab("calendar")}
|
||||||
|
>
|
||||||
|
Calendário
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
className="bg-gray-100 hover:bg-gray-200 hover:text-foreground rounded-r-[100px] rounded-l-[0px]"
|
||||||
|
onClick={() => setActiveTab("espera")}
|
||||||
|
>
|
||||||
|
Lista de espera
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeTab === "calendar" ? (
|
||||||
|
<div className="flex w-full">
|
||||||
|
<FullCalendar
|
||||||
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
|
initialView="dayGridMonth"
|
||||||
|
locale={pt_br_locale}
|
||||||
|
events={requestsList}
|
||||||
|
headerToolbar={{
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||||
|
}}
|
||||||
|
dateClick={(info) => {
|
||||||
|
info.view.calendar.changeView("timeGridDay", info.dateStr);
|
||||||
|
}}
|
||||||
|
selectable={true}
|
||||||
|
selectMirror={true}
|
||||||
|
dayMaxEvents={true}
|
||||||
|
dayMaxEventRows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ListaEspera
|
||||||
|
patients={waitingList}
|
||||||
|
onNotify={handleNotifyPatient}
|
||||||
|
onAddToWaitlist={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -140,7 +140,7 @@ export default function PacientesPage() {
|
|||||||
|
|
||||||
if (showForm) {
|
if (showForm) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 p-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button variant="ghost" onClick={() => setShowForm(false)}>
|
<Button variant="ghost" onClick={() => setShowForm(false)}>
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
@ -160,7 +160,7 @@ export default function PacientesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 p-6">
|
||||||
{}
|
{}
|
||||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
41
susconecta/app/(main-routes)/dashboard/page.tsx
Normal file
41
susconecta/app/(main-routes)/dashboard/page.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export default function DashboardPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="space-y-6 p-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Bem-vindo ao painel de controle
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<div className="bg-card p-6 rounded-lg border">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground">
|
||||||
|
Total de Pacientes
|
||||||
|
</h3>
|
||||||
|
<p className="text-2xl font-bold text-foreground">1,234</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-card p-6 rounded-lg border">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground">
|
||||||
|
Consultas Hoje
|
||||||
|
</h3>
|
||||||
|
<p className="text-2xl font-bold text-foreground">28</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-card p-6 rounded-lg border">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground">
|
||||||
|
Próximas Consultas
|
||||||
|
</h3>
|
||||||
|
<p className="text-2xl font-bold text-foreground">45</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-card p-6 rounded-lg border">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground">
|
||||||
|
Receita Mensal
|
||||||
|
</h3>
|
||||||
|
<p className="text-2xl font-bold text-foreground">R$ 45.230</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
susconecta/app/(main-routes)/layout.tsx
Normal file
22
susconecta/app/(main-routes)/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import type React from "react";
|
||||||
|
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||||
|
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
|
import { PagesHeader } from "@/components/dashboard/header";
|
||||||
|
|
||||||
|
export default function MainRoutesLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background flex">
|
||||||
|
<SidebarProvider>
|
||||||
|
<Sidebar />
|
||||||
|
<main className="flex-1">
|
||||||
|
<PagesHeader />
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</SidebarProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
373
susconecta/app/agenda/page.tsx
Normal file
373
susconecta/app/agenda/page.tsx
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Calendar } from "lucide-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
RotateCcw,
|
||||||
|
Accessibility,
|
||||||
|
Volume2,
|
||||||
|
Flame,
|
||||||
|
Settings,
|
||||||
|
Clipboard,
|
||||||
|
Search,
|
||||||
|
ChevronDown,
|
||||||
|
Upload,
|
||||||
|
FileDown,
|
||||||
|
Tag,
|
||||||
|
Save,
|
||||||
|
} from "lucide-react";
|
||||||
|
import HeaderAgenda from "@/components/agenda/HeaderAgenda";
|
||||||
|
import FooterAgenda from "@/components/agenda/FooterAgenda";
|
||||||
|
|
||||||
|
export default function NovoAgendamentoPage() {
|
||||||
|
const [bloqueio, setBloqueio] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// ====== WRAPPER COM ESPAÇAMENTO GERAL ======
|
||||||
|
<div className="min-h-screen flex flex-col bg-white">
|
||||||
|
{/* HEADER fora do <main>, usando o MESMO container do footer */}
|
||||||
|
<HeaderAgenda />
|
||||||
|
|
||||||
|
{/* Conteúdo */}
|
||||||
|
<main className="flex-1 mx-auto w-full max-w-7xl px-8 py-8 space-y-8">
|
||||||
|
{/* ==== INFORMAÇÕES DO PACIENTE — layout idêntico ao print ==== */}
|
||||||
|
<div className="border rounded-md p-6 space-y-4 bg-white">
|
||||||
|
<h2 className="font-medium">Informações do paciente</h2>
|
||||||
|
|
||||||
|
{/* grade principal: 12 colunas para controlar as larguras */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-4">
|
||||||
|
{/* ===== Linha 1 ===== */}
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<Label>Nome *</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
|
<Input
|
||||||
|
placeholder="Digite o nome do paciente"
|
||||||
|
className="h-10 pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-3">
|
||||||
|
<Label>CPF do paciente</Label>
|
||||||
|
<Input placeholder="Número do CPF" className="h-10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-3">
|
||||||
|
<Label>RG</Label>
|
||||||
|
<Input placeholder="Número do RG" className="h-10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ===== Linha 2 ===== */}
|
||||||
|
{/* 1ª coluna (span 6) com sub-grid: Data (5 col) + Telefone (7 col) */}
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<div className="grid grid-cols-12 gap-3">
|
||||||
|
<div className="col-span-5">
|
||||||
|
<Label>Data de nascimento *</Label>
|
||||||
|
<Input type="date" className="h-10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-7">
|
||||||
|
<Label>Telefone</Label>
|
||||||
|
<div className="grid grid-cols-[86px_1fr] gap-2">
|
||||||
|
<select className="h-10 rounded-md border border-input bg-background px-2 text-[13px]">
|
||||||
|
<option value="+55">+55</option>
|
||||||
|
<option value="+351">+351</option>
|
||||||
|
<option value="+1">+1</option>
|
||||||
|
</select>
|
||||||
|
<Input placeholder="(99) 99999-9999" className="h-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2ª coluna da linha 2: E-mail (span 6) */}
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<Label>E-mail</Label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
placeholder="email@exemplo.com"
|
||||||
|
className="h-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ===== Linha 3 ===== */}
|
||||||
|
<div className="md:col-span-6">
|
||||||
|
<Label>Convênio</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
defaultValue="particular"
|
||||||
|
className="h-10 w-full rounded-md border border-input bg-background pr-8 pl-3 text-[13px] appearance-none"
|
||||||
|
>
|
||||||
|
<option value="particular">Particular</option>
|
||||||
|
<option value="plano-a">Plano A</option>
|
||||||
|
<option value="plano-b">Plano B</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-6 grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Matrícula</Label>
|
||||||
|
<Input defaultValue="000000000" className="h-10" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Validade</Label>
|
||||||
|
<Input placeholder="00/0000" className="h-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* link Informações adicionais */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-sm text-blue-600 inline-flex items-center gap-1 hover:underline"
|
||||||
|
aria-label="Ver informações adicionais do paciente"
|
||||||
|
>
|
||||||
|
Informações adicionais
|
||||||
|
<ChevronDown className="h-4 w-4" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* barra Documentos e anexos */}
|
||||||
|
<div className="flex items-center justify-between border rounded-md px-3 py-2">
|
||||||
|
<span className="text-sm">Documentos e anexos</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8"
|
||||||
|
aria-label="Enviar documento"
|
||||||
|
>
|
||||||
|
<Upload className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8"
|
||||||
|
aria-label="Baixar documento"
|
||||||
|
>
|
||||||
|
<FileDown className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8"
|
||||||
|
aria-label="Gerenciar etiquetas"
|
||||||
|
>
|
||||||
|
<Tag className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ==== INFORMAÇÕES DO ATENDIMENTO ==== */}
|
||||||
|
<div className="border rounded-md p-6 space-y-4 bg-white">
|
||||||
|
<h2 className="font-medium">Informações do atendimento</h2>
|
||||||
|
|
||||||
|
{/* GRID PRINCIPAL: 12 colunas */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-6">
|
||||||
|
{/* COLUNA ESQUERDA (span 6) */}
|
||||||
|
<div className="md:col-span-6 space-y-3">
|
||||||
|
{/* Nome do profissional */}
|
||||||
|
<div>
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Nome do profissional <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
defaultValue="Robson Alves dos Anjos Neto"
|
||||||
|
className="h-10 w-full rounded-full border border-input pl-8 pr-12 text-[13px] focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
<span className="absolute right-2 top-1/2 -translate-y-1/2 inline-flex h-6 min-w-[28px] items-center justify-center rounded-full bg-muted px-2 text-xs font-medium">
|
||||||
|
RA
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{/* Unidade */}
|
||||||
|
<div>
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Unidade <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<select
|
||||||
|
defaultValue="nei"
|
||||||
|
className="h-10 w-full rounded-md border border-input bg-background pr-8 pl-3 text-[13px] appearance-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
<option value="nei">
|
||||||
|
Núcleo de Especialidades Integradas
|
||||||
|
</option>
|
||||||
|
<option value="cc">Clínica Central</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Data com ícone */}
|
||||||
|
<div>
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Data <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Calendar className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
defaultValue="2025-07-29"
|
||||||
|
className="h-10 w-full rounded-md border border-input pl-8 pr-3 text-[13px] focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Início / Término / Profissional solicitante (na mesma linha) */}
|
||||||
|
<div className="grid grid-cols-12 gap-3 items-end">
|
||||||
|
{/* Início (maior) */}
|
||||||
|
<div className="col-span-12 md:col-span-3">
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Início <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
defaultValue="21:03"
|
||||||
|
className="h-10 w-full rounded-md border border-input px-3 text-[13px] focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Término (maior) */}
|
||||||
|
<div className="col-span-12 md:col-span-3">
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Término <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
defaultValue="21:03"
|
||||||
|
className="h-10 w-full rounded-md border border-input px-3 text-[13px] focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profissional solicitante */}
|
||||||
|
<div className="col-span-12 md:col-span-6">
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Profissional solicitante
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
{/* ícone de busca à esquerda */}
|
||||||
|
<svg
|
||||||
|
className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8" />
|
||||||
|
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<select
|
||||||
|
defaultValue=""
|
||||||
|
className="h-10 w-full rounded-md border border-input bg-background pl-8 pr-8 text-[13px] appearance-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
<option value="" disabled>
|
||||||
|
Selecione o solicitante
|
||||||
|
</option>
|
||||||
|
<option value="1">Dr. Carlos Silva</option>
|
||||||
|
<option value="2">Dra. Maria Santos</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<path d="M6 9l6 6 6-6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* COLUNA DIREITA — altura/posição como a imagem 1 */}
|
||||||
|
<div className="md:col-span-6 relative -top-10">
|
||||||
|
{/* toolbar */}
|
||||||
|
<div className="mb-2 flex items-center justify-end gap-1">
|
||||||
|
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||||
|
<Accessibility className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||||
|
<Volume2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||||
|
<Flame className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" className="h-8 w-8">
|
||||||
|
<Clipboard className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tipo de atendimento + campo de busca */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="text-[13px]">
|
||||||
|
Tipo de atendimento <span className="text-red-600">*</span>
|
||||||
|
</Label>
|
||||||
|
<label className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3.5 w-3.5 accent-current"
|
||||||
|
/>{" "}
|
||||||
|
Pagamento via Reembolso
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative mt-1">
|
||||||
|
<Search className="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Pesquisar"
|
||||||
|
className="h-10 w-full rounded-md border border-input pl-8 pr-8 text-[13px]"
|
||||||
|
/>
|
||||||
|
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Observações + imprimir */}
|
||||||
|
<div className="mb-0.2 flex items-center justify-between">
|
||||||
|
<Label className="text-[13px]">Observações</Label>
|
||||||
|
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3.5 w-3.5 accent-current"
|
||||||
|
/>{" "}
|
||||||
|
Imprimir na Etiqueta / Pulseira
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Textarea mais baixo e compacto */}
|
||||||
|
<Textarea
|
||||||
|
rows={4}
|
||||||
|
placeholder=""
|
||||||
|
className="text-[13px] h-[110px] min-h-0 resize-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* ====== FOOTER FIXO ====== */}
|
||||||
|
<FooterAgenda />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,139 +0,0 @@
|
|||||||
|
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
|
|
||||||
|
|
||||||
const AgendaCalendar = dynamic(() => import('@/components/agendamento/AgendaCalendar'), {
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<div className="bg-white rounded-lg shadow p-6">
|
|
||||||
<div className="animate-pulse">
|
|
||||||
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4"></div>
|
|
||||||
<div className="h-4 bg-gray-200 rounded w-1/2 mb-6"></div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="h-12 bg-gray-200 rounded"></div>
|
|
||||||
<div className="h-64 bg-gray-200 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
const AppointmentModal = dynamic(() => import('@/components/agendamento/AppointmentModal'), { ssr: false });
|
|
||||||
const ListaEspera = dynamic(() => import('@/components/agendamento/ListaEspera'), { ssr: false });
|
|
||||||
|
|
||||||
|
|
||||||
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: '' },
|
|
||||||
{ id: '3', patient: 'Mariana Lima', time: '2025-09-10T14:00', duration: 60, type: 'exame' as const, status: 'confirmed' as const, professional: '3', notes: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockWaitingList = [
|
|
||||||
{ id: '1', name: 'Ana Costa', specialty: 'Cardiologia', preferredDate: '2025-09-12', priority: 'high' as const, contact: '(11) 99999-9999' },
|
|
||||||
{ id: '2', name: 'Pedro Alves', specialty: 'Dermatologia', preferredDate: '2025-09-15', priority: 'medium' as const, contact: '(11) 98888-8888' },
|
|
||||||
{ id: '3', name: 'Mariana Lima', specialty: 'Ortopedia', preferredDate: '2025-09-20', priority: 'low' as const, contact: '(11) 97777-7777' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockProfessionals = [
|
|
||||||
{ id: '1', name: 'Dr. Carlos Silva', specialty: 'Cardiologia' },
|
|
||||||
{ id: '2', name: 'Dra. Maria Santos', specialty: 'Dermatologia' },
|
|
||||||
{ id: '3', name: 'Dr. João Oliveira', specialty: 'Ortopedia' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function AgendamentoPage() {
|
|
||||||
const [appointments, setAppointments] = useState(mockAppointments);
|
|
||||||
const [waitingList, setWaitingList] = useState(mockWaitingList);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const [selectedAppointment, setSelectedAppointment] = useState<any>(null);
|
|
||||||
const [activeTab, setActiveTab] = useState<'agenda' | 'espera'>('agenda');
|
|
||||||
|
|
||||||
const handleSaveAppointment = (appointment: any) => {
|
|
||||||
if (appointment.id) {
|
|
||||||
setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
|
|
||||||
} else {
|
|
||||||
const newAppointment = {
|
|
||||||
...appointment,
|
|
||||||
id: Date.now().toString(),
|
|
||||||
};
|
|
||||||
setAppointments(prev => [...prev, newAppointment]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditAppointment = (appointment: any) => {
|
|
||||||
setSelectedAppointment(appointment);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddAppointment = () => {
|
|
||||||
setSelectedAppointment(null);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
setSelectedAppointment(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNotifyPatient = (patientId: string) => {
|
|
||||||
console.log(`Notificando paciente ${patientId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Agendamento</h1>
|
|
||||||
<p className="text-gray-600 mt-2">Gerencie a agenda da clínica</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('agenda')}
|
|
||||||
className={`px-4 py-2 rounded-md ${
|
|
||||||
activeTab === 'agenda'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-200 text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Agenda
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('espera')}
|
|
||||||
className={`px-4 py-2 rounded-md ${
|
|
||||||
activeTab === 'espera'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-200 text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Lista de Espera
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{activeTab === 'agenda' ? (
|
|
||||||
<AgendaCalendar
|
|
||||||
professionals={mockProfessionals}
|
|
||||||
appointments={appointments}
|
|
||||||
onAddAppointment={handleAddAppointment}
|
|
||||||
onEditAppointment={handleEditAppointment}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ListaEspera
|
|
||||||
patients={waitingList}
|
|
||||||
onNotify={handleNotifyPatient}
|
|
||||||
onAddToWaitlist={() => {}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<AppointmentModal
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
onClose={handleCloseModal}
|
|
||||||
onSave={handleSaveAppointment}
|
|
||||||
professionals={mockProfessionals}
|
|
||||||
appointment={selectedAppointment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import type React from "react"
|
|
||||||
import { Sidebar } from "@/components/dashboard/sidebar"
|
|
||||||
import { DashboardHeader } from "@/components/dashboard/header"
|
|
||||||
|
|
||||||
export default function DashboardLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background flex">
|
|
||||||
<Sidebar />
|
|
||||||
<div className="flex-1 flex flex-col">
|
|
||||||
<DashboardHeader />
|
|
||||||
<main className="flex-1 p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
export default function DashboardPage() {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
|
|
||||||
<p className="text-muted-foreground">Bem-vindo ao painel de controle</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
||||||
<div className="bg-card p-6 rounded-lg border">
|
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">Total de Pacientes</h3>
|
|
||||||
<p className="text-2xl font-bold text-foreground">1,234</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-card p-6 rounded-lg border">
|
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">Consultas Hoje</h3>
|
|
||||||
<p className="text-2xl font-bold text-foreground">28</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-card p-6 rounded-lg border">
|
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">Próximas Consultas</h3>
|
|
||||||
<p className="text-2xl font-bold text-foreground">45</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-card p-6 rounded-lg border">
|
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">Receita Mensal</h3>
|
|
||||||
<p className="text-2xl font-bold text-foreground">R$ 45.230</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
--card-foreground: #334155;
|
--card-foreground: #334155;
|
||||||
--popover: #ffffff;
|
--popover: #ffffff;
|
||||||
--popover-foreground: #475569;
|
--popover-foreground: #475569;
|
||||||
--primary: #0f766e;
|
--primary: var(--color-blue-500);
|
||||||
--primary-foreground: #ffffff;
|
--primary-foreground: #ffffff;
|
||||||
--secondary: #e2e8f0;
|
--secondary: #e2e8f0;
|
||||||
--secondary-foreground: #475569;
|
--secondary-foreground: #475569;
|
||||||
@ -22,7 +22,7 @@
|
|||||||
--destructive-foreground: #ffffff;
|
--destructive-foreground: #ffffff;
|
||||||
--border: #e2e8f0;
|
--border: #e2e8f0;
|
||||||
--input: #f1f5f9;
|
--input: #f1f5f9;
|
||||||
--ring: #0f766e;
|
--ring: var(--color-blue-500);
|
||||||
--chart-1: #0891b2;
|
--chart-1: #0891b2;
|
||||||
--chart-2: #0f766e;
|
--chart-2: #0f766e;
|
||||||
--chart-3: #f59e0b;
|
--chart-3: #f59e0b;
|
||||||
@ -31,47 +31,12 @@
|
|||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--sidebar: #ffffff;
|
--sidebar: #ffffff;
|
||||||
--sidebar-foreground: #475569;
|
--sidebar-foreground: #475569;
|
||||||
--sidebar-primary: #0f766e;
|
--sidebar-primary: var(--color-blue-500);
|
||||||
--sidebar-primary-foreground: #ffffff;
|
--sidebar-primary-foreground: #ffffff;
|
||||||
--sidebar-accent: #0891b2;
|
--sidebar-accent: var(--color-blue-500);
|
||||||
--sidebar-accent-foreground: #ffffff;
|
--sidebar-accent-foreground: #ffffff;
|
||||||
--sidebar-border: #e2e8f0;
|
--sidebar-border: #e2e8f0;
|
||||||
--sidebar-ring: #0f766e;
|
--sidebar-ring: var(--color-blue-500);
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: #0f172a;
|
|
||||||
--foreground: #f1f5f9;
|
|
||||||
--card: #1e293b;
|
|
||||||
--card-foreground: #f1f5f9;
|
|
||||||
--popover: #1e293b;
|
|
||||||
--popover-foreground: #f1f5f9;
|
|
||||||
--primary: #14b8a6;
|
|
||||||
--primary-foreground: #0f172a;
|
|
||||||
--secondary: #334155;
|
|
||||||
--secondary-foreground: #f1f5f9;
|
|
||||||
--muted: #334155;
|
|
||||||
--muted-foreground: #94a3b8;
|
|
||||||
--accent: #0891b2;
|
|
||||||
--accent-foreground: #f1f5f9;
|
|
||||||
--destructive: #ef4444;
|
|
||||||
--destructive-foreground: #f1f5f9;
|
|
||||||
--border: #334155;
|
|
||||||
--input: #334155;
|
|
||||||
--ring: #14b8a6;
|
|
||||||
--chart-1: #0891b2;
|
|
||||||
--chart-2: #14b8a6;
|
|
||||||
--chart-3: #f59e0b;
|
|
||||||
--chart-4: #ef4444;
|
|
||||||
--chart-5: #94a3b8;
|
|
||||||
--sidebar: #1e293b;
|
|
||||||
--sidebar-foreground: #f1f5f9;
|
|
||||||
--sidebar-primary: #14b8a6;
|
|
||||||
--sidebar-primary-foreground: #0f172a;
|
|
||||||
--sidebar-accent: #0891b2;
|
|
||||||
--sidebar-accent-foreground: #f1f5f9;
|
|
||||||
--sidebar-border: #334155;
|
|
||||||
--sidebar-ring: #14b8a6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
|
|||||||
89
susconecta/app/procedimento/page.tsx
Normal file
89
susconecta/app/procedimento/page.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Search, ChevronDown, RotateCcw } from "lucide-react";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import HeaderAgenda from "@/components/agenda/HeaderAgenda";
|
||||||
|
import FooterAgenda from "@/components/agenda/FooterAgenda";
|
||||||
|
|
||||||
|
export default function ProcedimentoPage() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
const [bloqueio, setBloqueio] = useState(false);
|
||||||
|
|
||||||
|
const isAg = pathname?.startsWith("/agendamento");
|
||||||
|
const isPr = pathname?.startsWith("/procedimento");
|
||||||
|
const isFi = pathname?.startsWith("/financeiro");
|
||||||
|
const tab = (active: boolean, extra = "") =>
|
||||||
|
`px-4 py-1.5 text-[13px] border ${
|
||||||
|
active
|
||||||
|
? "border-sky-500 bg-sky-50 text-sky-700 font-medium"
|
||||||
|
: "text-gray-700 hover:bg-gray-100"
|
||||||
|
} ${extra}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full min-h-screen flex flex-col bg-white">
|
||||||
|
{/* HEADER */}
|
||||||
|
<HeaderAgenda />
|
||||||
|
|
||||||
|
{/* CORPO */}
|
||||||
|
<main className="mx-auto w-full max-w-7xl px-8 py-6 space-y-6 flex-grow">
|
||||||
|
{/* ATENDIMENTOS */}
|
||||||
|
<section className="space-y-6">
|
||||||
|
{/* Selo Atendimento com + dentro da bolinha */}
|
||||||
|
<div className="inline-flex items-center gap-2 border px-3 py-1.5 bg-white text-[12px] rounded-md cursor-pointer hover:bg-gray-50">
|
||||||
|
<span className="flex h-5 w-5 items-center justify-center rounded-full border border-gray-400 bg-gray-100 text-gray-700">
|
||||||
|
<Plus className="h-3 w-3" strokeWidth={2} />
|
||||||
|
</span>
|
||||||
|
Atendimento
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Traço separador */}
|
||||||
|
<div className="border-b border-gray-200" />
|
||||||
|
|
||||||
|
{/* PROCEDIMENTOS */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-[13px] text-foreground/80">
|
||||||
|
Procedimentos
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Buscar"
|
||||||
|
className="h-10 w-full rounded-md pl-8 pr-8 border border-gray-300 focus-visible:ring-1 focus-visible:ring-sky-500 focus-visible:border-sky-500"
|
||||||
|
/>
|
||||||
|
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Traço separador */}
|
||||||
|
<div className="border-b border-gray-200" />
|
||||||
|
|
||||||
|
{/* OUTRAS DESPESAS */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-[13px] text-foreground/80">
|
||||||
|
Outras despesas
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Buscar"
|
||||||
|
className="h-10 w-full rounded-md pl-8 pr-8 border border-gray-300 focus-visible:ring-1 focus-visible:ring-sky-500 focus-visible:border-sky-500"
|
||||||
|
/>
|
||||||
|
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* RODAPÉ FIXO */}
|
||||||
|
<FooterAgenda />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
susconecta/components/agenda/FooterAgenda.tsx
Normal file
34
susconecta/components/agenda/FooterAgenda.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Save } from "lucide-react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function FooterAgenda() {
|
||||||
|
const [bloqueio, setBloqueio] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sticky bottom-0 left-0 right-0 border-t bg-white">
|
||||||
|
<div className="mx-auto w-full max-w-7xl px-8 py-3 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch checked={bloqueio} onCheckedChange={setBloqueio} />
|
||||||
|
<Label className="text-sm">Bloqueio de Agenda</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link href={"/calendar"}>
|
||||||
|
<Button variant="ghost" className="hover:bg-blue-100 hover:text-foreground">Cancelar</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/calendar"}>
|
||||||
|
<Button>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
Salvar as alterações
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
71
susconecta/components/agenda/HeaderAgenda.tsx
Normal file
71
susconecta/components/agenda/HeaderAgenda.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { RotateCcw } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
export default function HeaderAgenda() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const isAg = pathname?.startsWith("/agendamento");
|
||||||
|
const isPr = pathname?.startsWith("/procedimento");
|
||||||
|
const isFi = pathname?.startsWith("/financeiro");
|
||||||
|
|
||||||
|
const tabCls = (active: boolean, extra = "") =>
|
||||||
|
`px-4 py-1.5 text-[13px] border ${
|
||||||
|
active
|
||||||
|
? "border-blue-500 bg-blue-50 text-blue-700 font-medium"
|
||||||
|
: "text-gray-700 hover:bg-gray-100"
|
||||||
|
} ${extra}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="border-b bg-white">
|
||||||
|
<div className="mx-auto w-full max-w-7xl px-8 py-3 flex items-center justify-between">
|
||||||
|
<h1 className="text-[18px] font-semibold">Novo Agendamento</h1>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<nav
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Navegação de Agendamento"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/agenda"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={isAg}
|
||||||
|
className={tabCls(Boolean(isAg)) + " rounded-md"}
|
||||||
|
>
|
||||||
|
Agendamento
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/procedimento"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={isPr}
|
||||||
|
className={tabCls(Boolean(isPr)) + " rounded-md"}
|
||||||
|
>
|
||||||
|
Procedimento
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/financeiro"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={isFi}
|
||||||
|
className={tabCls(Boolean(isFi)) + " rounded-md"}
|
||||||
|
>
|
||||||
|
Financeiro
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Histórico"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="inline-flex h-8 w-8 items-center justify-center rounded-md border bg-white text-gray-700 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<RotateCcw className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,119 +0,0 @@
|
|||||||
|
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { AgendaCalendar, AppointmentModal, ListaEspera } from '@/components/agendamento';
|
|
||||||
|
|
||||||
|
|
||||||
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: '' },
|
|
||||||
{ id: '3', patient: 'Mariana Lima', time: '2025-09-10T14:00', duration: 60, type: 'exame' as const, status: 'confirmed' as const, professional: '3', notes: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockWaitingList = [
|
|
||||||
{ id: '1', name: 'Ana Costa', specialty: 'Cardiologia', preferredDate: '2025-09-12', priority: 'high' as const, contact: '(11) 99999-9999' },
|
|
||||||
{ id: '2', name: 'Pedro Alves', specialty: 'Dermatologia', preferredDate: '2025-09-15', priority: 'medium' as const, contact: '(11) 98888-8888' },
|
|
||||||
{ id: '3', name: 'Mariana Lima', specialty: 'Ortopedia', preferredDate: '2025-09-20', priority: 'low' as const, contact: '(11) 97777-7777' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockProfessionals = [
|
|
||||||
{ id: '1', name: 'Dr. Carlos Silva', specialty: 'Cardiologia' },
|
|
||||||
{ id: '2', name: 'Dra. Maria Santos', specialty: 'Dermatologia' },
|
|
||||||
{ id: '3', name: 'Dr. João Oliveira', specialty: 'Ortopedia' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function AgendaPage() {
|
|
||||||
const [appointments, setAppointments] = useState(mockAppointments);
|
|
||||||
const [waitingList, setWaitingList] = useState(mockWaitingList);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
||||||
const [selectedAppointment, setSelectedAppointment] = useState<any>(null);
|
|
||||||
const [activeTab, setActiveTab] = useState<'agenda' | 'espera'>('agenda');
|
|
||||||
|
|
||||||
const handleSaveAppointment = (appointment: any) => {
|
|
||||||
if (appointment.id) {
|
|
||||||
|
|
||||||
setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const newAppointment = {
|
|
||||||
...appointment,
|
|
||||||
id: Date.now().toString(),
|
|
||||||
};
|
|
||||||
setAppointments(prev => [...prev, newAppointment]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditAppointment = (appointment: any) => {
|
|
||||||
setSelectedAppointment(appointment);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddAppointment = () => {
|
|
||||||
setSelectedAppointment(null);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
setSelectedAppointment(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNotifyPatient = (patientId: string) => {
|
|
||||||
|
|
||||||
console.log(`Notificando paciente ${patientId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Agendamento</h1>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('agenda')}
|
|
||||||
className={`px-4 py-2 rounded-md ${
|
|
||||||
activeTab === 'agenda'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-200 text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Agenda
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('espera')}
|
|
||||||
className={`px-4 py-2 rounded-md ${
|
|
||||||
activeTab === 'espera'
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'bg-gray-200 text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Lista de Espera
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{activeTab === 'agenda' ? (
|
|
||||||
<AgendaCalendar
|
|
||||||
professionals={mockProfessionals}
|
|
||||||
appointments={appointments}
|
|
||||||
onAddAppointment={handleAddAppointment}
|
|
||||||
onEditAppointment={handleEditAppointment}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ListaEspera
|
|
||||||
patients={waitingList}
|
|
||||||
onNotify={handleNotifyPatient}
|
|
||||||
onAddToWaitlist={() => {/* implementar */}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<AppointmentModal
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
onClose={handleCloseModal}
|
|
||||||
onSave={handleSaveAppointment}
|
|
||||||
professionals={mockProfessionals}
|
|
||||||
appointment={selectedAppointment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -12,12 +12,17 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { SidebarTrigger } from "../ui/sidebar"
|
||||||
|
|
||||||
export function DashboardHeader() {
|
export function PagesHeader({ title = "", subtitle = "" }: { title?: string, subtitle?: string }) {
|
||||||
return (
|
return (
|
||||||
<header className="h-16 border-b border-border bg-background px-6 flex items-center justify-between">
|
<header className="h-16 border-b border-border bg-background px-6 flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex flex-row items-center gap-4">
|
||||||
<h1 className="text-lg font-semibold text-foreground">NÚCLEO DE ESPECIALIDADES</h1>
|
<SidebarTrigger />
|
||||||
|
<div className="flex items-start flex-col justify-center py-2">
|
||||||
|
<h1 className="text-lg font-semibold text-foreground">{title}</h1>
|
||||||
|
<p className="text-gray-600">{subtitle}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
|
|||||||
@ -3,11 +3,35 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Home, Calendar, Users, UserCheck, FileText, BarChart3, Settings, Stethoscope, User } from "lucide-react"
|
import {
|
||||||
|
Sidebar as ShadSidebar,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarRail,
|
||||||
|
} from "@/components/ui/sidebar"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Home,
|
||||||
|
Calendar,
|
||||||
|
Users,
|
||||||
|
UserCheck,
|
||||||
|
FileText,
|
||||||
|
BarChart3,
|
||||||
|
Settings,
|
||||||
|
Stethoscope,
|
||||||
|
User,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: "Dashboard", href: "/dashboard", icon: Home },
|
{ name: "Dashboard", href: "/dashboard", icon: Home },
|
||||||
{ name: "Agendamento", href: "/agendamento", icon: Calendar },
|
{ name: "Calendario", href: "/calendar", icon: Calendar },
|
||||||
{ name: "Pacientes", href: "/dashboard/pacientes", icon: Users },
|
{ name: "Pacientes", href: "/dashboard/pacientes", icon: Users },
|
||||||
{ name: "Médicos", href: "/dashboard/doutores", icon: User },
|
{ name: "Médicos", href: "/dashboard/doutores", icon: User },
|
||||||
{ name: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
|
{ name: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
|
||||||
@ -20,36 +44,61 @@ export function Sidebar() {
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-64 bg-sidebar border-r border-sidebar-border">
|
<ShadSidebar
|
||||||
<div className="p-6">
|
/* mude para side="right" se preferir */
|
||||||
<Link href="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
side="left"
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
/* isso faz colapsar para ícones */
|
||||||
|
collapsible="icon"
|
||||||
|
className="border-r border-sidebar-border"
|
||||||
|
>
|
||||||
|
<SidebarHeader>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="flex items-center gap-2 hover:opacity-80 transition-opacity pt-2"
|
||||||
|
>
|
||||||
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shrink-0">
|
||||||
<Stethoscope className="w-4 h-4 text-primary-foreground" />
|
<Stethoscope className="w-4 h-4 text-primary-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-semibold text-sidebar-foreground">SUSConecta</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav className="px-3 space-y-1">
|
{/* este span some no modo ícone */}
|
||||||
{navigation.map((item) => {
|
<span className="text-lg font-semibold text-sidebar-foreground group-data-[collapsible=icon]:hidden">
|
||||||
const isActive = pathname === item.href
|
SUSConecta
|
||||||
return (
|
</span>
|
||||||
<Link
|
</Link>
|
||||||
key={item.name}
|
</SidebarHeader>
|
||||||
href={item.href}
|
|
||||||
className={cn(
|
<SidebarContent>
|
||||||
"flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors",
|
<SidebarGroup>
|
||||||
isActive
|
<SidebarGroupLabel className="group-data-[collapsible=icon]:hidden">
|
||||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
Menu
|
||||||
: "text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
|
</SidebarGroupLabel>
|
||||||
)}
|
<SidebarGroupContent>
|
||||||
>
|
<SidebarMenu>
|
||||||
<item.icon className="mr-3 h-4 w-4" />
|
{navigation.map((item) => {
|
||||||
{item.name}
|
const isActive = pathname === item.href
|
||||||
</Link>
|
return (
|
||||||
)
|
<SidebarMenuItem key={item.name}>
|
||||||
})}
|
<SidebarMenuButton asChild isActive={isActive}>
|
||||||
</nav>
|
<Link href={item.href} className="flex items-center">
|
||||||
</div>
|
<item.icon className="mr-3 h-4 w-4 shrink-0" />
|
||||||
|
{/* o texto esconde quando colapsa */}
|
||||||
|
<span className="truncate group-data-[collapsible=icon]:hidden">
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
</SidebarContent>
|
||||||
|
|
||||||
|
<SidebarFooter>{/* espaço para perfil/logout, se quiser */}</SidebarFooter>
|
||||||
|
|
||||||
|
{/* rail clicável/hover que ajuda a reabrir/fechar */}
|
||||||
|
<SidebarRail />
|
||||||
|
</ShadSidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react";
|
||||||
import Link from "next/link"
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { Menu, X } from "lucide-react"
|
import { Menu, X } from "lucide-react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-background border-b border-border sticky top-0 z-50">
|
<header className="bg-background border-b border-border sticky top-0 z-50">
|
||||||
@ -20,14 +22,21 @@ export function Header() {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{}
|
{}
|
||||||
<nav className="hidden md:flex items-center space-x-8">
|
<nav className="hidden md:flex items-center gap-10">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="text-foreground hover:text-primary transition-colors border-b-2 border-primary pb-1"
|
className={`text-foreground hover:text-primary transition-colors border-b-2 border-b-[transparent] ${
|
||||||
|
pathname === "/" ? "border-b-blue-500" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Início
|
Início
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/sobre" className="text-muted-foreground hover:text-primary transition-colors">
|
<Link
|
||||||
|
href="/sobre"
|
||||||
|
className={`text-foreground hover:text-primary transition-colors border-b-2 border-b-[transparent] ${
|
||||||
|
pathname === "/sobre" ? "border-b-blue-500" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
Sobre
|
Sobre
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
@ -54,7 +63,11 @@ export function Header() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{}
|
{}
|
||||||
<button className="md:hidden p-2" onClick={() => setIsMenuOpen(!isMenuOpen)} aria-label="Toggle menu">
|
<button
|
||||||
|
className="md:hidden p-2"
|
||||||
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -101,5 +114,5 @@ export function Header() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
65
susconecta/lib/mocks/appointment-mocks.ts
Normal file
65
susconecta/lib/mocks/appointment-mocks.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
export 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: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
patient: "Mariana Lima",
|
||||||
|
time: "2025-09-10T14:00",
|
||||||
|
duration: 60,
|
||||||
|
type: "exame" as const,
|
||||||
|
status: "confirmed" as const,
|
||||||
|
professional: "3",
|
||||||
|
notes: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockWaitingList = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: "Ana Costa",
|
||||||
|
specialty: "Cardiologia",
|
||||||
|
preferredDate: "2025-09-12",
|
||||||
|
priority: "high" as const,
|
||||||
|
contact: "(11) 99999-9999",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: "Pedro Alves",
|
||||||
|
specialty: "Dermatologia",
|
||||||
|
preferredDate: "2025-09-15",
|
||||||
|
priority: "medium" as const,
|
||||||
|
contact: "(11) 98888-8888",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
name: "Mariana Lima",
|
||||||
|
specialty: "Ortopedia",
|
||||||
|
preferredDate: "2025-09-20",
|
||||||
|
priority: "low" as const,
|
||||||
|
contact: "(11) 97777-7777",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockProfessionals = [
|
||||||
|
{ id: "1", name: "Dr. Carlos Silva", specialty: "Cardiologia" },
|
||||||
|
{ id: "2", name: "Dra. Maria Santos", specialty: "Dermatologia" },
|
||||||
|
{ id: "3", name: "Dr. João Oliveira", specialty: "Ortopedia" },
|
||||||
|
];
|
||||||
Loading…
x
Reference in New Issue
Block a user