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:
@@ -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>
|
||||
|
||||
|
||||
129
frontend/src/lib/components/ui/date-input-dmy.svelte
Normal file
129
frontend/src/lib/components/ui/date-input-dmy.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user