- Colonnes Bâtiment et Case ajoutées sur inventory (inline buildingCase via readableLink) - Bouton Rafraîchir repositionné dans l'en-tête du tableau (pattern case.vue) - Sync : date du jour pour l'appel EDNOTIF, extraction de la dernière exit date - UiDateMaskedInput : nouveau composant date masqué JJ/MM/AAAA - Propagation du masque date sur tous les datatables (reception, shipment, case, inventory) - Label de colonne "Date et heure" raccourci en "Date" - Champ exitDate ajouté en back (caché côté front, prêt pour future feature) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109 lines
2.9 KiB
Vue
109 lines
2.9 KiB
Vue
<template>
|
|
<div :class="['flex flex-col', wrapperClass]">
|
|
<label
|
|
v-if="label"
|
|
:for="id"
|
|
class="font-bold uppercase text-xl text-primary-700"
|
|
:class="labelClass"
|
|
>
|
|
{{ label }}
|
|
</label>
|
|
<input
|
|
:id="id"
|
|
v-maska="'##/##/####'"
|
|
type="text"
|
|
inputmode="numeric"
|
|
:value="displayValue"
|
|
:placeholder="placeholder"
|
|
:disabled="disabled"
|
|
v-bind="attrs"
|
|
class="w-full min-w-0 border-b border-primary-700 bg-transparent"
|
|
:class="[
|
|
sizeClass,
|
|
isEmpty ? 'text-neutral-400' : 'text-primary-700',
|
|
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
|
inputClass
|
|
]"
|
|
@input="onInput"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { vMaska } from 'maska/vue'
|
|
import { computed, ref, useAttrs, watch } from 'vue'
|
|
|
|
defineOptions({ inheritAttrs: false })
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
id?: string
|
|
label?: string
|
|
modelValue: string | null | undefined
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
size?: 'default' | 'compact'
|
|
wrapperClass?: string
|
|
labelClass?: string
|
|
inputClass?: string
|
|
}>(),
|
|
{
|
|
placeholder: 'JJ/MM/AAAA',
|
|
disabled: false,
|
|
size: 'default',
|
|
wrapperClass: '',
|
|
labelClass: '',
|
|
inputClass: ''
|
|
}
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'update:modelValue', value: string): void
|
|
}>()
|
|
|
|
const attrs = useAttrs()
|
|
|
|
const toDisplay = (iso: string | null | undefined): string => {
|
|
if (!iso) return ''
|
|
const parts = iso.split('-')
|
|
if (parts.length !== 3) return ''
|
|
const [year, month, day] = parts
|
|
if (year.length !== 4 || month.length !== 2 || day.length !== 2) return ''
|
|
return `${day}/${month}/${year}`
|
|
}
|
|
|
|
const toIso = (display: string): string | null => {
|
|
const match = display.match(/^(\d{2})\/(\d{2})\/(\d{4})$/)
|
|
if (!match) return null
|
|
const [, day, month, year] = match
|
|
return `${year}-${month}-${day}`
|
|
}
|
|
|
|
const displayValue = ref(toDisplay(props.modelValue))
|
|
|
|
watch(() => props.modelValue, (newIso) => {
|
|
const expected = toDisplay(newIso)
|
|
if (expected !== displayValue.value) {
|
|
displayValue.value = expected
|
|
}
|
|
})
|
|
|
|
const isEmpty = computed(() => !displayValue.value)
|
|
const sizeClass = computed(() =>
|
|
props.size === 'compact'
|
|
? 'text-sm h-8 font-normal normal-case tracking-normal'
|
|
: 'text-xl py-[6px]'
|
|
)
|
|
|
|
const onInput = (event: Event) => {
|
|
const target = event.target as HTMLInputElement
|
|
displayValue.value = target.value
|
|
if (target.value === '') {
|
|
emit('update:modelValue', '')
|
|
return
|
|
}
|
|
const iso = toIso(target.value)
|
|
emit('update:modelValue', iso ?? '')
|
|
}
|
|
</script>
|