From 078a50e1f98e9b65a7343d805332ec499cec994c Mon Sep 17 00:00:00 2001
From: Seohyunjho
- 상태: {sess.status} · 시작 {new Date(sess.createdAt).toLocaleString("ko-KR")} + 상태: {sess.status} · 시작 {formatKST(sess.createdAt)}
{report && report.status === "ready" ? ( diff --git a/apps/web/components/DashboardHeader.tsx b/apps/web/components/DashboardHeader.tsx index 57baa61..0f79c3a 100644 --- a/apps/web/components/DashboardHeader.tsx +++ b/apps/web/components/DashboardHeader.tsx @@ -27,8 +27,7 @@ export function DashboardHeader({ user }: { user: SessionUser | null }) { - Neuro-Sync{" "} - 의료진 + 의료진용 대시보드- {new Date(msg.createdAt).toLocaleString("ko-KR")} + {formatKST(msg.createdAt)} {msg.inputModality === "voice" ? " · 🎤 음성" : ""}
diff --git a/apps/web/components/ReportActions.tsx b/apps/web/components/ReportActions.tsx index fdd17e0..aee48df 100644 --- a/apps/web/components/ReportActions.tsx +++ b/apps/web/components/ReportActions.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { formatKST } from "../lib/datetime"; import type { HandoffReport } from "../lib/api"; @@ -24,7 +25,7 @@ function toEmrText(report: HandoffReport, patientName: string): string { const L: string[] = []; L.push("[Neuro-Sync 사전 문진 리포트]"); L.push(`환자: ${patientName}`); - if (report.generatedAt) L.push(`생성: ${new Date(report.generatedAt).toLocaleString("ko-KR")}`); + if (report.generatedAt) L.push(`생성: ${formatKST(report.generatedAt)}`); L.push(""); if (report.questionnaires.length) { diff --git a/apps/web/components/RiskEventCard.tsx b/apps/web/components/RiskEventCard.tsx index 5a91611..f8b98c1 100644 --- a/apps/web/components/RiskEventCard.tsx +++ b/apps/web/components/RiskEventCard.tsx @@ -1,4 +1,5 @@ import type { RiskEventOut } from "../lib/api"; +import { formatKST } from "../lib/datetime"; import { riskColor } from "../lib/tokens"; import { RiskBadge } from "./RiskBadge"; @@ -131,7 +132,7 @@ export function RiskEventCard({ event }: { event: RiskEventOut }) {- 시작 {new Date(session.createdAt).toLocaleString("ko-KR")} + 시작 {formatKST(session.createdAt)}
{session.submittedAt ? (- 제출 {new Date(session.submittedAt).toLocaleString("ko-KR")} + 제출 {formatKST(session.submittedAt)}
) : null} diff --git a/apps/web/lib/datetime.ts b/apps/web/lib/datetime.ts new file mode 100644 index 0000000..8620266 --- /dev/null +++ b/apps/web/lib/datetime.ts @@ -0,0 +1,18 @@ +/** + * ISO 시각 문자열을 항상 **한국 시간(KST, Asia/Seoul)**으로 포맷한다. + * + * API는 타임스탬프를 UTC(`...Z`)로 내려주는데, `toLocaleString("ko-KR")`은 실행 + * 런타임의 기본 타임존을 쓴다 — DGX 배포 컨테이너의 Node는 TZ 미설정이라 UTC로 + * 동작해 시각이 9시간 어긋나 보였다(BUG). `timeZone: "Asia/Seoul"`을 명시하면 + * Node/브라우저 내장 ICU가 컨테이너 TZ와 무관하게 KST로 변환하며, 서버(SSR)와 + * 클라이언트가 동일 문자열을 내므로 하이드레이션에도 안전하다. + */ +export function formatKST( + iso: string | null | undefined, + opts?: Intl.DateTimeFormatOptions, +): string { + if (!iso) return "—"; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return String(iso); + return d.toLocaleString("ko-KR", { timeZone: "Asia/Seoul", ...opts }); +} diff --git a/apps/web/lib/tokens.ts b/apps/web/lib/tokens.ts index 59074e5..b8660db 100644 --- a/apps/web/lib/tokens.ts +++ b/apps/web/lib/tokens.ts @@ -67,21 +67,30 @@ export function ctrsFromLevel(level: RiskLevel | null | undefined): CtrsStage { } } +// 중증도 그라데이션: 1(고위험)로 갈수록 강한 위험색, 5(안정)로 갈수록 옅은 중립. +// 1은 solid red + 흰 글씨로 대비 확보(기존엔 배경 CSS 미생성으로 흰 배경에 안 보였음). export const CTRS_META: Record< CtrsStage, { label: string; badge: { bg: string; border: string; text: string; dot: string } } > = { - 1: { label: "고위험", badge: riskBadge.critical }, - 2: { label: "위험", badge: riskBadge.high }, - 3: { label: "주의", badge: riskBadge.medium }, - 4: { label: "관찰", badge: riskBadge.low }, + 1: { + label: "고위험", + badge: { bg: "bg-state-danger", border: "border-state-danger", text: "text-white", dot: "bg-white" }, + }, + 2: { + label: "위험", + badge: { bg: "bg-danger-soft", border: "border-state-danger", text: "text-danger-ink", dot: "bg-state-danger" }, + }, + 3: { + label: "주의", + badge: { bg: "bg-warn-soft", border: "border-warn-line", text: "text-warn", dot: "bg-warn" }, + }, + 4: { + label: "관찰", + badge: { bg: "bg-surface", border: "border-border-strong", text: "text-ink2", dot: "bg-text-secondary" }, + }, 5: { label: "안정", - badge: { - bg: "bg-surface-elevated", - border: "border-border", - text: "text-text-secondary", - dot: "bg-faint", - }, + badge: { bg: "bg-surface-elevated", border: "border-border", text: "text-text-secondary", dot: "bg-faint" }, }, }; diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index 827ae52..cdc0717 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -5,7 +5,10 @@ import type { Config } from "tailwindcss"; * 흰 바탕 · ink 텍스트 · 색은 위험(danger)과 경고(warn)에만. 나머지는 무채색. */ const config: Config = { - content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"], + // lib/ 포함 필수 — tokens.ts의 배지 색 클래스(riskBadge/CTRS_META의 bg-state-danger + // 등)는 여기서만 문자열로 존재한다. 스캔하지 않으면 해당 utility CSS가 생성되지 않아 + // 배경/색이 통째로 빠진다(CTRS 1 흰 배경+흰 글씨로 안 보이던 원인). + content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./lib/**/*.{ts,tsx}"], theme: { extend: { colors: {