Skip to content

PR 5: refactor(effort): component extraction + report context centralization#178

Open
rlorenzo wants to merge 12 commits into
refactor/dead-code-and-shared-chromefrom
refactor/effort-component-extraction
Open

PR 5: refactor(effort): component extraction + report context centralization#178
rlorenzo wants to merge 12 commits into
refactor/dead-code-and-shared-chromefrom
refactor/effort-component-extraction

Conversation

@rlorenzo
Copy link
Copy Markdown
Contributor

@rlorenzo rlorenzo commented May 5, 2026

Summary

Part 5 of 6. Stacks on top of PR #177.

Effort-area refactors driven by jscpd (duplication detection): extract repeated UI shells, dialog scaffolding, and form-field clusters into reusable components. Plus a small report-service consolidation in C#. Also folds in the EmergencyContact shared page shell because it pairs naturally with the Effort UI patterns.

What's in this PR

Vue/UI extractions

  • EmergencyContactPageShell — shared page shell for emergency contact forms
  • DialogSubmitActions — submit/cancel button cluster used across multiple dialogs
  • PercentAssignmentFormFields — shared form-field cluster
  • EffortReportPage — page-layout shell for report screens
  • EffortRecordSharedFields — duplicate field cluster across record dialogs
  • InstructorPageShell — breadcrumbs + loading/error states for instructor pages
  • AsyncOperationDialog — preview-dialog shell
  • TermTable — term-selection rows component

C# / report services

  • BaseReportService centralizes report context loading; per-report services now consume it instead of each loading the term/context independently
  • CalculateWeightedAverage extracted in EvaluationReportService to replace four near-identical N5..N1 weighted-average + divide-by-zero guards

Bundled fixes

  • fix(effort): restore StatusBanner import in InstructorEdit — the InstructorPageShell extraction dropped a StatusBanner import the template still referenced. Pairs with the extraction commit it patches.
  • chore(quality): materialize CalculateWeightedAverage rows once — carry-over from PR 3's materialize IEnumerable batch. The helper that needed materialization is introduced in this PR, so the analyzer fix is bundled here rather than in PR 3.

Commits

  • refactor(emergency-contact): extract shared page shell
  • refactor(effort): extract DialogSubmitActions component
  • refactor(effort): extract PercentAssignmentFormFields
  • refactor(effort): extract EffortReportPage layout shell
  • refactor(effort): extract EffortRecordSharedFields from dialogs
  • refactor(effort): extract InstructorPageShell for breadcrumbs and states
  • refactor(effort): extract AsyncOperationDialog shell for preview dialogs
  • refactor(effort): extract TermTable for term selection rows
  • refactor(effort): centralize report context loading in BaseReportService
  • refactor(effort): extract CalculateWeightedAverage in EvaluationReportService
  • fix(effort): restore StatusBanner import in InstructorEdit
  • chore(quality): materialize CalculateWeightedAverage rows once

PR stack

Test plan

  • CI green
  • Effort instructor edit page: save flow + error banners render
  • Effort report pages: department/instructor/eval reports render the same as before
  • Percent assignment dialogs: add/edit submit flow
  • Term selection table: rendering + selection
  • Emergency contact form: page shell renders
  • EvaluationReportService: report numbers unchanged (spot-check a known eval period)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added consistent dialog patterns with improved async operation handling, including loading states, progress tracking, and error recovery
    • Introduced responsive term table supporting both desktop and mobile displays
  • UI/UX Improvements

    • Standardized report page layouts with consistent filtering and export functionality
    • Enhanced page navigation with unified loading and error state handling across all pages
    • Improved form field consistency and reusability across various dialogs and forms

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

PR extracts reusable Vue component primitives (\AsyncOperationDialog\\, \DialogSubmitActions\\, form field components, page shells, and \EffortReportPage\\ wrapper) and consolidates backend report-data loading via \BaseReportService\\ helpers. Dialogs and pages delegate rendering to new components; services centralize \ITermService\\ dependency and term-resolution logic.

Changes

Vue Component Extraction & Refactoring

Layer / File(s) Summary
Async dialog and action primitives
VueApp/src/Effort/components/AsyncOperationDialog.vue, DialogSubmitActions.vue
\AsyncOperationDialog\\ replaces inline dialog state rendering with a reusable wrapper handling loading/committing/error/progress; \DialogSubmitActions\\ provides standardized submit/cancel buttons.
Effort record shared fields and dialogs
VueApp/src/Effort/components/EffortRecordSharedFields.vue, AddCourseEffortDialog.vue, EffortRecordAddDialog.vue, EffortRecordEditDialog.vue
Extracts \EffortRecordSharedFields\\ consolidating role/effort/notes inputs; three dialogs delegate form rendering and actions to shared components, removing 50+ lines of inline markup.
Percent assignment form and dialogs
VueApp/src/Effort/components/PercentAssignmentFormFields.vue, PercentAssignmentAddDialog.vue, PercentAssignmentEditDialog.vue
New \PercentAssignmentFormFields\\ encapsulates type/modifier/unit/date/percent/comment UI; dialogs delegate form and action rendering to components.
Dialogs with AsyncOperationDialog wrapper
VueApp/src/Effort/components/ClinicalImportDialog.vue, HarvestDialog.vue, PercentRolloverDialog.vue
Three dialogs wrap preview content in \AsyncOperationDialog\\ instead of inline state handling, centralizing loading/progress/error UI.
Page shells for layout standardization
VueApp/src/Effort/components/InstructorPageShell.vue, VueApp/src/Students/EmergencyContact/components/EmergencyContactPageShell.vue
New shells standardize breadcrumb, loading, and error display for detail pages.
Instructor pages refactored with shell
VueApp/src/Effort/pages/InstructorDetail.vue, InstructorEdit.vue
Pages wrap content in \InstructorPageShell\\, delegating breadcrumbs, loading, and error display.
Emergency contact pages refactored with shell
VueApp/src/Students/EmergencyContact/pages/EmergencyContactForm.vue, EmergencyContactView.vue
Pages delegate layout to \EmergencyContactPageShell\\.
EffortReportPage wrapper component
VueApp/src/Effort/components/EffortReportPage.vue
New component handles report layout, loading/empty states, filter form, and export toolbar via props, centralizing repeated UI pattern.
Report pages refactored with wrapper
VueApp/src/Effort/pages/DeptSummary.vue, EvalDetail.vue, EvalSummary.vue, MeritAverage.vue, MeritDetail.vue, MeritSummary.vue, SchoolSummary.vue, TeachingActivityGrouped.vue, TeachingActivityIndividual.vue
Nine report pages delegate layout/filter/export UI to \EffortReportPage\\, retaining only table rendering logic.
TermTable component and TermSelection refactoring
VueApp/src/Effort/components/TermTable.vue, VueApp/src/Effort/pages/TermSelection.vue
New \TermTable\\ provides responsive desktop/mobile term list rendering; \TermSelection\\ replaces inline markup with the component.
Type exports for percentage form
VueApp/src/Effort/composables/use-percentage-form.ts
Exports \PercentageFormState\\ and \TypeOption\\ for use by \PercentAssignmentFormFields\\.

Backend Service Refactoring

Layer / File(s) Summary
BaseReportService enhanced with ITermService and data helpers
web/Areas/Effort/Services/BaseReportService.cs
Constructor now accepts \ITermService\\; adds \LoadSingleTermContextAsync\\ and \LoadYearlyReportContextAsync\\ to execute general report stored procedure and resolve term names/codes and clinical faculty IDs; adds \ExtractDistinctEffortTypes\\ utility.
Report services forward ITermService to base
web/Areas/Effort/Services/ClinicalEffortService.cs, MeritMultiYearService.cs, ClinicalScheduleService.cs, MeritReportService.cs, MeritSummaryService.cs, SchoolSummaryService.cs
Services updated to accept and forward \ITermService\\ to \BaseReportService\\, removing local \_termService\\ field declarations.
DeptSummary, SchoolSummary, TeachingActivity report methods refactored
web/Areas/Effort/Services/DeptSummaryService.cs, SchoolSummaryService.cs, TeachingActivityService.cs
Single-term reports use \LoadSingleTermContextAsync\\ instead of inline queries; yearly variants use \LoadYearlyReportContextAsync\\ and \ExtractDistinctEffortTypes\\, replacing per-term row aggregation.
EvaluationReportService refactored
web/Areas/Effort/Services/EvaluationReportService.cs
Constructor forwards \ITermService\\ to base class; introduces \CalculateWeightedAverage\\ helper centralizing weighted-average computation for instructor/department summaries.
Test fixture updates
test/Effort/BaseReportServiceTests.cs, ExcelGenerationTests.cs
Test constructors updated to pass additional \null!\\ parameter matching new service signatures.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • bsedwards
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(effort): component extraction + report context centralization' directly summarizes the main changes: extracting reusable components (dialogs, shells, form fields, report page) and centralizing report context loading in BaseReportService.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/effort-component-extraction

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from eee8d10 to 8f5ffd2 Compare May 5, 2026 07:39
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch from ebe32ec to 75abeb2 Compare May 5, 2026 07:39
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from 8f5ffd2 to 9cad656 Compare May 5, 2026 14:47
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch 4 times, most recently from d26c5d8 to 8cbfb69 Compare May 7, 2026 15:57
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from 9cad656 to d14e7b9 Compare May 7, 2026 15:57
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch 2 times, most recently from 0d62a4e to a0c55c4 Compare May 9, 2026 05:22
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from d14e7b9 to 94b732b Compare May 9, 2026 05:33
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch from a0c55c4 to d8a3735 Compare May 9, 2026 17:27
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from 94b732b to 61ee064 Compare May 9, 2026 17:27
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch from d8a3735 to ddd64e7 Compare May 11, 2026 16:29
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from 61ee064 to f53d1a9 Compare May 11, 2026 16:29
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch from ddd64e7 to 66fe537 Compare May 12, 2026 02:33
rlorenzo added 12 commits May 11, 2026 20:17
Hoist breadcrumb, loading spinner, and not-found banner from
EmergencyContactForm.vue and EmergencyContactView.vue into a new
EmergencyContactPageShell.vue. Each page now owns only its h1 and
body content. Pure template refactor; no runtime change.
AddCourseEffortDialog and EffortRecordEditDialog shared a Cancel +
primary-action q-card-actions block with an identical #loading slot.
Hoist into DialogSubmitActions.vue parameterized by submitLabel and
isSaving. Other Effort dialogs still inline the pattern; migrate as
they are touched.
PercentAssignmentAddDialog and PercentAssignmentEditDialog shared a
165-line q-select/q-input/q-checkbox form body. Hoist into
PercentAssignmentFormFields.vue, v-modeled on the form state the
usePercentageForm composable already centralized. Each dialog now
owns only its title, save logic, banner variants, and footer. Also
migrate both footers to DialogSubmitActions. PercentageFormState
and TypeOption are re-exported from the composable so the new
component can type its props.
Consolidates the h1 + ReportFilterForm + loading spinner + ReportLayout
header/ExportToolbar + empty-state boilerplate that was duplicated
across all 9 standard Effort report pages into a single wrapper, so
individual pages only define their table content.
Share the Role / Effort Value / Notes / warning + error banner block
between EffortRecordAddDialog and EffortRecordEditDialog, and route the
Add dialog's footer through the existing DialogSubmitActions component.
Share the Instructors breadcrumb, loading spinner, and error banner
between InstructorDetail and InstructorEdit via a slot-based shell.
Share the dialog scaffold (close affordance, title/subtitle, loading
spinner, progress bar, and error state) between HarvestDialog,
PercentRolloverDialog, and ClinicalImportDialog. Per-dialog preview
bodies and action buttons stay in the consumers.
Share the desktop table + mobile list rendering across unopened / open /
closed term sections instead of inlining three near-identical q-table
blocks.
Move ITermService injection into BaseReportService and add shared
helpers (LoadSingleTermContextAsync, LoadYearlyReportContextAsync,
ExtractDistinctEffortTypes) so DeptSummary, SchoolSummary, and
TeachingActivity services no longer hand-roll the same row +
clinical-faculty + term-name plumbing.
…tService

Replace four near-identical N5..N1 weighted-average + divide-by-zero
guards in the summary and detail builders with a single helper.
The InstructorPageShell extraction (refactor 4601108) replaced the
StatusBanner import in InstructorEdit.vue, but the template still
references StatusBanner three times for save/error feedback. Vue logged
"Failed to resolve component: StatusBanner" warnings and rendered those
banners blank.
Carry-over from the analyzer-driven cleanup batch (PR 3). The materialization fix targets the helper extracted in d12711e — that helper doesn't exist in PR 3's base, so the fix moves here.
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from f53d1a9 to 9518c20 Compare May 12, 2026 03:17
@rlorenzo
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 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 `@VueApp/src/Effort/components/AsyncOperationDialog.vue`:
- Line 45: Remove the inline style attributes on the QCard and the element at
the top-right: replace the :style binding on the q-card (the element using
`:style="`width: 100%; max-width: ${maxWidth}; position: relative`"`) with a
class (e.g., `async-op-card`) and move the top-right element's inline z-index
style into a class (e.g., `absolute-top-right`); then add those CSS rules in the
component's <style scoped> block (set width/ max-width/position rules for
`.async-op-card` and z-index/positioning for `.absolute-top-right`) and update
the template to use the new class names or Quasar utility classes instead of
inline styles.

In `@VueApp/src/Effort/components/ClinicalImportDialog.vue`:
- Around line 151-156: The Confirm Import QBtn in ClinicalImportDialog.vue (the
<q-btn> with label="Confirm Import", color="info", :disable="totalChanges === 0
|| isCommitting", `@click`="confirmImport") needs the text-color="dark" attribute
added to satisfy the UI guidelines and ensure sufficient contrast on the info
background; update that <q-btn> to include text-color="dark" (and mirror the
same change for any other info/warning QBtn instances in this component if
present).

In `@VueApp/src/Effort/components/EffortReportPage.vue`:
- Around line 20-21: Replace the callback-prop pattern with Vue events: remove
the onPdfExport and onExcelExport props from ExportToolbar usage and instead
have ExportToolbar emit "export-pdf" and "export-excel"; update
EffortReportPage.vue to listen for those emits (e.g., <ExportToolbar
`@export-pdf`="handlePdfExport" `@export-excel`="handleExcelExport">) and rework any
prop passthrough so EffortReportPage emits its own events
(this.$emit('export-pdf') / this.$emit('export-excel') or calls local handlers)
rather than passing functions down; ensure you update the component's emits
option and any parent pages to use `@export-pdf` and `@export-excel` instead of
:on-pdf-export/:on-excel-export.

In `@VueApp/src/Effort/components/HarvestDialog.vue`:
- Around line 198-205: Replace the raw q-badge usage inside each
body-cell-status template with the project's StatusBadge component so accessible
text-color logic is preserved: for each template (e.g., the one using
getStatusColor(slotProps.value)) render StatusBadge and pass the status value
and the color computed by getStatusColor(slotProps.value) instead of q-badge,
and restore/ensure the StatusBadge import at the top of the file; apply the same
replacement to all eight body-cell-status sites referenced so they consistently
use StatusBadge rather than q-badge.

In `@VueApp/src/Effort/components/PercentAssignmentFormFields.vue`:
- Around line 67-80: The q-select for Unit (v-model="form.unitId") is marked
required via label "Unit *" and the rule requiredRule('Unit') but still includes
the clearable prop; remove the clearable attribute from the q-select element in
PercentAssignmentFormFields.vue so users cannot clear the required Unit field
and validation remains consistent.

In `@VueApp/src/Effort/components/TermTable.vue`:
- Line 18: The title is rendered twice in TermTable.vue (the <h2 class="text-h6
q-mb-sm q-mt-none">{{ title }}</h2> and the separate title inside the lt-sm
section), causing duplicate display on mobile; fix by either adding the
responsive class gt-xs to the existing <h2> so it hides on extra-small screens,
or remove the duplicate subtitle inside the lt-sm block so the single <h2>
remains; update the element that currently prints {{ title }} inside the lt-sm
section (or add gt-xs to the <h2>) accordingly to ensure only one title is shown
on mobile.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01234b4d-9fac-4c89-90f3-55c6ec94e220

📥 Commits

Reviewing files that changed from the base of the PR and between 66fe537 and 9518c20.

📒 Files selected for processing (43)
  • VueApp/src/Effort/components/AddCourseEffortDialog.vue
  • VueApp/src/Effort/components/AsyncOperationDialog.vue
  • VueApp/src/Effort/components/ClinicalImportDialog.vue
  • VueApp/src/Effort/components/DialogSubmitActions.vue
  • VueApp/src/Effort/components/EffortRecordAddDialog.vue
  • VueApp/src/Effort/components/EffortRecordEditDialog.vue
  • VueApp/src/Effort/components/EffortRecordSharedFields.vue
  • VueApp/src/Effort/components/EffortReportPage.vue
  • VueApp/src/Effort/components/HarvestDialog.vue
  • VueApp/src/Effort/components/InstructorPageShell.vue
  • VueApp/src/Effort/components/PercentAssignmentAddDialog.vue
  • VueApp/src/Effort/components/PercentAssignmentEditDialog.vue
  • VueApp/src/Effort/components/PercentAssignmentFormFields.vue
  • VueApp/src/Effort/components/PercentRolloverDialog.vue
  • VueApp/src/Effort/components/TermTable.vue
  • VueApp/src/Effort/composables/use-percentage-form.ts
  • VueApp/src/Effort/pages/DeptSummary.vue
  • VueApp/src/Effort/pages/EvalDetail.vue
  • VueApp/src/Effort/pages/EvalSummary.vue
  • VueApp/src/Effort/pages/InstructorDetail.vue
  • VueApp/src/Effort/pages/InstructorEdit.vue
  • VueApp/src/Effort/pages/MeritAverage.vue
  • VueApp/src/Effort/pages/MeritDetail.vue
  • VueApp/src/Effort/pages/MeritSummary.vue
  • VueApp/src/Effort/pages/SchoolSummary.vue
  • VueApp/src/Effort/pages/TeachingActivityGrouped.vue
  • VueApp/src/Effort/pages/TeachingActivityIndividual.vue
  • VueApp/src/Effort/pages/TermSelection.vue
  • VueApp/src/Students/EmergencyContact/components/EmergencyContactPageShell.vue
  • VueApp/src/Students/EmergencyContact/pages/EmergencyContactForm.vue
  • VueApp/src/Students/EmergencyContact/pages/EmergencyContactView.vue
  • test/Effort/BaseReportServiceTests.cs
  • test/Effort/ExcelGenerationTests.cs
  • web/Areas/Effort/Services/BaseReportService.cs
  • web/Areas/Effort/Services/ClinicalEffortService.cs
  • web/Areas/Effort/Services/ClinicalScheduleService.cs
  • web/Areas/Effort/Services/DeptSummaryService.cs
  • web/Areas/Effort/Services/EvaluationReportService.cs
  • web/Areas/Effort/Services/MeritMultiYearService.cs
  • web/Areas/Effort/Services/MeritReportService.cs
  • web/Areas/Effort/Services/MeritSummaryService.cs
  • web/Areas/Effort/Services/SchoolSummaryService.cs
  • web/Areas/Effort/Services/TeachingActivityService.cs

aria-labelledby="async-operation-dialog-title"
@keydown.escape="$emit('close')"
>
<q-card :style="`width: 100%; max-width: ${maxWidth}; position: relative`">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove inline styles.

Lines 45 and 52 use inline style attributes. Prefer Quasar utility classes or define styles in a <style scoped> block. As per coding guidelines, no inline styles are allowed.

♻️ Proposed fix

For line 45, use :style binding or move to scoped styles:

-        <q-card :style="`width: 100%; max-width: ${maxWidth}; position: relative`">
+        <q-card 
+            class="full-width"
+            :style="{ maxWidth, position: 'relative' }"
+        >

For line 52, move z-index to scoped styles:

-                style="z-index: 1"

Add to <style scoped> block at the end of the file:

<style scoped>
.absolute-top-right {
    z-index: 1;
}
</style>

Also applies to: 52-52

🤖 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 `@VueApp/src/Effort/components/AsyncOperationDialog.vue` at line 45, Remove the
inline style attributes on the QCard and the element at the top-right: replace
the :style binding on the q-card (the element using `:style="`width: 100%;
max-width: ${maxWidth}; position: relative`"`) with a class (e.g.,
`async-op-card`) and move the top-right element's inline z-index style into a
class (e.g., `absolute-top-right`); then add those CSS rules in the component's
<style scoped> block (set width/ max-width/position rules for `.async-op-card`
and z-index/positioning for `.absolute-top-right`) and update the template to
use the new class names or Quasar utility classes instead of inline styles.

Comment on lines 151 to 156
<q-btn
label="Retry"
color="primary"
class="q-mt-md"
@click="loadPreview"
label="Confirm Import"
color="info"
:disable="totalChanges === 0 || isCommitting"
@click="confirmImport"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing text-color="dark" on info button.

The Confirm Import button uses color="info" without the required dark text color, hurting contrast on the light info background.

♻️ Proposed fix
                 <q-btn
                     label="Confirm Import"
                     color="info"
+                    text-color="dark"
                     :disable="totalChanges === 0 || isCommitting"
                     `@click`="confirmImport"
                 />

As per coding guidelines: "Use button color scheme: primary (Aggie Blue), positive (create), negative (delete), info text-color="dark" (tertiary), warning text-color="dark" (caution), secondary."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<q-btn
label="Retry"
color="primary"
class="q-mt-md"
@click="loadPreview"
label="Confirm Import"
color="info"
:disable="totalChanges === 0 || isCommitting"
@click="confirmImport"
/>
<q-btn
label="Confirm Import"
color="info"
text-color="dark"
:disable="totalChanges === 0 || isCommitting"
`@click`="confirmImport"
/>
🤖 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 `@VueApp/src/Effort/components/ClinicalImportDialog.vue` around lines 151 -
156, The Confirm Import QBtn in ClinicalImportDialog.vue (the <q-btn> with
label="Confirm Import", color="info", :disable="totalChanges === 0 ||
isCommitting", `@click`="confirmImport") needs the text-color="dark" attribute
added to satisfy the UI guidelines and ensure sufficient contrast on the info
background; update that <q-btn> to include text-color="dark" (and mirror the
same change for any other info/warning QBtn instances in this component if
present).

Comment on lines +20 to +21
onPdfExport: () => void | Promise<void>
onExcelExport: () => Promise<void>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer events over callback props.

In Vue 3, the idiomatic pattern is to emit events rather than accept callback functions as props. Consider having ExportToolbar emit @export-pdf and @export-excel events, then emit corresponding events from this component instead of passing callbacks through.

♻️ Refactor to use events

Props:

-    onPdfExport: () => void | Promise<void>
-    onExcelExport: () => Promise<void>

Emits:

 defineEmits<{
     (e: "generate", params: ReportFilterParams): void
+    (e: "export-pdf"): void
+    (e: "export-excel"): void
 }>()

Template:

 <ExportToolbar
-    :pdf-export="onPdfExport"
-    :excel-export="onExcelExport"
+    `@export-pdf`="$emit('export-pdf')"
+    `@export-excel`="$emit('export-excel')"
 />

Parent pages would then use @export-pdf and @export-excel instead of :on-pdf-export and :on-excel-export.

🤖 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 `@VueApp/src/Effort/components/EffortReportPage.vue` around lines 20 - 21,
Replace the callback-prop pattern with Vue events: remove the onPdfExport and
onExcelExport props from ExportToolbar usage and instead have ExportToolbar emit
"export-pdf" and "export-excel"; update EffortReportPage.vue to listen for those
emits (e.g., <ExportToolbar `@export-pdf`="handlePdfExport"
`@export-excel`="handleExcelExport">) and rework any prop passthrough so
EffortReportPage emits its own events (this.$emit('export-pdf') /
this.$emit('export-excel') or calls local handlers) rather than passing
functions down; ensure you update the component's emits option and any parent
pages to use `@export-pdf` and `@export-excel` instead of
:on-pdf-export/:on-excel-export.

Comment on lines +198 to +205
<template #body-cell-status="slotProps">
<q-td :props="slotProps">
<q-badge
:color="getStatusColor(slotProps.value)"
:label="slotProps.value"
/>
</q-td>
</template>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regression: raw q-badge replaces StatusBadge in 8 status cells.

Switching back to raw q-badge with getStatusColor(...) drops the accessible text-color handling that StatusBadge provides. Colors like positive/info/grey-6 will render with default light text and fail contrast on light backgrounds.

This also runs counter to the PR's deduplication goal — eight identical body-cell-status templates can collapse into a single StatusBadge call site.

♻️ Proposed fix (apply at each of the 8 sites)
                             <template `#body-cell-status`="slotProps">
                                 <q-td :props="slotProps">
-                                    <q-badge
-                                        :color="getStatusColor(slotProps.value)"
-                                        :label="slotProps.value"
-                                    />
+                                    <StatusBadge
+                                        :color="getStatusColor(slotProps.value)"
+                                        :label="slotProps.value"
+                                    />
                                 </q-td>
                             </template>

And restore the import:

 import StatusBanner from "@/components/StatusBanner.vue"
+import StatusBadge from "@/components/StatusBadge.vue"
 import AsyncOperationDialog from "./AsyncOperationDialog.vue"

As per coding guidelines: "Use the StatusBadge component instead of raw q-badge with manual getAccessibleTextColor."

Also applies to: 237-244, 275-282, 320-327, 359-366, 397-404, 442-449, 481-488

🤖 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 `@VueApp/src/Effort/components/HarvestDialog.vue` around lines 198 - 205,
Replace the raw q-badge usage inside each body-cell-status template with the
project's StatusBadge component so accessible text-color logic is preserved: for
each template (e.g., the one using getStatusColor(slotProps.value)) render
StatusBadge and pass the status value and the color computed by
getStatusColor(slotProps.value) instead of q-badge, and restore/ensure the
StatusBadge import at the top of the file; apply the same replacement to all
eight body-cell-status sites referenced so they consistently use StatusBadge
rather than q-badge.

Comment on lines +67 to +80
<!-- Unit Selection -->
<q-select
v-model="form.unitId"
:options="unitOptions"
label="Unit *"
dense
options-dense
outlined
emit-value
map-options
clearable
:rules="[requiredRule('Unit')]"
lazy-rules="ondemand"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove clearable from required Unit field.

The Unit field is marked as required (line 71: label="Unit *" and line 78: requiredRule('Unit')), but line 77 includes clearable. This allows users to clear a required field, causing validation failure. Remove clearable for consistency.

🔧 Proposed fix
     <q-select
         v-model="form.unitId"
         :options="unitOptions"
         label="Unit *"
         dense
         options-dense
         outlined
         emit-value
         map-options
-        clearable
         :rules="[requiredRule('Unit')]"
         lazy-rules="ondemand"
     />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Unit Selection -->
<q-select
v-model="form.unitId"
:options="unitOptions"
label="Unit *"
dense
options-dense
outlined
emit-value
map-options
clearable
:rules="[requiredRule('Unit')]"
lazy-rules="ondemand"
/>
<!-- Unit Selection -->
<q-select
v-model="form.unitId"
:options="unitOptions"
label="Unit *"
dense
options-dense
outlined
emit-value
map-options
:rules="[requiredRule('Unit')]"
lazy-rules="ondemand"
/>
🤖 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 `@VueApp/src/Effort/components/PercentAssignmentFormFields.vue` around lines 67
- 80, The q-select for Unit (v-model="form.unitId") is marked required via label
"Unit *" and the rule requiredRule('Unit') but still includes the clearable
prop; remove the clearable attribute from the q-select element in
PercentAssignmentFormFields.vue so users cannot clear the required Unit field
and validation remains consistent.


<template>
<div class="q-mb-lg">
<h2 class="text-h6 q-mb-sm q-mt-none">{{ title }}</h2>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Title renders twice on mobile.

The <h2> at line 18 is visible on all screen sizes, but line 63 displays the title again inside the lt-sm section. On mobile, users see both.

Add gt-xs class to the <h2> or remove the duplicate subtitle at line 63.

🎨 Proposed fix: make title responsive

Option 1 (recommended): Keep h2 always visible, remove mobile duplicate

-        <div class="lt-sm">
-            <div class="text-subtitle2 q-mb-xs">{{ title }}</div>
-            <q-list
+        <div class="lt-sm">
+            <q-list

Option 2: Show h2 on desktop only, keep mobile subtitle

-        <h2 class="text-h6 q-mb-sm q-mt-none">{{ title }}</h2>
+        <h2 class="text-h6 q-mb-sm q-mt-none gt-xs">{{ title }}</h2>

Also applies to: 63-63

🤖 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 `@VueApp/src/Effort/components/TermTable.vue` at line 18, The title is rendered
twice in TermTable.vue (the <h2 class="text-h6 q-mb-sm q-mt-none">{{ title
}}</h2> and the separate title inside the lt-sm section), causing duplicate
display on mobile; fix by either adding the responsive class gt-xs to the
existing <h2> so it hides on extra-small screens, or remove the duplicate
subtitle inside the lt-sm block so the single <h2> remains; update the element
that currently prints {{ title }} inside the lt-sm section (or add gt-xs to the
<h2>) accordingly to ensure only one title is shown on mobile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant