From 82308faeb1e3791016b52ccd3fc73a11f40e334f Mon Sep 17 00:00:00 2001 From: Milton Gonzalez Date: Wed, 30 Sep 2026 08:04:50 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20version=202.6.0=20--=20barra=20de=20pro?= =?UTF-8?q?greso=20redise=C3=B1ada=20+=20icono=20de=20compartido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Porcentaje de avance dentro de la barra (antes aparte), fondo gris visible en vez de casi invisible sobre el tema oscuro. Nuevo icono en el listado de Proyectos que muestra con quien esta compartido cada proyecto y su permiso, solo si aplica. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 10 ++++ VERSION | 2 +- backend/package.json | 2 +- backend/src/modules/projects/repository.js | 3 +- frontend/package.json | 2 +- frontend/src/components/SharedWithIcon.jsx | 50 +++++++++++++++++++ frontend/src/components/StatusBadge.jsx | 8 ++- frontend/src/i18n/messages.js | 2 + .../src/pages/Proyectos/ProyectosPage.jsx | 4 +- frontend/src/styles/app.css | 11 +++- 10 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/SharedWithIcon.jsx 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; }