refactor: uniform validation, fix create form UX, dedupe filter dropdowns
- Remove dead registerValidator (Auth/register.ts), unused since signupValidator is the one actually wired to NewAccountController - computers/create.vue: switch to useForm() + @submit.prevent so the form submits on Enter and shows a processing state, matching components/create.vue - components/edit.vue: drop the 'origin' field, which was never provided by the backend (edit() never shared it) and therefore always undefined - Extract FilterDropdown.vue and reuse it in ComponentsViewer/ComputersViewer, removing ~80 lines of duplicated dropdown markup/logic and fixing duplicate HTML ids (id="used" appeared twice in ComputersViewer) - Move MIGRATION_*.md artifacts into docs/migration-history/
This commit is contained in:
1 parent
3cc189c534
commit
3579c9dfec
10 files changed
+163
-262
No files matched your search
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import TableComponent from '~/pages/widgets/TableComponent.vue'
|
||||
import FilterDropdown from '~/pages/widgets/FilterDropdown.vue'
|
||||
import type Component from '#models/component'
|
||||
import type ComponentType from '#models/component_type'
|
||||
import { usePage } from '@inertiajs/vue3'
|
||||
@@ -8,19 +9,6 @@ import { usePage } from '@inertiajs/vue3'
|
||||
const { props } = usePage<{ components: Component[] }>()
|
||||
const { components } = props
|
||||
|
||||
const usageDropdownOpen = ref(false)
|
||||
const typeDropdownOpen = ref(false)
|
||||
|
||||
function toggleDropdown(dropdown: any) {
|
||||
if (dropdown === 'usage') {
|
||||
usageDropdownOpen.value = !usageDropdownOpen.value
|
||||
typeDropdownOpen.value = false
|
||||
} else if (dropdown === 'type') {
|
||||
typeDropdownOpen.value = !typeDropdownOpen.value
|
||||
usageDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const componentTypes = computed(() => {
|
||||
const types = new Map<number, ComponentType>()
|
||||
components.forEach((component) => {
|
||||
@@ -31,152 +19,51 @@ const componentTypes = computed(() => {
|
||||
return Array.from(types.values())
|
||||
})
|
||||
|
||||
// D├®finir le type de l'objet filters
|
||||
interface Filters {
|
||||
showUsed: boolean
|
||||
showUnused: boolean
|
||||
types: { [key: number]: boolean }
|
||||
}
|
||||
const typeOptions = computed(() =>
|
||||
componentTypes.value.map((type) => ({ id: type.id, label: type.name }))
|
||||
)
|
||||
|
||||
const filters = reactive<Filters>({
|
||||
showUsed: false,
|
||||
showUnused: true,
|
||||
types: componentTypes.value.reduce(
|
||||
const usageFilters = ref<Record<string, boolean>>({
|
||||
unused: true,
|
||||
used: false,
|
||||
})
|
||||
|
||||
const typeFilters = ref<Record<number, boolean>>(
|
||||
componentTypes.value.reduce(
|
||||
(acc, type) => {
|
||||
acc[type.id] = true
|
||||
return acc
|
||||
},
|
||||
{} as { [key: number]: boolean }
|
||||
),
|
||||
})
|
||||
{} as Record<number, boolean>
|
||||
)
|
||||
)
|
||||
|
||||
const filteredComponents = computed(() => {
|
||||
return components.filter((component) => {
|
||||
const isUsed = filters.showUsed && component.computerId !== null
|
||||
const isUnused = filters.showUnused && component.computerId === null
|
||||
const isTypeSelected = filters.types[component.type.id]
|
||||
const isUsed = usageFilters.value.used && component.computerId !== null
|
||||
const isUnused = usageFilters.value.unused && component.computerId === null
|
||||
const isTypeSelected = typeFilters.value[component.type.id]
|
||||
return (isUsed || isUnused) && isTypeSelected
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<!-- Dropdown pour les filtres -->
|
||||
<div class="relative">
|
||||
<h4 class="text-xl underline">Filters</h4>
|
||||
<div class="flex gap-2">
|
||||
<!-- Usage Dropdown -->
|
||||
<div id="usage-selector" class="relative" style="width: fit-content">
|
||||
<button
|
||||
@click="toggleDropdown('usage')"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition text-center inline-flex items-center"
|
||||
type="button"
|
||||
>
|
||||
Usage
|
||||
<svg
|
||||
class="w-4 h-4 ml-2"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<div
|
||||
v-show="usageDropdownOpen"
|
||||
class="absolute right-0 z-20 py-2 mt-2 bg-white rounded-md shadow-xl"
|
||||
>
|
||||
<ul class="space-y-2 text-sm px-4 py-2">
|
||||
<li class="flex items-center">
|
||||
<input
|
||||
id="unused"
|
||||
type="checkbox"
|
||||
v-model="filters.showUnused"
|
||||
class="w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
/>
|
||||
<label
|
||||
for="unused"
|
||||
class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
Unused
|
||||
</label>
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<input
|
||||
id="used"
|
||||
type="checkbox"
|
||||
v-model="filters.showUsed"
|
||||
class="w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
/>
|
||||
<label for="used" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
Used
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type Dropdown -->
|
||||
<div id="type-selector" class="relative" style="width: fit-content">
|
||||
<button
|
||||
@click="toggleDropdown('type')"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition text-center inline-flex items-center"
|
||||
type="button"
|
||||
>
|
||||
Types
|
||||
<svg
|
||||
class="w-4 h-4 ml-2"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<div
|
||||
v-show="typeDropdownOpen"
|
||||
class="absolute right-0 z-20 py-2 mt-2 bg-white rounded-md shadow-xl"
|
||||
>
|
||||
<ul class="space-y-2 text-sm px-4 py-2">
|
||||
<li class="flex items-center" v-for="type in componentTypes" :key="type.id">
|
||||
<input
|
||||
:id="`type-${type.id}`"
|
||||
type="checkbox"
|
||||
v-model="filters.types[type.id]"
|
||||
class="w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
/>
|
||||
<label
|
||||
:for="`type-${type.id}`"
|
||||
class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
{{ type.name }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<FilterDropdown
|
||||
v-model="usageFilters"
|
||||
label="Usage"
|
||||
:options="[
|
||||
{ id: 'unused', label: 'Unused' },
|
||||
{ id: 'used', label: 'Used' },
|
||||
]"
|
||||
/>
|
||||
<FilterDropdown v-model="typeFilters" label="Types" :options="typeOptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tableau des composants filtr├®s -->
|
||||
<TableComponent :components="filteredComponents" />
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user