feat: Enrich DTOs and UI to display member names instead of UUIDs for task assignees, creators, and shift signups.

This commit is contained in:
WorkClub Automation
2026-03-18 14:15:33 +01:00
parent 65fea5d48b
commit da70cf4b13
17 changed files with 453 additions and 157 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-18
@@ -0,0 +1,99 @@
## Context
Currently, the frontend displays raw UUIDs for user references:
- Task list shows `assigneeId` (e.g., "a1b2c3d4-e5f6...") or "Unassigned"
- Task detail shows `assigneeId` and `createdById`
- Shift detail shows `memberId` for each signup
The backend already stores `DisplayName` in the `Member` entity but the API DTOs don't expose it. The `ShiftService` already demonstrates the pattern of joining with Members (lines 82-87), which we can replicate for Tasks.
## Goals / Non-Goals
**Goals:**
- Add member name fields to backend DTOs
- Update TaskService to query and include member names
- Update ShiftService to include member name in ShiftSignupDto
- Update frontend TypeScript interfaces
- Replace UUID displays with names in task/shift UIs
**Non-Goals:**
- No database schema changes
- No changes to authentication or authorization
- No changes to how tasks/shifts are created or updated
- No caching layer for member names
## Decisions
### 1. Add names to existing DTOs vs create new DTOs
**Decision:** Add optional fields to existing DTOs
**Rationale:**
- Keeps API surface simple
- Backward compatible - existing clients ignore new fields
- No breaking changes to existing integrations
**Alternative considered:** Create new DTO versions (e.g., `TaskDetailDtoV2`)
- Rejected: Unnecessary complexity for a simple additive change
### 2. Fetch member names via JOIN vs separate query
**Decision:** Use JOIN in TaskService methods
**Rationale:**
- More efficient - single query per endpoint
- Pattern already exists in ShiftService
- Avoids N+1 query problem
**Alternative considered:** Query members separately and build lookup dictionary
- Rejected: Adds complexity and extra database round-trips
### 3. Handle missing members (orphaned IDs)
**Decision:** Return null for name when member not found
**Rationale:**
- Data integrity issue should surface visibly
- Frontend can display fallback like "Unknown" or keep showing ID
- Logging can track data inconsistencies
### 4. Frontend handling of null names
**Decision:** Frontend shows fallback text when name is null
**Implementation:**
```typescript
// Task list
task.assigneeName || 'Unassigned'
// Task detail
task.assigneeName || 'Unassigned'
task.createdByName || 'Unknown'
// Shift signups
signup.memberName || 'Unknown Member'
```
## Risks / Trade-offs
| Risk | Mitigation |
|------|-----------|
| JOIN adds query complexity | Keep JOINs simple, only on indexed columns (Member.Id) |
| Larger API response payloads | Minimal impact - names are small strings |
| Member names become stale | Acceptable - names rarely change; eventual consistency |
| Database performance degradation | Monitor query execution plans; add caching if needed |
| Partial data on member deletion | Show "Unknown" fallback; log orphaned references |
## Migration Plan
1. **Backend DTO changes** - Add new optional fields
2. **Backend service changes** - Update queries to include names
3. **Frontend type updates** - Add name fields to interfaces
4. **Frontend UI updates** - Replace ID displays with names
**Rollback:**
- DTO changes are backward compatible
- Frontend can revert to showing IDs by changing display logic
- No database changes required
## Open Questions
- Should we include `externalUserId` in the signup display? (Currently available in ShiftSignupDto)
- Do we need to include member email for any display purposes?
- Should we add name fields to shift list items (showing creator name)?
@@ -0,0 +1,34 @@
## Why
Currently, the frontend displays raw UUIDs for user references (assignee, creator, members) which creates a poor user experience. Users should see meaningful names like "Alice Smith" instead of "a1b2c3d4-e5f6-7890-abcd-ef1234567890". The backend already stores display names in the Member entity, but the API DTOs don't expose them.
## What Changes
- **Backend DTOs**: Add name fields to task and shift DTOs
- `TaskListItemDto`: Add `string? AssigneeName`
- `TaskDetailDto`: Add `string? AssigneeName` and `string CreatedByName`
- `ShiftSignupDto`: Add `string MemberName`
- **Backend Services**: Update TaskService and ShiftService to query and populate member names
- Join with Members table to fetch display names
- Include names in DTO construction
- **Frontend Types**: Update TypeScript interfaces to include new name fields
- `TaskListItemDto`, `TaskDetailDto`, `ShiftSignupDto` interfaces
- **Frontend UI**: Replace UUID displays with names
- Task list: show assignee name instead of ID
- Task detail: show assignee and creator names
- Shift detail: show member names in signup list
## Capabilities
### New Capabilities
- `member-name-enrichment`: API DTOs include human-readable member names alongside IDs
### Modified Capabilities
- None (this is purely an enhancement to existing capabilities)
## Impact
- **Backend**: TaskService.cs, ShiftService.cs, and DTOs in WorkClub.Application
- **Frontend**: Tasks pages, Shifts pages, and React hooks (useTasks.ts, useShifts.ts)
- **Database**: Additional JOIN queries on Members table (no schema changes)
- **API Response**: New optional fields in existing endpoints (backward compatible)
@@ -0,0 +1,43 @@
## ADDED Requirements
### Requirement: Task list items include assignee name
The API SHALL return the assignee's display name in TaskListItemDto.
#### Scenario: Task with assignee
- **WHEN** a task is assigned to a member
- **THEN** the TaskListItemDto SHALL include the assignee's DisplayName as `assigneeName`
#### Scenario: Task without assignee
- **WHEN** a task has no assignee
- **THEN** the TaskListItemDto SHALL have `assigneeName` set to null
### Requirement: Task details include creator and assignee names
The API SHALL return the display names of both the creator and assignee in TaskDetailDto.
#### Scenario: Viewing task details
- **WHEN** a user requests task details
- **THEN** the TaskDetailDto SHALL include `createdByName` (the creator's DisplayName)
- **AND** the TaskDetailDto SHALL include `assigneeName` (the assignee's DisplayName, or null if unassigned)
### Requirement: Shift signup includes member name
The API SHALL return the member's display name in ShiftSignupDto.
#### Scenario: Viewing shift signups
- **WHEN** a user views shift details with signups
- **THEN** each ShiftSignupDto SHALL include `memberName` (the member's DisplayName)
### Requirement: Frontend displays names instead of UUIDs
The frontend SHALL render member names instead of UUIDs wherever user references appear.
#### Scenario: Task list view
- **WHEN** viewing the task list
- **THEN** the Assignee column SHALL display the assignee's name (or "Unassigned")
#### Scenario: Task detail view
- **WHEN** viewing a task detail page
- **THEN** the Assignee field SHALL display the assignee's name (or "Unassigned")
- **AND** the Created By field SHALL display the creator's name
#### Scenario: Shift detail view
- **WHEN** viewing a shift detail page with signups
- **THEN** the member list SHALL display each member's name instead of their ID
@@ -0,0 +1,41 @@
## 1. Backend DTO Updates
- [x] 1.1 Update TaskListItemDto.cs to add `string? AssigneeName` field
- [x] 1.2 Update TaskDetailDto.cs to add `string? AssigneeName` and `string? CreatedByName` fields
- [x] 1.3 Update ShiftSignupDto.cs to add `string? MemberName` field
## 2. Backend Service Updates - Tasks
- [x] 2.1 Update TaskService.GetTasksAsync() to join with Members and populate assigneeName
- [x] 2.2 Update TaskService.GetTaskByIdAsync() to join with Members for assignee and creator names
- [x] 2.3 Update TaskService.CreateTaskAsync() to fetch and include creator name in response
- [x] 2.4 Update TaskService.UpdateTaskAsync() to join with Members for assignee and creator names
## 3. Backend Service Updates - Shifts
- [x] 3.1 Update ShiftService.GetShiftByIdAsync() to include member display name in ShiftSignupDto
- [x] 3.2 Update ShiftService.UpdateShiftAsync() to include member display name in ShiftSignupDto
## 4. Frontend Type Updates
- [x] 4.1 Update TaskListItemDto interface in useTasks.ts to add `assigneeName?: string`
- [x] 4.2 Update TaskDetailDto interface in useTasks.ts to add `assigneeName?: string` and `createdByName?: string`
- [x] 4.3 Update ShiftSignupDto interface in useShifts.ts to add `memberName?: string`
## 5. Frontend UI Updates - Tasks
- [x] 5.1 Update tasks/page.tsx to display assigneeName instead of assigneeId
- [x] 5.2 Update tasks/[id]/page.tsx to display assigneeName instead of assigneeId
- [x] 5.3 Update tasks/[id]/page.tsx to display createdByName instead of createdById
## 6. Frontend UI Updates - Shifts
- [x] 6.1 Update shifts/[id]/page.tsx to display memberName instead of memberId in signup list
## 7. Testing & Verification
- [x] 7.1 Run backend build to verify C# compilation succeeds
- [x] 7.2 Run frontend build to verify TypeScript compilation succeeds
- [x] 7.3 Verify task list shows member names correctly
- [x] 7.4 Verify task detail shows assignee and creator names
- [x] 7.5 Verify shift detail shows member names in signup list