trello_fake/app/perfil/page.tsx

312 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2026-02-06 19:06:25 +00:00
"use client";
import { useEffect, useState } from "react";
import { User, Mail, Building, Calendar, Shield, Briefcase, Globe, Phone } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { getUser } from "@/lib/usersService";
import { getProjects } from "@/lib/projectsService";
import { Project } from "@/types/project";
interface DolibarrUser {
id: string;
login: string;
firstname: string;
lastname: string;
email: string;
admin: string;
statut: string;
employee: string;
job: string;
address: string;
zip: string;
town: string;
state_id: string;
office_phone: string;
user_mobile: string;
fk_member: string;
datelastlogin: number;
datepreviouslogin: number;
datec: string;
datem: string;
fk_soc: string;
entity: string;
}
export default function PerfilPage() {
const [user, setUser] = useState<DolibarrUser | null>(null);
const [projects, setProjects] = useState<Project[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function loadData() {
try {
const [userData, projectsData] = await Promise.all([
getUser(),
getProjects(),
]);
setUser(userData);
setProjects(projectsData);
} catch (err) {
console.error("Error loading profile data:", err);
setError("No se pudo cargar la información del perfil");
} finally {
setIsLoading(false);
}
}
loadData();
}, []);
if (isLoading) {
return <ProfileSkeleton />;
}
if (error || !user) {
return (
<div className="p-6">
<div className="flex flex-col items-center justify-center py-12 text-center">
<User className="h-12 w-12 text-muted-foreground mb-4" />
<h2 className="text-lg font-semibold mb-2">Error al cargar el perfil</h2>
<p className="text-sm text-muted-foreground">{error}</p>
</div>
</div>
);
}
const fullName = `${user.firstname} ${user.lastname}`;
const initials = `${user.firstname?.charAt(0) || ""}${user.lastname?.charAt(0) || ""}`;
const isAdmin = user.admin === "1";
const isActive = user.statut === "1";
const isEmployee = user.employee === "1";
// Stats from projects
const activeProjects = projects.filter((p) => p.status === "1").length;
const completedProjects = projects.filter((p) => p.status === "2").length;
const totalBudget = projects.reduce((acc, p) => acc + (p.budget || 0), 0);
const formatDate = (timestamp: number | string) => {
if (!timestamp) return "N/A";
const ts = typeof timestamp === "string" ? parseInt(timestamp) : timestamp;
const date = new Date(ts * 1000);
return date.toLocaleDateString("es-ES", {
day: "2-digit",
month: "long",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
return (
<div className="p-6 space-y-6 max-w-4xl mx-auto">
{/* Header */}
<div>
<h1 className="text-2xl font-bold tracking-tight">Mi Perfil</h1>
<p className="text-muted-foreground">Información de tu cuenta de Dolibarr</p>
</div>
{/* Profile card */}
<Card>
<CardContent className="p-6">
<div className="flex flex-col sm:flex-row items-center sm:items-start gap-6">
{/* Avatar */}
<Avatar className="h-24 w-24 text-2xl">
<AvatarFallback className="bg-gradient-to-br from-blue-500 to-purple-600 text-white text-2xl">
{initials}
</AvatarFallback>
</Avatar>
{/* Info */}
<div className="flex-1 text-center sm:text-left space-y-3">
<div>
<h2 className="text-2xl font-bold">{fullName}</h2>
<p className="text-muted-foreground">@{user.login}</p>
</div>
<div className="flex flex-wrap items-center justify-center sm:justify-start gap-2">
<Badge variant={isActive ? "default" : "secondary"} className={isActive ? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" : ""}>
{isActive ? "Activo" : "Inactivo"}
</Badge>
{isAdmin && (
<Badge variant="outline" className="bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800">
<Shield className="h-3 w-3 mr-1" />
Administrador
</Badge>
)}
{isEmployee && (
<Badge variant="outline">
<Briefcase className="h-3 w-3 mr-1" />
Empleado
</Badge>
)}
</div>
</div>
</div>
</CardContent>
</Card>
{/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<Card>
<CardContent className="p-4 text-center">
<p className="text-3xl font-bold text-blue-600">{activeProjects}</p>
<p className="text-sm text-muted-foreground">Proyectos activos</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 text-center">
<p className="text-3xl font-bold text-green-600">{completedProjects}</p>
<p className="text-sm text-muted-foreground">Proyectos completados</p>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 text-center">
<p className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
{totalBudget.toLocaleString("es-ES")} &euro;
</p>
<p className="text-sm text-muted-foreground">Presupuesto total gestionado</p>
</CardContent>
</Card>
</div>
{/* Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Contact info */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Informaci&oacute;n de Contacto</CardTitle>
<CardDescription>Datos de contacto registrados en Dolibarr</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<InfoRow icon={Mail} label="Email" value={user.email || "No configurado"} />
<Separator />
<InfoRow icon={Phone} label="Tel&eacute;fono oficina" value={user.office_phone || "No configurado"} />
<Separator />
<InfoRow icon={Phone} label="M&oacute;vil" value={user.user_mobile || "No configurado"} />
<Separator />
<InfoRow icon={Briefcase} label="Puesto" value={user.job || "No configurado"} />
</CardContent>
</Card>
{/* Account info */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Informaci&oacute;n de la Cuenta</CardTitle>
<CardDescription>Detalles de tu cuenta en el sistema</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<InfoRow icon={User} label="ID de usuario" value={`#${user.id}`} />
<Separator />
<InfoRow icon={Globe} label="Entidad" value={user.entity || "1"} />
<Separator />
<InfoRow
icon={Calendar}
label="&Uacute;ltimo acceso"
value={user.datelastlogin ? formatDate(user.datelastlogin) : "N/A"}
/>
<Separator />
<InfoRow
icon={Calendar}
label="Acceso anterior"
value={user.datepreviouslogin ? formatDate(user.datepreviouslogin) : "N/A"}
/>
</CardContent>
</Card>
</div>
{/* Address */}
{(user.address || user.town || user.zip) && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Direcci&oacute;n</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-start gap-3">
<Building className="h-5 w-5 text-muted-foreground mt-0.5" />
<div className="text-sm">
{user.address && <p>{user.address}</p>}
{(user.zip || user.town) && (
<p className="text-muted-foreground">
{[user.zip, user.town].filter(Boolean).join(", ")}
</p>
)}
</div>
</div>
</CardContent>
</Card>
)}
</div>
);
}
// Reusable info row component
function InfoRow({ icon: Icon, label, value }: { icon: React.ComponentType<{ className?: string }>; label: string; value: string }) {
return (
<div className="flex items-center gap-3">
<Icon className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="text-xs text-muted-foreground">{label}</p>
<p className="text-sm font-medium truncate">{value}</p>
</div>
</div>
);
}
// Skeleton
function ProfileSkeleton() {
return (
<div className="p-6 space-y-6 max-w-4xl mx-auto">
<div>
<Skeleton className="h-8 w-48 mb-2" />
<Skeleton className="h-4 w-72" />
</div>
<Card>
<CardContent className="p-6">
<div className="flex flex-col sm:flex-row items-center sm:items-start gap-6">
<Skeleton className="h-24 w-24 rounded-full" />
<div className="space-y-3 flex-1">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-32" />
<div className="flex gap-2">
<Skeleton className="h-6 w-16" />
<Skeleton className="h-6 w-28" />
</div>
</div>
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<Card key={i}>
<CardContent className="p-4 text-center space-y-2">
<Skeleton className="h-8 w-16 mx-auto" />
<Skeleton className="h-4 w-32 mx-auto" />
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[1, 2].map((i) => (
<Card key={i}>
<CardHeader>
<Skeleton className="h-6 w-48" />
<Skeleton className="h-4 w-64" />
</CardHeader>
<CardContent className="space-y-4">
{[1, 2, 3, 4].map((j) => (
<div key={j}>
<Skeleton className="h-10 w-full" />
</div>
))}
</CardContent>
</Card>
))}
</div>
</div>
);
}