Skip to content

feat: 워크스페이스 공지 기능 구현 (#19) - #19

Merged
seongjinss555 merged 2 commits into
developfrom
feat/#17/workspace-notices
Jul 8, 2026
Merged

feat: 워크스페이스 공지 기능 구현 (#19)#19
seongjinss555 merged 2 commits into
developfrom
feat/#17/workspace-notices

Conversation

@seongjinss555

@seongjinss555 seongjinss555 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Pull Request

작업 내용

  • 워크스페이스 공지 페이지를 추가했습니다.
  • 목업 데이터 기반으로 공지 작성, 수정, 삭제, 고정/고정 해제, 선택 상세보기를 구현했습니다.
  • 공지 도메인을 entities/notice, 공지 관리 기능을 features/manage-notices, 매장 운영 화면 조합을 views/store-operation/notices로 분리했습니다.

작업 결과

  • /workspaces/[workspaceId]/notices에서 공지 목록과 선택한 공지 본문을 확인할 수 있습니다.
  • 공지 작성 버튼으로 작성 폼을 열고 새 공지를 등록할 수 있습니다.
  • 각 공지 우측 메뉴에서 수정, 삭제, 고정/고정 해제를 선택할 수 있습니다.
  • 고정 공지는 목록 상단에 우선 노출됩니다.

변경 사항

Added

  • src/app/workspaces/[workspaceId]/notices/page.tsx
  • src/entities/notice
  • src/features/manage-notices
  • src/views/store-operation/notices

Changed

  • 없음

Fixed

  • 없음

실행화면

  • 전체 레이아웃
image
  • 수정/고정/삭제
image
  • 공지 작성
image

테스트

  • 로컬 실행 확인
  • 주요 시나리오 확인
  • 영향 범위 확인

검증 명령:

  • npm run lint
  • npm run typecheck
  • npm run build

리뷰 체크리스트

  • PR base branch가 올바릅니다. (feature/* -> develop, 배포 시 develop 또는 release/* -> main)
  • 브랜치명이 Type/#issue-number/description 형식을 따릅니다.
  • 커밋 메시지가 컨벤션을 따릅니다.
  • 불필요한 console.log, 주석, 임시 코드를 제거했습니다.
  • 타입 에러와 린트 에러를 확인했습니다.
  • CodeRabbit 1차 리뷰를 확인했습니다.
  • CodeRabbit 리뷰 반영 후 Discord에 공유했습니다.
  • 최소 1명 이상의 approve 후 merge합니다.

리뷰 요청사항

  • 팀프로젝트 도메인에서도 같은 공지 기능을 사용할 예정이라, features/manage-noticeswidgets에 의존하지 않도록 분리했습니다.
  • 다른 도메인에서는 아래 의존 방향으로 새 view만 조합하면 됩니다.
views/<domain>/notices
  -> features/manage-notices
    -> entities/notice
  • entities/notice: 공지 타입과 목업 데이터, 이후 Supabase row/mapper 위치
  • features/manage-notices: 작성, 수정, 삭제, 고정, 선택 상태와 공통 UI
  • views/<domain>/notices: 도메인별 초기 데이터, 문구, 레이아웃 조합
  • widgets/workspace-shell: import하지 않고 app layout에서만 공통 shell로 감싸는 구조 유지
  • 공지 템플릿 공유 방법
    src/views/team-project/notices/ui/TeamProjectNoticesView.tsx
    -> import {
    NoticeComposer,
    NoticeDetailPanel,
    NoticeList,
    useNoticeBoardState,
    } from '@/features/manage-notices'

관련 이슈

Closes #19
Ref #2

Summary by CodeRabbit

  • New Features
    • 워크스페이스 공지 화면을 추가해 공지 목록, 상세(고정 배지 포함), 작성/수정 컴포저를 한 자리에서 제공합니다.
    • 공지별 메뉴에서 수정, 삭제, 고정/해제를 지원합니다.
    • 공지 데이터 모델과 목업 기반 렌더링을 위한 타입/샘플 공지를 도입했습니다.
  • Bug Fixes
    • 공지 선택/편집/삭제/고정 변경 시에도 목록과 상세 패널 상태가 자연스럽게 연동되도록 개선했습니다.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

워크스페이스 공지사항 화면이 추가되었습니다. 공지 타입과 목데이터, 공지 상태 관리 훅, 작성·상세·목록 UI, 그리고 /workspaces/[workspaceId]/notices 라우트와 뷰 조합이 연결되었습니다.

Changes

공지사항 기능

Layer / File(s) Summary
공지 타입과 목데이터
src/entities/notice/model/notice.types.ts, src/entities/notice/model/mock-notices.ts, src/entities/notice/index.ts
Notice/NoticeFormValues 타입과 4개 항목의 mockNotices 목데이터가 정의되고, entities 배럴에서 재-export됩니다.
공지 보드 상태 관리
src/features/manage-notices/model/use-notice-board-state.ts
정렬 규칙(고정 우선, 생성일 내림차순), 선택/편집/컴포저 상태, 작성·수정·삭제·고정 토글 로직을 제공하는 useNoticeBoardState 훅이 추가됩니다.
공지 UI 컴포넌트
src/features/manage-notices/ui/NoticeComposer.tsx, src/features/manage-notices/ui/NoticeDetailPanel.tsx, src/features/manage-notices/ui/NoticeList.tsx, src/features/manage-notices/index.ts
작성/수정 폼, 공지 상세 패널, 목록 카드(메뉴/고정/삭제 포함) 컴포넌트가 추가되고 features 배럴에서 재-export됩니다.
공지 뷰와 라우트
src/views/store-operation/notices/ui/NoticesView.tsx, src/views/store-operation/notices/index.ts, src/app/workspaces/[workspaceId]/notices/page.tsx
NoticesView가 컴포저/리스트/상세 패널을 조합해 렌더링하며, 워크스페이스 라우트 페이지가 workspaceId를 전달합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RoutePage
  participant NoticesView
  participant useNoticeBoardState
  participant NoticeList
  participant NoticeDetailPanel

  RoutePage->>NoticesView: workspaceId 전달
  NoticesView->>useNoticeBoardState: mockNotices, workspaceId, authorName 전달
  useNoticeBoardState-->>NoticesView: notices / selectedNotice / handlers 반환
  NoticesView->>NoticeList: notices 및 선택/액션 전달
  NoticesView->>NoticeDetailPanel: selectedNotice 전달
Loading

Suggested reviewers: 0011810, JiWoongE, Kwon812

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 워크스페이스 공지 기능 구현이라는 변경의 핵심을 간결하게 잘 요약합니다.
Description check ✅ Passed 템플릿의 주요 섹션과 필요한 정보가 대부분 채워져 있어 설명이 충분합니다.
Linked Issues check ✅ Passed 공지 페이지, 목업 기반 CRUD/고정, 상세보기, 모듈 분리와 의존 방향이 이슈 #19 요구와 일치합니다.
Out of Scope Changes check ✅ Passed 제공된 변경은 공지 기능 구현 범위 안에 있으며, 명백한 무관 변경은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#17/workspace-notices

Comment @coderabbitai help to get the list of available commands.

@seongjinss555 seongjinss555 changed the title feat: 워크스페이스 공지 기능 구현 (#17) feat: 워크스페이스 공지 기능 구현 (#19) Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/manage-notices/model/use-notice-board-state.ts`:
- Around line 23-25: The createNoticeId helper currently uses Date.now(), which
can produce duplicate notice IDs when created within the same millisecond.
Update createNoticeId to generate a more collision-resistant ID, such as using
crypto.randomUUID(), and keep the notice- prefix if needed so callers in
use-notice-board-state still receive a stable string identifier.
- Around line 36-43: The initial selection in useNoticeBoardState is using the
unsorted initialNotices[0]?.id, which can differ from the first item in the
rendered sortedNotices list. Update the selectedNoticeId initialization so it
derives from the same sorted order used by sortNotices (the pinned-first,
latest-first list), keeping selectedNotice and the visible board consistent. Use
the useNoticeBoardState hook, sortNotices, and selectedNoticeId as the key
symbols when making the change.
- Around line 97-111: The deleteNotice flow in use-notice-board-state should not
call setSelectedNoticeId inside the setNotices functional updater, because that
updater must stay pure. Refactor deleteNotice to first compute nextNotices from
currentNotices, then perform setNotices and setSelectedNoticeId as separate
top-level updates using nextNotices and selectedNoticeId, and keep the
closeComposer handling for editingNoticeId unchanged.
- Around line 36-41: The notice board state is sorting the same data twice:
`useNoticeBoardState` already stores `notices` in sorted order via
`sortNotices`, so the `useMemo` for `sortedNotices` is redundant. Remove the
extra `useMemo`/`sortedNotices` path in `useNoticeBoardState`, keep `notices` as
the source of truth, and return `notices` directly instead of `notices:
sortedNotices`; use the existing `sortNotices`, `setNotices`, and state
initialization to locate the change.

In `@src/features/manage-notices/ui/NoticeComposer.tsx`:
- Around line 19-24: The NoticeComposer form submission is handled with onSubmit
and preventDefault, which makes the form depend on JavaScript. Update the Form
handling in NoticeComposer so it can be used with a standard form action pattern
when moving to Supabase/server actions, or otherwise keep the submit flow
explicitly client-only and avoid suggesting progressive enhancement. Ensure the
submit logic around onSubmit and onSubmit({ title, content }) is adjusted to
match the chosen approach.

In `@src/features/manage-notices/ui/NoticeList.tsx`:
- Around line 90-124: The dropdown actions in NoticeList should be exposed as an
accessible menu so screen readers can recognize and navigate it properly. Update
the menu container and its action buttons in the NoticeList component to use the
appropriate menu semantics, including a menu role on the popup and menuitem
roles on the interactive items, while keeping the existing open/close behavior
and action handlers (onEditNotice, onTogglePinned, onDeleteNotice,
setOpenMenuNoticeId) unchanged.
- Line 25: 드롭다운이 외부 클릭 시 닫히지 않는 문제를 해결해야 합니다. NoticeList의 openMenuNoticeId 상태를
유지하되, 메뉴가 열린 카드의 토글 버튼/메뉴 영역을 감싸는 요소에 menuRef를 연결하고 문서 클릭을 감지해 현재 열린 메뉴 외부를 클릭하면
setOpenMenuNoticeId(null)로 초기화하도록 추가하세요. handleToggle 같은 기존 열기/닫기 로직과 메뉴 렌더링 조건을
함께 확인해, 다른 공지 카드나 페이지의 다른 영역을 클릭했을 때 열린 메뉴가 자동으로 닫히게 수정하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8f3f7664-be8a-483a-bdfb-19116b7b2d10

📥 Commits

Reviewing files that changed from the base of the PR and between 3a83ba0 and ff326b0.

📒 Files selected for processing (11)
  • src/app/workspaces/[workspaceId]/notices/page.tsx
  • src/entities/notice/index.ts
  • src/entities/notice/model/mock-notices.ts
  • src/entities/notice/model/notice.types.ts
  • src/features/manage-notices/index.ts
  • src/features/manage-notices/model/use-notice-board-state.ts
  • src/features/manage-notices/ui/NoticeComposer.tsx
  • src/features/manage-notices/ui/NoticeDetailPanel.tsx
  • src/features/manage-notices/ui/NoticeList.tsx
  • src/views/store-operation/notices/index.ts
  • src/views/store-operation/notices/ui/NoticesView.tsx

Comment thread src/features/manage-notices/model/use-notice-board-state.ts
Comment thread src/features/manage-notices/model/use-notice-board-state.ts Outdated
Comment thread src/features/manage-notices/model/use-notice-board-state.ts Outdated
Comment thread src/features/manage-notices/model/use-notice-board-state.ts
Comment thread src/features/manage-notices/ui/NoticeComposer.tsx
onDeleteNotice,
onTogglePinned,
}: NoticeListProps) {
const [openMenuNoticeId, setOpenMenuNoticeId] = useState<string | null>(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

드롭다운 메뉴가 외부 클릭 시 닫히지 않음.

openMenuNoticeId를 토글 버튼과 메뉴 항목 클릭에서만 초기화하고 있어, 메뉴를 연 상태에서 문서의 다른 영역(예: 다른 공지 카드 선택 버튼)을 클릭하면 메뉴가 계속 열려 있습니다.

🛠️ 외부 클릭 시 메뉴 닫기 제안
+import { useEffect, useRef, useState } from 'react';
-import { useState } from 'react';
...
   const [openMenuNoticeId, setOpenMenuNoticeId] = useState<string | null>(null);
+  const menuRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    if (!openMenuNoticeId) return;
+
+    function handleClickOutside(event: MouseEvent) {
+      if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
+        setOpenMenuNoticeId(null);
+      }
+    }
+
+    document.addEventListener('mousedown', handleClickOutside);
+    return () => document.removeEventListener('mousedown', handleClickOutside);
+  }, [openMenuNoticeId]);

이 경우 메뉴가 열려있을 때만 열린 카드의 <div className="absolute top-4 right-4">ref={menuRef}를 연결하도록 구조를 조정해야 합니다.

Also applies to: 76-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-notices/ui/NoticeList.tsx` at line 25, 드롭다운이 외부 클릭 시 닫히지
않는 문제를 해결해야 합니다. NoticeList의 openMenuNoticeId 상태를 유지하되, 메뉴가 열린 카드의 토글 버튼/메뉴 영역을
감싸는 요소에 menuRef를 연결하고 문서 클릭을 감지해 현재 열린 메뉴 외부를 클릭하면 setOpenMenuNoticeId(null)로
초기화하도록 추가하세요. handleToggle 같은 기존 열기/닫기 로직과 메뉴 렌더링 조건을 함께 확인해, 다른 공지 카드나 페이지의 다른
영역을 클릭했을 때 열린 메뉴가 자동으로 닫히게 수정하세요.

Comment on lines +90 to +124
{isMenuOpen ? (
<div className="absolute right-0 z-10 mt-2 w-36 overflow-hidden rounded-xl border border-slate-200 bg-white py-2 text-sm font-bold text-slate-700 shadow-lg">
<button
type="button"
onClick={() => {
onEditNotice(notice.id);
setOpenMenuNoticeId(null);
}}
className="block w-full px-4 py-2 text-left hover:bg-indigo-50 hover:text-indigo-600"
>
수정
</button>
<button
type="button"
onClick={() => {
onTogglePinned(notice.id);
setOpenMenuNoticeId(null);
}}
className="flex w-full items-center gap-2 px-4 py-2 text-left hover:bg-indigo-50 hover:text-indigo-600"
>
<Pin className="h-4 w-4" aria-hidden="true" />
{notice.isPinned ? '고정 해제' : '고정'}
</button>
<button
type="button"
onClick={() => {
onDeleteNotice(notice.id);
setOpenMenuNoticeId(null);
}}
className="block w-full px-4 py-2 text-left text-rose-600 hover:bg-rose-50"
>
삭제
</button>
</div>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

드롭다운 메뉴에 접근성 role 미지정.

role="menu"/role="menuitem" 등이 없어 스크린 리더 사용자에게 메뉴로 인식되지 않을 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-notices/ui/NoticeList.tsx` around lines 90 - 124, The
dropdown actions in NoticeList should be exposed as an accessible menu so screen
readers can recognize and navigate it properly. Update the menu container and
its action buttons in the NoticeList component to use the appropriate menu
semantics, including a menu role on the popup and menuitem roles on the
interactive items, while keeping the existing open/close behavior and action
handlers (onEditNotice, onTogglePinned, onDeleteNotice, setOpenMenuNoticeId)
unchanged.

@seongjinss555
seongjinss555 force-pushed the feat/#17/workspace-notices branch from ff326b0 to f4a073e Compare July 7, 2026 08:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/features/manage-notices/model/use-notice-board-state.ts (2)

97-111: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

setState 업데이터 내부에서 다른 상태의 setter 호출.

setNotices의 함수형 업데이터 안에서 setSelectedNoticeId를 호출하고 있습니다(line 102). React 업데이터 함수는 순수해야 하며, Strict Mode에서는 개발 중 해당 콜백이 두 번 호출되어 부수 효과가 중복될 수 있습니다. 이전 리뷰에서 이미 지적된 사항으로, 아직 수정되지 않아 재제기합니다.

🔧 제안하는 수정
   const deleteNotice = (noticeId: string) => {
-    setNotices((currentNotices) => {
-      const nextNotices = currentNotices.filter((notice) => notice.id !== noticeId);
-
-      if (selectedNoticeId === noticeId) {
-        setSelectedNoticeId(sortNotices(nextNotices)[0]?.id ?? null);
-      }
-
-      return nextNotices;
-    });
+    setNotices((currentNotices) => currentNotices.filter((notice) => notice.id !== noticeId));
+
+    if (selectedNoticeId === noticeId) {
+      setSelectedNoticeId(
+        sortNotices(notices.filter((notice) => notice.id !== noticeId))[0]?.id ?? null,
+      );
+    }
 
     if (editingNoticeId === noticeId) {
       closeComposer();
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-notices/model/use-notice-board-state.ts` around lines 97
- 111, In use-notice-board-state, deleteNotice is still calling
setSelectedNoticeId from inside the setNotices functional updater, which makes
the updater impure. Move the selected-notice recalculation out of the setNotices
callback so it only computes nextNotices there, then update selectedNoticeId
afterward using the same deleteNotice flow; keep the closeComposer logic in
deleteNotice and use the existing sortNotices helper and
selectedNoticeId/editingNoticeId checks to preserve behavior.

36-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

초기 선택 공지가 정렬된 목록과 불일치할 수 있음.

selectedNoticeId는 line 37에서 정렬 전 initialNotices[0]?.id를 사용하지만, 실제 렌더링되는 목록은 sortedNotices(고정 우선, 최신순)입니다. 고정 공지가 원본 배열 맨 앞이 아니면 초기 선택 공지와 목록 최상단 공지가 달라집니다. 이전 리뷰에서 이미 지적된 사항으로, 아직 수정되지 않아 재제기합니다.

🐛 제안하는 수정
   const [notices, setNotices] = useState(() => sortNotices(initialNotices));
-  const [selectedNoticeId, setSelectedNoticeId] = useState(initialNotices[0]?.id ?? null);
+  const [selectedNoticeId, setSelectedNoticeId] = useState(
+    () => sortNotices(initialNotices)[0]?.id ?? null,
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-notices/model/use-notice-board-state.ts` around lines 36
- 43, The initial selected notice in useNoticeBoardState is being derived from
initialNotices before sorting, so it can differ from the first item in the
rendered sorted list. Update selectedNoticeId to initialize from the sorted
result produced by sortNotices, or otherwise derive it from the same sorted
ordering used by sortedNotices, so the initial selection always matches the top
notice shown in the UI.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/views/store-operation/notices/ui/NoticesView.tsx`:
- Around line 50-74: The selected notice body is being rendered twice on xl+
screens because NoticeList shows the inline body for isSelected items while
NoticeDetailPanel also displays the same notice in the sticky sidebar. Update
the selection rendering in NoticeList and/or the xl-only sidebar in NoticesView
so the inline body is limited to smaller screens only, keeping a single source
of detail display on wide layouts.
- Around line 30-34: `NoticesView` is passing the full `mockNotices` list into
`useNoticeBoardState`, so `/workspaces/:id/notices` shows the same initial
notices for every workspace. Update the `initialNotices` setup in `NoticesView`
or the `useNoticeBoardState` initialization logic so notices are filtered by
`workspaceId` before being used. Make sure the workspace-specific filtering
happens around the `useNoticeBoardState` call and any related initial state
handling for `mockNotices`/`workspaceId`.

---

Duplicate comments:
In `@src/features/manage-notices/model/use-notice-board-state.ts`:
- Around line 97-111: In use-notice-board-state, deleteNotice is still calling
setSelectedNoticeId from inside the setNotices functional updater, which makes
the updater impure. Move the selected-notice recalculation out of the setNotices
callback so it only computes nextNotices there, then update selectedNoticeId
afterward using the same deleteNotice flow; keep the closeComposer logic in
deleteNotice and use the existing sortNotices helper and
selectedNoticeId/editingNoticeId checks to preserve behavior.
- Around line 36-43: The initial selected notice in useNoticeBoardState is being
derived from initialNotices before sorting, so it can differ from the first item
in the rendered sorted list. Update selectedNoticeId to initialize from the
sorted result produced by sortNotices, or otherwise derive it from the same
sorted ordering used by sortedNotices, so the initial selection always matches
the top notice shown in the UI.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 59dab173-9d70-4a3a-b133-0a75f6917f31

📥 Commits

Reviewing files that changed from the base of the PR and between ff326b0 and f4a073e.

📒 Files selected for processing (11)
  • src/app/workspaces/[workspaceId]/notices/page.tsx
  • src/entities/notice/index.ts
  • src/entities/notice/model/mock-notices.ts
  • src/entities/notice/model/notice.types.ts
  • src/features/manage-notices/index.ts
  • src/features/manage-notices/model/use-notice-board-state.ts
  • src/features/manage-notices/ui/NoticeComposer.tsx
  • src/features/manage-notices/ui/NoticeDetailPanel.tsx
  • src/features/manage-notices/ui/NoticeList.tsx
  • src/views/store-operation/notices/index.ts
  • src/views/store-operation/notices/ui/NoticesView.tsx

Comment on lines +50 to +74
<div className="grid items-start gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<div className="space-y-5">
{isComposerOpen ? (
<NoticeComposer
key={editingNotice?.id ?? 'new-notice'}
editingNotice={editingNotice}
onSubmit={submitNotice}
onCancel={closeComposer}
/>
) : null}

<NoticeList
notices={notices}
selectedNoticeId={selectedNotice?.id ?? null}
onSelectNotice={selectNotice}
onEditNotice={openEditComposer}
onDeleteNotice={deleteNotice}
onTogglePinned={togglePinned}
/>
</div>

<div className="sticky top-6 hidden xl:block">
<NoticeDetailPanel notice={selectedNotice} />
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

xl 이상 화면에서 선택된 공지 본문 중복 노출 가능성.

NoticeListisSelected일 때 화면 크기와 무관하게 카드 내부에 본문을 인라인으로 노출합니다(NoticeList.tsx Line 69-73). 동시에 Line 71의 사이드 상세 패널(hidden xl:block)도 동일한 공지 본문을 보여주므로, xl 이상 화면에서는 같은 내용이 두 곳에 중복 표시됩니다.

xl 이상에서는 리스트 내 인라인 본문을 숨기거나, 인라인 본문 자체를 좁은 화면 전용으로 제한하는 것을 고려해보세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/store-operation/notices/ui/NoticesView.tsx` around lines 50 - 74,
The selected notice body is being rendered twice on xl+ screens because
NoticeList shows the inline body for isSelected items while NoticeDetailPanel
also displays the same notice in the sticky sidebar. Update the selection
rendering in NoticeList and/or the xl-only sidebar in NoticesView so the inline
body is limited to smaller screens only, keeping a single source of detail
display on wide layouts.

seongjinss555 pushed a commit that referenced this pull request Jul 7, 2026
@seongjinss555
seongjinss555 force-pushed the feat/#17/workspace-notices branch from 5a42e45 to a9f7b06 Compare July 8, 2026 00:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/entities/notice/model/mock-notices.ts`:
- Around line 4-44: The mock notices in mock-notices.ts are hardcoded to a
single workspaceId, which will break once workspace-aware filtering is
introduced. Update the fixture so each notice uses a configurable or
per-workspace workspaceId value instead of the fixed 'test' string, keeping
submitNotice and any future workspace-scoped consumers aligned with realistic
data.

In `@src/views/store-operation/notices/ui/NoticesView.tsx`:
- Around line 30-34: `useNoticeBoardState` is receiving `workspaceId` but
`NoticesView` still passes the full `mockNotices` list, so the initial notice
set can include other workspaces. Update the `NoticesView` setup to filter
`initialNotices` by the current `workspaceId` before passing them into
`useNoticeBoardState`, using the existing `workspaceId` and `mockNotices`
symbols so only notices for the active workspace are initialized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b7fc1225-d0a1-45ff-965d-b0c420fae823

📥 Commits

Reviewing files that changed from the base of the PR and between 5a42e45 and a9f7b06.

📒 Files selected for processing (11)
  • src/app/workspaces/[workspaceId]/notices/page.tsx
  • src/entities/notice/index.ts
  • src/entities/notice/model/mock-notices.ts
  • src/entities/notice/model/notice.types.ts
  • src/features/manage-notices/index.ts
  • src/features/manage-notices/model/use-notice-board-state.ts
  • src/features/manage-notices/ui/NoticeComposer.tsx
  • src/features/manage-notices/ui/NoticeDetailPanel.tsx
  • src/features/manage-notices/ui/NoticeList.tsx
  • src/views/store-operation/notices/index.ts
  • src/views/store-operation/notices/ui/NoticesView.tsx

Comment on lines +4 to +44
{
id: 'notice-1',
workspaceId: 'test',
title: '7월 신메뉴 출시 안내',
authorName: '김민서',
createdAt: '2025-06-28',
isPinned: true,
content:
"7월 1일부터 여름 한정 '망고 라떼'와 '피치 에이드'가 출시됩니다. 레시피 숙지 부탁드립니다.",
},
{
id: 'notice-2',
workspaceId: 'test',
title: '주간 청소 구역 배정',
authorName: '이준혁',
createdAt: '2025-06-26',
isPinned: false,
content:
'이번 주 청소 구역 배정표를 확인해주세요. 마감 담당자는 냉장고 하단과 픽업대 주변을 추가로 점검해 주세요.',
},
{
id: 'notice-3',
workspaceId: 'test',
title: '유니폼 교체 안내',
authorName: '김민서',
createdAt: '2025-06-24',
isPinned: false,
content:
'신규 유니폼이 입고되었습니다. 이번 주 출근 시 기존 유니폼을 반납하고 새 유니폼을 수령해 주세요.',
},
{
id: 'notice-4',
workspaceId: 'test',
title: '카드 단말기 교체 완료',
authorName: '이준혁',
createdAt: '2025-06-22',
isPinned: false,
content:
'카드 단말기 교체가 완료되었습니다. 결제 오류가 반복되면 매니저에게 바로 공유해 주세요.',
},
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

mock 데이터의 workspaceId가 모두 'test'로 고정됨

실제 라우트에서는 동적 workspaceId가 전달되지만(submitNotice에서 신규 공지 생성 시 실제 workspaceId 사용), mock 데이터는 항상 'test'로 고정되어 있습니다. 현재는 workspaceId로 필터링하는 로직이 없어 문제되지 않지만, 추후 Supabase 연동 시 workspaceId 필터링이 추가되면 이 mock fixture가 깨질 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/notice/model/mock-notices.ts` around lines 4 - 44, The mock
notices in mock-notices.ts are hardcoded to a single workspaceId, which will
break once workspace-aware filtering is introduced. Update the fixture so each
notice uses a configurable or per-workspace workspaceId value instead of the
fixed 'test' string, keeping submitNotice and any future workspace-scoped
consumers aligned with realistic data.

Comment on lines +30 to +34
} = useNoticeBoardState({
initialNotices: mockNotices,
workspaceId,
authorName: mockCurrentWorkspaceMember.workspaceNickname,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "workspaceId" src/features/manage-notices/model/use-notice-board-state.ts

Repository: TeampleRun/syncly

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Outline: use-notice-board-state.ts"
ast-grep outline src/features/manage-notices/model/use-notice-board-state.ts --view expanded || true

echo
echo "[2] Read use-notice-board-state.ts"
wc -l src/features/manage-notices/model/use-notice-board-state.ts
cat -n src/features/manage-notices/model/use-notice-board-state.ts | sed -n '1,220p'

echo
echo "[3] Outline: NoticesView.tsx"
ast-grep outline src/views/store-operation/notices/ui/NoticesView.tsx --view expanded || true

echo
echo "[4] Read NoticesView.tsx"
wc -l src/views/store-operation/notices/ui/NoticesView.tsx
cat -n src/views/store-operation/notices/ui/NoticesView.tsx | sed -n '1,220p'

echo
echo "[5] Search for NoticeDetailPanel and NoticeList usage"
rg -n "NoticeDetailPanel|NoticeList|isSelected|sticky" src/views/store-operation/notices src/features/manage-notices -S

Repository: TeampleRun/syncly

Length of output: 9720


workspaceId 기준으로 초기 공지 목록을 좁혀야 합니다. useNoticeBoardStateworkspaceId를 받지만 initialNotices를 그대로 정렬해서 쓰고 있어, 상위에서 워크스페이스별로 걸러주지 않으면 다른 워크스페이스 공지가 섞여 보일 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/store-operation/notices/ui/NoticesView.tsx` around lines 30 - 34,
`useNoticeBoardState` is receiving `workspaceId` but `NoticesView` still passes
the full `mockNotices` list, so the initial notice set can include other
workspaces. Update the `NoticesView` setup to filter `initialNotices` by the
current `workspaceId` before passing them into `useNoticeBoardState`, using the
existing `workspaceId` and `mockNotices` symbols so only notices for the active
workspace are initialized.

@Kwon812 Kwon812 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

고생하셨습니다~

@seongjinss555
seongjinss555 merged commit a5da39b into develop Jul 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants