UNPKG

@focuson/form_components

Version:

Components that can be used by @focuson/forms

270 lines (269 loc) 14.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DatePicker = exports.RawDatePicker = exports.defaultDatePickerOnCheck = exports.defaultDatePickerWithExtraTxs = exports.calcInfoTheDatePickerNeeds = exports.acceptDateForTest = exports.firstAllowedDate = exports.validateDateInfo = exports.errorsAndT = exports.errorsAnd = exports.defaultDateErrorMessage = exports.parseDate = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const utils_1 = require("@focuson/utils"); const date_fns_1 = require("date-fns"); const state_1 = require("@focuson/state"); const label_1 = require("./label"); const makeButtons_1 = require("./makeButtons"); const react_datepicker_1 = __importDefault(require("react-datepicker")); const react_1 = require("react"); const CustomError_1 = require("./CustomError"); const labelAndInput_1 = require("./labelAndInput"); function isDateRangeInPast(d) { const a = d; return a.type === 'past'; } function isDateRangeInFuture(d) { const a = d; return a.type === 'future'; } const parseDate = (prefix, format) => (date) => { if (typeof date !== 'string') throw new Error(`${prefix}. parseDate called with non string. Type is ${typeof date}. Value is ${JSON.stringify(date)}`); let result = (0, date_fns_1.parse)(date.replace(/\//g, '-'), format.replace(/\//g, '-'), new Date()); return isNaN(result.getTime()) ? [`${prefix}Please use date format ${format.toLowerCase()}`] : result; }; exports.parseDate = parseDate; exports.defaultDateErrorMessage = { beforeFirstValid: "before first valid date", isHoliday: 'is a holiday', isWeekend: 'is a weekend', isInPast: `is in the future` }; const holidaysOK = (holidays, dateRange, dateErrorMessage) => (date) => { if (dateRange.allowHolidays !== false || !date) return []; const found = holidays.find(h => h.getTime() === date.getTime()); if (found) return [dateErrorMessage.isHoliday]; return []; }; const weekEndsOk = (dateRange, dateErrorMessage) => (date) => { if (dateRange.allowWeekends !== false || !date) return []; return [0, 6].includes(date.getDay()) ? [dateErrorMessage.isWeekend] : []; }; const futureOk = (udi, dateRange, dateErrorMessage) => { // const firstValidDate = firstAllowedDate ( udi.today, udi.holidays, dateRange, udi.firstValidDate, udi.dateFormat, dateErrorMessage ) return date => { if (date && isDateRangeInFuture(dateRange)) { const firstValidDate = udi.firstValidDate; if (firstValidDate === undefined) { console.error('error - undefined firstValidDate', udi, dateRange); throw Error(`Cannot work out if date is in future because firstValidDate is undefined`); } return date.getTime() >= firstValidDate.getTime() ? [] : [dateErrorMessage.beforeFirstValid]; } else return []; }; }; const pastOk = (dateRange, today, dateErrorMessage) => date => { if (date && isDateRangeInPast(dateRange)) { return date.getTime() <= today.getTime() ? [] : [dateErrorMessage.isInPast]; } else return []; }; function combine(...fns) { return date => fns.flatMap(fn => fn(date)); } function errorsAnd(ds) { const errors = ds.flatMap(e => Array.isArray(e) ? e : []); const ts = ds.flatMap(d => Array.isArray(d) ? [] : [d]); return [errors, ts]; } exports.errorsAnd = errorsAnd; function errorsAndT(d) { if (Array.isArray(d)) return [d, undefined]; return [[], d]; } exports.errorsAndT = errorsAndT; function map(d, fn) { if (Array.isArray(d)) return d; return fn(d); } const noErrorsBooleanFn = (fn) => t => fn(t).length === 0; const errorsBooleanFn = (fn) => t => fn(t).length > 0; function validateDateInfo(dateInfo, targetJurisdiction, dateRange, firstValidDate, dateErrorMessage, debug) { if (dateInfo === undefined) return { today: new Date(), holidays: [], firstValidDate: undefined }; const dateFormatForInfo = dateInfo.dateFormat; const [holidayErrors, holidays] = errorsAnd((0, utils_1.safeArray)(dateInfo.holidays).filter(h => h.jurisdiction === targetJurisdiction || targetJurisdiction === undefined) .map(({ date, jurisdiction }, i) => (0, exports.parseDate)(`holidays[${i}]: `, dateFormatForInfo)(date))); const [todayErrors, todayDates] = errorsAnd([(0, exports.parseDate)('', dateFormatForInfo)(dateInfo.today)]); const errors = [...todayErrors, ...holidayErrors]; if (debug) console.log('validateDateInfo', dateInfo, errors); if (errors.length > 0) return errors; const today = todayDates[0]; const actualFirstValidDate = firstAllowedDate(today, holidays, dateRange, firstValidDate, dateErrorMessage); return { holidays, today, firstValidDate: actualFirstValidDate }; } exports.validateDateInfo = validateDateInfo; function firstAllowedDate(today, holidays, dateRange, firstValidDate, dateErrorMessage) { if (firstValidDate) return firstValidDate; if (isDateRangeInFuture(dateRange)) { const checkDate = combine(holidaysOK(holidays, dateRange, dateErrorMessage), weekEndsOk(dateRange, dateErrorMessage)); if (dateRange.minWorkingDaysBefore === undefined) return today; if (dateRange.minWorkingDaysBefore < 0) throw Error(`Illegal argument: ${JSON.stringify(dateRange)}`); if (today === undefined) throw Error(``); const date = new Date(today.getTime()); var count = 0; while (true) { const ok = checkDate(date).length === 0; if (ok && count >= dateRange.minWorkingDaysBefore) return date; else { date.setDate(date.getDate() + 1); if (ok) count = count + 1; } } } } exports.firstAllowedDate = firstAllowedDate; function acceptDate(udi, dateRange, dateErrorMessage) { const { holidays, today } = udi; return combine(futureOk(udi, dateRange, dateErrorMessage), pastOk(dateRange, today, dateErrorMessage), holidaysOK(holidays, dateRange, dateErrorMessage), weekEndsOk(dateRange, dateErrorMessage)); } function acceptDateForTest(state, jurisdiction, dateInfo, dateRange, dateFormat, dateErrorMessage) { const firstSelectableDate = findFirstSelectableDate(state, dateRange.firstSelectableDatePath, dateFormat); const usableInfo = validateDateInfo(dateInfo, jurisdiction, dateRange, firstSelectableDate, dateErrorMessage); if (Array.isArray(usableInfo)) throw new Error(`Problem in dateInfo\n${JSON.stringify(usableInfo)}`); return acceptDate(usableInfo, dateRange, dateErrorMessage); } exports.acceptDateForTest = acceptDateForTest; function findFirstSelectableDate(state, firstSelectableDatePath, dateFormat) { if (firstSelectableDatePath === undefined) return undefined; const path = state.context.pathToLens(state.main)(firstSelectableDatePath); const firstSelectableDateAsString = path.getOption(state.main); if (firstSelectableDateAsString === undefined) return undefined; const parsed = (0, exports.parseDate)(`DatePicker.firstSelectableDate`, dateFormat)(firstSelectableDateAsString.toString()); if (Array.isArray(parsed)) { console.error('findFirstSelectableDate', 'path', path, 'dateString', firstSelectableDateAsString, 'result', parsed); return undefined; } return parsed; } function calcInfoTheDatePickerNeeds(id, state, jurisdiction, dateInfo, dateFormat, dateRange, dateErrorMessage, debug) { const actualDateRange = dateRange ? dateRange : {}; const firstSelectableDate = findFirstSelectableDate(state, actualDateRange.firstSelectableDatePath, dateFormat); const usableInfo = validateDateInfo(dateInfo, jurisdiction, actualDateRange, firstSelectableDate, dateErrorMessage); if (Array.isArray(usableInfo)) throw new Error(`Problem in dateInfo\n${JSON.stringify(usableInfo)}`); const dateAcceptor = acceptDate(usableInfo, actualDateRange, dateErrorMessage); let result = { dateAcceptor, dateFilter: noErrorsBooleanFn(acceptDate(usableInfo, actualDateRange, dateErrorMessage)), defaultDate: usableInfo.firstValidDate ? usableInfo.firstValidDate : usableInfo.today, holidays: usableInfo.holidays }; if (debug) console.log('calcInfoTheDatePickerNeeds', id, dateInfo, result); return result; } exports.calcInfoTheDatePickerNeeds = calcInfoTheDatePickerNeeds; function selectedDate(state, dateFormat, defaultScrollToDate) { const dateString = state.optJson(); if (dateString === undefined) return ({ date: undefined, scrollToDate: defaultScrollToDate, selectedDateErrors: [] }); const [selectedDateErrors, date] = errorsAndT((0, exports.parseDate)(``, dateFormat)(dateString)); const scrollToDate = date ? date : defaultScrollToDate; return { date, scrollToDate, selectedDateErrors }; } const defaultDatePickerWithExtraTxs = (txs) => (debug, props) => (eventName, date) => { const { id, state, onChange, parentState, regexForChange } = props; const {} = state.context; const changeTxs = regexForChange === undefined || (date && date.match(regexForChange) !== null) ? (0, labelAndInput_1.makeInputChangeTxs)(id, parentState, onChange) : []; if (debug) console.log('datePicker.defaultDatePickerOnCheck', id, 'date', date); state.massTransform((0, state_1.reasonFor)('DatePicker', eventName, id))([state.optional, () => date], ...txs(props, date), ...changeTxs); }; exports.defaultDatePickerWithExtraTxs = defaultDatePickerWithExtraTxs; exports.defaultDatePickerOnCheck = (0, exports.defaultDatePickerWithExtraTxs)(() => []); function myformat(e, dateFormat) { try { return (0, date_fns_1.format)(e, dateFormat); } catch (e) { return undefined; } } function RawDatePicker(selectFn) { return (props) => { var _a; const { state, jurisdiction, dateInfo, dateRange, name, label, id, mode, readonly, dateFormat, showMonthYearPicker, required, dateErrorMessage } = props; const main = state.main; const debug = (_a = main === null || main === void 0 ? void 0 : main.debug) === null || _a === void 0 ? void 0 : _a.dateDebug; const { defaultDate, dateFilter, holidays, dateAcceptor } = calcInfoTheDatePickerNeeds(id, state, jurisdiction === null || jurisdiction === void 0 ? void 0 : jurisdiction.optJson(), dateInfo === null || dateInfo === void 0 ? void 0 : dateInfo.optJson(), dateFormat, dateRange, Object.assign(Object.assign({}, exports.defaultDateErrorMessage), dateErrorMessage), debug); const { date, selectedDateErrors, scrollToDate } = selectedDate(state, dateFormat, defaultDate); function onChange(e /* probably a date or an array of dates if we are selecting a range (which we aren't)*/) { try { const formattedDate = e === undefined ? undefined : myformat(e, dateFormat); if (debug) console.log('datePicker.onChange', id, 'e', typeof e, e, 'dateFormat', dateFormat, 'formattedDate', formattedDate); (0, CustomError_1.setEdited)(e === null || e === void 0 ? void 0 : e.target, formattedDate); selectFn(debug, props)('onChange', formattedDate); // state.setJson ( formattedDate, reasonFor ( 'DatePicker', 'onChange', id ) ) } catch (err) { console.error("e is", e); throw err; } } function onChangeRaw(e) { var _a, _b; const value = (_a = e.target) === null || _a === void 0 ? void 0 : _a.value; if (value !== undefined) { (0, CustomError_1.setEdited)(e === null || e === void 0 ? void 0 : e.target, (_b = e === null || e === void 0 ? void 0 : e.target) === null || _b === void 0 ? void 0 : _b.value); if (debug) console.log('datePicker.onChangeRaw', id, value, 'changed', e); selectFn(debug, props)('changeRaw', value); } } const value = state.optJson(); function findErrorsFrom(value) { if (!value) return required !== false && !readonly ? [`Date is required`] : []; const valueAsDate = (0, exports.parseDate)(``, dateFormat)(value); if (Array.isArray(valueAsDate)) return valueAsDate; const result = dateAcceptor(valueAsDate); return result; } const errorFromValue = findErrorsFrom(value); const dateError = errorFromValue.length > 0 ? (0, utils_1.unique)(errorFromValue, e => e).join(", ") : undefined; if (debug) console.log('datePicker', id, 'value', value, 'date', date, 'errorFromValue', errorFromValue); const error = selectedDateErrors.length > 0 || (dateError !== undefined); //@ts-ignore - because react doesn't understand currying so thinks this isn't being called inside a function component (0, react_1.useEffect)(() => { const current = document.getElementById(id); if (current === null || current === void 0 ? void 0 : current.setCustomValidity) current.setCustomValidity(error ? dateError : ''); }); return (0, jsx_runtime_1.jsxs)("div", Object.assign({ "data-error": dateError, className: `labelAndDate ${props.labelPosition == 'Horizontal' ? 'd-flex-inline' : ''}` }, { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { state: state, htmlFor: name, label: label }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: `${props.buttons && props.buttons.length > 0 ? 'inputAndButtons' : ''} ` }, { children: [(0, jsx_runtime_1.jsx)(react_datepicker_1.default, { id: id, dateFormat: dateFormat, todayButton: 'Today', openToDate: scrollToDate, selected: error ? undefined : date, onChange: (date) => onChange(date), filterDate: dateFilter, showMonthYearPicker: showMonthYearPicker, highlightDates: holidays, readOnly: mode === 'view' || readonly, className: dateError ? "red-border" : "", closeOnScroll: true, onChangeRaw: onChangeRaw, value: error ? value : undefined, placeholderText: "Select a date" }), (0, makeButtons_1.makeButtons)(props)] })), (0, jsx_runtime_1.jsx)(CustomError_1.CustomError, { id: props.id, validationMessage: dateError, error: error })] })); }; } exports.RawDatePicker = RawDatePicker; function DatePicker(props) { const selectFn = exports.defaultDatePickerOnCheck; return RawDatePicker(selectFn)(props); } exports.DatePicker = DatePicker;