diff --git a/client/src/features/main/domains/DomainCard.tsx b/client/src/features/main/domains/DomainCard.tsx
index f3f28bb..9964da8 100644
--- a/client/src/features/main/domains/DomainCard.tsx
+++ b/client/src/features/main/domains/DomainCard.tsx
@@ -10,18 +10,23 @@ type Props = {
};
export function DomainCard({ icon, title, desc, onClick, disabled }: Props) {
+ const isDisabled = !!disabled;
+ const buttonLabel = isDisabled ? "설정 완료" : "설정하기";
+
return (
-
+
{icon}
{title}
{desc}
);
diff --git a/client/src/features/main/domains/domains.module.scss b/client/src/features/main/domains/domains.module.scss
index 6e766fe..6487f55 100644
--- a/client/src/features/main/domains/domains.module.scss
+++ b/client/src/features/main/domains/domains.module.scss
@@ -70,4 +70,21 @@
}
-
\ No newline at end of file
+.buttonDisabled {
+ color: #757373;
+ border-color: #d0d3d8;
+ cursor: default;
+}
+
+.buttonDisabled:hover {
+ color: #757373;
+ border-color: #d0d3d8;
+}
+
+.card.disabled,
+.card.disabled:hover {
+ transform: none !important;
+ box-shadow: 0 10px 30px rgba(2, 6, 23, .06);
+ background: #ffffff !important;
+ cursor: default !important;
+}
\ No newline at end of file
diff --git a/client/src/features/notice/NoticeSetting.tsx b/client/src/features/notice/NoticeSetting.tsx
index 0a8073e..1ba8465 100644
--- a/client/src/features/notice/NoticeSetting.tsx
+++ b/client/src/features/notice/NoticeSetting.tsx
@@ -273,7 +273,12 @@ function NoticeCard({
// 편집 중이거나 버튼/인풋 클릭 시에는 선택 동작 안함
if (editing) return;
const target = e.target as HTMLElement;
- if (target.closest("button") || target.closest("input") || target.closest("a")) return;
+ if (
+ target.closest("button") ||
+ target.closest("input") ||
+ target.closest("a")
+ )
+ return;
// 이미 선택된 카드면 선택 해제, 아니면 선택
onSelect(isSelected ? null : setting.domain_id);
@@ -281,7 +286,9 @@ function NoticeCard({
return (
@@ -437,7 +444,7 @@ function NoticeCard({
>
) : (
<>
- {/*
*/}
+
{/* 다중 배지 표시 */}
@@ -453,7 +460,7 @@ function NoticeCard({
)}
- {/* 요약 상태 토글 (읽기 전용) */}
+ {/* 요약 상태 토글 (바로 수정 가능) */}
요약
@@ -461,12 +468,25 @@ function NoticeCard({
{summary ? "ON" : "OFF"}
{
+ e.stopPropagation(); // 카드 선택 방지
+ const newSummary = !summary;
+ setSummary(newSummary);
+ try {
+ await updateSetting(getId(setting), { summary: newSummary });
+ onUpdated({ ...setting, summary: newSummary });
+ } catch {
+ setSummary(!newSummary); // 실패 시 롤백
+ show("요약 설정 변경에 실패했습니다.");
+ }
+ }}
+ style={{ cursor: "pointer" }}
>
-
+
@@ -490,7 +510,10 @@ interface NoticeSettingProps {
onSelectDomain: (domainId: string | null) => void;
}
-function NoticeSettingInner({ selectedDomainId, onSelectDomain }: NoticeSettingProps) {
+function NoticeSettingInner({
+ selectedDomainId,
+ onSelectDomain,
+}: NoticeSettingProps) {
const [items, setItems] = useState
([]);
const [settingsMap, setSettingsMap] = useState>({});
const [domainMap, setDomainMap] = useState>({});
@@ -613,9 +636,15 @@ function NoticeSettingInner({ selectedDomainId, onSelectDomain }: NoticeSettingP
);
}
-const NoticeSetting: React.FC = ({ selectedDomainId, onSelectDomain }) => (
+const NoticeSetting: React.FC = ({
+ selectedDomainId,
+ onSelectDomain,
+}) => (
-
+
);
diff --git a/client/src/features/notice/RecentNotice.tsx b/client/src/features/notice/RecentNotice.tsx
index 3bab1a9..b822bf8 100644
--- a/client/src/features/notice/RecentNotice.tsx
+++ b/client/src/features/notice/RecentNotice.tsx
@@ -1,5 +1,5 @@
// src/features/notice/RecentNotice.tsx
-import React, { useEffect, useMemo, useRef, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./RecentNotice.scss";
import "../../../public/assets/style/_flex.scss";
import "../../../public/assets/style/_typography.scss";
@@ -27,6 +27,7 @@ type RecentItem = {
const INITIAL_COUNT = 10;
const PAGE_SIZE = 5;
+const POLLING_INTERVAL = 500; // 0.5초마다 새 알림 확인
const pad2 = (n: number) => (n < 10 ? `0${n}` : String(n));
@@ -113,26 +114,28 @@ const RecentNotice: React.FC = ({ selectedDomainId }) => {
const [allItems, setAllItems] = useState([]);
const [visibleCount, setVisibleCount] = useState(INITIAL_COUNT);
- // 초기 로드: settings + domains
- useEffect(() => {
- let alive = true;
- (async () => {
- try {
+ // 데이터 로드 함수
+ const loadData = useCallback(async (isInitial = false) => {
+ try {
+ if (isInitial) {
setLoading(true);
setBanner(null);
+ }
+
+ const [settings, domainList] = await Promise.all([
+ fetchSettings(),
+ fetchMain(),
+ ]);
- const [settings, domainList] = await Promise.all([
- fetchSettings(),
- fetchMain(),
- ]);
- if (!alive) return;
+ const flat = flattenAndSortMessages(settings);
+ setAllItems(flat);
+ setDomains(domainList);
- const flat = flattenAndSortMessages(settings);
- setAllItems(flat);
- setDomains(domainList);
+ if (isInitial) {
setVisibleCount(Math.min(INITIAL_COUNT, flat.length));
- } catch (e: any) {
- if (!alive) return;
+ }
+ } catch (e: any) {
+ if (isInitial) {
setBanner({
type: "error",
text: e?.message || "최근 알림을 불러오지 못했습니다.",
@@ -140,15 +143,26 @@ const RecentNotice: React.FC = ({ selectedDomainId }) => {
setAllItems([]);
setDomains([]);
setVisibleCount(0);
- } finally {
- if (alive) setLoading(false);
}
- })();
- return () => {
- alive = false;
- };
+ } finally {
+ if (isInitial) setLoading(false);
+ }
}, []);
+ // 초기 로드
+ useEffect(() => {
+ loadData(true);
+ }, [loadData]);
+
+ // 주기적 polling (30초마다)
+ useEffect(() => {
+ const interval = setInterval(() => {
+ loadData(false);
+ }, POLLING_INTERVAL);
+
+ return () => clearInterval(interval);
+ }, [loadData]);
+
// 선택된 도메인으로 필터링
const filteredItems = useMemo(() => {
if (!selectedDomainId) return allItems;
diff --git a/server/src/crawl/webCrawler.ts b/server/src/crawl/webCrawler.ts
index cf2c93b..8de02ae 100644
--- a/server/src/crawl/webCrawler.ts
+++ b/server/src/crawl/webCrawler.ts
@@ -39,7 +39,7 @@ export class WebCrawler {
case 'pknu':
crawler = new PKNUCrawler();
break;
- case 'pknu-notice-watch':
+ case 'preview--pknu-notice-watch':
console.log(`[WebCrawler] TestCrawler 사용: ${url}`);
crawler = new TestCrawler(url);
break;
diff --git a/server/src/data/initialDomains.ts b/server/src/data/initialDomains.ts
index 27cd414..e0c4bcb 100644
--- a/server/src/data/initialDomains.ts
+++ b/server/src/data/initialDomains.ts
@@ -43,7 +43,7 @@ export const initialDomains: InitialDomainData[] = [
_id: "4",
name: "테스트",
url_list: [
- "https://pknu-notice-watch.lovable.app",
+ "https://preview--pknu-notice-watch.lovable.app",
],
keywords: ["테스트"],
setting_ids: [],