feat: 일정 참석자 알림(지정/수정/삭제)

일정 CRUD 시 참석자에게 인앱 알림 발송(기존 NotificationService + SSE 재사용).

- 생성: 참석자로 지정된 사용자에게 schedule.invited (전 직원 일정은 개별 알림 생략)
- 수정: 신규 참석자 → schedule.invited, 기존 참석자 → schedule.updated
- 삭제: 참석자에게 schedule.removed (삭제 전 참석자 확보)
- 행위자 본인은 자동 제외, payload(actorName/eventTitle/start), 클릭 시 /schedule 이동
- NotificationType(백/프론트) 3종 추가 + notification.store 렌더 케이스

런타임 검증: 지정/수정/삭제 알림이 참석자에게만 정상 도달.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 13:56:03 +09:00
parent a344945e4e
commit 42bf97636a
6 changed files with 111 additions and 9 deletions
+17
View File
@@ -27,6 +27,8 @@ function toView(n: ApiNotification): NotificationView {
projectId && taskSeq !== null
? `/projects/${projectId}/tasks/${taskSeq}`
: null
// 일정 알림용
const eventTitle = escapeHtml(pstr(p, 'eventTitle'))
let tone = ''
let html = '새 알림'
@@ -95,6 +97,21 @@ function toView(n: ApiNotification): NotificationView {
to = projectId ? `/projects/${projectId}` : null
html = `<b>${actor}</b>님이 <b>${projectName}</b> 프로젝트에서 회원님을 제외했습니다`
break
case 'schedule.invited':
tone = 'accent'
to = '/schedule'
html = `<b>${actor}</b>님이 <b>${eventTitle}</b> 일정에 회원님을 참석자로 지정했습니다`
break
case 'schedule.updated':
tone = 'blue'
to = '/schedule'
html = `<b>${actor}</b>님이 참석 일정 <b>${eventTitle}</b>을(를) 수정했습니다`
break
case 'schedule.removed':
tone = 'red'
to = '/schedule'
html = `<b>${actor}</b>님이 참석 일정 <b>${eventTitle}</b>을(를) 삭제했습니다`
break
}
return { id: n.id, read: n.read, tone, html, time: relativeTimeKo(n.createdAt), to }
}
+3
View File
@@ -17,6 +17,9 @@ export type NotificationType =
| 'member.invited'
| 'member.role_changed'
| 'member.removed'
| 'schedule.invited'
| 'schedule.updated'
| 'schedule.removed'
// 알림(API 원시) — 표시 문구/링크는 프론트가 type+payload 로 조립
export interface ApiNotification {