Merge pull request 'fix(multimoneda): formato fijo DD/MM/YYYY en campo de fecha' (#476) from fix/catalogos-multimoneda into development

Reviewed-on: ADUANASOFT/anexo76#476
This commit is contained in:
2026-06-02 17:17:29 +00:00
2 changed files with 131 additions and 3 deletions

View File

@@ -16,6 +16,7 @@
import type { CurrencyType } from '$lib/api/dashboard/reference_data/currency_types';
import type { Country } from '$lib/api/dashboard/reference_data/countries';
import { toast } from 'svelte-sonner';
import DateInputDMY from '$lib/components/ui/date-input-dmy.svelte';
let {
open = $bindable(false),
@@ -205,14 +206,12 @@
{m.multi_currency_date_label()} <span class="text-destructive">*</span>
</Label>
<div class="col-span-3">
<Input
<DateInputDMY
id="pub_date"
type="date"
bind:value={formData.date_str}
disabled={loading}
required
/>
<p class="mt-1 text-[10px] text-muted-foreground">{m.multi_currency_date_help()}</p>
</div>
</div>

View File

@@ -0,0 +1,129 @@
<script lang="ts">
import { Calendar } from 'lucide-svelte';
import { cn } from '$lib/utils.js';
let {
value = $bindable(''),
id = '',
disabled = false,
required = false,
class: className = ''
}: {
value: string;
id?: string;
disabled?: boolean;
required?: boolean;
class?: string;
} = $props();
// YYYY-MM-DD → DD/MM/YYYY
function toDisplay(iso: string): string {
if (!iso || iso.length !== 10) return '';
const [y, m, d] = iso.split('-');
return `${d}/${m}/${y}`;
}
// DD/MM/YYYY → YYYY-MM-DD
function toIso(dmy: string): string {
if (!dmy || dmy.length !== 10) return '';
const [d, m, y] = dmy.split('/');
if (!d || !m || !y || y.length !== 4) return '';
return `${y}-${m}-${d}`;
}
let display = $state(toDisplay(value));
let lastSetByInput = value;
let hiddenRef = $state<HTMLInputElement | null>(null);
$effect(() => {
if (value !== lastSetByInput) {
display = toDisplay(value);
}
});
function handleInput(e: Event) {
const input = e.currentTarget as HTMLInputElement;
const digits = input.value.replace(/\D/g, '');
let formatted = digits;
if (digits.length > 4) {
formatted = digits.slice(0, 2) + '/' + digits.slice(2, 4) + '/' + digits.slice(4, 8);
} else if (digits.length > 2) {
formatted = digits.slice(0, 2) + '/' + digits.slice(2);
}
display = formatted;
input.value = formatted;
const iso = formatted.length === 10 ? toIso(formatted) : '';
lastSetByInput = iso;
value = iso;
}
function handleKeydown(e: KeyboardEvent) {
const allowed = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End'];
if (allowed.includes(e.key)) return;
if (!/^\d$/.test(e.key)) e.preventDefault();
}
// Cuando el usuario elige una fecha desde el picker nativo, sincroniza al texto
function handleHiddenChange(e: Event) {
const iso = (e.currentTarget as HTMLInputElement).value;
lastSetByInput = iso;
value = iso;
display = toDisplay(iso);
}
function openPicker() {
if (!hiddenRef || disabled) return;
// Sincroniza el valor actual antes de abrir
hiddenRef.value = value;
if ('showPicker' in hiddenRef && typeof hiddenRef.showPicker === 'function') {
hiddenRef.showPicker();
} else {
hiddenRef.click();
}
}
</script>
<div class="relative w-full">
<input
{id}
type="text"
inputmode="numeric"
placeholder="DD/MM/YYYY"
value={display}
oninput={handleInput}
onkeydown={handleKeydown}
{disabled}
{required}
maxlength={10}
autocomplete="off"
class={cn(
'border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 pr-11 text-base text-foreground outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className
)}
/>
<!-- Input nativo oculto — solo sirve para abrir el calendar picker del navegador -->
<input
bind:this={hiddenRef}
type="date"
tabindex="-1"
aria-hidden="true"
onchange={handleHiddenChange}
class="pointer-events-none absolute inset-0 h-full w-full opacity-0"
/>
<button
type="button"
aria-label="Abrir selector de fecha"
onclick={openPicker}
{disabled}
class="absolute top-1/2 right-1 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50"
>
<Calendar aria-hidden="true" class="h-4 w-4" />
</button>
</div>