fix/report #70

Merged
M-Gabrielly merged 7 commits from fix/report into develop 2025-11-07 03:25:37 +00:00
2 changed files with 145 additions and 119 deletions
Showing only changes of commit 77afb91c70 - Show all commits

View File

@ -155,13 +155,13 @@ export default function AgendamentoPage() {
}; };
return ( return (
<div className="flex flex-row bg-background"> <div className="bg-background">
<div className="flex w-full flex-col"> <div className="w-full">
<div className="flex w-full flex-col gap-10 p-6"> <div className="w-full max-w-7xl mx-auto flex flex-col gap-6 sm:gap-10 p-4 sm:p-6">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2">
{/* Cabeçalho simplificado (sem 3D) */} {/* Cabeçalho simplificado (sem 3D) */}
<div> <div>
<h1 className="text-2xl font-bold text-foreground">Calendário</h1> <h1 className="text-xl sm:text-2xl font-bold text-foreground">Calendário</h1>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Navegue através do atalho: Calendário (C). Navegue através do atalho: Calendário (C).
</p> </p>
@ -170,8 +170,8 @@ export default function AgendamentoPage() {
</div> </div>
{/* Legenda de status (aplica-se ao EventManager) */} {/* Legenda de status (aplica-se ao EventManager) */}
<div className="rounded-md border bg-card/60 p-2 sm:p-3 -mt-4"> <div className="rounded-md border bg-card/60 p-2 sm:p-3 -mt-2 sm:-mt-4 overflow-x-auto">
<div className="flex flex-wrap items-center gap-6 text-sm"> <div className="flex flex-nowrap items-center gap-4 sm:gap-6 text-xs sm:text-sm whitespace-nowrap">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span aria-hidden className="h-3 w-3 rounded-full bg-blue-500 ring-2 ring-blue-500/30" /> <span aria-hidden className="h-3 w-3 rounded-full bg-blue-500 ring-2 ring-blue-500/30" />
<span className="text-foreground">Solicitado</span> <span className="text-foreground">Solicitado</span>
@ -192,11 +192,11 @@ export default function AgendamentoPage() {
<div className="flex w-full"> <div className="flex w-full">
<div className="w-full"> <div className="w-full">
{managerLoading ? ( {managerLoading ? (
<div className="flex items-center justify-center w-full min-h-[70vh]"> <div className="flex items-center justify-center w-full min-h-[60vh] sm:min-h-[70vh]">
<div className="text-sm text-muted-foreground">Conectando ao calendário carregando agendamentos...</div> <div className="text-sm text-muted-foreground">Conectando ao calendário carregando agendamentos...</div>
</div> </div>
) : ( ) : (
<div className="w-full min-h-[70vh]"> <div className="w-full min-h-[60vh] sm:min-h-[70vh]">
<EventManager <EventManager
events={managerEvents} events={managerEvents}
className="compact-event-manager" className="compact-event-manager"

View File

@ -642,7 +642,7 @@ export default function ResultadosClient() {
// Render // Render
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-4 py-10 md:px-8"> <div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-4 py-6 sm:py-10 md:px-8">
{/* Toast */} {/* Toast */}
{toast && ( {toast && (
<div className={`fixed top-4 right-4 z-50 px-4 py-2 rounded shadow-lg ${toast.type==='success'?'bg-green-600 text-white':'bg-red-600 text-white'}`} role="alert"> <div className={`fixed top-4 right-4 z-50 px-4 py-2 rounded shadow-lg ${toast.type==='success'?'bg-green-600 text-white':'bg-red-600 text-white'}`} role="alert">
@ -699,10 +699,10 @@ export default function ResultadosClient() {
</Dialog> </Dialog>
{/* Hero de filtros (mantido) */} {/* Hero de filtros (mantido) */}
<section className="rounded-3xl bg-primary p-6 text-primary-foreground shadow-lg"> <section className="rounded-2xl sm:rounded-3xl bg-primary p-4 sm:p-6 text-primary-foreground shadow-lg">
<div className="flex flex-wrap items-center justify-between gap-4"> <div className="flex flex-wrap items-center justify-between gap-4">
<div> <div>
<h1 className="text-2xl font-semibold md:text-3xl">Resultados da procura</h1> <h1 className="text-xl font-semibold sm:text-2xl md:text-3xl">Resultados da procura</h1>
<p className="text-sm text-primary-foreground/80">Qual especialização você deseja?</p> <p className="text-sm text-primary-foreground/80">Qual especialização você deseja?</p>
</div> </div>
<Button <Button
@ -712,14 +712,14 @@ export default function ResultadosClient() {
Ajustar filtros Ajustar filtros
</Button> </Button>
</div> </div>
<div className="mt-6 flex flex-wrap gap-3"> <div className="mt-4 sm:mt-6 flex flex-wrap gap-2 sm:gap-3">
{especialidadesHero.map(item => ( {especialidadesHero.map(item => (
<button <button
key={item} key={item}
type="button" type="button"
onClick={() => setEspecialidadeHero(item)} onClick={() => setEspecialidadeHero(item)}
className={cn( className={cn(
'rounded-full px-5 py-2 text-sm font-medium transition focus-visible:ring-2 focus-visible:ring-primary-foreground/80', 'rounded-full px-4 sm:px-5 py-2 text-sm font-medium transition focus-visible:ring-2 focus-visible:ring-primary-foreground/80',
especialidadeHero === item ? 'bg-primary-foreground text-primary' : 'bg-primary-foreground/10' especialidadeHero === item ? 'bg-primary-foreground text-primary' : 'bg-primary-foreground/10'
)} )}
> >
@ -729,103 +729,123 @@ export default function ResultadosClient() {
</div> </div>
</section> </section>
{/* Barra de filtros secundários (mantida) */} {/* Barra de filtros secundários (agora fluída, sem sticky) */}
<section className="sticky top-0 z-30 flex flex-wrap gap-3 rounded-2xl border border-border bg-card/90 p-4 shadow-lg backdrop-blur"> <section className="rounded-2xl border border-border bg-card/80 p-4 sm:p-5 shadow-md backdrop-blur">
<Toggle <div className="grid grid-cols-1 sm:grid-cols-12 gap-3">
pressed={tipoConsulta === 'teleconsulta'} {/* Segmented control: tipo da consulta */}
onPressedChange={() => setTipoConsulta('teleconsulta')} <div className="sm:col-span-12">
className={cn('rounded-full px-4 py-2.5 text-sm font-medium transition hover:bg-primary hover:text-primary-foreground focus-visible:ring-2 focus-visible:ring-primary/60 active:scale-[0.97]', <div className="flex w-full overflow-hidden rounded-full border border-primary/25 bg-primary/5 shadow-sm ring-1 ring-primary/10">
tipoConsulta === 'teleconsulta' ? 'bg-primary text-primary-foreground' : 'border border-primary/40 text-primary')} <Toggle
> pressed={tipoConsulta === 'teleconsulta'}
<Globe className="mr-2 h-4 w-4" /> onPressedChange={() => setTipoConsulta('teleconsulta')}
Teleconsulta className="flex-1 rounded-none first:rounded-l-full px-4 py-2.5 text-sm font-medium transition data-[state=on]:bg-primary data-[state=on]:text-primary-foreground hover:bg-primary/10"
</Toggle> >
<Toggle <Globe className="mr-2 h-4 w-4" />
pressed={tipoConsulta === 'local'} Teleconsulta
onPressedChange={() => setTipoConsulta('local')} </Toggle>
className={cn('rounded-full px-4 py-2.5 text-sm font-medium transition hover:bg-primary hover:text-primary-foreground focus-visible:ring-2 focus-visible:ring-primary/60 active:scale-[0.97]', <Toggle
tipoConsulta === 'local' ? 'bg-primary text-primary-foreground' : 'border border-primary/40 text-primary')} pressed={tipoConsulta === 'local'}
> onPressedChange={() => setTipoConsulta('local')}
<Building2 className="mr-2 h-4 w-4" /> className="flex-1 rounded-none last:rounded-r-full px-4 py-2.5 text-sm font-medium transition data-[state=on]:bg-primary data-[state=on]:text-primary-foreground hover:bg-primary/10"
Consulta no local >
</Toggle> <Building2 className="mr-2 h-4 w-4" />
Consulta no local
</Toggle>
</div>
</div>
<Select value={convenio} onValueChange={setConvenio}> {/* divider visual */}
<SelectTrigger className="h-10 min-w-[180px] rounded-full border border-primary/40 bg-primary/10 text-primary transition duration-200 hover:border-primary! focus:ring-2 focus:ring-primary cursor-pointer"> <div className="sm:col-span-12 h-px bg-border/60 my-1" />
<SelectValue placeholder="Convênio" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Todos">Todos os convênios</SelectItem>
<SelectItem value="Amil">Amil</SelectItem>
<SelectItem value="Unimed">Unimed</SelectItem>
<SelectItem value="SulAmérica">SulAmérica</SelectItem>
<SelectItem value="Bradesco Saúde">Bradesco Saúde</SelectItem>
<SelectItem value="Particular">Particular</SelectItem>
</SelectContent>
</Select>
{/* Search input para buscar médico por nome (movido antes do Select de bairro para ficar ao lado visualmente) */} {/* Convênio */}
<div className="flex items-center gap-2"> <div className="sm:col-span-6 lg:col-span-4">
<Input <Select value={convenio} onValueChange={setConvenio}>
placeholder="Buscar médico por nome" <SelectTrigger className="h-10 w-full rounded-full border border-primary/30 bg-primary/5 text-primary hover:border-primary focus:ring-2 focus:ring-primary">
value={searchQuery} <SelectValue placeholder="Convênio" />
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)} </SelectTrigger>
className="min-w-[220px] rounded-full" <SelectContent>
/> <SelectItem value="Todos">Todos os convênios</SelectItem>
{searchQuery ? ( <SelectItem value="Amil">Amil</SelectItem>
<SelectItem value="Unimed">Unimed</SelectItem>
<SelectItem value="SulAmérica">SulAmérica</SelectItem>
<SelectItem value="Bradesco Saúde">Bradesco Saúde</SelectItem>
<SelectItem value="Particular">Particular</SelectItem>
</SelectContent>
</Select>
</div>
{/* Busca por nome + Mais filtros/Limpar */}
<div className="sm:col-span-6 lg:col-span-4">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
<Input
placeholder="Buscar médico por nome"
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
className="w-full sm:min-w-[220px] rounded-full"
/>
{searchQuery ? (
<Button
variant="ghost"
className="h-10 w-full sm:w-auto rounded-full"
onClick={async () => {
setSearchQuery('')
setCurrentPage(1)
try {
setLoadingMedicos(true)
setMedicos([])
setAgendaByDoctor({})
setAgendasExpandida({})
const termo = (especialidadeHero && especialidadeHero !== 'Veja mais') ? especialidadeHero : (params?.get('q') || 'medico')
const list = await buscarMedicos(termo).catch(() => [])
setMedicos(Array.isArray(list) ? list : [])
} catch (e: any) {
showToast('error', e?.message || 'Falha ao buscar profissionais')
} finally {
setLoadingMedicos(false)
}
}}
>
Limpar
</Button>
) : (
<Button
variant="outline"
className="h-10 w-full sm:w-auto rounded-full border border-primary/30 bg-primary/5 text-primary hover:bg-primary hover:text-primary-foreground"
>
<Filter className="mr-2 h-4 w-4" />
Mais filtros
</Button>
)}
</div>
</div>
{/* Bairro */}
<div className="sm:col-span-6 lg:col-span-4">
<Select value={bairro} onValueChange={setBairro}>
<SelectTrigger className="h-10 w-full rounded-full border border-primary/30 bg-primary/5 text-primary hover:border-primary focus:ring-2 focus:ring-primary">
<SelectValue placeholder="Bairro" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Todos">Todos os bairros</SelectItem>
<SelectItem value="Centro">Centro</SelectItem>
<SelectItem value="Jardins">Jardins</SelectItem>
<SelectItem value="Farolândia">Farolândia</SelectItem>
</SelectContent>
</Select>
</div>
{/* Voltar */}
<div className="sm:col-span-12">
<Button <Button
variant="ghost" variant="ghost"
className="h-10" className="w-full rounded-full text-primary hover:bg-primary hover:text-primary-foreground transition-colors"
onClick={async () => { onClick={() => router.back()}
// limpar o termo de busca e restaurar a lista por especialidade
setSearchQuery('')
setCurrentPage(1)
try {
setLoadingMedicos(true)
setMedicos([])
setAgendaByDoctor({})
setAgendasExpandida({})
const termo = (especialidadeHero && especialidadeHero !== 'Veja mais') ? especialidadeHero : (params?.get('q') || 'medico')
const list = await buscarMedicos(termo).catch(() => [])
setMedicos(Array.isArray(list) ? list : [])
} catch (e: any) {
showToast('error', e?.message || 'Falha ao buscar profissionais')
} finally {
setLoadingMedicos(false)
}
}}
>Limpar</Button>
) : (
<Button
variant="outline"
className="rounded-full border border-primary/40 bg-primary/10 text-primary hover:bg-primary! hover:text-white! transition-colors"
> >
<Filter className="mr-2 h-4 w-4" /> Voltar
Mais filtros <ChevronRight className="ml-1 h-4 w-4 rotate-180" />
</Button> </Button>
)} </div>
</div> </div>
<Select value={bairro} onValueChange={setBairro}>
<SelectTrigger className="h-10 min-w-40 rounded-full border border-primary/40 bg-primary/10 text-primary transition duration-200 hover:border-primary! focus:ring-2 focus:ring-primary cursor-pointer">
<SelectValue placeholder="Bairro" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Todos">Todos os bairros</SelectItem>
<SelectItem value="Centro">Centro</SelectItem>
<SelectItem value="Jardins">Jardins</SelectItem>
<SelectItem value="Farolândia">Farolândia</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
className="ml-auto rounded-full text-primary hover:bg-primary! hover:text-white! transition-colors"
onClick={() => router.back()}
>
Voltar
<ChevronRight className="ml-1 h-4 w-4 rotate-180" />
</Button>
</section> </section>
{/* Lista de profissionais */} {/* Lista de profissionais */}
@ -879,7 +899,7 @@ export default function ResultadosClient() {
</div> </div>
<Button <Button
variant="ghost" variant="ghost"
className="ml-auto h-fit rounded-full text-primary hover:bg-primary! hover:text-white! transition-colors" className="ml-0 sm:ml-auto w-full sm:w-auto h-fit rounded-full text-primary hover:bg-primary! hover:text-white! transition-colors"
onClick={() => { onClick={() => {
setMedicoSelecionado(medico) setMedicoSelecionado(medico)
setAbaDetalhe('experiencia') setAbaDetalhe('experiencia')
@ -945,7 +965,7 @@ export default function ResultadosClient() {
<div className="flex flex-wrap gap-3 pt-2"> <div className="flex flex-wrap gap-3 pt-2">
<Button <Button
className="h-11 rounded-full bg-primary text-primary-foreground hover:bg-primary/90" className="h-11 w-full sm:w-auto rounded-full bg-primary text-primary-foreground hover:bg-primary/90"
onClick={async () => { onClick={async () => {
// If we don't have the agenda loaded, load it and try to open the nearest slot. // If we don't have the agenda loaded, load it and try to open the nearest slot.
if (!agendaByDoctor[id]) { if (!agendaByDoctor[id]) {
@ -972,12 +992,12 @@ export default function ResultadosClient() {
> >
Agendar consulta Agendar consulta
</Button> </Button>
<Button variant="outline" className="h-11 rounded-full border-primary/40 bg-primary/10 text-primary hover:bg-primary! hover:text-white! transition-colors"> <Button variant="outline" className="h-11 w-full sm:w-auto rounded-full border-primary/40 bg-primary/10 text-primary hover:bg-primary! hover:text-white! transition-colors">
Enviar mensagem Enviar mensagem
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
className="h-11 rounded-full text-primary hover:bg-primary! hover:text-white! transition-colors" className="h-11 w-full sm:w-auto rounded-full text-primary hover:bg-primary! hover:text-white! transition-colors"
onClick={() => { onClick={() => {
const willOpen = !agendasExpandida[id] const willOpen = !agendasExpandida[id]
setAgendasExpandida(prev => ({ ...prev, [id]: !prev[id] })) setAgendasExpandida(prev => ({ ...prev, [id]: !prev[id] }))
@ -1009,18 +1029,23 @@ export default function ResultadosClient() {
{/* Pagination controls */} {/* Pagination controls */}
{!loadingMedicos && profissionais.length > 0 && ( {!loadingMedicos && profissionais.length > 0 && (
<div className="flex items-center justify-between mt-2"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mt-2 gap-3">
<div className="flex items-center gap-3 text-sm text-muted-foreground"> <div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 text-sm text-muted-foreground w-full sm:w-auto">
<span>Itens por página:</span> <span>Itens por página:</span>
<select value={itemsPerPage} onChange={(e) => setItemsPerPage(Number(e.target.value))} className="h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary cursor-pointer"> <Select value={String(itemsPerPage)} onValueChange={(v) => setItemsPerPage(Number(v))}>
<option value={5}>5</option> <SelectTrigger className="h-9 w-full sm:w-28 min-w-[110px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus:ring-2 focus:ring-primary">
<option value={10}>10</option> <SelectValue placeholder="Itens" />
<option value={20}>20</option> </SelectTrigger>
</select> <SelectContent className="z-50">
<SelectItem value="5">5</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
</SelectContent>
</Select>
<span>Mostrando {startItem} a {endItem} de {profissionais.length}</span> <span>Mostrando {startItem} a {endItem} de {profissionais.length}</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 w-full sm:w-auto">
<Button variant="outline" size="sm" onClick={() => setCurrentPage(1)} disabled={currentPage === 1} className="hover:bg-primary! hover:text-white!">Primeira</Button> <Button variant="outline" size="sm" onClick={() => setCurrentPage(1)} disabled={currentPage === 1} className="hover:bg-primary! hover:text-white!">Primeira</Button>
<Button variant="outline" size="sm" onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} className="hover:bg-primary! hover:text-white!">Anterior</Button> <Button variant="outline" size="sm" onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} className="hover:bg-primary! hover:text-white!">Anterior</Button>
<span className="text-sm text-muted-foreground">Página {currentPage} de {totalPages}</span> <span className="text-sm text-muted-foreground">Página {currentPage} de {totalPages}</span>
@ -1033,7 +1058,7 @@ export default function ResultadosClient() {
{/* Dialog de perfil completo (mantido e adaptado) */} {/* Dialog de perfil completo (mantido e adaptado) */}
<Dialog open={!!medicoSelecionado} onOpenChange={(open: boolean) => !open && setMedicoSelecionado(null)}> <Dialog open={!!medicoSelecionado} onOpenChange={(open: boolean) => !open && setMedicoSelecionado(null)}>
<DialogContent className="max-h[90vh] max-h-[90vh] w-full max-w-5xl overflow-y-auto border border-border bg-card p-0"> <DialogContent className="w-full max-w-[95vw] sm:max-w-5xl max-h-[90vh] overflow-y-auto border border-border bg-card p-0 sm:rounded-lg">
{medicoSelecionado && ( {medicoSelecionado && (
<> <>
<DialogHeader className="border-b border-border px-6 py-4"> <DialogHeader className="border-b border-border px-6 py-4">
@ -1147,9 +1172,10 @@ export default function ResultadosClient() {
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Dialog: Mostrar mais horários (escolher data arbitrária) */}
{/* Dialog: Mostrar mais horários */}
<Dialog open={!!moreTimesForDoctor} onOpenChange={(open: boolean) => { if (!open) { setMoreTimesForDoctor(null); setMoreTimesSlots([]); setMoreTimesException(null); } }}> <Dialog open={!!moreTimesForDoctor} onOpenChange={(open: boolean) => { if (!open) { setMoreTimesForDoctor(null); setMoreTimesSlots([]); setMoreTimesException(null); } }}>
<DialogContent className="w-full max-w-2xl border border-border bg-card p-6"> <DialogContent className="w-full max-w-[95vw] sm:max-w-2xl border border-border bg-card p-4 sm:p-6">
<DialogHeader className="mb-4"> <DialogHeader className="mb-4">
<DialogTitle>Mais horários</DialogTitle> <DialogTitle>Mais horários</DialogTitle>
</DialogHeader> </DialogHeader>