{siteConfig.name}
diff --git a/apps/web/app/stamp/page.tsx b/apps/web/app/stamp/page.tsx index 462d1c7cd4..15724d068c 100644 --- a/apps/web/app/stamp/page.tsx +++ b/apps/web/app/stamp/page.tsx @@ -874,6 +874,8 @@ export default function StampPage() { ref={mainScrollRef} className="h-full overflow-y-auto flex flex-col [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']" data-stamp-scroll-container="true" + data-layout-primitives="list-detail stack" + data-scroll-owner="stamp-list" onScroll={handleMainScroll} onTouchStart={stampBottomNavAutoHide.onTouchStart} onTouchMove={stampBottomNavAutoHide.onTouchMove} @@ -930,7 +932,7 @@ export default function StampPage() { > {activeFilterCount > 0 && ( - + {activeFilterCount} )} diff --git a/apps/web/components/admin/AdminConsoleOverview.tsx b/apps/web/components/admin/AdminConsoleOverview.tsx index f6e06cc151..6f91ad9957 100644 --- a/apps/web/components/admin/AdminConsoleOverview.tsx +++ b/apps/web/components/admin/AdminConsoleOverview.tsx @@ -3004,7 +3004,7 @@ const adminDashboardFocusPalette = { const adminDashboardControlGroupClassName = "inline-flex h-7 shrink-0 items-center rounded-full border border-border bg-muted/25 p-0.5"; const adminDashboardControlButtonClassName = - "inline-flex h-6 shrink-0 items-center justify-center gap-1 rounded-full px-2 text-[10px] font-extrabold leading-none transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"; + "inline-flex h-6 shrink-0 items-center justify-center gap-1 rounded-full px-2 text-2xs font-extrabold leading-none transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"; const adminDashboardFullscreenCardClassName = "fixed inset-2 z-[80] h-[calc(100dvh-1rem)] max-h-[calc(100dvh-1rem)] overflow-auto scrollbar-hide rounded-3xl border-primary/35 bg-card p-4 shadow-2xl sm:inset-4 sm:h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-2rem)]"; @@ -3098,7 +3098,7 @@ function AdminDashboardTooltipPanel({ {row.value} {row.note ? ( - + {row.note} ) : null} @@ -3107,7 +3107,7 @@ function AdminDashboardTooltipPanel({전체 {selectedCount.toLocaleString()}개
{treemapContextText}
{treemapLegendText}
@@ -1525,7 +1525,11 @@ export default function InsightsClient({ embedded = false }: { embedded?: boolea } return ( -{(userProfile?.qualityScore ?? 0).toFixed(1)} - + 점
@@ -1294,7 +1294,7 @@ export default function ProfilePage() { aria-hidden="true" />+
{footer}
) : null} @@ -3360,7 +3360,7 @@ function AdminDashboardFullscreenButton({ function AdminDashboardDiagnosisMeta({ periodLabel }: { periodLabel: string }) { return (KPI 데이터를 불러오는 중입니다. 모바일에서는 핵심 카드부터 순서대로 표시됩니다. @@ -4157,7 +4157,7 @@ function AdminDashboardCardTitle({ data-admin-dashboard-card-title-row="single-line" >
+
{title}
{metric ? (
@@ -4326,7 +4326,7 @@ function AdminDashboardKpiCard({
data-admin-dashboard-kpi-title-row="single-line"
>
+
{title}
+
{caption}
+
{insight.description}
기간 설정
-+
KPI와 차트에 적용할 조회 기간을 선택합니다.
데이터 수집 상태
-+
Supabase 저장 기준 ·{" "} {logs?.workflow.workflowId ?? "youtube-kpi-snapshot.yml"}
@@ -5754,7 +5754,7 @@ function AdminDashboardCollectionLogPopover({ type="button" variant="ghost" size="sm" - className="h-11 min-h-11 min-w-[44px] shrink-0 rounded-full px-2 text-[11px] font-bold md:h-7 md:min-h-0 md:min-w-0" + className="h-11 min-h-11 min-w-[44px] shrink-0 rounded-full px-2 text-2xs font-bold md:h-7 md:min-h-0 md:min-w-0" disabled={isFetching} onClick={onRefresh} > @@ -5795,7 +5795,7 @@ function AdminDashboardCollectionLogPopover({ ? "최근 스냅샷 저장됨" : "저장된 스냅샷 없음"} -+
{hasSavedSnapshot
? `마지막 저장: ${formatDashboardDateTime(snapshot?.fetchedAt)}`
: "수집 결과가 아직 저장되지 않았습니다."}
@@ -5804,7 +5804,7 @@ function AdminDashboardCollectionLogPopover({
GitHub Actions 로그
-+
{logs?.workflow.available
? latestRun
? `최근 실행: #${latestRun.runNumber ?? "—"} · ${formatDashboardDateTime(latestRun.startedAt ?? latestRun.createdAt)}`
@@ -5866,7 +5866,7 @@ function AdminDashboardCollectionLogPopover({
+
서버 환경 변수에 GitHub 읽기 토큰이 없거나 권한이
부족합니다. GITHUB_ACTIONS_TOKEN 또는 GH_TOKEN을 설정하면
실행 로그도 표시됩니다.
@@ -5904,7 +5904,7 @@ function AdminDashboardCollectionLogPopover({
-
+
판정 기준: 최신 스냅샷이 저장되어 있으면 대시보드 데이터 수집은
정상으로 봅니다.
KPI 데이터를 불러오는 중입니다. 모바일에서는 핵심 카드부터 순서대로 표시됩니다. @@ -7333,7 +7365,7 @@ function AdminDashboardManagementPanel({ {isDashboardOrderEditorOpen ? (
{dashboardOrderMessage}
@@ -7392,11 +7424,11 @@ function AdminDashboardManagementPanel({
)}
style={getDashboardCardOrderStyle("views")}
reorderProps={getDashboardCardReorderProps("views")}
- value={isChartLoading ? "—" : formatNumber(periodViewDisplayValue)}
- caption={`${periodMetricCaption} · 현재 전체 누적 ${formatNumber(cumulativeViewValue)}`}
- delta={formatDashboardChangeLabel(viewChange)}
+ value={isChartLoading ? "—" : formatNumber(viewCardValue)}
+ caption={`${videoPeriodDeltasAreFlat ? `${selectedPeriodLabel} · 채널 조회 순증` : periodMetricCaption} · 현재 전체 누적 ${formatNumber(channelStats?.viewCount ?? cumulativeViewValue)}`}
+ delta={formatDashboardChangeLabel(viewCardChange)}
deltaLabel="기간 대비"
- progress={getDashboardChangeProgress(viewChange)}
+ progress={getDashboardChangeProgress(viewCardChange)}
tone="sky"
emphasis="primary"
sparklineData={viewSparklineDisplayPoints}
@@ -8191,7 +8223,7 @@ function AdminSidebar({
className={cn(
"group relative flex min-w-0 items-center gap-2 overflow-hidden whitespace-nowrap border text-left transition touch-manipulation focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
isDropdown
- ? "min-h-9 w-full rounded-lg px-2 py-1.5 text-[13px]"
+ ? "min-h-9 w-full rounded-lg px-2 py-1.5 text-sm"
: "min-h-9 w-full rounded-lg px-2 py-1 text-sm",
!isDropdown &&
isCollapsed &&
@@ -8218,31 +8250,20 @@ function AdminSidebar({
-
+
{item.title}
- {item.badge && (
-
- {item.badge}
-
- )}
{itemStatus && (
순서 편집을 켜야 이동 버튼이 활성화됩니다.
@@ -8362,7 +8383,7 @@ function AdminSidebar({
data-admin-sidebar-order-section="compact"
>
{sidebarOrderMessage}
@@ -8533,7 +8554,7 @@ function AdminSidebar({
+
표시 모드
{renderThemeControls("sidebar", { compact: false })} @@ -8678,7 +8699,7 @@ function AdminSidebar({ {accountDisplayName} - + 계정·표시·메뉴 설정 @@ -8726,7 +8747,7 @@ function AdminSidebar({관리자 콘솔
-+
현재 화면 · {activeSidebarLabel}
관리자 콘솔
-+
현재 화면 · {activeSidebarLabel}
{event.status}
-
+
- 감사 ID
- {event.id} @@ -10258,7 +10279,7 @@ function AdminYoutubeThumbnailModuleLoadingSkeleton() {
- • {blocker} ))} @@ -1443,7 +1443,7 @@ function AdminMapInfoPanel({
- {label} +
- {label}
- {children}
- previewHash
- {previewQuery.data.confirmation.previewHash} @@ -308,7 +308,7 @@ export function TrendProposalQueue() {
+
선택한 마커
-
+
{selectedRestaurant.category ?? "카테고리 확인"}
@@ -1267,7 +1267,7 @@ function AdminMapInfoPanel({
) : (
-
+
선택한 마커
-
+
촬영 동선
{isLoading ? (
@@ -1370,7 +1370,7 @@ function AdminMapInfoPanel({
{preset.label}
-
+
{preset.description}
@@ -1411,7 +1411,7 @@ function AdminMapInfoPanel({
data-admin-route-readiness-panel="local-heuristic"
>
-
+
동선 준비도
-
+
로컬 후보 품질, 도로 경로 확보, 좌표 데이터 충분성 기준의
준비도입니다.
{routeModeOption
@@ -1434,7 +1434,7 @@ function AdminMapInfoPanel({
: null}
{routeAhpAssessment.blockers.length > 0 && (
-
+
{routeAhpAssessment.blockers.map((blocker) => (
@@ -1478,7 +1478,7 @@ function AdminMapInfoPanel({
{directionsStatus === "fallback" && directionsFallbackMessage ? (
@@ -1489,7 +1489,7 @@ function AdminMapInfoPanel({
{routePlan.warnings.length > 0 ? (
동선 주의사항
@@ -1525,7 +1525,7 @@ function AdminMapInfoPanel({
{restaurant.category ?? "카테고리 확인"}
{index > 0 && routePlan.legs[index - 1] && (
-
+
이전 지점에서{" "}
{routePlan.legs[index - 1].distanceKm.toFixed(1)}km ·
약{" "}
@@ -1546,7 +1546,7 @@ function AdminMapInfoPanel({
데이터가 부족하면 임의 동선을 만들지 않습니다.
)}
-
+
{directionsStatus === "ready"
? "네이버 Directions 5 기준 실제 도로 주행 경로를 지도에 표시합니다."
: routeMode === "driving"
@@ -1554,7 +1554,7 @@ function AdminMapInfoPanel({
: "네이버 Directions 5는 자동차만 지원하므로 도보·혼합은 근거리 촬영 초안으로 표시합니다."}
{adminRestaurantStatusChips.map((chip) => (
-
+
{chip}
))}
@@ -1151,7 +1151,7 @@ export function AdminRestaurantModal({
{adminRestaurantProgressSteps.map((step) => (
-
+
{step.label}
@@ -1178,7 +1178,7 @@ export function AdminRestaurantModal({
{description}
{badge && (
-
+
{badge}
)}
diff --git a/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx b/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
index f5a0ff2b67..1f7f2ba275 100644
--- a/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
+++ b/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
@@ -200,7 +200,7 @@ function ManagementStatusSummary({ items }: { items: StatusSummaryItem[] }) {
function RefreshWorkflowSteps() {
return (
-
+
{[
"1. 승인 맛집 스냅샷 수집",
"2. 외부 후보와 현재값 비교",
@@ -526,13 +526,13 @@ function RefreshCandidateDetailPanel({
{readbackLabel(selectedCandidate.readback_state)}
{selectedCandidate.readback_state.checked_at ? (
-
+
{formatDate(selectedCandidate.readback_state.checked_at)}
) : null}
근거:{" "}
@@ -571,7 +571,7 @@ function RefreshCandidateDetailPanel({
/>
승인과 동시에 현재 맛집 값 guarded apply
-
+
상호·전화·주소·좌표 변경 후보만 적용됩니다. 폐업 의심 후보는
자동 적용할 수 없습니다.
diff --git a/apps/web/components/admin/AdminReviewPanel.tsx b/apps/web/components/admin/AdminReviewPanel.tsx
index 42c68aefcf..b0c4c948d1 100644
--- a/apps/web/components/admin/AdminReviewPanel.tsx
+++ b/apps/web/components/admin/AdminReviewPanel.tsx
@@ -698,7 +698,7 @@ export default function AdminReviewPanel({ isOpen, onClose, onToggleCollapse, is
-
+
{selectedReview.profiles?.nickname?.[0] || '익'}
@@ -780,7 +780,7 @@ const ReviewCard = forwardRef(
-
+
{review.profiles?.nickname?.[0] || '익'}
diff --git a/apps/web/components/admin/AdminUsersPanel.tsx b/apps/web/components/admin/AdminUsersPanel.tsx
index 057cc468f4..63a3d6b4b2 100644
--- a/apps/web/components/admin/AdminUsersPanel.tsx
+++ b/apps/web/components/admin/AdminUsersPanel.tsx
@@ -123,7 +123,7 @@ function SummaryMetric({ label, value, tone = "default", isLoading = false }: {
)}
data-admin-users-summary-metric={label}
>
- {label}
+ {label}
{isLoading ? : value}
@@ -384,7 +384,7 @@ export default function AdminUsersPanel() {
{['관리자 확인 필수', '자기 잠금 방지', '상태 재확인', '삭제 대신 비활성화'].map((label) => (
- {label}
+ {label}
))}
diff --git a/apps/web/components/admin/CategorySidebar.tsx b/apps/web/components/admin/CategorySidebar.tsx
index 064e93e521..9ba2d9cb4a 100644
--- a/apps/web/components/admin/CategorySidebar.tsx
+++ b/apps/web/components/admin/CategorySidebar.tsx
@@ -4,11 +4,13 @@ interface CategorySidebarProps {
stats: CategoryStats;
selectedStatuses: EvaluationRecordStatus[];
onSelectStatuses: (statuses: EvaluationRecordStatus[]) => void;
+ showStatusChips?: boolean;
children?: React.ReactNode;
}
export function CategorySidebar({
stats,
+ showStatusChips = true,
children,
}: CategorySidebarProps) {
// 그룹 1: 전체, 미처리, 승인됨, 삭제 (통계만 표시)
@@ -23,7 +25,7 @@ export function CategorySidebar({
const statCategoriesGroup2 = [
{ label: '승인 대기', count: stats.ready_for_approval },
{ label: '미확정 좌표', count: stats.unconfirmed_map },
- { label: 'Missing', count: stats.missing },
+ { label: '누락', count: stats.missing },
{ label: '평가 미대상', count: stats.not_selected },
];
const statCategoriesDesktop = [...statCategoriesGroup1, ...statCategoriesGroup2];
@@ -46,11 +48,13 @@ export function CategorySidebar({
)}
-
- {statCategoriesDesktop.map((category, index) =>
- renderStatChip(category.label, category.count, `desktop-${index}`)
- )}
-
+ {showStatusChips ? (
+
+ {statCategoriesDesktop.map((category, index) =>
+ renderStatChip(category.label, category.count, `desktop-${index}`)
+ )}
+
+ ) : null}
);
diff --git a/apps/web/components/admin/EvaluationDetailView.tsx b/apps/web/components/admin/EvaluationDetailView.tsx
index 79a4e648c8..72e293334e 100644
--- a/apps/web/components/admin/EvaluationDetailView.tsx
+++ b/apps/web/components/admin/EvaluationDetailView.tsx
@@ -109,7 +109,7 @@ function NumberBadge({ index, tone }: { index: number; tone: EvaluationTone }) {
@@ -163,7 +163,7 @@ function SourceNameRow({
{label}
{value}
-
+
{provider}
@@ -185,21 +185,21 @@ function BooleanBadge({ value, rerunNeeded = false }: { value: boolean | null |
if (value === undefined || value === null) {
if (!rerunNeeded) {
return (
-
+
-
);
}
return (
-
+
-
);
}
return (
-
+
{value ? '일치' : '불일치'}
);
@@ -208,7 +208,7 @@ function BooleanBadge({ value, rerunNeeded = false }: { value: boolean | null |
function InfoItem({ label, children, className }: { label: string; children: React.ReactNode; className?: string }) {
return (
-
);
@@ -389,7 +389,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
- 승인됨 · {record.approved_name} : null}>
+ 승인됨 · {record.approved_name} : null}>
{addressConsistency.label}}
+ value={{addressConsistency.label} }
>
{addressConsistency.headline}
@@ -474,7 +474,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
운영 분류
{addressGuidance.label}
@@ -491,7 +491,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
= 98
? 'border-emerald-300 bg-emerald-50 text-emerald-700'
: 'border-sky-300 bg-background/70 text-sky-700',
@@ -508,7 +508,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
{addressAhp.evidenceFamilies.length > 0 && (
{addressAhp.evidenceFamilies.map((family) => (
-
+
{family}
))}
@@ -523,7 +523,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
{signal.label}
diff --git a/apps/web/components/admin/EvaluationSlideView.tsx b/apps/web/components/admin/EvaluationSlideView.tsx
index 3ece5fd8f0..23ef1d81a0 100644
--- a/apps/web/components/admin/EvaluationSlideView.tsx
+++ b/apps/web/components/admin/EvaluationSlideView.tsx
@@ -219,7 +219,7 @@ export function EvaluationSlideView({
pending: { label: '미처리', variant: 'secondary' },
approved: { label: '승인됨', variant: 'default' },
hold: { label: '보류', variant: 'outline' },
- missing: { label: 'Missing', variant: 'destructive' },
+ missing: { label: '누락', variant: 'destructive' },
geocoding_failed: { label: '지오코딩 실패', variant: 'destructive' },
not_selected: { label: '평가 미대상', variant: 'outline' },
deleted: { label: '삭제됨', variant: 'destructive' },
diff --git a/apps/web/components/admin/EvaluationTableNew.tsx b/apps/web/components/admin/EvaluationTableNew.tsx
index 8bdfcd3f7a..52ad4577fc 100644
--- a/apps/web/components/admin/EvaluationTableNew.tsx
+++ b/apps/web/components/admin/EvaluationTableNew.tsx
@@ -392,7 +392,7 @@ const EvaluationTableRow = memo(forwardRef
{getAdminEvaluationVideoLabel(record)}
-
+
{hasAdminEvaluationYoutubeTitle(record) ? null : '제목 없음 · '}
{new Date(record.youtube_meta?.publishedAt || record.created_at).toLocaleDateString('ko-KR')}
@@ -1163,7 +1163,7 @@ export function EvaluationTable({
{record.restaurant_name || record.name || '이름 없음'}
-
+
{publishedAt} | ID {record.id.slice(0, 8)}
@@ -1209,14 +1209,14 @@ export function EvaluationTable({
{getAdminEvaluationVideoLabel(record)}
{!hasAdminEvaluationYoutubeTitle(record) && (
- 제목 없음 · 메타 백필 필요
+ 제목 없음 · 메타 백필 필요
)}
{canonicalYoutubeUrl && (
e.stopPropagation()}
>
영상 열기
@@ -1228,10 +1228,10 @@ export function EvaluationTable({
- 방문 {visitValue}
- 추론 {inferenceValue}
- 근거 {groundingValue}
- 주소 {geocodingText}
+ 방문 {visitValue}
+ 추론 {inferenceValue}
+ 근거 {groundingValue}
+ 주소 {geocodingText}
@@ -1339,7 +1339,7 @@ export function EvaluationTable({
{isExpanded && (
-
+
평가 항목
diff --git a/apps/web/components/admin/SubmissionDetailView.tsx b/apps/web/components/admin/SubmissionDetailView.tsx
index 69b9f35bb5..4566d97fc5 100644
--- a/apps/web/components/admin/SubmissionDetailView.tsx
+++ b/apps/web/components/admin/SubmissionDetailView.tsx
@@ -720,12 +720,12 @@ export function SubmissionDetailView({
{sanitizePlainText(result.title)}
{result.address}
- {result.roadAddress && {result.roadAddress}
}
+ {result.roadAddress && {result.roadAddress}
}
{result.isMatch ? (
- 주소 일치
+ 주소 일치
) : (
- 불일치
+ 불일치
)}
))}
@@ -927,18 +927,18 @@ export function SubmissionDetailView({
{metaData.title}
{new Date(metaData.publishedAt).toLocaleDateString('ko-KR')} · {formatDuration(metaData.duration)}
- {metaData.is_shorts && Shorts }
+ {metaData.is_shorts && Shorts }
{metaData.ads_info.is_ads ? (
<>
- 광고
+ 광고
{metaData.ads_info.what_ads && metaData.ads_info.what_ads.length > 0 && (
({metaData.ads_info.what_ads.join(', ')})
)}
>
) : (
- 광고 아님
+ 광고 아님
)}
@@ -1011,18 +1011,18 @@ export function SubmissionDetailView({
{originalMeta.published_at ? new Date(originalMeta.published_at).toLocaleDateString('ko-KR') : '-'}
{originalMeta.duration && ` · ${formatDuration(originalMeta.duration)}`}
- {originalMeta.is_shorts && Shorts }
+ {originalMeta.is_shorts && Shorts }
{originalMeta.is_ads ? (
<>
- 광고
+ 광고
{originalMeta.what_ads && originalMeta.what_ads.length > 0 && (
({originalMeta.what_ads.join(', ')})
)}
>
) : (
- 광고 아님
+ 광고 아님
)}
@@ -1039,7 +1039,7 @@ export function SubmissionDetailView({
{originalYoutubeUrl}
) : 기존 YouTube 링크 없음}
- 메타데이터 없음
+ 메타데이터 없음
)}
diff --git a/apps/web/components/admin/SubmissionListView.tsx b/apps/web/components/admin/SubmissionListView.tsx
index 4631dbabe8..cebe2e16f8 100644
--- a/apps/web/components/admin/SubmissionListView.tsx
+++ b/apps/web/components/admin/SubmissionListView.tsx
@@ -220,7 +220,7 @@ const ReviewPhotoItem = memo(function ReviewPhotoItem({
@@ -1070,7 +1070,7 @@ export function SubmissionListView({
reviews.length,
]);
const tabTriggerClassName = cn(
- "h-8 gap-1.5 px-2 text-[11px] xl:h-9 xl:min-w-[128px] xl:justify-center xl:px-3 xl:text-sm",
+ "h-8 gap-1.5 px-2 text-2xs xl:h-9 xl:min-w-[128px] xl:justify-center xl:px-3 xl:text-sm",
isMobile && "justify-center gap-1"
);
const listContainerClassName = "h-full min-h-0 overflow-hidden rounded-lg border bg-card";
@@ -1082,19 +1082,19 @@ export function SubmissionListView({
);
const listSearchInputClassName = "h-9 pl-8 pr-8 text-sm xl:h-9 xl:text-sm";
const listCardBaseClassName = "cursor-pointer rounded-lg border p-3 transition-colors hover:bg-muted/40 xl:p-3.5";
- const listTitleClassName = "truncate text-sm font-semibold xl:text-[15px]";
- const listSubTextClassName = "mt-1 line-clamp-2 text-xs text-muted-foreground xl:text-[13px]";
- const listMetaClassName = "mt-2 flex items-center justify-between text-[11px] text-muted-foreground xl:text-xs";
+ const listTitleClassName = "truncate text-sm font-semibold xl:text-sm";
+ const listSubTextClassName = "mt-1 line-clamp-2 text-xs text-muted-foreground xl:text-sm";
+ const listMetaClassName = "mt-2 flex items-center justify-between text-2xs text-muted-foreground xl:text-xs";
const listActionButtonClassName = "h-8 px-2 text-xs xl:h-8 xl:px-2.5 xl:text-xs";
const listActionIconButtonClassName = "ml-auto h-8 w-8 p-0";
- const listCategoryBadgeClassName = "px-1.5 py-0 text-[10px] xl:text-[11px]";
- const summaryBadgeBaseClassName = "px-2 py-0 text-[11px] leading-none tabular-nums xl:px-2.5 xl:text-sm";
+ const listCategoryBadgeClassName = "px-1.5 py-0 text-2xs xl:text-2xs";
+ const summaryBadgeBaseClassName = "px-2 py-0 text-2xs leading-none tabular-nums xl:px-2.5 xl:text-sm";
const summaryBadgeWithIconClassName = cn(summaryBadgeBaseClassName, "gap-1");
- const summaryLabelClassName = "text-[11px] text-muted-foreground xl:text-sm";
+ const summaryLabelClassName = "text-2xs text-muted-foreground xl:text-sm";
const getTabCountBadgeVariant = (count: number) => (count > 0 ? "secondary" : "outline");
const getTabCountBadgeClassName = (count: number) =>
cn(
- "ml-1 min-w-[20px] justify-center px-1.5 py-0 text-[11px] leading-none tabular-nums xl:text-sm",
+ "ml-1 min-w-[20px] justify-center px-1.5 py-0 text-2xs leading-none tabular-nums xl:text-sm",
count > 0 ? "bg-yellow-100 text-yellow-700" : "border-border bg-muted text-muted-foreground",
isMobile && "ml-0 min-w-[18px] px-1"
);
@@ -1290,9 +1290,9 @@ export function SubmissionListView({
};
const getSubmissionQueueReasonBadgeClassName = (reason: AdminSubmissionQueueReason) =>
cn(
- "max-w-full rounded-full px-2 py-0 text-[10px] font-semibold leading-5 xl:text-[11px]",
+ "max-w-full rounded-full px-2 py-0 text-2xs font-semibold leading-5 xl:text-2xs",
reason.severity === 'danger' && "border-red-200 bg-red-50 text-red-700 dark:border-red-900/70 dark:bg-red-950/25 dark:text-red-200",
- reason.severity === 'warning' && "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/70 dark:bg-amber-950/25 dark:text-amber-200",
+ reason.severity === 'warning' && "border-border bg-secondary text-foreground",
reason.severity === 'info' && "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/70 dark:bg-blue-950/25 dark:text-blue-200"
);
@@ -1324,7 +1324,7 @@ export function SubmissionListView({
{placement === 'card' && summary.reasons.length > reasons.length && (
+{summary.reasons.length - reasons.length}
@@ -2049,7 +2049,7 @@ export function SubmissionListView({
>
- 제보 상세 작업
+ 제보 상세 작업
{selectedSubmission?.restaurant_name || '왼쪽 목록에서 제보를 선택하세요'}
{selectedSubmission && (
@@ -2214,7 +2214,7 @@ export function SubmissionListView({
{ocrRerunningIds.has(review.id) && !review.ocr_processed_at && (
-
+
OCR 처리 중... {ocrCountdowns[review.id] || 0}초 후 완료 예정
@@ -2281,7 +2281,7 @@ export function SubmissionListView({
>
- 리뷰 상세 작업
+ 리뷰 상세 작업
{selectedReview?.restaurants?.name || '왼쪽 목록에서 리뷰를 선택하세요'}
{selectedReview && (
@@ -2325,7 +2325,7 @@ export function SubmissionListView({
-
+
{selectedReview.profiles?.nickname?.[0] || '?'}
@@ -2661,7 +2661,7 @@ export function SubmissionListView({
variant="outline"
onClick={handleRunOcr}
disabled={isOcrRunning || (ocrStatus?.pending === 0)}
- className="h-8 justify-center gap-1 text-[11px]"
+ className="h-8 justify-center gap-1 text-2xs"
>
{isOcrRunning ? (
@@ -2676,7 +2676,7 @@ export function SubmissionListView({
onClick={handleResetAllOcr}
disabled={isOcrRunning}
className={cn(
- "h-8 justify-center gap-1 text-[11px] text-orange-600 border-orange-300 hover:bg-orange-50 dark:text-orange-400 dark:border-orange-700 dark:hover:bg-orange-950/30"
+ "h-8 justify-center gap-1 text-2xs text-orange-600 border-orange-300 hover:bg-orange-50 dark:text-orange-400 dark:border-orange-700 dark:hover:bg-orange-950/30"
)}
>
@@ -2879,7 +2879,7 @@ export function SubmissionListView({
type="button"
variant={isSelected ? "default" : "outline"}
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
aria-pressed={isSelected}
disabled={isUnavailable}
data-admin-submission-queue-reason-filter-option={option.value}
@@ -2887,7 +2887,7 @@ export function SubmissionListView({
>
{option.label}
{option.count}
@@ -2917,7 +2917,7 @@ export function SubmissionListView({
{submission.restaurant_name}{submission.submission_type === 'recommend' && 쯔양 제보}
-
+
{submission.restaurant_address || '-'}
@@ -2937,7 +2937,7 @@ export function SubmissionListView({
))}
{!submission.restaurant_categories?.length && (
- 카테고리 없음
+ 카테고리 없음
)}
@@ -2948,7 +2948,7 @@ export function SubmissionListView({
{submission.recommendation_reason || submission.items[0]?.tzuyang_review || '추천 사유 없음'}
{submission.items[0]?.youtube_link && (
- {submission.items[0].youtube_link}
+ {submission.items[0].youtube_link}
)}
>
) : (
@@ -2959,7 +2959,7 @@ export function SubmissionListView({
))}
{submission.items.length > 2 && (
-
+
+{submission.items.length - 2}개 리뷰
)}
diff --git a/apps/web/components/admin/TrendProposalQueue.tsx b/apps/web/components/admin/TrendProposalQueue.tsx
index 7f0c37d6cb..f25a2c3aaa 100644
--- a/apps/web/components/admin/TrendProposalQueue.tsx
+++ b/apps/web/components/admin/TrendProposalQueue.tsx
@@ -219,7 +219,7 @@ export function TrendProposalQueue() {
>
- 트렌드 제안
+ 트렌드 제안
오버레이 제안 검토
@@ -249,11 +249,11 @@ export function TrendProposalQueue() {
{proposal.label}
{proposal.restaurant.name}
-
+
{formatScore(proposal.score)}
-
+
{proposal.overlayType}
{formatWindow(proposal)}
근거 {proposal.evidenceSummary.observationCount}개
@@ -270,7 +270,7 @@ export function TrendProposalQueue() {
- 선택 제안
+ 선택 제안
{selectedProposal.label}
@@ -278,7 +278,7 @@ export function TrendProposalQueue() {
{selectedProposal.description ?? "설명 없음"}
-
+
근거 신선도
{selectedProposal.evidenceSummary.freshness}
@@ -295,9 +295,9 @@ export function TrendProposalQueue() {
>
미리보기 해시
{previewQuery.isFetching ? (
- 오버레이 미리보기를 계산하는 중입니다.
+ 오버레이 미리보기를 계산하는 중입니다.
) : previewQuery.data ? (
-
+
) : (
- 미리보기 생성 실패 시 승인하지 않습니다.
+ 미리보기 생성 실패 시 승인하지 않습니다.
)}
@@ -366,16 +366,16 @@ export function TrendProposalQueue() {
제안 승인
-
+
승인하려면 정확히 “오버레이 적용”을 입력합니다. 승인은 오버레이 적용, 감사 이벤트, 제안 상태 변경을 하나의 원자 RPC로 처리합니다.
{rejectMutation.data ? (
-
+
검토 이벤트 {rejectMutation.data.reviewEvent.eventId} 기록 완료 · {rejectMutation.data.status}
) : null}
{approveMutation.data ? (
-
+
승인 감사 {approveMutation.data.proposal.overlayAuditId} 기록 완료 · {approveMutation.data.status}
) : null}
diff --git a/apps/web/components/admin/evaluation-status-filter-options.ts b/apps/web/components/admin/evaluation-status-filter-options.ts
index 76b6810785..f1c9c3eb02 100644
--- a/apps/web/components/admin/evaluation-status-filter-options.ts
+++ b/apps/web/components/admin/evaluation-status-filter-options.ts
@@ -5,7 +5,7 @@ export const PRIMARY_STATUS_FILTER_OPTIONS = [
{ value: 'deleted', label: '삭제됨' },
{ value: 'ready_for_approval', label: '승인 대기' },
{ value: 'unconfirmed_map', label: '미확정 좌표' },
- { value: 'missing', label: 'Missing' },
+ { value: 'missing', label: '누락' },
{ value: 'not_selected', label: '평가 미대상' },
] as const;
diff --git a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
index 522a02a459..d3605db7cb 100644
--- a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
+++ b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
@@ -195,7 +195,7 @@ export function AdminPipelineDashboard() {
로컬 control-plane이 없으면 GitHub Actions 크롤러 최근 실행을 보여 줍니다. enqueue는 로컬 API가 있을 때만 동작합니다.
-
+
hardware: {query.data?.hardware ?? "unknown"}
diff --git a/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx b/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
index 83158938d9..793bab55c5 100644
--- a/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
+++ b/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
@@ -2185,7 +2185,7 @@ function StoryboardThinkingTracePanel({
return (
@@ -2224,7 +2224,7 @@ function StoryboardThinkingTracePanel({
{entry.label}
-
+
{statusText}
@@ -2253,7 +2253,7 @@ function StoryboardImageGenerationProgressPanel({
return (
@@ -2298,7 +2298,7 @@ function StoryboardImageGenerationProgressPanel({
{cut.label}
스토리보드 기록
{storyboardHistoryStatus === "loading"
@@ -8210,7 +8210,7 @@ function LegacyAdminStoryboardGenerator({
: "없음"}
-
+
이전에 만든 결과를 이 페이지에서 다시 불러옵니다.
@@ -8258,7 +8258,7 @@ function LegacyAdminStoryboardGenerator({
{storyboardHistoryError ? (
아직 저장된 실제 생성 기록이 없습니다. 채팅에서
@@ -8311,7 +8311,7 @@ function LegacyAdminStoryboardGenerator({
) : (
-
+
이미지 없음
)}
@@ -8362,7 +8362,7 @@ function LegacyAdminStoryboardGenerator({
? "secondary"
: "outline"
}
- className="shrink-0 px-1.5 text-[10px]"
+ className="shrink-0 px-1.5 text-2xs"
data-storyboard-history-source-label="true"
>
{historyTrace.mode === "actual"
@@ -8375,7 +8375,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="secondary"
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
onClick={() => {
void applyStoryboardHistoryResult(
historyCase,
@@ -8415,7 +8415,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="ghost"
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
disabled={proofSummaries.length === 0}
onClick={() =>
setOpenStoryboardHistoryProofId(
@@ -8431,20 +8431,20 @@ function LegacyAdminStoryboardGenerator({
{isProofOpen && proofSummaries.length ? (
이미지 확인 완료
안전 확인됨
@@ -8532,7 +8532,7 @@ function LegacyAdminStoryboardGenerator({
이미지 설정
-
+
기본 OAuth · 고급 로컬 · API Key 백업
@@ -8579,10 +8579,10 @@ function LegacyAdminStoryboardGenerator({
}
data-storyboard-api-router-oauth-transport="server"
>
-
+
기본
-
+
{storyboardImageProviderReadiness.status ===
"ready" &&
storyboardImageProviderReadiness.providerId ===
@@ -8620,10 +8620,10 @@ function LegacyAdminStoryboardGenerator({
}
data-storyboard-api-router-oauth-transport="local-bridge"
>
-
+
고급 로컬
-
+
{storyboardLocalBridgeStatus === "connected"
? "연결됨"
: storyboardLocalBridgeStatus === "checking"
@@ -8650,13 +8650,13 @@ function LegacyAdminStoryboardGenerator({
>
사용: {storyboardImageApiRouterView.label}
@@ -8678,7 +8678,7 @@ function LegacyAdminStoryboardGenerator({
gpt-image-2
@@ -8697,13 +8697,13 @@ function LegacyAdminStoryboardGenerator({
@@ -8776,7 +8776,7 @@ function LegacyAdminStoryboardGenerator({
@@ -8807,7 +8807,7 @@ function LegacyAdminStoryboardGenerator({
) : null}
{storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyError}
@@ -8816,7 +8816,7 @@ function LegacyAdminStoryboardGenerator({
{storyboardBrowserOpenAIApiKeyMessage &&
!storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyMessage}
@@ -8826,7 +8826,7 @@ function LegacyAdminStoryboardGenerator({
>
) : (
@@ -8847,13 +8847,13 @@ function LegacyAdminStoryboardGenerator({
사용자 PC helper의 loopback 브릿지만 호출합니다.
토큰 입력값은 적용 직후 비우며 컴포넌트 ref에만 있고 Web Storage에 저장되지 않습니다. 페이지 전환·복원·닫기 때 제거됩니다.
@@ -8887,7 +8887,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="ghost"
size="sm"
- className="h-6 shrink-0 rounded-full px-2 text-[10px]"
+ className="h-6 shrink-0 rounded-full px-2 text-2xs"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
@@ -8899,7 +8899,7 @@ function LegacyAdminStoryboardGenerator({
{STORYBOARD_LOCAL_BRIDGE_TERMINAL_COMMAND}
@@ -8959,7 +8959,7 @@ function LegacyAdminStoryboardGenerator({
) : null}
{formatStoryboardJobStatusLabel(acceptedStoryboardJob.status)}
@@ -9157,7 +9157,7 @@ function LegacyAdminStoryboardGenerator({
>
{messageProgressLabel ? (
{message.status === "streaming" ? (
@@ -9207,7 +9207,7 @@ function LegacyAdminStoryboardGenerator({
무엇부터 만들까요?
@@ -9246,14 +9246,14 @@ function LegacyAdminStoryboardGenerator({
preset.id
}
>
-
+
{preset.label}
-
+
{preset.segmentCount}컷 ·{" "}
{preset.targetLengthMinutes}분
-
+
{preset.description}
@@ -9289,7 +9289,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="outline"
size="sm"
- className="h-8 rounded-full px-3 text-[11px]"
+ className="h-8 rounded-full px-3 text-2xs"
onClick={handleStoryboardUsageGuideClick}
disabled={
isGenerating ||
@@ -9305,7 +9305,7 @@ function LegacyAdminStoryboardGenerator({
+
선택한 마커
-
+
촬영 동선
{isLoading ? (
@@ -1370,7 +1370,7 @@ function AdminMapInfoPanel({
{preset.label}
-
+
{preset.description}
@@ -1411,7 +1411,7 @@ function AdminMapInfoPanel({
data-admin-route-readiness-panel="local-heuristic"
>
-
+
동선 준비도
-
+
로컬 후보 품질, 도로 경로 확보, 좌표 데이터 충분성 기준의
준비도입니다.
{routeModeOption
@@ -1434,7 +1434,7 @@ function AdminMapInfoPanel({
: null}
{routeAhpAssessment.blockers.length > 0 && (
-
+
{routeAhpAssessment.blockers.map((blocker) => (
@@ -1478,7 +1478,7 @@ function AdminMapInfoPanel({
{directionsStatus === "fallback" && directionsFallbackMessage ? (
@@ -1489,7 +1489,7 @@ function AdminMapInfoPanel({
{routePlan.warnings.length > 0 ? (
동선 주의사항
@@ -1525,7 +1525,7 @@ function AdminMapInfoPanel({
{restaurant.category ?? "카테고리 확인"}
{index > 0 && routePlan.legs[index - 1] && (
-
+
이전 지점에서{" "}
{routePlan.legs[index - 1].distanceKm.toFixed(1)}km ·
약{" "}
@@ -1546,7 +1546,7 @@ function AdminMapInfoPanel({
데이터가 부족하면 임의 동선을 만들지 않습니다.
)}
-
+
{directionsStatus === "ready"
? "네이버 Directions 5 기준 실제 도로 주행 경로를 지도에 표시합니다."
: routeMode === "driving"
@@ -1554,7 +1554,7 @@ function AdminMapInfoPanel({
: "네이버 Directions 5는 자동차만 지원하므로 도보·혼합은 근거리 촬영 초안으로 표시합니다."}
{adminRestaurantStatusChips.map((chip) => (
-
+
{chip}
))}
@@ -1151,7 +1151,7 @@ export function AdminRestaurantModal({
{adminRestaurantProgressSteps.map((step) => (
-
+
{step.label}
@@ -1178,7 +1178,7 @@ export function AdminRestaurantModal({
{description}
{badge && (
-
+
{badge}
)}
diff --git a/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx b/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
index f5a0ff2b67..1f7f2ba275 100644
--- a/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
+++ b/apps/web/components/admin/AdminRestaurantRefreshHistoryPanel.tsx
@@ -200,7 +200,7 @@ function ManagementStatusSummary({ items }: { items: StatusSummaryItem[] }) {
function RefreshWorkflowSteps() {
return (
-
+
{[
"1. 승인 맛집 스냅샷 수집",
"2. 외부 후보와 현재값 비교",
@@ -526,13 +526,13 @@ function RefreshCandidateDetailPanel({
{readbackLabel(selectedCandidate.readback_state)}
{selectedCandidate.readback_state.checked_at ? (
-
+
{formatDate(selectedCandidate.readback_state.checked_at)}
) : null}
근거:{" "}
@@ -571,7 +571,7 @@ function RefreshCandidateDetailPanel({
/>
승인과 동시에 현재 맛집 값 guarded apply
-
+
상호·전화·주소·좌표 변경 후보만 적용됩니다. 폐업 의심 후보는
자동 적용할 수 없습니다.
diff --git a/apps/web/components/admin/AdminReviewPanel.tsx b/apps/web/components/admin/AdminReviewPanel.tsx
index 42c68aefcf..b0c4c948d1 100644
--- a/apps/web/components/admin/AdminReviewPanel.tsx
+++ b/apps/web/components/admin/AdminReviewPanel.tsx
@@ -698,7 +698,7 @@ export default function AdminReviewPanel({ isOpen, onClose, onToggleCollapse, is
-
+
{selectedReview.profiles?.nickname?.[0] || '익'}
@@ -780,7 +780,7 @@ const ReviewCard = forwardRef(
-
+
{review.profiles?.nickname?.[0] || '익'}
diff --git a/apps/web/components/admin/AdminUsersPanel.tsx b/apps/web/components/admin/AdminUsersPanel.tsx
index 057cc468f4..63a3d6b4b2 100644
--- a/apps/web/components/admin/AdminUsersPanel.tsx
+++ b/apps/web/components/admin/AdminUsersPanel.tsx
@@ -123,7 +123,7 @@ function SummaryMetric({ label, value, tone = "default", isLoading = false }: {
)}
data-admin-users-summary-metric={label}
>
- {label}
+ {label}
{isLoading ? : value}
@@ -384,7 +384,7 @@ export default function AdminUsersPanel() {
{['관리자 확인 필수', '자기 잠금 방지', '상태 재확인', '삭제 대신 비활성화'].map((label) => (
- {label}
+ {label}
))}
diff --git a/apps/web/components/admin/CategorySidebar.tsx b/apps/web/components/admin/CategorySidebar.tsx
index 064e93e521..9ba2d9cb4a 100644
--- a/apps/web/components/admin/CategorySidebar.tsx
+++ b/apps/web/components/admin/CategorySidebar.tsx
@@ -4,11 +4,13 @@ interface CategorySidebarProps {
stats: CategoryStats;
selectedStatuses: EvaluationRecordStatus[];
onSelectStatuses: (statuses: EvaluationRecordStatus[]) => void;
+ showStatusChips?: boolean;
children?: React.ReactNode;
}
export function CategorySidebar({
stats,
+ showStatusChips = true,
children,
}: CategorySidebarProps) {
// 그룹 1: 전체, 미처리, 승인됨, 삭제 (통계만 표시)
@@ -23,7 +25,7 @@ export function CategorySidebar({
const statCategoriesGroup2 = [
{ label: '승인 대기', count: stats.ready_for_approval },
{ label: '미확정 좌표', count: stats.unconfirmed_map },
- { label: 'Missing', count: stats.missing },
+ { label: '누락', count: stats.missing },
{ label: '평가 미대상', count: stats.not_selected },
];
const statCategoriesDesktop = [...statCategoriesGroup1, ...statCategoriesGroup2];
@@ -46,11 +48,13 @@ export function CategorySidebar({
)}
-
- {statCategoriesDesktop.map((category, index) =>
- renderStatChip(category.label, category.count, `desktop-${index}`)
- )}
-
+ {showStatusChips ? (
+
+ {statCategoriesDesktop.map((category, index) =>
+ renderStatChip(category.label, category.count, `desktop-${index}`)
+ )}
+
+ ) : null}
);
diff --git a/apps/web/components/admin/EvaluationDetailView.tsx b/apps/web/components/admin/EvaluationDetailView.tsx
index 79a4e648c8..72e293334e 100644
--- a/apps/web/components/admin/EvaluationDetailView.tsx
+++ b/apps/web/components/admin/EvaluationDetailView.tsx
@@ -109,7 +109,7 @@ function NumberBadge({ index, tone }: { index: number; tone: EvaluationTone }) {
@@ -163,7 +163,7 @@ function SourceNameRow({
{label}
{value}
-
+
{provider}
@@ -185,21 +185,21 @@ function BooleanBadge({ value, rerunNeeded = false }: { value: boolean | null |
if (value === undefined || value === null) {
if (!rerunNeeded) {
return (
-
+
-
);
}
return (
-
+
-
);
}
return (
-
+
{value ? '일치' : '불일치'}
);
@@ -208,7 +208,7 @@ function BooleanBadge({ value, rerunNeeded = false }: { value: boolean | null |
function InfoItem({ label, children, className }: { label: string; children: React.ReactNode; className?: string }) {
return (
-
);
@@ -389,7 +389,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
- 승인됨 · {record.approved_name} : null}>
+ 승인됨 · {record.approved_name} : null}>
{addressConsistency.label}}
+ value={{addressConsistency.label} }
>
{addressConsistency.headline}
@@ -474,7 +474,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
운영 분류
{addressGuidance.label}
@@ -491,7 +491,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
= 98
? 'border-emerald-300 bg-emerald-50 text-emerald-700'
: 'border-sky-300 bg-background/70 text-sky-700',
@@ -508,7 +508,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
{addressAhp.evidenceFamilies.length > 0 && (
{addressAhp.evidenceFamilies.map((family) => (
-
+
{family}
))}
@@ -523,7 +523,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record,
{signal.label}
diff --git a/apps/web/components/admin/EvaluationSlideView.tsx b/apps/web/components/admin/EvaluationSlideView.tsx
index 3ece5fd8f0..23ef1d81a0 100644
--- a/apps/web/components/admin/EvaluationSlideView.tsx
+++ b/apps/web/components/admin/EvaluationSlideView.tsx
@@ -219,7 +219,7 @@ export function EvaluationSlideView({
pending: { label: '미처리', variant: 'secondary' },
approved: { label: '승인됨', variant: 'default' },
hold: { label: '보류', variant: 'outline' },
- missing: { label: 'Missing', variant: 'destructive' },
+ missing: { label: '누락', variant: 'destructive' },
geocoding_failed: { label: '지오코딩 실패', variant: 'destructive' },
not_selected: { label: '평가 미대상', variant: 'outline' },
deleted: { label: '삭제됨', variant: 'destructive' },
diff --git a/apps/web/components/admin/EvaluationTableNew.tsx b/apps/web/components/admin/EvaluationTableNew.tsx
index 8bdfcd3f7a..52ad4577fc 100644
--- a/apps/web/components/admin/EvaluationTableNew.tsx
+++ b/apps/web/components/admin/EvaluationTableNew.tsx
@@ -392,7 +392,7 @@ const EvaluationTableRow = memo(forwardRef
{getAdminEvaluationVideoLabel(record)}
-
+
{hasAdminEvaluationYoutubeTitle(record) ? null : '제목 없음 · '}
{new Date(record.youtube_meta?.publishedAt || record.created_at).toLocaleDateString('ko-KR')}
@@ -1163,7 +1163,7 @@ export function EvaluationTable({
{record.restaurant_name || record.name || '이름 없음'}
-
+
{publishedAt} | ID {record.id.slice(0, 8)}
@@ -1209,14 +1209,14 @@ export function EvaluationTable({
{getAdminEvaluationVideoLabel(record)}
{!hasAdminEvaluationYoutubeTitle(record) && (
- 제목 없음 · 메타 백필 필요
+ 제목 없음 · 메타 백필 필요
)}
{canonicalYoutubeUrl && (
e.stopPropagation()}
>
영상 열기
@@ -1228,10 +1228,10 @@ export function EvaluationTable({
- 방문 {visitValue}
- 추론 {inferenceValue}
- 근거 {groundingValue}
- 주소 {geocodingText}
+ 방문 {visitValue}
+ 추론 {inferenceValue}
+ 근거 {groundingValue}
+ 주소 {geocodingText}
@@ -1339,7 +1339,7 @@ export function EvaluationTable({
{isExpanded && (
-
+
평가 항목
diff --git a/apps/web/components/admin/SubmissionDetailView.tsx b/apps/web/components/admin/SubmissionDetailView.tsx
index 69b9f35bb5..4566d97fc5 100644
--- a/apps/web/components/admin/SubmissionDetailView.tsx
+++ b/apps/web/components/admin/SubmissionDetailView.tsx
@@ -720,12 +720,12 @@ export function SubmissionDetailView({
{sanitizePlainText(result.title)}
{result.address}
- {result.roadAddress && {result.roadAddress}
}
+ {result.roadAddress && {result.roadAddress}
}
{result.isMatch ? (
- 주소 일치
+ 주소 일치
) : (
- 불일치
+ 불일치
)}
))}
@@ -927,18 +927,18 @@ export function SubmissionDetailView({
{metaData.title}
{new Date(metaData.publishedAt).toLocaleDateString('ko-KR')} · {formatDuration(metaData.duration)}
- {metaData.is_shorts && Shorts }
+ {metaData.is_shorts && Shorts }
{metaData.ads_info.is_ads ? (
<>
- 광고
+ 광고
{metaData.ads_info.what_ads && metaData.ads_info.what_ads.length > 0 && (
({metaData.ads_info.what_ads.join(', ')})
)}
>
) : (
- 광고 아님
+ 광고 아님
)}
@@ -1011,18 +1011,18 @@ export function SubmissionDetailView({
{originalMeta.published_at ? new Date(originalMeta.published_at).toLocaleDateString('ko-KR') : '-'}
{originalMeta.duration && ` · ${formatDuration(originalMeta.duration)}`}
- {originalMeta.is_shorts && Shorts }
+ {originalMeta.is_shorts && Shorts }
{originalMeta.is_ads ? (
<>
- 광고
+ 광고
{originalMeta.what_ads && originalMeta.what_ads.length > 0 && (
({originalMeta.what_ads.join(', ')})
)}
>
) : (
- 광고 아님
+ 광고 아님
)}
@@ -1039,7 +1039,7 @@ export function SubmissionDetailView({
{originalYoutubeUrl}
) : 기존 YouTube 링크 없음}
- 메타데이터 없음
+ 메타데이터 없음
)}
diff --git a/apps/web/components/admin/SubmissionListView.tsx b/apps/web/components/admin/SubmissionListView.tsx
index 4631dbabe8..cebe2e16f8 100644
--- a/apps/web/components/admin/SubmissionListView.tsx
+++ b/apps/web/components/admin/SubmissionListView.tsx
@@ -220,7 +220,7 @@ const ReviewPhotoItem = memo(function ReviewPhotoItem({
@@ -1070,7 +1070,7 @@ export function SubmissionListView({
reviews.length,
]);
const tabTriggerClassName = cn(
- "h-8 gap-1.5 px-2 text-[11px] xl:h-9 xl:min-w-[128px] xl:justify-center xl:px-3 xl:text-sm",
+ "h-8 gap-1.5 px-2 text-2xs xl:h-9 xl:min-w-[128px] xl:justify-center xl:px-3 xl:text-sm",
isMobile && "justify-center gap-1"
);
const listContainerClassName = "h-full min-h-0 overflow-hidden rounded-lg border bg-card";
@@ -1082,19 +1082,19 @@ export function SubmissionListView({
);
const listSearchInputClassName = "h-9 pl-8 pr-8 text-sm xl:h-9 xl:text-sm";
const listCardBaseClassName = "cursor-pointer rounded-lg border p-3 transition-colors hover:bg-muted/40 xl:p-3.5";
- const listTitleClassName = "truncate text-sm font-semibold xl:text-[15px]";
- const listSubTextClassName = "mt-1 line-clamp-2 text-xs text-muted-foreground xl:text-[13px]";
- const listMetaClassName = "mt-2 flex items-center justify-between text-[11px] text-muted-foreground xl:text-xs";
+ const listTitleClassName = "truncate text-sm font-semibold xl:text-sm";
+ const listSubTextClassName = "mt-1 line-clamp-2 text-xs text-muted-foreground xl:text-sm";
+ const listMetaClassName = "mt-2 flex items-center justify-between text-2xs text-muted-foreground xl:text-xs";
const listActionButtonClassName = "h-8 px-2 text-xs xl:h-8 xl:px-2.5 xl:text-xs";
const listActionIconButtonClassName = "ml-auto h-8 w-8 p-0";
- const listCategoryBadgeClassName = "px-1.5 py-0 text-[10px] xl:text-[11px]";
- const summaryBadgeBaseClassName = "px-2 py-0 text-[11px] leading-none tabular-nums xl:px-2.5 xl:text-sm";
+ const listCategoryBadgeClassName = "px-1.5 py-0 text-2xs xl:text-2xs";
+ const summaryBadgeBaseClassName = "px-2 py-0 text-2xs leading-none tabular-nums xl:px-2.5 xl:text-sm";
const summaryBadgeWithIconClassName = cn(summaryBadgeBaseClassName, "gap-1");
- const summaryLabelClassName = "text-[11px] text-muted-foreground xl:text-sm";
+ const summaryLabelClassName = "text-2xs text-muted-foreground xl:text-sm";
const getTabCountBadgeVariant = (count: number) => (count > 0 ? "secondary" : "outline");
const getTabCountBadgeClassName = (count: number) =>
cn(
- "ml-1 min-w-[20px] justify-center px-1.5 py-0 text-[11px] leading-none tabular-nums xl:text-sm",
+ "ml-1 min-w-[20px] justify-center px-1.5 py-0 text-2xs leading-none tabular-nums xl:text-sm",
count > 0 ? "bg-yellow-100 text-yellow-700" : "border-border bg-muted text-muted-foreground",
isMobile && "ml-0 min-w-[18px] px-1"
);
@@ -1290,9 +1290,9 @@ export function SubmissionListView({
};
const getSubmissionQueueReasonBadgeClassName = (reason: AdminSubmissionQueueReason) =>
cn(
- "max-w-full rounded-full px-2 py-0 text-[10px] font-semibold leading-5 xl:text-[11px]",
+ "max-w-full rounded-full px-2 py-0 text-2xs font-semibold leading-5 xl:text-2xs",
reason.severity === 'danger' && "border-red-200 bg-red-50 text-red-700 dark:border-red-900/70 dark:bg-red-950/25 dark:text-red-200",
- reason.severity === 'warning' && "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/70 dark:bg-amber-950/25 dark:text-amber-200",
+ reason.severity === 'warning' && "border-border bg-secondary text-foreground",
reason.severity === 'info' && "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/70 dark:bg-blue-950/25 dark:text-blue-200"
);
@@ -1324,7 +1324,7 @@ export function SubmissionListView({
{placement === 'card' && summary.reasons.length > reasons.length && (
+{summary.reasons.length - reasons.length}
@@ -2049,7 +2049,7 @@ export function SubmissionListView({
>
- 제보 상세 작업
+ 제보 상세 작업
{selectedSubmission?.restaurant_name || '왼쪽 목록에서 제보를 선택하세요'}
{selectedSubmission && (
@@ -2214,7 +2214,7 @@ export function SubmissionListView({
{ocrRerunningIds.has(review.id) && !review.ocr_processed_at && (
-
+
OCR 처리 중... {ocrCountdowns[review.id] || 0}초 후 완료 예정
@@ -2281,7 +2281,7 @@ export function SubmissionListView({
>
- 리뷰 상세 작업
+ 리뷰 상세 작업
{selectedReview?.restaurants?.name || '왼쪽 목록에서 리뷰를 선택하세요'}
{selectedReview && (
@@ -2325,7 +2325,7 @@ export function SubmissionListView({
-
+
{selectedReview.profiles?.nickname?.[0] || '?'}
@@ -2661,7 +2661,7 @@ export function SubmissionListView({
variant="outline"
onClick={handleRunOcr}
disabled={isOcrRunning || (ocrStatus?.pending === 0)}
- className="h-8 justify-center gap-1 text-[11px]"
+ className="h-8 justify-center gap-1 text-2xs"
>
{isOcrRunning ? (
@@ -2676,7 +2676,7 @@ export function SubmissionListView({
onClick={handleResetAllOcr}
disabled={isOcrRunning}
className={cn(
- "h-8 justify-center gap-1 text-[11px] text-orange-600 border-orange-300 hover:bg-orange-50 dark:text-orange-400 dark:border-orange-700 dark:hover:bg-orange-950/30"
+ "h-8 justify-center gap-1 text-2xs text-orange-600 border-orange-300 hover:bg-orange-50 dark:text-orange-400 dark:border-orange-700 dark:hover:bg-orange-950/30"
)}
>
@@ -2879,7 +2879,7 @@ export function SubmissionListView({
type="button"
variant={isSelected ? "default" : "outline"}
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
aria-pressed={isSelected}
disabled={isUnavailable}
data-admin-submission-queue-reason-filter-option={option.value}
@@ -2887,7 +2887,7 @@ export function SubmissionListView({
>
{option.label}
{option.count}
@@ -2917,7 +2917,7 @@ export function SubmissionListView({
{submission.restaurant_name}{submission.submission_type === 'recommend' && 쯔양 제보}
-
+
{submission.restaurant_address || '-'}
@@ -2937,7 +2937,7 @@ export function SubmissionListView({
))}
{!submission.restaurant_categories?.length && (
- 카테고리 없음
+ 카테고리 없음
)}
@@ -2948,7 +2948,7 @@ export function SubmissionListView({
{submission.recommendation_reason || submission.items[0]?.tzuyang_review || '추천 사유 없음'}
{submission.items[0]?.youtube_link && (
- {submission.items[0].youtube_link}
+ {submission.items[0].youtube_link}
)}
>
) : (
@@ -2959,7 +2959,7 @@ export function SubmissionListView({
))}
{submission.items.length > 2 && (
-
+
+{submission.items.length - 2}개 리뷰
)}
diff --git a/apps/web/components/admin/TrendProposalQueue.tsx b/apps/web/components/admin/TrendProposalQueue.tsx
index 7f0c37d6cb..f25a2c3aaa 100644
--- a/apps/web/components/admin/TrendProposalQueue.tsx
+++ b/apps/web/components/admin/TrendProposalQueue.tsx
@@ -219,7 +219,7 @@ export function TrendProposalQueue() {
>
- 트렌드 제안
+ 트렌드 제안
오버레이 제안 검토
@@ -249,11 +249,11 @@ export function TrendProposalQueue() {
{proposal.label}
{proposal.restaurant.name}
-
+
{formatScore(proposal.score)}
-
+
{proposal.overlayType}
{formatWindow(proposal)}
근거 {proposal.evidenceSummary.observationCount}개
@@ -270,7 +270,7 @@ export function TrendProposalQueue() {
- 선택 제안
+ 선택 제안
{selectedProposal.label}
@@ -278,7 +278,7 @@ export function TrendProposalQueue() {
{selectedProposal.description ?? "설명 없음"}
-
+
근거 신선도
{selectedProposal.evidenceSummary.freshness}
@@ -295,9 +295,9 @@ export function TrendProposalQueue() {
>
미리보기 해시
{previewQuery.isFetching ? (
- 오버레이 미리보기를 계산하는 중입니다.
+ 오버레이 미리보기를 계산하는 중입니다.
) : previewQuery.data ? (
-
+
) : (
- 미리보기 생성 실패 시 승인하지 않습니다.
+ 미리보기 생성 실패 시 승인하지 않습니다.
)}
@@ -366,16 +366,16 @@ export function TrendProposalQueue() {
제안 승인
-
+
승인하려면 정확히 “오버레이 적용”을 입력합니다. 승인은 오버레이 적용, 감사 이벤트, 제안 상태 변경을 하나의 원자 RPC로 처리합니다.
{rejectMutation.data ? (
-
+
검토 이벤트 {rejectMutation.data.reviewEvent.eventId} 기록 완료 · {rejectMutation.data.status}
) : null}
{approveMutation.data ? (
-
+
승인 감사 {approveMutation.data.proposal.overlayAuditId} 기록 완료 · {approveMutation.data.status}
) : null}
diff --git a/apps/web/components/admin/evaluation-status-filter-options.ts b/apps/web/components/admin/evaluation-status-filter-options.ts
index 76b6810785..f1c9c3eb02 100644
--- a/apps/web/components/admin/evaluation-status-filter-options.ts
+++ b/apps/web/components/admin/evaluation-status-filter-options.ts
@@ -5,7 +5,7 @@ export const PRIMARY_STATUS_FILTER_OPTIONS = [
{ value: 'deleted', label: '삭제됨' },
{ value: 'ready_for_approval', label: '승인 대기' },
{ value: 'unconfirmed_map', label: '미확정 좌표' },
- { value: 'missing', label: 'Missing' },
+ { value: 'missing', label: '누락' },
{ value: 'not_selected', label: '평가 미대상' },
] as const;
diff --git a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
index 522a02a459..d3605db7cb 100644
--- a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
+++ b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx
@@ -195,7 +195,7 @@ export function AdminPipelineDashboard() {
로컬 control-plane이 없으면 GitHub Actions 크롤러 최근 실행을 보여 줍니다. enqueue는 로컬 API가 있을 때만 동작합니다.
-
+
hardware: {query.data?.hardware ?? "unknown"}
diff --git a/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx b/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
index 83158938d9..793bab55c5 100644
--- a/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
+++ b/apps/web/components/admin/storyboard/AdminStoryboardGenerator.tsx
@@ -2185,7 +2185,7 @@ function StoryboardThinkingTracePanel({
return (
@@ -2224,7 +2224,7 @@ function StoryboardThinkingTracePanel({
{entry.label}
-
+
{statusText}
@@ -2253,7 +2253,7 @@ function StoryboardImageGenerationProgressPanel({
return (
@@ -2298,7 +2298,7 @@ function StoryboardImageGenerationProgressPanel({
{cut.label}
스토리보드 기록
{storyboardHistoryStatus === "loading"
@@ -8210,7 +8210,7 @@ function LegacyAdminStoryboardGenerator({
: "없음"}
-
+
이전에 만든 결과를 이 페이지에서 다시 불러옵니다.
@@ -8258,7 +8258,7 @@ function LegacyAdminStoryboardGenerator({
{storyboardHistoryError ? (
아직 저장된 실제 생성 기록이 없습니다. 채팅에서
@@ -8311,7 +8311,7 @@ function LegacyAdminStoryboardGenerator({
) : (
-
+
이미지 없음
)}
@@ -8362,7 +8362,7 @@ function LegacyAdminStoryboardGenerator({
? "secondary"
: "outline"
}
- className="shrink-0 px-1.5 text-[10px]"
+ className="shrink-0 px-1.5 text-2xs"
data-storyboard-history-source-label="true"
>
{historyTrace.mode === "actual"
@@ -8375,7 +8375,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="secondary"
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
onClick={() => {
void applyStoryboardHistoryResult(
historyCase,
@@ -8415,7 +8415,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="ghost"
size="sm"
- className="h-7 rounded-full px-2 text-[11px]"
+ className="h-7 rounded-full px-2 text-2xs"
disabled={proofSummaries.length === 0}
onClick={() =>
setOpenStoryboardHistoryProofId(
@@ -8431,20 +8431,20 @@ function LegacyAdminStoryboardGenerator({
{isProofOpen && proofSummaries.length ? (
이미지 확인 완료
안전 확인됨
@@ -8532,7 +8532,7 @@ function LegacyAdminStoryboardGenerator({
이미지 설정
-
+
기본 OAuth · 고급 로컬 · API Key 백업
@@ -8579,10 +8579,10 @@ function LegacyAdminStoryboardGenerator({
}
data-storyboard-api-router-oauth-transport="server"
>
-
+
기본
-
+
{storyboardImageProviderReadiness.status ===
"ready" &&
storyboardImageProviderReadiness.providerId ===
@@ -8620,10 +8620,10 @@ function LegacyAdminStoryboardGenerator({
}
data-storyboard-api-router-oauth-transport="local-bridge"
>
-
+
고급 로컬
-
+
{storyboardLocalBridgeStatus === "connected"
? "연결됨"
: storyboardLocalBridgeStatus === "checking"
@@ -8650,13 +8650,13 @@ function LegacyAdminStoryboardGenerator({
>
사용: {storyboardImageApiRouterView.label}
@@ -8678,7 +8678,7 @@ function LegacyAdminStoryboardGenerator({
gpt-image-2
@@ -8697,13 +8697,13 @@ function LegacyAdminStoryboardGenerator({
@@ -8776,7 +8776,7 @@ function LegacyAdminStoryboardGenerator({
@@ -8807,7 +8807,7 @@ function LegacyAdminStoryboardGenerator({
) : null}
{storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyError}
@@ -8816,7 +8816,7 @@ function LegacyAdminStoryboardGenerator({
{storyboardBrowserOpenAIApiKeyMessage &&
!storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyMessage}
@@ -8826,7 +8826,7 @@ function LegacyAdminStoryboardGenerator({
>
) : (
@@ -8847,13 +8847,13 @@ function LegacyAdminStoryboardGenerator({
사용자 PC helper의 loopback 브릿지만 호출합니다.
토큰 입력값은 적용 직후 비우며 컴포넌트 ref에만 있고 Web Storage에 저장되지 않습니다. 페이지 전환·복원·닫기 때 제거됩니다.
@@ -8887,7 +8887,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="ghost"
size="sm"
- className="h-6 shrink-0 rounded-full px-2 text-[10px]"
+ className="h-6 shrink-0 rounded-full px-2 text-2xs"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
@@ -8899,7 +8899,7 @@ function LegacyAdminStoryboardGenerator({
{STORYBOARD_LOCAL_BRIDGE_TERMINAL_COMMAND}
@@ -8959,7 +8959,7 @@ function LegacyAdminStoryboardGenerator({
) : null}
{formatStoryboardJobStatusLabel(acceptedStoryboardJob.status)}
@@ -9157,7 +9157,7 @@ function LegacyAdminStoryboardGenerator({
>
{messageProgressLabel ? (
{message.status === "streaming" ? (
@@ -9207,7 +9207,7 @@ function LegacyAdminStoryboardGenerator({
무엇부터 만들까요?
@@ -9246,14 +9246,14 @@ function LegacyAdminStoryboardGenerator({
preset.id
}
>
-
+
{preset.label}
-
+
{preset.segmentCount}컷 ·{" "}
{preset.targetLengthMinutes}분
-
+
{preset.description}
@@ -9289,7 +9289,7 @@ function LegacyAdminStoryboardGenerator({
type="button"
variant="outline"
size="sm"
- className="h-8 rounded-full px-3 text-[11px]"
+ className="h-8 rounded-full px-3 text-2xs"
onClick={handleStoryboardUsageGuideClick}
disabled={
isGenerating ||
@@ -9305,7 +9305,7 @@ function LegacyAdminStoryboardGenerator({
+
촬영 동선
{isLoading ? ( @@ -1370,7 +1370,7 @@ function AdminMapInfoPanel({ {preset.label} - + {preset.description} @@ -1411,7 +1411,7 @@ function AdminMapInfoPanel({ data-admin-route-readiness-panel="local-heuristic" >+
동선 준비도
+
로컬 후보 품질, 도로 경로 확보, 좌표 데이터 충분성 기준의 준비도입니다. {routeModeOption @@ -1434,7 +1434,7 @@ function AdminMapInfoPanel({ : null}
{routeAhpAssessment.blockers.length > 0 && ( --
+
-
{routeAhpAssessment.blockers.map((blocker) => (
동선 주의사항
@@ -1525,7 +1525,7 @@ function AdminMapInfoPanel({ {restaurant.category ?? "카테고리 확인"} {index > 0 && routePlan.legs[index - 1] && ( -+
이전 지점에서{" "} {routePlan.legs[index - 1].distanceKm.toFixed(1)}km · 약{" "} @@ -1546,7 +1546,7 @@ function AdminMapInfoPanel({ 데이터가 부족하면 임의 동선을 만들지 않습니다.
)} -+
{directionsStatus === "ready" ? "네이버 Directions 5 기준 실제 도로 주행 경로를 지도에 표시합니다." : routeMode === "driving" @@ -1554,7 +1554,7 @@ function AdminMapInfoPanel({ : "네이버 Directions 5는 자동차만 지원하므로 도보·혼합은 근거리 촬영 초안으로 표시합니다."}
{description}
근거:{" "}
@@ -571,7 +571,7 @@ function RefreshCandidateDetailPanel({
/>
승인과 동시에 현재 맛집 값 guarded apply
-
+
상호·전화·주소·좌표 변경 후보만 적용됩니다. 폐업 의심 후보는
자동 적용할 수 없습니다.
diff --git a/apps/web/components/admin/AdminReviewPanel.tsx b/apps/web/components/admin/AdminReviewPanel.tsx
index 42c68aefcf..b0c4c948d1 100644
--- a/apps/web/components/admin/AdminReviewPanel.tsx
+++ b/apps/web/components/admin/AdminReviewPanel.tsx
@@ -698,7 +698,7 @@ export default function AdminReviewPanel({ isOpen, onClose, onToggleCollapse, is
{label} {label}
{isLoading ? : value}
{addressConsistency.headline}
@@ -474,7 +474,7 @@ export const EvaluationDetailView = memo(function EvaluationDetailView({ record, 운영 분류{record.restaurant_name || record.name || '이름 없음'}
-+
{publishedAt} | ID {record.id.slice(0, 8)}
제목 없음 · 메타 백필 필요
+제목 없음 · 메타 백필 필요
)} {canonicalYoutubeUrl && ( e.stopPropagation()} > 영상 열기 @@ -1228,10 +1228,10 @@ export function EvaluationTable({평가 항목
-
diff --git a/apps/web/components/admin/SubmissionDetailView.tsx b/apps/web/components/admin/SubmissionDetailView.tsx
index 69b9f35bb5..4566d97fc5 100644
--- a/apps/web/components/admin/SubmissionDetailView.tsx
+++ b/apps/web/components/admin/SubmissionDetailView.tsx
@@ -720,12 +720,12 @@ export function SubmissionDetailView({
{sanitizePlainText(result.title)}
{result.address}
- {result.roadAddress &&{result.roadAddress}
} + {result.roadAddress &&{result.roadAddress}
}{metaData.title}
{new Date(metaData.publishedAt).toLocaleDateString('ko-KR')} · {formatDuration(metaData.duration)}
- {metaData.is_shorts &&
{originalMeta.published_at ? new Date(originalMeta.published_at).toLocaleDateString('ko-KR') : '-'}
{originalMeta.duration && ` · ${formatDuration(originalMeta.duration)}`}
- {originalMeta.is_shorts &&
메타데이터 없음
+메타데이터 없음
제보 상세 작업
+제보 상세 작업
{selectedSubmission?.restaurant_name || '왼쪽 목록에서 제보를 선택하세요'}
리뷰 상세 작업
+리뷰 상세 작업
{selectedReview?.restaurants?.name || '왼쪽 목록에서 리뷰를 선택하세요'}
{submission.restaurant_name}{submission.submission_type === 'recommend' && 쯔양 제보}
-+
{submission.restaurant_address || '-'}
{submission.items[0].youtube_link}
+{submission.items[0].youtube_link}
)} > ) : ( @@ -2959,7 +2959,7 @@ export function SubmissionListView({ ))} {submission.items.length > 2 && ( - + +{submission.items.length - 2}개 리뷰 )} diff --git a/apps/web/components/admin/TrendProposalQueue.tsx b/apps/web/components/admin/TrendProposalQueue.tsx index 7f0c37d6cb..f25a2c3aaa 100644 --- a/apps/web/components/admin/TrendProposalQueue.tsx +++ b/apps/web/components/admin/TrendProposalQueue.tsx @@ -219,7 +219,7 @@ export function TrendProposalQueue() { >트렌드 제안
+트렌드 제안
오버레이 제안 검토
{proposal.label}
{proposal.restaurant.name}
선택 제안
+선택 제안
{selectedProposal.label}
{selectedProposal.description ?? "설명 없음"}
-근거 신선도
{selectedProposal.evidenceSummary.freshness}
@@ -295,9 +295,9 @@ export function TrendProposalQueue() { >미리보기 해시
{previewQuery.isFetching ? ( -오버레이 미리보기를 계산하는 중입니다.
+오버레이 미리보기를 계산하는 중입니다.
) : previewQuery.data ? ( --
+
미리보기 생성 실패 시 승인하지 않습니다.
+미리보기 생성 실패 시 승인하지 않습니다.
)}+
승인하려면 정확히 “오버레이 적용”을 입력합니다. 승인은 오버레이 적용, 감사 이벤트, 제안 상태 변경을 하나의 원자 RPC로 처리합니다.
{rejectMutation.data ? ( -+
검토 이벤트 {rejectMutation.data.reviewEvent.eventId} 기록 완료 · {rejectMutation.data.status}
) : null} {approveMutation.data ? ( -+
승인 감사 {approveMutation.data.proposal.overlayAuditId} 기록 완료 · {approveMutation.data.status}
) : null} diff --git a/apps/web/components/admin/evaluation-status-filter-options.ts b/apps/web/components/admin/evaluation-status-filter-options.ts index 76b6810785..f1c9c3eb02 100644 --- a/apps/web/components/admin/evaluation-status-filter-options.ts +++ b/apps/web/components/admin/evaluation-status-filter-options.ts @@ -5,7 +5,7 @@ export const PRIMARY_STATUS_FILTER_OPTIONS = [ { value: 'deleted', label: '삭제됨' }, { value: 'ready_for_approval', label: '승인 대기' }, { value: 'unconfirmed_map', label: '미확정 좌표' }, - { value: 'missing', label: 'Missing' }, + { value: 'missing', label: '누락' }, { value: 'not_selected', label: '평가 미대상' }, ] as const; diff --git a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx index 522a02a459..d3605db7cb 100644 --- a/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx +++ b/apps/web/components/admin/pipeline/AdminPipelineDashboard.tsx @@ -195,7 +195,7 @@ export function AdminPipelineDashboard() { 로컬 control-plane이 없으면 GitHub Actions 크롤러 최근 실행을 보여 줍니다. enqueue는 로컬 API가 있을 때만 동작합니다. -@@ -2224,7 +2224,7 @@ function StoryboardThinkingTracePanel({ {entry.label} - + {statusText}
+
이전에 만든 결과를 이 페이지에서 다시 불러옵니다.
이미지 설정
-+
기본 OAuth · 고급 로컬 · API Key 백업
사용: {storyboardImageApiRouterView.label}
@@ -8678,7 +8678,7 @@ function LegacyAdminStoryboardGenerator({
@@ -8776,7 +8776,7 @@ function LegacyAdminStoryboardGenerator({
@@ -8807,7 +8807,7 @@ function LegacyAdminStoryboardGenerator({ ) : null} {storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyError} @@ -8816,7 +8816,7 @@ function LegacyAdminStoryboardGenerator({ {storyboardBrowserOpenAIApiKeyMessage && !storyboardBrowserOpenAIApiKeyError ? (
{storyboardBrowserOpenAIApiKeyMessage} @@ -8826,7 +8826,7 @@ function LegacyAdminStoryboardGenerator({ > ) : (
@@ -8847,13 +8847,13 @@ function LegacyAdminStoryboardGenerator({
사용자 PC helper의 loopback 브릿지만 호출합니다. 토큰 입력값은 적용 직후 비우며 컴포넌트 ref에만 있고 Web Storage에 저장되지 않습니다. 페이지 전환·복원·닫기 때 제거됩니다.
@@ -8887,7 +8887,7 @@ function LegacyAdminStoryboardGenerator({ type="button" variant="ghost" size="sm" - className="h-6 shrink-0 rounded-full px-2 text-[10px]" + className="h-6 shrink-0 rounded-full px-2 text-2xs" onClick={(event) => { event.preventDefault(); event.stopPropagation(); @@ -8899,7 +8899,7 @@ function LegacyAdminStoryboardGenerator({
{formatStoryboardJobStatusLabel(acceptedStoryboardJob.status)} @@ -9157,7 +9157,7 @@ function LegacyAdminStoryboardGenerator({ > {messageProgressLabel ? (
@@ -9246,14 +9246,14 @@ function LegacyAdminStoryboardGenerator({ preset.id } > - + {preset.label} - + {preset.segmentCount}컷 ·{" "} {preset.targetLengthMinutes}분 - + {preset.description} @@ -9289,7 +9289,7 @@ function LegacyAdminStoryboardGenerator({ type="button" variant="outline" size="sm" - className="h-8 rounded-full px-3 text-[11px]" + className="h-8 rounded-full px-3 text-2xs" onClick={handleStoryboardUsageGuideClick} disabled={ isGenerating || @@ -9305,7 +9305,7 @@ function LegacyAdminStoryboardGenerator({