pcm-shared-components 2.1.387 → 2.1.389
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.
|
@@ -8,6 +8,7 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
|
|
8
8
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
9
9
|
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
|
10
10
|
import * as i18nextLocal from 'i18next';
|
|
11
|
+
import { classifyAnimalFieldValue } from '../animalFieldDrift';
|
|
11
12
|
|
|
12
13
|
// Every visible string this component owns lives under these two pre-existing i18next namespaces
|
|
13
14
|
// (already shared across both PitchCamp apps via pcm-shared-components' translations.json). Keyed
|
|
@@ -84,6 +85,7 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
84
85
|
onRemoveItem,
|
|
85
86
|
canAddMore,
|
|
86
87
|
maxItems,
|
|
88
|
+
showHistoricalAnswers,
|
|
87
89
|
className,
|
|
88
90
|
theme,
|
|
89
91
|
i18next
|
|
@@ -114,6 +116,28 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
114
116
|
setPendingRemoveId(null);
|
|
115
117
|
};
|
|
116
118
|
|
|
119
|
+
// Historical answers have no live field config left (see the Historical answers block below),
|
|
120
|
+
// so there's no field.type to route through a type-specific "clear" -- but classifyAnimalFieldValue
|
|
121
|
+
// treats an empty string as 'unanswered' regardless of what type the value used to be (a retired
|
|
122
|
+
// boolean/array/number all read back through the exact same generic-stringify branch above), so
|
|
123
|
+
// routing every deletion through the one existing onFieldChange callback with value '' is enough
|
|
124
|
+
// to make the entry disappear next render. No new prop needed on top of the ones this component
|
|
125
|
+
// already takes. Holds an array of keys so one dialog/confirm serves both a single delete and
|
|
126
|
+
// "Clear" (all of them at once) -- the array just has one entry for a single delete.
|
|
127
|
+
const [pendingHistoricalDelete, setPendingHistoricalDelete] = useState(null);
|
|
128
|
+
const requestDeleteHistorical = (itemId, deleteKeys) => setPendingHistoricalDelete({
|
|
129
|
+
itemId,
|
|
130
|
+
keys: deleteKeys
|
|
131
|
+
});
|
|
132
|
+
const confirmDeleteHistorical = () => {
|
|
133
|
+
const {
|
|
134
|
+
itemId,
|
|
135
|
+
keys: deleteKeys
|
|
136
|
+
} = pendingHistoricalDelete;
|
|
137
|
+
deleteKeys.forEach(k => onFieldChange(itemId, k, ''));
|
|
138
|
+
setPendingHistoricalDelete(null);
|
|
139
|
+
};
|
|
140
|
+
|
|
117
141
|
// A drifted field (its stored value no longer matches this field's current type/options -- see
|
|
118
142
|
// isFieldDrifted) renders read-only instead of feeding a mismatched value into a live
|
|
119
143
|
// type-specific control (e.g. never shove a raw string into a date picker). "Replace" reveals
|
|
@@ -279,8 +303,12 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
279
303
|
// replaceable. Not drifted (or already revealed): behaves exactly as before.
|
|
280
304
|
const value = showAsDrifted ? rawValue : wasDrifted ? field.type === 'select_multiple' ? [] : '' : rawValue;
|
|
281
305
|
const showError = field.required && !isFieldAnswered(field.type, value);
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
// No "(Optional)" suffix -- required fields already say so via the asterisk, so
|
|
307
|
+
// spelling out the alternative on every other field was redundant noise, and the
|
|
308
|
+
// extra length was long enough to wrap onto a second line in a narrow column,
|
|
309
|
+
// throwing that field's input out of vertical alignment with its paired neighbor.
|
|
310
|
+
const labelText = field.required ? `${field.label} *` : field.label;
|
|
311
|
+
const wrapperClassName = ['select', 'textarea', 'select_multiple'].includes(field.type) ? 'col-span-full' : undefined;
|
|
284
312
|
let control;
|
|
285
313
|
if (showAsDrifted) {
|
|
286
314
|
// Generic stringify -- this renders whatever type the field used to be, which
|
|
@@ -424,7 +452,8 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
424
452
|
if (val !== null) onFieldChange(selectedItem.id, field.key, val === 'yes');
|
|
425
453
|
},
|
|
426
454
|
fullWidth: true,
|
|
427
|
-
size: "small"
|
|
455
|
+
size: "small",
|
|
456
|
+
color: showError ? 'error' : 'standard'
|
|
428
457
|
}, /*#__PURE__*/React.createElement(ToggleButton, {
|
|
429
458
|
value: "yes"
|
|
430
459
|
}, i18next.t('common.app.yes')), /*#__PURE__*/React.createElement(ToggleButton, {
|
|
@@ -483,7 +512,104 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
483
512
|
color: "error",
|
|
484
513
|
className: "block mt-4"
|
|
485
514
|
}, requiredErrorText(field.type)));
|
|
486
|
-
})))), (() => {
|
|
515
|
+
})))), showHistoricalAnswers && selectedItem && (() => {
|
|
516
|
+
const liveKeys = new Set(fieldConfig.map(f => f.key));
|
|
517
|
+
const historicalEntries = Object.keys(selectedItem.values || {}).filter(k => !liveKeys.has(k)).map(k => ({
|
|
518
|
+
key: k,
|
|
519
|
+
value: selectedItem.values[k]
|
|
520
|
+
})).filter(entry => classifyAnimalFieldValue(null, entry.value) !== 'unanswered');
|
|
521
|
+
if (historicalEntries.length === 0) return null;
|
|
522
|
+
return /*#__PURE__*/React.createElement(Paper, {
|
|
523
|
+
variant: "outlined",
|
|
524
|
+
sx: {
|
|
525
|
+
mt: 1,
|
|
526
|
+
p: 1.5,
|
|
527
|
+
borderRadius: 2,
|
|
528
|
+
bgcolor: 'action.hover'
|
|
529
|
+
}
|
|
530
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
531
|
+
className: "flex items-center justify-between",
|
|
532
|
+
style: {
|
|
533
|
+
marginBottom: 4
|
|
534
|
+
}
|
|
535
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
536
|
+
className: "flex items-center",
|
|
537
|
+
style: {
|
|
538
|
+
gap: 6
|
|
539
|
+
}
|
|
540
|
+
}, /*#__PURE__*/React.createElement(WarningAmberIcon, {
|
|
541
|
+
fontSize: "small",
|
|
542
|
+
color: "warning"
|
|
543
|
+
}), /*#__PURE__*/React.createElement(Typography, {
|
|
544
|
+
sx: {
|
|
545
|
+
fontWeight: 700
|
|
546
|
+
}
|
|
547
|
+
}, i18next.t('animal.historical_answers_title'))), /*#__PURE__*/React.createElement(Button, {
|
|
548
|
+
size: "small",
|
|
549
|
+
color: "error",
|
|
550
|
+
onClick: () => requestDeleteHistorical(selectedItem.id, historicalEntries.map(e => e.key)),
|
|
551
|
+
sx: {
|
|
552
|
+
textTransform: 'none',
|
|
553
|
+
minWidth: 0
|
|
554
|
+
}
|
|
555
|
+
}, i18next.t('common.app.clear'))), /*#__PURE__*/React.createElement(Typography, {
|
|
556
|
+
variant: "caption",
|
|
557
|
+
color: "text.secondary",
|
|
558
|
+
sx: {
|
|
559
|
+
display: 'block',
|
|
560
|
+
mb: 1.5
|
|
561
|
+
}
|
|
562
|
+
}, i18next.t('animal.historical_answers_hint')), /*#__PURE__*/React.createElement("div", {
|
|
563
|
+
className: "grid grid-cols-2",
|
|
564
|
+
style: {
|
|
565
|
+
columnGap: 12,
|
|
566
|
+
rowGap: 12
|
|
567
|
+
}
|
|
568
|
+
}, historicalEntries.map(({
|
|
569
|
+
key: k,
|
|
570
|
+
value: v
|
|
571
|
+
}) => {
|
|
572
|
+
const displayText = typeof v === 'boolean' ? v ? i18next.t('common.app.yes') : i18next.t('common.app.no') : Array.isArray(v) ? v.join(', ') : String(v);
|
|
573
|
+
// No live config left to carry a real label -- same title-cased-slug fallback the
|
|
574
|
+
// read-only review screens use for a retired key.
|
|
575
|
+
const label = k.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
576
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
577
|
+
key: k,
|
|
578
|
+
className: "flex items-start justify-between",
|
|
579
|
+
style: {
|
|
580
|
+
gap: 4
|
|
581
|
+
}
|
|
582
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
583
|
+
className: "flex flex-col",
|
|
584
|
+
style: {
|
|
585
|
+
minWidth: 0
|
|
586
|
+
}
|
|
587
|
+
}, /*#__PURE__*/React.createElement(Typography, {
|
|
588
|
+
variant: "body2",
|
|
589
|
+
sx: {
|
|
590
|
+
fontWeight: 500,
|
|
591
|
+
color: 'text.secondary'
|
|
592
|
+
}
|
|
593
|
+
}, label), /*#__PURE__*/React.createElement(Typography, {
|
|
594
|
+
variant: "body2",
|
|
595
|
+
sx: {
|
|
596
|
+
color: 'text.primary',
|
|
597
|
+
wordBreak: 'break-word'
|
|
598
|
+
}
|
|
599
|
+
}, displayText)), /*#__PURE__*/React.createElement(IconButton, {
|
|
600
|
+
size: "small",
|
|
601
|
+
color: "error",
|
|
602
|
+
"aria-label": i18next.t('common.app.delete'),
|
|
603
|
+
onClick: () => requestDeleteHistorical(selectedItem.id, [k]),
|
|
604
|
+
sx: {
|
|
605
|
+
flexShrink: 0,
|
|
606
|
+
mt: -0.5
|
|
607
|
+
}
|
|
608
|
+
}, /*#__PURE__*/React.createElement(DeleteOutlineIcon, {
|
|
609
|
+
fontSize: "small"
|
|
610
|
+
})));
|
|
611
|
+
})));
|
|
612
|
+
})(), (() => {
|
|
487
613
|
const incompleteCount = items.filter(item => missingRequiredFields(item).length > 0).length;
|
|
488
614
|
if (incompleteCount === 0) return null;
|
|
489
615
|
return /*#__PURE__*/React.createElement(Alert, {
|
|
@@ -524,7 +650,18 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
524
650
|
color: "error",
|
|
525
651
|
variant: "contained",
|
|
526
652
|
onClick: confirmRemove
|
|
527
|
-
}, i18next.t('common.app.remove'))))
|
|
653
|
+
}, i18next.t('common.app.remove')))), /*#__PURE__*/React.createElement(Dialog, {
|
|
654
|
+
open: pendingHistoricalDelete !== null,
|
|
655
|
+
onClose: () => setPendingHistoricalDelete(null),
|
|
656
|
+
maxWidth: "xs",
|
|
657
|
+
fullWidth: true
|
|
658
|
+
}, /*#__PURE__*/React.createElement(DialogTitle, null, i18next.t('common.app.delete')), /*#__PURE__*/React.createElement(DialogContent, null, /*#__PURE__*/React.createElement(DialogContentText, null, i18next.t('common.app.generic_delete_message'))), /*#__PURE__*/React.createElement(DialogActions, null, /*#__PURE__*/React.createElement(Button, {
|
|
659
|
+
onClick: () => setPendingHistoricalDelete(null)
|
|
660
|
+
}, i18next.t('common.app.cancel')), /*#__PURE__*/React.createElement(Button, {
|
|
661
|
+
color: "error",
|
|
662
|
+
variant: "contained",
|
|
663
|
+
onClick: confirmDeleteHistorical
|
|
664
|
+
}, i18next.t('common.app.delete'))))));
|
|
528
665
|
};
|
|
529
666
|
AnimalVehicleDetailsEditor.defaultProps = {
|
|
530
667
|
items: [],
|
|
@@ -535,6 +672,7 @@ AnimalVehicleDetailsEditor.defaultProps = {
|
|
|
535
672
|
isFieldDrifted: () => false,
|
|
536
673
|
canAddMore: true,
|
|
537
674
|
maxItems: 0,
|
|
675
|
+
showHistoricalAnswers: true,
|
|
538
676
|
className: 'w-full',
|
|
539
677
|
theme: undefined,
|
|
540
678
|
i18next: i18nextLocal
|
|
@@ -576,6 +714,11 @@ AnimalVehicleDetailsEditor.propTypes = {
|
|
|
576
714
|
canAddMore: PropTypes.bool,
|
|
577
715
|
/** Shown in the "max reached" message once canAddMore is false. */
|
|
578
716
|
maxItems: PropTypes.number,
|
|
717
|
+
/** Whether the read-only "Historical Answers" block (retired field keys still holding a value)
|
|
718
|
+
* can render at all. Defaults to true for the admin edit screen, where reviewing/clearing a
|
|
719
|
+
* since-retired answer on an EXISTING reservation is legitimate. A guest-facing booking widget
|
|
720
|
+
* creating a brand-new reservation has no history to show and should pass false. */
|
|
721
|
+
showHistoricalAnswers: PropTypes.bool,
|
|
579
722
|
/** Classes applied to the component's own wrapper div. */
|
|
580
723
|
className: PropTypes.string,
|
|
581
724
|
/** Theme object to use on this component, if none provided then the MUI5 theme provider theme is used. */
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"remove_confirm_message": "All information entered for this animal will be lost. This can't be undone.",
|
|
40
40
|
"field_drifted_hint": "This answer no longer matches this question's current setup.",
|
|
41
41
|
"field_drifted_replace": "Enter a new answer",
|
|
42
|
-
"field_removed": "This question no longer exists in this lot's setup."
|
|
42
|
+
"field_removed": "This question no longer exists in this lot's setup.",
|
|
43
|
+
"historical_answers_title": "Historical Answers",
|
|
44
|
+
"historical_answers_hint": "These questions are no longer part of this lot's setup and can't be edited."
|
|
43
45
|
},
|
|
44
46
|
"app": {
|
|
45
47
|
"common": {
|
|
@@ -2467,7 +2469,14 @@
|
|
|
2467
2469
|
"change_type_confirm_title": "Change field type?",
|
|
2468
2470
|
"change_type_confirm_text": "{{count}} reservation(s) already have an answer for \"{{label}}\". Changing its type may make that answer display incorrectly. Continue?",
|
|
2469
2471
|
"remove_option_confirm_title": "Remove option?",
|
|
2470
|
-
"remove_option_confirm_text": "{{count}} reservation(s) have an answer using an option you're removing from \"{{label}}\". Their answer will no longer match this field's choices. Continue?"
|
|
2472
|
+
"remove_option_confirm_text": "{{count}} reservation(s) have an answer using an option you're removing from \"{{label}}\". Their answer will no longer match this field's choices. Continue?",
|
|
2473
|
+
"visibility_hint": "Also controls whether this section appears in Online Booking → Online Checkout Options.",
|
|
2474
|
+
"hidden_from_guests_warning": "Guests won't see this until you turn it on.",
|
|
2475
|
+
"online_booking_title": "Animal details for online booking",
|
|
2476
|
+
"manage_fields": "Manage fields",
|
|
2477
|
+
"fields_configured_count_one": "{{count}} field configured",
|
|
2478
|
+
"fields_configured_count_other": "{{count}} fields configured",
|
|
2479
|
+
"fields_configured_count_plural": "{{count}} fields configured"
|
|
2471
2480
|
},
|
|
2472
2481
|
"edit_lot": "Edit Lot",
|
|
2473
2482
|
"lot_id_exist": "Lot number already exist",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"remove_confirm_message": "Toda la información ingresada para este animal se perderá. Esto no se puede deshacer.",
|
|
40
40
|
"field_drifted_hint": "Esta respuesta ya no coincide con la configuración actual de esta pregunta.",
|
|
41
41
|
"field_drifted_replace": "Ingrese una nueva respuesta",
|
|
42
|
-
"field_removed": "Esta pregunta ya no existe en la configuración de este lote."
|
|
42
|
+
"field_removed": "Esta pregunta ya no existe en la configuración de este lote.",
|
|
43
|
+
"historical_answers_title": "Respuestas Históricas",
|
|
44
|
+
"historical_answers_hint": "Estas preguntas ya no forman parte de la configuración de este lote y no se pueden editar."
|
|
43
45
|
},
|
|
44
46
|
"app": {
|
|
45
47
|
"common": {
|
|
@@ -2467,7 +2469,14 @@
|
|
|
2467
2469
|
"change_type_confirm_title": "¿Cambiar el tipo de campo?",
|
|
2468
2470
|
"change_type_confirm_text": "{{count}} reserva(s) ya tienen una respuesta para \"{{label}}\". Cambiar su tipo puede hacer que esa respuesta se muestre incorrectamente. ¿Continuar?",
|
|
2469
2471
|
"remove_option_confirm_title": "¿Eliminar la opción?",
|
|
2470
|
-
"remove_option_confirm_text": "{{count}} reserva(s) tienen una respuesta usando una opción que está eliminando de \"{{label}}\". Su respuesta ya no coincidirá con las opciones de este campo. ¿Continuar?"
|
|
2472
|
+
"remove_option_confirm_text": "{{count}} reserva(s) tienen una respuesta usando una opción que está eliminando de \"{{label}}\". Su respuesta ya no coincidirá con las opciones de este campo. ¿Continuar?",
|
|
2473
|
+
"visibility_hint": "También controla si esta sección aparece en Reserva en línea → Opciones de pago en línea.",
|
|
2474
|
+
"hidden_from_guests_warning": "Los huéspedes no verán esto hasta que lo actives.",
|
|
2475
|
+
"online_booking_title": "Detalles de los animales para la reserva en línea",
|
|
2476
|
+
"manage_fields": "Administrar campos",
|
|
2477
|
+
"fields_configured_count_one": "{{count}} campo configurado",
|
|
2478
|
+
"fields_configured_count_other": "{{count}} campos configurados",
|
|
2479
|
+
"fields_configured_count_plural": "{{count}} campos configurados"
|
|
2471
2480
|
},
|
|
2472
2481
|
"edit_lot": "Editar lote",
|
|
2473
2482
|
"lot_id_exist": "El número de lote ya existe",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"remove_confirm_message": "Toutes les informations saisies pour cet animal seront perdues. Cela ne peut pas être annulé.",
|
|
40
40
|
"field_drifted_hint": "Cette réponse ne correspond plus à la configuration actuelle de cette question.",
|
|
41
41
|
"field_drifted_replace": "Entrez une nouvelle réponse",
|
|
42
|
-
"field_removed": "Cette question n'existe plus dans la configuration de ce terrain."
|
|
42
|
+
"field_removed": "Cette question n'existe plus dans la configuration de ce terrain.",
|
|
43
|
+
"historical_answers_title": "Réponses Historiques",
|
|
44
|
+
"historical_answers_hint": "Ces questions ne font plus partie de la configuration de ce terrain et ne peuvent pas être modifiées."
|
|
43
45
|
},
|
|
44
46
|
"app": {
|
|
45
47
|
"common": {
|
|
@@ -2467,7 +2469,14 @@
|
|
|
2467
2469
|
"change_type_confirm_title": "Changer le type de champ ?",
|
|
2468
2470
|
"change_type_confirm_text": "{{count}} réservation(s) ont déjà une réponse pour « {{label}} ». Modifier son type peut faire afficher incorrectement cette réponse. Continuer ?",
|
|
2469
2471
|
"remove_option_confirm_title": "Supprimer l'option ?",
|
|
2470
|
-
"remove_option_confirm_text": "{{count}} réservation(s) ont une réponse utilisant une option que vous supprimez de « {{label}} ». Leur réponse ne correspondra plus aux choix de ce champ. Continuer ?"
|
|
2472
|
+
"remove_option_confirm_text": "{{count}} réservation(s) ont une réponse utilisant une option que vous supprimez de « {{label}} ». Leur réponse ne correspondra plus aux choix de ce champ. Continuer ?",
|
|
2473
|
+
"visibility_hint": "Contrôle également l'affichage de cette section dans Réservation en ligne → Options de paiement en ligne.",
|
|
2474
|
+
"hidden_from_guests_warning": "Les clients ne verront cela que lorsque vous l'activerez.",
|
|
2475
|
+
"online_booking_title": "Détails sur les animaux pour la réservation en ligne",
|
|
2476
|
+
"manage_fields": "Gérer les champs",
|
|
2477
|
+
"fields_configured_count_one": "{{count}} champ configuré",
|
|
2478
|
+
"fields_configured_count_other": "{{count}} champs configurés",
|
|
2479
|
+
"fields_configured_count_plural": "{{count}} champs configurés"
|
|
2471
2480
|
},
|
|
2472
2481
|
"edit_lot": "Editer le lot",
|
|
2473
2482
|
"lot_id_exist": "Le numéro de lot existe déjà",
|