ui: degradado de cabeceras y ajustes responsive
This commit is contained in:
parent
f4c885031b
commit
d209959d0e
|
|
@ -72,7 +72,17 @@
|
|||
|
||||
--sidebar-border: 220 13% 91%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%}
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
--header-gradient: linear-gradient(
|
||||
120deg,
|
||||
hsl(220 95% 94% / 0.95) 0%,
|
||||
hsl(232 95% 95% / 0.92) 22%,
|
||||
hsl(246 95% 96% / 0.9) 48%,
|
||||
hsl(260 95% 95% / 0.88) 72%,
|
||||
hsl(214 95% 94% / 0.95) 100%
|
||||
);
|
||||
}
|
||||
.dark {
|
||||
|
||||
--background: 0 0% 3.9%;
|
||||
|
|
@ -138,7 +148,17 @@
|
|||
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
|
||||
--sidebar-ring: 217.2 91.2% 59.8%}
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
--header-gradient: linear-gradient(
|
||||
120deg,
|
||||
hsl(220 45% 12% / 0.9) 0%,
|
||||
hsl(232 45% 14% / 0.88) 22%,
|
||||
hsl(246 45% 15% / 0.86) 48%,
|
||||
hsl(260 45% 14% / 0.86) 72%,
|
||||
hsl(214 45% 12% / 0.9) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -182,3 +202,11 @@
|
|||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.header-gradient {
|
||||
background-image: var(--header-gradient);
|
||||
background-size: 200% 200%;
|
||||
background-position: 50% 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,23 @@ import { ProjectsTable } from "@/components/projects/projects-table";
|
|||
|
||||
export default function ProjectsPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderKanban className="h-6 w-6 text-primary" />
|
||||
<h1 className="text-2xl font-bold tracking-tight">Proyectos</h1>
|
||||
<div className="flex min-w-0 flex-col gap-6">
|
||||
<header className="header-gradient border-b px-6 py-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderKanban className="h-6 w-6 text-primary" />
|
||||
<h1 className="text-2xl font-bold tracking-tight">Proyectos</h1>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
Gestiona y visualiza todos tus proyectos en un solo lugar.
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
Gestiona y visualiza todos tus proyectos en un solo lugar.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Data Table */}
|
||||
<ProjectsTable />
|
||||
<div className="p-6">
|
||||
<ProjectsTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default function DashboardHeader({
|
|||
onCreateProject
|
||||
}: DashboardHeaderProps) {
|
||||
return (
|
||||
<header className="bg-card border-b">
|
||||
<header className="header-gradient border-b">
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
|
|
@ -62,4 +62,4 @@ export default function DashboardHeader({
|
|||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ export default function GanttPage() {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<section className="border-b bg-gradient-to-r from-blue-50/70 via-background to-violet-50/70 dark:from-blue-950/25 dark:via-background dark:to-violet-950/25">
|
||||
<section className="border-b header-gradient">
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-5 space-y-4">
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -55,7 +55,11 @@ export function MainLayout({ children }: MainLayoutProps) {
|
|||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<main>
|
||||
<SidebarTrigger />
|
||||
<div className="header-gradient border-b">
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<SidebarTrigger />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</main>
|
||||
</SidebarInset>
|
||||
|
|
|
|||
|
|
@ -185,13 +185,13 @@ export function ProjectDetailView({ project: initialProject }: ProjectDetailView
|
|||
/>
|
||||
|
||||
{/* Contenido principal */}
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="min-w-0 p-6 space-y-6">
|
||||
{/* Stats cards */}
|
||||
<ProjectStats project={project} taskStats={taskStats} />
|
||||
|
||||
{/* Tabs de contenido */}
|
||||
<Tabs defaultValue="overview" className="space-y-6">
|
||||
<TabsList>
|
||||
<TabsList className="flex flex-wrap items-center gap-1">
|
||||
<TabsTrigger value="overview" className="gap-2">
|
||||
<LayoutDashboard className="h-4 w-4" />
|
||||
Resumen
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export function ProjectHeader({ project, tasks = [], onProjectUpdated, onProject
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="border-b bg-card">
|
||||
<div className="border-b header-gradient">
|
||||
<div className="px-6 py-4">
|
||||
{/* Navegación */}
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-4">
|
||||
|
|
@ -84,8 +84,8 @@ export function ProjectHeader({ project, tasks = [], onProjectUpdated, onProject
|
|||
</div>
|
||||
|
||||
{/* Header principal */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="flex min-w-0 items-start gap-4">
|
||||
{/* Avatar */}
|
||||
<Avatar className="h-16 w-16 shrink-0">
|
||||
<AvatarFallback className="bg-gradient-to-br from-blue-500 to-purple-600 text-white font-semibold text-xl">
|
||||
|
|
@ -94,13 +94,15 @@ export function ProjectHeader({ project, tasks = [], onProjectUpdated, onProject
|
|||
</Avatar>
|
||||
|
||||
{/* Info principal */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight">{project.name}</h1>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight break-words">
|
||||
{project.name}
|
||||
</h1>
|
||||
<ProjectStatusBadge status={project.status} className="text-sm px-3 py-1" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
|
||||
<button
|
||||
onClick={handleCopyRef}
|
||||
className="flex items-center gap-1.5 hover:text-foreground transition-colors font-mono"
|
||||
|
|
@ -109,19 +111,16 @@ export function ProjectHeader({ project, tasks = [], onProjectUpdated, onProject
|
|||
{project.ref}
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
|
||||
{project.client !== "Sin cliente" && (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">|</span>
|
||||
<span>{project.client}</span>
|
||||
</>
|
||||
<span>{project.client}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Acciones */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleExportPDF}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Exportar PDF
|
||||
|
|
|
|||
|
|
@ -195,8 +195,9 @@ export function ProjectTaskGantt({ tasks }: ProjectTaskGanttProps) {
|
|||
|
||||
const maxTimelineWidth = Math.max(320, fitTimelineWidth);
|
||||
const baseTickWidth = zoom === "fit" ? 72 : zoom === "week" ? 84 : 36;
|
||||
const tickWidth = Math.min(baseTickWidth, maxTimelineWidth / Math.max(1, ticks.length));
|
||||
const timelineWidth = Math.max(320, ticks.length * tickWidth);
|
||||
const rawTimelineWidth = ticks.length * baseTickWidth;
|
||||
const timelineWidth = Math.max(320, Math.min(maxTimelineWidth, rawTimelineWidth));
|
||||
const tickWidth = timelineWidth / Math.max(1, ticks.length);
|
||||
|
||||
const today = startOfDay(new Date());
|
||||
const showToday = today >= visibleStart && today <= visibleEnd;
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ export function TasksSection({ tasks, projectId, isLoading, onTaskCreated, onTas
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="min-w-0 space-y-4">
|
||||
{/* Stats rápidos */}
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -282,10 +282,10 @@ export function TasksSection({ tasks, projectId, isLoading, onTaskCreated, onTas
|
|||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div className="flex flex-1 flex-wrap items-center gap-2">
|
||||
{/* Búsqueda */}
|
||||
<div className="relative w-full sm:w-[250px]">
|
||||
<div className="relative w-full sm:w-[240px]">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar tareas..."
|
||||
|
|
@ -297,7 +297,7 @@ export function TasksSection({ tasks, projectId, isLoading, onTaskCreated, onTas
|
|||
|
||||
{/* Filtro estado */}
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-[130px] h-9">
|
||||
<SelectTrigger className="w-full sm:w-[130px] h-9">
|
||||
<SelectValue placeholder="Estado" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -310,7 +310,7 @@ export function TasksSection({ tasks, projectId, isLoading, onTaskCreated, onTas
|
|||
|
||||
{/* Filtro prioridad */}
|
||||
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
|
||||
<SelectTrigger className="w-[130px] h-9">
|
||||
<SelectTrigger className="w-full sm:w-[130px] h-9">
|
||||
<SelectValue placeholder="Prioridad" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -331,7 +331,7 @@ export function TasksSection({ tasks, projectId, isLoading, onTaskCreated, onTas
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* Toggle vista */}
|
||||
<div className="flex items-center border rounded-md">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -76,10 +76,10 @@ export function DataTableToolbar({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div className="flex flex-1 flex-wrap items-center gap-2">
|
||||
{/* Búsqueda */}
|
||||
<div className="relative w-full sm:w-[300px]">
|
||||
<div className="relative w-full sm:w-[260px]">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar proyectos..."
|
||||
|
|
@ -91,7 +91,7 @@ export function DataTableToolbar({
|
|||
|
||||
{/* Filtro por estado */}
|
||||
<Select value={statusFilter} onValueChange={onStatusFilterChange}>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectTrigger className="w-full sm:w-[150px]">
|
||||
<SelectValue placeholder="Estado" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -119,7 +119,7 @@ export function DataTableToolbar({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* Contador de seleccionados */}
|
||||
{selectedCount > 0 && (
|
||||
<Badge variant="secondary" className="rounded-sm px-2 font-normal">
|
||||
|
|
@ -136,7 +136,7 @@ export function DataTableToolbar({
|
|||
Vista
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[180px]">
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuLabel>Columnas visibles</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{columns
|
||||
|
|
@ -158,9 +158,9 @@ export function DataTableToolbar({
|
|||
)}
|
||||
|
||||
{/* Exportar */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9"
|
||||
onClick={handleExport}
|
||||
disabled={!onExport}
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ export function DataTable<TData, TValue>({
|
|||
}, [searchKey, searchValue, table]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="min-w-0 space-y-4">
|
||||
{/* Tabla */}
|
||||
<div className="rounded-md border">
|
||||
<div className="min-w-0 rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export function ProjectsTable() {
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<DataTableToolbar
|
||||
searchValue={searchValue}
|
||||
onSearchChange={setSearchValue}
|
||||
|
|
@ -193,7 +193,7 @@ export function ProjectsTable() {
|
|||
getSelectedProjects={tableInfo?.getSelectedRows}
|
||||
getAllVisibleProjects={tableInfo?.getAllVisibleRows}
|
||||
/>
|
||||
<Button onClick={() => setIsCreateSheetOpen(true)} size="sm">
|
||||
<Button onClick={() => setIsCreateSheetOpen(true)} size="sm" className="shrink-0">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Nuevo proyecto
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1058,7 +1058,7 @@ export default function StatisticsPage() {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<header className="bg-card border-b sticky top-0 z-10">
|
||||
<header className="header-gradient border-b sticky top-0 z-10">
|
||||
<div className="px-6 py-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg">
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export function TaskHeader({ task, project, onTaskUpdated, onTaskDeleted }: Task
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="border-b bg-card">
|
||||
<div className="border-b header-gradient">
|
||||
<div className="px-6 py-4">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="flex items-center gap-2 text-sm text-muted-foreground mb-4">
|
||||
|
|
|
|||
|
|
@ -40,15 +40,17 @@ const AUTH_USER_KEY = "dolibarr_auth_user";
|
|||
* Obtiene la URL base de la API de Dolibarr
|
||||
*/
|
||||
function getApiUrl(): string {
|
||||
// Intentar obtener del entorno (cliente usa NEXT_PUBLIC_)
|
||||
const url = typeof window !== "undefined"
|
||||
? process.env.NEXT_PUBLIC_API_URL
|
||||
: process.env.DOLIBARR_API_URL || process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
// En cliente, usar proxy para evitar problemas de red/CORS
|
||||
if (typeof window !== "undefined") {
|
||||
return "/api/dolibarr";
|
||||
}
|
||||
|
||||
const url = process.env.DOLIBARR_API_URL || process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
if (!url) {
|
||||
throw new Error("API URL not configured");
|
||||
}
|
||||
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue