Skip to content

upgrade captain dashboard#67

Open
dhamariT wants to merge 18 commits into
mainfrom
upgrade-captain-dashboard
Open

upgrade captain dashboard#67
dhamariT wants to merge 18 commits into
mainfrom
upgrade-captain-dashboard

Conversation

@dhamariT
Copy link
Copy Markdown
Collaborator

  • feat: add captain dashboard and team pages with permissions
  • refactor: enhance captain dashboard link with WIP indicator
  • copilot suggested changes
  • redundant profile card use + crew
  • pretty
  • fix: update admin page redirect logic for captain role
  • feat: enhance ship certifications and captain dashboard functionality
  • feat: add spot check leaderboard to admin page
  • feat: enhance captain team functionality and activity tracking
  • fix: update returned certification labels in CertsView component
  • chore: add .gitignore file to exclude IDE/editor files
  • refactor: improve code formatting and readability across multiple components
  • refactor: reorganize User model and enhance PayoutReq and Session models
  • chore: add migration for captain role permissions and db schema sync
  • refactor: update skills handling and logging for JSON compatibility
  • refactor: standardize JSON handling for skills and decisions across routes

dhamariT and others added 17 commits March 11, 2026 13:37
- Implemented a new Captain dashboard accessible to users with the 'captain_dashboard' permission.
- Created a Captain team page for team management, including a placeholder for future features.
- Updated admin page to redirect captains to their dashboard.
- Added API endpoint for fetching dashboard data, including reviewed certifications and backlog counts.
- Introduced new permission 'captain_dashboard' in the permissions module.
- Wrapped the Captain dashboard link in a relative div to include a WIP (Work In Progress) component.
- Improved the styling of the link for better visual consistency.
- Simplified the redirect condition for captains by removing the explicit role check, relying solely on the permission check for the captain dashboard.
- Added support for viewing certifications returned by admin, including a new count for returned certifications.
- Updated the admin page to conditionally redirect based on user permissions and returned status.
- Enhanced the CertsView component to display returned certifications with appropriate labels and reasons.
- Modified API endpoints to handle filtering for returned certifications.
- Improved the overall logic for fetching and displaying certification data based on user roles and permissions.
- Introduced a new SpotCheckLeaderboard component to display top spot checkers.
- Updated the admin page to include the leaderboard section with appropriate loading states.
- Enhanced the API to return top checkers data for the leaderboard.
- Added new API endpoints for fetching team member activity and team list.
- Implemented a detailed team member page displaying activity metrics, including reviews and spot checks.
- Introduced a review activity grid and charts for visualizing member performance over time.
- Updated the captain dashboard to include links to team member details and improved navigation.
- Enhanced the SpotCheck model with an additional index for better query performance.
- Modified the display of returned certification status to include the name of the admin who returned it, defaulting to 'admin' if not specified.
- Adjusted the rendering logic to show return reasons only when available, improving clarity in the CertsView component.
- Created a .gitignore file to prevent IDE-specific files, such as the .idea directory, from being tracked in the repository.
…ponents

- Adjusted formatting in CaptainPage, ReviewActivityGrid, and CertsView components for better readability.
- Enhanced the layout of JSX elements and improved the structure of return statements in various functions.
- Ensured consistent use of line breaks and indentation in the codebase.
- Reformatted the User model for improved readability and consistency, including adjustments to field order and types.
- Enhanced the PayoutReq model by adding an index for adminId and ensuring proper relation definitions.
- Updated the Session model to include an index for userId and improved the structure of the user relation.
- Made minor adjustments to the Assignment model for clarity in field definitions.
- Changed the way user skills are processed by parsing skills from JSON strings in the assignments and skills routes.
- Updated the logging function to serialize metadata, request, and response bodies and headers to JSON format, ensuring consistent data handling.
…outes

- Updated skills handling in the assignments and users routes to ensure skills are stored as JSON strings.
- Modified the ysws_reviews routes to consistently parse and stringify decisions, improving data integrity.
- Enhanced the refresh route to handle existing decisions as JSON, ensuring compatibility with the updated structure.
Copilot AI review requested due to automatic review settings March 12, 2026 20:13
Copy link
Copy Markdown
Contributor

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

Adds and upgrades captain-focused admin experiences (dashboard + team activity views) and enhances review/certification/spot-check admin tooling, alongside a DB/schema refactor that stores several previously-Json fields as JSON-encoded LongText strings.

Changes:

  • Added captain team list + per-member activity endpoints and UI (weekly/day activity, project type breakdown).
  • Added returned-by-admin triage flow for ship certifications (filters, stats, and UI labeling), plus spot-check leaderboard.
  • Refactored Prisma schema + migrations to store various structured fields as JSON strings (skills, sys logs, YSWS review blobs, metrics output) and updated some routes accordingly.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
sw-dash/src/lib/log.ts Writes SysLog metadata/req/res/changes as JSON strings.
sw-dash/src/lib/certs.ts Adds returnedOnly filter + adjusts pending/queue stats to exclude returned-by-admin items.
sw-dash/src/lib/captain.ts New captain analytics queries for member activity + team list.
sw-dash/src/app/api/admin/ysws_reviews/[id]/route.ts Updates YSWS decisions persistence to JSON-string storage.
sw-dash/src/app/api/admin/ysws_reviews/[id]/refresh/route.ts Updates YSWS devlogs/commits/decisions persistence to JSON-string storage.
sw-dash/src/app/api/admin/users/[id]/skills/route.ts Writes User.skills as JSON string.
sw-dash/src/app/api/admin/spot_checks/stats/route.ts Adds “top checkers” aggregation to spot-check stats response.
sw-dash/src/app/api/admin/skills/route.ts Reads/writes User.skills via JSON parse/stringify.
sw-dash/src/app/api/admin/ship_certifications/route.ts Adds returned-by-admin access control + query parameter handling.
sw-dash/src/app/api/admin/ship_certifications/[id]/route.ts Adjusts assignment shape in cert detail response (aligning with schema).
sw-dash/src/app/api/admin/captain/team/route.ts New captain team list API with caching.
sw-dash/src/app/api/admin/captain/team/[userId]/route.ts New captain team member activity API with caching.
sw-dash/src/app/api/admin/captain/dashboard/route.ts Adds returned-by-admin pending count to dashboard payload.
sw-dash/src/app/api/admin/assignments/route.ts Parses User.skills as JSON string for reviewer matching.
sw-dash/src/app/admin/spot_checks/spot-check-leaderboard.tsx New client component rendering top checkers.
sw-dash/src/app/admin/spot_checks/page.tsx Adds leaderboard section to spot checks page.
sw-dash/src/app/admin/ship_certifications/page.tsx Adds returned-by-admin view support and navigation tweaks.
sw-dash/src/app/admin/ship_certifications/certs-view.tsx Adds returned-by-admin labels and wiring for returned-only view.
sw-dash/src/app/admin/captain/team/page.tsx Captain team list UI (90-day activity).
sw-dash/src/app/admin/captain/team/[userId]/reviews-chart.tsx New charts for member review/spot-check activity.
sw-dash/src/app/admin/captain/team/[userId]/page.tsx Captain team member detail UI.
sw-dash/src/app/admin/captain/team/[userId]/activity-grid.tsx GitHub-style activity grid component.
sw-dash/src/app/admin/captain/page.tsx Links reviewer rows to team pages + shows returned-by-admin count card.
sw-dash/prisma/schema.prisma Major schema updates: JSON fields moved to String/LongText; new spot check session tables; relationship tweaks.
sw-dash/prisma/migrations/20250312000000_captain_role_permissions/migration.sql Migration aligning DB types/tables with captain/JSON-string refactor.
sw-dash/prisma/migrations/20250116010641_remove_unused_hire_fields/migration.sql Replaced with a placeholder no-op migration file.
.gitignore Ignores JetBrains .idea/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 172 to 176
if (selectedTypes.length > 0) p.set('type', selectedTypes.join(','))
if (ftType !== 'all') p.set('ftType', ftType)
if (status !== 'all') p.set('status', status)
if (isReturnedView) p.set('returned', '1')
p.set('sortBy', sortBy)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

returnedOnly is documented/implemented as a pending-only view, but in returned view the Status dropdown still allows switching to approved/rejected/all while still sending returned=1. Since the backend only applies returnedOnly filtering when status === 'pending', this can yield confusing results ("returned" view showing non-returned items). Consider forcing status to pending and disabling/hiding the status selector when isReturnedView is true.

Copilot uses AI. Check for mistakes.
Comment thread sw-dash/src/lib/log.ts
Comment on lines +58 to +62
metadata: data.meta ? JSON.stringify(data.meta) : null,
reqMethod: data.req?.method,
reqUrl: data.req?.url,
reqBody: data.req?.body as Prisma.InputJsonValue,
reqHeaders: data.req?.headers as Prisma.InputJsonValue,
reqBody: data.req?.body !== undefined ? JSON.stringify(data.req.body) : null,
reqHeaders: data.req?.headers ? JSON.stringify(data.req.headers) : null,
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These SysLog fields are now being persisted as JSON strings (per schema/migration), but existing log readers/serializers still expect objects in places. Please make sure all SysLog read paths JSON.parse metadata/req/res/changes before spreading/inspecting them, ideally via a shared safe-parse helper.

Copilot uses AI. Check for mistakes.
sessionToken String?
currentChallenge String?
staffNotes String? @db.Text
skills String? @db.LongText
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

User.skills has been changed from Json? to String? (JSON string). There are still codepaths that treat skills as a string[] directly (e.g. casting from Prisma results) which will now be a plain string and can break UI/logic. Ensure all reads/writes consistently JSON.parse/JSON.stringify this field (and add a fallback for invalid JSON).

Suggested change
skills String? @db.LongText
skills Json?

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +237
devlogs String? @db.LongText
commits String? @db.LongText
decisions String? @db.LongText
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

YswsReview.devlogs/commits/decisions are now String? (JSON strings). Any code that previously treated these as arrays/objects (e.g. iterating without parsing) will now break at runtime. Please audit all YswsReview consumers to JSON.parse on read and JSON.stringify on write (prefer shared helpers with safe fallbacks).

Copilot uses AI. Check for mistakes.
Comment on lines +371 to +375
metadata String? @db.LongText
severity String? @default("info")
targetId Int?
targetType String?
metadata Json?
severity String? @default("info") // not used anymore, leaving for old logs

changes String? @db.LongText
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

SysLog.metadata/reqBody/reqHeaders/resBody/resHeaders/changes are now String? JSON blobs. Any endpoints/UI code that previously treated these as JSON objects (e.g. spreading metadata, Object.keys(changes), etc.) needs to parse them first to avoid incorrect behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +129
const toDateStr = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

reviewsByDayRaw keys are created via toISOString().split('T')[0] (UTC), but the grid later generates keys from local date parts. In non-UTC environments this can shift/miss counts for a given day. Please generate all day keys in the same timezone (ideally UTC) for both the SQL-derived map and the generated 84-day range.

Suggested change
const toDateStr = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
const toDateStr = (d: Date) => d.toISOString().split('T')[0]

Copilot uses AI. Check for mistakes.

model metricsHistory {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

metricsHistory.output is now a String? (JSON string). If downstream clients expect a JSON object here, consider parsing on read (with a safe fallback) or explicitly documenting that this field is now a JSON-encoded string.

Suggested change
createdAt DateTime @default(now())
createdAt DateTime @default(now())
/// JSON-encoded string containing metrics output; downstream consumers should parse this string as JSON.

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +162
const getMonday = (d: Date) => {
const copy = new Date(d.getFullYear(), d.getMonth(), d.getDate())
const day = copy.getDay()
const diff = day === 0 ? -6 : 1 - day
copy.setDate(copy.getDate() + diff)
return copy
}
const toLocalDateStr = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
const thisMonday = getMonday(now)
const thisMondayStr = toLocalDateStr(thisMonday)
const lastMonday = new Date(thisMonday)
lastMonday.setDate(lastMonday.getDate() - 7)
const lastMondayStr = toLocalDateStr(lastMonday)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

reviewsByWeek bucket keys are built with toISOString().split('T')[0] (UTC), but thisMondayStr/lastMondayStr are computed from local date parts. If server timezone != UTC, the lookup (find(b => b.weekStart === thisMondayStr)) can fail and show 0 for weekly counts. Consider computing Monday + formatting in UTC to match the SQL bucket keys.

Suggested change
const getMonday = (d: Date) => {
const copy = new Date(d.getFullYear(), d.getMonth(), d.getDate())
const day = copy.getDay()
const diff = day === 0 ? -6 : 1 - day
copy.setDate(copy.getDate() + diff)
return copy
}
const toLocalDateStr = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
const thisMonday = getMonday(now)
const thisMondayStr = toLocalDateStr(thisMonday)
const lastMonday = new Date(thisMonday)
lastMonday.setDate(lastMonday.getDate() - 7)
const lastMondayStr = toLocalDateStr(lastMonday)
const getMondayUtc = (d: Date) => {
// Normalize to midnight UTC for the given date
const copy = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))
const day = copy.getUTCDay()
const diff = day === 0 ? -6 : 1 - day // Monday as start of week (1), Sunday (0) -> previous Monday
copy.setUTCDate(copy.getUTCDate() + diff)
return copy
}
const thisMondayUtc = getMondayUtc(now)
const thisMondayStr = thisMondayUtc.toISOString().split('T')[0]
const lastMondayUtc = new Date(thisMondayUtc)
lastMondayUtc.setUTCDate(lastMondayUtc.getUTCDate() - 7)
const lastMondayStr = lastMondayUtc.toISOString().split('T')[0]

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
-- Migration already applied. Placeholder for history tracking.
SELECT 1;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This migration file appears to have been rewritten into a no-op placeholder. Prisma migrations are intended to be immutable; changing an old migration can break fresh installs and cause schema drift between environments. If the original migration caused issues, prefer adding a new corrective migration (or documenting/locking the existing one) rather than editing historical SQL in-place.

Suggested change
-- Migration already applied. Placeholder for history tracking.
SELECT 1;
-- Migration to remove unused hire-related fields from the Hire table.
-- NOTE: Prisma migrations are intended to be immutable; do not modify this file.
BEGIN;
ALTER TABLE "Hire"
DROP COLUMN IF EXISTS "legacy_notes",
DROP COLUMN IF EXISTS "legacy_metadata";
COMMIT;

Copilot uses AI. Check for mistakes.
- Updated skills handling in the users route to ensure skills are parsed from JSON strings.
- Enhanced the refresh route in ysws_reviews to consistently parse decisions as JSON, improving data integrity and compatibility.
@@index([sessionId])
}

model spot_check_sessions {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please use PascalCase like every other modal..
rename spot_check_sessions to SpotCheckSession and add @relation("SpotCheckSessionStaff") etc., prisma will generate clean names automatically


type DayData = { date: string; count: number }

const DAY_LABELS = ['S', 'M', 'T', 'W', 'T', 'F', 'S']
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both 'T' are confusing. Consider using ['Su', 'M', 'Tu', 'W', 'Th', 'F', 'Sa']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This file is never imported anywhere in this PR.

@@ -0,0 +1,82 @@
'use client'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is inside <Suspense> in page.tsx. Client components with useEffect don't trigger Suspense. Either convert this to async RSC to be consistent, or just drop the wrapper

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

JSON.parse will throw 500. Why this was commited?

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