@asteby/metacore-runtime-react 18.5.0 → 18.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3b16664: `DynamicRecordDialog` now refetches its own parent record after a child relation
|
|
8
|
+
row (line item, etc.) is created/updated/deleted, so server-recomputed
|
|
9
|
+
declarative rollups (sub_total, tax_amount, total) appear in place without a
|
|
10
|
+
manual page reload. Also exposes an optional `onChange` callback so hosts can
|
|
11
|
+
invalidate their own list/detail query underneath the dialog.
|
|
12
|
+
|
|
13
|
+
## 18.6.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- ed63683: Record dialog date fields use the real shadcn Calendar (react-day-picker) from
|
|
18
|
+
`@asteby/metacore-ui` instead of the dependency-free native `<input type="date">`
|
|
19
|
+
shim, and match datetime/timestamp(tz) types too. Empty/Go-zero dates
|
|
20
|
+
(0001-01-01) now show the "Seleccionar fecha" placeholder instead of
|
|
21
|
+
"31 de diciembre de 1".
|
|
22
|
+
|
|
3
23
|
## 18.5.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -137,9 +137,17 @@ export interface DynamicRecordDialogProps {
|
|
|
137
137
|
* explicit per-field currency. Optional — defaults to 'USD'.
|
|
138
138
|
*/
|
|
139
139
|
currency?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Fired after a child relation row (line item, etc.) is created/updated/
|
|
142
|
+
* deleted from within the dialog. The dialog ALREADY refetches its own
|
|
143
|
+
* parent record so server-recomputed rollups (sub_total, tax_amount, total)
|
|
144
|
+
* appear in place — this callback additionally lets the host invalidate its
|
|
145
|
+
* own list/detail query so the parent row's totals refresh underneath.
|
|
146
|
+
*/
|
|
147
|
+
onChange?: () => void;
|
|
140
148
|
}
|
|
141
149
|
export declare function isMoneyField(field: FieldDef, value: any): boolean;
|
|
142
|
-
export declare function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl, timeZone, currency, }: DynamicRecordDialogProps): import("react").JSX.Element;
|
|
150
|
+
export declare function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl, timeZone, currency, onChange, }: DynamicRecordDialogProps): import("react").JSX.Element;
|
|
143
151
|
export declare function ViewValue({ field, value: rawValue, record, getImageUrl: getImageUrlProp, timeZone: timeZoneProp, currency: currencyProp, }: {
|
|
144
152
|
field: FieldDef;
|
|
145
153
|
value: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
|
|
@@ -10,11 +10,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
// flows through <ApiProvider> from runtime-react. Host-specific runtime values —
|
|
11
11
|
// the image-url resolver and the org IANA timezone — are passed as props so the
|
|
12
12
|
// SDK stays transport- and host-agnostic.
|
|
13
|
-
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Input, Textarea, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Skeleton, Badge, Popover, PopoverContent, PopoverTrigger, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@asteby/metacore-ui/primitives';
|
|
15
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Input, Textarea, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Skeleton, Badge, Popover, PopoverContent, PopoverTrigger, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Calendar, } from '@asteby/metacore-ui/primitives';
|
|
16
16
|
import { cn } from '@asteby/metacore-ui/lib';
|
|
17
|
-
import { Calendar } from './_primitives';
|
|
18
17
|
import { toast } from 'sonner';
|
|
19
18
|
import { format, parseISO } from 'date-fns';
|
|
20
19
|
import { es } from 'date-fns/locale';
|
|
@@ -179,7 +178,7 @@ export function isMoneyField(field, value) {
|
|
|
179
178
|
return false;
|
|
180
179
|
return MONEY_KEY_HEURISTIC.some(m => key === m || key.endsWith(`_${m}`) || key.startsWith(`${m}_`));
|
|
181
180
|
}
|
|
182
|
-
export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, currency, }) {
|
|
181
|
+
export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, currency, onChange, }) {
|
|
183
182
|
const api = useApi();
|
|
184
183
|
const { t } = useTranslation();
|
|
185
184
|
const [modalMeta, setModalMeta] = useState(schema ? schema : null);
|
|
@@ -324,6 +323,38 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
324
323
|
});
|
|
325
324
|
return () => { cancelled = true; };
|
|
326
325
|
}, [open, mode, model, recordId, api, t]);
|
|
326
|
+
// After a child relation mutation (add/edit/delete line item) the server
|
|
327
|
+
// recomputes the parent's declarative rollups (sub_total, tax_amount, total).
|
|
328
|
+
// Refetch the parent record so those fresh totals render in place — view mode
|
|
329
|
+
// reads `record`; edit mode also reseeds the form so derived fields update.
|
|
330
|
+
// Then bubble onChange so the host can refresh its own list/detail query.
|
|
331
|
+
const handleChildChange = useCallback(async () => {
|
|
332
|
+
if (!isCreate && recordId) {
|
|
333
|
+
try {
|
|
334
|
+
const recordEndpoint = endpoint
|
|
335
|
+
? `${endpoint}/${recordId}`
|
|
336
|
+
: `/dynamic/${model}/${recordId}`;
|
|
337
|
+
const recRes = await api.get(recordEndpoint);
|
|
338
|
+
const rec = recRes.data?.data ?? recRes.data;
|
|
339
|
+
if (rec) {
|
|
340
|
+
setRecord((prev) => (prev ? { ...prev, ...rec } : rec));
|
|
341
|
+
setFormValues(prev => {
|
|
342
|
+
const next = { ...prev };
|
|
343
|
+
for (const field of modalMeta?.fields ?? []) {
|
|
344
|
+
const v = resolvePath(rec, field.key);
|
|
345
|
+
if (v !== undefined)
|
|
346
|
+
next[field.key] = v;
|
|
347
|
+
}
|
|
348
|
+
return next;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
console.error('[DynamicRecordDialog] parent refetch error:', err);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
onChange?.();
|
|
357
|
+
}, [api, endpoint, model, recordId, isCreate, modalMeta, onChange]);
|
|
327
358
|
const handleSubmit = async (e) => {
|
|
328
359
|
e?.preventDefault();
|
|
329
360
|
if (!modalMeta)
|
|
@@ -411,7 +442,7 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
411
442
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsx(TimeZoneContext.Provider, { value: timeZone, children: _jsxs(CurrencyContext.Provider, { value: currency, children: [_jsxs("form", { id: "dynamic-record-form", onSubmit: handleSubmit, className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: [visibleFields.map(field => {
|
|
412
443
|
const isFullWidth = field.type === 'textarea';
|
|
413
444
|
return (_jsx("div", { className: isFullWidth ? 'sm:col-span-2' : '', children: _jsx(FieldRow, { field: field, record: record, value: formValues[field.key] ?? '', mode: mode, onChange: val => setFormValues((prev) => ({ ...prev, [field.key]: val })) }) }, field.key));
|
|
414
|
-
}), record?.external_url && (_jsx("div", { className: "sm:col-span-2", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit' }) }))] }) }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel }), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && (_jsxs(Button, { type: "submit", form: "dynamic-record-form", disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }));
|
|
445
|
+
}), record?.external_url && (_jsx("div", { className: "sm:col-span-2", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit', onChange: handleChildChange }) }))] }) }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel }), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && (_jsxs(Button, { type: "submit", form: "dynamic-record-form", disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }));
|
|
415
446
|
}
|
|
416
447
|
function LoadingSkeleton() {
|
|
417
448
|
return (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-9 w-full" })] }, i))) }));
|
|
@@ -580,9 +611,13 @@ function EditField({ field, value, onChange }) {
|
|
|
580
611
|
if (field.type === 'color') {
|
|
581
612
|
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "color", value: value || '#6366f1', onChange: (e) => onChange(e.target.value), className: "h-9 w-14 cursor-pointer rounded-md border p-1" }), _jsx(Input, { value: value || '', onChange: (e) => onChange(e.target.value), placeholder: "#6366f1", className: "flex-1 h-9" })] }));
|
|
582
613
|
}
|
|
583
|
-
if (field.type === 'date') {
|
|
614
|
+
if (field.type === 'date' || field.type === 'datetime' || field.type === 'timestamp' || field.type === 'timestamptz') {
|
|
584
615
|
const dateValue = value ? (typeof value === 'string' ? parseISO(value) : new Date(value)) : undefined;
|
|
585
|
-
|
|
616
|
+
// Treat the Go zero-time (0001-01-01) as empty so an unset date shows the
|
|
617
|
+
// placeholder instead of "31 de diciembre de 1".
|
|
618
|
+
const validDate = dateValue && !isNaN(dateValue.getTime()) && dateValue.getFullYear() > 1
|
|
619
|
+
? dateValue
|
|
620
|
+
: undefined;
|
|
586
621
|
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", className: cn("w-full justify-start text-left font-normal h-9", !validDate && "text-muted-foreground"), children: [_jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }), validDate
|
|
587
622
|
? format(validDate, 'PPP', { locale: es })
|
|
588
623
|
: "Seleccionar fecha"] }) }), _jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: _jsx(Calendar, { mode: "single", selected: validDate, onSelect: (date) => onChange(date ? format(date, 'yyyy-MM-dd') : ''), locale: es }) })] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.7.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^6.0.0",
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
|
-
"@asteby/metacore-
|
|
65
|
-
"@asteby/metacore-
|
|
64
|
+
"@asteby/metacore-ui": "2.5.0",
|
|
65
|
+
"@asteby/metacore-sdk": "3.2.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// flows through <ApiProvider> from runtime-react. Host-specific runtime values —
|
|
10
10
|
// the image-url resolver and the org IANA timezone — are passed as props so the
|
|
11
11
|
// SDK stays transport- and host-agnostic.
|
|
12
|
-
import { createContext, useContext, useEffect, useRef, useState } from 'react'
|
|
12
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
|
13
13
|
import { useTranslation } from 'react-i18next'
|
|
14
14
|
import type { ModelSchema } from './types'
|
|
15
15
|
import {
|
|
@@ -40,9 +40,9 @@ import {
|
|
|
40
40
|
CommandInput,
|
|
41
41
|
CommandItem,
|
|
42
42
|
CommandList,
|
|
43
|
+
Calendar,
|
|
43
44
|
} from '@asteby/metacore-ui/primitives'
|
|
44
45
|
import { cn } from '@asteby/metacore-ui/lib'
|
|
45
|
-
import { Calendar } from './_primitives'
|
|
46
46
|
import { toast } from 'sonner'
|
|
47
47
|
import { format, parseISO } from 'date-fns'
|
|
48
48
|
import { es } from 'date-fns/locale'
|
|
@@ -228,6 +228,14 @@ export interface DynamicRecordDialogProps {
|
|
|
228
228
|
* explicit per-field currency. Optional — defaults to 'USD'.
|
|
229
229
|
*/
|
|
230
230
|
currency?: string
|
|
231
|
+
/**
|
|
232
|
+
* Fired after a child relation row (line item, etc.) is created/updated/
|
|
233
|
+
* deleted from within the dialog. The dialog ALREADY refetches its own
|
|
234
|
+
* parent record so server-recomputed rollups (sub_total, tax_amount, total)
|
|
235
|
+
* appear in place — this callback additionally lets the host invalidate its
|
|
236
|
+
* own list/detail query so the parent row's totals refresh underneath.
|
|
237
|
+
*/
|
|
238
|
+
onChange?: () => void
|
|
231
239
|
}
|
|
232
240
|
|
|
233
241
|
function resolvePath(obj: any, path: string): any {
|
|
@@ -392,6 +400,7 @@ export function DynamicRecordDialog({
|
|
|
392
400
|
getImageUrl = identityImageUrl,
|
|
393
401
|
timeZone,
|
|
394
402
|
currency,
|
|
403
|
+
onChange,
|
|
395
404
|
}: DynamicRecordDialogProps) {
|
|
396
405
|
const api = useApi()
|
|
397
406
|
const { t } = useTranslation()
|
|
@@ -544,6 +553,37 @@ export function DynamicRecordDialog({
|
|
|
544
553
|
return () => { cancelled = true }
|
|
545
554
|
}, [open, mode, model, recordId, api, t])
|
|
546
555
|
|
|
556
|
+
// After a child relation mutation (add/edit/delete line item) the server
|
|
557
|
+
// recomputes the parent's declarative rollups (sub_total, tax_amount, total).
|
|
558
|
+
// Refetch the parent record so those fresh totals render in place — view mode
|
|
559
|
+
// reads `record`; edit mode also reseeds the form so derived fields update.
|
|
560
|
+
// Then bubble onChange so the host can refresh its own list/detail query.
|
|
561
|
+
const handleChildChange = useCallback(async () => {
|
|
562
|
+
if (!isCreate && recordId) {
|
|
563
|
+
try {
|
|
564
|
+
const recordEndpoint = endpoint
|
|
565
|
+
? `${endpoint}/${recordId}`
|
|
566
|
+
: `/dynamic/${model}/${recordId}`
|
|
567
|
+
const recRes = await api.get(recordEndpoint)
|
|
568
|
+
const rec = recRes.data?.data ?? recRes.data
|
|
569
|
+
if (rec) {
|
|
570
|
+
setRecord((prev: any) => (prev ? { ...prev, ...rec } : rec))
|
|
571
|
+
setFormValues(prev => {
|
|
572
|
+
const next = { ...prev }
|
|
573
|
+
for (const field of modalMeta?.fields ?? []) {
|
|
574
|
+
const v = resolvePath(rec, field.key)
|
|
575
|
+
if (v !== undefined) next[field.key] = v
|
|
576
|
+
}
|
|
577
|
+
return next
|
|
578
|
+
})
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error('[DynamicRecordDialog] parent refetch error:', err)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
onChange?.()
|
|
585
|
+
}, [api, endpoint, model, recordId, isCreate, modalMeta, onChange])
|
|
586
|
+
|
|
547
587
|
const handleSubmit = async (e?: React.FormEvent) => {
|
|
548
588
|
e?.preventDefault()
|
|
549
589
|
if (!modalMeta) return
|
|
@@ -695,6 +735,7 @@ export function DynamicRecordDialog({
|
|
|
695
735
|
canCreate={mode === 'edit'}
|
|
696
736
|
canEdit={mode === 'edit'}
|
|
697
737
|
canDelete={mode === 'edit'}
|
|
738
|
+
onChange={handleChildChange}
|
|
698
739
|
/>
|
|
699
740
|
</div>
|
|
700
741
|
)}
|
|
@@ -1108,9 +1149,14 @@ function EditField({ field, value, onChange }: {
|
|
|
1108
1149
|
)
|
|
1109
1150
|
}
|
|
1110
1151
|
|
|
1111
|
-
if (field.type === 'date') {
|
|
1152
|
+
if (field.type === 'date' || field.type === 'datetime' || field.type === 'timestamp' || field.type === 'timestamptz') {
|
|
1112
1153
|
const dateValue = value ? (typeof value === 'string' ? parseISO(value) : new Date(value)) : undefined
|
|
1113
|
-
|
|
1154
|
+
// Treat the Go zero-time (0001-01-01) as empty so an unset date shows the
|
|
1155
|
+
// placeholder instead of "31 de diciembre de 1".
|
|
1156
|
+
const validDate =
|
|
1157
|
+
dateValue && !isNaN(dateValue.getTime()) && dateValue.getFullYear() > 1
|
|
1158
|
+
? dateValue
|
|
1159
|
+
: undefined
|
|
1114
1160
|
|
|
1115
1161
|
return (
|
|
1116
1162
|
<Popover>
|