Skip to content

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

Open
rlorenzo wants to merge 14 commits into
mainfrom
refactor/effort-component-extraction
Open

PR 5: refactor(effort): component extraction + report context centralization#178
rlorenzo wants to merge 14 commits into
mainfrom
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

  • restore StatusBanner import in InstructorEdit: patches the InstructorPageShell extraction, which dropped a still-referenced import.
  • materialize CalculateWeightedAverage rows once: carry-over from PR 3's materialize IEnumerable batch, bundled here because the helper it targets is introduced in this PR.
  • restore DBNull guards for nullable SQL params: PR 3's CA1508 cleanup wrongly stripped (object?) ?? DBNull.Value from int? SqlCommand params, returning 500 on eval summary/detail and merit detail when no faculty/role filter was set. Restored as HasValue ? Value : DBNull.Value to keep CA1508 quiet.
  • polish Percent Rollover preview UI: three smoke-test fixes. Dropped the three expansion-section background tints (Copilot flagged a brand-color regression in the markup re-flatten). Added hide-bottom-space on the boundary-year q-input so Reset / Preview Rollover stay vertically centered. Swapped the inline "changing the year is unusual" caption for StatusBanner type="warning" live="assertive".

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
  • fix(effort): restore DBNull guards for nullable SQL params
  • fix(effort): polish Percent Rollover preview UI

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
  • Eval summary + Merit detail endpoints return 200 with default filters (no personId/role)
  • 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)
  • Percent Rollover preview dialog: three expansion sections render without colored backgrounds; Reset / Preview Rollover buttons align with the boundary-year input; warning banner appears when "Change" is clicked

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
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.
Description check ✅ Passed The pull request description clearly outlines the scope: Vue/UI component extractions, C# service refactoring, bundled fixes, and test plan validation.

✏️ 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 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

Comment thread VueApp/src/Effort/components/AsyncOperationDialog.vue Outdated
Comment thread VueApp/src/Effort/components/ClinicalImportDialog.vue
Comment thread VueApp/src/Effort/components/EffortReportPage.vue
Comment thread VueApp/src/Effort/components/HarvestDialog.vue
Comment thread VueApp/src/Effort/components/PercentAssignmentFormFields.vue
Comment thread VueApp/src/Effort/components/TermTable.vue
@rlorenzo rlorenzo force-pushed the refactor/dead-code-and-shared-chrome branch from 66fe537 to d09da71 Compare May 21, 2026 06:36
Base automatically changed from refactor/dead-code-and-shared-chrome to main May 21, 2026 07:19
@rlorenzo rlorenzo force-pushed the refactor/effort-component-extraction branch from 9518c20 to 53f2c8a Compare May 21, 2026 08:11
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 21, 2026

Bundle Report

Changes will decrease total bundle size by 7.12kB (-0.33%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
viper-frontend-esm 2.13MB -7.12kB (-0.33%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: viper-frontend-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/TermManagement-*.js -1.12kB 52.46kB -2.09%
assets/CourseDetail-*.js -148 bytes 40.43kB -0.36%
assets/InstructorEdit-*.js -2.42kB 29.78kB -7.52%
assets/CrossListedCoursesSection-*.js -628 bytes 23.08kB -2.65%
assets/EmergencyContactForm-*.js -376 bytes 17.19kB -2.14%
assets/MyEffort-*.js 5 bytes 8.21kB 0.06%
assets/EmergencyContactView-*.js -427 bytes 7.6kB -5.32%
assets/EffortRecordEditDialog-*.js 941 bytes 6.36kB 17.36% ⚠️
assets/InstructorDetail-*.js -383 bytes 5.01kB -7.1%
assets/TermSelection-*.js -1.85kB 4.54kB -29.01%
assets/SchoolSummary-*.js -422 bytes 3.97kB -9.61%
assets/TeachingActivityGrouped-*.js -606 bytes 3.67kB -14.16%
assets/MeritAverage-*.js -598 bytes 3.61kB -14.22%
assets/TeachingActivityIndividual-*.js -499 bytes 3.4kB -12.78%
assets/MeritDetail-*.js -605 bytes 3.3kB -15.5%
assets/DeptSummary-*.js -604 bytes 3.16kB -16.05%
assets/EvalDetail-*.js -604 bytes 2.94kB -17.04%
assets/MeritSummary-*.js -597 bytes 2.8kB -17.59%
assets/DialogSubmitActions-*.js (New) 2.13kB 2.13kB 100.0% 🚀
assets/EvalSummary-*.js -604 bytes 2.07kB -22.56%
assets/EffortReportPage-*.js (New) 1.78kB 1.78kB 100.0% 🚀
assets/TermManagement-*.css 132 bytes 1.59kB 9.06% ⚠️
assets/InstructorPageShell-*.js (New) 982 bytes 982 bytes 100.0% 🚀
assets/EmergencyContactPageShell-*.js (New) 875 bytes 875 bytes 100.0% 🚀
assets/SchoolSummary-*.css 49 bytes 467 bytes 11.72% ⚠️
assets/record-*.js (Deleted) -1.52kB 0 bytes -100.0% 🗑️

Files in assets/TermManagement-*.js:

  • ./src/Effort/components/ClinicalImportDialog.vue → Total Size: 171 bytes

  • ./src/Effort/components/PercentRolloverDialog.vue → Total Size: 174 bytes

  • ./src/Effort/pages/TermManagement.vue → Total Size: 235 bytes

  • ./src/Effort/components/AsyncOperationDialog.vue → Total Size: 258 bytes

  • ./src/Effort/components/HarvestDialog.vue → Total Size: 237 bytes

Files in assets/CourseDetail-*.js:

  • ./src/Effort/components/AddCourseEffortDialog.vue → Total Size: 174 bytes

Files in assets/InstructorEdit-*.js:

  • ./src/Effort/components/PercentAssignmentEditDialog.vue → Total Size: 192 bytes

  • ./src/Effort/composables/use-percentage-form.ts → Total Size: 3.4kB

  • ./src/Effort/components/PercentAssignmentAddDialog.vue → Total Size: 189 bytes

  • ./src/Effort/pages/InstructorEdit.vue → Total Size: 148 bytes

  • ./src/Effort/components/PercentAssignmentFormFields.vue → Total Size: 192 bytes

Files in assets/CrossListedCoursesSection-*.js:

  • ./src/Effort/components/EffortRecordAddDialog.vue → Total Size: 174 bytes

Files in assets/EffortRecordEditDialog-*.js:

  • ./src/Effort/components/EffortRecordSharedFields.vue → Total Size: 183 bytes

  • ./src/Effort/components/EffortRecordEditDialog.vue → Total Size: 177 bytes

Files in assets/InstructorDetail-*.js:

  • ./src/Effort/pages/InstructorDetail.vue → Total Size: 154 bytes

Files in assets/TermSelection-*.js:

  • ./src/Effort/components/TermTable.vue → Total Size: 138 bytes

  • ./src/Effort/pages/TermSelection.vue → Total Size: 145 bytes

Files in assets/SchoolSummary-*.js:

  • ./src/Effort/pages/SchoolSummary.vue → Total Size: 232 bytes

Files in assets/TeachingActivityGrouped-*.js:

  • ./src/Effort/pages/TeachingActivityGrouped.vue → Total Size: 262 bytes

Files in assets/MeritAverage-*.js:

  • ./src/Effort/pages/MeritAverage.vue → Total Size: 229 bytes

Files in assets/TeachingActivityIndividual-*.js:

  • ./src/Effort/pages/TeachingActivityIndividual.vue → Total Size: 271 bytes

Files in assets/MeritDetail-*.js:

  • ./src/Effort/pages/MeritDetail.vue → Total Size: 139 bytes

Files in assets/DeptSummary-*.js:

  • ./src/Effort/pages/DeptSummary.vue → Total Size: 139 bytes

Files in assets/EvalDetail-*.js:

  • ./src/Effort/pages/EvalDetail.vue → Total Size: 223 bytes

Files in assets/MeritSummary-*.js:

  • ./src/Effort/pages/MeritSummary.vue → Total Size: 142 bytes

Files in assets/DialogSubmitActions-*.js:

  • ./src/Effort/components/DialogSubmitActions.vue → Total Size: 168 bytes

Files in assets/EvalSummary-*.js:

  • ./src/Effort/pages/EvalSummary.vue → Total Size: 226 bytes

Files in assets/EffortReportPage-*.js:

  • ./src/Effort/components/EffortReportPage.vue → Total Size: 159 bytes

Files in assets/InstructorPageShell-*.js:

  • ./src/Effort/components/InstructorPageShell.vue → Total Size: 168 bytes

Files in assets/EmergencyContactPageShell-*.js:

  • ./src/Students/EmergencyContact/components/EmergencyContactPageShell.vue → Total Size: 205 bytes

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 21, 2026

Codecov Report

❌ Patch coverage is 5.00000% with 95 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.05%. Comparing base (ed1f48b) to head (8d5deb7).

Files with missing lines Patch % Lines
web/Areas/Effort/Services/BaseReportService.cs 4.44% 43 Missing ⚠️
...b/Areas/Effort/Services/EvaluationReportService.cs 0.00% 14 Missing ⚠️
web/Areas/Effort/Services/DeptSummaryService.cs 0.00% 12 Missing ⚠️
web/Areas/Effort/Services/SchoolSummaryService.cs 0.00% 12 Missing ⚠️
...b/Areas/Effort/Services/TeachingActivityService.cs 9.09% 10 Missing ⚠️
web/Areas/Effort/Services/MeritReportService.cs 0.00% 2 Missing ⚠️
...b/Areas/Effort/Services/ClinicalScheduleService.cs 0.00% 1 Missing ⚠️
web/Areas/Effort/Services/MeritSummaryService.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #178      +/-   ##
==========================================
+ Coverage   43.02%   43.05%   +0.02%     
==========================================
  Files         881      881              
  Lines       51437    51407      -30     
  Branches     4812     4810       -2     
==========================================
  Hits        22131    22131              
+ Misses      28780    28750      -30     
  Partials      526      526              
Flag Coverage Δ
backend 43.12% <5.00%> (+0.02%) ⬆️
frontend 41.47% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

rlorenzo added 3 commits May 22, 2026 19:41
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.
rlorenzo added 9 commits May 22, 2026 19:41
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 53f2c8a to ee16b44 Compare May 23, 2026 03:19
CA1508 (commit b9889d8) removed `(object?) ?? DBNull.Value` guards
on @PersonId/@ROLE in raw SqlCommand calls, treating them as dead
null-checks. For nullable value types, AddWithValue(null) sends no
value at all rather than DBNull, so the eval summary/detail and
merit detail endpoints returned 500 ("expects the parameter
'@personid', which was not supplied") whenever the UI omitted the
filter. Switch to `personId.HasValue ? personId.Value : DBNull.Value`
(already used at MeritReportService:559) so CA1508 stays quiet.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Part 5 of a 6-PR stack driven by jscpd/fallow audits. Extracts repeated Vue/Quasar UI shells in the Effort and Emergency Contact areas and centralizes report-context loading in a shared C# BaseReportService. Also bundles two correctness fixes that pair with the extractions (restored StatusBanner import in InstructorEdit; restored DBNull guards on @PersonId/@Role raw-SQL parameters after CA1508 wrongly stripped them in PR #176).

Changes:

  • Extract reusable Vue components: EffortReportPage, AsyncOperationDialog, InstructorPageShell, TermTable, EmergencyContactPageShell, DialogSubmitActions, PercentAssignmentFormFields, EffortRecordSharedFields.
  • Centralize report context in BaseReportService (LoadSingleTermContextAsync, LoadYearlyReportContextAsync, ExtractDistinctEffortTypes); update all subclasses + tests to pass ITermService through the base. Extract CalculateWeightedAverage in EvaluationReportService.
  • Restore personId.HasValue ? personId.Value : DBNull.Value and equivalent role guards in MeritReportService / EvaluationReportService raw SQL.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
web/Areas/Effort/Services/BaseReportService.cs Adds ITermService dependency and centralized context-loading helpers.
web/Areas/Effort/Services/TeachingActivityService.cs Drops local ITermService field; consumes base helpers.
web/Areas/Effort/Services/DeptSummaryService.cs Uses shared yearly context + ExtractDistinctEffortTypes.
web/Areas/Effort/Services/SchoolSummaryService.cs Uses shared yearly context + ExtractDistinctEffortTypes.
web/Areas/Effort/Services/ClinicalEffortService.cs Constructor updated to pass ITermService to base.
web/Areas/Effort/Services/ClinicalScheduleService.cs Constructor updated to pass ITermService to base.
web/Areas/Effort/Services/MeritReportService.cs Restores nullable DBNull guards; base constructor wiring.
web/Areas/Effort/Services/MeritMultiYearService.cs Constructor updated to pass ITermService to base.
web/Areas/Effort/Services/MeritSummaryService.cs Constructor updated to pass ITermService to base.
web/Areas/Effort/Services/EvaluationReportService.cs Adds CalculateWeightedAverage; restores DBNull guards.
test/Effort/BaseReportServiceTests.cs Test fixture updated for new base ctor signature.
test/Effort/ExcelGenerationTests.cs Updated to pass null! for new ITermService param.
VueApp/src/Effort/components/EffortReportPage.vue New shared report-page shell.
VueApp/src/Effort/components/AsyncOperationDialog.vue New preview/commit dialog shell.
VueApp/src/Effort/components/InstructorPageShell.vue Breadcrumbs + loading/error states for instructor pages.
VueApp/src/Effort/components/TermTable.vue Desktop+mobile term-selection rows.
VueApp/src/Effort/components/DialogSubmitActions.vue Standard Cancel + submit cluster.
VueApp/src/Effort/components/PercentAssignmentFormFields.vue Shared percent-assignment field cluster.
VueApp/src/Effort/components/EffortRecordSharedFields.vue Role/value/notes/banner cluster.
VueApp/src/Effort/components/PercentRolloverDialog.vue Refactored onto AsyncOperationDialog; swaps brand bg classes.
VueApp/src/Effort/components/ClinicalImportDialog.vue Refactored onto AsyncOperationDialog.
VueApp/src/Effort/components/HarvestDialog.vue Refactored onto AsyncOperationDialog.
VueApp/src/Effort/pages/Report/SchoolSummary/MeritDetail/etc. Converted to use EffortReportPage.
VueApp/src/Effort/pages/InstructorEdit.vue / InstructorDetail.vue Use InstructorPageShell; restore StatusBanner import.
VueApp/src/Students/EmergencyContact/components/EmergencyContactPageShell.vue New shared page shell for EC pages.

Comment thread VueApp/src/Effort/components/PercentRolloverDialog.vue Outdated
- Drop the three q-expansion-item background tints in PercentRolloverDialog;
  section differentiation now comes from the header icon + label alone,
  resolving a brand-color regression flagged in PR review.
- TermManagement: hide-bottom-space on the boundary-year q-input so the
  reserved validation-message area no longer pushes the Reset / Preview
  Rollover buttons out of vertical alignment.
- Swap the inline "changing the year is unusual" caption for a
  StatusBanner type="warning" live="assertive" per project banner guidance.
@rlorenzo
Copy link
Copy Markdown
Contributor Author

@bsedwards This branch is on TEST. This branch results in a net -600 lines of code due to deduplication and fixes some regressions in a previous fix.

@rlorenzo rlorenzo requested a review from bsedwards May 26, 2026 17:30
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.

3 participants