feat: restructure navbar and fix dead-end navigation across pages
Restructure the AppShell navbar for better UX grouping: rename "Organizações" to "Equipe", consolidate Org Dashboard and admin items inside the Equipe dropdown, add a persistent "+ Nova Tarefa" CTA button, and replace the isolated logout button with an avatar dropdown showing user name and email. Add "Buscar Organização" to the Equipe dropdown for all roles. Mobile nav simplified to Dashboard · Tarefas · + · Equipe. Per-page fixes: remove non-functional chevron and dead links from ProjectsPage; replace the PDF export button in DashboardPage with a working "Copiar Relatório" clipboard button; remove dead info links from OrganizationsPage; remove the broken support link from LoginPage; add Lista/Quadro view toggle to TaskListPage header. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6e77f7b194
commit
96e35dc21d
6 changed files with 244 additions and 144 deletions
|
|
@ -2,17 +2,20 @@ import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
ListTodo,
|
ListTodo,
|
||||||
PlusCircle,
|
Plus,
|
||||||
Columns,
|
Columns,
|
||||||
LogOut,
|
LogOut,
|
||||||
Settings,
|
Settings,
|
||||||
Building2,
|
Building2,
|
||||||
Clock,
|
Clock,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Shield,
|
|
||||||
FolderKanban,
|
FolderKanban,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
PieChart,
|
PieChart,
|
||||||
|
Search,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
PlusCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { NavLink, Outlet, Navigate, Link } from "react-router-dom";
|
import { NavLink, Outlet, Navigate, Link } from "react-router-dom";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
|
@ -43,7 +46,11 @@ export function AppShell() {
|
||||||
const [showOrgModal, setShowOrgModal] = useState(false);
|
const [showOrgModal, setShowOrgModal] = useState(false);
|
||||||
const [showOrgSwitcher, setShowOrgSwitcher] = useState(false);
|
const [showOrgSwitcher, setShowOrgSwitcher] = useState(false);
|
||||||
const [showTasksDropdown, setShowTasksDropdown] = useState(false);
|
const [showTasksDropdown, setShowTasksDropdown] = useState(false);
|
||||||
const [showOrgDropdown, setShowOrgDropdown] = useState(false);
|
const [showTeamDropdown, setShowTeamDropdown] = useState(false);
|
||||||
|
const [showAvatarDropdown, setShowAvatarDropdown] = useState(false);
|
||||||
|
|
||||||
|
const isAdmin =
|
||||||
|
activeOrg?.roles.includes("OWNER") || activeOrg?.roles.includes("ADMIN");
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isAuthenticated && user && user.organizations.length === 0) {
|
if (isAuthenticated && user && user.organizations.length === 0) {
|
||||||
|
|
@ -181,10 +188,11 @@ export function AppShell() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Navigation */}
|
||||||
<nav className="hidden items-center gap-1 md:flex">
|
<nav className="hidden items-center gap-1 md:flex">
|
||||||
<NavLink to="/dashboard" className={navLinkClassName}>
|
<NavLink to="/dashboard" className={navLinkClassName}>
|
||||||
<BarChart3 size={18} />
|
<BarChart3 size={18} />
|
||||||
Dashboard
|
Meu Dashboard
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
{/* Tarefas Dropdown */}
|
{/* Tarefas Dropdown */}
|
||||||
|
|
@ -252,69 +260,85 @@ export function AppShell() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(activeOrg?.roles.includes("OWNER") ||
|
{isAdmin && (
|
||||||
activeOrg?.roles.includes("ADMIN")) && (
|
|
||||||
<NavLink to="/projects" className={navLinkClassName}>
|
<NavLink to="/projects" className={navLinkClassName}>
|
||||||
<FolderKanban size={18} />
|
<FolderKanban size={18} />
|
||||||
Projetos
|
Projetos
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(activeOrg?.roles.includes("OWNER") ||
|
{/* Equipe Dropdown */}
|
||||||
activeOrg?.roles.includes("ADMIN")) && (
|
|
||||||
<NavLink to="/org-dashboard" className={navLinkClassName}>
|
|
||||||
<PieChart size={18} />
|
|
||||||
Org Dashboard
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Organizações Dropdown */}
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/organizations"
|
to="/organizations"
|
||||||
className={({ isActive }) => {
|
className={({ isActive }) => {
|
||||||
const isRequestsActive =
|
const path = window.location.pathname;
|
||||||
window.location.pathname === "/requests";
|
|
||||||
const isAdminActive = window.location.pathname === "/admin";
|
|
||||||
const active =
|
const active =
|
||||||
isActive || isRequestsActive || isAdminActive;
|
isActive ||
|
||||||
|
path === "/requests" ||
|
||||||
|
path === "/admin" ||
|
||||||
|
path === "/org-dashboard" ||
|
||||||
|
path === "/organizations/join";
|
||||||
return cn(
|
return cn(
|
||||||
"inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-semibold transition-all duration-200",
|
"inline-flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-semibold transition-all duration-200",
|
||||||
active || showOrgDropdown
|
active || showTeamDropdown
|
||||||
? "bg-brand text-white shadow-md shadow-brand/20"
|
? "bg-brand text-white shadow-md shadow-brand/20"
|
||||||
: "text-text-muted hover:bg-surface-muted hover:text-text-main",
|
: "text-text-muted hover:bg-surface-muted hover:text-text-main",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowOrgDropdown(!showOrgDropdown);
|
setShowTeamDropdown(!showTeamDropdown);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Building2 size={18} />
|
<Users size={18} />
|
||||||
Organizações
|
Equipe
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
size={14}
|
size={14}
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-transform",
|
"transition-transform",
|
||||||
showOrgDropdown && "rotate-180",
|
showTeamDropdown && "rotate-180",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
{showOrgDropdown && (
|
{showTeamDropdown && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40"
|
className="fixed inset-0 z-40"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="absolute right-0 mt-2 z-50 w-56 rounded-2xl border border-border-soft bg-surface p-2 shadow-xl shadow-brand/10"
|
className="absolute right-0 mt-2 z-50 w-60 rounded-2xl border border-border-soft bg-surface p-2 shadow-xl shadow-brand/10"
|
||||||
>
|
>
|
||||||
|
{isAdmin && (
|
||||||
|
<>
|
||||||
|
<NavLink
|
||||||
|
to="/org-dashboard"
|
||||||
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
|
className={dropdownItemClassName}
|
||||||
|
>
|
||||||
|
<PieChart size={18} />
|
||||||
|
Dashboard da Org
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/admin"
|
||||||
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
|
className={(props) =>
|
||||||
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Settings size={18} />
|
||||||
|
Solicitações Pendentes
|
||||||
|
</NavLink>
|
||||||
|
<div className="my-2 border-t border-border-soft" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/organizations"
|
to="/organizations"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
className={dropdownItemClassName}
|
className={dropdownItemClassName}
|
||||||
>
|
>
|
||||||
<Building2 size={18} />
|
<Building2 size={18} />
|
||||||
|
|
@ -322,7 +346,7 @@ export function AppShell() {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/requests"
|
to="/requests"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
className={(props) =>
|
className={(props) =>
|
||||||
cn(dropdownItemClassName(props), "mt-1")
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
}
|
}
|
||||||
|
|
@ -330,20 +354,16 @@ export function AppShell() {
|
||||||
<Clock size={18} />
|
<Clock size={18} />
|
||||||
Minhas Solicitações
|
Minhas Solicitações
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{activeOrg?.roles.some(
|
<NavLink
|
||||||
(role) => role === "OWNER" || role === "ADMIN",
|
to="/organizations/join"
|
||||||
) && (
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
<NavLink
|
className={(props) =>
|
||||||
to="/admin"
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
}
|
||||||
className={(props) =>
|
>
|
||||||
cn(dropdownItemClassName(props), "mt-1")
|
<Search size={18} />
|
||||||
}
|
Buscar Organização
|
||||||
>
|
</NavLink>
|
||||||
<Settings size={18} />
|
|
||||||
Solicitações Pendentes
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -351,16 +371,60 @@ export function AppShell() {
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{/* Right Actions */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link
|
||||||
|
to="/new"
|
||||||
|
className="hidden md:inline-flex items-center gap-2 rounded-xl bg-brand px-4 py-2 text-sm font-bold text-white shadow-md shadow-brand/20 transition-all hover:scale-[1.02] hover:bg-brand-dark active:scale-95"
|
||||||
|
>
|
||||||
|
<Plus size={16} />
|
||||||
|
Nova Tarefa
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="hidden h-8 w-px bg-border-soft md:block" />
|
<div className="hidden h-8 w-px bg-border-soft md:block" />
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
|
{/* Avatar Dropdown */}
|
||||||
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={() => setShowAvatarDropdown(!showAvatarDropdown)}
|
||||||
className="ml-2 p-2 text-text-muted hover:text-danger transition-colors"
|
className="flex h-9 w-9 items-center justify-center rounded-full bg-brand/10 text-brand hover:bg-brand hover:text-white transition-all"
|
||||||
title="Sair"
|
title={user?.name}
|
||||||
>
|
>
|
||||||
<LogOut size={18} />
|
<User size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{showAvatarDropdown && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-40"
|
||||||
|
onClick={() => setShowAvatarDropdown(false)}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="absolute right-0 mt-2 z-50 w-56 rounded-2xl border border-border-soft bg-surface p-2 shadow-xl shadow-brand/10"
|
||||||
|
>
|
||||||
|
<div className="px-3 py-3 border-b border-border-soft mb-1">
|
||||||
|
<p className="text-sm font-bold text-text-main truncate">
|
||||||
|
{user?.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-text-muted truncate mt-0.5">
|
||||||
|
{user?.email}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowAvatarDropdown(false);
|
||||||
|
logout();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-bold text-text-muted hover:bg-danger/5 hover:text-danger transition-all"
|
||||||
|
>
|
||||||
|
<LogOut size={16} />
|
||||||
|
Sair
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -383,7 +447,7 @@ export function AppShell() {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<BarChart3 size={20} />
|
<BarChart3 size={20} />
|
||||||
<span className="text-[10px]">Dash</span>
|
<span className="text-[10px]">Dashboard</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
{/* Mobile Tarefas Group */}
|
{/* Mobile Tarefas Group */}
|
||||||
|
|
@ -437,59 +501,88 @@ export function AppShell() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(activeOrg?.roles.includes("OWNER") ||
|
|
||||||
activeOrg?.roles.includes("ADMIN")) && (
|
|
||||||
<NavLink to="/projects" className={navLinkClassName}>
|
|
||||||
<FolderKanban size={18} />
|
|
||||||
Projetos
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(activeOrg?.roles.includes("OWNER") ||
|
{/* Mobile + New Task */}
|
||||||
activeOrg?.roles.includes("ADMIN")) && (
|
<Link
|
||||||
<NavLink
|
to="/new"
|
||||||
to="/org-dashboard"
|
className={cn(
|
||||||
className={({ isActive }) =>
|
navLinkClassName({ isActive: window.location.pathname === "/new" }),
|
||||||
cn(navLinkClassName({ isActive }), "flex-col gap-0.5 py-1")
|
"flex-col gap-0.5 py-1",
|
||||||
}
|
)}
|
||||||
>
|
>
|
||||||
<PieChart size={20} />
|
<Plus size={20} />
|
||||||
<span className="text-[10px]">Org</span>
|
<span className="text-[10px]">Nova</span>
|
||||||
</NavLink>
|
</Link>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mobile Organizações Group */}
|
{/* Mobile Equipe Group */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowOrgDropdown(!showOrgDropdown)}
|
onClick={() => setShowTeamDropdown(!showTeamDropdown)}
|
||||||
className={cn(
|
className={cn(
|
||||||
navLinkClassName({
|
navLinkClassName({
|
||||||
isActive:
|
isActive: [
|
||||||
window.location.pathname === "/organizations" ||
|
"/organizations",
|
||||||
window.location.pathname === "/requests" ||
|
"/requests",
|
||||||
window.location.pathname === "/admin",
|
"/admin",
|
||||||
|
"/org-dashboard",
|
||||||
|
"/organizations/join",
|
||||||
|
"/projects",
|
||||||
|
].includes(window.location.pathname),
|
||||||
}),
|
}),
|
||||||
"flex-col gap-0.5 py-1",
|
"flex-col gap-0.5 py-1",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Building2 size={20} />
|
<Users size={20} />
|
||||||
<span className="text-[10px]">Org</span>
|
<span className="text-[10px]">Equipe</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showOrgDropdown && (
|
{showTeamDropdown && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40"
|
className="fixed inset-0 z-40"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -10, scale: 0.95 }}
|
initial={{ opacity: 0, y: -10, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
className="absolute bottom-full left-1/2 -translate-x-1/2 mb-4 z-50 w-48 rounded-2xl border border-border-soft bg-surface p-1 shadow-2xl"
|
className="absolute bottom-full right-0 mb-4 z-50 w-52 rounded-2xl border border-border-soft bg-surface p-1 shadow-2xl"
|
||||||
>
|
>
|
||||||
|
{isAdmin && (
|
||||||
|
<>
|
||||||
|
<NavLink
|
||||||
|
to="/projects"
|
||||||
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
|
className={dropdownItemClassName}
|
||||||
|
>
|
||||||
|
<FolderKanban size={18} />
|
||||||
|
Projetos
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/org-dashboard"
|
||||||
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
|
className={(props) =>
|
||||||
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PieChart size={18} />
|
||||||
|
Dashboard da Org
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/admin"
|
||||||
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
|
className={(props) =>
|
||||||
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Settings size={18} />
|
||||||
|
Gestão
|
||||||
|
</NavLink>
|
||||||
|
<div className="my-1 border-t border-border-soft" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/organizations"
|
to="/organizations"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
className={dropdownItemClassName}
|
className={dropdownItemClassName}
|
||||||
>
|
>
|
||||||
<Building2 size={18} />
|
<Building2 size={18} />
|
||||||
|
|
@ -497,7 +590,7 @@ export function AppShell() {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/requests"
|
to="/requests"
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
className={(props) =>
|
className={(props) =>
|
||||||
cn(dropdownItemClassName(props), "mt-1")
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
}
|
}
|
||||||
|
|
@ -505,20 +598,16 @@ export function AppShell() {
|
||||||
<Clock size={18} />
|
<Clock size={18} />
|
||||||
Pedidos
|
Pedidos
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{activeOrg?.roles.some(
|
<NavLink
|
||||||
(role) => role === "OWNER" || role === "ADMIN",
|
to="/organizations/join"
|
||||||
) && (
|
onClick={() => setShowTeamDropdown(false)}
|
||||||
<NavLink
|
className={(props) =>
|
||||||
to="/admin"
|
cn(dropdownItemClassName(props), "mt-1")
|
||||||
onClick={() => setShowOrgDropdown(false)}
|
}
|
||||||
className={(props) =>
|
>
|
||||||
cn(dropdownItemClassName(props), "mt-1")
|
<Search size={18} />
|
||||||
}
|
Buscar Org
|
||||||
>
|
</NavLink>
|
||||||
<Settings size={18} />
|
|
||||||
Gestão
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Layout,
|
Layout,
|
||||||
FileText,
|
FileText,
|
||||||
Filter,
|
Filter,
|
||||||
Download,
|
Copy,
|
||||||
|
Check,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Loader2,
|
Loader2,
|
||||||
History,
|
History,
|
||||||
|
|
@ -30,6 +31,7 @@ export const DashboardPage: React.FC = () => {
|
||||||
const [startDate, setStartDate] = useState(startOfMonth(new Date()));
|
const [startDate, setStartDate] = useState(startOfMonth(new Date()));
|
||||||
const [endDate, setEndDate] = useState(endOfMonth(new Date()));
|
const [endDate, setEndDate] = useState(endOfMonth(new Date()));
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const fetchDashboardData = async () => {
|
const fetchDashboardData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
@ -57,6 +59,24 @@ export const DashboardPage: React.FC = () => {
|
||||||
return `${h}h ${m}m`;
|
return `${h}h ${m}m`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCopyReport = () => {
|
||||||
|
if (!data) return;
|
||||||
|
const header = "Tarefa\tProjeto\tConcluída em\tStatus";
|
||||||
|
const rows = data.finishedTasksInPeriod.map((task) =>
|
||||||
|
[
|
||||||
|
task.title,
|
||||||
|
task.project?.name ?? "-",
|
||||||
|
task.finishedAt
|
||||||
|
? format(new Date(task.finishedAt), "dd/MM/yyyy HH:mm")
|
||||||
|
: "-",
|
||||||
|
"Finalizado",
|
||||||
|
].join("\t"),
|
||||||
|
);
|
||||||
|
navigator.clipboard.writeText([header, ...rows].join("\n"));
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-10 pb-20">
|
<div className="space-y-10 pb-20">
|
||||||
{/* Header & Filters */}
|
{/* Header & Filters */}
|
||||||
|
|
@ -199,10 +219,6 @@ export const DashboardPage: React.FC = () => {
|
||||||
)
|
)
|
||||||
: "0h 0m"}
|
: "0h 0m"}
|
||||||
</div>
|
</div>
|
||||||
<button className="mt-6 inline-flex items-center gap-2 text-xs font-black uppercase tracking-widest bg-white/10 hover:bg-white/20 px-4 py-2 rounded-xl transition-all">
|
|
||||||
<Download size={14} />
|
|
||||||
Exportar Relatório
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<TrendingUp
|
<TrendingUp
|
||||||
size={160}
|
size={160}
|
||||||
|
|
@ -324,9 +340,12 @@ export const DashboardPage: React.FC = () => {
|
||||||
Relatório de Entregas
|
Relatório de Entregas
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<button className="flex items-center gap-2 text-sm font-bold text-brand hover:underline bg-brand/5 px-4 py-2 rounded-xl transition-all">
|
<button
|
||||||
<Download size={16} />
|
onClick={handleCopyReport}
|
||||||
Baixar PDF
|
className="flex items-center gap-2 text-sm font-bold text-brand bg-brand/5 px-4 py-2 rounded-xl transition-all hover:bg-brand/10 active:scale-95"
|
||||||
|
>
|
||||||
|
{copied ? <Check size={16} /> : <Copy size={16} />}
|
||||||
|
{copied ? "Copiado!" : "Copiar Relatório"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,10 @@ export const LoginPage: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-10 pt-8 border-t border-border-soft text-center space-y-4">
|
<div className="mt-10 pt-8 border-t border-border-soft text-center">
|
||||||
<p className="text-text-muted text-sm font-medium">
|
<p className="text-text-muted text-sm font-medium">
|
||||||
Não tem uma conta? <Link to="/register" className="text-brand font-bold hover:underline">Criar conta</Link>
|
Não tem uma conta? <Link to="/register" className="text-brand font-bold hover:underline">Criar conta</Link>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-text-muted text-sm font-medium">
|
|
||||||
Precisa de ajuda? <span className="text-brand font-bold cursor-pointer hover:underline">Falar com suporte</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
Users,
|
Users,
|
||||||
Settings,
|
Settings,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ExternalLink,
|
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
X,
|
X,
|
||||||
|
|
@ -238,10 +237,6 @@ export function OrganizationsPage() {
|
||||||
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
||||||
Organizações são espaços isolados para seus projetos e tarefas. Você pode alternar facilmente entre elas e gerenciar diferentes equipes.
|
Organizações são espaços isolados para seus projetos e tarefas. Você pode alternar facilmente entre elas e gerenciar diferentes equipes.
|
||||||
</p>
|
</p>
|
||||||
<a href="#" className="mt-6 inline-flex items-center gap-1.5 text-sm font-bold text-accent hover:underline">
|
|
||||||
Entender hierarquia
|
|
||||||
<ExternalLink size={14} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-[2rem] border border-border-soft bg-surface p-8">
|
<div className="rounded-[2rem] border border-border-soft bg-surface p-8">
|
||||||
|
|
@ -252,10 +247,6 @@ export function OrganizationsPage() {
|
||||||
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
||||||
Crie uma organização em segundos e comece a convidar seus membros. O BeeTask facilita a colaboração desde o primeiro dia.
|
Crie uma organização em segundos e comece a convidar seus membros. O BeeTask facilita a colaboração desde o primeiro dia.
|
||||||
</p>
|
</p>
|
||||||
<a href="#" className="mt-6 inline-flex items-center gap-1.5 text-sm font-bold text-brand hover:underline">
|
|
||||||
Dicas de onboarding
|
|
||||||
<ExternalLink size={14} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,12 @@ import {
|
||||||
FolderKanban,
|
FolderKanban,
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
Building2,
|
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Loader2,
|
Loader2,
|
||||||
X,
|
X,
|
||||||
Target,
|
Target,
|
||||||
Rocket,
|
Rocket,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
ExternalLink,
|
|
||||||
ChevronRight
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { projectService, type Project } from "../services/projectService";
|
import { projectService, type Project } from "../services/projectService";
|
||||||
|
|
@ -146,7 +143,7 @@ export const ProjectsPage: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flex items-center justify-between">
|
<div className="mt-8 flex items-center">
|
||||||
<div className="flex -space-x-2">
|
<div className="flex -space-x-2">
|
||||||
{[1, 2, 3].map(i => (
|
{[1, 2, 3].map(i => (
|
||||||
<div key={i} className="h-8 w-8 rounded-full border-2 border-surface bg-surface-muted flex items-center justify-center text-[10px] font-bold text-text-muted">
|
<div key={i} className="h-8 w-8 rounded-full border-2 border-surface bg-surface-muted flex items-center justify-center text-[10px] font-bold text-text-muted">
|
||||||
|
|
@ -154,9 +151,6 @@ export const ProjectsPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<button className="flex h-10 w-10 items-center justify-center rounded-xl bg-app-bg text-text-muted transition-all hover:bg-brand hover:text-white">
|
|
||||||
<ChevronRight size={20} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -233,10 +227,6 @@ export const ProjectsPage: React.FC = () => {
|
||||||
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
||||||
Cada projeto pode ser encarado como um objetivo específico. Use-os para separar demandas de diferentes clientes ou departamentos da sua organização.
|
Cada projeto pode ser encarado como um objetivo específico. Use-os para separar demandas de diferentes clientes ou departamentos da sua organização.
|
||||||
</p>
|
</p>
|
||||||
<a href="#" className="mt-6 inline-flex items-center gap-1.5 text-sm font-bold text-brand hover:underline">
|
|
||||||
Melhores práticas de organização
|
|
||||||
<ExternalLink size={14} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-[2rem] border border-border-soft bg-surface p-8">
|
<div className="rounded-[2rem] border border-border-soft bg-surface p-8">
|
||||||
|
|
@ -247,10 +237,6 @@ export const ProjectsPage: React.FC = () => {
|
||||||
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
<p className="mt-3 text-text-muted leading-relaxed font-medium">
|
||||||
Projetos herdam as permissões da organização. Apenas membros autorizados podem visualizar e interagir com as tarefas de cada projeto.
|
Projetos herdam as permissões da organização. Apenas membros autorizados podem visualizar e interagir com as tarefas de cada projeto.
|
||||||
</p>
|
</p>
|
||||||
<a href="#" className="mt-6 inline-flex items-center gap-1.5 text-sm font-bold text-accent hover:underline">
|
|
||||||
Ver detalhes de visibilidade
|
|
||||||
<ExternalLink size={14} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import {
|
||||||
Calendar,
|
Calendar,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock,
|
Clock,
|
||||||
|
Columns,
|
||||||
Filter,
|
Filter,
|
||||||
|
List,
|
||||||
Plus,
|
Plus,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Search,
|
Search,
|
||||||
|
|
@ -107,13 +109,29 @@ export function TaskListPage() {
|
||||||
Visualize e organize suas atividades em tempo real.
|
Visualize e organize suas atividades em tempo real.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<div className="flex items-center gap-3">
|
||||||
to="/new"
|
{/* View toggle */}
|
||||||
className="inline-flex items-center justify-center gap-2 rounded-xl bg-brand px-6 py-3 text-sm font-bold text-white shadow-lg shadow-brand/20 transition-all hover:scale-[1.02] hover:bg-brand-strong active:scale-95"
|
<div className="flex items-center rounded-xl border border-border-soft bg-surface p-1 shadow-sm">
|
||||||
>
|
<span className="inline-flex items-center gap-1.5 rounded-lg bg-brand px-3 py-2 text-xs font-bold text-white">
|
||||||
<Plus size={20} />
|
<List size={14} />
|
||||||
Criar Tarefa
|
Lista
|
||||||
</Link>
|
</span>
|
||||||
|
<Link
|
||||||
|
to="/board"
|
||||||
|
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-xs font-bold text-text-muted transition-all hover:bg-surface-muted hover:text-text-main"
|
||||||
|
>
|
||||||
|
<Columns size={14} />
|
||||||
|
Quadro
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
to="/new"
|
||||||
|
className="inline-flex items-center justify-center gap-2 rounded-xl bg-brand px-6 py-3 text-sm font-bold text-white shadow-lg shadow-brand/20 transition-all hover:scale-[1.02] hover:bg-brand-strong active:scale-95"
|
||||||
|
>
|
||||||
|
<Plus size={20} />
|
||||||
|
Criar Tarefa
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue