refactor(catalog) : extract page logic into useCategoriesAdmin and useCategoryForm composables
Extrait la logique fetch/CRUD inline de la page categories (ERP-49) vers deux composables dedies, conformement au pattern Starseed : - useCategoriesAdmin : singleton state (categories + types + loading + error). Pre-chargement des types au mount de la page (au lieu du fetch par ouverture du drawer). Reset au logout via onAuthSessionCleared + appel explicite dans logout.vue. - useCategoryForm : state local par form (pas singleton). Valide cote client en miroir des RG back (RG-1.02 / RG-1.04 / RG-1.05), mappe les erreurs 409 (doublon RG-1.07) et 422 (violations API Platform) sur les bons champs. submitCreate / submitUpdate / submitDelete renvoient la ressource ou null pour decoupler la decision de fermeture du drawer. La page et le drawer deviennent purement presentationnels. Aucune regression UX : meme validations, memes toasts, meme pattern view -> edit du drawer (via isDirty expose par useCategoryForm).
This commit is contained in:
@@ -18,7 +18,6 @@
|
||||
(name ASC, RG-1.10). La barre de pagination du MalioDataTable
|
||||
reste cosmetique tant qu'aucun slice client n'est cable : a
|
||||
traiter cote @malio/layer-ui le jour ou la volumetrie monte. -->
|
||||
|
||||
<MalioDataTable
|
||||
:columns="columns"
|
||||
:items="categoryItems"
|
||||
@@ -48,18 +47,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Category } from '~/modules/catalog/types/category'
|
||||
import type { HydraCollection } from '~/shared/utils/api'
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
const { can } = usePermissions()
|
||||
const { categories, fetchAll, fetchTypes } = useCategoriesAdmin()
|
||||
const { submitDelete } = useCategoryForm()
|
||||
|
||||
useHead({ title: t('admin.categories.title') })
|
||||
|
||||
const canManage = computed(() => can('catalog.categories.manage'))
|
||||
|
||||
const categories = ref<Category[]>([])
|
||||
const loading = ref(false)
|
||||
const drawerOpen = ref(false)
|
||||
const selectedCategory = ref<Category | null>(null)
|
||||
const deleteModalOpen = ref(false)
|
||||
@@ -90,35 +87,6 @@ function onRowClick(item: Record<string, unknown>) {
|
||||
if (category) openEditDrawer(category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge la liste des categories. Le serveur exclut les soft-deleted par
|
||||
* defaut (RG-1.08) et trie par name ASC (RG-1.10). Pas de pagination
|
||||
* serveur (volumetrie cible <= 300) ni de slice client — toute la liste
|
||||
* est rendue d'un coup ; la barre du MalioDataTable est donc cosmetique
|
||||
* jusqu'a la mise a jour layer-ui (ticket ERP-70).
|
||||
*
|
||||
* Logique inline volontaire au M0 (decision prompt ERP-49) : extraction
|
||||
* en composable `useCategoriesAdmin` au ticket 0.8 (ERP-50).
|
||||
*/
|
||||
async function loadCategories(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<HydraCollection<Category>>(
|
||||
'/categories',
|
||||
{ itemsPerPage: 999 },
|
||||
{ toast: false },
|
||||
)
|
||||
categories.value = data.member ?? []
|
||||
} catch {
|
||||
// Reset sur echec pour ne pas afficher de donnees stale. Pas de
|
||||
// toast : un user sans permission view recoit 403 et voit une
|
||||
// liste vide propre — le mecanisme de gating se fait cote sidebar.
|
||||
categories.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateDrawer() {
|
||||
selectedCategory.value = null
|
||||
drawerOpen.value = true
|
||||
@@ -136,32 +104,36 @@ function onDeleteRequest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/categories/{id} → soft delete (RG-1.12). Le serveur pose
|
||||
* `deleted_at = now()` et retourne 204. Refresh de la liste a la fin
|
||||
* pour retirer la ligne (l'index unique partiel autorise une recreation
|
||||
* ulterieure avec le meme couple (name, type) — RG-1.07).
|
||||
* Soft delete via le composable de form (qui gere toast + erreur). Refresh
|
||||
* de la liste a la fin pour retirer la ligne. L'index unique partiel
|
||||
* autorise une recreation ulterieure avec le meme couple (name, type) —
|
||||
* RG-1.07.
|
||||
*/
|
||||
async function handleDelete(): Promise<void> {
|
||||
if (!categoryToDelete.value) return
|
||||
deleting.value = true
|
||||
try {
|
||||
await api.delete(`/categories/${categoryToDelete.value.id}`, {}, {
|
||||
toastSuccessMessage: t('admin.categories.toast.deleted'),
|
||||
})
|
||||
deleteModalOpen.value = false
|
||||
categoryToDelete.value = null
|
||||
drawerOpen.value = false
|
||||
await loadCategories()
|
||||
const ok = await submitDelete(categoryToDelete.value.id)
|
||||
if (ok) {
|
||||
deleteModalOpen.value = false
|
||||
categoryToDelete.value = null
|
||||
drawerOpen.value = false
|
||||
await fetchAll()
|
||||
}
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onCategorySaved() {
|
||||
loadCategories()
|
||||
fetchAll()
|
||||
}
|
||||
|
||||
// Chargement initial des deux ressources (liste + referentiel des types).
|
||||
// Le referentiel est pre-charge ici (et pas dans le drawer) pour que le
|
||||
// select soit pret au moment ou l'utilisateur clique sur « + Ajouter ».
|
||||
onMounted(() => {
|
||||
loadCategories()
|
||||
fetchAll()
|
||||
fetchTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user