style(dashboard) ajuste na responsividade
This commit is contained in:
parent
77b7fdd599
commit
07c0533224
@ -602,7 +602,7 @@ export default function PacientePage() {
|
||||
|
||||
{/* Cards com Informações */}
|
||||
<div className="grid grid-cols-1 gap-3 sm:gap-4 md:gap-4 md:grid-cols-2">
|
||||
<Card className="group rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5 md:p-5 backdrop-blur-sm shadow-sm transition hover:shadow-md">
|
||||
<Card className="group rounded-2xl border border-border/60 bg-card p-4 sm:p-5 md:p-5 shadow-sm transition hover:shadow-md">
|
||||
<div className="flex h-32 sm:h-36 md:h-40 w-full flex-col items-center justify-center gap-2 sm:gap-3">
|
||||
<div className="flex h-10 w-10 sm:h-11 sm:w-11 md:h-12 md:w-12 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<Calendar className="h-5 w-5 sm:h-5 sm:w-5 md:h-6 md:w-6" aria-hidden />
|
||||
@ -616,7 +616,7 @@ export default function PacientePage() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="group rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5 md:p-5 backdrop-blur-sm shadow-sm transition hover:shadow-md">
|
||||
<Card className="group rounded-2xl border border-border/60 bg-card p-4 sm:p-5 md:p-5 shadow-sm transition hover:shadow-md">
|
||||
<div className="flex h-32 sm:h-36 md:h-40 w-full flex-col items-center justify-center gap-2 sm:gap-3">
|
||||
<div className="flex h-10 w-10 sm:h-11 sm:w-11 md:h-12 md:w-12 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<FileText className="h-5 w-5 sm:h-5 sm:w-5 md:h-6 md:w-6" aria-hidden />
|
||||
@ -1960,8 +1960,8 @@ export default function PacientePage() {
|
||||
{/* Layout com sidebar e conteúdo */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-[200px_1fr] lg:grid-cols-[220px_1fr] gap-4 sm:gap-5 md:gap-6">
|
||||
{/* Sidebar vertical - sticky */}
|
||||
<aside className="sticky top-24 h-fit md:top-24">
|
||||
<nav aria-label="Navegação do dashboard" className="bg-card shadow-md rounded-lg border border-border p-1.5 sm:p-2 md:p-3 z-30">
|
||||
<aside className="sticky top-24 h-fit md:top-24 z-40">
|
||||
<nav aria-label="Navegação do dashboard" className="relative isolate bg-card shadow-lg rounded-lg border border-border p-1.5 sm:p-2 md:p-3 z-50">
|
||||
<div className="grid grid-cols-2 md:grid-cols-1 gap-1 sm:gap-1.5">
|
||||
<Button
|
||||
variant={tab==='dashboard'?'default':'ghost'}
|
||||
|
||||
@ -33,105 +33,89 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub
|
||||
}, [dropdownOpen]);
|
||||
|
||||
return (
|
||||
<header className="h-16 border-b border-border bg-background px-6 flex items-center justify-between">
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<header className="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 px-3 sm:px-6 py-2 flex flex-wrap items-center gap-3">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<SidebarTrigger />
|
||||
<div className="flex items-start flex-col justify-center py-2">
|
||||
<h1 className="text-lg font-semibold text-foreground">{title}</h1>
|
||||
<p className="text-muted-foreground">{subtitle}</p>
|
||||
<div className="flex flex-col justify-center leading-tight min-w-0">
|
||||
<h1 className="text-sm sm:text-lg font-semibold text-foreground truncate max-w-[55vw] sm:max-w-none">{title}</h1>
|
||||
{subtitle && (
|
||||
<p className="text-[11px] sm:text-xs text-muted-foreground truncate max-w-[55vw] sm:max-w-none">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button variant="ghost" size="icon" className="hover-primary-blue">
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
<Button variant="ghost" size="icon" className="hover-primary-blue hidden xs:flex">
|
||||
<Bell className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<SimpleThemeToggle />
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-blue-500 border-blue-500 bg-transparent shadow-sm shadow-blue-500/10 border hover-primary-blue"
|
||||
asChild
|
||||
></Button>
|
||||
{/* Avatar Dropdown Simples */}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="relative h-8 w-8 rounded-full border-2 border-border hover:border-primary"
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="relative h-8 w-8 rounded-full border border-border hover:border-primary"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-label="Abrir menu do perfil"
|
||||
>
|
||||
{/* Mostrar foto do usuário quando disponível; senão, mostrar fallback com iniciais */}
|
||||
<Avatar className="h-8 w-8">
|
||||
{
|
||||
(() => {
|
||||
const userPhoto = (user as any)?.profile?.foto_url || (user as any)?.profile?.fotoUrl || (user as any)?.profile?.avatar_url
|
||||
const alt = user?.name || user?.email || 'Usuário'
|
||||
|
||||
const getInitials = (name?: string, email?: string) => {
|
||||
if (name) {
|
||||
const parts = name.trim().split(/\s+/)
|
||||
const first = parts[0]?.charAt(0) ?? ''
|
||||
const second = parts[1]?.charAt(0) ?? ''
|
||||
return (first + second).toUpperCase() || (email?.charAt(0) ?? 'U').toUpperCase()
|
||||
}
|
||||
if (email) return email.charAt(0).toUpperCase()
|
||||
return 'U'
|
||||
{(() => {
|
||||
const userPhoto = (user as any)?.profile?.foto_url || (user as any)?.profile?.fotoUrl || (user as any)?.profile?.avatar_url
|
||||
const alt = user?.name || user?.email || 'Usuário'
|
||||
const getInitials = (name?: string, email?: string) => {
|
||||
if (name) {
|
||||
const parts = name.trim().split(/\s+/)
|
||||
const first = parts[0]?.charAt(0) ?? ''
|
||||
const second = parts[1]?.charAt(0) ?? ''
|
||||
return (first + second).toUpperCase() || (email?.charAt(0) ?? 'U').toUpperCase()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AvatarImage src={userPhoto || undefined} alt={alt} />
|
||||
<AvatarFallback className="bg-primary text-primary-foreground font-semibold">{getInitials(user?.name, user?.email)}</AvatarFallback>
|
||||
</>
|
||||
)
|
||||
})()
|
||||
}
|
||||
if (email) return email.charAt(0).toUpperCase()
|
||||
return 'U'
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<AvatarImage src={userPhoto || undefined} alt={alt} />
|
||||
<AvatarFallback className="bg-primary text-primary-foreground font-semibold">{getInitials(user?.name, user?.email)}</AvatarFallback>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</Avatar>
|
||||
</Button>
|
||||
|
||||
{/* Dropdown Content */}
|
||||
{dropdownOpen && (
|
||||
<div className="absolute right-0 mt-2 w-80 bg-popover border border-border rounded-md shadow-lg z-50 text-popover-foreground">
|
||||
<div className="p-4 border-b border-border">
|
||||
<div className="absolute right-0 mt-2 w-64 sm:w-80 bg-popover border border-border rounded-md shadow-lg z-50 text-popover-foreground animate-in fade-in slide-in-from-top-2">
|
||||
<div className="p-3 sm:p-4 border-b border-border">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-semibold leading-none">
|
||||
<p className="text-xs sm:text-sm font-semibold leading-none">
|
||||
{user?.userType === 'administrador' ? 'Administrador da Clínica' : 'Usuário do Sistema'}
|
||||
</p>
|
||||
{user?.email ? (
|
||||
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
|
||||
<p className="text-[10px] sm:text-xs leading-none text-muted-foreground truncate">{user.email}</p>
|
||||
) : (
|
||||
<p className="text-xs leading-none text-muted-foreground">Email não disponível</p>
|
||||
<p className="text-[10px] sm:text-xs leading-none text-muted-foreground">Email não disponível</p>
|
||||
)}
|
||||
<p className="text-xs leading-none text-primary font-medium">
|
||||
<p className="text-[10px] sm:text-xs leading-none text-primary font-medium">
|
||||
Tipo: {user?.userType === 'administrador' ? 'Administrador' : user?.userType || 'Não definido'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<button
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDropdownOpen(false);
|
||||
router.push('/perfil');
|
||||
}}
|
||||
className="w-full text-left px-4 py-2 text-sm hover:bg-accent cursor-pointer"
|
||||
className="w-full text-left px-3 sm:px-4 py-2 text-xs sm:text-sm hover:bg-accent cursor-pointer"
|
||||
>
|
||||
Perfil
|
||||
Perfil
|
||||
</button>
|
||||
|
||||
<div className="border-t border-border my-1"></div>
|
||||
<button
|
||||
<div className="border-t border-border my-1" />
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDropdownOpen(false);
|
||||
|
||||
// Usar sempre o logout do hook useAuth (ele já redireciona corretamente)
|
||||
logout();
|
||||
}}
|
||||
className="w-full text-left px-4 py-2 text-sm text-destructive hover:bg-destructive/10 cursor-pointer"
|
||||
className="w-full text-left px-3 sm:px-4 py-2 text-xs sm:text-sm text-destructive hover:bg-destructive/10 cursor-pointer"
|
||||
>
|
||||
Sair
|
||||
Sair
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -49,6 +49,23 @@ const FileUploadChat = ({ onOpenVoice }: { onOpenVoice?: () => void }) => {
|
||||
const chatEndRef = useRef<HTMLDivElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// Placeholder responsivo (não quebra, adapta o texto)
|
||||
const [responsivePlaceholder, setResponsivePlaceholder] = useState("Pergunte qualquer coisa para a Zoe");
|
||||
|
||||
const computePlaceholder = (w: number) => {
|
||||
if (w < 340) return "Pergunte à Zoe"; // ultra pequeno
|
||||
if (w < 400) return "Pergunte algo à Zoe"; // pequeno
|
||||
if (w < 520) return "Pergunte algo para a Zoe"; // médio estreito
|
||||
return "Pergunte qualquer coisa para a Zoe"; // normal
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const update = () => setResponsivePlaceholder(computePlaceholder(window.innerWidth));
|
||||
update();
|
||||
window.addEventListener("resize", update);
|
||||
return () => window.removeEventListener("resize", update);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
chatEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages]);
|
||||
@ -511,12 +528,11 @@ const FileUploadChat = ({ onOpenVoice }: { onOpenVoice?: () => void }) => {
|
||||
|
||||
{/* Input unificado com ícones embutidos */}
|
||||
<div className="flex w-full">
|
||||
<div className={`relative flex items-center w-full rounded-full border ${themeClasses.border} ${themeClasses.inputBg} overflow-hidden h-11`}>
|
||||
{/* Botão anexar (esquerda) */}
|
||||
<div className={`flex items-center w-full rounded-full border ${themeClasses.border} ${themeClasses.inputBg} h-11 px-2 gap-2`}>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
type="button"
|
||||
className={`absolute left-2 flex items-center justify-center h-7 w-7 rounded-full transition-colors hover:bg-primary/20 ${themeClasses.text}`}
|
||||
className={`flex items-center justify-center h-7 w-7 rounded-full transition-colors hover:bg-primary/20 flex-shrink-0 ${themeClasses.text}`}
|
||||
aria-label="Anexar arquivos"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
@ -528,41 +544,33 @@ const FileUploadChat = ({ onOpenVoice }: { onOpenVoice?: () => void }) => {
|
||||
className="hidden"
|
||||
onChange={(e) => handleFileSelect(e.target.files)}
|
||||
/>
|
||||
{/* Textarea */}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Pergunte qualquer coisa para a Zoe"
|
||||
placeholder={responsivePlaceholder}
|
||||
rows={1}
|
||||
className={`pl-11 pr-24 w-full h-full bg-transparent resize-none focus:outline-none text-sm leading-snug py-3 ${themeClasses.text} placeholder-gray-400`}
|
||||
className={`flex-1 bg-transparent resize-none focus:outline-none leading-snug py-3 pr-2 ${themeClasses.text} placeholder-gray-400 text-[13px] sm:text-sm placeholder:text-[12px] sm:placeholder:text-sm whitespace-nowrap overflow-hidden text-ellipsis placeholder:overflow-hidden placeholder:text-ellipsis`}
|
||||
style={{ minHeight: 'auto', overflow: 'hidden' }}
|
||||
/>
|
||||
{/* Ícones à direita */}
|
||||
<div className="absolute right-2 flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => onOpenVoice?.()}
|
||||
type="button"
|
||||
className={`flex items-center justify-center h-8 w-8 rounded-full border ${themeClasses.border} transition-colors hover:bg-primary/20 ${themeClasses.text}`}
|
||||
aria-label="Entrada de voz"
|
||||
>
|
||||
<AudioLines className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
disabled={!inputValue.trim() && uploadedFiles.length === 0}
|
||||
type="button"
|
||||
className="flex items-center justify-center h-8 w-8 rounded-full bg-linear-to-r from-blue-500 to-purple-600 text-white hover:from-blue-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 disabled:cursor-not-allowed transition-colors shadow-md"
|
||||
aria-label="Enviar mensagem"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Contador de caracteres */}
|
||||
{inputValue.length > 0 && (
|
||||
<span className={`absolute bottom-1 right-24 text-[10px] ${themeClasses.textSecondary}`}>{inputValue.length}</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onOpenVoice?.()}
|
||||
type="button"
|
||||
className={`flex items-center justify-center h-8 w-8 rounded-full border ${themeClasses.border} transition-colors hover:bg-primary/20 flex-shrink-0 ${themeClasses.text}`}
|
||||
aria-label="Entrada de voz"
|
||||
>
|
||||
<AudioLines className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
disabled={!inputValue.trim() && uploadedFiles.length === 0}
|
||||
type="button"
|
||||
className="flex items-center justify-center h-8 w-8 rounded-full bg-linear-to-r from-blue-500 to-purple-600 text-white hover:from-blue-600 hover:to-purple-700 disabled:from-gray-400 disabled:to-gray-500 disabled:cursor-not-allowed transition-colors shadow-md flex-shrink-0"
|
||||
aria-label="Enviar mensagem"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user