Compare commits

...

4 Commits

Author SHA1 Message Date
gitea-actions
191e071957 chore : bump version to v1.9.25
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 35s
2026-04-06 16:54:32 +00:00
f964df76b9 feat(custom-fields) : messages warning champs obligatoires + commandes make frontend
All checks were successful
Auto Tag Develop / tag (push) Successful in 10s
Ajoute des messages visuels (warning + error) quand des champs perso
obligatoires ne sont pas renseignés sur les pages composant (création
et édition). Ajoute make test-front et make test-front-watch au Makefile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:54:22 +02:00
gitea-actions
6744542f84 chore : bump version to v1.9.24
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
Build & Push Docker Image / build (push) Successful in 37s
2026-04-06 15:23:07 +00:00
3e0e9d5270 feat(categories) : aligner design catégories sur catalogues
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- Ajoute colonne createdAt triable dans la datatable des catégories
- Retire le bouton « Créer » de la vue catégorie (ManagementView)
- Retire l'action « Convertir » de toutes les catégories
- Le bouton « Ajouter » des pages catalogue switch selon l'onglet
  actif : crée un item (catalogue) ou une catégorie (catégories)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:22:57 +02:00
8 changed files with 38 additions and 64 deletions

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '1.9.23' app.version: '1.9.25'

View File

@@ -57,16 +57,6 @@
/> />
</label> </label>
<button
v-if="canEdit"
type="button"
class="btn btn-primary btn-sm"
:disabled="loading"
@click="openCreatePage"
>
<IconLucidePlus class="w-4 h-4" aria-hidden="true" />
Créer
</button>
</template> </template>
<template #cell-name="{ row }"> <template #cell-name="{ row }">
@@ -78,19 +68,15 @@
<span v-else class="text-base-content/50"></span> <span v-else class="text-base-content/50"></span>
</template> </template>
<template #cell-createdAt="{ row }">
<span class="whitespace-nowrap">{{ formatDate(row.createdAt) }}</span>
</template>
<template #cell-actions="{ row }"> <template #cell-actions="{ row }">
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button type="button" class="btn btn-ghost btn-xs" @click="openRelatedModal(row)"> <button type="button" class="btn btn-ghost btn-xs" @click="openRelatedModal(row)">
Liés Liés
</button> </button>
<button
v-if="canEdit && showConvertButton"
type="button"
class="btn btn-ghost btn-xs text-warning"
@click="openConversionModal(row)"
>
Convertir
</button>
<button type="button" class="btn btn-ghost btn-xs" @click="openEditPage(row)"> <button type="button" class="btn btn-ghost btn-xs" @click="openEditPage(row)">
Éditer Éditer
</button> </button>
@@ -101,13 +87,6 @@
</template> </template>
</DataTable> </DataTable>
<ConversionModal
:open="conversionModalOpen"
:model-type="conversionTarget"
@close="closeConversionModal"
@converted="onConverted"
/>
<RelatedItemsModal <RelatedItemsModal
:open="relatedModalOpen" :open="relatedModalOpen"
:model-type="relatedType" :model-type="relatedType"
@@ -121,7 +100,6 @@
import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue'
import { useHead, useRouter } from '#imports' import { useHead, useRouter } from '#imports'
import DataTable from '~/components/common/DataTable.vue' import DataTable from '~/components/common/DataTable.vue'
import ConversionModal from '~/components/model-types/ConversionModal.vue'
import { useUrlState } from '~/composables/useUrlState' import { useUrlState } from '~/composables/useUrlState'
import type { DataTableSort } from '~/shared/types/dataTable' import type { DataTableSort } from '~/shared/types/dataTable'
import { import {
@@ -135,7 +113,7 @@ import { useToast } from '~/composables/useToast'
import { humanizeError } from '~/shared/utils/errorMessages' import { humanizeError } from '~/shared/utils/errorMessages'
import { invalidateEntityTypeCache } from '~/composables/useEntityTypes' import { invalidateEntityTypeCache } from '~/composables/useEntityTypes'
import IconLucideSearch from '~icons/lucide/search' import IconLucideSearch from '~icons/lucide/search'
import IconLucidePlus from '~icons/lucide/plus' import { formatFrenchDate } from '~/utils/date'
const DEFAULT_DESCRIPTION const DEFAULT_DESCRIPTION
= 'Gérez les catégories utilisées pour structurer les catalogues de composants, de pièces et de produits. Ajoutez, modifiez ou supprimez des entrées avec tri, recherche et pagination.' = 'Gérez les catégories utilisées pour structurer les catalogues de composants, de pièces et de produits. Ajoutez, modifiez ou supprimez des entrées avec tri, recherche et pagination.'
@@ -199,12 +177,11 @@ useHead(() => ({ title: headingText.value }))
const columns = [ const columns = [
{ key: 'name', label: 'Nom', sortable: true }, { key: 'name', label: 'Nom', sortable: true },
{ key: 'notes', label: 'Notes' }, { key: 'notes', label: 'Notes' },
{ key: 'createdAt', label: 'Date', sortable: true },
{ key: 'actions', label: 'Actions', align: 'right' as const, width: 'w-48' }, { key: 'actions', label: 'Actions', align: 'right' as const, width: 'w-48' },
] ]
const showConvertButton = computed(() => const formatDate = formatFrenchDate
selectedCategory.value === 'PIECE' || selectedCategory.value === 'COMPONENT',
)
const categories: Array<{ label: string, value: ModelCategory }> = [ const categories: Array<{ label: string, value: ModelCategory }> = [
{ label: 'Composants', value: 'COMPONENT' }, { label: 'Composants', value: 'COMPONENT' },
@@ -339,13 +316,6 @@ const resolveCategoryBasePath = (category: ModelCategory) => {
return '/product-category' return '/product-category'
} }
const openCreatePage = () => {
const basePath = resolveCategoryBasePath(selectedCategory.value)
router.push(`${basePath}/new`).catch(() => {
showError('Navigation impossible vers la page de création.')
})
}
const openEditPage = (item: ModelType) => { const openEditPage = (item: ModelType) => {
const category = item.category ?? selectedCategory.value const category = item.category ?? selectedCategory.value
const basePath = resolveCategoryBasePath(category) const basePath = resolveCategoryBasePath(category)
@@ -400,26 +370,6 @@ const openRelatedEdit = (entry: { id: string }) => {
}) })
} }
const conversionModalOpen = ref(false)
const conversionTarget = ref<ModelType | null>(null)
const openConversionModal = (item: ModelType) => {
conversionTarget.value = item
conversionModalOpen.value = true
}
const closeConversionModal = () => {
conversionModalOpen.value = false
}
const onConverted = () => {
conversionModalOpen.value = false
invalidateEntityTypeCache('PIECE')
invalidateEntityTypeCache('COMPONENT')
showSuccess('Catégorie convertie avec succès.')
doRefresh()
}
watch( watch(
() => searchInput.value, () => searchInput.value,
(value) => { (value) => {

View File

@@ -5,8 +5,8 @@
<h1 class="text-3xl font-bold tracking-tight">Composants</h1> <h1 class="text-3xl font-bold tracking-tight">Composants</h1>
<p class="text-sm text-base-content/70">Catalogue et catégories de composants.</p> <p class="text-sm text-base-content/70">Catalogue et catégories de composants.</p>
</div> </div>
<NuxtLink v-if="canEdit" to="/component/create" class="btn btn-primary btn-sm md:btn-md"> <NuxtLink v-if="canEdit" :to="activeTab === 'categories' ? '/component-category/new' : '/component/create'" class="btn btn-primary btn-sm md:btn-md">
Ajouter un composant {{ activeTab === 'categories' ? 'Ajouter une catégorie' : 'Ajouter un composant' }}
</NuxtLink> </NuxtLink>
</div> </div>

View File

@@ -5,8 +5,8 @@
<h1 class="text-3xl font-bold tracking-tight">Pièces</h1> <h1 class="text-3xl font-bold tracking-tight">Pièces</h1>
<p class="text-sm text-base-content/70">Catalogue et catégories de pièces.</p> <p class="text-sm text-base-content/70">Catalogue et catégories de pièces.</p>
</div> </div>
<NuxtLink v-if="canEdit" to="/pieces/create" class="btn btn-primary btn-sm md:btn-md"> <NuxtLink v-if="canEdit" :to="activeTab === 'categories' ? '/piece-category/new' : '/pieces/create'" class="btn btn-primary btn-sm md:btn-md">
Ajouter une pièce {{ activeTab === 'categories' ? 'Ajouter une catégorie' : 'Ajouter une pièce' }}
</NuxtLink> </NuxtLink>
</div> </div>

View File

@@ -5,8 +5,8 @@
<h1 class="text-3xl font-bold tracking-tight">Produits</h1> <h1 class="text-3xl font-bold tracking-tight">Produits</h1>
<p class="text-sm text-base-content/70">Catalogue et catégories de produits.</p> <p class="text-sm text-base-content/70">Catalogue et catégories de produits.</p>
</div> </div>
<NuxtLink v-if="canEdit" to="/product/create" class="btn btn-primary btn-sm md:btn-md"> <NuxtLink v-if="canEdit" :to="activeTab === 'categories' ? '/product-category/new' : '/product/create'" class="btn btn-primary btn-sm md:btn-md">
Ajouter un produit {{ activeTab === 'categories' ? 'Ajouter une catégorie' : 'Ajouter un produit' }}
</NuxtLink> </NuxtLink>
</div> </div>

View File

@@ -408,6 +408,9 @@
</header> </header>
<template v-if="isEditMode"> <template v-if="isEditMode">
<CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || saving" /> <CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || saving" />
<p v-if="hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-warning">
Certains champs personnalisés sont obligatoires. Veuillez les renseigner avant de valider.
</p>
</template> </template>
<template v-else> <template v-else>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2"> <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
@@ -468,6 +471,9 @@
Enregistrer les modifications Enregistrer les modifications
</button> </button>
</div> </div>
<p v-if="isEditMode && hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
Merci de renseigner tous les champs personnalisés obligatoires.
</p>
</div> </div>
</section> </section>
</main> </main>
@@ -511,6 +517,7 @@ const {
constructeurLinks, constructeurLinks,
constructeurIdsFromForm, constructeurIdsFromForm,
customFieldInputs, customFieldInputs,
requiredCustomFieldsFilled,
historyFieldLabels, historyFieldLabels,
canSubmit, canSubmit,
componentTypeList, componentTypeList,
@@ -538,6 +545,8 @@ const {
formatStructurePreview, formatStructurePreview,
} = useComponentEdit(String(route.params.id)) } = useComponentEdit(String(route.params.id))
const hasRequiredCustomFields = computed(() => customFieldInputs.value.some(f => f.required))
const submitEdition = async () => { const submitEdition = async () => {
await _submitEdition() await _submitEdition()
if (!saving.value) { if (!saving.value) {

View File

@@ -223,6 +223,9 @@
</p> </p>
</header> </header>
<CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || submitting" /> <CustomFieldInputGrid :fields="customFieldInputs" :disabled="!canEdit || submitting" />
<p v-if="hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-warning">
Certains champs personnalisés sont obligatoires. Veuillez les renseigner avant de valider.
</p>
</div> </div>
<EmptyState <EmptyState
v-else v-else
@@ -242,6 +245,9 @@
Créer le composant Créer le composant
</button> </button>
</div> </div>
<p v-if="selectedType && hasRequiredCustomFields && !requiredCustomFieldsFilled" class="text-xs text-error text-right">
Merci de renseigner tous les champs personnalisés obligatoires avant de créer le composant.
</p>
</div> </div>
</section> </section>
</main> </main>
@@ -290,8 +296,11 @@ const {
resolveProductLabel, resolveProductLabel,
resolveSubcomponentLabel, resolveSubcomponentLabel,
submitCreation, submitCreation,
requiredCustomFieldsFilled,
} = useComponentCreate() } = useComponentCreate()
const hasRequiredCustomFields = computed(() => customFieldInputs.value.some(f => f.required))
const entityTabs = computed(() => [ const entityTabs = computed(() => [
{ key: 'general', label: 'Général' }, { key: 'general', label: 'Général' },
{ key: 'structure', label: 'Structure' }, { key: 'structure', label: 'Structure' },

View File

@@ -127,6 +127,12 @@ php-cs-fixer-allow-risky:
test: test:
$(EXEC_PHP) php -d memory_limit="512M" vendor/bin/phpunit $(FILES) $(EXEC_PHP) php -d memory_limit="512M" vendor/bin/phpunit $(FILES)
test-front:
cd frontend && npx vitest run $(FILES)
test-front-watch:
cd frontend && npx vitest --watch $(FILES)
test-setup: test-setup:
$(SYMFONY_CONSOLE) doctrine:database:create --if-not-exists --env=test $(SYMFONY_CONSOLE) doctrine:database:create --if-not-exists --env=test
$(SYMFONY_CONSOLE) doctrine:schema:update --force --env=test $(SYMFONY_CONSOLE) doctrine:schema:update --force --env=test