fix: menu acessibilidade todos os modos funcionais
This commit is contained in:
parent
f2a9dc7b70
commit
60c8b5eaa9
@ -24,6 +24,8 @@ export function SecretaryAppointmentList() {
|
|||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState("Todos");
|
const [statusFilter, setStatusFilter] = useState("Todos");
|
||||||
const [typeFilter, setTypeFilter] = useState("Todos");
|
const [typeFilter, setTypeFilter] = useState("Todos");
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [itemsPerPage] = useState(10);
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [modalMode, setModalMode] = useState<"create" | "edit">("create");
|
const [modalMode, setModalMode] = useState<"create" | "edit">("create");
|
||||||
const [selectedAppointment, setSelectedAppointment] = useState<
|
const [selectedAppointment, setSelectedAppointment] = useState<
|
||||||
@ -152,6 +154,12 @@ export function SecretaryAppointmentList() {
|
|||||||
return matchesSearch && matchesStatus && matchesType;
|
return matchesSearch && matchesStatus && matchesType;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cálculos de paginação
|
||||||
|
const totalPages = Math.ceil(filteredAppointments.length / itemsPerPage);
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedAppointments = filteredAppointments.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const loadDoctorsAndPatients = async () => {
|
const loadDoctorsAndPatients = async () => {
|
||||||
try {
|
try {
|
||||||
const [patientsData, doctorsData] = await Promise.all([
|
const [patientsData, doctorsData] = await Promise.all([
|
||||||
@ -237,9 +245,15 @@ export function SecretaryAppointmentList() {
|
|||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
setStatusFilter("Todos");
|
setStatusFilter("Todos");
|
||||||
setTypeFilter("Todos");
|
setTypeFilter("Todos");
|
||||||
|
setCurrentPage(1);
|
||||||
loadAppointments();
|
loadAppointments();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reset página quando filtros mudarem
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [searchTerm, statusFilter, typeFilter]);
|
||||||
|
|
||||||
const getStatusBadge = (status: string) => {
|
const getStatusBadge = (status: string) => {
|
||||||
const statusMap: Record<string, { label: string; className: string }> = {
|
const statusMap: Record<string, { label: string; className: string }> = {
|
||||||
confirmada: {
|
confirmada: {
|
||||||
@ -293,8 +307,8 @@ export function SecretaryAppointmentList() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Consultas</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Consultas</h1>
|
||||||
<p className="text-gray-600 mt-1">Gerencie as consultas agendadas</p>
|
<p className="text-gray-600 dark:text-gray-400 mt-1">Gerencie as consultas agendadas</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleOpenCreateModal}
|
onClick={handleOpenCreateModal}
|
||||||
@ -306,16 +320,16 @@ export function SecretaryAppointmentList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 space-y-4">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar consultas por paciente ou médico..."
|
placeholder="Buscar consultas por paciente ou médico..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -326,7 +340,7 @@ export function SecretaryAppointmentList() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-6 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
Limpar
|
Limpar
|
||||||
</button>
|
</button>
|
||||||
@ -334,11 +348,11 @@ export function SecretaryAppointmentList() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-600">Status:</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Status:</span>
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={(e) => setStatusFilter(e.target.value)}
|
onChange={(e) => setStatusFilter(e.target.value)}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option>Todos</option>
|
<option>Todos</option>
|
||||||
<option>Confirmada</option>
|
<option>Confirmada</option>
|
||||||
@ -348,11 +362,11 @@ export function SecretaryAppointmentList() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-600">Tipo:</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Tipo:</span>
|
||||||
<select
|
<select
|
||||||
value={typeFilter}
|
value={typeFilter}
|
||||||
onChange={(e) => setTypeFilter(e.target.value)}
|
onChange={(e) => setTypeFilter(e.target.value)}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option>Todos</option>
|
<option>Todos</option>
|
||||||
<option>Presencial</option>
|
<option>Presencial</option>
|
||||||
@ -363,36 +377,36 @@ export function SecretaryAppointmentList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Paciente
|
Paciente
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Médico
|
Médico
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Data/Hora
|
Data/Hora
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Tipo
|
Tipo
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Ações
|
Ações
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={6}
|
colSpan={6}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
Carregando consultas...
|
Carregando consultas...
|
||||||
</td>
|
</td>
|
||||||
@ -401,7 +415,7 @@ export function SecretaryAppointmentList() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={6}
|
colSpan={6}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
{searchTerm ||
|
{searchTerm ||
|
||||||
statusFilter !== "Todos" ||
|
statusFilter !== "Todos" ||
|
||||||
@ -411,10 +425,10 @@ export function SecretaryAppointmentList() {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
filteredAppointments.map((appointment) => (
|
paginatedAppointments.map((appointment) => (
|
||||||
<tr
|
<tr
|
||||||
key={appointment.id}
|
key={appointment.id}
|
||||||
className="hover:bg-gray-50 transition-colors"
|
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -425,11 +439,11 @@ export function SecretaryAppointmentList() {
|
|||||||
color="blue"
|
color="blue"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-900">
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{appointment.patient?.full_name ||
|
{appointment.patient?.full_name ||
|
||||||
"Paciente não encontrado"}
|
"Paciente não encontrado"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
{appointment.patient?.email || "—"}
|
{appointment.patient?.email || "—"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -444,7 +458,7 @@ export function SecretaryAppointmentList() {
|
|||||||
color="green"
|
color="green"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-900">
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{appointment.doctor?.full_name ||
|
{appointment.doctor?.full_name ||
|
||||||
"Médico não encontrado"}
|
"Médico não encontrado"}
|
||||||
</p>
|
</p>
|
||||||
@ -515,6 +529,62 @@ export function SecretaryAppointmentList() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{filteredAppointments.length > 0 && (
|
||||||
|
<div className="flex items-center justify-between bg-white dark:bg-gray-800 px-6 py-4 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
Mostrando {startIndex + 1} até {Math.min(endIndex, filteredAppointments.length)} de {filteredAppointments.length} consultas
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{(() => {
|
||||||
|
const maxPagesToShow = 4;
|
||||||
|
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
||||||
|
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
||||||
|
|
||||||
|
// Ajusta startPage se estivermos próximos do fim
|
||||||
|
if (endPage - startPage < maxPagesToShow - 1) {
|
||||||
|
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.map((page) => (
|
||||||
|
<button
|
||||||
|
key={page}
|
||||||
|
onClick={() => setCurrentPage(page)}
|
||||||
|
className={`px-3 py-2 text-sm rounded-lg transition-colors ${
|
||||||
|
currentPage === page
|
||||||
|
? "bg-green-600 text-white"
|
||||||
|
: "border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
));
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Próxima
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal de Criar Consulta */}
|
{/* Modal de Criar Consulta */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
|
|||||||
@ -70,6 +70,8 @@ export function SecretaryDoctorList({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [specialtyFilter, setSpecialtyFilter] = useState("Todas");
|
const [specialtyFilter, setSpecialtyFilter] = useState("Todas");
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [itemsPerPage] = useState(10);
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
@ -121,6 +123,12 @@ export function SecretaryDoctorList({
|
|||||||
return matchesSearch && matchesSpecialty;
|
return matchesSearch && matchesSpecialty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cálculos de paginação
|
||||||
|
const totalPages = Math.ceil(filteredDoctors.length / itemsPerPage);
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedDoctors = filteredDoctors.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
loadDoctors();
|
loadDoctors();
|
||||||
};
|
};
|
||||||
@ -128,9 +136,15 @@ export function SecretaryDoctorList({
|
|||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
setSpecialtyFilter("Todas");
|
setSpecialtyFilter("Todas");
|
||||||
|
setCurrentPage(1);
|
||||||
loadDoctors();
|
loadDoctors();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reset página quando filtros mudarem
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [searchTerm, specialtyFilter]);
|
||||||
|
|
||||||
const handleNewDoctor = () => {
|
const handleNewDoctor = () => {
|
||||||
setModalMode("create");
|
setModalMode("create");
|
||||||
setFormData({
|
setFormData({
|
||||||
@ -246,8 +260,8 @@ export function SecretaryDoctorList({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Médicos</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Médicos</h1>
|
||||||
<p className="text-gray-600 mt-1">Gerencie os médicos cadastrados</p>
|
<p className="text-gray-600 dark:text-gray-400 mt-1">Gerencie os médicos cadastrados</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleNewDoctor}
|
onClick={handleNewDoctor}
|
||||||
@ -259,16 +273,16 @@ export function SecretaryDoctorList({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 space-y-4">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar médicos por nome ou CRM..."
|
placeholder="Buscar médicos por nome ou CRM..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -279,18 +293,18 @@ export function SecretaryDoctorList({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-6 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
Limpar
|
Limpar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-600">Especialidade:</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Especialidade:</span>
|
||||||
<select
|
<select
|
||||||
value={specialtyFilter}
|
value={specialtyFilter}
|
||||||
onChange={(e) => setSpecialtyFilter(e.target.value)}
|
onChange={(e) => setSpecialtyFilter(e.target.value)}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option>Todas</option>
|
<option>Todas</option>
|
||||||
<option>Cardiologia</option>
|
<option>Cardiologia</option>
|
||||||
@ -304,33 +318,33 @@ export function SecretaryDoctorList({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Médico
|
Médico
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Especialidade
|
Especialidade
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
CRM
|
CRM
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Próxima Disponível
|
Próxima Disponível
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Ações
|
Ações
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
Carregando médicos...
|
Carregando médicos...
|
||||||
</td>
|
</td>
|
||||||
@ -339,7 +353,7 @@ export function SecretaryDoctorList({
|
|||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
{searchTerm || specialtyFilter !== "Todas"
|
{searchTerm || specialtyFilter !== "Todas"
|
||||||
? "Nenhum médico encontrado com esses filtros"
|
? "Nenhum médico encontrado com esses filtros"
|
||||||
@ -347,10 +361,10 @@ export function SecretaryDoctorList({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
filteredDoctors.map((doctor, index) => (
|
paginatedDoctors.map((doctor, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={doctor.id}
|
key={doctor.id}
|
||||||
className="hover:bg-gray-50 transition-colors"
|
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -362,20 +376,20 @@ export function SecretaryDoctorList({
|
|||||||
{getInitials(doctor.full_name || "")}
|
{getInitials(doctor.full_name || "")}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-900">
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{formatDoctorName(doctor.full_name)}
|
{formatDoctorName(doctor.full_name)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">{doctor.email}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">{doctor.email}</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{doctor.phone_mobile}
|
{doctor.phone_mobile}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
||||||
{doctor.specialty || "—"}
|
{doctor.specialty || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
||||||
{doctor.crm || "—"}
|
{doctor.crm || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700">
|
||||||
@ -431,6 +445,61 @@ export function SecretaryDoctorList({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{filteredDoctors.length > 0 && (
|
||||||
|
<div className="flex items-center justify-between bg-white dark:bg-gray-800 px-6 py-4 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
Mostrando {startIndex + 1} até {Math.min(endIndex, filteredDoctors.length)} de {filteredDoctors.length} médicos
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{(() => {
|
||||||
|
const maxPagesToShow = 4;
|
||||||
|
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
||||||
|
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
||||||
|
|
||||||
|
if (endPage - startPage < maxPagesToShow - 1) {
|
||||||
|
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.map((page) => (
|
||||||
|
<button
|
||||||
|
key={page}
|
||||||
|
onClick={() => setCurrentPage(page)}
|
||||||
|
className={`px-3 py-2 text-sm rounded-lg transition-colors ${
|
||||||
|
currentPage === page
|
||||||
|
? "bg-green-600 text-white"
|
||||||
|
: "border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
));
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Próxima
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal de Formulário */}
|
{/* Modal de Formulário */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
|||||||
@ -460,22 +460,22 @@ export function SecretaryDoctorSchedule() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Agenda Médica</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Agenda Médica</h1>
|
||||||
<p className="text-gray-600 mt-1">
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
Gerencie disponibilidades e exceções
|
Gerencie disponibilidades e exceções
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Doctor Selector */}
|
{/* Doctor Selector */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
Selecione o Médico
|
Selecione o Médico
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={selectedDoctorId}
|
value={selectedDoctorId}
|
||||||
onChange={(e) => setSelectedDoctorId(e.target.value)}
|
onChange={(e) => setSelectedDoctorId(e.target.value)}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
{doctors.map((doctor) => (
|
{doctors.map((doctor) => (
|
||||||
<option key={doctor.id} value={doctor.id}>
|
<option key={doctor.id} value={doctor.id}>
|
||||||
@ -486,27 +486,27 @@ export function SecretaryDoctorSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Calendar */}
|
{/* Calendar */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 capitalize">
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 capitalize">
|
||||||
{formatMonthYear(currentDate)}
|
{formatMonthYear(currentDate)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={goToToday}
|
onClick={goToToday}
|
||||||
className="px-4 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Hoje
|
Hoje
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={previousMonth}
|
onClick={previousMonth}
|
||||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
className="p-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={nextMonth}
|
onClick={nextMonth}
|
||||||
className="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
className="p-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-gray-700 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -517,15 +517,15 @@ export function SecretaryDoctorSchedule() {
|
|||||||
<div className="mb-4 flex flex-wrap gap-3 text-xs">
|
<div className="mb-4 flex flex-wrap gap-3 text-xs">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-3 h-3 rounded bg-yellow-100 border border-yellow-300"></div>
|
<div className="w-3 h-3 rounded bg-yellow-100 border border-yellow-300"></div>
|
||||||
<span className="text-gray-600">Solicitada</span>
|
<span className="text-gray-600 dark:text-gray-400">Solicitada</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-3 h-3 rounded bg-green-100 border border-green-300"></div>
|
<div className="w-3 h-3 rounded bg-green-100 border border-green-300"></div>
|
||||||
<span className="text-gray-600">Confirmada</span>
|
<span className="text-gray-600 dark:text-gray-400">Confirmada</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-3 h-3 rounded bg-blue-100 border border-blue-300"></div>
|
<div className="w-3 h-3 rounded bg-blue-100 border border-blue-300"></div>
|
||||||
<span className="text-gray-600">Concluída</span>
|
<span className="text-gray-600 dark:text-gray-400">Concluída</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-3 h-3 rounded bg-red-100 border border-red-300"></div>
|
<div className="w-3 h-3 rounded bg-red-100 border border-red-300"></div>
|
||||||
@ -537,11 +537,11 @@ export function SecretaryDoctorSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-px bg-gray-200 border border-gray-200 rounded-lg overflow-hidden">
|
<div className="grid grid-cols-7 gap-px bg-gray-200 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden">
|
||||||
{["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"].map((day) => (
|
{["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"].map((day) => (
|
||||||
<div
|
<div
|
||||||
key={day}
|
key={day}
|
||||||
className="bg-gray-50 px-2 py-3 text-center text-sm font-semibold text-gray-700"
|
className="bg-gray-50 dark:bg-gray-700 px-2 py-3 text-center text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
{day}
|
{day}
|
||||||
</div>
|
</div>
|
||||||
@ -549,15 +549,15 @@ export function SecretaryDoctorSchedule() {
|
|||||||
{calendarDays.map((day, index) => (
|
{calendarDays.map((day, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`bg-white p-2 min-h-[100px] ${
|
className={`bg-white dark:bg-gray-800 p-2 min-h-[100px] ${
|
||||||
day.isCurrentMonth ? "" : "opacity-40"
|
day.isCurrentMonth ? "" : "opacity-40"
|
||||||
} ${
|
} ${
|
||||||
day.date.toDateString() === new Date().toDateString()
|
day.date.toDateString() === new Date().toDateString()
|
||||||
? "bg-blue-50"
|
? "bg-blue-50 dark:bg-blue-900"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="text-sm text-gray-700 mb-1 font-medium">
|
<div className="text-sm text-gray-700 dark:text-gray-300 mb-1 font-medium">
|
||||||
{day.date.getDate()}
|
{day.date.getDate()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -575,8 +575,8 @@ export function SecretaryDoctorSchedule() {
|
|||||||
key={`exc-${i}`}
|
key={`exc-${i}`}
|
||||||
className={`text-xs p-1 rounded mb-1 truncate ${
|
className={`text-xs p-1 rounded mb-1 truncate ${
|
||||||
exc.kind === "bloqueio"
|
exc.kind === "bloqueio"
|
||||||
? "bg-red-100 text-red-800"
|
? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
|
||||||
: "bg-purple-100 text-purple-800"
|
: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"
|
||||||
}`}
|
}`}
|
||||||
title={tooltipText}
|
title={tooltipText}
|
||||||
>
|
>
|
||||||
@ -598,14 +598,14 @@ export function SecretaryDoctorSchedule() {
|
|||||||
key={`apt-${i}`}
|
key={`apt-${i}`}
|
||||||
className={`text-xs p-1 rounded mb-1 truncate ${
|
className={`text-xs p-1 rounded mb-1 truncate ${
|
||||||
apt.status === "requested"
|
apt.status === "requested"
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"
|
||||||
: apt.status === "confirmed"
|
: apt.status === "confirmed"
|
||||||
? "bg-green-100 text-green-800"
|
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||||
: apt.status === "completed"
|
: apt.status === "completed"
|
||||||
? "bg-blue-100 text-blue-800"
|
? "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||||
: apt.status === "cancelled"
|
: apt.status === "cancelled"
|
||||||
? "bg-gray-100 text-gray-600"
|
? "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
||||||
: "bg-orange-100 text-orange-800"
|
: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"
|
||||||
}`}
|
}`}
|
||||||
title={`${time} - ${apt.patient_id}`}
|
title={`${time} - ${apt.patient_id}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -52,6 +52,8 @@ export function SecretaryPatientList({
|
|||||||
const [insuranceFilter, setInsuranceFilter] = useState("Todos");
|
const [insuranceFilter, setInsuranceFilter] = useState("Todos");
|
||||||
const [showBirthdays, setShowBirthdays] = useState(false);
|
const [showBirthdays, setShowBirthdays] = useState(false);
|
||||||
const [showVIP, setShowVIP] = useState(false);
|
const [showVIP, setShowVIP] = useState(false);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [itemsPerPage] = useState(10);
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
@ -153,6 +155,12 @@ export function SecretaryPatientList({
|
|||||||
return matchesSearch && matchesBirthday && matchesInsurance && matchesVIP;
|
return matchesSearch && matchesBirthday && matchesInsurance && matchesVIP;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cálculos de paginação
|
||||||
|
const totalPages = Math.ceil(filteredPatients.length / itemsPerPage);
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedPatients = filteredPatients.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
loadPatients();
|
loadPatients();
|
||||||
};
|
};
|
||||||
@ -162,9 +170,15 @@ export function SecretaryPatientList({
|
|||||||
setInsuranceFilter("Todos");
|
setInsuranceFilter("Todos");
|
||||||
setShowBirthdays(false);
|
setShowBirthdays(false);
|
||||||
setShowVIP(false);
|
setShowVIP(false);
|
||||||
|
setCurrentPage(1);
|
||||||
loadPatients();
|
loadPatients();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reset página quando filtros mudarem
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [searchTerm, insuranceFilter, showBirthdays, showVIP]);
|
||||||
|
|
||||||
const handleNewPatient = () => {
|
const handleNewPatient = () => {
|
||||||
setModalMode("create");
|
setModalMode("create");
|
||||||
setFormData({
|
setFormData({
|
||||||
@ -399,8 +413,8 @@ export function SecretaryPatientList({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Pacientes</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Pacientes</h1>
|
||||||
<p className="text-gray-600 mt-1">
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
Gerencie os pacientes cadastrados
|
Gerencie os pacientes cadastrados
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -414,16 +428,16 @@ export function SecretaryPatientList({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 space-y-4">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar pacientes por nome ou email..."
|
placeholder="Buscar pacientes por nome ou email..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -434,7 +448,7 @@ export function SecretaryPatientList({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-6 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
Limpar
|
Limpar
|
||||||
</button>
|
</button>
|
||||||
@ -448,7 +462,7 @@ export function SecretaryPatientList({
|
|||||||
onChange={(e) => setShowBirthdays(e.target.checked)}
|
onChange={(e) => setShowBirthdays(e.target.checked)}
|
||||||
className="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500"
|
className="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
Aniversariantes do mês
|
Aniversariantes do mês
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -459,14 +473,14 @@ export function SecretaryPatientList({
|
|||||||
onChange={(e) => setShowVIP(e.target.checked)}
|
onChange={(e) => setShowVIP(e.target.checked)}
|
||||||
className="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500"
|
className="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-700">Somente VIP</span>
|
<span className="text-sm text-gray-700 dark:text-gray-300">Somente VIP</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
<span className="text-sm text-gray-600">Convênio:</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Convênio:</span>
|
||||||
<select
|
<select
|
||||||
value={insuranceFilter}
|
value={insuranceFilter}
|
||||||
onChange={(e) => setInsuranceFilter(e.target.value)}
|
onChange={(e) => setInsuranceFilter(e.target.value)}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option>Todos</option>
|
<option>Todos</option>
|
||||||
<option>Particular</option>
|
<option>Particular</option>
|
||||||
@ -479,30 +493,30 @@ export function SecretaryPatientList({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Paciente
|
Paciente
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Próximo Atendimento
|
Próximo Atendimento
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Convênio
|
Convênio
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Ações
|
Ações
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={4}
|
colSpan={4}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
Carregando pacientes...
|
Carregando pacientes...
|
||||||
</td>
|
</td>
|
||||||
@ -511,7 +525,7 @@ export function SecretaryPatientList({
|
|||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={4}
|
colSpan={4}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
{searchTerm
|
{searchTerm
|
||||||
? "Nenhum paciente encontrado com esse termo"
|
? "Nenhum paciente encontrado com esse termo"
|
||||||
@ -519,10 +533,10 @@ export function SecretaryPatientList({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
filteredPatients.map((patient, index) => (
|
paginatedPatients.map((patient, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={patient.id}
|
key={patient.id}
|
||||||
className="hover:bg-gray-50 transition-colors"
|
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -533,20 +547,20 @@ export function SecretaryPatientList({
|
|||||||
color={getPatientColor(index)}
|
color={getPatientColor(index)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-900">
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{patient.full_name}
|
{patient.full_name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">{patient.email}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">{patient.email}</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{patient.phone_mobile}
|
{patient.phone_mobile}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
||||||
{/* TODO: Buscar próximo agendamento */}—
|
{/* TODO: Buscar próximo agendamento */}—
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
||||||
{(patient as any).convenio || "Particular"}
|
{(patient as any).convenio || "Particular"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
@ -588,6 +602,61 @@ export function SecretaryPatientList({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{filteredPatients.length > 0 && (
|
||||||
|
<div className="flex items-center justify-between bg-white dark:bg-gray-800 px-6 py-4 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
Mostrando {startIndex + 1} até {Math.min(endIndex, filteredPatients.length)} de {filteredPatients.length} pacientes
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{(() => {
|
||||||
|
const maxPagesToShow = 4;
|
||||||
|
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
||||||
|
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
||||||
|
|
||||||
|
if (endPage - startPage < maxPagesToShow - 1) {
|
||||||
|
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.map((page) => (
|
||||||
|
<button
|
||||||
|
key={page}
|
||||||
|
onClick={() => setCurrentPage(page)}
|
||||||
|
className={`px-3 py-2 text-sm rounded-lg transition-colors ${
|
||||||
|
currentPage === page
|
||||||
|
? "bg-green-600 text-white"
|
||||||
|
: "border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
));
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Próxima
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal de Formulário */}
|
{/* Modal de Formulário */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
|||||||
@ -335,8 +335,8 @@ export function SecretaryReportList() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Relatórios</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Relatórios</h1>
|
||||||
<p className="text-gray-600 mt-1">
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
Visualize e baixe relatórios do sistema
|
Visualize e baixe relatórios do sistema
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -350,16 +350,16 @@ export function SecretaryReportList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6 space-y-4">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar relatórios..."
|
placeholder="Buscar relatórios..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -370,7 +370,7 @@ export function SecretaryReportList() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
className="px-6 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
Limpar
|
Limpar
|
||||||
</button>
|
</button>
|
||||||
@ -378,11 +378,11 @@ export function SecretaryReportList() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-600">Status:</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Status:</span>
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={(e) => setStatusFilter(e.target.value)}
|
onChange={(e) => setStatusFilter(e.target.value)}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
className="px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||||
>
|
>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
<option value="draft">Rascunho</option>
|
<option value="draft">Rascunho</option>
|
||||||
@ -395,33 +395,33 @@ export function SecretaryReportList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Relatório
|
Relatório
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Criado Em
|
Criado Em
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Solicitante
|
Solicitante
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">
|
<th className="px-6 py-4 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Ações
|
Ações
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
Carregando relatórios...
|
Carregando relatórios...
|
||||||
</td>
|
</td>
|
||||||
@ -430,7 +430,7 @@ export function SecretaryReportList() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
className="px-6 py-12 text-center text-gray-500"
|
className="px-6 py-12 text-center text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
Nenhum relatório encontrado
|
Nenhum relatório encontrado
|
||||||
</td>
|
</td>
|
||||||
@ -439,18 +439,18 @@ export function SecretaryReportList() {
|
|||||||
reports.map((report) => (
|
reports.map((report) => (
|
||||||
<tr
|
<tr
|
||||||
key={report.id}
|
key={report.id}
|
||||||
className="hover:bg-gray-50 transition-colors"
|
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-blue-100 rounded-lg">
|
<div className="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||||||
<FileText className="h-5 w-5 text-blue-600" />
|
<FileText className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-900">
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
{report.order_number}
|
{report.order_number}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
{report.exam || "Sem exame"}
|
{report.exam || "Sem exame"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -460,12 +460,12 @@ export function SecretaryReportList() {
|
|||||||
<span
|
<span
|
||||||
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
||||||
report.status === "completed"
|
report.status === "completed"
|
||||||
? "bg-green-100 text-green-800"
|
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||||
: report.status === "pending"
|
: report.status === "pending"
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"
|
||||||
: report.status === "draft"
|
: report.status === "draft"
|
||||||
? "bg-gray-100 text-gray-800"
|
? "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200"
|
||||||
: "bg-red-100 text-red-800"
|
: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{report.status === "completed"
|
{report.status === "completed"
|
||||||
@ -477,7 +477,7 @@ export function SecretaryReportList() {
|
|||||||
: "Cancelado"}
|
: "Cancelado"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
||||||
{formatDate(report.created_at)}
|
{formatDate(report.created_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
<td className="px-6 py-4 text-sm text-gray-700">
|
||||||
|
|||||||
262
src/index.css
262
src/index.css
@ -65,11 +65,32 @@ html.reduced-motion *::after {
|
|||||||
scroll-behavior: auto !important;
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filtro de luz azul (aplica matiz e tonalidade amarelada) */
|
/* Filtro de luz azul (aplica overlay amarelada sem quebrar position: fixed) */
|
||||||
/* Filtro de luz azul (modo mais "padrão" com tom amarelado suave) */
|
html.low-blue-light {
|
||||||
html.low-blue-light body {
|
position: relative;
|
||||||
/* Mais quente: mais sepia e matiz mais próximo do laranja */
|
}
|
||||||
filter: sepia(40%) hue-rotate(315deg) saturate(85%) brightness(98%);
|
|
||||||
|
html.low-blue-light::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 220, 150, 0.25),
|
||||||
|
rgba(255, 200, 120, 0.25)
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 999999;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Garante que o menu de acessibilidade fique acima do filtro */
|
||||||
|
html.low-blue-light button[aria-label="Menu de Acessibilidade"],
|
||||||
|
html.low-blue-light [role="dialog"][aria-modal="true"] {
|
||||||
|
z-index: 9999999 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modo foco: destaque reforçado no elemento focado, sem quebrar layout */
|
/* Modo foco: destaque reforçado no elemento focado, sem quebrar layout */
|
||||||
@ -137,50 +158,259 @@ html.focus-mode.dark *:focus-visible,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos de Acessibilidade */
|
/* Estilos de Acessibilidade - Alto Contraste */
|
||||||
.high-contrast {
|
.high-contrast {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.high-contrast body {
|
.high-contrast body {
|
||||||
background-color: #000 !important;
|
background-color: #000 !important;
|
||||||
color: #fff !important;
|
color: #ffff00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.high-contrast .bg-white {
|
/* Backgrounds brancos/claros viram pretos */
|
||||||
|
.high-contrast .bg-white,
|
||||||
|
.high-contrast .bg-gray-50,
|
||||||
|
.high-contrast .bg-gray-100 {
|
||||||
background-color: #000 !important;
|
background-color: #000 !important;
|
||||||
color: #fff !important;
|
color: #ffff00 !important;
|
||||||
border: 2px solid #fff !important;
|
border-color: #ffff00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Backgrounds escuros ficam pretos */
|
||||||
|
.high-contrast .bg-gray-800,
|
||||||
|
.high-contrast .bg-gray-900 {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textos cinzas ficam amarelos */
|
||||||
|
.high-contrast .text-gray-400,
|
||||||
|
.high-contrast .text-gray-500,
|
||||||
.high-contrast .text-gray-600,
|
.high-contrast .text-gray-600,
|
||||||
.high-contrast .text-gray-700,
|
.high-contrast .text-gray-700,
|
||||||
.high-contrast .text-gray-800,
|
.high-contrast .text-gray-800,
|
||||||
.high-contrast .text-gray-900 {
|
.high-contrast .text-gray-900 {
|
||||||
color: #fff !important;
|
color: #ffff00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Textos brancos ficam amarelos */
|
||||||
|
.high-contrast .text-white,
|
||||||
|
.high-contrast .text-gray-100 {
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botões primários (verde/azul) */
|
||||||
.high-contrast .bg-blue-600,
|
.high-contrast .bg-blue-600,
|
||||||
.high-contrast .bg-blue-500,
|
.high-contrast .bg-blue-500,
|
||||||
.high-contrast .bg-green-600 {
|
.high-contrast .bg-green-600,
|
||||||
|
.high-contrast .bg-green-700 {
|
||||||
background-color: #ffff00 !important;
|
background-color: #ffff00 !important;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
|
border: 2px solid #000 !important;
|
||||||
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.high-contrast a,
|
/* Botões com bordas */
|
||||||
.high-contrast button:not(.bg-red-500) {
|
.high-contrast .border-gray-300,
|
||||||
text-decoration: underline;
|
.high-contrast .border-gray-600,
|
||||||
font-weight: bold;
|
.high-contrast .border-gray-200,
|
||||||
|
.high-contrast .border-gray-700 {
|
||||||
|
border-color: #ffff00 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Links e botões secundários */
|
||||||
|
.high-contrast a {
|
||||||
|
color: #ffff00 !important;
|
||||||
|
text-decoration: underline !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast button {
|
||||||
|
border: 2px solid #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs e selects */
|
||||||
.high-contrast input,
|
.high-contrast input,
|
||||||
.high-contrast select,
|
.high-contrast select,
|
||||||
.high-contrast textarea {
|
.high-contrast textarea {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
|
border: 3px solid #000 !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast input::placeholder,
|
||||||
|
.high-contrast textarea::placeholder {
|
||||||
|
color: #666 !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges e status */
|
||||||
|
.high-contrast .bg-green-100,
|
||||||
|
.high-contrast .bg-blue-100,
|
||||||
|
.high-contrast .bg-yellow-100,
|
||||||
|
.high-contrast .bg-red-100,
|
||||||
|
.high-contrast .bg-purple-100,
|
||||||
|
.high-contrast .bg-orange-100 {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
color: #000 !important;
|
||||||
border: 2px solid #000 !important;
|
border: 2px solid #000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tabelas */
|
||||||
|
.high-contrast table {
|
||||||
|
border: 2px solid #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast th,
|
||||||
|
.high-contrast td {
|
||||||
|
border: 1px solid #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast thead {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover states */
|
||||||
|
.high-contrast tr:hover,
|
||||||
|
.high-contrast .hover\:bg-gray-50:hover,
|
||||||
|
.high-contrast .hover\:bg-gray-100:hover {
|
||||||
|
background-color: #1a1a1a !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icons devem ser visíveis */
|
||||||
|
.high-contrast svg {
|
||||||
|
color: #ffff00 !important;
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botões de ação com cores específicas */
|
||||||
|
.high-contrast .text-blue-600,
|
||||||
|
.high-contrast .text-green-600,
|
||||||
|
.high-contrast .text-orange-600,
|
||||||
|
.high-contrast .text-red-600,
|
||||||
|
.high-contrast .text-purple-600 {
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast .hover\:bg-blue-50:hover,
|
||||||
|
.high-contrast .hover\:bg-green-50:hover,
|
||||||
|
.high-contrast .hover\:bg-orange-50:hover,
|
||||||
|
.high-contrast .hover\:bg-red-50:hover {
|
||||||
|
background-color: #333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divisores e bordas */
|
||||||
|
.high-contrast .divide-y > * {
|
||||||
|
border-color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards e containers */
|
||||||
|
.high-contrast .rounded-xl,
|
||||||
|
.high-contrast .rounded-lg {
|
||||||
|
border: 2px solid #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modals e dialogs */
|
||||||
|
.high-contrast .shadow-xl,
|
||||||
|
.high-contrast .shadow-sm {
|
||||||
|
box-shadow: 0 0 0 3px #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botões desabilitados */
|
||||||
|
.high-contrast button:disabled {
|
||||||
|
background-color: #333 !important;
|
||||||
|
color: #666 !important;
|
||||||
|
border-color: #666 !important;
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paginação - página ativa */
|
||||||
|
.high-contrast .bg-green-600.text-white {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
color: #000 !important;
|
||||||
|
border: 3px solid #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - células cinzas */
|
||||||
|
.high-contrast .bg-gray-200 {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - dias da semana e células */
|
||||||
|
.high-contrast .bg-gray-50,
|
||||||
|
.high-contrast .bg-gray-100 {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - dia atual (azul claro) */
|
||||||
|
.high-contrast .bg-blue-50 {
|
||||||
|
background-color: #1a1a1a !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
border: 3px solid #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - eventos/horários nas células */
|
||||||
|
.high-contrast .bg-blue-100,
|
||||||
|
.high-contrast .bg-green-100,
|
||||||
|
.high-contrast .text-blue-800,
|
||||||
|
.high-contrast .text-green-800,
|
||||||
|
.high-contrast .text-yellow-800,
|
||||||
|
.high-contrast .text-red-800,
|
||||||
|
.high-contrast .text-purple-800 {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
color: #000 !important;
|
||||||
|
border: 2px solid #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - Grid com divisórias amarelas */
|
||||||
|
.high-contrast .grid-cols-7 {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
gap: 2px !important;
|
||||||
|
padding: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-contrast .grid-cols-7 > div {
|
||||||
|
background-color: #000 !important;
|
||||||
|
border: 2px solid #ffff00 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendário - Background do grid */
|
||||||
|
.high-contrast .bg-gray-200.border.border-gray-200 {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
border-color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers com fundo cinza */
|
||||||
|
.high-contrast .bg-gray-700 {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Texto em fundos coloridos */
|
||||||
|
.high-contrast .text-blue-700,
|
||||||
|
.high-contrast .text-green-700,
|
||||||
|
.high-contrast .text-purple-700 {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Garantir que backgrounds cinzas fiquem pretos */
|
||||||
|
.high-contrast [class*="bg-gray"] {
|
||||||
|
background-color: #000 !important;
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Garantir que textos cinzas fiquem amarelos */
|
||||||
|
.high-contrast [class*="text-gray"] {
|
||||||
|
color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Modo Escuro Melhorado */
|
/* Modo Escuro Melhorado */
|
||||||
.dark {
|
.dark {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|||||||
@ -36,24 +36,24 @@ export default function PainelSecretaria() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
|
<header className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-10">
|
||||||
<div className="max-w-[1400px] mx-auto px-6 py-4">
|
<div className="max-w-[1400px] mx-auto px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
Painel da Secretaria
|
Painel da Secretaria
|
||||||
</h1>
|
</h1>
|
||||||
{user && (
|
{user && (
|
||||||
<p className="text-sm text-gray-600 mt-1">
|
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||||
Bem-vinda, {user.email}
|
Bem-vinda, {user.email}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
|
className="flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<LogOut className="h-4 w-4" />
|
<LogOut className="h-4 w-4" />
|
||||||
Sair
|
Sair
|
||||||
@ -63,7 +63,7 @@ export default function PainelSecretaria() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Tabs Navigation */}
|
{/* Tabs Navigation */}
|
||||||
<div className="bg-white border-b border-gray-200">
|
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="max-w-[1400px] mx-auto px-6">
|
<div className="max-w-[1400px] mx-auto px-6">
|
||||||
<nav className="flex gap-2">
|
<nav className="flex gap-2">
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
@ -75,8 +75,8 @@ export default function PainelSecretaria() {
|
|||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`flex items-center gap-2 px-4 py-3 border-b-2 transition-colors ${
|
className={`flex items-center gap-2 px-4 py-3 border-b-2 transition-colors ${
|
||||||
isActive
|
isActive
|
||||||
? "border-green-600 text-green-600 font-medium"
|
? "border-green-600 text-green-600 dark:text-green-400 font-medium"
|
||||||
: "border-transparent text-gray-600 hover:text-gray-900 hover:border-gray-300"
|
: "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user