Compare commits
2 Commits
ef7ef93887
...
176489f9fd
| Author | SHA1 | Date | |
|---|---|---|---|
| 176489f9fd | |||
| b46da18c45 |
@ -10,50 +10,50 @@ import isBetween from 'dayjs/plugin/isBetween';
|
|||||||
import localeData from 'dayjs/plugin/localeData';
|
import localeData from 'dayjs/plugin/localeData';
|
||||||
import { Search, ChevronLeft, ChevronRight, Edit, Trash2, CheckCircle } from 'lucide-react';
|
import { Search, ChevronLeft, ChevronRight, Edit, Trash2, CheckCircle } from 'lucide-react';
|
||||||
import "../pages/style/Agendamento.css";
|
import "../pages/style/Agendamento.css";
|
||||||
import '../pages/style/FilaEspera.css';
|
import "../pages/style/FilaEspera.css";
|
||||||
import Spinner from '../components/Spinner.jsx';
|
import Spinner from "../components/Spinner.jsx";
|
||||||
|
|
||||||
|
dayjs.locale("pt-br");
|
||||||
dayjs.locale('pt-br');
|
|
||||||
dayjs.extend(isBetween);
|
dayjs.extend(isBetween);
|
||||||
dayjs.extend(localeData);
|
dayjs.extend(localeData);
|
||||||
|
|
||||||
|
|
||||||
const Agendamento = () => {
|
const Agendamento = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getAuthorizationHeader, user } = useAuth();
|
const { getAuthorizationHeader, user } = useAuth();
|
||||||
const authHeader = getAuthorizationHeader();
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
|
|
||||||
const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df";
|
const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df";
|
||||||
|
|
||||||
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]);
|
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]);
|
||||||
const [selectedID, setSelectedId] = useState('0');
|
const [selectedID, setSelectedId] = useState("0");
|
||||||
const [filaEsperaData, setFilaEsperaData] = useState([]);
|
const [filaEsperaData, setFilaEsperaData] = useState([]);
|
||||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState({});
|
const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState(
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
{}
|
||||||
const [ListaDeMedicos, setListaDeMedicos] = useState([]);
|
);
|
||||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
const [ListaDeMedicos, setListaDeMedicos] = useState([]);
|
||||||
const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" });
|
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]);
|
||||||
const [motivoCancelamento, setMotivoCancelamento] = useState("");
|
const [searchTermDoctor, setSearchTermDoctor] = useState("");
|
||||||
const [showSpinner, setShowSpinner] = useState(true);
|
const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" });
|
||||||
const [waitlistSearch, setWaitlistSearch] = useState('');
|
const [motivoCancelamento, setMotivoCancelamento] = useState("");
|
||||||
const [waitSortKey, setWaitSortKey] = useState(null);
|
const [showSpinner, setShowSpinner] = useState(true);
|
||||||
const [waitSortDir, setWaitSortDir] = useState('asc');
|
const [waitlistSearch, setWaitlistSearch] = useState("");
|
||||||
const [waitPage, setWaitPage] = useState(1);
|
const [waitSortKey, setWaitSortKey] = useState(null);
|
||||||
const [waitPerPage, setWaitPerPage] = useState(10);
|
const [waitSortDir, setWaitSortDir] = useState("asc");
|
||||||
const [cacheMedicos, setCacheMedicos] = useState({});
|
const [waitPage, setWaitPage] = useState(1);
|
||||||
const [cachePacientes, setCachePacientes] = useState({});
|
const [waitPerPage, setWaitPerPage] = useState(10);
|
||||||
const [currentDate, setCurrentDate] = useState(dayjs());
|
const [cacheMedicos, setCacheMedicos] = useState({});
|
||||||
const [selectedDay, setSelectedDay] = useState(dayjs());
|
const [cachePacientes, setCachePacientes] = useState({});
|
||||||
const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null);
|
const [currentDate, setCurrentDate] = useState(dayjs());
|
||||||
const [quickJump, setQuickJump] = useState({
|
const [selectedDay, setSelectedDay] = useState(dayjs());
|
||||||
month: currentDate.month(),
|
const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null);
|
||||||
year: currentDate.year()
|
const [quickJump, setQuickJump] = useState({
|
||||||
});
|
month: currentDate.month(),
|
||||||
|
year: currentDate.year(),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const fetchAppointments = useCallback(async () => {
|
const fetchAppointments = useCallback(async () => {
|
||||||
@ -67,65 +67,89 @@ const Agendamento = () => {
|
|||||||
|
|
||||||
const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`;
|
const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(apiUrl, requestOptions);
|
const res = await fetch(apiUrl, requestOptions);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setListaTodosAgendamentos(data || []);
|
setListaTodosAgendamentos(data || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao buscar agendamentos', err);
|
console.error("Erro ao buscar agendamentos", err);
|
||||||
setListaTodosAgendamentos([]);
|
setListaTodosAgendamentos([]);
|
||||||
} finally {
|
} finally {
|
||||||
setShowSpinner(false);
|
setShowSpinner(false);
|
||||||
}
|
}
|
||||||
}, [authHeader, ID_MEDICO_ESPECIFICO]);
|
}, [authHeader, ID_MEDICO_ESPECIFICO]);
|
||||||
|
|
||||||
|
const updateAppointmentStatus = useCallback(
|
||||||
const updateAppointmentStatus = useCallback(async (id, updates) => {
|
async (id, updates) => {
|
||||||
setShowSpinner(true);
|
setShowSpinner(true);
|
||||||
const myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
myHeaders.append("Prefer", "return=representation");
|
myHeaders.append("Prefer", "return=representation");
|
||||||
const requestOptions = { method: 'PATCH', headers: myHeaders, body: JSON.stringify(updates) };
|
const requestOptions = {
|
||||||
try {
|
method: "PATCH",
|
||||||
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions);
|
headers: myHeaders,
|
||||||
if (response.ok) {
|
body: JSON.stringify(updates),
|
||||||
await fetchAppointments();
|
};
|
||||||
return true;
|
try {
|
||||||
} else {
|
const response = await fetch(
|
||||||
console.error('Erro ao atualizar agendamento:', await response.text());
|
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`,
|
||||||
return false;
|
requestOptions
|
||||||
}
|
);
|
||||||
} catch (error) {
|
if (response.ok) {
|
||||||
console.error('Erro de rede/servidor:', error);
|
await fetchAppointments();
|
||||||
return false;
|
return true;
|
||||||
} finally {
|
|
||||||
setShowSpinner(false);
|
|
||||||
}
|
|
||||||
}, [authHeader, fetchAppointments]);
|
|
||||||
|
|
||||||
|
|
||||||
const deleteConsulta = useCallback(async (id) => {
|
|
||||||
const success = await updateAppointmentStatus(id, { status: "cancelled", cancellation_reason: motivoCancelamento, updated_at: new Date().toISOString() });
|
|
||||||
if (success) {
|
|
||||||
setShowDeleteModal(false);
|
|
||||||
setMotivoCancelamento("");
|
|
||||||
setSelectedId('0');
|
|
||||||
} else {
|
} else {
|
||||||
alert("Falha ao cancelar a consulta.");
|
console.error(
|
||||||
|
"Erro ao atualizar agendamento:",
|
||||||
|
await response.text()
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}, [motivoCancelamento, updateAppointmentStatus]);
|
} catch (error) {
|
||||||
|
console.error("Erro de rede/servidor:", error);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setShowSpinner(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[authHeader, fetchAppointments]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteConsulta = useCallback(
|
||||||
|
async (id) => {
|
||||||
|
const success = await updateAppointmentStatus(id, {
|
||||||
|
status: "cancelled",
|
||||||
|
cancellation_reason: motivoCancelamento,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
if (success) {
|
||||||
|
setShowDeleteModal(false);
|
||||||
|
setMotivoCancelamento("");
|
||||||
|
setSelectedId("0");
|
||||||
|
} else {
|
||||||
|
alert("Falha ao cancelar a consulta.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[motivoCancelamento, updateAppointmentStatus]
|
||||||
|
);
|
||||||
|
|
||||||
const confirmConsulta = useCallback(async (id) => {
|
const confirmConsulta = useCallback(
|
||||||
const success = await updateAppointmentStatus(id, { status: "agendado", cancellation_reason: null, updated_at: new Date().toISOString() });
|
async (id) => {
|
||||||
if (success) {
|
const success = await updateAppointmentStatus(id, {
|
||||||
setSelectedId('0');
|
status: "agendado",
|
||||||
} else {
|
cancellation_reason: null,
|
||||||
alert("Falha ao reverter o cancelamento.");
|
updated_at: new Date().toISOString(),
|
||||||
}
|
});
|
||||||
}, [updateAppointmentStatus]);
|
if (success) {
|
||||||
|
setSelectedId("0");
|
||||||
|
} else {
|
||||||
|
alert("Falha ao reverter o cancelamento.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[updateAppointmentStatus]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(authHeader) {
|
if(authHeader) {
|
||||||
@ -150,24 +174,24 @@ const Agendamento = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowSpinner(true);
|
setShowSpinner(true);
|
||||||
|
|
||||||
|
|
||||||
const appointmentsToShow = listaTodosAgendamentos;
|
const appointmentsToShow = listaTodosAgendamentos;
|
||||||
|
|
||||||
const patientIdsToFetch = new Set();
|
const patientIdsToFetch = new Set();
|
||||||
const doctorIdsToFetch = new Set();
|
const doctorIdsToFetch = new Set();
|
||||||
|
|
||||||
appointmentsToShow.forEach(ag => {
|
appointmentsToShow.forEach((ag) => {
|
||||||
if (ag.patient_id && !cachePacientes[ag.patient_id]) {
|
if (ag.patient_id && !cachePacientes[ag.patient_id]) {
|
||||||
patientIdsToFetch.add(ag.patient_id);
|
patientIdsToFetch.add(ag.patient_id);
|
||||||
}
|
}
|
||||||
if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) {
|
if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) {
|
||||||
doctorIdsToFetch.add(ag.doctor_id);
|
doctorIdsToFetch.add(ag.doctor_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchPromises = [];
|
const fetchPromises = [];
|
||||||
|
|
||||||
if (patientIdsToFetch.size > 0) {
|
if (patientIdsToFetch.size > 0) {
|
||||||
const query = `id=in.(${Array.from(patientIdsToFetch).join(',')})`;
|
const query = `id=in.(${Array.from(patientIdsToFetch).join(',')})`;
|
||||||
@ -180,95 +204,106 @@ const Agendamento = () => {
|
|||||||
fetchPromises.push(Promise.resolve(null));
|
fetchPromises.push(Promise.resolve(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doctorIdsToFetch.size > 0) {
|
if (doctorIdsToFetch.size > 0) {
|
||||||
const query = `id=in.(${Array.from(doctorIdsToFetch).join(',')})`;
|
const query = `id=in.(${Array.from(doctorIdsToFetch).join(",")})`;
|
||||||
fetchPromises.push(
|
fetchPromises.push(
|
||||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`, {
|
fetch(
|
||||||
headers: { apikey: API_KEY, Authorization: authHeader }
|
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`,
|
||||||
}).then(res => res.json())
|
{
|
||||||
);
|
headers: { apikey: API_KEY, Authorization: authHeader },
|
||||||
} else {
|
|
||||||
fetchPromises.push(Promise.resolve(null));
|
|
||||||
}
|
}
|
||||||
|
).then((res) => res.json())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fetchPromises.push(Promise.resolve(null));
|
||||||
|
}
|
||||||
|
|
||||||
const [newPatients, newDoctors] = await Promise.all(fetchPromises);
|
const [newPatients, newDoctors] = await Promise.all(fetchPromises);
|
||||||
|
|
||||||
const updatedPatientCache = { ...cachePacientes };
|
const updatedPatientCache = { ...cachePacientes };
|
||||||
if (newPatients) newPatients.forEach(p => updatedPatientCache[p.id] = p);
|
if (newPatients)
|
||||||
|
newPatients.forEach((p) => (updatedPatientCache[p.id] = p));
|
||||||
|
|
||||||
const updatedDoctorCache = { ...cacheMedicos };
|
const updatedDoctorCache = { ...cacheMedicos };
|
||||||
if (newDoctors) newDoctors.forEach(d => updatedDoctorCache[d.id] = d);
|
if (newDoctors) newDoctors.forEach((d) => (updatedDoctorCache[d.id] = d));
|
||||||
|
|
||||||
setCachePacientes(updatedPatientCache);
|
setCachePacientes(updatedPatientCache);
|
||||||
setCacheMedicos(updatedDoctorCache);
|
setCacheMedicos(updatedDoctorCache);
|
||||||
|
|
||||||
const newDict = {};
|
const newDict = {};
|
||||||
const newFila = [];
|
const newFila = [];
|
||||||
|
|
||||||
for (const agendamento of appointmentsToShow) {
|
for (const agendamento of appointmentsToShow) {
|
||||||
const medico = updatedDoctorCache[agendamento.doctor_id];
|
const medico = updatedDoctorCache[agendamento.doctor_id];
|
||||||
const paciente = updatedPatientCache[agendamento.patient_id];
|
const paciente = updatedPatientCache[agendamento.patient_id];
|
||||||
|
|
||||||
if (!medico || !paciente) continue;
|
if (!medico || !paciente) continue;
|
||||||
|
|
||||||
const agendamentoMelhorado = {
|
const agendamentoMelhorado = {
|
||||||
...agendamento,
|
...agendamento,
|
||||||
medico_nome: medico.full_name || 'N/A',
|
medico_nome: medico.full_name || "N/A",
|
||||||
paciente_nome: paciente.full_name || 'N/A',
|
paciente_nome: paciente.full_name || "N/A",
|
||||||
paciente_cpf: paciente.cpf || 'N/A'
|
paciente_cpf: paciente.cpf || "N/A",
|
||||||
};
|
|
||||||
|
|
||||||
if (agendamento.status === "requested") {
|
|
||||||
newFila.push({ agendamento: agendamentoMelhorado, Infos: agendamentoMelhorado });
|
|
||||||
} else {
|
|
||||||
const DiaAgendamento = dayjs(agendamento.scheduled_at).format("YYYY-MM-DD");
|
|
||||||
if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = [];
|
|
||||||
newDict[DiaAgendamento].push(agendamentoMelhorado);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in newDict) {
|
|
||||||
newDict[key].sort((a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at));
|
|
||||||
}
|
|
||||||
|
|
||||||
setAgendamentosOrganizados(newDict);
|
|
||||||
setFilaEsperaData(newFila);
|
|
||||||
setShowSpinner(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (agendamento.status === "requested") {
|
||||||
|
newFila.push({
|
||||||
|
agendamento: agendamentoMelhorado,
|
||||||
|
Infos: agendamentoMelhorado,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const DiaAgendamento = dayjs(agendamento.scheduled_at).format(
|
||||||
|
"YYYY-MM-DD"
|
||||||
|
);
|
||||||
|
if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = [];
|
||||||
|
newDict[DiaAgendamento].push(agendamentoMelhorado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in newDict) {
|
||||||
|
newDict[key].sort(
|
||||||
|
(a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAgendamentosOrganizados(newDict);
|
||||||
|
setFilaEsperaData(newFila);
|
||||||
|
setShowSpinner(false);
|
||||||
|
};
|
||||||
|
|
||||||
processData();
|
processData();
|
||||||
}, [listaTodosAgendamentos, authHeader]);
|
}, [listaTodosAgendamentos, authHeader]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleEditConsulta = (agendamento) => {
|
const handleEditConsulta = (agendamento) => {
|
||||||
setAgendamentoParaEdicao(agendamento);
|
setAgendamentoParaEdicao(agendamento);
|
||||||
setPageConsulta(true);
|
setPageConsulta(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchMedicos = (term) => {
|
const handleSearchMedicos = (term) => {
|
||||||
setSearchTermDoctor(term);
|
setSearchTermDoctor(term);
|
||||||
if (term.trim()) {
|
if (term.trim()) {
|
||||||
const filtered = ListaDeMedicos.filter(medico =>
|
const filtered = ListaDeMedicos.filter((medico) =>
|
||||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||||
);
|
);
|
||||||
setFiltredTodosMedicos(filtered);
|
setFiltredTodosMedicos(filtered);
|
||||||
} else {
|
} else {
|
||||||
setFiltredTodosMedicos([]);
|
setFiltredTodosMedicos([]);
|
||||||
setMedicoFiltrado({ id: "vazio" });
|
setMedicoFiltrado({ id: "vazio" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateDateGrid = () => {
|
const generateDateGrid = () => {
|
||||||
const grid = [];
|
const grid = [];
|
||||||
const startOfMonth = currentDate.startOf('month');
|
const startOfMonth = currentDate.startOf("month");
|
||||||
let currentDay = startOfMonth.subtract(startOfMonth.day(), 'day');
|
let currentDay = startOfMonth.subtract(startOfMonth.day(), "day");
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 42; i++) {
|
||||||
grid.push(currentDay);
|
grid.push(currentDay);
|
||||||
currentDay = currentDay.add(1, 'day');
|
currentDay = currentDay.add(1, "day");
|
||||||
}
|
}
|
||||||
return grid;
|
return grid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateGrid = useMemo(() => generateDateGrid(), [currentDate]);
|
const dateGrid = useMemo(() => generateDateGrid(), [currentDate]);
|
||||||
const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
|
const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
|
||||||
@ -293,68 +328,86 @@ const DeleteModal = () => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setQuickJump({
|
||||||
|
month: currentDate.month(),
|
||||||
|
year: currentDate.year(),
|
||||||
|
});
|
||||||
|
}, [currentDate]);
|
||||||
|
|
||||||
useEffect(() => {
|
const filaEsperaFiltrada = useMemo(() => {
|
||||||
setQuickJump({
|
if (!waitlistSearch.trim()) return filaEsperaData;
|
||||||
month: currentDate.month(),
|
const term = waitlistSearch.toLowerCase();
|
||||||
year: currentDate.year()
|
return filaEsperaData.filter(
|
||||||
});
|
(item) =>
|
||||||
}, [currentDate]);
|
(item?.Infos?.paciente_nome?.toLowerCase() || "").includes(term) ||
|
||||||
|
(item?.Infos?.paciente_cpf?.toLowerCase() || "").includes(term) ||
|
||||||
|
(item?.Infos?.medico_nome?.toLowerCase() || "").includes(term)
|
||||||
|
);
|
||||||
|
}, [waitlistSearch, filaEsperaData]);
|
||||||
|
|
||||||
const filaEsperaFiltrada = useMemo(() => {
|
const applySortingWaitlist = useCallback(
|
||||||
if (!waitlistSearch.trim()) return filaEsperaData;
|
(arr) => {
|
||||||
const term = waitlistSearch.toLowerCase();
|
if (!Array.isArray(arr) || !waitSortKey) return arr;
|
||||||
return filaEsperaData.filter(item =>
|
const copy = [...arr];
|
||||||
(item?.Infos?.paciente_nome?.toLowerCase() || '').includes(term) ||
|
const key = waitSortKey;
|
||||||
(item?.Infos?.paciente_cpf?.toLowerCase() || '').includes(term) ||
|
const dir = waitSortDir === "asc" ? 1 : -1;
|
||||||
(item?.Infos?.medico_nome?.toLowerCase() || '').includes(term)
|
copy.sort((a, b) => {
|
||||||
);
|
const valA =
|
||||||
}, [waitlistSearch, filaEsperaData]);
|
key === "data"
|
||||||
|
? new Date(a.agendamento.scheduled_at)
|
||||||
|
: a.Infos?.[`${key}_nome`] || "";
|
||||||
|
const valB =
|
||||||
|
key === "data"
|
||||||
|
? new Date(b.agendamento.scheduled_at)
|
||||||
|
: b.Infos?.[`${key}_nome`] || "";
|
||||||
|
if (valA < valB) return -1 * dir;
|
||||||
|
if (valA > valB) return 1 * dir;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return copy;
|
||||||
|
},
|
||||||
|
[waitSortKey, waitSortDir]
|
||||||
|
);
|
||||||
|
|
||||||
const applySortingWaitlist = useCallback((arr) => {
|
const filaEsperaOrdenada = useMemo(
|
||||||
if (!Array.isArray(arr) || !waitSortKey) return arr;
|
() => applySortingWaitlist(filaEsperaFiltrada),
|
||||||
const copy = [...arr];
|
[filaEsperaFiltrada, applySortingWaitlist]
|
||||||
const key = waitSortKey;
|
);
|
||||||
const dir = waitSortDir === 'asc' ? 1 : -1;
|
const waitTotalPages =
|
||||||
copy.sort((a, b) => {
|
Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1;
|
||||||
const valA = key === 'data' ? new Date(a.agendamento.scheduled_at) : (a.Infos?.[`${key}_nome`] || '');
|
const waitIndiceInicial = (waitPage - 1) * waitPerPage;
|
||||||
const valB = key === 'data' ? new Date(b.agendamento.scheduled_at) : (b.Infos?.[`${key}_nome`] || '');
|
const waitIndiceFinal = waitIndiceInicial + waitPerPage;
|
||||||
if (valA < valB) return -1 * dir;
|
const filaEsperaPaginada = filaEsperaOrdenada.slice(
|
||||||
if (valA > valB) return 1 * dir;
|
waitIndiceInicial,
|
||||||
return 0;
|
waitIndiceFinal
|
||||||
});
|
);
|
||||||
return copy;
|
|
||||||
}, [waitSortKey, waitSortDir]);
|
|
||||||
|
|
||||||
const filaEsperaOrdenada = useMemo(() => applySortingWaitlist(filaEsperaFiltrada), [filaEsperaFiltrada, applySortingWaitlist]);
|
const gerarNumerosWaitPages = () => {
|
||||||
const waitTotalPages = Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1;
|
const paginas = [];
|
||||||
const waitIndiceInicial = (waitPage - 1) * waitPerPage;
|
const paginasParaMostrar = 5;
|
||||||
const waitIndiceFinal = waitIndiceInicial + waitPerPage;
|
let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2));
|
||||||
const filaEsperaPaginada = filaEsperaOrdenada.slice(waitIndiceInicial, waitIndiceFinal);
|
let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1);
|
||||||
|
inicio = Math.max(1, fim - paginasParaMostrar + 1);
|
||||||
|
for (let i = inicio; i <= fim; i++) paginas.push(i);
|
||||||
|
return paginas;
|
||||||
|
};
|
||||||
|
|
||||||
const gerarNumerosWaitPages = () => {
|
useEffect(() => {
|
||||||
const paginas = [];
|
setWaitPage(1);
|
||||||
const paginasParaMostrar = 5;
|
}, [waitlistSearch, waitSortKey, waitSortDir]);
|
||||||
let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2));
|
|
||||||
let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1);
|
|
||||||
inicio = Math.max(1, fim - paginasParaMostrar + 1);
|
|
||||||
for (let i = inicio; i <= fim; i++) paginas.push(i);
|
|
||||||
return paginas;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => { setWaitPage(1); }, [waitlistSearch, waitSortKey, waitSortDir]);
|
const handleQuickJumpChange = (type, value) => {
|
||||||
|
setQuickJump((prev) => ({ ...prev, [type]: Number(value) }));
|
||||||
|
};
|
||||||
|
|
||||||
const handleQuickJumpChange = (type, value) => {
|
const applyQuickJump = () => {
|
||||||
setQuickJump(prev => ({ ...prev, [type]: Number(value) }));
|
let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1);
|
||||||
};
|
setCurrentDate(newDate);
|
||||||
|
setSelectedDay(newDate);
|
||||||
const applyQuickJump = () => {
|
};
|
||||||
let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1);
|
|
||||||
setCurrentDate(newDate);
|
|
||||||
setSelectedDay(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -113,7 +113,7 @@ function mockFetchPagamentos() {
|
|||||||
desconto: 0,
|
desconto: 0,
|
||||||
observacoes: "Não respondeu ao contato"
|
observacoes: "Não respondeu ao contato"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "PAY-004",
|
id: "PAY-004",
|
||||||
paciente: { nome: "Carlos Almeida", convenio: "Particular" },
|
paciente: { nome: "Carlos Almeida", convenio: "Particular" },
|
||||||
valor: 10000,
|
valor: 10000,
|
||||||
@ -169,7 +169,7 @@ export default function FinanceiroDashboard() {
|
|||||||
recebido += valorLiquido;
|
recebido += valorLiquido;
|
||||||
descontos += p.desconto;
|
descontos += p.desconto;
|
||||||
} else {
|
} else {
|
||||||
aReceber += p.valor;
|
aReceber += valorLiquido;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -244,8 +244,7 @@ export default function FinanceiroDashboard() {
|
|||||||
<option value="vencido">Vencido</option>
|
<option value="vencido">Vencido</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
className="action-btn"
|
className="btn btn-primary"
|
||||||
style={{ background: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalPagamento({
|
setModalPagamento({
|
||||||
paciente: { nome:"", convenio: CONVENIOS_LIST[0] },
|
paciente: { nome:"", convenio: CONVENIOS_LIST[0] },
|
||||||
@ -284,7 +283,7 @@ export default function FinanceiroDashboard() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{filteredPagamentos.map(p => (
|
{filteredPagamentos.map(p => (
|
||||||
<tr key={p.id}>
|
<tr key={p.id}>
|
||||||
<td>{p.paciente.nome}</td>
|
<td style={{ fontWeight: 600 }}>{p.paciente.nome}</td>
|
||||||
<td>{p.paciente.convenio}</td>
|
<td>{p.paciente.convenio}</td>
|
||||||
<td>{formatCurrency(p.valor)}</td>
|
<td>{formatCurrency(p.valor)}</td>
|
||||||
<td>{formatCurrency(p.desconto)}</td>
|
<td>{formatCurrency(p.desconto)}</td>
|
||||||
@ -295,16 +294,16 @@ export default function FinanceiroDashboard() {
|
|||||||
<td>
|
<td>
|
||||||
<div className="action-group">
|
<div className="action-group">
|
||||||
<button
|
<button
|
||||||
className="action-btn"
|
className="btn-view"
|
||||||
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }}
|
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }}
|
||||||
>
|
>
|
||||||
Ver / Editar
|
<i className="bi bi-eye me-1"></i> Ver / Editar
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="action-btn delete"
|
className="btn-delete"
|
||||||
onClick={() => handleDelete(p.id)}
|
onClick={() => handleDelete(p.id)}
|
||||||
>
|
>
|
||||||
Excluir
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -416,15 +415,14 @@ export default function FinanceiroDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button className="action-btn" onClick={() => handleSave(modalPagamento)}>
|
<button className="btn-view" onClick={() => handleSave(modalPagamento)}>
|
||||||
Salvar
|
<i className="bi bi-check-circle me-1"></i> Salvar
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="action-btn"
|
className="btn-delete"
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
style={{ borderColor: '#d1d5db', color: '#4b5563' }}
|
|
||||||
>
|
>
|
||||||
Cancelar
|
<i className="bi bi-x-circle me-1"></i> Cancelar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -273,6 +273,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 15px 0 20px;
|
margin: 15px 0 20px;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-agenda-fila {
|
.tabs-agenda-fila {
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,35 +108,79 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Botões de ação */
|
/* Botões de ação */
|
||||||
.action-group {
|
|
||||||
display: flex;
|
.btn-view {
|
||||||
gap: 8px;
|
background-color: #E6F2FF !important;
|
||||||
align-items: center;
|
color: #004085 !important;
|
||||||
|
border: 1px solid #B8D4F0 !important;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.btn-view:hover {
|
||||||
cursor: pointer;
|
background-color: #D1E7FF !important;
|
||||||
padding: 6px 12px;
|
border-color: #9EC5FE !important;
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #d7e6fb;
|
|
||||||
background: #fff;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:hover {
|
.btn-edit {
|
||||||
background: #f6f9fc;
|
background-color: #FFF3CD !important;
|
||||||
border-color: #93c5fd;
|
color: #856404 !important;
|
||||||
|
border: 1px solid #FFEAA7 !important;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.delete {
|
.btn-edit:hover {
|
||||||
border-color: #fca5a5;
|
background-color: #FFEEBA !important;
|
||||||
color: #b91c1c;
|
border-color: #FFE087 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.delete:hover {
|
.btn-delete:hover {
|
||||||
background: #fee2e2;
|
background-color: #F1B0B7 !important;
|
||||||
border-color: #ef4444;
|
border-color: #ED969E !important;
|
||||||
|
}
|
||||||
|
.btn-delete {
|
||||||
|
background-color: #F8D7DA !important;
|
||||||
|
color: #721C24 !important;
|
||||||
|
border: 1px solid #F5C6CB !important;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .btn-view {
|
||||||
|
background-color: #1e3a8a !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .btn-edit {
|
||||||
|
background-color: #78350f !important;
|
||||||
|
color: #fef3c7 !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .btn-delete {
|
||||||
|
background-color: #7f1d1d !important;
|
||||||
|
color: #fee2e2 !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Badges de status */
|
/* Badges de status */
|
||||||
@ -182,7 +227,7 @@
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 550px;
|
max-width: 550px;
|
||||||
max-height: 90vh;
|
max-height: 85vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
@ -208,6 +253,12 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-card .input-field,
|
||||||
|
.modal-card .select-field,
|
||||||
|
.modal-card textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -241,12 +292,23 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
.input-field,
|
||||||
|
.select-field,
|
||||||
|
textarea {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Inputs e selects */
|
/* Inputs e selects */
|
||||||
.input-field,
|
.input-field,
|
||||||
.select-field,
|
.select-field,
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -269,6 +331,18 @@ textarea {
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.financeiro-wrap .input-field:not(.modal-card *),
|
||||||
|
.financeiro-wrap .select-field:not(.modal-card *),
|
||||||
|
.financeiro-wrap textarea:not(.modal-card *) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card .input-field,
|
||||||
|
.modal-card .select-field,
|
||||||
|
.modal-card textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mensagem quando não há pagamentos */
|
/* Mensagem quando não há pagamentos */
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -70,10 +70,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Cores dos ícones */
|
/* Cores dos ícones */
|
||||||
.stat-icon-wrapper.blue { background-color: #5d5dff; }
|
.stat-icon-wrapper.blue { background-color: #1D3B88; }
|
||||||
.stat-icon-wrapper.green { background-color: #30d158; }
|
.stat-icon-wrapper.green { background-color: #399CE5; }
|
||||||
.stat-icon-wrapper.purple { background-color: #a272ff; }
|
.stat-icon-wrapper.purple { background-color: #5F5DF2; }
|
||||||
.stat-icon-wrapper.orange { background-color: #f1952e; }
|
.stat-icon-wrapper.orange { background-color: #051AFF; }
|
||||||
|
|
||||||
/* Seção de Ações Rápidas */
|
/* Seção de Ações Rápidas */
|
||||||
.quick-actions h2 {
|
.quick-actions h2 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user