feat(browse): icône crayon inline sur les tuiles + apiCount récursif

- Bouton modifier réduit à l'icône crayon, placé à côté du titre de la tuile
- apiCount dans CategoryWithCounts inclut désormais les APIs de tous les
  sous-dossiers récursivement (countApisRecursively)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
z3n
2026-06-09 13:01:46 +00:00
parent e54c21042f
commit ecb45e8248
2 changed files with 20 additions and 13 deletions

View File

@@ -81,6 +81,13 @@ export class CategoriesService {
await this.categoryRepo.delete(id);
}
private async countApisRecursively(categoryId: string): Promise<number> {
const direct = await this.apiRepo.count({ where: { categoryId } });
const children = await this.categoryRepo.find({ where: { parentId: categoryId }, select: ['id'] });
const childCounts = await Promise.all(children.map((c) => this.countApisRecursively(c.id)));
return direct + childCounts.reduce((sum, n) => sum + n, 0);
}
async browse(categoryId: string | null): Promise<CategoryBrowseResponse> {
/* 1. Charge la catégorie courante */
let currentCategory: CategoryEntity | null = null;
@@ -111,7 +118,7 @@ export class CategoriesService {
const subcategories: CategoryWithCounts[] = await Promise.all(
directChildren.map(async (child) => {
const subcategoryCount = await this.categoryRepo.count({ where: { parentId: child.id } });
const apiCount = await this.apiRepo.count({ where: { categoryId: child.id } });
const apiCount = await this.countApisRecursively(child.id);
return { ...toCategory(child), subcategoryCount, apiCount };
}),
);

View File

@@ -86,6 +86,18 @@ import { forkJoin } from 'rxjs';
<div class="fr-tile__content">
<h3 class="fr-tile__title">
<a [routerLink]="['/browse', cat.id]">{{ cat.name }}</a>
<button title="Modifier"
style="position:relative;z-index:1;background:none;border:none;cursor:pointer;padding:0 0.25rem;vertical-align:middle;opacity:0.6"
(click)="openEdit(cat); $event.stopPropagation()">
<span class="fr-icon-edit-line" aria-hidden="true" style="font-size:0.75rem"></span>
</button>
@if (cat.apiCount === 0 && cat.subcategoryCount === 0) {
<button title="Supprimer"
style="position:relative;z-index:1;background:none;border:none;cursor:pointer;padding:0 0.25rem;vertical-align:middle;opacity:0.6"
(click)="deleteCategory(cat); $event.stopPropagation()">
<span class="fr-icon-delete-bin-line" aria-hidden="true" style="font-size:0.75rem"></span>
</button>
}
</h3>
@if (cat.description) {
<p class="fr-tile__desc">{{ cat.description }}</p>
@@ -93,18 +105,6 @@ import { forkJoin } from 'rxjs';
<p class="fr-tile__detail">
{{ cat.subcategoryCount }} sous-catégorie(s) · {{ cat.apiCount }} API(s)
</p>
<div style="position:relative;z-index:1;margin-top:0.5rem;display:flex;gap:0.25rem;order:5">
<button class="fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-edit-line"
(click)="openEdit(cat); $event.stopPropagation()">
Modifier
</button>
@if (cat.apiCount === 0 && cat.subcategoryCount === 0) {
<button class="fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-delete-bin-line"
(click)="deleteCategory(cat); $event.stopPropagation()">
Supprimer
</button>
}
</div>
</div>
</div>
</div>