feat: make all pages and components fully responsive for mobile devices

This commit is contained in:
Erik Silva
2026-01-21 00:48:25 -03:00
parent ad819e84b9
commit ede4bbfabf
6 changed files with 312 additions and 231 deletions

View File

@@ -14,7 +14,6 @@ import {
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Sidebar } from "@/components/Sidebar"; import { Sidebar } from "@/components/Sidebar";
type Organization = { type Organization = {
@@ -57,29 +56,29 @@ export default function DashboardClient({
}; };
return ( return (
<div className="min-h-screen bg-white flex"> <div className="min-h-screen bg-white flex flex-col lg:flex-row">
<Sidebar user={user} organization={organization} /> <Sidebar user={user} organization={organization} />
{/* Main Content Area */} {/* Main Content Area */}
<main className="flex-1 overflow-y-auto"> <main className="flex-1 overflow-y-auto pt-16 lg:pt-0">
{/* Top Banner / Hero - Integrated Background */} {/* Top Banner / Hero - Integrated Background */}
<div className="relative border-b border-slate-100 bg-slate-50/40 p-8 lg:p-10"> <div className="relative border-b border-slate-100 bg-slate-50/40 p-4 sm:p-6 lg:p-8 xl:p-10">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div> <div>
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="px-2 py-0.5 bg-white border border-slate-200 rounded-full text-[9px] font-bold uppercase tracking-[0.1em] text-slate-500">Overview Panel</span> <span className="px-2 py-0.5 bg-white border border-slate-200 rounded-full text-[9px] font-bold uppercase tracking-[0.1em] text-slate-500">Overview Panel</span>
</div> </div>
<h2 className="text-2xl font-black text-slate-900 tracking-tight mb-1">Dashboard</h2> <h2 className="text-xl sm:text-2xl font-black text-slate-900 tracking-tight mb-1">Dashboard</h2>
<p className="text-sm text-slate-500 font-medium"> <p className="text-sm text-slate-500 font-medium">
Bem-vindo, Administrador <span className="text-slate-900">{user.name?.split(' ')[0] || "Administrador"}</span>. Bem-vindo, <span className="text-slate-900">{user.name?.split(' ')[0] || "Administrador"}</span>.
</p> </p>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center">
<Button <Button
asChild asChild
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
className="h-10 px-6 rounded-lg font-bold text-xs shadow-none hover:opacity-90 active:scale-95 transition-all text-white" className="w-full sm:w-auto h-11 sm:h-10 px-4 sm:px-6 rounded-xl sm:rounded-lg font-bold text-xs shadow-none hover:opacity-90 active:scale-95 transition-all text-white"
> >
<Link href="/dashboard/documentos?upload=true"> <Link href="/dashboard/documentos?upload=true">
<Plus size={18} className="mr-2 stroke-[3]" /> <Plus size={18} className="mr-2 stroke-[3]" />
@@ -91,87 +90,87 @@ export default function DashboardClient({
{/* Faded accent circle */} {/* Faded accent circle */}
<div <div
className="absolute top-0 right-0 w-[300px] h-[300px] rounded-full blur-[80px] opacity-[0.03] pointer-events-none" className="absolute top-0 right-0 w-[200px] sm:w-[300px] h-[200px] sm:h-[300px] rounded-full blur-[60px] sm:blur-[80px] opacity-[0.03] pointer-events-none"
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
/> />
</div> </div>
<div className="p-8 lg:p-10 w-full"> <div className="p-4 sm:p-6 lg:p-8 xl:p-10 w-full">
{/* Stats Grid - Large and minimalist */} {/* Stats Grid - Responsive */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mb-10"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4 lg:gap-5 mb-6 lg:mb-10">
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }}> <motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }}>
<div className="group relative p-5 rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500"> <div className="group relative p-4 lg:p-5 rounded-2xl lg:rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500">
<div className="flex flex-col gap-3.5"> <div className="flex items-center gap-4 sm:flex-col sm:items-start sm:gap-3.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between w-full sm:w-auto">
<div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-blue-100 transition-colors"> <div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-blue-100 transition-colors">
<FileText size={20} className="stroke-[2]" /> <FileText size={20} className="stroke-[2]" />
</div> </div>
<div className="p-1 rounded-full bg-blue-50 text-blue-600"> <div className="hidden sm:block p-1 rounded-full bg-blue-50 text-blue-600">
<ArrowUpRight size={14} /> <ArrowUpRight size={14} />
</div> </div>
</div> </div>
<div> <div className="flex-1 sm:flex-none">
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Total de Documentos</p> <p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Total de Documentos</p>
<h3 className="text-2xl font-black text-slate-900">{stats.docCount}</h3> <h3 className="text-xl lg:text-2xl font-black text-slate-900">{stats.docCount}</h3>
</div> </div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }}> <motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }}>
<div className="group relative p-5 rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500"> <div className="group relative p-4 lg:p-5 rounded-2xl lg:rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500">
<div className="flex flex-col gap-3.5"> <div className="flex items-center gap-4 sm:flex-col sm:items-start sm:gap-3.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between w-full sm:w-auto">
<div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-green-100 transition-colors"> <div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-green-100 transition-colors">
<Eye size={20} className="stroke-[2]" /> <Eye size={20} className="stroke-[2]" />
</div> </div>
<div className="p-1 rounded-full bg-green-50 text-green-600"> <div className="hidden sm:block p-1 rounded-full bg-green-50 text-green-600">
<ArrowUpRight size={14} /> <ArrowUpRight size={14} />
</div> </div>
</div> </div>
<div> <div className="flex-1 sm:flex-none">
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Visualizações Totais</p> <p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Visualizações Totais</p>
<h3 className="text-2xl font-black text-slate-900">{stats.viewCount.toLocaleString()}</h3> <h3 className="text-xl lg:text-2xl font-black text-slate-900">{stats.viewCount.toLocaleString()}</h3>
</div> </div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }}> <motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }}>
<div className="group relative p-5 rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500"> <div className="group relative p-4 lg:p-5 rounded-2xl lg:rounded-[20px] bg-slate-50 hover:bg-white border-2 border-transparent hover:border-slate-100 transition-all duration-500">
<div className="flex flex-col gap-3.5"> <div className="flex items-center gap-4 sm:flex-col sm:items-start sm:gap-3.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between w-full sm:w-auto">
<div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-purple-100 transition-colors"> <div className="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-slate-800 border border-slate-100 group-hover:border-purple-100 transition-colors">
<TrendingUp size={20} className="stroke-[2]" /> <TrendingUp size={20} className="stroke-[2]" />
</div> </div>
<div className="p-1 rounded-full bg-purple-50 text-purple-600"> <div className="hidden sm:block p-1 rounded-full bg-purple-50 text-purple-600">
<ArrowUpRight size={14} /> <ArrowUpRight size={14} />
</div> </div>
</div> </div>
<div> <div className="flex-1 sm:flex-none">
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Downloads Totais</p> <p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-0.5">Downloads Totais</p>
<h3 className="text-2xl font-black text-slate-900">{stats.downloadCount.toLocaleString()}</h3> <h3 className="text-xl lg:text-2xl font-black text-slate-900">{stats.downloadCount.toLocaleString()}</h3>
</div> </div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-4 lg:gap-8">
{/* Recent Documents Table - More integrated */} {/* Recent Documents Table - More integrated */}
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3 }} className="lg:col-span-8"> <motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3 }} className="lg:col-span-8">
<div className="flex items-center justify-between mb-4 px-1"> <div className="flex items-center justify-between mb-3 lg:mb-4 px-1">
<h3 className="text-lg font-black text-slate-900 tracking-tight">Atividade Recente</h3> <h3 className="text-base lg:text-lg font-black text-slate-900 tracking-tight">Atividade Recente</h3>
<Link href="/dashboard/documentos" className="group flex items-center gap-2 text-[9px] font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors"> <Link href="/dashboard/documentos" className="group flex items-center gap-2 text-[9px] font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors">
Ver Todos <ArrowRight size={12} className="group-hover:translate-x-0.5 transition-transform" /> Ver Todos <ArrowRight size={12} className="group-hover:translate-x-0.5 transition-transform" />
</Link> </Link>
</div> </div>
<div className="bg-white rounded-[24px] border-2 border-slate-100 overflow-hidden"> <div className="bg-white rounded-2xl lg:rounded-[24px] border-2 border-slate-100 overflow-hidden">
{stats.recentDocs.length === 0 ? ( {stats.recentDocs.length === 0 ? (
<div className="p-14 text-center"> <div className="p-10 lg:p-14 text-center">
<div className="w-14 h-14 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-3"> <div className="w-12 h-12 lg:w-14 lg:h-14 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-3">
<FileText size={24} className="text-slate-300" /> <FileText size={20} className="text-slate-300" />
</div> </div>
<p className="text-slate-500 font-medium mb-4 text-sm">Nenhum documento encontrado.</p> <p className="text-slate-500 font-medium mb-4 text-sm">Nenhum documento encontrado.</p>
<Button asChild variant="outline" className="rounded-lg border-2 font-bold px-6 h-9 text-[10px] uppercase tracking-widest"> <Button asChild variant="outline" className="rounded-lg border-2 font-bold px-6 h-9 text-[10px] uppercase tracking-widest">
@@ -184,25 +183,26 @@ export default function DashboardClient({
<Link <Link
key={doc.id} key={doc.id}
href={`/dashboard/documentos/${doc.id}`} href={`/dashboard/documentos/${doc.id}`}
className="flex items-center gap-4 py-3.5 px-6 hover:bg-slate-50/50 transition-all group" className="flex items-center gap-3 lg:gap-4 py-3 lg:py-3.5 px-4 lg:px-6 hover:bg-slate-50/50 transition-all group"
> >
<div className="w-10 h-10 rounded-lg bg-slate-50 group-hover:bg-white border-2 border-transparent group-hover:border-slate-100 flex items-center justify-center transition-all shrink-0"> <div className="w-9 h-9 lg:w-10 lg:h-10 rounded-lg bg-slate-50 group-hover:bg-white border-2 border-transparent group-hover:border-slate-100 flex items-center justify-center transition-all shrink-0">
<FileText size={18} className="text-slate-500 group-hover:text-slate-900 transition-colors" /> <FileText size={16} className="text-slate-500 group-hover:text-slate-900 transition-colors" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h4 className="font-black text-slate-900 group-hover:text-blue-600 transition-colors truncate mb-0.5 text-sm">{doc.title}</h4> <h4 className="font-black text-slate-900 group-hover:text-blue-600 transition-colors truncate mb-0.5 text-sm">{doc.title}</h4>
<div className="flex items-center gap-3 text-[9px] font-bold text-slate-400 uppercase tracking-widest"> <div className="flex items-center gap-2 lg:gap-3 text-[9px] font-bold text-slate-400 uppercase tracking-widest">
<span>{doc.folder?.name || "Sem categoria"}</span> <span className="truncate max-w-[80px] sm:max-w-none">{doc.folder?.name || "Sem categoria"}</span>
<span className="w-1 h-1 rounded-full bg-slate-200" /> <span className="w-1 h-1 rounded-full bg-slate-200 shrink-0" />
<span>{formatDate(doc.createdAt)}</span> <span>{formatDate(doc.createdAt)}</span>
</div> </div>
</div> </div>
<div className="text-right shrink-0"> <div className="hidden sm:flex text-right shrink-0">
<div className="flex items-center gap-2 text-slate-400 group-hover:text-slate-900 transition-colors"> <div className="flex items-center gap-2 text-slate-400 group-hover:text-slate-900 transition-colors">
<span className="text-[10px] font-bold uppercase tracking-widest">Detalhes</span> <span className="text-[10px] font-bold uppercase tracking-widest">Detalhes</span>
<ChevronRight size={14} className="stroke-[3]" /> <ChevronRight size={14} className="stroke-[3]" />
</div> </div>
</div> </div>
<ChevronRight size={18} className="sm:hidden text-slate-300 shrink-0" />
</Link> </Link>
))} ))}
</div> </div>
@@ -211,29 +211,29 @@ export default function DashboardClient({
</motion.div> </motion.div>
{/* Summary Column */} {/* Summary Column */}
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: 0.4 }} className="lg:col-span-4 space-y-6"> <motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: 0.4 }} className="lg:col-span-4 space-y-4 lg:space-y-6">
<div className="bg-slate-900 rounded-[24px] p-6 text-white relative overflow-hidden group"> <div className="bg-slate-900 rounded-2xl lg:rounded-[24px] p-5 lg:p-6 text-white relative overflow-hidden group">
<div className="relative z-10"> <div className="relative z-10">
<div className="w-10 h-10 bg-white/10 rounded-lg flex items-center justify-center mb-4"> <div className="w-9 h-9 lg:w-10 lg:h-10 bg-white/10 rounded-lg flex items-center justify-center mb-3 lg:mb-4">
<Calendar size={20} className="text-white" /> <Calendar size={18} className="text-white" />
</div> </div>
<h4 className="text-xl font-black tracking-tight mb-1">Status do Portal</h4> <h4 className="text-lg lg:text-xl font-black tracking-tight mb-1">Status do Portal</h4>
<p className="text-white/60 text-xs font-medium leading-relaxed mb-6"> <p className="text-white/60 text-xs font-medium leading-relaxed mb-4 lg:mb-6">
O portal está online e sincronizado com os últimos envios de documentos. O portal está online e sincronizado com os últimos envios de documentos.
</p> </p>
<Button variant="ghost" className="w-full bg-white/10 hover:bg-white/20 text-white font-bold text-[10px] uppercase tracking-widest h-10 rounded-lg border-none"> <Button variant="ghost" className="w-full bg-white/10 hover:bg-white/20 text-white font-bold text-[10px] uppercase tracking-widest h-10 rounded-lg border-none">
Ver Log de Atividades Ver Log de Atividades
</Button> </Button>
</div> </div>
<div className="absolute -right-4 -bottom-4 w-32 h-32 bg-white/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" /> <div className="absolute -right-4 -bottom-4 w-24 lg:w-32 h-24 lg:h-32 bg-white/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" />
</div> </div>
<div className="bg-white border-2 border-slate-100 rounded-[24px] p-6"> <div className="bg-white border-2 border-slate-100 rounded-2xl lg:rounded-[24px] p-5 lg:p-6">
<h4 className="text-base font-black text-slate-900 tracking-tight mb-4">Informações Rápidas</h4> <h4 className="text-sm lg:text-base font-black text-slate-900 tracking-tight mb-3 lg:mb-4">Informações Rápidas</h4>
<div className="space-y-4"> <div className="space-y-3 lg:space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Organização</span> <span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Organização</span>
<span className="text-xs font-black text-slate-900">{organization.name}</span> <span className="text-xs font-black text-slate-900 truncate max-w-[120px]">{organization.name}</span>
</div> </div>
<div className="h-px bg-slate-100" /> <div className="h-px bg-slate-100" />
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -254,25 +254,3 @@ export default function DashboardClient({
</div> </div>
); );
} }
function Users(props: any) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
)
}

View File

@@ -523,11 +523,11 @@ export default function DocumentsClient({
}; };
return ( return (
<div className="min-h-screen bg-[#f9fafb] flex"> <div className="min-h-screen bg-[#f9fafb] flex flex-col lg:flex-row">
<Sidebar user={user} organization={organization} /> <Sidebar user={user} organization={organization} />
<main <main
className="flex-1 overflow-y-auto flex flex-col relative" className="flex-1 overflow-y-auto flex flex-col relative pt-16 lg:pt-0"
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onDrop={handleDrop} onDrop={handleDrop}
@@ -575,20 +575,20 @@ export default function DocumentsClient({
</AnimatePresence> </AnimatePresence>
{/* Header Section - Integrated */} {/* Header Section - Integrated */}
<div className="relative bg-white p-8 lg:p-10"> <div className="relative bg-white p-4 sm:p-6 lg:p-8 xl:p-10">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between lg:gap-6">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{/* Breadcrumbs */} {/* Breadcrumbs */}
<nav className="flex items-center gap-1.5 mb-3 flex-wrap"> <nav className="flex items-center gap-1.5 mb-3 flex-wrap overflow-x-auto">
<Link <Link
href="/dashboard/documentos" href="/dashboard/documentos"
className="p-1.5 rounded-lg hover:bg-slate-100 border border-transparent hover:border-slate-200 transition-all text-slate-400 hover:text-slate-900" className="p-1.5 rounded-lg hover:bg-slate-100 border border-transparent hover:border-slate-200 transition-all text-slate-400 hover:text-slate-900 shrink-0"
> >
<Home size={14} /> <Home size={14} />
</Link> </Link>
{breadcrumb.length === 0 ? ( {breadcrumb.length === 0 ? (
<> <>
<ChevronRight size={10} className="text-slate-300" /> <ChevronRight size={10} className="text-slate-300 shrink-0" />
<span className="px-2 py-0.5 bg-slate-100 border border-slate-200 rounded-md text-[9px] font-bold uppercase tracking-widest text-slate-600"> <span className="px-2 py-0.5 bg-slate-100 border border-slate-200 rounded-md text-[9px] font-bold uppercase tracking-widest text-slate-600">
Raiz do Portal Raiz do Portal
</span> </span>
@@ -596,15 +596,15 @@ export default function DocumentsClient({
) : ( ) : (
breadcrumb.map((folder, index) => ( breadcrumb.map((folder, index) => (
<React.Fragment key={folder.id}> <React.Fragment key={folder.id}>
<ChevronRight size={10} className="text-slate-300" /> <ChevronRight size={10} className="text-slate-300 shrink-0" />
{index === breadcrumb.length - 1 ? ( {index === breadcrumb.length - 1 ? (
<span className="px-2 py-0.5 bg-slate-900 text-white rounded-md text-[9px] font-bold uppercase tracking-widest"> <span className="px-2 py-0.5 bg-slate-900 text-white rounded-md text-[9px] font-bold uppercase tracking-widest truncate max-w-[120px] sm:max-w-none">
{folder.name} {folder.name}
</span> </span>
) : ( ) : (
<Link <Link
href={`/dashboard/documentos?folder=${folder.id}`} href={`/dashboard/documentos?folder=${folder.id}`}
className="px-2 py-0.5 bg-slate-100 hover:bg-slate-200 border border-slate-200 rounded-md text-[9px] font-bold uppercase tracking-widest text-slate-600 hover:text-slate-900 transition-all" className="px-2 py-0.5 bg-slate-100 hover:bg-slate-200 border border-slate-200 rounded-md text-[9px] font-bold uppercase tracking-widest text-slate-600 hover:text-slate-900 transition-all truncate max-w-[80px] sm:max-w-none"
> >
{folder.name} {folder.name}
</Link> </Link>
@@ -615,33 +615,33 @@ export default function DocumentsClient({
</nav> </nav>
{/* Título com imagem da pasta se existir */} {/* Título com imagem da pasta se existir */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-3 sm:gap-4">
{currentFolder?.imageUrl ? ( {currentFolder?.imageUrl ? (
<div className="w-12 h-12 rounded-xl overflow-hidden shrink-0 border border-slate-200"> <div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl overflow-hidden shrink-0 border border-slate-200">
<img src={currentFolder.imageUrl} alt={currentFolder.name} className="w-full h-full object-cover" /> <img src={currentFolder.imageUrl} alt={currentFolder.name} className="w-full h-full object-cover" />
</div> </div>
) : currentFolder ? ( ) : currentFolder ? (
<div className="w-12 h-12 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: currentFolder.color + '20', color: currentFolder.color }}> <div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: currentFolder.color + '20', color: currentFolder.color }}>
<FolderOpen size={24} fill="currentColor" fillOpacity={0.3} strokeWidth={2.5} /> <FolderOpen size={20} fill="currentColor" fillOpacity={0.3} strokeWidth={2.5} />
</div> </div>
) : null} ) : null}
<div> <div className="min-w-0">
<h2 className="text-2xl font-black text-slate-900 tracking-tight mb-1"> <h2 className="text-xl sm:text-2xl font-black text-slate-900 tracking-tight mb-1 truncate">
{currentFolder ? currentFolder.name : "Documentos"} {currentFolder ? currentFolder.name : "Documentos"}
</h2> </h2>
<p className="text-sm text-slate-500 font-medium"> <p className="text-xs sm:text-sm text-slate-500 font-medium hidden sm:block">
{currentFolder ? "Conteúdo desta pasta" : "Gestão de arquivos e transparência governamental."} {currentFolder ? "Conteúdo desta pasta" : "Gestão de arquivos e transparência."}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0 flex-wrap">
{/* Botão Editar Pasta */} {/* Botão Editar Pasta */}
{currentFolder && ( {currentFolder && (
<button <button
onClick={handleOpenEditFolder} onClick={handleOpenEditFolder}
className="h-9 w-9 rounded-lg bg-slate-100 text-slate-600 border-2 border-slate-200 hover:bg-slate-200 transition-all flex items-center justify-center" className="h-10 w-10 sm:h-9 sm:w-9 rounded-lg bg-slate-100 text-slate-600 border-2 border-slate-200 hover:bg-slate-200 transition-all flex items-center justify-center"
title="Editar pasta" title="Editar pasta"
> >
<Pencil size={14} /> <Pencil size={14} />
@@ -651,22 +651,22 @@ export default function DocumentsClient({
{currentFolder && ( {currentFolder && (
<button <button
onClick={() => handleShare({ ...currentFolder, isFolder: true })} onClick={() => handleShare({ ...currentFolder, isFolder: true })}
className={`h-9 px-4 rounded-lg font-bold text-[10px] uppercase tracking-widest transition-all flex items-center gap-2 ${currentFolder.isPublished className={`h-10 sm:h-9 px-3 sm:px-4 rounded-lg font-bold text-[10px] uppercase tracking-widest transition-all flex items-center gap-2 ${currentFolder.isPublished
? "bg-green-100 text-green-700 border-2 border-green-200 hover:bg-green-200" ? "bg-green-100 text-green-700 border-2 border-green-200 hover:bg-green-200"
: "bg-slate-100 text-slate-600 border-2 border-slate-200 hover:bg-slate-200" : "bg-slate-100 text-slate-600 border-2 border-slate-200 hover:bg-slate-200"
}`} }`}
> >
<Share2 size={14} /> <Share2 size={14} />
{currentFolder.isPublished ? "Pública" : "Privada"} <span className="hidden sm:inline">{currentFolder.isPublished ? "Pública" : "Privada"}</span>
</button> </button>
)} )}
<Button <Button
variant="outline" variant="outline"
onClick={() => setShowNewFolder(true)} onClick={() => setShowNewFolder(true)}
className="h-9 px-4 rounded-lg border-2 font-bold text-[10px] uppercase tracking-widest text-slate-700 hover:bg-white hover:border-slate-300 transition-all shadow-none" className="h-10 sm:h-9 px-3 sm:px-4 rounded-lg border-2 font-bold text-[10px] uppercase tracking-widest text-slate-700 hover:bg-white hover:border-slate-300 transition-all shadow-none"
> >
<FolderPlus size={16} className="mr-2 stroke-[2.5]" /> <FolderPlus size={16} className="sm:mr-2 stroke-[2.5]" />
Nova Pasta <span className="hidden sm:inline">Nova Pasta</span>
</Button> </Button>
<Button <Button
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
@@ -674,28 +674,28 @@ export default function DocumentsClient({
setUpFolderId(currentFolderId || "__none__"); setUpFolderId(currentFolderId || "__none__");
setShowUploadDialog(true); setShowUploadDialog(true);
}} }}
className="h-9 px-4 rounded-lg font-bold text-[10px] uppercase tracking-widest shadow-none hover:opacity-90 active:scale-95 transition-all text-white" className="h-10 sm:h-9 px-3 sm:px-4 rounded-lg font-bold text-[10px] uppercase tracking-widest shadow-none hover:opacity-90 active:scale-95 transition-all text-white"
> >
<Plus size={16} className="mr-2 stroke-[3]" /> <Plus size={16} className="sm:mr-2 stroke-[3]" />
Fazer Upload <span className="hidden sm:inline">Fazer Upload</span>
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
{/* Toolbar: Search & Filters */} {/* Toolbar: Search & Filters */}
<div className="sticky top-0 z-10 bg-[#f9fafb] px-8 pt-6 pb-4"> <div className="sticky top-16 lg:top-0 z-10 bg-[#f9fafb] px-4 sm:px-6 lg:px-8 pt-4 lg:pt-6 pb-3 lg:pb-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-2 sm:gap-4">
{/* Search - Compact */} {/* Search - Compact */}
<div className="relative w-full max-w-sm group"> <div className="relative flex-1 max-w-sm group">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 sm:pl-4 flex items-center pointer-events-none">
<Search className="text-slate-400 group-focus-within:text-red-500 transition-colors" size={18} /> <Search className="text-slate-400 group-focus-within:text-red-500 transition-colors" size={16} />
</div> </div>
<Input <Input
placeholder="Buscar pastas ou arquivos..." placeholder="Buscar..."
value={searchTerm} value={searchTerm}
onChange={(e) => { setSearchTerm(e.target.value); setCurrentPage(1); }} onChange={(e) => { setSearchTerm(e.target.value); setCurrentPage(1); }}
className="h-12 pl-12 pr-4 bg-white border-slate-200 rounded-xl text-sm font-medium focus:ring-0 focus:border-slate-300 transition-all" className="h-10 sm:h-12 pl-9 sm:pl-12 pr-4 bg-white border-slate-200 rounded-xl text-sm font-medium focus:ring-0 focus:border-slate-300 transition-all"
/> />
</div> </div>
@@ -703,18 +703,18 @@ export default function DocumentsClient({
<Button <Button
variant="outline" variant="outline"
onClick={() => setShowFilterPanel(true)} onClick={() => setShowFilterPanel(true)}
className={`h-12 px-5 rounded-xl border-2 font-bold text-xs uppercase tracking-wider transition-all ${selectedYear !== 'all' ? 'bg-blue-50 border-blue-200 text-blue-600' : 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'}`} className={`h-10 sm:h-12 px-3 sm:px-5 rounded-xl border-2 font-bold text-xs uppercase tracking-wider transition-all ${selectedYear !== 'all' ? 'bg-blue-50 border-blue-200 text-blue-600' : 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'}`}
> >
<Filter size={16} className="mr-2" /> <Filter size={16} className="sm:mr-2" />
Filtros <span className="hidden sm:inline">Filtros</span>
{selectedYear !== 'all' && ( {selectedYear !== 'all' && (
<span className="ml-2 w-5 h-5 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[9px] font-black">1</span> <span className="ml-1 sm:ml-2 w-5 h-5 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-[9px] font-black">1</span>
)} )}
</Button> </Button>
</div> </div>
</div> </div>
<div className="p-8 lg:p-10 w-full"> <div className="p-4 sm:p-6 lg:p-8 xl:p-10 w-full">
{/* Unified Explorer using StandardTable */} {/* Unified Explorer using StandardTable */}
<StandardTable <StandardTable
hideSearch={true} hideSearch={true}

View File

@@ -94,10 +94,10 @@ export default function SetupClient() {
const prevStep = () => setStep((s) => Math.max(s - 1, 1)); const prevStep = () => setStep((s) => Math.max(s - 1, 1));
return ( return (
<div className="min-h-screen bg-[#f8fafc] flex items-center justify-center p-6 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-50 via-slate-50 to-white font-sans"> <div className="min-h-screen bg-[#f8fafc] flex items-center justify-center p-4 sm:p-6 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-50 via-slate-50 to-white font-sans">
<div className="max-w-4xl w-full"> <div className="max-w-4xl w-full">
{/* Header Section */} {/* Header Section */}
<div className="text-center mb-10"> <div className="text-center mb-6 sm:mb-10">
<motion.div <motion.div
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
@@ -111,7 +111,7 @@ export default function SetupClient() {
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
className="text-4xl font-bold text-slate-900 tracking-tight mb-2" className="text-2xl sm:text-3xl lg:text-4xl font-bold text-slate-900 tracking-tight mb-2"
> >
Configuração Master Configuração Master
</motion.h1> </motion.h1>
@@ -119,28 +119,28 @@ export default function SetupClient() {
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="text-slate-500" className="text-slate-500 text-sm sm:text-base px-2"
> >
Este portal está cru. Vamos realizar o setup inicial da sua organização. Setup inicial da sua organização.
</motion.p> </motion.p>
</div> </div>
{/* Progress Bar */} {/* Progress Bar */}
<div className="mb-12 relative"> <div className="mb-8 sm:mb-12 relative px-4">
<div className="flex justify-between items-center max-w-2xl mx-auto relative z-10"> <div className="flex justify-between items-center max-w-xs sm:max-w-2xl mx-auto relative z-10">
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<div <div
key={i} key={i}
className={`w-10 h-10 rounded-full flex items-center justify-center transition-all duration-500 ${step >= i ? "bg-blue-600 text-white shadow-lg shadow-blue-200" : "bg-white text-slate-400 border border-slate-200" className={`w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center transition-all duration-500 text-sm sm:text-base ${step >= i ? "bg-blue-600 text-white shadow-lg shadow-blue-200" : "bg-white text-slate-400 border border-slate-200"
}`} }`}
> >
{step > i ? <CheckCircle2 size={24} /> : <span>{i}</span>} {step > i ? <CheckCircle2 size={20} /> : <span>{i}</span>}
</div> </div>
))} ))}
</div> </div>
<div className="absolute top-1/2 left-0 w-full h-0.5 bg-slate-200 -translate-y-1/2 max-w-2xl mx-auto right-0" /> <div className="absolute top-1/2 left-4 right-4 h-0.5 bg-slate-200 -translate-y-1/2 max-w-xs sm:max-w-2xl mx-auto" />
<motion.div <motion.div
className="absolute top-1/2 left-0 h-0.5 bg-blue-600 -translate-y-1/2 max-w-2xl mx-auto right-0 origin-left" className="absolute top-1/2 left-4 right-4 h-0.5 bg-blue-600 -translate-y-1/2 max-w-xs sm:max-w-2xl mx-auto origin-left"
initial={{ scaleX: 0 }} initial={{ scaleX: 0 }}
animate={{ scaleX: (step - 1) / (totalSteps - 1) }} animate={{ scaleX: (step - 1) / (totalSteps - 1) }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
@@ -149,7 +149,7 @@ export default function SetupClient() {
{/* Form Card */} {/* Form Card */}
<Card className="border-0 shadow-xl bg-white/80 backdrop-blur-md"> <Card className="border-0 shadow-xl bg-white/80 backdrop-blur-md">
<CardContent className="p-8 md:p-12 min-h-[500px] flex flex-col justify-between"> <CardContent className="p-4 sm:p-8 md:p-12 min-h-[450px] sm:min-h-[500px] flex flex-col justify-between">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{step === 1 && ( {step === 1 && (
<motion.div <motion.div
@@ -159,13 +159,14 @@ export default function SetupClient() {
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-8"
> >
<div className="flex items-center gap-4 mb-8"> <div className="flex items-start sm:items-center gap-3 sm:gap-4 mb-6 sm:mb-8">
<div className="p-3 bg-blue-50 text-blue-600 rounded-2xl"> <div className="p-2 sm:p-3 bg-blue-50 text-blue-600 rounded-xl sm:rounded-2xl shrink-0">
<Building2 size={32} /> <Building2 size={24} className="sm:hidden" />
<Building2 size={32} className="hidden sm:block" />
</div> </div>
<div> <div>
<h2 className="text-2xl font-semibold text-slate-800">Identidade da Organização</h2> <h2 className="text-lg sm:text-2xl font-semibold text-slate-800">Identidade da Organização</h2>
<p className="text-slate-500">Defina o nome oficial e a logo que aparecerá no portal.</p> <p className="text-slate-500 text-sm hidden sm:block">Defina o nome oficial e a logo que aparecerá no portal.</p>
</div> </div>
</div> </div>
@@ -234,13 +235,14 @@ export default function SetupClient() {
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-8"
> >
<div className="flex items-center gap-4 mb-8"> <div className="flex items-start sm:items-center gap-3 sm:gap-4 mb-6 sm:mb-8">
<div className="p-3 bg-indigo-50 text-indigo-600 rounded-2xl"> <div className="p-2 sm:p-3 bg-indigo-50 text-indigo-600 rounded-xl sm:rounded-2xl shrink-0">
<Palette size={32} /> <Palette size={24} className="sm:hidden" />
<Palette size={32} className="hidden sm:block" />
</div> </div>
<div> <div>
<h2 className="text-2xl font-semibold text-slate-800">Personalização Visual</h2> <h2 className="text-lg sm:text-2xl font-semibold text-slate-800">Personalização Visual</h2>
<p className="text-slate-500">Adapte o portal às cores da sua marca.</p> <p className="text-slate-500 text-sm hidden sm:block">Adapte o portal às cores da sua marca.</p>
</div> </div>
</div> </div>
@@ -294,13 +296,14 @@ export default function SetupClient() {
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-8"
> >
<div className="flex items-center gap-4 mb-8"> <div className="flex items-start sm:items-center gap-3 sm:gap-4 mb-6 sm:mb-8">
<div className="p-3 bg-emerald-50 text-emerald-600 rounded-2xl"> <div className="p-2 sm:p-3 bg-emerald-50 text-emerald-600 rounded-xl sm:rounded-2xl shrink-0">
<User size={32} /> <User size={24} className="sm:hidden" />
<User size={32} className="hidden sm:block" />
</div> </div>
<div> <div>
<h2 className="text-2xl font-semibold text-slate-800">Administrador Master</h2> <h2 className="text-lg sm:text-2xl font-semibold text-slate-800">Administrador Master</h2>
<p className="text-slate-500">Crie a conta que terá controle total sobre o portal.</p> <p className="text-slate-500 text-sm hidden sm:block">Crie a conta que terá controle total sobre o portal.</p>
</div> </div>
</div> </div>
@@ -365,30 +368,32 @@ export default function SetupClient() {
)} )}
</AnimatePresence> </AnimatePresence>
<div className="flex justify-between items-center mt-12 pt-8 border-t border-slate-100"> <div className="flex justify-between items-center mt-8 sm:mt-12 pt-6 sm:pt-8 border-t border-slate-100">
<Button <Button
variant="ghost" variant="ghost"
onClick={prevStep} onClick={prevStep}
disabled={isSubmitting} disabled={isSubmitting}
className={step === 1 ? "invisible" : ""} className={`h-10 sm:h-11 ${step === 1 ? "invisible" : ""}`}
> >
<ArrowLeft size={18} className="mr-2" /> <ArrowLeft size={18} className="mr-1 sm:mr-2" />
Voltar <span className="hidden sm:inline">Voltar</span>
</Button> </Button>
<Button <Button
onClick={nextStep} onClick={nextStep}
disabled={isSubmitting || isUploading} disabled={isSubmitting || isUploading}
className="h-12 px-6" className="h-10 sm:h-12 px-4 sm:px-6"
> >
{isSubmitting ? ( {isSubmitting ? (
<> <>
<Loader2 size={18} className="animate-spin mr-2" /> <Loader2 size={18} className="animate-spin mr-2" />
Finalizando... <span className="hidden sm:inline">Finalizando...</span>
<span className="sm:hidden">...</span>
</> </>
) : ( ) : (
<> <>
{step === totalSteps ? "Finalizar Instalação" : "Próximo Passo"} <span className="hidden sm:inline">{step === totalSteps ? "Finalizar Instalação" : "Próximo Passo"}</span>
<ArrowRight size={18} className="ml-2" /> <span className="sm:hidden">{step === totalSteps ? "Finalizar" : "Próximo"}</span>
<ArrowRight size={18} className="ml-1 sm:ml-2" />
</> </>
)} )}
</Button> </Button>

View File

@@ -100,49 +100,50 @@ export default function FolderViewClient({
<div className="min-h-screen bg-[#f9fafb] selection:bg-red-100 selection:text-red-900 flex flex-col"> <div className="min-h-screen bg-[#f9fafb] selection:bg-red-100 selection:text-red-900 flex flex-col">
{/* Header */} {/* Header */}
<header className="bg-white border-b border-slate-100 sticky top-0 z-10"> <header className="bg-white border-b border-slate-100 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-8 h-20 flex items-center justify-between"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 sm:h-20 flex items-center justify-between">
<div className="flex items-center gap-4"> <div className="flex items-center gap-3 sm:gap-4 min-w-0">
{organization.logoUrl ? ( {organization.logoUrl ? (
<img src={organization.logoUrl} alt={organization.name} className="h-10 object-contain" /> <img src={organization.logoUrl} alt={organization.name} className="h-8 sm:h-10 object-contain shrink-0" />
) : ( ) : (
<div className="w-10 h-10 rounded-xl bg-slate-100 flex items-center justify-center font-bold text-slate-800"> <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg sm:rounded-xl bg-slate-100 flex items-center justify-center font-bold text-slate-800 shrink-0 text-sm">
{organization.name[0]} {organization.name[0]}
</div> </div>
)} )}
<div> <div className="min-w-0">
<h1 className="text-sm font-black text-slate-900 uppercase tracking-tight">{organization.name}</h1> <h1 className="text-xs sm:text-sm font-black text-slate-900 uppercase tracking-tight truncate">{organization.name}</h1>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Portal da Transparência</p> <p className="text-[9px] sm:text-[10px] font-bold text-slate-400 uppercase tracking-widest hidden sm:block">Portal da Transparência</p>
</div> </div>
</div> </div>
<Badge variant="outline" className="border-green-200 bg-green-50 text-green-600 font-bold text-[10px] uppercase py-1 px-3"> <Badge variant="outline" className="border-green-200 bg-green-50 text-green-600 font-bold text-[9px] sm:text-[10px] uppercase py-1 px-2 sm:px-3 shrink-0">
Ambiente Seguro <span className="hidden sm:inline">Ambiente </span>Seguro
</Badge> </Badge>
</div> </div>
</header> </header>
<main className="w-full flex-1"> <main className="w-full flex-1">
<div className="w-full max-w-7xl mx-auto px-8 py-12"> <div className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8 lg:py-12">
{/* Folder Info / Project Header */} {/* Folder Info / Project Header */}
<div className="mb-8"> <div className="mb-6 sm:mb-8">
<div className="flex items-center gap-5 mb-6"> <div className="flex items-start sm:items-center gap-3 sm:gap-5 mb-4 sm:mb-6">
{displayFolder.imageUrl ? ( {displayFolder.imageUrl ? (
<div className="w-20 h-20 rounded-2xl overflow-hidden shrink-0 border border-slate-100"> <div className="w-14 h-14 sm:w-20 sm:h-20 rounded-xl sm:rounded-2xl overflow-hidden shrink-0 border border-slate-100">
<img src={displayFolder.imageUrl} alt={displayFolder.name} className="w-full h-full object-cover" /> <img src={displayFolder.imageUrl} alt={displayFolder.name} className="w-full h-full object-cover" />
</div> </div>
) : ( ) : (
<div <div
className="w-16 h-16 rounded-2xl flex items-center justify-center shrink-0" className="w-12 h-12 sm:w-16 sm:h-16 rounded-xl sm:rounded-2xl flex items-center justify-center shrink-0"
style={{ backgroundColor: `${displayFolder.color}15`, color: displayFolder.color }} style={{ backgroundColor: `${displayFolder.color}15`, color: displayFolder.color }}
> >
<FolderOpen size={32} fill="currentColor" fillOpacity={0.2} strokeWidth={2} /> <FolderOpen size={24} className="sm:hidden" fill="currentColor" fillOpacity={0.2} strokeWidth={2} />
<FolderOpen size={32} className="hidden sm:block" fill="currentColor" fillOpacity={0.2} strokeWidth={2} />
</div> </div>
)} )}
<div> <div className="min-w-0">
<h2 className="text-3xl font-black text-slate-900 uppercase tracking-tighter leading-tight mb-1"> <h2 className="text-xl sm:text-2xl lg:text-3xl font-black text-slate-900 uppercase tracking-tighter leading-tight mb-1 truncate">
{displayFolder.name} {displayFolder.name}
</h2> </h2>
<p className="text-sm font-medium text-slate-500"> <p className="text-xs sm:text-sm font-medium text-slate-500 line-clamp-2">
{displayFolder.description || "Pasta pública contendo documentos oficiais e informativos."} {displayFolder.description || "Pasta pública contendo documentos oficiais."}
</p> </p>
</div> </div>
</div> </div>
@@ -168,24 +169,24 @@ export default function FolderViewClient({
</div> </div>
{/* Search & Stats */} {/* Search & Stats */}
<div className="flex flex-col md:flex-row justify-between items-center gap-6 mb-8 mt-4"> <div className="flex flex-col sm:flex-row justify-between items-stretch sm:items-center gap-3 sm:gap-6 mb-6 sm:mb-8 mt-4">
<div className="relative w-full md:w-96 group"> <div className="relative flex-1 max-w-full sm:max-w-sm group">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 sm:pl-4 flex items-center pointer-events-none">
<Search className="text-slate-400 group-focus-within:text-red-500 transition-colors" size={18} /> <Search className="text-slate-400 group-focus-within:text-red-500 transition-colors" size={16} />
</div> </div>
<Input <Input
placeholder="Buscar documentos nesta pasta..." placeholder="Buscar documentos..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="h-12 pl-12 pr-4 bg-white border-slate-200 rounded-xl text-sm font-medium focus:ring-0 focus:border-slate-300 transition-all shadow-none" className="h-10 sm:h-12 pl-10 sm:pl-12 pr-4 bg-white border-slate-200 rounded-xl text-sm font-medium focus:ring-0 focus:border-slate-300 transition-all shadow-none"
/> />
</div> </div>
<div className="flex items-center gap-3 bg-white px-5 py-2.5 rounded-xl border border-slate-100"> <div className="flex items-center gap-3 bg-white px-4 sm:px-5 py-2 sm:py-2.5 rounded-xl border border-slate-100 self-start">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Itens</span> <span className="text-[9px] sm:text-[10px] font-bold text-slate-400 uppercase tracking-widest">Itens</span>
<span className="text-sm font-black text-slate-900">{filteredChildren.length + filteredDocs.length}</span> <span className="text-sm font-black text-slate-900">{filteredChildren.length + filteredDocs.length}</span>
<div className="w-[1px] h-5 bg-slate-100 mx-1" /> <div className="w-[1px] h-5 bg-slate-100 mx-1" />
<ShieldCheck className="text-green-500" size={18} /> <ShieldCheck className="text-green-500" size={16} />
</div> </div>
</div> </div>
@@ -255,12 +256,12 @@ export default function FolderViewClient({
</div> </div>
</div> </div>
<div className="flex items-center gap-2 relative z-10"> <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 relative z-10">
<Button <Button
asChild asChild
variant="outline" variant="outline"
size="sm" size="sm"
className="h-10 border-slate-200 hover:bg-slate-50 text-slate-600 font-bold text-[10px] uppercase rounded-lg px-4 shadow-none" className="h-9 sm:h-10 border-slate-200 hover:bg-slate-50 text-slate-600 font-bold text-[10px] uppercase rounded-lg px-3 sm:px-4 shadow-none justify-center"
> >
<Link href={`/documento/${doc.id}`} target="_blank"> <Link href={`/documento/${doc.id}`} target="_blank">
<ExternalLink size={14} className="mr-2" /> <ExternalLink size={14} className="mr-2" />
@@ -272,7 +273,7 @@ export default function FolderViewClient({
asChild asChild
variant="default" variant="default"
size="sm" size="sm"
className="h-10 bg-slate-900 border-none hover:bg-red-600 text-white font-bold text-[10px] uppercase rounded-lg px-4 transition-all shadow-none" className="h-9 sm:h-10 bg-slate-900 border-none hover:bg-red-600 text-white font-bold text-[10px] uppercase rounded-lg px-3 sm:px-4 transition-all shadow-none justify-center"
> >
<a href={`/api/view/${doc.id}`} download={doc.fileName}> <a href={`/api/view/${doc.id}`} download={doc.fileName}>
<Download size={14} className="mr-2" /> <Download size={14} className="mr-2" />
@@ -301,19 +302,19 @@ export default function FolderViewClient({
{/* Footer - Apenas logo da organização */} {/* Footer - Apenas logo da organização */}
<footer className="bg-white border-t border-slate-100 mt-auto"> <footer className="bg-white border-t border-slate-100 mt-auto">
<div className="max-w-7xl mx-auto px-8 h-20 flex items-center justify-center"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 sm:h-20 flex items-center justify-center">
{organization.logoUrl ? ( {organization.logoUrl ? (
<img <img
src={organization.logoUrl} src={organization.logoUrl}
alt={organization.name} alt={organization.name}
className="h-10 object-contain opacity-60 grayscale hover:opacity-100 hover:grayscale-0 transition-all cursor-pointer" className="h-8 sm:h-10 object-contain opacity-60 grayscale hover:opacity-100 hover:grayscale-0 transition-all cursor-pointer"
/> />
) : ( ) : (
<div className="flex items-center gap-3 text-slate-400"> <div className="flex items-center gap-2 sm:gap-3 text-slate-400">
<div className="w-10 h-10 rounded-xl bg-slate-100 flex items-center justify-center font-bold text-slate-500"> <div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg sm:rounded-xl bg-slate-100 flex items-center justify-center font-bold text-slate-500 text-sm">
{organization.name[0]} {organization.name[0]}
</div> </div>
<span className="text-sm font-bold uppercase tracking-widest">{organization.name}</span> <span className="text-xs sm:text-sm font-bold uppercase tracking-widest truncate max-w-[150px] sm:max-w-none">{organization.name}</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -2,14 +2,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Lock, Mail, ArrowRight, ShieldCheck, CreditCard, FileText } from "lucide-react"; import { Lock, Mail, ArrowRight, ShieldCheck, CreditCard, FileText, Loader2 } from "lucide-react";
import { login } from "@/app/actions/auth"; import { login } from "@/app/actions/auth";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
type Organization = { type Organization = {
id: string; id: string;
@@ -42,8 +41,8 @@ export default function LoginClient({ organization }: { organization: Organizati
}; };
return ( return (
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2 bg-white selection:bg-slate-900 selection:text-white"> <div className="min-h-screen flex flex-col lg:grid lg:grid-cols-2 bg-white selection:bg-slate-900 selection:text-white">
{/* Left Side: Illustration & Info */} {/* Left Side: Illustration & Info - Hidden on Mobile */}
<div <div
className="hidden lg:flex flex-col justify-between p-16 text-white relative overflow-hidden" className="hidden lg:flex flex-col justify-between p-16 text-white relative overflow-hidden"
style={{ background: `linear-gradient(135deg, ${primaryColor} 0%, ${primaryColor}dd 100%)` }} style={{ background: `linear-gradient(135deg, ${primaryColor} 0%, ${primaryColor}dd 100%)` }}
@@ -103,20 +102,43 @@ export default function LoginClient({ organization }: { organization: Organizati
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-black/10 rounded-full blur-[100px] -ml-40 -mb-40 opacity-20 pointer-events-none" /> <div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-black/10 rounded-full blur-[100px] -ml-40 -mb-40 opacity-20 pointer-events-none" />
</div> </div>
{/* Right Side: Login Form */} {/* Right Side: Login Form - Full screen on mobile */}
<div className="flex flex-col items-center justify-center p-10 lg:p-16 relative overflow-hidden bg-white"> <div className="flex-1 flex flex-col items-center justify-center p-6 sm:p-10 lg:p-16 relative overflow-hidden bg-white min-h-screen lg:min-h-0">
{/* Mobile Header */}
<div className="lg:hidden w-full max-w-sm mb-8">
<div className="flex items-center justify-center gap-3 mb-6">
{organization.logoUrl ? (
<div
className="w-12 h-12 rounded-xl flex items-center justify-center p-2 border"
style={{ borderColor: `${primaryColor}30`, backgroundColor: `${primaryColor}10` }}
>
<img src={organization.logoUrl} alt="Logo" className="max-w-full max-h-full object-contain" />
</div>
) : (
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold"
style={{ backgroundColor: primaryColor }}
>
{organization.name.charAt(0)}
</div>
)}
</div>
<h1 className="text-xl font-black text-slate-900 text-center tracking-tight">{organization.name}</h1>
<p className="text-xs text-slate-400 text-center font-medium mt-1">Portal de Transparência</p>
</div>
<main className="w-full max-w-sm relative z-10"> <main className="w-full max-w-sm relative z-10">
<div className="mb-10 text-center lg:text-left"> <div className="mb-8 lg:mb-10 text-center lg:text-left">
<div className="inline-block px-2.5 py-0.5 bg-slate-50 border border-slate-100 rounded-full text-[9px] font-black uppercase tracking-[0.15em] text-slate-400 mb-4"> <div className="inline-block px-2.5 py-0.5 bg-slate-50 border border-slate-100 rounded-full text-[9px] font-black uppercase tracking-[0.15em] text-slate-400 mb-3 lg:mb-4">
Admin Access Admin Access
</div> </div>
<h2 className="text-4xl font-black text-slate-900 tracking-tighter mb-2 leading-none italic">Acesso Restrito.</h2> <h2 className="text-2xl sm:text-3xl lg:text-4xl font-black text-slate-900 tracking-tighter mb-2 leading-none italic">Acesso Restrito.</h2>
<p className="text-base text-slate-500 font-medium">Insira suas credenciais corporativas.</p> <p className="text-sm lg:text-base text-slate-500 font-medium">Insira suas credenciais corporativas.</p>
</div> </div>
<form className="space-y-6" onSubmit={handleSubmit}> <form className="space-y-5 lg:space-y-6" onSubmit={handleSubmit}>
{error && ( {error && (
<Alert variant="destructive" className="rounded-xl border-2 border-red-100 bg-red-50 text-red-600 font-bold p-5"> <Alert variant="destructive" className="rounded-xl border-2 border-red-100 bg-red-50 text-red-600 font-bold p-4 lg:p-5">
<AlertDescription className="text-sm">{error}</AlertDescription> <AlertDescription className="text-sm">{error}</AlertDescription>
</Alert> </Alert>
)} )}
@@ -131,7 +153,7 @@ export default function LoginClient({ organization }: { organization: Organizati
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="admin@exemplo.com" placeholder="admin@exemplo.com"
className="pl-12 h-11 bg-slate-50 border-none rounded-xl text-sm font-bold focus:bg-white focus:ring-4 focus:ring-slate-50 transition-all shadow-none" className="pl-12 h-12 lg:h-11 bg-slate-50 border-none rounded-xl text-sm font-bold focus:bg-white focus:ring-4 focus:ring-slate-50 transition-all shadow-none"
required required
/> />
</div> </div>
@@ -147,7 +169,7 @@ export default function LoginClient({ organization }: { organization: Organizati
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••" placeholder="••••••••"
className="pl-12 h-11 bg-slate-50 border-none rounded-xl text-sm font-bold focus:bg-white focus:ring-4 focus:ring-slate-50 transition-all shadow-none" className="pl-12 h-12 lg:h-11 bg-slate-50 border-none rounded-xl text-sm font-bold focus:bg-white focus:ring-4 focus:ring-slate-50 transition-all shadow-none"
required required
/> />
</div> </div>
@@ -156,15 +178,24 @@ export default function LoginClient({ organization }: { organization: Organizati
<Button <Button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="w-full h-11 rounded-xl text-xs font-black uppercase tracking-widest italic shadow-none transition-all active:scale-95 flex items-center justify-center gap-3 group text-white" className="w-full h-12 lg:h-11 rounded-xl text-xs font-black uppercase tracking-widest italic shadow-none transition-all active:scale-95 flex items-center justify-center gap-3 group text-white"
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
> >
{isLoading ? "Validando..." : "Entrar no Painel"} {isLoading ? (
{!isLoading && <ArrowRight size={20} className="stroke-[3] group-hover:translate-x-1.5 transition-transform" />} <>
<Loader2 size={18} className="animate-spin" />
Validando...
</>
) : (
<>
Entrar no Painel
<ArrowRight size={20} className="stroke-[3] group-hover:translate-x-1.5 transition-transform" />
</>
)}
</Button> </Button>
</form> </form>
<p className="mt-10 text-center text-slate-400 text-[10px] font-bold uppercase tracking-widest"> <p className="mt-8 lg:mt-10 text-center text-slate-400 text-[10px] font-bold uppercase tracking-widest">
Segurança protegida por criptografia. Segurança protegida por criptografia.
</p> </p>
</main> </main>

View File

@@ -1,12 +1,14 @@
"use client"; "use client";
import React from "react"; import React, { useState } from "react";
import { import {
LayoutDashboard, LayoutDashboard,
FileText, FileText,
Users, Users,
Settings, Settings,
LogOut, LogOut,
Menu,
X,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
@@ -36,6 +38,7 @@ export function Sidebar({
}) { }) {
const pathname = usePathname(); const pathname = usePathname();
const primaryColor = organization.primaryColor || "#2563eb"; const primaryColor = organization.primaryColor || "#2563eb";
const [mobileOpen, setMobileOpen] = useState(false);
const menuItems = [ const menuItems = [
{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, { href: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
@@ -44,12 +47,12 @@ export function Sidebar({
{ href: "/dashboard/configuracoes", label: "Configurações", icon: Settings }, { href: "/dashboard/configuracoes", label: "Configurações", icon: Settings },
]; ];
return ( const SidebarContent = () => (
<aside className="w-72 bg-slate-50/50 border-r border-slate-200/60 flex flex-col h-screen sticky top-0 shrink-0 select-none"> <>
<div className="p-8"> <div className="p-6 lg:p-8">
<div className="flex items-center gap-4"> <div className="flex items-center gap-3 lg:gap-4">
{organization.logoUrl ? ( {organization.logoUrl ? (
<div className="w-12 h-12 rounded-2xl bg-white border border-slate-200 p-2 flex items-center justify-center"> <div className="w-10 h-10 lg:w-12 lg:h-12 rounded-xl lg:rounded-2xl bg-white border border-slate-200 p-1.5 lg:p-2 flex items-center justify-center shrink-0">
<img <img
src={organization.logoUrl} src={organization.logoUrl}
alt="Logo" alt="Logo"
@@ -58,58 +61,67 @@ export function Sidebar({
</div> </div>
) : ( ) : (
<div <div
className="w-12 h-12 rounded-2xl flex items-center justify-center text-white font-bold text-xl shrink-0 border border-white/20" className="w-10 h-10 lg:w-12 lg:h-12 rounded-xl lg:rounded-2xl flex items-center justify-center text-white font-bold text-lg lg:text-xl shrink-0 border border-white/20"
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
> >
{organization.name.charAt(0)} {organization.name.charAt(0)}
</div> </div>
)} )}
<div className="min-w-0"> <div className="min-w-0 flex-1">
<h1 className="text-base font-black text-slate-800 tracking-tight truncate leading-tight"> <h1 className="text-sm lg:text-base font-black text-slate-800 tracking-tight truncate leading-tight">
{organization.name} {organization.name}
</h1> </h1>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">Gestão Fiscal</p> <p className="text-[9px] lg:text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">Gestão Fiscal</p>
</div> </div>
{/* Close button for mobile */}
<button
className="lg:hidden p-2 -mr-2 text-slate-500 hover:text-slate-900"
onClick={() => setMobileOpen(false)}
>
<X size={20} />
</button>
</div> </div>
</div> </div>
<nav className="flex-1 px-4 space-y-1.5 overflow-y-auto pt-2"> <nav className="flex-1 px-3 lg:px-4 space-y-1 lg:space-y-1.5 overflow-y-auto pt-2">
{menuItems.map((item) => { {menuItems.map((item) => {
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href)); const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href));
return ( return (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
className={`flex items-center gap-3.5 px-5 py-3.5 rounded-2xl transition-all duration-300 group ${isActive onClick={() => setMobileOpen(false)}
? "font-bold" className={`flex items-center gap-3 lg:gap-3.5 px-4 lg:px-5 py-3 lg:py-3.5 rounded-xl lg:rounded-2xl transition-all duration-300 group ${isActive
: "text-slate-500 hover:text-slate-900 hover:bg-white" ? "font-bold"
: "text-slate-500 hover:text-slate-900 hover:bg-white"
}`} }`}
style={isActive ? { backgroundColor: `${primaryColor}10`, color: primaryColor } : {}} style={isActive ? { backgroundColor: `${primaryColor}10`, color: primaryColor } : {}}
> >
<item.icon size={20} className={`transition-transform duration-300 ${isActive ? "scale-110" : "group-hover:scale-110 opacity-70 group-hover:opacity-100"}`} /> <item.icon size={18} className={`transition-transform duration-300 ${isActive ? "scale-110" : "group-hover:scale-110 opacity-70 group-hover:opacity-100"}`} />
<span className="text-[13px] tracking-tight">{item.label}</span> <span className="text-[13px] tracking-tight">{item.label}</span>
</Link> </Link>
); );
})} })}
</nav> </nav>
<div className="p-6 mt-auto"> <div className="p-4 lg:p-6 mt-auto">
<div className="flex flex-col gap-4 p-5 rounded-[24px] bg-white border border-slate-200/80"> <div className="flex flex-col gap-3 lg:gap-4 p-4 lg:p-5 rounded-2xl lg:rounded-[24px] bg-white border border-slate-200/80">
<Link <Link
href="/dashboard/perfil" href="/dashboard/perfil"
className="flex items-center gap-3.5 group" onClick={() => setMobileOpen(false)}
className="flex items-center gap-3 lg:gap-3.5 group"
> >
<div <div
className="w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-black shrink-0 border-2 border-slate-100" className="w-9 h-9 lg:w-10 lg:h-10 rounded-full flex items-center justify-center text-white text-xs lg:text-sm font-black shrink-0 border-2 border-slate-100"
style={{ backgroundColor: primaryColor }} style={{ backgroundColor: primaryColor }}
> >
{user.name?.charAt(0) || user.email.charAt(0).toUpperCase()} {user.name?.charAt(0) || user.email.charAt(0).toUpperCase()}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[13px] font-black text-slate-900 truncate leading-tight group-hover:underline decoration-2 underline-offset-2"> <p className="text-[12px] lg:text-[13px] font-black text-slate-900 truncate leading-tight group-hover:underline decoration-2 underline-offset-2">
{user.name || "Usuário"} {user.name || "Usuário"}
</p> </p>
<p className="text-[11px] font-medium text-slate-400 truncate mt-0.5">{user.email}</p> <p className="text-[10px] lg:text-[11px] font-medium text-slate-400 truncate mt-0.5">{user.email}</p>
</div> </div>
</Link> </Link>
@@ -119,15 +131,69 @@ export function Sidebar({
<Button <Button
type="submit" type="submit"
variant="ghost" variant="ghost"
className="w-full justify-start gap-3 h-11 px-4 rounded-xl text-slate-500 hover:text-red-600 hover:bg-red-50 text-[13px] font-bold transition-all duration-300" className="w-full justify-start gap-3 h-10 lg:h-11 px-3 lg:px-4 rounded-xl text-slate-500 hover:text-red-600 hover:bg-red-50 text-[12px] lg:text-[13px] font-bold transition-all duration-300"
title="Sair do sistema" title="Sair do sistema"
> >
<LogOut size={18} /> <LogOut size={16} />
Encerrar Sessão Encerrar Sessão
</Button> </Button>
</form> </form>
</div> </div>
</div> </div>
</aside> </>
);
return (
<>
{/* Mobile Header */}
<div className="lg:hidden fixed top-0 left-0 right-0 h-16 bg-white border-b border-slate-200 z-40 flex items-center justify-between px-4">
<div className="flex items-center gap-3">
{organization.logoUrl ? (
<div className="w-9 h-9 rounded-xl bg-white border border-slate-200 p-1.5 flex items-center justify-center">
<img
src={organization.logoUrl}
alt="Logo"
className="max-w-full max-h-full object-contain"
/>
</div>
) : (
<div
className="w-9 h-9 rounded-xl flex items-center justify-center text-white font-bold text-sm"
style={{ backgroundColor: primaryColor }}
>
{organization.name.charAt(0)}
</div>
)}
<span className="text-sm font-bold text-slate-800 truncate max-w-[180px]">{organization.name}</span>
</div>
<button
onClick={() => setMobileOpen(true)}
className="p-2 text-slate-600 hover:text-slate-900 hover:bg-slate-100 rounded-lg transition-colors"
>
<Menu size={22} />
</button>
</div>
{/* Mobile Overlay */}
{mobileOpen && (
<div
className="lg:hidden fixed inset-0 bg-black/50 z-40 backdrop-blur-sm"
onClick={() => setMobileOpen(false)}
/>
)}
{/* Mobile Sidebar Drawer */}
<aside
className={`lg:hidden fixed top-0 left-0 bottom-0 w-[280px] bg-slate-50/95 backdrop-blur-lg z-50 flex flex-col transform transition-transform duration-300 ease-out ${mobileOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
<SidebarContent />
</aside>
{/* Desktop Sidebar */}
<aside className="hidden lg:flex w-72 bg-slate-50/50 border-r border-slate-200/60 flex-col h-screen sticky top-0 shrink-0 select-none">
<SidebarContent />
</aside>
</>
); );
} }