pcm-shared-components 2.1.403 → 2.1.405
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/dist/components/Reservation/AnimalVehicleDetailsEditor/AnimalFieldsOnceBlock.js +262 -0
- package/dist/components/Reservation/AnimalVehicleDetailsEditor/fieldTypeControl.js +189 -0
- package/dist/components/Reservation/AnimalVehicleDetailsEditor/index.js +12 -156
- package/dist/components/Reservation/animalFieldDrift.js +10 -0
- package/dist/index.js +2 -1
- package/dist/languages/en/translations.json +7 -1
- package/dist/languages/es/translations.json +7 -1
- package/dist/languages/fr/translations.json +7 -1
- package/dist/styles/tailwind.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import React, { useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Typography, Paper, Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
|
|
4
|
+
import { ThemeProvider } from '@mui/system';
|
|
5
|
+
import { createTheme, useTheme } from '@mui/material/styles';
|
|
6
|
+
import * as i18nextLocal from 'i18next';
|
|
7
|
+
import { renderFieldTypeControl } from './fieldTypeControl';
|
|
8
|
+
|
|
9
|
+
// Collapsed height for a notice block before it needs a "View full ..." link. A handful of lines
|
|
10
|
+
// of admin-entered text/markup fits inside this without ever showing the link at all; anything
|
|
11
|
+
// longer gets clipped here and offered in full through the dialog below.
|
|
12
|
+
const NOTICE_COLLAPSED_MAX_HEIGHT = 160;
|
|
13
|
+
|
|
14
|
+
// Field-type controls this block ever renders inline (everything else -- 'notice' -- gets its own
|
|
15
|
+
// read-only display, not a live input control).
|
|
16
|
+
const INPUT_FIELD_TYPES = ['text', 'select', 'select_multiple', 'number', 'textarea', 'yes_no', 'date'];
|
|
17
|
+
|
|
18
|
+
// Full-width field types, same rule AnimalVehicleDetailsEditor's per-item grid uses for its
|
|
19
|
+
// non-orphan fields (a dropdown/textarea/multi-select reads badly squeezed into a half column).
|
|
20
|
+
// This block skips that component's further wrapped-label-promotion pass (labelRefs/wrappedKeys in
|
|
21
|
+
// index.jsx) -- once-per-reservation fields are typically a handful at most, so the extra
|
|
22
|
+
// measurement machinery needed to keep a large tab-per-animal grid's columns aligned isn't worth
|
|
23
|
+
// carrying here too.
|
|
24
|
+
const isFullWidthField = field => ['select', 'textarea', 'select_multiple'].includes(field.type);
|
|
25
|
+
|
|
26
|
+
// `once_per_reservation`/`content_html` are read from the field object itself first (the flat,
|
|
27
|
+
// "normalized" shape this block's `fields` prop expects -- the same convention
|
|
28
|
+
// AnimalVehicleDetailsEditor's `fieldConfig` already uses for label/options/unit/placeholder: the
|
|
29
|
+
// caller resolves the raw lot_animal_fields.meta payload into flat props before handing fields to
|
|
30
|
+
// either component). A `field.meta.<key>` fallback is also accepted so a caller who instead passes
|
|
31
|
+
// the meta object through unflattened still works.
|
|
32
|
+
const readFieldProp = (field, key) => field[key] !== undefined ? field[key] : field.meta?.[key];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One read-only "View full {label}" notice block: renders `field`'s sanitized content_html inside
|
|
36
|
+
* a bounded-height container, and only if that content actually overflows the bound does it show a
|
|
37
|
+
* link that opens the full content in a Dialog. Not exported -- internal to AnimalFieldsOnceBlock,
|
|
38
|
+
* which is the only place a `type: 'notice'` field ever gets rendered.
|
|
39
|
+
*/
|
|
40
|
+
const NoticeFieldBlock = ({
|
|
41
|
+
field,
|
|
42
|
+
i18next
|
|
43
|
+
}) => {
|
|
44
|
+
const contentRef = useRef(null);
|
|
45
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
46
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
47
|
+
// Already HTML-sanitized server-side before this ever reaches the client -- safe to render as-is.
|
|
48
|
+
const contentHtml = readFieldProp(field, 'content_html') || '';
|
|
49
|
+
|
|
50
|
+
// Measured after mount/paint (useLayoutEffect, not useEffect) so the "View full ..." link never
|
|
51
|
+
// flashes in for a heartbeat on content that turns out to fit -- same reasoning
|
|
52
|
+
// AnimalVehicleDetailsEditor's own label-wrap measurement (wrappedKeys, index.jsx) already uses.
|
|
53
|
+
// Re-checked on window resize too: the height bound is fixed in CSS, but at a narrower viewport
|
|
54
|
+
// the same HTML can wrap onto more lines and start overflowing where it didn't before.
|
|
55
|
+
useLayoutEffect(() => {
|
|
56
|
+
const checkOverflow = () => {
|
|
57
|
+
const el = contentRef.current;
|
|
58
|
+
if (!el) return;
|
|
59
|
+
setOverflowing(el.scrollHeight > el.clientHeight);
|
|
60
|
+
};
|
|
61
|
+
checkOverflow();
|
|
62
|
+
window.addEventListener('resize', checkOverflow);
|
|
63
|
+
return () => window.removeEventListener('resize', checkOverflow);
|
|
64
|
+
}, [contentHtml]);
|
|
65
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
66
|
+
className: "flex flex-col",
|
|
67
|
+
style: {
|
|
68
|
+
gap: 4
|
|
69
|
+
}
|
|
70
|
+
}, /*#__PURE__*/React.createElement(Typography, {
|
|
71
|
+
variant: "body2",
|
|
72
|
+
sx: {
|
|
73
|
+
fontWeight: 500
|
|
74
|
+
}
|
|
75
|
+
}, field.label), /*#__PURE__*/React.createElement(Paper, {
|
|
76
|
+
variant: "outlined",
|
|
77
|
+
sx: {
|
|
78
|
+
p: 1.5,
|
|
79
|
+
borderRadius: 1,
|
|
80
|
+
bgcolor: 'action.hover',
|
|
81
|
+
maxHeight: NOTICE_COLLAPSED_MAX_HEIGHT,
|
|
82
|
+
overflow: 'hidden'
|
|
83
|
+
}
|
|
84
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
85
|
+
ref: contentRef,
|
|
86
|
+
dangerouslySetInnerHTML: {
|
|
87
|
+
__html: contentHtml
|
|
88
|
+
}
|
|
89
|
+
})), overflowing && /*#__PURE__*/React.createElement(Button, {
|
|
90
|
+
size: "small",
|
|
91
|
+
onClick: () => setDialogOpen(true),
|
|
92
|
+
sx: {
|
|
93
|
+
alignSelf: 'flex-start',
|
|
94
|
+
textTransform: 'none',
|
|
95
|
+
minWidth: 0,
|
|
96
|
+
p: 0
|
|
97
|
+
}
|
|
98
|
+
}, i18next.t('animal.view_full_field', {
|
|
99
|
+
label: field.label
|
|
100
|
+
})), /*#__PURE__*/React.createElement(Dialog, {
|
|
101
|
+
open: dialogOpen,
|
|
102
|
+
onClose: () => setDialogOpen(false),
|
|
103
|
+
maxWidth: "sm",
|
|
104
|
+
fullWidth: true,
|
|
105
|
+
scroll: "paper"
|
|
106
|
+
}, /*#__PURE__*/React.createElement(DialogTitle, null, field.label), /*#__PURE__*/React.createElement(DialogContent, {
|
|
107
|
+
dividers: true
|
|
108
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
109
|
+
dangerouslySetInnerHTML: {
|
|
110
|
+
__html: contentHtml
|
|
111
|
+
}
|
|
112
|
+
})), /*#__PURE__*/React.createElement(DialogActions, null, /*#__PURE__*/React.createElement(Button, {
|
|
113
|
+
onClick: () => setDialogOpen(false)
|
|
114
|
+
}, i18next.t('common.app.close')))));
|
|
115
|
+
};
|
|
116
|
+
NoticeFieldBlock.propTypes = {
|
|
117
|
+
field: PropTypes.shape({
|
|
118
|
+
key: PropTypes.string.isRequired,
|
|
119
|
+
label: PropTypes.string.isRequired,
|
|
120
|
+
content_html: PropTypes.string,
|
|
121
|
+
meta: PropTypes.object
|
|
122
|
+
}).isRequired,
|
|
123
|
+
i18next: PropTypes.any.isRequired
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Flat, non-tabbed block for a lot's "once per reservation" animal fields: no tabs, no per-animal
|
|
128
|
+
* chrome, no add/remove -- these are single shared values/notices for the whole reservation, not
|
|
129
|
+
* one row per animal. Renders two kinds of entries, in whatever order `fields` gives them:
|
|
130
|
+
*
|
|
131
|
+
* - Any input-type field (text/select/number/textarea/yes_no/date/select_multiple) whose config
|
|
132
|
+
* opts into `once_per_reservation` -- reuses the exact same `renderFieldTypeControl` that
|
|
133
|
+
* AnimalVehicleDetailsEditor's tab-per-animal grid uses (see fieldTypeControl.jsx, this
|
|
134
|
+
* directory), so a once-per-reservation field looks and behaves identically to its per-animal
|
|
135
|
+
* counterpart.
|
|
136
|
+
* - Any `field_type === 'notice'` field -- a read-only sanitized-HTML display, never an input.
|
|
137
|
+
* `once_per_reservation` doesn't apply to notices (they're never per-animal to begin with) and
|
|
138
|
+
* is ignored for this type.
|
|
139
|
+
*
|
|
140
|
+
* `fields` does NOT need to be pre-filtered: this component filters internally (any field that is
|
|
141
|
+
* neither a notice nor once_per_reservation is silently skipped), so it's safe to pass the lot's
|
|
142
|
+
* whole field list straight through. Passing an already-filtered subset works identically, since
|
|
143
|
+
* the filter is idempotent.
|
|
144
|
+
*
|
|
145
|
+
* Two usage modes, both through this one component:
|
|
146
|
+
* - Editable (staff/guest edit flow): pass `onChange`. Inputs render live and call
|
|
147
|
+
* `onChange(field.key, newValue)`.
|
|
148
|
+
* - Read-only (review screen): omit `onChange` entirely. Every input control renders disabled
|
|
149
|
+
* (MUI's own disabled/read-only appearance) instead of interactive -- there is no second
|
|
150
|
+
* "read-only variant" component to import, this is the same export either way. The notice
|
|
151
|
+
* block's "View full ..." link/dialog behaves identically in both modes, since it was never an
|
|
152
|
+
* input.
|
|
153
|
+
*
|
|
154
|
+
* ## Importing the component in your project
|
|
155
|
+
*
|
|
156
|
+
```js
|
|
157
|
+
|
|
158
|
+
import {AnimalFieldsOnceBlock} from 'pcm-shared-components';
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
export const AnimalFieldsOnceBlock = props => {
|
|
164
|
+
const {
|
|
165
|
+
fields,
|
|
166
|
+
values,
|
|
167
|
+
onChange,
|
|
168
|
+
className,
|
|
169
|
+
theme,
|
|
170
|
+
i18next
|
|
171
|
+
} = props;
|
|
172
|
+
const compTheme = theme || useTheme();
|
|
173
|
+
const defaultTheme = createTheme(compTheme);
|
|
174
|
+
const relevantFields = fields.filter(field => field.type === 'notice' || INPUT_FIELD_TYPES.includes(field.type) && readFieldProp(field, 'once_per_reservation') === true);
|
|
175
|
+
if (relevantFields.length === 0) return null;
|
|
176
|
+
return /*#__PURE__*/React.createElement(ThemeProvider, {
|
|
177
|
+
theme: defaultTheme
|
|
178
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
179
|
+
className: className
|
|
180
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
181
|
+
className: "grid grid-cols-2",
|
|
182
|
+
style: {
|
|
183
|
+
columnGap: 12,
|
|
184
|
+
rowGap: 16,
|
|
185
|
+
gridAutoFlow: 'row dense'
|
|
186
|
+
}
|
|
187
|
+
}, relevantFields.map(field => {
|
|
188
|
+
if (field.type === 'notice') {
|
|
189
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
190
|
+
key: field.key,
|
|
191
|
+
className: "col-span-full"
|
|
192
|
+
}, /*#__PURE__*/React.createElement(NoticeFieldBlock, {
|
|
193
|
+
field: field,
|
|
194
|
+
i18next: i18next
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
const rawValue = values?.[field.key] ?? (field.type === 'select_multiple' ? [] : '');
|
|
198
|
+
const labelText = field.required ? `${field.label} *` : field.label;
|
|
199
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
200
|
+
key: field.key,
|
|
201
|
+
className: isFullWidthField(field) ? 'col-span-full' : undefined
|
|
202
|
+
}, /*#__PURE__*/React.createElement(Typography, {
|
|
203
|
+
variant: "body2",
|
|
204
|
+
sx: {
|
|
205
|
+
fontWeight: 500,
|
|
206
|
+
mb: 0.5
|
|
207
|
+
}
|
|
208
|
+
}, labelText), renderFieldTypeControl({
|
|
209
|
+
field,
|
|
210
|
+
value: rawValue,
|
|
211
|
+
// Read-only mode (no onChange prop): the control renders disabled, so this
|
|
212
|
+
// handler is unreachable -- still a no-op rather than undefined, in case a
|
|
213
|
+
// consumer's own event wiring calls it directly in a test.
|
|
214
|
+
onChange: onChange ? newValue => onChange(field.key, newValue) : () => {},
|
|
215
|
+
disabled: !onChange,
|
|
216
|
+
error: false,
|
|
217
|
+
helperText: '',
|
|
218
|
+
i18next
|
|
219
|
+
}));
|
|
220
|
+
}))));
|
|
221
|
+
};
|
|
222
|
+
AnimalFieldsOnceBlock.defaultProps = {
|
|
223
|
+
fields: [],
|
|
224
|
+
values: {},
|
|
225
|
+
onChange: undefined,
|
|
226
|
+
className: 'w-full',
|
|
227
|
+
theme: undefined,
|
|
228
|
+
i18next: i18nextLocal
|
|
229
|
+
};
|
|
230
|
+
AnimalFieldsOnceBlock.propTypes = {
|
|
231
|
+
/** The lot's field configs -- does not need to be pre-filtered, see the component doc above.
|
|
232
|
+
* Normalized to the same flat shape AnimalVehicleDetailsEditor's `fieldConfig` already uses
|
|
233
|
+
* (label/options/unit/placeholder resolved by the caller), plus this feature's two additions:
|
|
234
|
+
* `once_per_reservation` (input types) and `content_html` (type 'notice'). Both are also read
|
|
235
|
+
* from `field.meta.<key>` as a fallback if a caller passes the meta object through unflattened. */
|
|
236
|
+
fields: PropTypes.arrayOf(PropTypes.shape({
|
|
237
|
+
key: PropTypes.string.isRequired,
|
|
238
|
+
label: PropTypes.string.isRequired,
|
|
239
|
+
type: PropTypes.oneOf(['text', 'number', 'select', 'select_multiple', 'textarea', 'yes_no', 'date', 'notice']).isRequired,
|
|
240
|
+
required: PropTypes.bool,
|
|
241
|
+
options: PropTypes.arrayOf(PropTypes.string),
|
|
242
|
+
unit: PropTypes.string,
|
|
243
|
+
placeholder: PropTypes.string,
|
|
244
|
+
maxLength: PropTypes.number,
|
|
245
|
+
once_per_reservation: PropTypes.bool,
|
|
246
|
+
content_html: PropTypes.string,
|
|
247
|
+
meta: PropTypes.object
|
|
248
|
+
})),
|
|
249
|
+
/** One animal's `field_values`, keyed by field.key -- safe to read from any single animal row
|
|
250
|
+
* since once-per-reservation values are kept synced identically across every animal. */
|
|
251
|
+
values: PropTypes.object,
|
|
252
|
+
/** (fieldKey, value) => void. Omit entirely to render every input control disabled/read-only
|
|
253
|
+
* (review-screen usage) -- see the component doc above. */
|
|
254
|
+
onChange: PropTypes.func,
|
|
255
|
+
/** Classes applied to the component's own wrapper div. */
|
|
256
|
+
className: PropTypes.string,
|
|
257
|
+
/** Theme object to use on this component, if none provided then the MUI5 theme provider theme is used. */
|
|
258
|
+
theme: PropTypes.object,
|
|
259
|
+
/** The i18next instance to translate with -- pass your own if the default singleton import isn't the one your app initialized. */
|
|
260
|
+
i18next: PropTypes.any
|
|
261
|
+
};
|
|
262
|
+
export default AnimalFieldsOnceBlock;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { TextField, InputAdornment, MenuItem, ToggleButton, ToggleButtonGroup, Checkbox, ListItemText, Chip } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
// A native <input type="date"> only opens its calendar popup on its own when the browser's tiny
|
|
6
|
+
// built-in icon is clicked -- clicking anywhere else in the field just places the text caret
|
|
7
|
+
// (still lets someone type the year/month/day directly, which showPicker() doesn't change). Most
|
|
8
|
+
// people expect clicking anywhere in the box to open the calendar. showPicker() is Chrome/Edge/
|
|
9
|
+
// Firefox only as of this writing (not Safari) and throws if called on a disabled/read-only input
|
|
10
|
+
// or outside a user gesture, so this fails silently rather than breaking the click -- on an
|
|
11
|
+
// unsupported browser, the field just falls back to manual typing and the browser's own
|
|
12
|
+
// icon-click affordance, exactly as before.
|
|
13
|
+
export const openNativeDatePicker = e => {
|
|
14
|
+
try {
|
|
15
|
+
e.target.showPicker?.();
|
|
16
|
+
} catch {
|
|
17
|
+
// ignore -- unsupported browser or transient DOM state, typing/icon-click still work
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Renders the single form control for one {field, value, onChange} triple across every field type
|
|
23
|
+
* this feature knows about: text / number / select / select_multiple / textarea / yes_no / date
|
|
24
|
+
* (unknown/legacy types fall back to plain text, same as before). Pulled out of the per-item field
|
|
25
|
+
* grid in `index.jsx` (AnimalVehicleDetailsEditor's tab-per-animal editor) so
|
|
26
|
+
* `AnimalFieldsOnceBlock`'s flat once-per-reservation layout, in the same directory, can render the
|
|
27
|
+
* exact same controls without a second copy of this switch. Both call sites feed it the same shape
|
|
28
|
+
* and get back one MUI element (`<TextField>` / `<ToggleButtonGroup>`).
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT responsible for:
|
|
31
|
+
* - The "drifted value" read-only display (a stored value that no longer matches the field's
|
|
32
|
+
* current type/options) -- that's a meta-concern about which control to show at all, decided by
|
|
33
|
+
* the caller before it ever gets here, not part of "how do I render a control of this type".
|
|
34
|
+
* - The required-field error caption under a `yes_no` control -- both call sites render that
|
|
35
|
+
* themselves right after this control, since its spacing differs slightly between the tab grid
|
|
36
|
+
* and the flat once-block layout.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} params
|
|
39
|
+
* @param {object} params.field - `{ key, type, label, options?, unit?, placeholder?, maxLength? }`.
|
|
40
|
+
* Only `key`/`type` and the type-specific extras are read here; `label`/`required` are the
|
|
41
|
+
* caller's concern (rendered outside this function).
|
|
42
|
+
* @param {*} params.value - Current value, already shaped for `field.type` (string for
|
|
43
|
+
* text/select/number/date, array for select_multiple, boolean|null for yes_no).
|
|
44
|
+
* @param {(value: *) => void} params.onChange - Called with the new value in that same shape.
|
|
45
|
+
* Callers needing an (itemId, key, value) or (key, value) signature wrap it, e.g.
|
|
46
|
+
* `(v) => onFieldChange(item.id, field.key, v)`.
|
|
47
|
+
* @param {boolean} [params.disabled] - Renders the control disabled (read-only review usage).
|
|
48
|
+
* @param {boolean} [params.error] - Error/invalid styling.
|
|
49
|
+
* @param {string} [params.helperText] - Helper/error text under the control. Caller decides
|
|
50
|
+
* whether that's a required-field message or blank -- this function doesn't compute it.
|
|
51
|
+
* @param {object} params.i18next - i18next instance, for this function's own small vocabulary
|
|
52
|
+
* (the select placeholder, Yes/No toggle labels).
|
|
53
|
+
* @returns {JSX.Element}
|
|
54
|
+
*/
|
|
55
|
+
export const renderFieldTypeControl = ({
|
|
56
|
+
field,
|
|
57
|
+
value,
|
|
58
|
+
onChange,
|
|
59
|
+
disabled,
|
|
60
|
+
error,
|
|
61
|
+
helperText,
|
|
62
|
+
i18next
|
|
63
|
+
}) => {
|
|
64
|
+
const commonProps = {
|
|
65
|
+
name: field.key,
|
|
66
|
+
variant: 'outlined',
|
|
67
|
+
size: 'small',
|
|
68
|
+
error,
|
|
69
|
+
helperText,
|
|
70
|
+
fullWidth: true,
|
|
71
|
+
disabled
|
|
72
|
+
};
|
|
73
|
+
switch (field.type) {
|
|
74
|
+
case 'number':
|
|
75
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({}, commonProps, {
|
|
76
|
+
value: value,
|
|
77
|
+
onChange: e => onChange(e.target.value),
|
|
78
|
+
onKeyDown: e => {
|
|
79
|
+
if (e.key === 'e' || e.key === 'E') e.preventDefault();
|
|
80
|
+
},
|
|
81
|
+
inputProps: {
|
|
82
|
+
maxLength: field.maxLength || 45
|
|
83
|
+
},
|
|
84
|
+
type: "number",
|
|
85
|
+
placeholder: field.placeholder,
|
|
86
|
+
InputProps: {
|
|
87
|
+
endAdornment: field.unit ? /*#__PURE__*/React.createElement(InputAdornment, {
|
|
88
|
+
position: "end"
|
|
89
|
+
}, field.unit) : undefined
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
case 'select':
|
|
93
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({
|
|
94
|
+
select: true
|
|
95
|
+
}, commonProps, {
|
|
96
|
+
value: value,
|
|
97
|
+
onChange: e => onChange(e.target.value)
|
|
98
|
+
}), /*#__PURE__*/React.createElement(MenuItem, {
|
|
99
|
+
value: ""
|
|
100
|
+
}, /*#__PURE__*/React.createElement("em", null, field.placeholder || i18next.t('common.app.select'))), (field.options || []).map(opt => /*#__PURE__*/React.createElement(MenuItem, {
|
|
101
|
+
key: opt,
|
|
102
|
+
value: opt
|
|
103
|
+
}, opt)));
|
|
104
|
+
case 'textarea':
|
|
105
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({}, commonProps, {
|
|
106
|
+
value: value,
|
|
107
|
+
onChange: e => onChange(e.target.value),
|
|
108
|
+
inputProps: {
|
|
109
|
+
maxLength: field.maxLength || 45
|
|
110
|
+
},
|
|
111
|
+
placeholder: field.placeholder,
|
|
112
|
+
multiline: true,
|
|
113
|
+
rows: 3
|
|
114
|
+
}));
|
|
115
|
+
case 'select_multiple':
|
|
116
|
+
{
|
|
117
|
+
const selectedValues = Array.isArray(value) ? value : [];
|
|
118
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({
|
|
119
|
+
select: true
|
|
120
|
+
}, commonProps, {
|
|
121
|
+
value: selectedValues,
|
|
122
|
+
onChange: e => onChange(e.target.value),
|
|
123
|
+
SelectProps: {
|
|
124
|
+
multiple: true,
|
|
125
|
+
renderValue: selected => /*#__PURE__*/React.createElement("div", {
|
|
126
|
+
className: "flex flex-wrap gap-4"
|
|
127
|
+
}, selected.map(opt => /*#__PURE__*/React.createElement(Chip, {
|
|
128
|
+
key: opt,
|
|
129
|
+
label: opt,
|
|
130
|
+
size: "small"
|
|
131
|
+
})))
|
|
132
|
+
}
|
|
133
|
+
}), (field.options || []).map(opt => /*#__PURE__*/React.createElement(MenuItem, {
|
|
134
|
+
key: opt,
|
|
135
|
+
value: opt
|
|
136
|
+
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
137
|
+
checked: selectedValues.indexOf(opt) > -1
|
|
138
|
+
}), /*#__PURE__*/React.createElement(ListItemText, {
|
|
139
|
+
primary: opt
|
|
140
|
+
}))));
|
|
141
|
+
}
|
|
142
|
+
case 'yes_no':
|
|
143
|
+
{
|
|
144
|
+
const toggleValue = value === true ? 'yes' : value === false ? 'no' : null;
|
|
145
|
+
return /*#__PURE__*/React.createElement(ToggleButtonGroup, {
|
|
146
|
+
value: toggleValue,
|
|
147
|
+
exclusive: true,
|
|
148
|
+
onChange: (_, val) => {
|
|
149
|
+
if (!disabled && val !== null) onChange(val === 'yes');
|
|
150
|
+
},
|
|
151
|
+
fullWidth: true,
|
|
152
|
+
size: "small",
|
|
153
|
+
color: error ? 'error' : 'standard',
|
|
154
|
+
disabled: disabled
|
|
155
|
+
}, /*#__PURE__*/React.createElement(ToggleButton, {
|
|
156
|
+
value: "yes"
|
|
157
|
+
}, i18next.t('common.app.yes')), /*#__PURE__*/React.createElement(ToggleButton, {
|
|
158
|
+
value: "no"
|
|
159
|
+
}, i18next.t('common.app.no')));
|
|
160
|
+
}
|
|
161
|
+
case 'date':
|
|
162
|
+
// Native date input, not each host app's own local DateTimeField wrapper -- this component
|
|
163
|
+
// can't depend on either app's app-local component tree. Value is a plain 'YYYY-MM-DD'
|
|
164
|
+
// string in and out, same convention both apps already use for this field type.
|
|
165
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({}, commonProps, {
|
|
166
|
+
type: "date",
|
|
167
|
+
value: value,
|
|
168
|
+
onChange: e => onChange(e.target.value),
|
|
169
|
+
InputLabelProps: {
|
|
170
|
+
shrink: true
|
|
171
|
+
},
|
|
172
|
+
inputProps: {
|
|
173
|
+
onClick: openNativeDatePicker
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
default:
|
|
177
|
+
// 'text' (default/unknown type too, for backward compat).
|
|
178
|
+
return /*#__PURE__*/React.createElement(TextField, _extends({}, commonProps, {
|
|
179
|
+
value: value,
|
|
180
|
+
onChange: e => onChange(e.target.value),
|
|
181
|
+
inputProps: {
|
|
182
|
+
maxLength: field.maxLength || 45
|
|
183
|
+
},
|
|
184
|
+
type: "text",
|
|
185
|
+
placeholder: field.placeholder
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
export default renderFieldTypeControl;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
3
|
+
import { Icon, IconButton, Button, Paper, Typography, Chip, Alert, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material';
|
|
4
4
|
import { ThemeProvider } from '@mui/system';
|
|
5
5
|
import { createTheme, useTheme, alpha } from '@mui/material/styles';
|
|
6
6
|
import AddIcon from '@mui/icons-material/Add';
|
|
@@ -9,6 +9,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
|
9
9
|
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
|
10
10
|
import * as i18nextLocal from 'i18next';
|
|
11
11
|
import { classifyAnimalFieldValue } from '../animalFieldDrift';
|
|
12
|
+
import { renderFieldTypeControl } from './fieldTypeControl';
|
|
12
13
|
|
|
13
14
|
// Every visible string this component owns lives under these two pre-existing i18next namespaces
|
|
14
15
|
// (already shared across both PitchCamp apps via pcm-shared-components' translations.json). Keyed
|
|
@@ -54,22 +55,6 @@ const REQUIRED_ERROR_KEY_BY_TYPE = {
|
|
|
54
55
|
yes_no: 'animal.required_error_yes_no'
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
// A native <input type="date"> only opens its calendar popup on its own when the browser's tiny
|
|
58
|
-
// built-in icon is clicked -- clicking anywhere else in the field just places the text caret
|
|
59
|
-
// (still lets someone type the year/month/day directly, which showPicker() doesn't change). Most
|
|
60
|
-
// people expect clicking anywhere in the box to open the calendar. showPicker() is Chrome/Edge/
|
|
61
|
-
// Firefox only as of this writing (not Safari) and throws if called on a disabled/read-only input
|
|
62
|
-
// or outside a user gesture, so this fails silently rather than breaking the click -- on an
|
|
63
|
-
// unsupported browser, the field just falls back to manual typing and the browser's own
|
|
64
|
-
// icon-click affordance, exactly as before.
|
|
65
|
-
const openNativeDatePicker = e => {
|
|
66
|
-
try {
|
|
67
|
-
e.target.showPicker?.();
|
|
68
|
-
} catch {
|
|
69
|
-
// ignore -- unsupported browser or transient DOM state, typing/icon-click still work
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
58
|
/**
|
|
74
59
|
* Tab-per-item editor for a lot's animal or vehicle detail fields: one tab per item (green
|
|
75
60
|
* check when every required field is answered, amber warning triangle otherwise), an "Add"
|
|
@@ -419,148 +404,19 @@ export const AnimalVehicleDetailsEditor = props => {
|
|
|
419
404
|
p: 0
|
|
420
405
|
}
|
|
421
406
|
}, i18next.t('animal.field_drifted_replace'))));
|
|
422
|
-
} else if (field.type === 'number') {
|
|
423
|
-
control = /*#__PURE__*/React.createElement(TextField, {
|
|
424
|
-
name: field.key,
|
|
425
|
-
value: value,
|
|
426
|
-
onChange: e => onFieldChange(selectedItem.id, field.key, e.target.value),
|
|
427
|
-
onKeyDown: e => {
|
|
428
|
-
if (e.key === 'e' || e.key === 'E') e.preventDefault();
|
|
429
|
-
},
|
|
430
|
-
variant: "outlined",
|
|
431
|
-
size: "small",
|
|
432
|
-
error: showError,
|
|
433
|
-
helperText: showError ? requiredErrorText(field.type) : '',
|
|
434
|
-
inputProps: {
|
|
435
|
-
maxLength: field.maxLength || 45
|
|
436
|
-
},
|
|
437
|
-
type: "number",
|
|
438
|
-
placeholder: field.placeholder,
|
|
439
|
-
fullWidth: true,
|
|
440
|
-
InputProps: {
|
|
441
|
-
endAdornment: field.unit ? /*#__PURE__*/React.createElement(InputAdornment, {
|
|
442
|
-
position: "end"
|
|
443
|
-
}, field.unit) : undefined
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
} else if (field.type === 'select') {
|
|
447
|
-
control = /*#__PURE__*/React.createElement(TextField, {
|
|
448
|
-
select: true,
|
|
449
|
-
name: field.key,
|
|
450
|
-
value: value,
|
|
451
|
-
onChange: e => onFieldChange(selectedItem.id, field.key, e.target.value),
|
|
452
|
-
variant: "outlined",
|
|
453
|
-
size: "small",
|
|
454
|
-
error: showError,
|
|
455
|
-
helperText: showError ? requiredErrorText(field.type) : '',
|
|
456
|
-
fullWidth: true
|
|
457
|
-
}, /*#__PURE__*/React.createElement(MenuItem, {
|
|
458
|
-
value: ""
|
|
459
|
-
}, /*#__PURE__*/React.createElement("em", null, field.placeholder || i18next.t('common.app.select'))), (field.options || []).map(opt => /*#__PURE__*/React.createElement(MenuItem, {
|
|
460
|
-
key: opt,
|
|
461
|
-
value: opt
|
|
462
|
-
}, opt)));
|
|
463
|
-
} else if (field.type === 'textarea') {
|
|
464
|
-
control = /*#__PURE__*/React.createElement(TextField, {
|
|
465
|
-
name: field.key,
|
|
466
|
-
value: value,
|
|
467
|
-
onChange: e => onFieldChange(selectedItem.id, field.key, e.target.value),
|
|
468
|
-
variant: "outlined",
|
|
469
|
-
size: "small",
|
|
470
|
-
error: showError,
|
|
471
|
-
helperText: showError ? requiredErrorText(field.type) : '',
|
|
472
|
-
inputProps: {
|
|
473
|
-
maxLength: field.maxLength || 45
|
|
474
|
-
},
|
|
475
|
-
placeholder: field.placeholder,
|
|
476
|
-
multiline: true,
|
|
477
|
-
rows: 3,
|
|
478
|
-
fullWidth: true
|
|
479
|
-
});
|
|
480
|
-
} else if (field.type === 'select_multiple') {
|
|
481
|
-
const selectedValues = Array.isArray(value) ? value : [];
|
|
482
|
-
control = /*#__PURE__*/React.createElement(TextField, {
|
|
483
|
-
select: true,
|
|
484
|
-
name: field.key,
|
|
485
|
-
value: selectedValues,
|
|
486
|
-
onChange: e => onFieldChange(selectedItem.id, field.key, e.target.value),
|
|
487
|
-
variant: "outlined",
|
|
488
|
-
size: "small",
|
|
489
|
-
error: showError,
|
|
490
|
-
helperText: showError ? requiredErrorText(field.type) : '',
|
|
491
|
-
fullWidth: true,
|
|
492
|
-
SelectProps: {
|
|
493
|
-
multiple: true,
|
|
494
|
-
renderValue: selected => /*#__PURE__*/React.createElement("div", {
|
|
495
|
-
className: "flex flex-wrap gap-4"
|
|
496
|
-
}, selected.map(opt => /*#__PURE__*/React.createElement(Chip, {
|
|
497
|
-
key: opt,
|
|
498
|
-
label: opt,
|
|
499
|
-
size: "small"
|
|
500
|
-
})))
|
|
501
|
-
}
|
|
502
|
-
}, (field.options || []).map(opt => /*#__PURE__*/React.createElement(MenuItem, {
|
|
503
|
-
key: opt,
|
|
504
|
-
value: opt
|
|
505
|
-
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
506
|
-
checked: selectedValues.indexOf(opt) > -1
|
|
507
|
-
}), /*#__PURE__*/React.createElement(ListItemText, {
|
|
508
|
-
primary: opt
|
|
509
|
-
}))));
|
|
510
|
-
} else if (field.type === 'yes_no') {
|
|
511
|
-
const toggleValue = value === true ? 'yes' : value === false ? 'no' : null;
|
|
512
|
-
control = /*#__PURE__*/React.createElement(ToggleButtonGroup, {
|
|
513
|
-
value: toggleValue,
|
|
514
|
-
exclusive: true,
|
|
515
|
-
onChange: (_, val) => {
|
|
516
|
-
if (val !== null) onFieldChange(selectedItem.id, field.key, val === 'yes');
|
|
517
|
-
},
|
|
518
|
-
fullWidth: true,
|
|
519
|
-
size: "small",
|
|
520
|
-
color: showError ? 'error' : 'standard'
|
|
521
|
-
}, /*#__PURE__*/React.createElement(ToggleButton, {
|
|
522
|
-
value: "yes"
|
|
523
|
-
}, i18next.t('common.app.yes')), /*#__PURE__*/React.createElement(ToggleButton, {
|
|
524
|
-
value: "no"
|
|
525
|
-
}, i18next.t('common.app.no')));
|
|
526
|
-
} else if (field.type === 'date') {
|
|
527
|
-
// Native date input, not each host app's own local DateTimeField wrapper --
|
|
528
|
-
// this component can't depend on either app's app-local component tree.
|
|
529
|
-
// Value is a plain 'YYYY-MM-DD' string in and out, same convention both apps
|
|
530
|
-
// already use for this field type.
|
|
531
|
-
control = /*#__PURE__*/React.createElement(TextField, {
|
|
532
|
-
name: field.key,
|
|
533
|
-
type: "date",
|
|
534
|
-
value: value,
|
|
535
|
-
onChange: e => onFieldChange(selectedItem.id, field.key, e.target.value),
|
|
536
|
-
variant: "outlined",
|
|
537
|
-
size: "small",
|
|
538
|
-
error: showError,
|
|
539
|
-
helperText: showError ? requiredErrorText(field.type) : '',
|
|
540
|
-
fullWidth: true,
|
|
541
|
-
InputLabelProps: {
|
|
542
|
-
shrink: true
|
|
543
|
-
},
|
|
544
|
-
inputProps: {
|
|
545
|
-
onClick: openNativeDatePicker
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
407
|
} else {
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
408
|
+
// Every live (non-drifted) control, for every field type this component
|
|
409
|
+
// knows about, is one call into the extracted, reusable renderer -- see
|
|
410
|
+
// fieldTypeControl.jsx in this same directory. AnimalFieldsOnceBlock (also
|
|
411
|
+
// in this directory) calls the exact same function for its once-per-
|
|
412
|
+
// reservation fields, so the two never carry separate copies of this switch.
|
|
413
|
+
control = renderFieldTypeControl({
|
|
414
|
+
field,
|
|
415
|
+
value,
|
|
416
|
+
onChange: newValue => onFieldChange(selectedItem.id, field.key, newValue),
|
|
556
417
|
error: showError,
|
|
557
418
|
helperText: showError ? requiredErrorText(field.type) : '',
|
|
558
|
-
|
|
559
|
-
maxLength: field.maxLength || 45
|
|
560
|
-
},
|
|
561
|
-
type: "text",
|
|
562
|
-
placeholder: field.placeholder,
|
|
563
|
-
fullWidth: true
|
|
419
|
+
i18next
|
|
564
420
|
});
|
|
565
421
|
}
|
|
566
422
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -38,6 +38,16 @@ export const classifyAnimalFieldValue = (fieldConfig, rawValue) => {
|
|
|
38
38
|
type,
|
|
39
39
|
options
|
|
40
40
|
} = fieldConfig;
|
|
41
|
+
|
|
42
|
+
// notice fields are never per-animal and never have a value to hold in the first place (see the
|
|
43
|
+
// shared lot_animal_fields.meta contract) -- always 'unanswered' regardless of any stray content
|
|
44
|
+
// that might still be sitting in field_values under this key (e.g. left over from before the
|
|
45
|
+
// field was retyped to 'notice'), so a caller filtering on `!== 'unanswered'` never shows a
|
|
46
|
+
// spurious 'drifted' warning for a field that was never answerable to begin with. Every consumer
|
|
47
|
+
// in the app/ repo already excludes 'notice' from this call entirely (see DetailsSummary.jsx /
|
|
48
|
+
// Occupant.js's per-animal vs. once-per-reservation field split), so this is a defensive fallback
|
|
49
|
+
// for any other/future caller that passes one through unfiltered.
|
|
50
|
+
if (type === 'notice') return 'unanswered';
|
|
41
51
|
if (type === 'yes_no') {
|
|
42
52
|
if (typeof rawValue === 'boolean') return 'answered';
|
|
43
53
|
return hasWrongShapeContent(rawValue) ? 'drifted' : 'unanswered';
|
package/dist/index.js
CHANGED
|
@@ -37,5 +37,6 @@ import VerticalImageTile from './components/Images/VerticalImageTile';
|
|
|
37
37
|
import EventCard from './components/Cards/EventCard';
|
|
38
38
|
import ImageAssets from './components/Icons/ImageAssets';
|
|
39
39
|
import AnimalVehicleDetailsEditor from './components/Reservation/AnimalVehicleDetailsEditor';
|
|
40
|
+
import { AnimalFieldsOnceBlock } from './components/Reservation/AnimalVehicleDetailsEditor/AnimalFieldsOnceBlock';
|
|
40
41
|
import { classifyAnimalFieldValue } from './components/Reservation/animalFieldDrift';
|
|
41
|
-
export { TestButton, CodeHighlight, CustomFab, HelpButton, GenericDialogWindow, PcSharedComponentThemeInheritor, ImageCanvasDraw, PCMScrollbar, TwoColumnDisplay, SimpleTab, HrefLink, GenericAppBar, ReusablePopupMenu, SearchBar, TillButton, ProductItemButton, DropdownButton, translationResources, coreLocals, currencySymbol, DateRangeCalendar, DateCalendar, SimpleLabel, CurrencyTextInput, ImageStack, PlatformPaymentPlans, PlatformPlanTitles, PlatformPlanPrices, LoadingBackdrop, SimpleAccordion, PricingCalendar, FancyDate, HorizontalImageTile, VerticalImageTile, EventCard, ImageAssets, AnimalVehicleDetailsEditor, classifyAnimalFieldValue };
|
|
42
|
+
export { TestButton, CodeHighlight, CustomFab, HelpButton, GenericDialogWindow, PcSharedComponentThemeInheritor, ImageCanvasDraw, PCMScrollbar, TwoColumnDisplay, SimpleTab, HrefLink, GenericAppBar, ReusablePopupMenu, SearchBar, TillButton, ProductItemButton, DropdownButton, translationResources, coreLocals, currencySymbol, DateRangeCalendar, DateCalendar, SimpleLabel, CurrencyTextInput, ImageStack, PlatformPaymentPlans, PlatformPlanTitles, PlatformPlanPrices, LoadingBackdrop, SimpleAccordion, PricingCalendar, FancyDate, HorizontalImageTile, VerticalImageTile, EventCard, ImageAssets, AnimalVehicleDetailsEditor, AnimalFieldsOnceBlock, classifyAnimalFieldValue };
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"field_drifted_replace": "Enter a new answer",
|
|
42
42
|
"field_removed": "This question no longer exists in this lot's setup.",
|
|
43
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."
|
|
44
|
+
"historical_answers_hint": "These questions are no longer part of this lot's setup and can't be edited.",
|
|
45
|
+
"view_full_field": "View full {{label}}"
|
|
45
46
|
},
|
|
46
47
|
"app": {
|
|
47
48
|
"common": {
|
|
@@ -2464,6 +2465,11 @@
|
|
|
2464
2465
|
"field_type_yes_no": "Yes / No",
|
|
2465
2466
|
"field_type_date": "Date",
|
|
2466
2467
|
"field_type_select_multiple": "Dropdown (Select Multiple)",
|
|
2468
|
+
"field_type_notice": "Notice",
|
|
2469
|
+
"ask_once_per_reservation": "Ask once per reservation",
|
|
2470
|
+
"once_per_reservation_helper": "Shown once for the whole reservation instead of once per animal.",
|
|
2471
|
+
"notice_content_label": "Notice content",
|
|
2472
|
+
"notice_preview_label": "Preview",
|
|
2467
2473
|
"options_label": "Options",
|
|
2468
2474
|
"add_option": "Add Option",
|
|
2469
2475
|
"unit_label": "Unit",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"field_drifted_replace": "Ingrese una nueva respuesta",
|
|
42
42
|
"field_removed": "Esta pregunta ya no existe en la configuración de este lote.",
|
|
43
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."
|
|
44
|
+
"historical_answers_hint": "Estas preguntas ya no forman parte de la configuración de este lote y no se pueden editar.",
|
|
45
|
+
"view_full_field": "Ver {{label}} completo"
|
|
45
46
|
},
|
|
46
47
|
"app": {
|
|
47
48
|
"common": {
|
|
@@ -2464,6 +2465,11 @@
|
|
|
2464
2465
|
"field_type_yes_no": "Sí / No",
|
|
2465
2466
|
"field_type_date": "Fecha",
|
|
2466
2467
|
"field_type_select_multiple": "Desplegable (Seleccionar varios)",
|
|
2468
|
+
"field_type_notice": "Aviso",
|
|
2469
|
+
"ask_once_per_reservation": "Preguntar una sola vez por reserva",
|
|
2470
|
+
"once_per_reservation_helper": "Se muestra una sola vez para toda la reserva, en lugar de una vez por animal.",
|
|
2471
|
+
"notice_content_label": "Contenido del aviso",
|
|
2472
|
+
"notice_preview_label": "Vista previa",
|
|
2467
2473
|
"options_label": "Opciones",
|
|
2468
2474
|
"add_option": "Agregar opción",
|
|
2469
2475
|
"unit_label": "Unidad",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"field_drifted_replace": "Entrez une nouvelle réponse",
|
|
42
42
|
"field_removed": "Cette question n'existe plus dans la configuration de ce terrain.",
|
|
43
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."
|
|
44
|
+
"historical_answers_hint": "Ces questions ne font plus partie de la configuration de ce terrain et ne peuvent pas être modifiées.",
|
|
45
|
+
"view_full_field": "Voir {{label}} en entier"
|
|
45
46
|
},
|
|
46
47
|
"app": {
|
|
47
48
|
"common": {
|
|
@@ -2464,6 +2465,11 @@
|
|
|
2464
2465
|
"field_type_yes_no": "Oui / Non",
|
|
2465
2466
|
"field_type_date": "Date",
|
|
2466
2467
|
"field_type_select_multiple": "Liste déroulante (Sélectionner plusieurs)",
|
|
2468
|
+
"field_type_notice": "Avis",
|
|
2469
|
+
"ask_once_per_reservation": "Demander une seule fois par réservation",
|
|
2470
|
+
"once_per_reservation_helper": "Affiché une seule fois pour toute la réservation, plutôt que pour chaque animal.",
|
|
2471
|
+
"notice_content_label": "Contenu de l'avis",
|
|
2472
|
+
"notice_preview_label": "Aperçu",
|
|
2467
2473
|
"options_label": "Options",
|
|
2468
2474
|
"add_option": "Ajouter une option",
|
|
2469
2475
|
"unit_label": "Unité",
|
package/dist/styles/tailwind.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2196f380;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.visible{visibility:visible}.static{position:static}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-2{top:-.2rem}.-top-6{top:-.6rem}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-20{top:2rem}.top-64{top:6.4rem}.z-10{z-index:10}.z-99{z-index:99}.col-span-full{grid-column:1/-1}.m-10{margin:1rem}.m-12{margin:1.2rem}.m-24{margin:2.4rem}.m-4{margin:.4rem}.m-6{margin:.6rem}.m-auto{margin:auto}.mx-0{margin-left:0;margin-right:0}.mx-12{margin-left:1.2rem;margin-right:1.2rem}.mx-2{margin-left:.2rem;margin-right:.2rem}.mx-4{margin-left:.4rem;margin-right:.4rem}.mx-6{margin-left:.6rem;margin-right:.6rem}.mx-8{margin-left:.8rem;margin-right:.8rem}.mx-auto{margin-left:auto;margin-right:auto}.my-12{margin-top:1.2rem;margin-bottom:1.2rem}.my-24{margin-top:2.4rem;margin-bottom:2.4rem}.my-4{margin-top:.4rem;margin-bottom:.4rem}.my-6{margin-top:.6rem;margin-bottom:.6rem}.my-auto{margin-top:auto;margin-bottom:auto}.-ml-10{margin-left:-1rem}.mb-0{margin-bottom:0}.mb-12{margin-bottom:1.2rem}.mb-2{margin-bottom:.2rem}.mb-24{margin-bottom:2.4rem}.mb-6{margin-bottom:.6rem}.mb-8{margin-bottom:.8rem}.ml-0{margin-left:0}.ml-1{margin-left:.1rem}.ml-12{margin-left:1.2rem}.ml-24{margin-left:2.4rem}.ml-6{margin-left:.6rem}.ml-8{margin-left:.8rem}.mr-0{margin-right:0}.mr-12{margin-right:1.2rem}.mr-24{margin-right:2.4rem}.mr-6{margin-right:.6rem}.mt-0{margin-top:0}.mt-12{margin-top:1.2rem}.mt-24{margin-top:2.4rem}.mt-4{margin-top:.4rem}.mt-6{margin-top:.6rem}.mt-auto{margin-top:auto}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-136{height:13.6rem}.h-200{height:20rem}.h-24{height:2.4rem}.h-400{height:40rem}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:3.2rem}.max-h-512{max-height:51.2rem}.min-h-160{min-height:16rem}.min-h-224{min-height:22.4rem}.min-h-256{min-height:25.6rem}.w-1\/2{width:50%}.w-2\/5{width:40%}.w-24{width:2.4rem}.w-3\/4{width:75%}.w-auto{width:auto}.w-full{width:100%}.max-w-full{max-width:100%}.max-w-min{max-width:-moz-min-content;max-width:min-content}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-4{gap:.4rem}.gap-6{gap:.6rem}.space-x-10>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overscroll-none{overscroll-behavior:none}.truncate{overflow:hidden;white-space:nowrap}.overflow-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.rounded{border-radius:.4rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.8rem}.rounded-md{border-radius:.6rem}.rounded-t-md{border-top-left-radius:.6rem;border-top-right-radius:.6rem}.rounded-bl-lg{border-bottom-left-radius:.8rem}.rounded-br-lg{border-bottom-right-radius:.8rem}.rounded-tl-lg{border-top-left-radius:.8rem}.rounded-tr-lg{border-top-right-radius:.8rem}.border{border-width:1px}.border-2{border-width:2px}.border-t-8{border-top-width:8px}.border-transparent{border-color:#0000}.bg-black{--tw-bg-opacity:1;background-color:rgb(34 41 47/var(--tw-bg-opacity,1))}.bg-blue{--tw-bg-opacity:1;background-color:rgb(33 150 243/var(--tw-bg-opacity,1))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(66 66 66/var(--tw-bg-opacity,1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(67 160 71/var(--tw-bg-opacity,1))}.bg-grey-100{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity,1))}.bg-grey-200{--tw-bg-opacity:1;background-color:rgb(238 238 238/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100\/50{background-color:#fff9c480}.bg-cover{background-size:cover}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-10{padding:1rem}.p-12{padding:1.2rem}.p-24{padding:2.4rem}.p-6{padding:.6rem}.px-2{padding-left:.2rem;padding-right:.2rem}.px-4{padding-left:.4rem;padding-right:.4rem}.px-6{padding-left:.6rem;padding-right:.6rem}.px-8{padding-left:.8rem;padding-right:.8rem}.py-2{padding-top:.2rem;padding-bottom:.2rem}.py-4{padding-top:.4rem;padding-bottom:.4rem}.py-6{padding-top:.6rem;padding-bottom:.6rem}.pb-4{padding-bottom:.4rem}.pl-0{padding-left:0}.pl-4{padding-left:.4rem}.pl-8{padding-left:.8rem}.pr-0{padding-right:0}.pr-6{padding-right:.6rem}.pt-0{padding-top:0}.pt-12{padding-top:1.2rem}.pt-4{padding-top:.4rem}.text-left{text-align:left}.text-center{text-align:center}.font-sans{font-family:Muli,Roboto,-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-serif{font-family:Georgia,Cambria,Times New Roman,Times,serif}.text-14{font-size:1.4rem}.text-16{font-size:1.6rem}.text-17{font-size:1.7rem}.text-24,.text-2xl{font-size:2.4rem}.text-2xl{line-height:3.2rem}.text-32{font-size:3.2rem}.text-4xl{font-size:3.6rem;line-height:4rem}.text-5xl{font-size:4.8rem;line-height:1}.text-6xl{font-size:6rem;line-height:1}.text-base{font-size:1.6rem;line-height:2.4rem}.text-sm{font-size:1.4rem;line-height:2rem}.text-xl{font-size:2rem;line-height:2.8rem}.font-500{font-weight:500}.font-600{font-weight:600}.font-800{font-weight:800}.font-900{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.text-gray-300{--tw-text-opacity:1;color:rgb(224 224 224/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(158 158 158/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(117 117 117/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(97 97 97/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(67 160 71/var(--tw-text-opacity,1))}.text-green-700{--tw-text-opacity:1;color:rgb(56 142 60/var(--tw-text-opacity,1))}.text-grey-800{--tw-text-opacity:1;color:rgb(66 66 66/var(--tw-text-opacity,1))}.text-orange-600{--tw-text-opacity:1;color:rgb(251 140 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.decoration-gray-500{text-decoration-color:#9e9e9e}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px 0 #0000000f;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-1{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-1{--tw-shadow:0px 2px 1px -1px #0003,0px 1px 1px 0px #00000024,0px 1px 3px 0px #0000001f;--tw-shadow-colored:0px 2px 1px -1px var(--tw-shadow-color),0px 1px 1px 0px var(--tw-shadow-color),0px 1px 3px 0px var(--tw-shadow-color)}.shadow-2{--tw-shadow:0px 3px 1px -2px #0003,0px 2px 2px 0px #00000024,0px 1px 5px 0px #0000001f;--tw-shadow-colored:0px 3px 1px -2px var(--tw-shadow-color),0px 2px 2px 0px var(--tw-shadow-color),0px 1px 5px 0px var(--tw-shadow-color)}.shadow-2,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -1px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.contrast-0{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.contrast-0{--tw-contrast:contrast(0)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f)}.drop-shadow,.drop-shadow-none{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-none{--tw-drop-shadow:drop-shadow(0 0 #0000)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:translate-y-1:hover{--tw-translate-y:0.1rem}.hover\:rotate-3:hover,.hover\:translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:rotate-3:hover{--tw-rotate:3deg}.hover\:skew-y-3:hover{--tw-skew-y:3deg}.hover\:scale-105:hover,.hover\:skew-y-3:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:list-disc:hover{list-style-type:disc}.hover\:bg-gray-100\/75:hover{background-color:#f5f5f5bf}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(56 142 60/var(--tw-bg-opacity,1))}.hover\:bg-grey-300:hover{--tw-bg-opacity:1;background-color:rgb(224 224 224/var(--tw-bg-opacity,1))}.hover\:bg-indigo-100\/25:hover{background-color:#c5cae940}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(97 97 97/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-75:hover{--tw-brightness:brightness(.75)}.hover\:brightness-75:hover,.hover\:drop-shadow-2xl:hover{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:drop-shadow-2xl:hover{--tw-drop-shadow:drop-shadow(0 25px 25px #00000026)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-green-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(76 175 80/var(--tw-ring-opacity,1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}@media (min-width:600px){.sm\:p-8{padding:.8rem}}@media (min-width:960px){.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-12{margin-left:1.2rem}.md\:flex{display:flex}.md\:hidden{display:none}.md\:max-w-288{max-width:28.8rem}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:pt-0{padding-top:0}}@media (prefers-color-scheme:dark){.dark\:text-gray-500{--tw-text-opacity:1;color:rgb(158 158 158/var(--tw-text-opacity,1))}.dark\:text-green-500{--tw-text-opacity:1;color:rgb(76 175 80/var(--tw-text-opacity,1))}.dark\:text-orange-500{--tw-text-opacity:1;color:rgb(255 152 0/var(--tw-text-opacity,1))}}
|
|
1
|
+
*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2196f380;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-2{top:-.2rem}.-top-6{top:-.6rem}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-20{top:2rem}.top-64{top:6.4rem}.z-10{z-index:10}.z-99{z-index:99}.col-span-full{grid-column:1/-1}.m-10{margin:1rem}.m-12{margin:1.2rem}.m-24{margin:2.4rem}.m-4{margin:.4rem}.m-6{margin:.6rem}.m-auto{margin:auto}.mx-0{margin-left:0;margin-right:0}.mx-12{margin-left:1.2rem;margin-right:1.2rem}.mx-2{margin-left:.2rem;margin-right:.2rem}.mx-4{margin-left:.4rem;margin-right:.4rem}.mx-6{margin-left:.6rem;margin-right:.6rem}.mx-8{margin-left:.8rem;margin-right:.8rem}.mx-auto{margin-left:auto;margin-right:auto}.my-12{margin-top:1.2rem;margin-bottom:1.2rem}.my-24{margin-top:2.4rem;margin-bottom:2.4rem}.my-4{margin-top:.4rem;margin-bottom:.4rem}.my-6{margin-top:.6rem;margin-bottom:.6rem}.my-auto{margin-top:auto;margin-bottom:auto}.-ml-10{margin-left:-1rem}.mb-0{margin-bottom:0}.mb-12{margin-bottom:1.2rem}.mb-2{margin-bottom:.2rem}.mb-24{margin-bottom:2.4rem}.mb-6{margin-bottom:.6rem}.mb-8{margin-bottom:.8rem}.ml-0{margin-left:0}.ml-1{margin-left:.1rem}.ml-12{margin-left:1.2rem}.ml-24{margin-left:2.4rem}.ml-6{margin-left:.6rem}.ml-8{margin-left:.8rem}.mr-0{margin-right:0}.mr-12{margin-right:1.2rem}.mr-24{margin-right:2.4rem}.mr-6{margin-right:.6rem}.mt-0{margin-top:0}.mt-12{margin-top:1.2rem}.mt-24{margin-top:2.4rem}.mt-4{margin-top:.4rem}.mt-6{margin-top:.6rem}.mt-auto{margin-top:auto}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-136{height:13.6rem}.h-200{height:20rem}.h-24{height:2.4rem}.h-400{height:40rem}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:3.2rem}.max-h-512{max-height:51.2rem}.min-h-160{min-height:16rem}.min-h-224{min-height:22.4rem}.min-h-256{min-height:25.6rem}.w-1\/2{width:50%}.w-2\/5{width:40%}.w-24{width:2.4rem}.w-3\/4{width:75%}.w-auto{width:auto}.w-full{width:100%}.max-w-full{max-width:100%}.max-w-min{max-width:-moz-min-content;max-width:min-content}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-4{gap:.4rem}.gap-6{gap:.6rem}.space-x-10>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overscroll-none{overscroll-behavior:none}.truncate{overflow:hidden;white-space:nowrap}.overflow-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.rounded{border-radius:.4rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.8rem}.rounded-md{border-radius:.6rem}.rounded-t-md{border-top-left-radius:.6rem;border-top-right-radius:.6rem}.rounded-bl-lg{border-bottom-left-radius:.8rem}.rounded-br-lg{border-bottom-right-radius:.8rem}.rounded-tl-lg{border-top-left-radius:.8rem}.rounded-tr-lg{border-top-right-radius:.8rem}.border{border-width:1px}.border-2{border-width:2px}.border-t-8{border-top-width:8px}.border-transparent{border-color:#0000}.bg-black{--tw-bg-opacity:1;background-color:rgb(34 41 47/var(--tw-bg-opacity,1))}.bg-blue{--tw-bg-opacity:1;background-color:rgb(33 150 243/var(--tw-bg-opacity,1))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(66 66 66/var(--tw-bg-opacity,1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(67 160 71/var(--tw-bg-opacity,1))}.bg-grey-100{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity,1))}.bg-grey-200{--tw-bg-opacity:1;background-color:rgb(238 238 238/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100\/50{background-color:#fff9c480}.bg-cover{background-size:cover}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-10{padding:1rem}.p-12{padding:1.2rem}.p-24{padding:2.4rem}.p-6{padding:.6rem}.px-2{padding-left:.2rem;padding-right:.2rem}.px-4{padding-left:.4rem;padding-right:.4rem}.px-6{padding-left:.6rem;padding-right:.6rem}.px-8{padding-left:.8rem;padding-right:.8rem}.py-2{padding-top:.2rem;padding-bottom:.2rem}.py-4{padding-top:.4rem;padding-bottom:.4rem}.py-6{padding-top:.6rem;padding-bottom:.6rem}.pb-4{padding-bottom:.4rem}.pl-0{padding-left:0}.pl-4{padding-left:.4rem}.pl-8{padding-left:.8rem}.pr-0{padding-right:0}.pr-6{padding-right:.6rem}.pt-0{padding-top:0}.pt-12{padding-top:1.2rem}.pt-4{padding-top:.4rem}.text-left{text-align:left}.text-center{text-align:center}.font-sans{font-family:Muli,Roboto,-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-serif{font-family:Georgia,Cambria,Times New Roman,Times,serif}.text-14{font-size:1.4rem}.text-16{font-size:1.6rem}.text-17{font-size:1.7rem}.text-24,.text-2xl{font-size:2.4rem}.text-2xl{line-height:3.2rem}.text-32{font-size:3.2rem}.text-4xl{font-size:3.6rem;line-height:4rem}.text-5xl{font-size:4.8rem;line-height:1}.text-6xl{font-size:6rem;line-height:1}.text-base{font-size:1.6rem;line-height:2.4rem}.text-sm{font-size:1.4rem;line-height:2rem}.text-xl{font-size:2rem;line-height:2.8rem}.font-500{font-weight:500}.font-600{font-weight:600}.font-800{font-weight:800}.font-900{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.text-gray-300{--tw-text-opacity:1;color:rgb(224 224 224/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(158 158 158/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(117 117 117/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(97 97 97/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(67 160 71/var(--tw-text-opacity,1))}.text-green-700{--tw-text-opacity:1;color:rgb(56 142 60/var(--tw-text-opacity,1))}.text-grey-800{--tw-text-opacity:1;color:rgb(66 66 66/var(--tw-text-opacity,1))}.text-orange-600{--tw-text-opacity:1;color:rgb(251 140 0/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.decoration-gray-500{text-decoration-color:#9e9e9e}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px 0 #0000000f;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-1{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-1{--tw-shadow:0px 2px 1px -1px #0003,0px 1px 1px 0px #00000024,0px 1px 3px 0px #0000001f;--tw-shadow-colored:0px 2px 1px -1px var(--tw-shadow-color),0px 1px 1px 0px var(--tw-shadow-color),0px 1px 3px 0px var(--tw-shadow-color)}.shadow-2{--tw-shadow:0px 3px 1px -2px #0003,0px 2px 2px 0px #00000024,0px 1px 5px 0px #0000001f;--tw-shadow-colored:0px 3px 1px -2px var(--tw-shadow-color),0px 2px 2px 0px var(--tw-shadow-color),0px 1px 5px 0px var(--tw-shadow-color)}.shadow-2,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -1px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.contrast-0{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.contrast-0{--tw-contrast:contrast(0)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f)}.drop-shadow,.drop-shadow-none{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-none{--tw-drop-shadow:drop-shadow(0 0 #0000)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:translate-y-1:hover{--tw-translate-y:0.1rem}.hover\:rotate-3:hover,.hover\:translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:rotate-3:hover{--tw-rotate:3deg}.hover\:skew-y-3:hover{--tw-skew-y:3deg}.hover\:scale-105:hover,.hover\:skew-y-3:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:list-disc:hover{list-style-type:disc}.hover\:bg-gray-100\/75:hover{background-color:#f5f5f5bf}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(56 142 60/var(--tw-bg-opacity,1))}.hover\:bg-grey-300:hover{--tw-bg-opacity:1;background-color:rgb(224 224 224/var(--tw-bg-opacity,1))}.hover\:bg-indigo-100\/25:hover{background-color:#c5cae940}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(97 97 97/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-75:hover{--tw-brightness:brightness(.75)}.hover\:brightness-75:hover,.hover\:drop-shadow-2xl:hover{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:drop-shadow-2xl:hover{--tw-drop-shadow:drop-shadow(0 25px 25px #00000026)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-green-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(76 175 80/var(--tw-ring-opacity,1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}@media (min-width:600px){.sm\:p-8{padding:.8rem}}@media (min-width:960px){.md\:mx-auto{margin-left:auto;margin-right:auto}.md\:ml-12{margin-left:1.2rem}.md\:flex{display:flex}.md\:hidden{display:none}.md\:max-w-288{max-width:28.8rem}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:pt-0{padding-top:0}}@media (prefers-color-scheme:dark){.dark\:text-gray-500{--tw-text-opacity:1;color:rgb(158 158 158/var(--tw-text-opacity,1))}.dark\:text-green-500{--tw-text-opacity:1;color:rgb(76 175 80/var(--tw-text-opacity,1))}.dark\:text-orange-500{--tw-text-opacity:1;color:rgb(255 152 0/var(--tw-text-opacity,1))}}
|