From dcba974aa45f3fcf8ba900589cb6dfb1ce0c5354 Mon Sep 17 00:00:00 2001 From: hided62 Date: Thu, 13 Aug 2026 15:26:53 +0000 Subject: [PATCH] fix(frontend): standardize server time display --- app/game-frontend/e2e/board.spec.ts | 26 ++- app/game-frontend/src/utils/legacyDateTime.ts | 26 +-- app/game-frontend/src/views/AuctionView.vue | 23 +-- .../src/views/BattleCenterView.vue | 17 +- app/game-frontend/src/views/BettingView.vue | 8 +- app/game-frontend/src/views/BoardView.vue | 16 +- .../src/views/DynastyDetailView.vue | 26 ++- app/game-frontend/src/views/InheritView.vue | 9 +- app/game-frontend/src/views/MainView.vue | 11 +- app/game-frontend/src/views/MyPageView.vue | 7 +- .../src/views/NationSecretView.vue | 7 +- app/game-frontend/src/views/SurveyView.vue | 12 +- .../src/views/TournamentView.vue | 8 +- app/game-frontend/src/views/TrafficView.vue | 10 +- app/game-frontend/src/views/TroopView.vue | 6 +- .../e2e/server-operations.spec.ts | 8 +- .../src/views/AccountView.vue | 5 +- app/gateway-frontend/src/views/AdminView.vue | 67 ++++---- app/gateway-frontend/src/views/LobbyView.vue | 4 +- .../src/views/ServerOperationsView.vue | 28 +--- packages/common/src/index.ts | 1 + packages/common/src/time/ServerDateTime.ts | 158 ++++++++++++++++++ packages/common/test/server-date-time.test.ts | 44 +++++ 23 files changed, 352 insertions(+), 175 deletions(-) create mode 100644 packages/common/src/time/ServerDateTime.ts create mode 100644 packages/common/test/server-date-time.test.ts diff --git a/app/game-frontend/e2e/board.spec.ts b/app/game-frontend/e2e/board.spec.ts index 0a027abf..183f2aa3 100644 --- a/app/game-frontend/e2e/board.spec.ts +++ b/app/game-frontend/e2e/board.spec.ts @@ -274,6 +274,8 @@ test('matches the ref meeting-room geometry, typography, textures, and controls' 'src', 'https://sam-image.hided.net/icons/22.jpg' ); + await expect(page.locator('.article-header .date')).toHaveText('07-26 19:20'); + await expect(page.locator('.comment-row .date')).toHaveText('07-26 19:25'); if (artifactRoot) { await page.screenshot({ path: resolve(artifactRoot, 'board-core-desktop.png'), @@ -392,7 +394,9 @@ test('uses the ref 500px responsive form widths', async ({ page }) => { await expect(page.getByRole('heading', { name: '기밀실' })).toBeVisible(); }); -test('retains article and comment input after a failed mutation, then reloads after success', async ({ page }, testInfo) => { +test('retains article and comment input after a failed mutation, then reloads after success', async ({ + page, +}, testInfo) => { const state: BoardFixture = { permission: 2, canMeeting: true, @@ -408,7 +412,9 @@ test('retains article and comment input after a failed mutation, then reloads af await page.locator('#board-title').fill('새 제목'); await page.locator('#board-content').fill('새 내용'); await page.locator('#submitArticle').click(); - const articleToast = page.getByTestId('game-toast').filter({ hasText: '게시물 등록에 실패했습니다: 접속 제한입니다.' }); + const articleToast = page + .getByTestId('game-toast') + .filter({ hasText: '게시물 등록에 실패했습니다: 접속 제한입니다.' }); await expect(articleToast).toHaveAttribute('data-feedback-kind', 'error'); await expect(articleToast).toHaveAttribute('role', 'alert'); await expect(page.locator('#board-title')).toHaveValue('새 제목'); @@ -416,7 +422,13 @@ test('retains article and comment input after a failed mutation, then reloads af const desktopToastGeometry = await articleToast.evaluate((element) => { const rect = element.getBoundingClientRect(); - return { left: rect.left, right: rect.right, top: rect.top, width: rect.width, viewportWidth: window.innerWidth }; + return { + left: rect.left, + right: rect.right, + top: rect.top, + width: rect.width, + viewportWidth: window.innerWidth, + }; }); expect(desktopToastGeometry.left).toBeGreaterThanOrEqual(0); expect(desktopToastGeometry.right).toBeLessThanOrEqual(desktopToastGeometry.viewportWidth); @@ -435,10 +447,14 @@ test('retains article and comment input after a failed mutation, then reloads af await page.setViewportSize({ width: 390, height: 844 }); const documentWidthBeforeToast = await page.evaluate(() => document.documentElement.scrollWidth); await commentInput.press('Enter'); - const commentToast = page.getByTestId('game-toast').filter({ hasText: '댓글 등록에 실패했습니다: 접속 제한입니다.' }); + const commentToast = page + .getByTestId('game-toast') + .filter({ hasText: '댓글 등록에 실패했습니다: 접속 제한입니다.' }); await expect(commentToast).toBeVisible(); await expect - .poll(async () => commentToast.evaluate((element) => window.innerHeight - element.getBoundingClientRect().bottom)) + .poll(async () => + commentToast.evaluate((element) => window.innerHeight - element.getBoundingClientRect().bottom) + ) .toBeGreaterThanOrEqual(0); const mobileToastGeometry = await commentToast.evaluate((element) => { const rect = element.getBoundingClientRect(); diff --git a/app/game-frontend/src/utils/legacyDateTime.ts b/app/game-frontend/src/utils/legacyDateTime.ts index 3b89fd7e..38ea3b55 100644 --- a/app/game-frontend/src/utils/legacyDateTime.ts +++ b/app/game-frontend/src/utils/legacyDateTime.ts @@ -1,24 +1,6 @@ -const KOREA_TIME_OFFSET_MS = 9 * 60 * 60 * 1000; +import { formatServerDateTime } from '@sammo-ts/common'; -const pad = (value: number): string => String(value).padStart(2, '0'); +export const formatSeoulDateTime = (value: string | Date): string => formatServerDateTime(value); -export const formatSeoulDateTime = (value: string | Date): string => { - if ( - typeof value === 'string' && - !/(?:Z|[+-]\d{2}:?\d{2})$/i.test(value.trim()) - ) { - return value.trim().replace('T', ' ').slice(0, 19); - } - const date = value instanceof Date ? value : new Date(value); - if (Number.isNaN(date.getTime())) { - return typeof value === 'string' ? value.slice(0, 19) : ''; - } - const koreaTime = new Date(date.getTime() + KOREA_TIME_OFFSET_MS); - return `${koreaTime.getUTCFullYear()}-${pad(koreaTime.getUTCMonth() + 1)}-${pad( - koreaTime.getUTCDate() - )} ${pad(koreaTime.getUTCHours())}:${pad(koreaTime.getUTCMinutes())}:${pad( - koreaTime.getUTCSeconds() - )}`; -}; - -export const formatSeoulHourMinute = (value: string | Date): string => formatSeoulDateTime(value).slice(11, 16); +export const formatSeoulHourMinute = (value: string | Date): string => + formatServerDateTime(value, { format: 'hourMinute' }); diff --git a/app/game-frontend/src/views/AuctionView.vue b/app/game-frontend/src/views/AuctionView.vue index dfb03396..fa3c4c58 100644 --- a/app/game-frontend/src/views/AuctionView.vue +++ b/app/game-frontend/src/views/AuctionView.vue @@ -1,4 +1,5 @@