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
- const labelText = field.required ? `${field.label} *` : `${field.label} (${i18next.t('common.app.optional')})`;
283
- const wrapperClassName = ['select', 'textarea', 'select_multiple', 'yes_no'].includes(field.type) ? 'col-span-full' : undefined;
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à",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pcm-shared-components",
3
- "version": "2.1.387",
3
+ "version": "2.1.389",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "babel": {