release: dev -> main v0.2.0 #93
3 changed files with 99 additions and 12 deletions
|
|
@ -4,12 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Catalogue\StatsRepository;
|
||||
use App\Core\Response;
|
||||
|
||||
/**
|
||||
* Tableau de bord back-office. GET /admin/dashboard (landing par defaut du role
|
||||
* admin, cf. seed role.default_route). Accessible a tout utilisateur authentifie ;
|
||||
* les KPI reels (stats.read) seront ajoutes au chunk statistiques.
|
||||
* admin, cf. seed role.default_route). Accessible a tout utilisateur authentifie.
|
||||
* Affiche des indicateurs synthetiques (catalogue + sante stock) ; le detail vit
|
||||
* sous /admin/stats (permission stats.read).
|
||||
*
|
||||
* Non `final` : les tests sous-classent pour injecter des doubles via les hooks.
|
||||
*/
|
||||
|
|
@ -25,10 +27,22 @@ class DashboardController extends AdminController
|
|||
return $guard;
|
||||
}
|
||||
|
||||
$stats = $this->statsRepository();
|
||||
|
||||
return $this->adminView(
|
||||
'admin/dashboard',
|
||||
['title' => 'Tableau de bord - Wakdo Admin', 'activeNav' => 'dashboard'],
|
||||
[
|
||||
'title' => 'Tableau de bord - Wakdo Admin',
|
||||
'activeNav' => 'dashboard',
|
||||
'counts' => $stats->counts(),
|
||||
'stock' => $stats->stockHealth(),
|
||||
],
|
||||
$guard,
|
||||
);
|
||||
}
|
||||
|
||||
protected function statsRepository(): StatsRepository
|
||||
{
|
||||
return new StatsRepository($this->db());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,65 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fragment du tableau de bord, injecte dans admin/layout.php. Volontairement
|
||||
* minimal en chunk shell : les KPI reels (ventes, commandes) viendront avec le
|
||||
* chunk statistiques (permission stats.read).
|
||||
* Tableau de bord, injecte dans admin/layout.php (direction UI A+C).
|
||||
* Indicateurs synthetiques catalogue + sante stock (StatsRepository).
|
||||
*
|
||||
* @var string $currentUserName
|
||||
* @var string $currentUserName
|
||||
* @var array<string, array<string,int>> $counts
|
||||
* @var array{bands:array<string,int>} $stock
|
||||
*/
|
||||
|
||||
$name = htmlspecialchars($currentUserName ?? 'Utilisateur', ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$kpi = isset($counts) && is_array($counts) ? $counts : [];
|
||||
$stk = isset($stock) && is_array($stock) ? $stock : [];
|
||||
|
||||
$nProducts = (int) ($kpi['products']['available'] ?? 0);
|
||||
$nCategories = (int) ($kpi['categories']['total'] ?? 0);
|
||||
$nMenus = (int) ($kpi['menus']['total'] ?? 0);
|
||||
$nCritical = (int) ($stk['bands']['critical'] ?? 0);
|
||||
?>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Tableau de bord</h1>
|
||||
<p class="page-subtitle">Bienvenue, <?= $name ?>.</p>
|
||||
<p class="page-subtitle">Bienvenue, <?= $name ?> — voici l'essentiel de votre restaurant aujourd'hui.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<p>Le back-office est en ligne. Utilisez la navigation pour gerer le catalogue,
|
||||
les commandes et les utilisateurs selon vos permissions.</p>
|
||||
<p><small>Les indicateurs (ventes, commandes du jour) seront ajoutes prochainement.</small></p>
|
||||
<section class="dash-tiles" aria-label="Indicateurs cles">
|
||||
<article class="tile">
|
||||
<div class="tile-top">
|
||||
<span class="tile-ico"><svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 8h14l-1 11a2 2 0 01-2 2H8a2 2 0 01-2-2L5 8z"/><path d="M9 8a3 3 0 016 0"/></svg></span>
|
||||
<span class="tile-tag">En vente</span>
|
||||
</div>
|
||||
<div class="tile-value"><?= $nProducts ?></div>
|
||||
<div class="tile-label">Produits actifs</div>
|
||||
</article>
|
||||
|
||||
<article class="tile">
|
||||
<div class="tile-top">
|
||||
<span class="tile-ico"><svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg></span>
|
||||
<span class="tile-tag">Classees</span>
|
||||
</div>
|
||||
<div class="tile-value"><?= $nCategories ?></div>
|
||||
<div class="tile-label">Categories</div>
|
||||
</article>
|
||||
|
||||
<article class="tile">
|
||||
<div class="tile-top">
|
||||
<span class="tile-ico"><svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3h14a1 1 0 011 1v17l-8-4-8 4V4a1 1 0 011-1z"/></svg></span>
|
||||
<span class="tile-tag">Proposes</span>
|
||||
</div>
|
||||
<div class="tile-value"><?= $nMenus ?></div>
|
||||
<div class="tile-label">Menus</div>
|
||||
</article>
|
||||
|
||||
<article class="tile<?= $nCritical > 0 ? ' alert' : '' ?>">
|
||||
<div class="tile-top">
|
||||
<span class="tile-ico"><svg width="27" height="27" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l9 16H3l9-16z"/><path d="M12 9v5"/><path d="M12 17.5h.01"/></svg></span>
|
||||
<span class="tile-tag"><?= $nCritical > 0 ? 'A recommander' : 'OK' ?></span>
|
||||
</div>
|
||||
<div class="tile-value"><?= $nCritical ?></div>
|
||||
<div class="tile-label">Stock critique</div>
|
||||
</article>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use App\Auth\GuardResult;
|
|||
use App\Auth\SessionGuard;
|
||||
use App\Auth\SessionManager;
|
||||
use App\Auth\UserDirectory;
|
||||
use App\Catalogue\StatsRepository;
|
||||
use App\Controllers\DashboardController;
|
||||
use App\Core\Config;
|
||||
use App\Core\Database;
|
||||
|
|
@ -17,6 +18,32 @@ use App\Core\Request;
|
|||
use App\Core\Response;
|
||||
use App\Tests\Support\FakeDatabase;
|
||||
|
||||
/**
|
||||
* Stub de StatsRepository : KPIs canned, sans base (les agregats reels sont
|
||||
* couverts par StatsRepositoryDbTest).
|
||||
*/
|
||||
final class DashStubStatsRepository extends StatsRepository
|
||||
{
|
||||
public function counts(): array
|
||||
{
|
||||
return [
|
||||
'products' => ['total' => 53, 'available' => 50],
|
||||
'categories' => ['total' => 9, 'active' => 9],
|
||||
'menus' => ['total' => 13, 'available' => 12],
|
||||
'ingredients' => ['total' => 7, 'active' => 6],
|
||||
];
|
||||
}
|
||||
|
||||
public function stockHealth(): array
|
||||
{
|
||||
return [
|
||||
'active_total' => 6,
|
||||
'bands' => ['normal' => 4, 'low' => 1, 'critical' => 1],
|
||||
'alerts' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sous-classe de test : injecte session test + FakeDatabase dans la garde,
|
||||
* l'autorisation et l'annuaire, sans base reelle.
|
||||
|
|
@ -53,6 +80,11 @@ final class TestDashboardController extends DashboardController
|
|||
return new UserDirectory($this->fakeDb);
|
||||
}
|
||||
|
||||
protected function statsRepository(): StatsRepository
|
||||
{
|
||||
return new DashStubStatsRepository($this->fakeDb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose le chemin garde par permission d'AdminController::guard() (RG-T03),
|
||||
* que le dashboard (auth seule) n'exerce pas.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue