방문자 사전신청·승인, 입·출입 체크인/아웃, QR 배지, 재실현황, 블랙리스트, 대시보드 통계, 방문 리포트(엑셀)까지 7단계 전 기능 구현. - backend: Spring Boot 3.4.5 / Java 21 (JDK 26 빌드), 세션 인증, JPA, H2/PostgreSQL, POI, ZXing, Flyway - frontend: React 19 / Vite 6 / TypeScript - infra: Docker Compose (db·app·web nginx), Flyway V1__init, Python 사용자 시드 - docs: 워크플로우 / 시퀀스 다이어그램(Mermaid) / 이슈·유의사항 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { VisitStatus } from './types';
|
|
|
|
export const STATUS_LABEL: Record<VisitStatus, string> = {
|
|
DRAFT: '임시저장',
|
|
PENDING: '승인대기',
|
|
APPROVED: '승인완료',
|
|
REJECTED: '반려',
|
|
CANCELLED: '취소',
|
|
EXPIRED: '만료',
|
|
};
|
|
|
|
/** CSS class suffix for the status badge color. */
|
|
export const STATUS_CLASS: Record<VisitStatus, string> = {
|
|
DRAFT: 'gray',
|
|
PENDING: 'amber',
|
|
APPROVED: 'green',
|
|
REJECTED: 'red',
|
|
CANCELLED: 'gray',
|
|
EXPIRED: 'gray',
|
|
};
|
|
|
|
export function formatDateTime(iso?: string): string {
|
|
if (!iso) return '-';
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return iso;
|
|
return d.toLocaleString('ko-KR', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
const pad = (n: number) => String(n).padStart(2, '0');
|
|
|
|
/** Compact `MM.DD HH:mm` for dense tables. */
|
|
export function formatShort(iso?: string): string {
|
|
if (!iso) return '-';
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return iso;
|
|
return `${pad(d.getMonth() + 1)}.${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
}
|
|
|
|
/** Compact `YYYY.MM.DD HH:mm` for tight table cells. */
|
|
function compact(d: Date): string {
|
|
return `${d.getFullYear()}.${pad(d.getMonth() + 1)}.${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
}
|
|
|
|
/**
|
|
* Compact visit period for table cells.
|
|
* Same day → `2026.07.01 16:29 ~ 16:33`, otherwise → `2026.07.01 16:29 ~ 2026.07.02 09:00`.
|
|
*/
|
|
export function formatVisitRange(fromIso?: string, toIso?: string): string {
|
|
if (!fromIso) return '-';
|
|
const from = new Date(fromIso);
|
|
if (Number.isNaN(from.getTime())) return fromIso;
|
|
if (!toIso) return compact(from);
|
|
const to = new Date(toIso);
|
|
if (Number.isNaN(to.getTime())) return `${compact(from)} ~ ${toIso}`;
|
|
const sameDay =
|
|
from.getFullYear() === to.getFullYear() &&
|
|
from.getMonth() === to.getMonth() &&
|
|
from.getDate() === to.getDate();
|
|
return `${compact(from)} ~ ${sameDay ? `${pad(to.getHours())}:${pad(to.getMinutes())}` : compact(to)}`;
|
|
}
|