Add PriorityAlertsPanel component to the dashboard, update DashboardController with alert logic, and apply misc UI fixes across sidebar, forms, and pages. Includes epic-1 retrospective and sprint status update. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
291 lines
8.9 KiB
PHP
291 lines
8.9 KiB
PHP
<?php
|
|
|
|
use App\Enums\DeclarationStatus;
|
|
use App\Models\Client;
|
|
use App\Models\Declaration;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
function setupAlertWorkspace(string $role = 'owner'): array
|
|
{
|
|
$user = User::factory()->create();
|
|
$workspace = Workspace::factory()->create();
|
|
$workspace->users()->attach($user->id, ['role' => $role]);
|
|
$client = Client::factory()->create(['workspace_id' => $workspace->id]);
|
|
session(['current_workspace_id' => $workspace->id]);
|
|
|
|
return [$user, $workspace, $client];
|
|
}
|
|
|
|
test('overdue declarations appear as critical alerts with correct daysOverdue', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
$declaration = Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays(5),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 1)
|
|
->where('alerts.0.severity', 'critical')
|
|
->where('alerts.0.clientName', $client->company_name)
|
|
->where('alerts.0.daysValue', 5)
|
|
->where('alerts.0.daysLabel', 'jours en retard')
|
|
->has('alerts.0.showUrl')
|
|
);
|
|
});
|
|
|
|
test('approaching deadline declarations appear as warning alerts', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->addDays(2),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 1)
|
|
->where('alerts.0.severity', 'warning')
|
|
->where('alerts.0.daysLabel', 'jours restants')
|
|
);
|
|
});
|
|
|
|
test('en_attente_client over 3 days appears as info alert', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnAttenteClient,
|
|
'due_date' => now()->addDays(10),
|
|
'updated_at' => now()->subDays(5),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->where('alerts.0.severity', 'info')
|
|
->where('alerts.0.daysLabel', 'jours en attente')
|
|
);
|
|
});
|
|
|
|
test('en_attente_client 3 days or less does not appear as info alert', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnAttenteClient,
|
|
'due_date' => now()->addDays(10),
|
|
'updated_at' => now()->subDays(2),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 0)
|
|
);
|
|
});
|
|
|
|
test('alerts are capped at 20 items', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
// Create 25 overdue declarations
|
|
for ($i = 1; $i <= 25; $i++) {
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays($i),
|
|
]);
|
|
}
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 20)
|
|
);
|
|
});
|
|
|
|
test('alerts are sorted by severity: critical first, then warning, then info', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
// Info alert (en_attente_client >3 days)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnAttenteClient,
|
|
'due_date' => now()->addDays(10),
|
|
'updated_at' => now()->subDays(5),
|
|
]);
|
|
|
|
// Warning alert (due in 2 days)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->addDays(2),
|
|
]);
|
|
|
|
// Critical alert (overdue)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays(3),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 3)
|
|
->where('alerts.0.severity', 'critical')
|
|
->where('alerts.1.severity', 'warning')
|
|
->where('alerts.2.severity', 'info')
|
|
);
|
|
});
|
|
|
|
test('worker sees only alerts for assigned declarations', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace('worker');
|
|
|
|
$otherUser = User::factory()->create();
|
|
$workspace->users()->attach($otherUser->id, ['role' => 'worker']);
|
|
|
|
// Overdue assigned to this worker (should appear)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'assigned_to' => $user->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays(2),
|
|
]);
|
|
|
|
// Overdue assigned to other worker (should NOT appear)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'assigned_to' => $otherUser->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays(3),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 1)
|
|
->where('alerts.0.severity', 'critical')
|
|
);
|
|
});
|
|
|
|
test('empty alerts array when no urgent items exist', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
// Declaration far in the future (no alert)
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->addDays(30),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 0)
|
|
);
|
|
});
|
|
|
|
test('alerts are included in cached dashboard payload', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::EnCours,
|
|
'due_date' => now()->subDays(2),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$this->get(route('dashboard'))->assertOk();
|
|
|
|
$cacheKey = "dashboard:{$workspace->id}:{$user->id}";
|
|
expect(Cache::has($cacheKey))->toBeTrue();
|
|
|
|
$cached = Cache::get($cacheKey);
|
|
expect($cached)->toBeArray()
|
|
->and($cached)->toHaveKey('alerts')
|
|
->and($cached['alerts'])->toBeArray()
|
|
->and($cached['alerts'])->toHaveCount(1)
|
|
->and($cached['alerts'][0]['severity'])->toBe('critical');
|
|
});
|
|
|
|
test('excluded statuses do not generate alerts', function () {
|
|
[$user, $workspace, $client] = setupAlertWorkspace();
|
|
|
|
// Termine (excluded) - overdue
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::Termine,
|
|
'due_date' => now()->subDays(5),
|
|
]);
|
|
|
|
// MiseEnDemeure (excluded) - overdue
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::MiseEnDemeure,
|
|
'due_date' => now()->subDays(3),
|
|
]);
|
|
|
|
// Ferme (excluded) - overdue
|
|
Declaration::factory()->create([
|
|
'workspace_id' => $workspace->id,
|
|
'client_id' => $client->id,
|
|
'status' => DeclarationStatus::Ferme,
|
|
'due_date' => now()->subDays(1),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$response = $this->get(route('dashboard'));
|
|
|
|
$response->assertOk();
|
|
$response->assertInertia(fn ($page) => $page
|
|
->component('Dashboard')
|
|
->has('alerts', 0)
|
|
);
|
|
});
|