-
- {{ getInitials(scope.row.full_name) }}
-
{{ scope.row.full_name }}
{{ scope.row.email }}
@@ -195,13 +192,6 @@ const activeCount = computed(() => allUsers.value.filter(u => u.status === 'ACTI
const pendingCount = computed(() => allUsers.value.filter(u => u.status === 'PENDING').length);
const disabledCount = computed(() => allUsers.value.filter(u => u.status === 'DISABLED').length);
-const getInitials = (name: string) => {
- if (!name) return '?';
- const parts = name.trim().split(/\s+/);
- if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
- return name.slice(0, 2).toUpperCase();
-};
-
const statusType = (status: string) => {
switch (status) {
case "ACTIVE": return "success";
@@ -487,37 +477,6 @@ onBeforeUnmount(() => {
.user-cell {
display: flex;
align-items: center;
- gap: 12px;
-}
-
-.user-avatar {
- width: 36px;
- height: 36px;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 13px;
- font-weight: 700;
- color: #fff;
- flex-shrink: 0;
- background: var(--ctms-primary);
-}
-
-.user-avatar.avatar--active {
- background: linear-gradient(135deg, #3f8f6b, #2d7a5a);
-}
-
-.user-avatar.avatar--pending {
- background: linear-gradient(135deg, #c58b2a, #a87420);
-}
-
-.user-avatar.avatar--disabled {
- background: linear-gradient(135deg, #94a3b8, #64748b);
-}
-
-.user-avatar.avatar--rejected {
- background: linear-gradient(135deg, #c24b4b, #a83a3a);
}
.user-info {
diff --git a/frontend/src/views/ia/ProjectOverview.vue b/frontend/src/views/ia/ProjectOverview.vue
index 3f3fc11c..087f039b 100644
--- a/frontend/src/views/ia/ProjectOverview.vue
+++ b/frontend/src/views/ia/ProjectOverview.vue
@@ -382,8 +382,17 @@ watch(
}
.legend-dot.active {
- border-color: var(--ctms-primary);
- box-shadow: 0 0 0 2px rgba(63, 93, 117, 0.18);
+ position: relative;
+ border: 3px solid #2f5f86;
+ box-shadow: 0 0 0 3px rgba(47, 95, 134, 0.14);
+}
+
+.legend-dot.active::after {
+ content: "";
+ position: absolute;
+ inset: 2px;
+ border-radius: inherit;
+ background: #2f5f86;
}
.legend-dot.pending {
diff --git a/frontend/src/views/ia/project-overview/CenterProgressRow.vue b/frontend/src/views/ia/project-overview/CenterProgressRow.vue
index 828edfe4..08620c32 100644
--- a/frontend/src/views/ia/project-overview/CenterProgressRow.vue
+++ b/frontend/src/views/ia/project-overview/CenterProgressRow.vue
@@ -100,7 +100,7 @@ const connectorClass = (status: StageStatus) => `connector-${status.toLowerCase(
align-items: flex-start;
gap: 8px;
overflow-x: auto;
- padding-bottom: 4px;
+ padding: 10px 0 4px;
width: 100%;
min-width: 0;
}
@@ -125,7 +125,7 @@ const connectorClass = (status: StageStatus) => `connector-${status.toLowerCase(
height: 2px;
background-color: var(--ctms-border-color);
border-radius: 999px;
- margin-top: 6px;
+ margin-top: 8px;
}
.stage-connector::after {
@@ -145,7 +145,9 @@ const connectorClass = (status: StageStatus) => `connector-${status.toLowerCase(
}
.connector-in_progress {
- background: linear-gradient(90deg, var(--ctms-primary), rgba(63, 93, 117, 0.3));
+ height: 3px;
+ background: linear-gradient(90deg, #2f5f86 0%, rgba(47, 95, 134, 0.42) 68%, rgba(47, 95, 134, 0.14) 100%);
+ box-shadow: 0 2px 8px rgba(47, 95, 134, 0.18);
}
.connector-blocked {
@@ -158,8 +160,10 @@ const connectorClass = (status: StageStatus) => `connector-${status.toLowerCase(
}
.connector-in_progress::after {
- border-top-color: var(--ctms-primary);
- border-right-color: var(--ctms-primary);
+ width: 7px;
+ height: 7px;
+ border-top-color: #2f5f86;
+ border-right-color: #2f5f86;
}
.connector-blocked::after {
diff --git a/frontend/src/views/ia/project-overview/StageNode.test.ts b/frontend/src/views/ia/project-overview/StageNode.test.ts
new file mode 100644
index 00000000..c252fb30
--- /dev/null
+++ b/frontend/src/views/ia/project-overview/StageNode.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const readStageNode = () => readFileSync(resolve(__dirname, "./StageNode.vue"), "utf8");
+const readCenterProgressRow = () => readFileSync(resolve(__dirname, "./CenterProgressRow.vue"), "utf8");
+const readProjectOverview = () => readFileSync(resolve(__dirname, "../ProjectOverview.vue"), "utf8");
+
+describe("project overview stage progress styling", () => {
+ it("makes the in-progress stage visually distinct from pending stages", () => {
+ const source = readStageNode();
+
+ expect(source).toContain(".stage-in_progress .stage-dot");
+ expect(source).toContain("border: 3px solid #2f5f86");
+ expect(source).toContain(".stage-in_progress .stage-dot::after");
+ expect(source).toContain(".stage-in_progress .stage-dot::before");
+ expect(source).toContain("animation: progress-halo");
+ expect(source).toContain(".stage-in_progress .stage-label");
+ expect(source).toContain("font-weight: 800");
+ });
+
+ it("highlights the active connector and legend consistently", () => {
+ const rowSource = readCenterProgressRow();
+ const overviewSource = readProjectOverview();
+
+ expect(rowSource).toContain(".connector-in_progress");
+ expect(rowSource).toContain("height: 3px");
+ expect(rowSource).toContain("linear-gradient(90deg, #2f5f86");
+ expect(overviewSource).toContain(".legend-dot.active::after");
+ expect(overviewSource).toContain("background: #2f5f86");
+ });
+});
diff --git a/frontend/src/views/ia/project-overview/StageNode.vue b/frontend/src/views/ia/project-overview/StageNode.vue
index e7229964..c6b0c4a3 100644
--- a/frontend/src/views/ia/project-overview/StageNode.vue
+++ b/frontend/src/views/ia/project-overview/StageNode.vue
@@ -44,11 +44,14 @@ const statusClass = computed(() => `stage-${props.status.toLowerCase()}`);
align-items: center;
gap: 5px;
min-width: 64px;
+ position: relative;
+ padding-top: 2px;
}
.stage-dot {
- width: 14px;
- height: 14px;
+ position: relative;
+ width: 15px;
+ height: 15px;
border-radius: 50%;
border: 2px solid var(--ctms-border-color);
background-color: #ffffff;
@@ -60,7 +63,7 @@ const statusClass = computed(() => `stage-${props.status.toLowerCase()}`);
color: var(--ctms-text-secondary);
text-align: center;
white-space: nowrap;
- transition: color 0.2s ease;
+ transition: color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
}
.stage-date {
@@ -80,15 +83,55 @@ const statusClass = computed(() => `stage-${props.status.toLowerCase()}`);
}
.stage-in_progress .stage-dot {
- border-color: var(--ctms-primary);
- box-shadow: 0 0 0 3px rgba(63, 93, 117, 0.15);
+ width: 18px;
+ height: 18px;
+ border: 3px solid #2f5f86;
background-color: #ffffff;
- animation: pulse-ring 2s ease-in-out infinite;
+ box-shadow:
+ 0 0 0 4px rgba(47, 95, 134, 0.14),
+ 0 6px 14px rgba(47, 95, 134, 0.22);
+ animation: progress-ring 1.8s ease-in-out infinite;
}
-@keyframes pulse-ring {
- 0%, 100% { box-shadow: 0 0 0 3px rgba(63, 93, 117, 0.15); }
- 50% { box-shadow: 0 0 0 5px rgba(63, 93, 117, 0.08); }
+.stage-in_progress .stage-dot::after {
+ content: "";
+ position: absolute;
+ inset: 3px;
+ border-radius: inherit;
+ background: #2f5f86;
+}
+
+.stage-in_progress .stage-dot::before {
+ content: "";
+ position: absolute;
+ inset: -8px;
+ border-radius: inherit;
+ border: 1px solid rgba(47, 95, 134, 0.24);
+ animation: progress-halo 1.8s ease-out infinite;
+}
+
+@keyframes progress-ring {
+ 0%, 100% {
+ box-shadow:
+ 0 0 0 4px rgba(47, 95, 134, 0.14),
+ 0 6px 14px rgba(47, 95, 134, 0.22);
+ }
+ 50% {
+ box-shadow:
+ 0 0 0 6px rgba(47, 95, 134, 0.08),
+ 0 8px 18px rgba(47, 95, 134, 0.26);
+ }
+}
+
+@keyframes progress-halo {
+ 0% {
+ opacity: 0.65;
+ transform: scale(0.82);
+ }
+ 100% {
+ opacity: 0;
+ transform: scale(1.25);
+ }
}
.stage-not_started .stage-dot {
@@ -108,8 +151,13 @@ const statusClass = computed(() => `stage-${props.status.toLowerCase()}`);
}
.stage-in_progress .stage-label {
- color: var(--ctms-primary);
- font-weight: 600;
+ margin-top: 1px;
+ padding: 2px 8px;
+ border-radius: 999px;
+ color: #244966;
+ background: rgba(47, 95, 134, 0.1);
+ box-shadow: inset 0 0 0 1px rgba(47, 95, 134, 0.14);
+ font-weight: 800;
}
.stage-blocked .stage-label {