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) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" onClick={() => setShowForm(false)}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
@ -160,7 +160,7 @@ export default function PacientesPage() {
|
||||
}
|
||||
|
||||
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>
|
||||
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;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #475569;
|
||||
--primary: #0f766e;
|
||||
--primary: var(--color-blue-500);
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #e2e8f0;
|
||||
--secondary-foreground: #475569;
|
||||
@ -22,7 +22,7 @@
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #e2e8f0;
|
||||
--input: #f1f5f9;
|
||||
--ring: #0f766e;
|
||||
--ring: var(--color-blue-500);
|
||||
--chart-1: #0891b2;
|
||||
--chart-2: #0f766e;
|
||||
--chart-3: #f59e0b;
|
||||
@ -31,47 +31,12 @@
|
||||
--radius: 0.5rem;
|
||||
--sidebar: #ffffff;
|
||||
--sidebar-foreground: #475569;
|
||||
--sidebar-primary: #0f766e;
|
||||
--sidebar-primary: var(--color-blue-500);
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #0891b2;
|
||||
--sidebar-accent: var(--color-blue-500);
|
||||
--sidebar-accent-foreground: #ffffff;
|
||||
--sidebar-border: #e2e8f0;
|
||||
--sidebar-ring: #0f766e;
|
||||
}
|
||||
|
||||
.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;
|
||||
--sidebar-ring: var(--color-blue-500);
|
||||
}
|
||||
|
||||
@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,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { SidebarTrigger } from "../ui/sidebar"
|
||||
|
||||
export function DashboardHeader() {
|
||||
export function PagesHeader({ title = "", subtitle = "" }: { title?: string, subtitle?: string }) {
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-lg font-semibold text-foreground">NÚCLEO DE ESPECIALIDADES</h1>
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<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 className="flex items-center space-x-4">
|
||||
|
||||
@ -3,11 +3,35 @@
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
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 = [
|
||||
{ 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: "Médicos", href: "/dashboard/doutores", icon: User },
|
||||
{ name: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
|
||||
@ -20,36 +44,61 @@ export function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<div className="w-64 bg-sidebar border-r border-sidebar-border">
|
||||
<div className="p-6">
|
||||
<Link href="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
||||
<ShadSidebar
|
||||
/* mude para side="right" se preferir */
|
||||
side="left"
|
||||
/* 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" />
|
||||
</div>
|
||||
<span className="text-lg font-semibold text-sidebar-foreground">SUSConecta</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="px-3 space-y-1">
|
||||
{navigation.map((item) => {
|
||||
const isActive = pathname === item.href
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors",
|
||||
isActive
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||
: "text-sidebar-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
|
||||
)}
|
||||
>
|
||||
<item.icon className="mr-3 h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
{/* este span some no modo ícone */}
|
||||
<span className="text-lg font-semibold text-sidebar-foreground group-data-[collapsible=icon]:hidden">
|
||||
SUSConecta
|
||||
</span>
|
||||
</Link>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="group-data-[collapsible=icon]:hidden">
|
||||
Menu
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{navigation.map((item) => {
|
||||
const isActive = pathname === item.href
|
||||
return (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild isActive={isActive}>
|
||||
<Link href={item.href} className="flex items-center">
|
||||
<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 Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Menu, X } from "lucide-react"
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export function Header() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<header className="bg-background border-b border-border sticky top-0 z-50">
|
||||
@ -20,14 +22,21 @@ export function Header() {
|
||||
</Link>
|
||||
|
||||
{}
|
||||
<nav className="hidden md:flex items-center space-x-8">
|
||||
<nav className="hidden md:flex items-center gap-10">
|
||||
<Link
|
||||
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
|
||||
</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
|
||||
</Link>
|
||||
</nav>
|
||||
@ -54,7 +63,11 @@ export function Header() {
|
||||
</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} />}
|
||||
</button>
|
||||
</div>
|
||||
@ -101,5 +114,5 @@ export function Header() {
|
||||
)}
|
||||
</div>
|
||||
</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