diff --git a/CHANGELOG.md b/CHANGELOG.md index fb954cd..0b62e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog - project.linkedcorp.net +## [2.6.0] - 2026-09-30 +### Changed +- Listado de Proyectos: el porcentaje de avance ahora va **dentro** de la barra de progreso + (antes iba aparte, a la derecha) y el fondo de la barra pasa a un gris visible sobre el fondo + oscuro (antes casi no se notaba). Libera espacio horizontal en la fila. +### Added +- Ícono de "compartido" en el listado de Proyectos: aparece solo si el proyecto tiene otros + usuarios además del dueño. Al hacer clic muestra con quién está compartido y su permiso + (Lectura/Escritura). + ## [2.5.0] - 2026-09-25 ### Fixed - **Bug de seguridad real**: cualquier usuario autenticado veía TODOS los proyectos del sistema diff --git a/VERSION b/VERSION index 437459c..e70b452 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.0 +2.6.0 diff --git a/backend/package.json b/backend/package.json index 0f9b0d0..fd5580d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "project-linkedcorp-backend", - "version": "2.5.0", + "version": "2.6.0", "private": true, "type": "module", "main": "src/server.js", diff --git a/backend/src/modules/projects/repository.js b/backend/src/modules/projects/repository.js index ff39b24..dda9156 100644 --- a/backend/src/modules/projects/repository.js +++ b/backend/src/modules/projects/repository.js @@ -8,7 +8,8 @@ const LIST_SELECT = ` p.requesting_department_id, rd.name AS requesting_department_name, p.responsible_department_id, sd.name AS responsible_department_name, p.start_date, p.estimated_end_date, p.actual_end_date, p.progress_pct, - p.risk_level, p.notes, p.created_by, p.created_at, p.updated_at, p.deleted_at, p.deleted_by + p.risk_level, p.notes, p.created_by, p.created_at, p.updated_at, p.deleted_at, p.deleted_by, + (SELECT COUNT(*) FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id <> p.owner_user_id) AS shared_count FROM projects p JOIN priorities pr ON pr.id = p.priority_id JOIN project_statuses ps ON ps.id = p.status_id diff --git a/frontend/package.json b/frontend/package.json index e98a9eb..b9fe558 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "project-linkedcorp-frontend", "private": true, - "version": "2.5.0", + "version": "2.6.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/components/SharedWithIcon.jsx b/frontend/src/components/SharedWithIcon.jsx new file mode 100644 index 0000000..b6a386e --- /dev/null +++ b/frontend/src/components/SharedWithIcon.jsx @@ -0,0 +1,50 @@ +import { useState } from 'react'; +import { Users } from 'lucide-react'; +import { Flyout } from './Flyout.jsx'; +import { Badge } from './Badge.jsx'; +import { useUi } from '../context/ui-context.jsx'; +import { getProject } from '../services/projects.js'; + +// Icono que solo aparece si el proyecto esta compartido (shared_count > 0, ver +// projects/repository.js#findAll) -- al abrir, carga los miembros (menos el dueno) una sola vez +// por click y muestra su permiso (Lectura/Escritura). +export function SharedWithIcon({ projectId, count }) { + const { t } = useUi(); + const [members, setMembers] = useState(null); + const [loading, setLoading] = useState(false); + + if (!count) return null; + + async function loadMembers() { + if (members) return; + setLoading(true); + try { + const project = await getProject(projectId); + setMembers(project.members.filter((m) => m.role_in_project !== 'owner')); + } finally { + setLoading(false); + } + } + + return ( + ( + + )}> +
{t('project_shared_with')}
+ {loading &&
{t('common_loading')}
} + {!loading && members?.map((m) => ( +
+ {m.name} + + {t(m.role_in_project === 'write' ? 'project_role_write' : 'project_role_read')} + +
+ ))} +
+ ); +} diff --git a/frontend/src/components/StatusBadge.jsx b/frontend/src/components/StatusBadge.jsx index 4fe126f..ba46154 100644 --- a/frontend/src/components/StatusBadge.jsx +++ b/frontend/src/components/StatusBadge.jsx @@ -16,11 +16,9 @@ export function DueBadge({ status }) { export function ProgressBar({ value }) { return ( -
-
-
-
- {Math.round(value)}% +
+
+ {Math.round(value)}%
); } diff --git a/frontend/src/i18n/messages.js b/frontend/src/i18n/messages.js index 516f2a7..8441c24 100644 --- a/frontend/src/i18n/messages.js +++ b/frontend/src/i18n/messages.js @@ -163,6 +163,7 @@ export const messages = { project_estimated_end_date: 'Fin estimado', project_actual_end_date: 'Fin real', project_progress: 'Avance', + project_shared_with: 'Compartido con', project_requesting_department: 'Departamento solicitante', project_responsible_department: 'Departamento responsable', project_risk: 'Riesgo', @@ -461,6 +462,7 @@ export const messages = { project_estimated_end_date: 'Estimated end', project_actual_end_date: 'Actual end', project_progress: 'Progress', + project_shared_with: 'Shared with', project_requesting_department: 'Requesting department', project_responsible_department: 'Responsible department', project_risk: 'Risk', diff --git a/frontend/src/pages/Proyectos/ProyectosPage.jsx b/frontend/src/pages/Proyectos/ProyectosPage.jsx index ebcebaa..a0a92b1 100644 --- a/frontend/src/pages/Proyectos/ProyectosPage.jsx +++ b/frontend/src/pages/Proyectos/ProyectosPage.jsx @@ -19,6 +19,7 @@ import { ComboBox } from '../../components/ComboBox.jsx'; import { StatusBadge, ProgressBar } from '../../components/StatusBadge.jsx'; import { Badge } from '../../components/Badge.jsx'; import { DetailPopup } from '../../components/DetailPopup.jsx'; +import { SharedWithIcon } from '../../components/SharedWithIcon.jsx'; const CAN_DELETE_ROLES = ['superadmin', 'admin', 'it_manager', 'project_lead']; @@ -168,7 +169,8 @@ export default function ProyectosPage() { { key: 'status_name', label: t('common_status'), render: (r) => }, { key: 'priority_name', label: t('common_priority'), sortValue: (r) => r.priority_level, render: (r) => }, { key: 'owner_name', label: t('project_owner') }, - { key: 'progress_pct', label: t('project_progress'), render: (r) =>
}, + { key: 'progress_pct', label: t('project_progress'), render: (r) =>
}, + { key: 'shared', label: '', sortable: false, render: (r) => }, { key: 'risk_level', label: t('project_risk'), render: (r) => {t(`risk_${r.risk_level}`)} }, { key: 'updated_at', label: t('project_updated_at'), render: (r) => String(r.updated_at).slice(0, 16) }, ...(showTrash ? [{ key: 'deleted_at', label: t('project_deleted_at'), render: (r) => String(r.deleted_at).slice(0, 10) }] : []), diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 11d11f0..ec066c1 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -378,8 +378,15 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; } .expander-desc { font-size: .72rem; color: rgb(var(--muted)); } .expander-body { padding: 14px; border-top: 1px solid rgb(var(--line)); } -.progress-bar { height: 6px; border-radius: 999px; background: rgb(var(--field)); overflow: hidden; } -.progress-bar-fill { height: 100%; background: rgb(var(--accent)); } +.progress-bar { + position: relative; height: 18px; border-radius: 999px; overflow: hidden; + background: rgb(var(--muted) / .32); +} +.progress-bar-fill { height: 100%; background: rgb(var(--accent)); transition: width .2s ease; } +.progress-bar-label { + position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; + font-size: .66rem; font-weight: 700; color: #fff; text-shadow: 0 1px 2px rgb(0 0 0 / .55); +} .text-muted { color: rgb(var(--muted)); } .mt-0 { margin-top: 0; }