Files
project.linkedcorp.net/frontend/src/pages/Proyectos/ProyectoDetallePage.jsx
T
2026-10-23 18:19:24 -05:00

98 lines
5.2 KiB
React

import { useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { ArrowLeft, FileText } from 'lucide-react';
import { ProjectReportDialog } from './ProjectReportDialog.jsx';
import { getAiStatus } from '../../services/ai.js';
import { useUi } from '../../context/ui-context.jsx';
import { useAsync } from '../../hooks/useAsync.js';
import { useUserDrafts } from '../../hooks/useDraftForm.js';
import { getProject } from '../../services/projects.js';
import { Button } from '../../components/Button.jsx';
import { Tabs } from '../../components/Tabs.jsx';
import { StatusBadge } from '../../components/StatusBadge.jsx';
import { CommentsPanel } from '../../components/CommentsPanel.jsx';
import { AttachmentsPanel } from '../../components/AttachmentsPanel.jsx';
import ProjectInfoTab from './ProjectInfoTab.jsx';
import ProjectMembersTab from './ProjectMembersTab.jsx';
import ProjectActivitiesTab from './ProjectActivitiesTab.jsx';
import ProjectGanttTab from './ProjectGanttTab.jsx';
import ProjectCalendarTab from './ProjectCalendarTab.jsx';
export default function ProyectoDetallePage() {
const { id } = useParams();
const projectId = Number(id);
const { t } = useUi();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
// La pestaña vive en la URL (?tab=): asi el retorno tras el login (o un F5) vuelve a la misma
// pestaña, y el aviso de cambios sin guardar intercepta el cambio de pestaña.
// Llegada desde el buscador (Proyectos): ?tab=activities&activity=<id>&activityTab=tasks abre
// directo la actividad (y su pestaña de tareas) que coincidio con la busqueda.
const tab = searchParams.get('tab') || 'info';
const setTab = (key) => setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set('tab', key);
next.delete('activity');
next.delete('activityTab');
return next;
}, { replace: true });
// Pestañas con borradores pendientes de este proyecto (el borrador guarda la URL con su ?tab=).
const drafts = useUserDrafts();
const dirtyTab = (key) => drafts.some((d) => {
if (typeof d.path !== 'string') return false;
const [path, query = ''] = d.path.split('?');
return path === `/proyectos/${projectId}` && (new URLSearchParams(query).get('tab') || 'info') === key;
});
const deepLinkActivityId = searchParams.get('activity') ? Number(searchParams.get('activity')) : null;
const deepLinkActivityTab = searchParams.get('activityTab') || undefined;
const { data: project, reload } = useAsync(() => getProject(projectId), [projectId]);
// Informe con IA: solo si el superadmin configuro la clave (el backend valida lectura del proyecto).
const { data: aiStatus } = useAsync(() => getAiStatus().catch(() => ({ enabled: false })), []);
const [reportOpen, setReportOpen] = useState(false);
if (!project) return <div className="text-muted">{t('common_loading')}</div>;
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<Button variant="subtle" icon={ArrowLeft} onClick={() => navigate('/proyectos')}>{t('projects_title')}</Button>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
<h2 className="page-title mt-0">{project.code} — {project.name}</h2>
<StatusBadge name={project.status_name} color={project.status_color} />
{aiStatus?.enabled && (
<div style={{ marginLeft: 'auto' }}>
<Button variant="standard" icon={FileText} onClick={() => setReportOpen(true)}>{t('ai_report_button')}</Button>
</div>
)}
</div>
<ProjectReportDialog open={reportOpen} projectId={projectId} onClose={() => setReportOpen(false)} />
<Tabs
tabs={[
{ key: 'info', label: t('tab_info'), dirty: dirtyTab('info') },
{ key: 'members', dirty: dirtyTab('members'), label: t('tab_members'), count: project.members_count },
{ key: 'activities', dirty: dirtyTab('activities'), label: t('tab_activities'), count: `${project.activities_complete}/${project.activities_total}` },
{ key: 'gantt', label: t('tab_gantt') },
{ key: 'calendar', label: t('tab_calendar') },
{ key: 'comments', dirty: dirtyTab('comments'), label: t('tab_comments'), count: project.comments_count },
{ key: 'documents', dirty: dirtyTab('documents'), label: t('tab_documents'), count: project.documents_count },
]}
active={tab}
onChange={setTab}
/>
{tab === 'info' && <ProjectInfoTab project={project} onChanged={reload} />}
{tab === 'members' && <ProjectMembersTab project={project} onChanged={reload} />}
{tab === 'activities' && (
<ProjectActivitiesTab project={project} projectId={projectId} openActivityId={deepLinkActivityId} openActivityTab={deepLinkActivityTab} />
)}
{tab === 'gantt' && <ProjectGanttTab projectId={projectId} />}
{tab === 'calendar' && <ProjectCalendarTab projectId={projectId} />}
{tab === 'comments' && <CommentsPanel entityType="project" entityId={projectId} onChanged={reload} />}
{tab === 'documents' && <AttachmentsPanel entityType="project" entityId={projectId} onChanged={reload} />}
</div>
);
}