diff --git a/src/pages/subscription/Subscription.css b/src/pages/subscription/Subscription.css
index c095b2e..ca355dd 100644
--- a/src/pages/subscription/Subscription.css
+++ b/src/pages/subscription/Subscription.css
@@ -1534,3 +1534,18 @@ input.sub-toggle:checked + .sub-toggle__label { color: var(--accent-subscription
grid-template-columns: 1fr;
}
}
+
+/* === 신규: 알림 대상 카운트 뱃지 ===================================== */
+.ns-pass-count {
+ display: inline-block;
+ margin-left: 8px;
+ padding: 1px 8px;
+ border-radius: 10px;
+ background: rgba(0, 212, 255, 0.12);
+ color: #00d4ff;
+ font-size: 11px;
+ font-weight: 600;
+}
+.ns-pass-count strong {
+ font-weight: 700;
+}
diff --git a/src/pages/subscription/Subscription.jsx b/src/pages/subscription/Subscription.jsx
index 19b9844..6e764db 100644
--- a/src/pages/subscription/Subscription.jsx
+++ b/src/pages/subscription/Subscription.jsx
@@ -618,16 +618,52 @@ function AnnouncementDetail({ item, onBookmark }) {
{item.match_score !== undefined && item.match_score !== null && (
-
-
매칭 분석
-
- ⭐ {item.match_score} / 100
-
+
+
+
매칭 분석
+
+ ⭐ {item.match_score} / 100
+
+
+ {item.score_breakdown && (
+
+
📊 점수 분석
+
+ {[
+ { key: 'region', label: '지역', max: 35, color: '#00d4ff' },
+ { key: 'type', label: '유형', max: 10, color: '#8b5cf6' },
+ { key: 'area', label: '면적', max: 15, color: '#f59e0b' },
+ { key: 'price', label: '가격', max: 15, color: '#f43f5e' },
+ { key: 'eligibility', label: '자격', max: 25, color: '#34d399' },
+ ].map(({ key, label, max, color }) => {
+ const v = item.score_breakdown[key] ?? 0;
+ return (
+
+
+ {label}
+
+ {v}
+ / {max}
+
+
+
+
+ );
+ })}
+
+
+ )}
+
{item.match_reasons && item.match_reasons.length > 0 && (
-
💡 매칭 사유
+
💡 매칭 사유
{item.match_reasons.map((r, idx) => (
- {r}
@@ -973,6 +1009,24 @@ function MatchesTab() {
).join(' · ')}
)}
+ {match.score_breakdown && (
+
+ {[
+ { key: 'region', max: 35, color: '#00d4ff' },
+ { key: 'type', max: 10, color: '#8b5cf6' },
+ { key: 'area', max: 15, color: '#f59e0b' },
+ { key: 'price', max: 15, color: '#f43f5e' },
+ { key: 'eligibility', max: 25, color: '#34d399' },
+ ].map(({ key, max, color }) => {
+ const v = match.score_breakdown[key] ?? 0;
+ return (
+
+ );
+ })}
+
+ )}
@@ -1026,6 +1080,7 @@ function MatchesTab() {
// ── ProfileTab ────────────────────────────────────────────────────────────────
function ProfileTab() {
const [profile, setProfile] = useState({ ...DEFAULT_PROFILE });
+ const [passCount, setPassCount] = useState(null);
const [saving, setSaving] = useState(false);
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState('');
@@ -1034,13 +1089,17 @@ function ProfileTab() {
(async () => {
setLoading(true);
try {
- const data = await apiGet('/api/realestate/profile');
+ const [data, dash] = await Promise.all([
+ apiGet('/api/realestate/profile'),
+ apiGet('/api/realestate/dashboard').catch(() => null),
+ ]);
if (data && Object.keys(data).length > 0) {
const display = { ...DEFAULT_PROFILE, ...data };
if (Array.isArray(display.preferred_regions)) display.preferred_regions = display.preferred_regions.join(', ');
if (Array.isArray(display.preferred_types)) display.preferred_types = display.preferred_types.join(', ');
setProfile(display);
}
+ if (dash?.pass_count != null) setPassCount(dash.pass_count);
} catch (e) {
console.error('Profile load error:', e);
} finally {
@@ -1379,6 +1438,7 @@ function ProfileTab() {
minScore={profile.min_match_score ?? 70}
notifyEnabled={profile.notify_enabled ?? true}
onChange={(patch) => setProfile(prev => ({ ...prev, ...patch }))}
+ passCount={passCount}
/>
diff --git a/src/pages/subscription/components/NotificationSettings.jsx b/src/pages/subscription/components/NotificationSettings.jsx
index d65443e..bde5027 100644
--- a/src/pages/subscription/components/NotificationSettings.jsx
+++ b/src/pages/subscription/components/NotificationSettings.jsx
@@ -1,4 +1,4 @@
-export default function NotificationSettings({ minScore, notifyEnabled, onChange }) {
+export default function NotificationSettings({ minScore, notifyEnabled, onChange, passCount }) {
const score = minScore ?? 70;
const enabled = notifyEnabled ?? true;
@@ -42,9 +42,16 @@ export default function NotificationSettings({ minScore, notifyEnabled, onChange
- {enabled
- ? `💡 ${score}점 이상 매치 시 텔레그램에 자동 알림합니다.`
- : "⚠️ 알림 OFF — 임계값을 통과한 매칭이 있어도 메시지가 발송되지 않습니다."}
+ {enabled ? (
+ <>
+ 💡 {score}점 이상 매치 시 텔레그램에 자동 알림합니다.
+ {passCount != null && (
+
+ 현재 {passCount}건 대상
+
+ )}
+ >
+ ) : "⚠️ 알림 OFF — 임계값을 통과한 매칭이 있어도 메시지가 발송되지 않습니다."}