@focuson/form_components
Version:
Components that can be used by @focuson/forms
270 lines (269 loc) • 14.9 kB
JavaScript
;
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;