Skip to content

Rep Portal CPD Log Page

Back to Regnify Product Docs

Previous Next


Overview

The Rep CPD Log page (/rep/cpd) allows representatives to view and self-log Continuing Professional Development (CPD) activities for the current calendar year. The page is implemented in client/src/pages/rep/RepCpdLogPage.tsx.

Access control: The repProfileByUser query resolves the current user's rep profile. The createCpdRecord mutation is role-gated to @Roles("FI_USER", "REP_USER", "COMP", "HR_ADMIN", "ORG_ADMIN", "SYS_ADMIN") — representatives can create their own CPD records.

Page context: The page does not wrap in a PageEntityProvider. The chatbot receives no entity context when opened from this page.

Page Layout

PageHeader component: - Title: "CPD Log" - Subtitle: "Self-log continuing professional development activities and hours." - Back link: "/rep" (navigates to Rep Dashboard)

YTD Summary Card (Top)

The YtdSummaryCard component displays the year-to-date CPD hours for the current calendar year (new Date().getFullYear()). The card has two sub-sections:

Ethics Warning Banner (conditional): Shown when totals !== null AND ethicsHours > 0 AND ethicsHours < 6.0. Amber warning card with: - AlertTriangle icon - "Core CPD hours below MAS minimum" heading - "MAS requires minimum 6.0 Core CPD hours (ethics/rules/regulations, IBF/SCI-accredited) per year per FAA-N26. Current: X.X hours." message

Hours Grid (4-column responsive): Displays four hour totals from the cpdHoursTotalForRep query:

Category Display Label Styling Source
Ethics "Ethics" (uppercase label) Gray background totals.ethics.toFixed(1)
Product "Product" (uppercase label) Gray background totals.product.toFixed(1)
Other "Other" (uppercase label) Gray background totals.other.toFixed(1)
Total "Total" (primary-colored) Primary-50 blue background, primary-700 text totals.total.toFixed(1)

The card heading is "YTD CPD Hours (YYYY)" with subtitle "Total hours logged across all categories for the current CPD cycle."

Category model note: The legacy ethics/product/other display buckets are being superseded by the statutory categories. The regnIFY CpdRecord model was migrated to track Core CPD (ethics or rules-&-regulations, IBF/SCI-accredited — subject to an accreditation gate) vs Supplementary CPD hours, matching FAA-N26 §5.4 / SFA 04-N22 §8.4. The "Ethics" total maps to Core; "Product"/"Other" map to Supplementary.

Add CPD Entry Toggle

When showAddForm is false, an "Add CPD Entry" button (Plus icon, primary variant) is shown aligned to the right above the records table. Clicking it sets showAddForm = true and reveals the AddCpdEntryForm component.

Add CPD Entry Form

The AddCpdEntryForm component renders as a full card with form fields:

Form Header: - "Log CPD Activity" heading - "Record a continuing professional development activity and its hours." subtitle - Cancel button (outline) aligned right

Form Fields (2-column grid):

Field Type Required Notes
Cycle Year number input Yes Default: current year. Range: 2000-2100.
Course Name text input Yes Placeholder: "e.g., Financial Products & Services"
Provider Name text input No Placeholder: "e.g., IBF, MAS, training provider"
IBF Course Code text input No Help text: "Optional: Include if this is an IBF-accredited course"; Placeholder: "e.g., IBF-CPD-12345"
Completion Date date input Yes CalendarIcon adornment; default: today's date in YYYY-MM-DD
Ethics Hours number input No Min: 0, step: 0.5; Help text: "Hours related to ethics, compliance, or regulation"
Product Hours number input No Min: 0, step: 0.5; Help text: "Hours on financial products, services, or markets"
Other Hours number input No Min: 0, step: 0.5; Help text: "Professional development hours not in the above categories"

Validation (via isFormValid()): - Course name must be non-empty after trim - Completion date must be non-empty - At least one of hoursEthics, hoursProduct, or hoursOther must be > 0 - Error toast: "Please fill in all required fields (course name, completion date, and at least one hour type)."

Footer: Cancel (outline) + "Log CPD Entry" (primary, Plus icon, disabled when form invalid, shows loading spinner while saving)

On successful submission: toast "CPD entry logged successfully.", form closes, records refresh.

CPD Records Table

The CpdRecordsTable component renders all CPD records for the current year:

Empty state: When no records exist, shows EmptyState component with: - BookOpen icon - "No CPD entries yet" title - "You haven't logged any CPD activities for the current cycle. Click 'Add CPD Entry' to get started." description

With records: Card with "CPD Log History" heading and horizontally scrollable table:

Column Content
Course Course name (bold) + IBF course code badge (info variant, small) if present
Provider Provider name or "—"
Date Completed SG-locale date
Ethics Hours formatted to 1 decimal place, or "—" if 0
Product Hours formatted to 1 decimal place, or "—" if 0
Other Hours formatted to 1 decimal place, or "—" if 0

Records are sorted by completedAt descending (newest first).

No-Profile State

If the user has no rep profile (!profileLoading && !repProfileId), the page renders: - PageHeader (same as normal) - EmptyState: "No Rep Profile Found" with BookOpen icon + description "Your representative profile has not been created yet. Please contact your administrator or HR to set up your profile before logging CPD activities."

FAA-N26 CPD Thresholds

The page displays one threshold warning: - Core CPD minimum: 6.0 hours per year (ethics/rules/regulations, IBF/SCI-accredited), shown via conditional warning banner when ethicsHours < 6.0 and ethicsHours > 0

The page does not display the full FAA-N26 thresholds (30h/year total: 6h Core CPD + 24h Supplementary CPD) as visual progress bars. Only the Core CPD minimum check is implemented in the UI. The draft_cpd_reminder chatbot tool (in FastAPI) can generate reminder emails that reference all FAA-N26 thresholds.

GraphQL Data Sources

Query Purpose Parameters Role Guard
repProfileByUser(userId) Get rep profile for current user userId: String (Keycloak sub) @Roles("FI_USER", "REP_USER", "COMP", "HR_ADMIN", "ORG_ADMIN", "SYS_ADMIN")
cpdRecordsForRep(repProfileId, cycleYear) Fetch CPD entries repProfileId: Int, cycleYear: Int (optional) Frontend note: resolver has @ServiceOnly() — the frontend GraphQL call to this query may fail without SERVICE_INTERNAL_TOKEN
cpdHoursTotalForRep(repProfileId, cycleYear) Get aggregated hours repProfileId: Int, cycleYear: Int @Roles("FI_USER", "REP_USER", "COMP", "HR_ADMIN", "ORG_ADMIN", "SYS_ADMIN")
createCpdRecord(input) Log a new CPD entry CreateCpdRecordInput @Roles("FI_USER", "REP_USER", "COMP", "HR_ADMIN", "ORG_ADMIN", "SYS_ADMIN")

CreateCpdRecordInput Shape

input CreateCpdRecordInput {
  repProfileId: Int!
  cycleYear: Int!
  courseName: String!
  providerName: String
  ibfCourseCode: String
  hoursEthics: Float      # defaults to 0 if not provided
  hoursProduct: Float     # defaults to 0 if not provided
  hoursOther: Float       # defaults to 0 if not provided
  completedAt: DateTime!
}

CpdHoursTotal Response Shape

type CpdHoursTotal {
  ethics: Float!
  product: Float!
  other: Float!
  total: Float!
}

Self-Service Capabilities

Representatives can: - View their own CPD records and YTD totals for the current year - Create new CPD entries (course name, provider, IBF code, hours by category, completion date) - See a warning when Core CPD hours fall below the 6.0 minimum (FAA-N26 §5.4(a)(i))

Representatives cannot: - Edit or delete existing CPD records (no update/delete mutation exposed on the frontend) - View CPD records for other representatives - Change the cycle year to a past or future year without admin assistance (frontend defaults to current year but allows manual override in the form input)

  • /rep — Rep Dashboard: overview with shortcuts to CPD Log, Passport, Attestations, Incident Disclosure
  • /rep/passport — Rep Passport: includes CPD compliance status as part of the exported regulatory passport
  • /admin/rep-register — Rep Register: admin view of all reps with R/A/G health badges computed from CMFAS + CPD status

Chatbot Interaction

The draft_cpd_reminder tool in FastAPI's chatbot_tools.py can draft reminder emails for CPD compliance. When a user on the CPD Log page asks about their CPD status, the chatbot should: - Read the YTD totals from the page data if available via page context (Note: the page does not currently provide page context, so the chatbot may need to call get_cpd_status tool) - Reference the FAA-N26 thresholds: 30h/year total: ≥6h Core CPD (ethics/rules, IBF/SCI-accredited) + ≥24h Supplementary CPD (FAA-N26 §5.4) - Point out which category is below threshold and suggest specific courses/types - The chatbot can call the draft_cpd_reminder tool which triggers a reminder email workflow (requires COMP or HR_ADMIN approval before sending)


Previous Next

Back to Regnify Product Docs