# Projects Page Blank State Fix

## 1. Root cause

The `/projects` page (`ProjectsPage.tsx`) only rendered a project grid when `!isLoading && !isError`. It had no branch for an empty list, no skeleton layout, no dedicated forbidden handling, and a minimal error message. When the API returned zero projects (or the list resolved to an empty array), users saw header actions and blank whitespace.

`fetchProjects()` also returned the raw axios body without validating Laravel’s paginated shape. A malformed response could yield `data?.data ?? []` as `[]` without surfacing an error.

`CreateProjectModal` silently returned on submit when the user had no `departments[0].id`, so “New project” appeared broken for admins without a default department.

## 2. Files changed

| Area | File |
|------|------|
| Page UI | `resources/js/src/features/projects/pages/ProjectsPage.tsx` |
| Skeleton | `resources/js/src/features/projects/components/ProjectsListSkeleton.tsx` |
| Cards | `resources/js/src/features/projects/components/ProjectCard.tsx` |
| Create modal | `resources/js/src/features/projects/components/CreateProjectModal.tsx` |
| API client | `resources/js/src/features/projects/services/projectService.ts` |
| Types | `resources/js/src/features/projects/types/project.types.ts` |
| i18n | `resources/js/src/locales/en/projects.json`, `resources/js/src/locales/ar/projects.json` |
| Backend list | `app/Modules/Projects/Services/ProjectService.php` |
| Backend resource | `app/Modules/Projects/Resources/ProjectResource.php` |
| Backend model | `app/Modules/Projects/Models/Project.php` |
| Backend tests | `tests/Feature/Projects/ProjectApiTest.php` |
| Frontend tests | `resources/js/src/__tests__/unit/features/projects/ProjectsPage.test.tsx` |
| Docs | `docs/AGILE_PROJECTS_AND_GANTT_REPORT.md` |

## 3. API response shape

`GET /api/v1/projects` returns Laravel pagination:

```json
{
  "data": [ { "id", "name", "key", "status", "department", "owner", "is_overdue", ... } ],
  "meta": { "current_page", "last_page", "per_page", "total" },
  "links": { ... }
}
```

List requests from the frontend add:

- `include_progress=1` — includes `completion_percentage` per project
- `include_health=1` — includes `health_level` (`healthy` | `attention` | `critical`)
- Eager-loaded `active_sprint` when an active sprint exists

`fetchProjects()` validates with `isPaginatedResponse(raw, isProjectSummary)` and throws if the shape is invalid (surfaces error UI). A bare array fallback is supported for compatibility.

## 4. UI states fixed

| State | Behavior |
|-------|----------|
| Loading | `ProjectsListSkeleton` (6 card skeletons), no blank area |
| Success (data) | Grid of `ProjectCard` with status, department, owner, due date, progress bar, health, active sprint |
| Empty | `EmptyStateCard`: “No projects yet”, description, “Create project”, portfolio link |
| Error | Message, retry button, stack trace in non-production only |
| Forbidden (403) | “You do not have permission to view projects.” |

Create project: modal with company/department pickers (super admin / company admin), validation messages, and API field errors.

## 5. Tests added

**Frontend** (`ProjectsPage.test.tsx`):

- Loading skeleton
- Project cards on success
- Empty state
- Error + retry
- Forbidden (403)
- New project opens modal

**Backend** (`ProjectApiTest.php`):

- Paginated index with project
- Empty `data` array
- User without company → 403 on index
- Department head scoping on index

## 6. Validation result

Run locally after merge:

```bash
php artisan test
npm run type-check
npm run lint
npm run build
npm run test -- --run
```

| Command | Result |
|---------|--------|
| `php artisan test` | 221 passed (Projects API: 12) |
| `npm run type-check` | Pass |
| `npm run lint` | Pass |
| `npm run build` | Pass |
| `npm run test -- --run` | 164 passed (46 files) |
