## Technical Design: API Resource Standard

### Overview
This design establishes Laravel API Resources as the standard for all API responses, ensuring consistent `"data"` wrapper across all endpoints.

---

### Directory Structure

```
backend/app/Http/Resources/
├── BaseResource.php              # Abstract base with common utilities
├── UserResource.php
├── RoleResource.php
├── TeamMemberResource.php
├── ProjectStatusResource.php
├── ProjectTypeResource.php
├── ProjectResource.php
├── HolidayResource.php
├── PtoResource.php
├── CapacityResource.php
├── TeamCapacityResource.php
└── RevenueResource.php
```

---

### Base Resource Design

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

abstract class BaseResource extends JsonResource
{
    /**
     * Standard date format for all API responses
     */
    protected function formatDate($date): ?string
    {
        return $date?->toIso8601String();
    }

    /**
     * Format decimal values consistently
     */
    protected function formatDecimal($value, int $decimals = 2): ?float
    {
        return $value !== null ? round((float) $value, $decimals) : null;
    }
}
```

---

### Resource Implementation Pattern

Each resource follows this pattern:

```php
<?php

namespace App\Http\Resources;

class TeamMemberResource extends BaseResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'role' => new RoleResource($this->whenLoaded('role')),
            'hourly_rate' => $this->formatDecimal($this->hourly_rate),
            'active' => (bool) $this->active,
            'created_at' => $this->formatDate($this->created_at),
            'updated_at' => $this->formatDate($this->updated_at),
        ];
    }
}
```

---

### Controller Update Pattern

**BEFORE:**
```php
public function index(Request $request): JsonResponse
{
    $members = $this->teamMemberService->getAll();
    return response()->json($members);
}
```

**AFTER:**
```php
public function index(Request $request): JsonResponse
{
    $members = $this->teamMemberService->getAll();
    return response()->json(new TeamMemberResource($members));
    // Or for collections:
    // return response()->json(TeamMemberResource::collection($members));
}
```

---

### Relationship Loading Strategy

Use Laravel's `whenLoaded()` to conditionally include relationships:

```php
public function toArray($request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        // Only include role if it was eager loaded
        'role' => new RoleResource($this->whenLoaded('role')),
        // Only include team_member if it was eager loaded  
        'team_member' => new TeamMemberResource($this->whenLoaded('teamMember')),
    ];
}
```

**Controller Responsibility:**
```php
// Eager load relationships before passing to resource
$members = TeamMember::with('role')->get();
return response()->json(TeamMemberResource::collection($members));
```

---

### Frontend API Client Update

**Create helper function to unwrap responses:**

```typescript
// src/lib/api/client.ts

export async function unwrapResponse<T>(response: Response): Promise<T> {
  const data = await response.json();
  return data.data as T;
}

// Usage in API functions:
export async function getTeamMembers(): Promise<TeamMember[]> {
  const response = await fetch('/api/team-members');
  return unwrapResponse<TeamMember[]>(response);
}
```

**Update all API calls:**
```typescript
// BEFORE:
const members = await response.json();

// AFTER:
const members = await unwrapResponse<TeamMember[]>(response);
```

---

### Test Update Pattern

**Backend Feature Tests:**
```php
// BEFORE:
$response->assertJson(['name' => 'John Doe']);

// AFTER:
$response->assertJson(['data' => ['name' => 'John Doe']]);
// Or more explicitly:
$this->assertEquals('John Doe', $response->json('data.name'));
```

**Resource Unit Tests:**
```php
test('resource wraps single model in data key', function () {
    $member = TeamMember::factory()->create();
    $resource = new TeamMemberResource($member);
    
    $this->assertArrayHasKey('data', $resource->resolve());
    $this->assertEquals($member->id, $resource->resolve()['data']['id']);
});

test('collection wraps in data array', function () {
    $members = TeamMember::factory(3)->create();
    $collection = TeamMemberResource::collection($members);
    
    $this->assertArrayHasKey('data', $collection->resolve());
    $this->assertCount(3, $collection->resolve()['data']);
});
```

---

### Caching Considerations

API Resources don't affect caching strategy - they transform data at response time. Cache storage remains the same:

```php
// Cache raw model data (not transformed resources)
$cached = Cache::remember('team-members:all', 3600, function () {
    return TeamMember::with('role')->get();
});

// Transform on response
return response()->json(TeamMemberResource::collection($cached));
```

---

### Error Response Handling

Error responses remain unchanged (no `"data"` wrapper):

```php
// Validation errors
return response()->json([
    'message' => 'The given data was invalid.',
    'errors' => $validator->errors(),
], 422);

// Not found
return response()->json([
    'message' => 'Resource not found',
], 404);
```

---

### Scribe Documentation Updates

Update all `@response` annotations to show new format:

```php
/**
 * @response {
 *   "data": {
 *     "id": "550e8400-e29b-41d4-a716-446655440000",
 *     "name": "John Doe"
 *   }
 * }
 */
```

---

### Migration Steps

1. **Phase 1: Resources** - Create all resource classes
2. **Phase 2: Controllers** - Update one controller at a time, run tests
3. **Phase 3: Frontend** - Update API client helper, then each endpoint
4. **Phase 4: Tests** - Update all test assertions
5. **Phase 5: Docs** - Regenerate Scribe documentation

---

### Rollback Plan

Since this is a breaking change for frontend:
- Commit after each controller update
- Run full test suite before next controller
- If critical issue found, revert specific controller commit
