chore: initialize insar management system v2

This commit is contained in:
2026-04-14 13:16:01 +08:00
commit ecc72ec9cd
361 changed files with 2142522 additions and 0 deletions
@@ -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>
);
}