feat: 오픈 게임 취소와 유산 정산 경로 추가

별도 최고위험 권한으로 실행되는 원자적 취소 작업을 추가한다. 버려진 게임과 장수 기록의 보존·삭제 옵션, 오픈 원금 전액 환급 및 획득 포인트 보전율, 취소 상태와 관리자·과거기록 UI를 함께 반영한다.
This commit is contained in:
2026-08-18 13:31:49 +00:00
parent a7b11811de
commit 383d173790
36 changed files with 1967 additions and 83 deletions
+22 -4
View File
@@ -20,6 +20,9 @@ type ArchiveSeason = Archive['seasons'][number] & {
source?: string;
openedAt?: string | null;
date?: string | null;
status?: 'OPEN' | 'COMPLETED' | 'ABANDONED' | 'LEGACY';
cancellationId?: string | null;
cancelledAt?: string | null;
};
type ArchiveGeneral = {
@@ -129,6 +132,12 @@ const formatOpenedAt = (season: ArchiveSeason): string => {
if (Number.isNaN(date.getTime())) return '개장일 미상';
return `${new Intl.DateTimeFormat('ko-KR', { dateStyle: 'medium', timeZone: 'Asia/Seoul' }).format(date)} 개장`;
};
const archiveLabel = (season: ArchiveSeason): string =>
season.status === 'ABANDONED' ? '취소 게임' : '이전 서버 기록';
const archiveIdentifier = (season: ArchiveSeason): string =>
season.status === 'ABANDONED' && season.cancellationId
? `취소 ID ${season.cancellationId.slice(0, 8)}`
: season.serverId;
const selectGeneral = async (season: ArchiveSeason, generalNo: number): Promise<void> => {
const key = detailKey(season, generalNo);
@@ -197,7 +206,7 @@ onMounted(() => {
</nav>
</header>
<p class="page-note">이전 서버에서 종료된 기수 보관된 장수 기록입니다.</p>
<p class="page-note">종료된 기수 관리자가 보존한 취소 게임의 장수 기록입니다.</p>
<p v-if="error" class="error-row">{{ error }}</p>
<p v-else-if="loading && !archive" class="empty-row">불러오는 중...</p>
<p v-else-if="archive?.seasons.length === 0" class="empty-row">보관된 지난 플레이가 없습니다.</p>
@@ -205,15 +214,19 @@ onMounted(() => {
<section v-for="season in archive?.seasons ?? []" :key="detailKey(season, 0)" class="season-card">
<div class="season-heading legacy-bg2">
<div class="season-identity">
<strong class="archive-label">이전 서버 기록</strong>
<strong class="archive-label" :class="{ abandoned: season.status === 'ABANDONED' }">
{{ archiveLabel(season) }}
</strong>
<strong>{{ seasonSourceProfile(season) }}</strong>
<span>{{ season.serverId }}</span>
<span>{{ archiveIdentifier(season) }}</span>
</div>
<div class="season-meta">
<span>{{ formatOpenedAt(season) }}</span>
<span>
{{ season.scenarioName ?? '시나리오 미상' }}
<template v-if="season.season !== null"> · {{ season.season }}</template>
<template v-if="season.status !== 'ABANDONED' && season.season !== null">
· {{ season.season }}
</template>
</span>
</div>
</div>
@@ -425,6 +438,11 @@ onMounted(() => {
background: #00582c;
}
.archive-label.abandoned {
border-color: #956f38;
background: #66471f;
}
.season-meta {
justify-content: flex-end;
}