106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
/** 백엔드의 양 끝 날짜 포함 최대 366일 계약을 기본값과 조회 검증에서 함께 사용한다. */
|
|
export const WATER_RISK_MAX_SEARCH_DAYS = 366;
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
/** 날짜 입력을 UTC 일자 값으로 읽어 시간대와 일광절약시간 영향을 제거한다. */
|
|
const parseDate = (value: string): number | null => {
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
const timestamp = Date.parse(`${value}T00:00:00Z`);
|
|
return Number.isFinite(timestamp) &&
|
|
new Date(timestamp).toISOString().slice(0, 10) === value
|
|
? timestamp
|
|
: null;
|
|
};
|
|
|
|
/** 선택 종료일을 포함해 서버가 허용하는 가장 이른 시작일이다. */
|
|
export const getWaterRiskMinStartDate = (
|
|
endDate: string,
|
|
): string | undefined => {
|
|
const end = parseDate(endDate);
|
|
return end === null
|
|
? undefined
|
|
: new Date(end - (WATER_RISK_MAX_SEARCH_DAYS - 1) * DAY_MS)
|
|
.toISOString()
|
|
.slice(0, 10);
|
|
};
|
|
|
|
/**
|
|
* 시간 단위 검색 입력(`datetime-local`, `step=3600`)의 값 형식 `yyyy-MM-ddTHH:00`이다.
|
|
* 분은 항상 00이며, 첫 번째 그룹은 날짜(yyyy-MM-dd)다.
|
|
*/
|
|
const HOUR_VALUE_PATTERN = /^(\d{4}-\d{2}-\d{2})T\d{2}:00$/;
|
|
|
|
/**
|
|
* `datetime-local` 입력값의 분을 00으로 맞춘다.
|
|
* 브라우저는 값의 분이 00이면 분 칸을 고정하지만, 빈 값에서 처음 입력하거나 직접 입력해 분이 들어온 경우는
|
|
* 스스로 고치지 않으므로 저장 전에 시간 단위로 내린다. 형식이 아니면(빈 값 포함) 그대로 둔다.
|
|
*/
|
|
export const toWaterRiskHourValue = (value: string) =>
|
|
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)
|
|
? `${value.slice(0, 13)}:00`
|
|
: value;
|
|
|
|
/** 로컬 시각을 시간 단위 입력값(yyyy-MM-ddTHH:00)으로 내린다. 기본 종료값과 미래 시각 제한에 쓴다. */
|
|
export const formatWaterRiskHourValue = (date: Date) => {
|
|
const pad = (value: number) => String(value).padStart(2, "0");
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:00`;
|
|
};
|
|
|
|
/** 시간 단위 입력값을 API 정시 일시(yyyy-MM-ddTHH:00:00)로 바꾼다. 빈 값은 빈 문자열이다. */
|
|
export const toWaterRiskApiDateTime = (value: string) =>
|
|
value ? `${value}:00` : "";
|
|
|
|
/**
|
|
* 시간 단위 기간을 백엔드 계약(시작·종료 함께, 정시, 시작 ≤ 종료, 날짜 기준 최대 366일)과
|
|
* 화면 조건(종료 ≤ 현재 정시)으로 검증한다. 오류가 없으면 빈 문자열을 반환한다.
|
|
* `required`가 아니면 시작·종료를 모두 비웠을 때 조건 없이 조회한다.
|
|
*/
|
|
const validateHourPeriod = (
|
|
from: string,
|
|
to: string,
|
|
currentHourValue: string,
|
|
{ label, required }: { label: string; required: boolean },
|
|
): string => {
|
|
if (!from && !to) {
|
|
return required ? `${label} 시작과 종료를 입력해야 합니다.` : "";
|
|
}
|
|
if (!from || !to) {
|
|
return required
|
|
? `${label} 시작과 종료를 입력해야 합니다.`
|
|
: `${label}은 시작과 종료를 모두 입력하거나 모두 비워야 합니다.`;
|
|
}
|
|
const fromMatch = HOUR_VALUE_PATTERN.exec(from);
|
|
const toMatch = HOUR_VALUE_PATTERN.exec(to);
|
|
const start = fromMatch ? parseDate(fromMatch[1]!) : null;
|
|
const end = toMatch ? parseDate(toMatch[1]!) : null;
|
|
if (start === null || end === null) return `올바른 ${label}을 입력해 주세요.`;
|
|
if (to > currentHourValue) return `${label} 종료는 현재 시각보다 늦을 수 없습니다.`;
|
|
if (from > to) return `${label} 시작은 종료보다 늦을 수 없습니다.`;
|
|
if ((end - start) / DAY_MS + 1 > WATER_RISK_MAX_SEARCH_DAYS) {
|
|
return `${label}은 최대 1년(366일)까지 조회할 수 있습니다.`;
|
|
}
|
|
return "";
|
|
};
|
|
|
|
/** 과거이력 강우시각(강우 시작 일시) 기간을 검증한다. 필수 조건이다. */
|
|
export const validateWaterRiskRainStartedPeriod = (
|
|
from: string,
|
|
to: string,
|
|
currentHourValue: string,
|
|
) =>
|
|
validateHourPeriod(from, to, currentHourValue, {
|
|
label: "강우시각",
|
|
required: true,
|
|
});
|
|
|
|
/** 과거이력 분석시각 기간을 검증한다. 선택 조건이며 모두 비우면 적용하지 않는다. */
|
|
export const validateWaterRiskAnalysisPeriod = (
|
|
from: string,
|
|
to: string,
|
|
currentHourValue: string,
|
|
) =>
|
|
validateHourPeriod(from, to, currentHourValue, {
|
|
label: "분석시각",
|
|
required: false,
|
|
});
|