chore: initialize insar management system v2
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import flatpickr from 'flatpickr';
|
||||
import 'flatpickr/dist/flatpickr.min.css';
|
||||
|
||||
const ZH_LOCALE = {
|
||||
weekdays: {
|
||||
shorthand: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
longhand: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||
},
|
||||
months: {
|
||||
shorthand: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
longhand: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
},
|
||||
firstDayOfWeek: 1,
|
||||
rangeSeparator: ' 至 ',
|
||||
};
|
||||
const EMPTY_DATE_LIST = [];
|
||||
|
||||
const toIsoDateString = (dateObj) => {
|
||||
if (!(dateObj instanceof Date) || Number.isNaN(dateObj.getTime())) return '';
|
||||
const y = dateObj.getFullYear();
|
||||
const m = String(dateObj.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(dateObj.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
};
|
||||
|
||||
const parseDateLikeValue = (value) => {
|
||||
const text = String(value ?? '').trim();
|
||||
if (!text) return null;
|
||||
|
||||
const compact = text.match(/^(\d{4})(\d{2})(\d{2})$/);
|
||||
if (compact) {
|
||||
const [, y, m, d] = compact;
|
||||
return new Date(Number(y), Number(m) - 1, Number(d));
|
||||
}
|
||||
|
||||
const dashed = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||
if (dashed) {
|
||||
const [, y, m, d] = dashed;
|
||||
return new Date(Number(y), Number(m) - 1, Number(d));
|
||||
}
|
||||
|
||||
const parsed = new Date(text);
|
||||
if (Number.isNaN(parsed.getTime())) return null;
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export default function UnifiedDatePicker({
|
||||
value,
|
||||
onChange,
|
||||
language = 'zh',
|
||||
disabled = false,
|
||||
title = '',
|
||||
ariaLabel = '',
|
||||
className = '',
|
||||
allowClear = true,
|
||||
placeholder = '',
|
||||
minDate = '',
|
||||
maxDate = '',
|
||||
enabledDates = EMPTY_DATE_LIST,
|
||||
}) {
|
||||
const inputRef = useRef(null);
|
||||
const pickerRef = useRef(null);
|
||||
const onChangeRef = useRef(onChange);
|
||||
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
|
||||
const selectedDate = useMemo(() => parseDateLikeValue(value), [value]);
|
||||
const parsedMinDate = useMemo(() => parseDateLikeValue(minDate), [minDate]);
|
||||
const parsedMaxDate = useMemo(() => parseDateLikeValue(maxDate), [maxDate]);
|
||||
|
||||
const enabledDatesKey = useMemo(() => (
|
||||
Array.isArray(enabledDates)
|
||||
? enabledDates
|
||||
.map((item) => String(item ?? '').trim())
|
||||
.filter(Boolean)
|
||||
.join('|')
|
||||
: ''
|
||||
), [enabledDates]);
|
||||
const pickedEnabledDates = useMemo(() => (
|
||||
enabledDatesKey
|
||||
? enabledDatesKey
|
||||
.split('|')
|
||||
.map(parseDateLikeValue)
|
||||
.filter((item) => item instanceof Date && !Number.isNaN(item.getTime()))
|
||||
: EMPTY_DATE_LIST
|
||||
), [enabledDatesKey]);
|
||||
|
||||
const selectedDateKey = useMemo(() => toIsoDateString(selectedDate), [selectedDate]);
|
||||
const minDateKey = useMemo(() => toIsoDateString(parsedMinDate), [parsedMinDate]);
|
||||
const maxDateKey = useMemo(() => toIsoDateString(parsedMaxDate), [parsedMaxDate]);
|
||||
|
||||
const applyAltInputAttrs = useCallback((instance) => {
|
||||
if (!instance?.altInput) return;
|
||||
instance.altInput.placeholder = placeholder || '';
|
||||
instance.altInput.title = title || '';
|
||||
if (ariaLabel) {
|
||||
instance.altInput.setAttribute('aria-label', ariaLabel);
|
||||
} else {
|
||||
instance.altInput.removeAttribute('aria-label');
|
||||
}
|
||||
instance.altInput.disabled = disabled;
|
||||
}, [placeholder, title, ariaLabel, disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) return undefined;
|
||||
|
||||
const pickerOptions = {
|
||||
dateFormat: 'Y-m-d',
|
||||
altInput: true,
|
||||
altInputClass: 'flatpickr-input unified-date-picker-visible-input',
|
||||
altFormat: language === 'en' ? 'Y-m-d' : 'Y年m月d日',
|
||||
locale: language === 'en' ? flatpickr.l10ns.default : ZH_LOCALE,
|
||||
allowInput: true,
|
||||
disableMobile: true,
|
||||
monthSelectorType: 'static',
|
||||
prevArrow: '<span aria-hidden="true">‹</span>',
|
||||
nextArrow: '<span aria-hidden="true">›</span>',
|
||||
clickOpens: !disabled,
|
||||
minDate: parsedMinDate || undefined,
|
||||
maxDate: parsedMaxDate || undefined,
|
||||
onReady: (_, __, fp) => {
|
||||
applyAltInputAttrs(fp);
|
||||
},
|
||||
onOpen: (_, __, fp) => {
|
||||
applyAltInputAttrs(fp);
|
||||
},
|
||||
onChange: (selectedDates) => {
|
||||
const nextDate = Array.isArray(selectedDates) && selectedDates.length > 0
|
||||
? selectedDates[0]
|
||||
: null;
|
||||
onChangeRef.current?.(toIsoDateString(nextDate));
|
||||
},
|
||||
};
|
||||
if (pickedEnabledDates.length > 0) {
|
||||
pickerOptions.enable = pickedEnabledDates;
|
||||
}
|
||||
|
||||
const instance = flatpickr(inputRef.current, pickerOptions);
|
||||
|
||||
pickerRef.current = instance;
|
||||
applyAltInputAttrs(instance);
|
||||
|
||||
return () => {
|
||||
const current = pickerRef.current;
|
||||
if (current && typeof current.destroy === 'function') {
|
||||
current.destroy();
|
||||
}
|
||||
pickerRef.current = null;
|
||||
};
|
||||
}, [
|
||||
language,
|
||||
disabled,
|
||||
parsedMinDate,
|
||||
parsedMaxDate,
|
||||
pickedEnabledDates,
|
||||
minDateKey,
|
||||
maxDateKey,
|
||||
enabledDatesKey,
|
||||
applyAltInputAttrs,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const current = pickerRef.current;
|
||||
if (!current) return;
|
||||
const currentSelectedDate = current.selectedDates && current.selectedDates.length > 0
|
||||
? current.selectedDates[0]
|
||||
: null;
|
||||
const currentValue = toIsoDateString(currentSelectedDate);
|
||||
const nextValue = toIsoDateString(selectedDate);
|
||||
|
||||
if (currentValue === nextValue) {
|
||||
applyAltInputAttrs(current);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDate) {
|
||||
current.setDate(selectedDate, false);
|
||||
} else {
|
||||
current.clear(false);
|
||||
}
|
||||
applyAltInputAttrs(current);
|
||||
}, [selectedDate, selectedDateKey, applyAltInputAttrs]);
|
||||
|
||||
return (
|
||||
<div className={`unified-date-picker ${className}`.trim()}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="unified-date-picker-source-input"
|
||||
disabled={disabled}
|
||||
aria-hidden="true"
|
||||
tabIndex={-1}
|
||||
/>
|
||||
{allowClear && value && (
|
||||
<button
|
||||
type="button"
|
||||
className="unified-date-picker-clear"
|
||||
onClick={() => onChange?.('')}
|
||||
title={language === 'en' ? 'Clear date' : '清空日期'}
|
||||
disabled={disabled}
|
||||
>
|
||||
{language === 'en' ? 'Clear' : '清空'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user