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:
@@ -81,6 +81,13 @@ export class CategoriesService {
|
|||||||
await this.categoryRepo.delete(id);
|
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> {
|
async browse(categoryId: string | null): Promise<CategoryBrowseResponse> {
|
||||||
/* 1. Charge la catégorie courante */
|
/* 1. Charge la catégorie courante */
|
||||||
let currentCategory: CategoryEntity | null = null;
|
let currentCategory: CategoryEntity | null = null;
|
||||||
@@ -111,7 +118,7 @@ export class CategoriesService {
|
|||||||
const subcategories: CategoryWithCounts[] = await Promise.all(
|
const subcategories: CategoryWithCounts[] = await Promise.all(
|
||||||
directChildren.map(async (child) => {
|
directChildren.map(async (child) => {
|
||||||
const subcategoryCount = await this.categoryRepo.count({ where: { parentId: child.id } });
|
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 };
|
return { ...toCategory(child), subcategoryCount, apiCount };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -86,6 +86,18 @@ import { forkJoin } from 'rxjs';
|
|||||||
<div class="fr-tile__content">
|
<div class="fr-tile__content">
|
||||||
<h3 class="fr-tile__title">
|
<h3 class="fr-tile__title">
|
||||||
<a [routerLink]="['/browse', cat.id]">{{ cat.name }}</a>
|
<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>
|
</h3>
|
||||||
@if (cat.description) {
|
@if (cat.description) {
|
||||||
<p class="fr-tile__desc">{{ cat.description }}</p>
|
<p class="fr-tile__desc">{{ cat.description }}</p>
|
||||||
@@ -93,18 +105,6 @@ import { forkJoin } from 'rxjs';
|
|||||||
<p class="fr-tile__detail">
|
<p class="fr-tile__detail">
|
||||||
{{ cat.subcategoryCount }} sous-catégorie(s) · {{ cat.apiCount }} API(s)
|
{{ cat.subcategoryCount }} sous-catégorie(s) · {{ cat.apiCount }} API(s)
|
||||||
</p>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user