Conversation
chore: back-merge main into staging (v14.7.0)
Add five read-only, company-scoped dashboard cards under a new "Project Progress" category, served by one additive endpoint (POST /api/v1/dashboard/project-metrics): - Active Projects β company-wide active count - Projects by Type β active projects grouped by ProjectType - Running Projects β active projects with logged time in the period - Active Time Trackers β who is tracking now + task/project + tracker memo - Work by Category β task count per user, bucketed into a user-defined category -> task-type template Work by Category reuses the standard edit-card settings modal for its filters (Projects, Teams/Users, Include-subtasks) plus an embedded category-template editor (CategoryTaskTypeMapper); period + refresh live in the card chrome. All queries are read-only and role-scoped (roleType 1/2 see all, others see only themselves). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦dashboard-cards-v14.8.0 feat(dashboard): add Project Progress cards (AHE-3789)
Replace the shared shield/gear icon on SSO, SCIM, Audit Log, Two-Factor Authentication and Integrations with distinct, purpose-built icons (key, people, document, padlock, puzzle) in the same inactive-gray (#535358) / active-blue (#2F3990) format. Additive: 10 new SVGs + icon wiring in the settings nav; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦-v14.8.0 feat(settings): distinct nav icons for security and integration menus
# Conflicts: # Modules/UserDashboard/controller.js # Modules/UserDashboard/routes.js # frontend/src/plugins/dashboard/views/HomePage.vue # utils/cardComponent.json
β¦lision Both branches introduced a dashboard card keyed 'LiveWorkTableCard' (ours mapping to LiveWorkTableCard.vue, staging's AHE-3789 card mapping to LiveWorkCard.vue), producing duplicate switch cases after the merge. Our card is renamed everywhere (component, switch cases, cardComponent key, dashboardTemplate componentId); staging's key is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors the abudhabi-web setup (husky + lint-staged pre-commit), adapted to this repo: frontend files get eslint --fix via the config in frontend/package.json; backend .js files get a node --check syntax gate and *.json files a JSON.parse validation (lint-staged.config.js). The commit-msg hook wires the existing commitlint.config.js locally, matching the commitlint CI workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
core.autocrlf=true rewrote the hook scripts with CRLF on checkout, which makes sh fail with 'husky - command not found' on Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
β¦e-cards feat(dashboard): resource-utilization cards + merge of AHE-3789 project-progress cards
Replace the AWS SES implementation in Modules/service.js with the nodemailer SMTP implementation from servicewithoutAWS.js. All three mail functions (SendEmail, SendNotificationEmail, sendAttachMail) now use the SMTP transport configured via NODEMAILER_* env vars, and the AWS SES imports/dependencies are removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
β¦ailer-smtp refactor(service): use nodemailer SMTP transport in service.js
π WalkthroughWalkthroughThis PR adds resource dashboard analytics and cards, wires dashboard period/global-range controls, migrates email delivery to Nodemailer, and adds Husky/lint-staged tooling plus dashboard test-case coverage. ChangesBackend resource-utilization analytics
Email delivery migration
Frontend dashboard cards and wiring
Developer tooling
Settings tab icons
Estimated code review effort: 4 (Complex) | ~75 minutes Possibly related PRs
Suggested reviewers: π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
π§Ή Nitpick comments (10)
Modules/service.js (1)
17-28: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winExtract the duplicated transporter setup into a shared helper.
The identical
nodemailer.createTransport({ host, port, secure, auth, tls })block is copied acrossSendEmail,SendNotificationEmail, andsendAttachMail. Consolidating into a single factory removes the three-way drift risk and makes fixes (e.g., the TLS change above) apply in one place.β»οΈ Example helper
const createTransporter = () => nodemailer.createTransport({ host: config.NODEMAILER_HOST, port: config.NODEMAILER_PORT, secure: config.NODEMAILER_PORT == 465, auth: { user: config.NODEMAILER_EMAIL, pass: config.NODEMAILER_EMAIL_PASSWORD, }, });π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Modules/service.js` around lines 17 - 28, The transporter setup is duplicated in SendEmail, SendNotificationEmail, and sendAttachMail, so extract the repeated nodemailer.createTransport configuration into a shared helper such as createTransporter. Update each mail-sending function to call that helper instead of inlining host/port/secure/auth/tls, and keep the transport configuration in one place so future changes like TLS behavior only need to be made once.frontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vue (1)
116-117: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winSwallowed fetch error β no logging.
Unlike sibling cards'
catchblocks (e.g.,ActiveWorkTableCard.vueLine 115), errors here are dropped silently with noconsole.error, making failures harder to diagnose.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vue` around lines 116 - 117, The LiveWorkCard fetch error is being swallowed in the catch block with no diagnostics. Update the catch in LiveWorkCard.vue to log the caught exception with console.error before resetting data.value, following the pattern used by sibling components such as ActiveWorkTableCard so failures can be diagnosed.frontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vue (2)
89-92: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winDuplicated taskMatch-building IIFE.
This exact IIFE (flatten
filterDatathenbuildFilterQuery) is repeated verbatim in FreeResourcesCard, TeamLoggedVsEtaCard, and WorkedTasksTableCard. Consider extracting abuildTaskMatch(filterData, userId)helper intouseResourceWorkload.jsto avoid drift between copies.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vue` around lines 89 - 92, The taskMatch IIFE in ActiveWorkTableCard duplicates the same flatten-and-build logic used in FreeResourcesCard, TeamLoggedVsEtaCard, and WorkedTasksTableCard. Extract that repeated logic into a shared buildTaskMatch(filterData, userId) helper in useResourceWorkload.js, then replace the inline IIFE in this component with a call to the helper so all cards stay consistent.
74-120: π©Ί Stability & Availability | π΅ Trivial | β‘ Quick winNo request sequencing guard against concurrent
load()calls.
refreshTrigger,cardData(deep), andfilterData(deep) each independently triggerload(). If two calls overlap, responses can resolve out of order and the older response can overwriterowsafter the newer one, leaving stale data displayed until the next refresh.β»οΈ Suggested fix β sequence token
+let requestSeq = 0; const load = async () => { + const seq = ++requestSeq; loading.value = true; try { ... - rows.value = flat; + if (seq === requestSeq) rows.value = flat; } catch (e) { console.error('ActiveWorkTableCard fetch error:', e); - rows.value = []; + if (seq === requestSeq) rows.value = []; } finally { - loading.value = false; + if (seq === requestSeq) loading.value = false; } };π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vue` around lines 74 - 120, The load() flow in ActiveWorkTableCard.vue has no sequencing protection, so overlapping refreshTrigger/cardData/filterData-driven requests can resolve out of order and overwrite newer rows with stale data. Add a request sequence/token guard inside load() so each invocation captures its own id and only the latest successful response is allowed to assign rows.value, while older in-flight responses are ignored even in the finally loading reset path.frontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vue (1)
73-76: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick win
PERIOD_LABELSduplicated withTeamLoggedVsEtaCard.vue.Identical object literal (Lines 73-76 here vs.
TeamLoggedVsEtaCard.vueLines 101) β extract to a shared constant (e.g. incommonFunction.jsoruseResourceWorkload.js) to keep the two in sync.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vue` around lines 73 - 76, The PERIOD_LABELS object is duplicated in WorkedTasksTableCard and TeamLoggedVsEtaCard, so move this shared mapping to a common location such as commonFunction.js or useResourceWorkload.js and update both components to import and use the same constant. Keep the existing PERIOD_LABELS symbol name or a similarly clear shared export so both views stay in sync from one source of truth.frontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vue (1)
66-69: π Maintainability & Code Quality | π΅ Trivial | π€ Low valueHardcoded English labels bypass i18n.
TYPE_LABELSvalues ("Fixed", "Hourly", "In House", "Retainer", "Unspecified") are hardcoded strings rendered directly, unlike other labels in this file that use$t(...). These won't be translated for non-English locales.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vue` around lines 66 - 69, The TYPE_LABELS mapping in ProjectMetricsCard is using hardcoded English display values, which bypasses the existing i18n flow. Update the label resolution used by ProjectMetricsCard so the displayed type names come from $t(...) or equivalent translation keys instead of literal strings, and make sure every entry referenced by the component (such as Fix, Hourly, InHouse, Retainer, and Unspecified) is localized consistently with the other labels in this file.frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue (1)
122-125: π©Ί Stability & Availability | π΅ Trivial | β‘ Quick winThree independent watchers can race on
load().
refreshTrigger, deepcardData, and deepfilterDatawatches each callload()without cancelling in-flight requests. Rapid successive changes (e.g., toggling the dimension select then changing a filter) can let a stale response overwrite fresher data.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue` around lines 122 - 125, Three separate watchers in TeamCategoryBreakdownCard are triggering load() concurrently and can let stale responses win. Update the load flow in TeamCategoryBreakdownCard.vue so refreshTrigger, cardData, and filterData changes do not start overlapping requests without protection; for example, add request cancellation or a sequence guard inside load() and keep the existing watch/onMounted hooks calling that guarded loader. Make sure the latest invocation of load() is the only one allowed to update the component state.frontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vue (3)
116-138: π©Ί Stability & Availability | π΅ Trivial | β‘ Quick winNo guard against out-of-order responses across concurrent watchers.
refreshTriggerand a deep watch oncardDataboth invokeload()independently with no request sequencing/abort. Rapid consecutive triggers (e.g., quick period changes) can let an older response overwrite a newer one.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vue` around lines 116 - 138, The load flow in ProjectPulseCard.vue can apply stale data because refreshTrigger and the deep watch on props.cardData both call load() without any sequencing or cancellation. Update the load function and its callers so only the latest request can commit state, using a request token/version or AbortController-style guard around apiRequest and the data.value assignment. Keep the check tied to the existing load, watch, and onMounted hooks so older responses are ignored if a newer load starts first.
62-66: π Maintainability & Code Quality | π΅ Trivial | π€ Low valueDuplicate hardcoded
PERIOD_LABELSobject, not localized.The same
PERIOD_LABELSmap is duplicated verbatim inTeamCategoryBreakdownCard.vue, and its values are plain English strings not routed through$t(...)like other labels in this file.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vue` around lines 62 - 66, The PERIOD_LABELS map in ProjectPulseCard is duplicated elsewhere and uses hardcoded English strings instead of localization. Replace the local hardcoded PERIOD_LABELS/periodLabel lookup with the shared source used by TeamCategoryBreakdownCard, and route each label through $t(...) so the timerange labels are translated consistently with the rest of the component.
69-114: π― Functional Correctness | π΅ Trivial | β‘ Quick winReuse the shared date-range helper here.
ProjectPulseCard.vueduplicates the timerangeβISO mapping thatuseResourceWorkload.resolveIsoRangealready centralizes. Pulling from the shared composable keeps the range IDs in one place and avoids drift if the semantics change later.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vue` around lines 69 - 114, The date range conversion in resolveDateRange is duplicating logic that already exists in useResourceWorkload.resolveIsoRange. Replace the local switch-based mapping in ProjectPulseCard.vue with the shared helper so the range IDs and ISO date semantics stay centralized and consistent. Keep the component using resolveDateRange only as a thin adapter, or remove it entirely if the composable can be called directly from the existing code path.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/test-cases/AHE-3789-project-progress-cards.md:
- Line 52: The boot-integrity test case row is stale because it still expects 24
cards instead of the current 30 from utils/cardComponent.json. Update the PPC-11
entry in AHE-3789-project-progress-cards.md so the expected card count matches
30, keeping the rest of the boot integrity description unchanged.
In `@frontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vue`:
- Line 71: The threshold handling in FreeResourcesCard.vue currently uses
Number(props.cardData?.freeThresholdHours) || 3, which overrides an explicit 0
value with the default. Update the thresholdHours computed logic so it only
falls back to 3 when the prop is missing or not a valid number, while preserving
0 as a valid configured value. Locate the fix in the FreeResourcesCard component
where thresholdHours is defined.
- Around line 64-65: The FreeResourcesCard setup is reading settings/teams once
into teamsArr, which can go stale if the store updates after mount. Update the
FreeResourcesCard component to derive teamsArr from a computed based on
getters['settings/teams'] so teamIdToUserId() always sees the latest teams, and
keep the existing symbol names useStore, getters, teamsArr, and teamIdToUserId
in place while refactoring.
In `@frontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vue`:
- Around line 82-91: The LiveWorkCard load flow is still using a global payload
and ignores the new card-specific inputs. Update the LiveWorkCard.vue logic
around load() to include cardData and filterData in the request payload, and add
the same deep watch behavior used by the sibling card components so changes to
assignee/project and advanced filters retrigger loading; if this card is meant
to stay global, explicitly document that in the component instead.
In `@frontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vue`:
- Around line 88-92: The project-metrics request is sending callerUserId and
callerRoleType from the client, which can be forged to bypass caller-scoped
restrictions. Update the backend handling for the project-metrics endpoint to
derive the authenticated user and role from the server session/context instead
of trusting request body fields, and use that same server-side source wherever
the logic in ProjectMetricsCard, ProjectResourceCard, and
TeamCategoryBreakdownCard depends on caller scoping.
In
`@frontend/src/components/organisms/ProjectResourceCard/ProjectResourceCard.vue`:
- Around line 45-62: The week-range logic in resolveRange is inconsistent with
the other dashboard cards, causing different results for the same timerange ids.
Update the shared resolveIsoRange helper to use the Monday-based week boundaries
used by ProjectPulseCard, then switch ProjectResourceCard to call that shared
helper instead of keeping its own Sunday-based calculations. Make sure the
timerange 3 and 4 cases now resolve to the same week definition everywhere.
In
`@frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue`:
- Around line 73-74: The settings/teams Vuex getter is being read once during
setup in TeamCategoryBreakdownCard.vue, so teamsArr can become stale after the
store updates. Make teamsArr reactive by wrapping the getter access in a
computed/ref-style wrapper and update load() to read teamsArr.value when
building teamIdToUserId, so later store changes are reflected.
In `@Modules/service.js`:
- Around line 37-40: The callback error path in service.js is returning the raw
Error object, which causes downstream consumers like
Modules/EmailNotification/routes.js to lose the message when serializing
result.error. Update the error branches in the relevant functions, including
SendNotificationEmail, to pass err.message instead of err while keeping the
existing status:false shape so the callback and catch behavior are consistent.
- Line 100: The error logging in the send-verify-email catch block is using a
misspelled property on the Error object, so the message is lost. Update the
logger.error call in the catch path for the send verification email flow to use
the correct error message property on the error variable, keeping the existing
log context intact.
- Around line 25-28: The Nodemailer transporter configuration currently disables
TLS certificate validation via tls.rejectUnauthorized in the mail setup, which
should be removed from all transporters in service.js. Update the transporter
configuration so certificate verification remains enabled by default, and if
self-signed certificates must be supported, guard that behavior behind an
explicit environment-driven opt-in in the transport creation logic.
In `@package.json`:
- Around line 104-114: The prepare hook in package.json should be made safe for
production installs because npm ci --omit=dev still runs it and the backend-deps
image stage has no .git and no husky binary. Update the prepare script so it
no-ops when husky is unavailable, or otherwise guard it behind a check that only
runs in a git/dev environment. Keep the fix localized to the prepare lifecycle
entry and ensure it does not break the Dockerfile backend-deps build path.
In `@utils/cardComponent.json`:
- Around line 2104-2141: The TeamCategoryBreakdownCard config is missing an
editable dimension selector, so it stays on the backend default instead of
letting the user choose. Update the card definition in cardComponent.json for
TeamCategoryBreakdownCard by adding a new field named dimension alongside the
existing data fields, and make its options match the backend-supported values
(type, effort_nature, work_category, billable). Ensure the default aligns with
the current template behavior while allowing the settings UI to override it.
---
Nitpick comments:
In
`@frontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vue`:
- Around line 89-92: The taskMatch IIFE in ActiveWorkTableCard duplicates the
same flatten-and-build logic used in FreeResourcesCard, TeamLoggedVsEtaCard, and
WorkedTasksTableCard. Extract that repeated logic into a shared
buildTaskMatch(filterData, userId) helper in useResourceWorkload.js, then
replace the inline IIFE in this component with a call to the helper so all cards
stay consistent.
- Around line 74-120: The load() flow in ActiveWorkTableCard.vue has no
sequencing protection, so overlapping refreshTrigger/cardData/filterData-driven
requests can resolve out of order and overwrite newer rows with stale data. Add
a request sequence/token guard inside load() so each invocation captures its own
id and only the latest successful response is allowed to assign rows.value,
while older in-flight responses are ignored even in the finally loading reset
path.
In `@frontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vue`:
- Around line 116-117: The LiveWorkCard fetch error is being swallowed in the
catch block with no diagnostics. Update the catch in LiveWorkCard.vue to log the
caught exception with console.error before resetting data.value, following the
pattern used by sibling components such as ActiveWorkTableCard so failures can
be diagnosed.
In `@frontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vue`:
- Around line 66-69: The TYPE_LABELS mapping in ProjectMetricsCard is using
hardcoded English display values, which bypasses the existing i18n flow. Update
the label resolution used by ProjectMetricsCard so the displayed type names come
from $t(...) or equivalent translation keys instead of literal strings, and make
sure every entry referenced by the component (such as Fix, Hourly, InHouse,
Retainer, and Unspecified) is localized consistently with the other labels in
this file.
In `@frontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vue`:
- Around line 116-138: The load flow in ProjectPulseCard.vue can apply stale
data because refreshTrigger and the deep watch on props.cardData both call
load() without any sequencing or cancellation. Update the load function and its
callers so only the latest request can commit state, using a request
token/version or AbortController-style guard around apiRequest and the
data.value assignment. Keep the check tied to the existing load, watch, and
onMounted hooks so older responses are ignored if a newer load starts first.
- Around line 62-66: The PERIOD_LABELS map in ProjectPulseCard is duplicated
elsewhere and uses hardcoded English strings instead of localization. Replace
the local hardcoded PERIOD_LABELS/periodLabel lookup with the shared source used
by TeamCategoryBreakdownCard, and route each label through $t(...) so the
timerange labels are translated consistently with the rest of the component.
- Around line 69-114: The date range conversion in resolveDateRange is
duplicating logic that already exists in useResourceWorkload.resolveIsoRange.
Replace the local switch-based mapping in ProjectPulseCard.vue with the shared
helper so the range IDs and ISO date semantics stay centralized and consistent.
Keep the component using resolveDateRange only as a thin adapter, or remove it
entirely if the composable can be called directly from the existing code path.
In
`@frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue`:
- Around line 122-125: Three separate watchers in TeamCategoryBreakdownCard are
triggering load() concurrently and can let stale responses win. Update the load
flow in TeamCategoryBreakdownCard.vue so refreshTrigger, cardData, and
filterData changes do not start overlapping requests without protection; for
example, add request cancellation or a sequence guard inside load() and keep the
existing watch/onMounted hooks calling that guarded loader. Make sure the latest
invocation of load() is the only one allowed to update the component state.
In
`@frontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vue`:
- Around line 73-76: The PERIOD_LABELS object is duplicated in
WorkedTasksTableCard and TeamLoggedVsEtaCard, so move this shared mapping to a
common location such as commonFunction.js or useResourceWorkload.js and update
both components to import and use the same constant. Keep the existing
PERIOD_LABELS symbol name or a similarly clear shared export so both views stay
in sync from one source of truth.
In `@Modules/service.js`:
- Around line 17-28: The transporter setup is duplicated in SendEmail,
SendNotificationEmail, and sendAttachMail, so extract the repeated
nodemailer.createTransport configuration into a shared helper such as
createTransporter. Update each mail-sending function to call that helper instead
of inlining host/port/secure/auth/tls, and keep the transport configuration in
one place so future changes like TLS behavior only need to be made once.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8394ee4d-e3e1-4255-8eea-350391bc9794
β Files ignored due to path filters (12)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/src/assets/images/svg/auditDoc.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/auditDocActive.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/integrationPuzzle.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/integrationPuzzleActive.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/scimUsers.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/scimUsersActive.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/ssoKey.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/ssoKeyActive.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/twoFactorLock.svgis excluded by!**/*.svgfrontend/src/assets/images/svg/twoFactorLockActive.svgis excluded by!**/*.svgpackage-lock.jsonis excluded by!**/package-lock.json
π Files selected for processing (35)
.claude/settings.local.json.claude/test-cases/AHE-3789-project-progress-cards.md.gitattributes.husky/commit-msg.husky/pre-commit.husky/pre-pushModules/UserDashboard/controller.jsModules/UserDashboard/helpers/resourceHelpers.jsModules/UserDashboard/routes.jsModules/service.jsfrontend/package.jsonfrontend/src/components/molecules/CardFieldComponent/CardFieldComponent.vuefrontend/src/components/molecules/CategoryTaskTypeMapper/CategoryTaskTypeMapper.vuefrontend/src/components/molecules/DashBoardCard/DashBoardCard.vuefrontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vuefrontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vuefrontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vuefrontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vuefrontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vuefrontend/src/components/organisms/ProjectResourceCard/ProjectResourceCard.vuefrontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vuefrontend/src/components/organisms/TeamLoggedVsEtaCard/TeamLoggedVsEtaCard.vuefrontend/src/components/organisms/UsersByCategoryCard/UsersByCategoryCard.vuefrontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vuefrontend/src/components/templates/Settings/Settings.vuefrontend/src/composable/commonFunction.jsfrontend/src/composable/useResourceWorkload.jsfrontend/src/config/env.jsfrontend/src/locales/en.jsfrontend/src/plugins/dashboard/views/HomePage.vuelint-staged.config.jspackage.jsonscripts/validate-json.jsutils/cardComponent.jsonutils/dashboardTemplate.json
- OnLeaveCard: approved leave tickets from the configured HR project for the selected window, with AB (absent) / PR (present) headcounts; new read-only POST /api/v1/dashboard/on-leave endpoint - Project-count drill-downs: clicking Active/Working counters, type-mix bars, Active Projects, Projects by Type, or Running Projects opens a modal listing the projects behind the number with colour-coded status (per-project projectStatusData palette); includeProjects mode on the existing endpoints - Header period dropdown replaces the static range label on Project Pulse, Worked Tasks, Team Effort Breakdown and Team Logged vs ETA - Dashboard-level date range picker (CalenderCompo range mode) with a per-card "Auto" period (timerange 0) that follows it; persisted per user in localStorage - Shared CardSkeleton shimmer for all self-fetching cards (no more "...") - Refresh icon on every card added in the recent dashboard PRs - LiveWorkCard header shows distinct-project count alongside users - Avatars in OnLeaveCard resolve via the UserProfile atom (Wasabi keys, default-avatar fallback) All new queries are companyId-scoped and read-only. Backend restart required (card catalog is loaded at boot). Test cases in .claude/test-cases/DashboardEnhancements-v2.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Currently Tracking card: live_work metric now returns per-row logged minutes today on the tracked task and the user's day total; table adds right-aligned "This Task" / "Logged Today" columns (mirrors the Active Work table; a running tracker counts once stopped/synced) - On Leave card: ticket key/name is a link opening the TaskDetail sidebar (same pattern as Active Time Trackers); on-leave endpoint now returns the ticket's sprintId for the sidebar Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
β¦-drilldowns-daterange-v14.8.0 feat(dashboard): on-leave card, project drill-downs, global date range (v14.8.0)
"prepare": "husky" broke production deploys β the server installs without devDependencies, so npm install died with "sh: husky: not found" (exit 127). prepare now runs .husky/install.mjs, which exits silently when NODE_ENV=production/CI or husky isn't installed, and bootstraps the hooks normally on dev machines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (2)
Modules/UserDashboard/controller.js (1)
1083-1087: π Security & Privacy | π Major | β‘ Quick winDo not source viewer identity/role from
req.body.callerRoleTypecan be spoofed here, anduserScope()returns{}whencallerUserIdis missing, so non-admin requests can fall back to company-wide log queries. Pull both values from authenticated request context and treat a missing scoped user as no access.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Modules/UserDashboard/controller.js` around lines 1083 - 1087, The role-based log scoping in controller.js is trusting spoofable values from the request payload, and missing callerUserId can incorrectly widen access. Update the callerUserId/callerRoleType lookup in the current log-query flow to read from authenticated request context instead of payload, and make userScope() deny access when the scoped user is absent rather than returning an empty filter. Keep the fix localized around the callerUserId, callerRoleType, restrictToSelf, and userScope logic so non-admin requests cannot fall back to company-wide logs.frontend/src/composable/useResourceWorkload.js (1)
71-79: π― Functional Correctness | π‘ Minor | β‘ Quick winRounding can overflow minutes to 60.
Math.round(n % 60)is applied after splitting hours/minutes, so a fractionalnnear a minute boundary (e.g.119.5) yieldsh=1, m=60β renders"1h 60m"instead of"2h 00m".π Proposed fix
export function formatMinutes(min) { - const n = Number(min) || 0; + const n = Math.round(Number(min) || 0); if (n <= 0) return '0h'; const h = Math.floor(n / 60); - const m = Math.round(n % 60); + const m = n % 60; if (h === 0) return `${m}m`; return m === 0 ? `${h}h` : `${h}h ${String(m).padStart(2, '0')}m`; }π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/composable/useResourceWorkload.js` around lines 71 - 79, The `formatMinutes` helper in `useResourceWorkload` can produce invalid β60mβ output because minutes are rounded after splitting hours and minutes. Update the calculation to round the total minute count first, then derive hours and remaining minutes from that rounded value so boundary values like 119.5 format as β2h 00mβ instead of β1h 60mβ.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Modules/UserDashboard/controller.js`:
- Around line 714-720: The active-project filtering is inconsistent between the
summary endpoints, causing different totals for the same company. Update
getProjectUtilizationSummary and getProjectProgressMetric to reuse the same
shared active-project predicate instead of each building its own
MongoDbCrudOpration filter, and make sure the shared condition covers both
deletedStatusKey: 0 and the legacy/unset project rows. Use the existing
activeProjects query in controller.js as the place to centralize the filter
logic.
---
Outside diff comments:
In `@frontend/src/composable/useResourceWorkload.js`:
- Around line 71-79: The `formatMinutes` helper in `useResourceWorkload` can
produce invalid β60mβ output because minutes are rounded after splitting hours
and minutes. Update the calculation to round the total minute count first, then
derive hours and remaining minutes from that rounded value so boundary values
like 119.5 format as β2h 00mβ instead of β1h 60mβ.
In `@Modules/UserDashboard/controller.js`:
- Around line 1083-1087: The role-based log scoping in controller.js is trusting
spoofable values from the request payload, and missing callerUserId can
incorrectly widen access. Update the callerUserId/callerRoleType lookup in the
current log-query flow to read from authenticated request context instead of
payload, and make userScope() deny access when the scoped user is absent rather
than returning an empty filter. Keep the fix localized around the callerUserId,
callerRoleType, restrictToSelf, and userScope logic so non-admin requests cannot
fall back to company-wide logs.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 79d983b1-4d62-47cd-960f-7ba87aa10f6b
π Files selected for processing (22)
.claude/test-cases/DashboardEnhancements-v2.mdModules/UserDashboard/controller.jsModules/UserDashboard/routes.jsfrontend/src/components/atom/CardSkeleton/CardSkeleton.vuefrontend/src/components/molecules/ProjectListModal/ProjectListModal.vuefrontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vuefrontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vuefrontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vuefrontend/src/components/organisms/MetricSummaryCard/MetricSummaryCard.vuefrontend/src/components/organisms/OnLeaveCard/OnLeaveCard.vuefrontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vuefrontend/src/components/organisms/ProjectPulseCard/ProjectPulseCard.vuefrontend/src/components/organisms/ProjectResourceCard/ProjectResourceCard.vuefrontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vuefrontend/src/components/organisms/TeamLoggedVsEtaCard/TeamLoggedVsEtaCard.vuefrontend/src/components/organisms/UsersByCategoryCard/UsersByCategoryCard.vuefrontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vuefrontend/src/composable/commonFunction.jsfrontend/src/composable/useResourceWorkload.jsfrontend/src/locales/en.jsfrontend/src/plugins/dashboard/views/HomePage.vueutils/cardComponent.json
β Files skipped from review due to trivial changes (2)
- .claude/test-cases/DashboardEnhancements-v2.md
- frontend/src/locales/en.js
π§ Files skipped from review as they are similar to previous changes (11)
- Modules/UserDashboard/routes.js
- frontend/src/composable/commonFunction.js
- frontend/src/components/organisms/LiveWorkCard/LiveWorkCard.vue
- utils/cardComponent.json
- frontend/src/components/organisms/ActiveWorkTableCard/ActiveWorkTableCard.vue
- frontend/src/components/organisms/WorkedTasksTableCard/WorkedTasksTableCard.vue
- frontend/src/components/organisms/ProjectMetricsCard/ProjectMetricsCard.vue
- frontend/src/components/organisms/TeamLoggedVsEtaCard/TeamLoggedVsEtaCard.vue
- frontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vue
- frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue
- frontend/src/components/organisms/UsersByCategoryCard/UsersByCategoryCard.vue
β¦uction-install fix: skip husky setup when devDependencies are omitted
- SECURITY (critical): dashboard endpoints scoped data visibility from client-supplied callerRoleType/callerUserId β a forged callerRoleType 1/2 could read every user's workload/time data. Now derive the caller from the authenticated session (req.uid) and resolve roleType server-side from company_users; body values are ignored. Applied to employee-workload, team-tasktype-breakdown, team-logged-vs-eta, project-metrics, on-leave. - FreeResourcesCard/TeamCategoryBreakdownCard: read settings/teams fresh in load() instead of capturing the getter once at setup (went stale on late store load). - FreeResourcesCard: treat an explicit freeThresholdHours 0 as valid (was falsy-coerced to the default 3). - docs: correct the boot-integrity expected card count (24 -> 31). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦328) - Security: TLS certificate validation is now ON by default across all three mail paths; self-signed certs require an explicit NODEMAILER_ALLOW_SELF_SIGNED opt-in (was a hardcoded rejectUnauthorized:false β MITM exposure). - Return err.message instead of the raw Error object (which serialized to {}) in the send callbacks, so the failure reason reaches the caller. - Fix the error.messge typo that logged undefined. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦cope-v14.8.0 fix: resolve CodeRabbit PR #328 review findings
There was a problem hiding this comment.
π§Ή Nitpick comments (1)
Modules/service.js (1)
5-8: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winCentralize the SMTP transport config.
The same security-sensitive Nodemailer config is duplicated in all three helpers. Extract it once and rename the module-level flag to UPPER_SNAKE_CASE while touching this code.
As per coding guidelines,
**/*.{js,ts,vue}: βUse UPPER_SNAKE_CASE for constant values.ββ»οΈ Proposed refactor
-const allowSelfSigned = String(process.env.NODEMAILER_ALLOW_SELF_SIGNED || "").toLowerCase() === "true"; +const ALLOW_SELF_SIGNED = String(process.env.NODEMAILER_ALLOW_SELF_SIGNED || "").toLowerCase() === "true"; + +const createMailerTransport = () => nodemailer.createTransport({ + host: config.NODEMAILER_HOST, + port: config.NODEMAILER_PORT, + secure: config.NODEMAILER_PORT == 465, + auth: { + user: config.NODEMAILER_EMAIL, + pass: config.NODEMAILER_EMAIL_PASSWORD, + }, + tls: { + rejectUnauthorized: !ALLOW_SELF_SIGNED + } +});Then replace each duplicated
nodemailer.createTransport({...})block with:-let transporter = nodemailer.createTransport({ - ... -}); +const transporter = createMailerTransport();Also applies to: 22-33, 73-84, 123-134
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Modules/service.js` around lines 5 - 8, The SMTP transport setup is duplicated across the three Nodemailer helpers, and the module-level opt-in flag should follow the constant naming rule. Centralize the shared security-sensitive transport configuration into one reusable module-level object or factory used by the helper functions, and rename allowSelfSigned to UPPER_SNAKE_CASE while keeping the same NODEMAILER_ALLOW_SELF_SIGNED behavior. Update the Nodemailer helper entry points that currently build transport inline to consume the shared config instead of repeating nodemailer.createTransport calls.Source: Coding guidelines
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@Modules/service.js`:
- Around line 5-8: The SMTP transport setup is duplicated across the three
Nodemailer helpers, and the module-level opt-in flag should follow the constant
naming rule. Centralize the shared security-sensitive transport configuration
into one reusable module-level object or factory used by the helper functions,
and rename allowSelfSigned to UPPER_SNAKE_CASE while keeping the same
NODEMAILER_ALLOW_SELF_SIGNED behavior. Update the Nodemailer helper entry points
that currently build transport inline to consume the shared config instead of
repeating nodemailer.createTransport calls.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 225ef5ea-ebdc-4758-b61d-46278f1d752d
π Files selected for processing (5)
.claude/test-cases/AHE-3789-project-progress-cards.mdModules/UserDashboard/controller.jsModules/service.jsfrontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vuefrontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue
π§ Files skipped from review as they are similar to previous changes (4)
- .claude/test-cases/AHE-3789-project-progress-cards.md
- frontend/src/components/organisms/FreeResourcesCard/FreeResourcesCard.vue
- Modules/UserDashboard/controller.js
- frontend/src/components/organisms/TeamCategoryBreakdownCard/TeamCategoryBreakdownCard.vue
Summary
Promotion PR: merges
stagingintomainfor release. Merge with a merge commit β do NOT squash.What's included
Feature work and fixes accumulated on
stagingsince the last promotion:Modules/service.js(replaces AWS SES) β refactor(service): use nodemailer SMTP transport in service.jsΒ #327NODEMAILER_HOST/NODEMAILER_PORT/NODEMAILER_EMAIL/NODEMAILER_EMAIL_PASSWORDenv vars.LiveWorkTableCardβActiveWorkTableCardto avoid key collisionType of change
choreβ promotion / releaseBreaking changes
NODEMAILER_*) instead of AWS SES. Ensure prod SMTP credentials are set before/at deploy.Notes for reviewers
Standard staging β main promotion. After merge, back-merge
mainβstagingand cut the release tag.Summary by CodeRabbit