fix(ui): no desmontar la pantalla en cada recarga en segundo plano
Las 11 pantallas con listado/detalle bloqueaban TODO su render con "if (loading || !data) return <Cargando/>". Como useAsync pone loading=true en cada reload (no solo en la carga inicial), cualquier accion dentro de un DetailPopup abierto que dispara un reload del padre (agregar subtarea, agregar dependencia, guardar comentario, etc.) desmontaba momentaneamente TODA la pantalla -y con ella el popup y su estado interno (pestaña activa)-, que volvia a montarse desde cero con sus valores iniciales. Sintoma reportado: al agregar una subtarea (Enter o boton "Nuevo"), el popup de la actividad volvia a la pestaña Info en vez de quedarse en Subtareas. Fix: la guarda ahora es solo "if (!data)" -bloquea unicamente la carga inicial, cuando todavia no hay nada que mostrar-. Las recargas subsiguientes actualizan el contenido en el sitio sin desmontar nada. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { Button } from './Button.jsx';
|
|||||||
export function AttachmentsPanel({ entityType, entityId }) {
|
export function AttachmentsPanel({ entityType, entityId }) {
|
||||||
const { t, openDialog } = useUi();
|
const { t, openDialog } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: files, loading, reload } = useAsync(() => listAttachments(entityType, entityId), [entityType, entityId]);
|
const { data: files, reload } = useAsync(() => listAttachments(entityType, entityId), [entityType, entityId]);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
async function handleFile(e) {
|
async function handleFile(e) {
|
||||||
@@ -33,7 +33,7 @@ export function AttachmentsPanel({ entityType, entityId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || !files) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!files) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Button } from './Button.jsx';
|
|||||||
export function CommentsPanel({ entityType, entityId }) {
|
export function CommentsPanel({ entityType, entityId }) {
|
||||||
const { t } = useUi();
|
const { t } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: comments, loading, reload } = useAsync(() => listComments(entityType, entityId), [entityType, entityId]);
|
const { data: comments, reload } = useAsync(() => listComments(entityType, entityId), [entityType, entityId]);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
async function send() {
|
async function send() {
|
||||||
@@ -20,7 +20,7 @@ export function CommentsPanel({ entityType, entityId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || !comments) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!comments) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function emptyForm(fields) {
|
|||||||
export function CatalogEditor({ catalogKey, fields, title }) {
|
export function CatalogEditor({ catalogKey, fields, title }) {
|
||||||
const { t, openDialog, pushToast } = useUi();
|
const { t, openDialog, pushToast } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: items, loading, reload } = useAsync(() => listCatalog(catalogKey), [catalogKey]);
|
const { data: items, reload } = useAsync(() => listCatalog(catalogKey), [catalogKey]);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
const [form, setForm] = useState(emptyForm(fields));
|
const [form, setForm] = useState(emptyForm(fields));
|
||||||
@@ -75,7 +75,7 @@ export function CatalogEditor({ catalogKey, fields, title }) {
|
|||||||
: undefined,
|
: undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (loading || !items) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!items) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { ProgressBar, DueBadge } from '../../components/StatusBadge.jsx';
|
|||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { t } = useUi();
|
const { t } = useUi();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, loading } = useAsync(getSummary, []);
|
const { data } = useAsync(getSummary, []);
|
||||||
|
|
||||||
if (loading || !data) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!data) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
const { kpis, myActivities, projectsByStatus } = data;
|
const { kpis, myActivities, projectsByStatus } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function flatten(nodes, acc = []) {
|
|||||||
export default function DepartmentsPanel() {
|
export default function DepartmentsPanel() {
|
||||||
const { t, openDialog, pushToast } = useUi();
|
const { t, openDialog, pushToast } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: tree, loading, reload } = useAsync(listDepartments, []);
|
const { data: tree, reload } = useAsync(listDepartments, []);
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [form, setForm] = useState({ name: '', parentId: '' });
|
const [form, setForm] = useState({ name: '', parentId: '' });
|
||||||
@@ -71,7 +71,7 @@ export default function DepartmentsPanel() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || !tree) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!tree) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const EMPTY_FORM = { name: '', email: '', roleId: '', departmentId: '', position
|
|||||||
export default function UsersPanel() {
|
export default function UsersPanel() {
|
||||||
const { t, pushToast } = useUi();
|
const { t, pushToast } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: users, loading, reload } = useAsync(listUsers, []);
|
const { data: users, reload } = useAsync(listUsers, []);
|
||||||
const { data: roles } = useAsync(listRoles, []);
|
const { data: roles } = useAsync(listRoles, []);
|
||||||
const { data: deptTree } = useAsync(listDepartments, []);
|
const { data: deptTree } = useAsync(listDepartments, []);
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export default function UsersPanel() {
|
|||||||
) },
|
) },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (loading || !users) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!users) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const EMPTY_FORM = { name: '', description: '', responsibleUserId: '', priorityI
|
|||||||
export default function ProjectActivitiesTab({ projectId }) {
|
export default function ProjectActivitiesTab({ projectId }) {
|
||||||
const { t, pushToast } = useUi();
|
const { t, pushToast } = useUi();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
const { data: activities, loading, reload } = useAsync(() => listActivitiesByProject(projectId), [projectId]);
|
const { data: activities, reload } = useAsync(() => listActivitiesByProject(projectId), [projectId]);
|
||||||
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
|
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
|
||||||
const { data: statuses } = useAsync(() => listCatalog('activity-statuses'), []);
|
const { data: statuses } = useAsync(() => listCatalog('activity-statuses'), []);
|
||||||
const { data: users } = useAsync(listUsers, []);
|
const { data: users } = useAsync(listUsers, []);
|
||||||
@@ -70,7 +70,7 @@ export default function ProjectActivitiesTab({ projectId }) {
|
|||||||
{ key: 'progress_pct', label: t('activity_progress'), render: (r) => <div style={{ width: 100 }}><ProgressBar value={Number(r.progress_pct)} /></div> },
|
{ key: 'progress_pct', label: t('activity_progress'), render: (r) => <div style={{ width: 100 }}><ProgressBar value={Number(r.progress_pct)} /></div> },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (loading || !activities) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!activities) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function buildGrid(year, month) {
|
|||||||
|
|
||||||
export default function ProjectCalendarTab({ projectId }) {
|
export default function ProjectCalendarTab({ projectId }) {
|
||||||
const { t } = useUi();
|
const { t } = useUi();
|
||||||
const { data: activities, loading } = useAsync(() => listActivitiesByProject(projectId), [projectId]);
|
const { data: activities } = useAsync(() => listActivitiesByProject(projectId), [projectId]);
|
||||||
const [cursor, setCursor] = useState(() => { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), 1); });
|
const [cursor, setCursor] = useState(() => { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), 1); });
|
||||||
|
|
||||||
const cells = useMemo(() => buildGrid(cursor.getFullYear(), cursor.getMonth()), [cursor]);
|
const cells = useMemo(() => buildGrid(cursor.getFullYear(), cursor.getMonth()), [cursor]);
|
||||||
@@ -35,7 +35,7 @@ export default function ProjectCalendarTab({ projectId }) {
|
|||||||
return map;
|
return map;
|
||||||
}, [activities]);
|
}, [activities]);
|
||||||
|
|
||||||
if (loading || !activities) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!activities) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { getGantt } from '../../services/projects.js';
|
|||||||
|
|
||||||
export default function ProjectGanttTab({ projectId }) {
|
export default function ProjectGanttTab({ projectId }) {
|
||||||
const { t } = useUi();
|
const { t } = useUi();
|
||||||
const { data: items, loading } = useAsync(() => getGantt(projectId), [projectId]);
|
const { data: items } = useAsync(() => getGantt(projectId), [projectId]);
|
||||||
|
|
||||||
if (loading || !items) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!items) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
const withDates = items.filter((i) => i.startDate && i.dueDate);
|
const withDates = items.filter((i) => i.startDate && i.dueDate);
|
||||||
if (withDates.length === 0) return <div className="empty-state">{t('common_empty')}</div>;
|
if (withDates.length === 0) return <div className="empty-state">{t('common_empty')}</div>;
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export default function ProyectoDetallePage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [tab, setTab] = useState('info');
|
const [tab, setTab] = useState('info');
|
||||||
|
|
||||||
const { data: project, loading, reload } = useAsync(() => getProject(projectId), [projectId]);
|
const { data: project, reload } = useAsync(() => getProject(projectId), [projectId]);
|
||||||
|
|
||||||
if (loading || !project) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!project) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function ProyectosPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const runAction = useAction();
|
const runAction = useAction();
|
||||||
|
|
||||||
const { data: projects, loading, reload } = useAsync(listProjects, []);
|
const { data: projects, reload } = useAsync(listProjects, []);
|
||||||
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
|
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
|
||||||
const { data: statuses } = useAsync(() => listCatalog('project-statuses'), []);
|
const { data: statuses } = useAsync(() => listCatalog('project-statuses'), []);
|
||||||
const { data: types } = useAsync(() => listCatalog('project-types'), []);
|
const { data: types } = useAsync(() => listCatalog('project-types'), []);
|
||||||
@@ -75,7 +75,7 @@ export default function ProyectosPage() {
|
|||||||
{ key: 'risk_level', label: t('project_risk'), render: (r) => <Badge color={RISK_COLOR[r.risk_level]}>{t(`risk_${r.risk_level}`)}</Badge> },
|
{ key: 'risk_level', label: t('project_risk'), render: (r) => <Badge color={RISK_COLOR[r.risk_level]}>{t(`risk_${r.risk_level}`)}</Badge> },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (loading || !projects) return <div className="text-muted">{t('common_loading')}</div>;
|
if (!projects) return <div className="text-muted">{t('common_loading')}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user