Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion messages/en/reservation.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
"complete": {
"title": "Reservation Complete",
"subtitle": "We will confirm it after review.",
"emailNotice": "A confirmation email has been sent. Please check your inbox or spam folder.",
"emailNotice": "You will receive a confirmation email within 24 hours. If you do not see the email, please check your spam folder.",
"missingData": "Reservation details not found.",
"cta": "My Bookings",
"dateTime": "{date} at {time}"
Expand Down
2 changes: 1 addition & 1 deletion messages/ko/reservation.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
"complete": {
"title": "์˜ˆ์•ฝ ์™„๋ฃŒ",
"subtitle": "๊ฒ€ํ†  ํ›„ ํ™•์ • ์•ˆ๋‚ด๋ฅผ ๋“œ๋ฆด๊ฒŒ์š”.",
"emailNotice": "์˜ˆ์•ฝ ํ™•์ธ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๋“œ๋ ธ์–ด์š”. ๋ฐ›์€ํŽธ์ง€ํ•จ ๋˜๋Š” ์ŠคํŒธํ•จ์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.",
"emailNotice": "24์‹œ๊ฐ„ ์ด๋‚ด ์˜ˆ์•ฝ ํ™•์ • ๋ฉ”์ผ์„ ๋ฐ›์•„๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ผ์ด ๋ณด์ด์ง€ ์•Š์œผ๋ฉด ์ŠคํŒธํ•จ์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.",
"missingData": "์˜ˆ์•ฝ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
"cta": "์˜ˆ์•ฝ ๋‚ด์—ญ",
"dateTime": "{date} {time}"
Expand Down
2 changes: 2 additions & 0 deletions src/models/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export interface AdminUserListItem {
age_group?: string | null;
topic_interest?: string[];
phone?: string | null;
referral_source?: string | null;
signup_platform?: 'web' | 'iOS' | null;
marketing_consent: boolean;
created_at?: string | null;
}
Expand Down
6 changes: 6 additions & 0 deletions src/models/reservation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export interface ReservationProgramInfo {
updated_at?: string;
}

export interface ReservationPromotionInfo {
promotion_code?: string | null;
promotion_code_rate?: number | null;
}

export interface ReservationDetail {
id: number;
reservation_code?: string;
Expand All @@ -129,6 +134,7 @@ export interface ReservationDetail {
currency?: string;
payment_amount?: number | null;
display_order_id?: string;
promotion_code_info?: ReservationPromotionInfo | null;
refund_info?: { currency: string; amount: number; status: string } | null;
admin_notes?: string | null;
created_at?: string | null;
Expand Down
51 changes: 51 additions & 0 deletions src/pages/admin/reservations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
AdminReservationDetail,
AdminReservationListStatus,
AvailabilityOption,
ReservationPromotionInfo,
} from '@/models';
import {
useGetAdminReservationDetailQuery,
Expand Down Expand Up @@ -114,6 +115,37 @@ const formatStatus = (value?: string | null) => {
}
};

const formatPromotionRate = (value?: number | null) => {
if (typeof value !== 'number' || !Number.isFinite(value)) return '-';

if (value <= 1) {
return new Intl.NumberFormat('ko-KR', {
style: 'percent',
maximumFractionDigits: 1,
}).format(value);
}

return `${value}%`;
};
Comment on lines +118 to +129
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

value <= 1 ์กฐ๊ฑด์œผ๋กœ ์†Œ์ˆ˜์  ๋น„์œจ(์˜ˆ: 0.1 -> 10%)๊ณผ ์ •์ˆ˜ ๋น„์œจ(์˜ˆ: 10 -> 10%)์„ ๋™์‹œ์— ์ง€์›ํ•˜๋ ค๋Š” ํœด๋ฆฌ์Šคํ‹ฑ์€ ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.\n\n๋งŒ์•ฝ API๊ฐ€ ์ •์ˆ˜ ๋น„์œจ(0100)์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ค์ œ ํ• ์ธ์œจ์ด 1% ๋˜๋Š” 0.5%์ธ ๊ฒฝ์šฐ, value <= 1 ์กฐ๊ฑด์— ๊ฑธ๋ ค ๊ฐ๊ฐ 100%, 50%๋กœ ์ž˜๋ชป ํ‘œ๊ธฐ๋˜๋Š” ์‹ฌ๊ฐํ•œ ๋ฐ์ดํ„ฐ ์™œ๊ณก์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\npromotion_code_rate๊ฐ€ ํ•ญ์ƒ ์†Œ์ˆ˜์  ํ˜•ํƒœ(01)์ธ์ง€, ์•„๋‹ˆ๋ฉด ํ•ญ์ƒ ์ •์ˆ˜ ํ˜•ํƒœ(0~100)์ธ์ง€ ๋ฐ์ดํ„ฐ ํฌ๋งท์„ ๋ช…ํ™•ํžˆ ์ •์˜ํ•˜๊ณ  ํ•˜๋‚˜์˜ ๋ฐฉ์‹์œผ๋กœ๋งŒ ํฌ๋งทํŒ…ํ•˜๋„๋ก ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.


const getReservationPromotionInfo = (
reservation?: AdminReservationDetail | null
): ReservationPromotionInfo | null => {
const candidate = reservation?.promotion_code_info;

if (!candidate) return null;

const hasPromotionCode =
typeof candidate.promotion_code === 'string' && candidate.promotion_code.trim().length > 0;
const hasPromotionRate = typeof candidate.promotion_code_rate === 'number';

if (!hasPromotionCode && !hasPromotionRate) {
return null;
}

return candidate;
};

const normalizeReservationStatus = (value?: string | null) => {
if (!value) return '';
if (value === 'pending') return 'request';
Expand Down Expand Up @@ -267,6 +299,7 @@ export default function AdminReservationsPage() {
);

const selectedReservation = reservationDetailQuery.data?.reservation ?? null;
const selectedReservationPromotionInfo = getReservationPromotionInfo(selectedReservation);
const availableActions = useMemo(
() => getReservationActions(selectedReservation?.status),
[selectedReservation?.status]
Expand Down Expand Up @@ -686,6 +719,24 @@ export default function AdminReservationsPage() {
label="๋Œ€ํ‘œ ๋ฐฉ๋ฌธ์ผ์‹œ"
value={formatDateTime(selectedReservation.visit_date)}
/>
<DetailField
label="ํ”„๋กœ๋ชจ์…˜ ์ ์šฉ"
value={selectedReservationPromotionInfo ? '์ ์šฉ' : '๋ฏธ์ ์šฉ'}
/>
{selectedReservationPromotionInfo && (
<DetailField
label="ํ”„๋กœ๋ชจ์…˜ ์ฝ”๋“œ"
value={selectedReservationPromotionInfo.promotion_code || '-'}
/>
)}
{selectedReservationPromotionInfo && (
<DetailField
label="ํ• ์ธ์œจ"
value={formatPromotionRate(
selectedReservationPromotionInfo.promotion_code_rate
)}
/>
)}
<DetailField label="ํ†ตํ™”" value={selectedReservation.currency || '-'} />
<DetailField
label="์ƒ์„ฑ์ผ"
Expand Down
30 changes: 28 additions & 2 deletions src/pages/admin/users/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ const GENDER_LABELS: Record<string, string> = {
noselect: '๋ฏธ์ž…๋ ฅ',
};

const SIGNUP_PLATFORM_LABELS: Record<string, string> = {
web: 'Web',
ios: 'iOS',
iOS: 'iOS',
};

const formatDate = (value?: string | null) => {
if (!value) return '-';

Expand All @@ -70,6 +76,11 @@ const formatGender = (value?: string | null) => {
return GENDER_LABELS[value] ?? value;
};

const formatSignupPlatform = (value?: string | null) => {
if (!value) return '-';
return SIGNUP_PLATFORM_LABELS[value] ?? value;
};

const buildUserProfileSummary = (user: AdminUserListItem) => {
const parts = [user.country ?? '', formatGender(user.gender), user.age_group ?? ''].filter(
(value) => value.length > 0
Expand Down Expand Up @@ -141,6 +152,9 @@ export default function AdminUsersPage() {
user.email,
user.country,
user.phone,
user.referral_source,
user.signup_platform,
formatSignupPlatform(user.signup_platform),
formatRole(user.role),
formatGender(user.gender),
user.age_group,
Expand Down Expand Up @@ -237,7 +251,7 @@ export default function AdminUsersPage() {
<div css={page}>
<AdminPageHeader
title="ํšŒ์›๊ด€๋ฆฌ"
description="ํšŒ์› ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๊ณ  ๊ตญ๊ฐ€์™€ ๋งˆ์ผ€ํŒ… ๋™์˜ ์—ฌ๋ถ€๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค."
description="ํšŒ์› ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๊ณ  ์œ ์ž…๊ฒฝ๋กœ์™€ ๊ฐ€์ž…๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•œ ์ฃผ์š” ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค."
actions={
<div css={headerActions}>
<button
Expand Down Expand Up @@ -349,6 +363,8 @@ export default function AdminUsersPage() {
<th>ํ”„๋กœํ•„</th>
<th>์—ญํ• </th>
<th>์—ฐ๋ฝ์ฒ˜</th>
<th>์œ ์ž…๊ฒฝ๋กœ</th>
<th>๊ฐ€์ž…๊ฒฝ๋กœ</th>
<th>๋งˆ์ผ€ํŒ…</th>
<th>๊ฐ€์ž…์ผ</th>
<th>์•ก์…˜</th>
Expand Down Expand Up @@ -385,6 +401,16 @@ export default function AdminUsersPage() {
{user.phone || '-'}
</Text>
</td>
<td>
<Text typo="body10" css={secondaryCellText}>
{user.referral_source || '-'}
</Text>
</td>
<td>
<Text typo="body10" css={secondaryCellText}>
{formatSignupPlatform(user.signup_platform)}
</Text>
</td>
<td>
<span css={consentChip(user.marketing_consent)}>
{user.marketing_consent ? '๋™์˜' : '๋ฏธ๋™์˜'}
Expand Down Expand Up @@ -505,7 +531,7 @@ const tableWrap = css`

const table = css`
width: 100%;
min-width: 1180px;
min-width: 1420px;
border-collapse: collapse;

thead tr {
Expand Down
12 changes: 12 additions & 0 deletions src/utils/admin-excel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const USER_GENDER_LABELS: Record<string, string> = {
noselect: '๋ฏธ์ž…๋ ฅ',
};

const USER_SIGNUP_PLATFORM_LABELS: Record<string, string> = {
web: 'Web',
ios: 'iOS',
iOS: 'iOS',
};

const padNumber = (value: number) => String(value).padStart(2, '0');

const getDateStamp = (value = new Date()) =>
Expand Down Expand Up @@ -100,6 +106,8 @@ const formatUserRole = (role: string | null | undefined) =>
formatOptionalText(role ? (USER_ROLE_LABELS[role] ?? role) : role);
const formatUserGender = (gender: string | null | undefined) =>
formatOptionalText(gender ? (USER_GENDER_LABELS[gender] ?? gender) : gender);
const formatUserSignupPlatform = (platform: string | null | undefined) =>
formatOptionalText(platform ? (USER_SIGNUP_PLATFORM_LABELS[platform] ?? platform) : platform);
const formatMarketingConsent = (value: boolean | null | undefined) => (value ? '๋™์˜' : '๋ฏธ๋™์˜');

const toCellText = (value: ExcelCellValue) => String(value);
Expand Down Expand Up @@ -298,6 +306,8 @@ const buildUserWorksheet = (users: AdminUserListItem[]): ExcelWorksheet => ({
'์—ฐ๋ น๋Œ€',
'๊ด€์‹ฌ ์ฃผ์ œ',
'์—ฐ๋ฝ์ฒ˜',
'์œ ์ž…๊ฒฝ๋กœ',
'๊ฐ€์ž…๊ฒฝ๋กœ',
'๋งˆ์ผ€ํŒ… ๋™์˜',
'๊ฐ€์ž…์ผ',
],
Expand All @@ -311,6 +321,8 @@ const buildUserWorksheet = (users: AdminUserListItem[]): ExcelWorksheet => ({
formatOptionalText(user.age_group),
formatListValue(user.topic_interest),
formatOptionalText(user.phone),
formatOptionalText(user.referral_source),
formatUserSignupPlatform(user.signup_platform),
formatMarketingConsent(user.marketing_consent),
formatDateTime(user.created_at),
]),
Expand Down
Loading