feat: [approval] 완료함 미읽음 알림 뱃지 기능 추가

- approvals 테이블에 drafter_read_at 컬럼 추가 (API 마이그레이션)
- 승인/반려/전결 완료 시 drafter_read_at = null 설정
- getBadgeCounts()에 completed_unread 카운트 추가
- 사이드메뉴 완료함에 미읽음 뱃지 표시 (주황색)
- 완료함 페이지 진입 시 일괄 읽음 처리
- 상세 페이지 열람 시 개별 읽음 처리
This commit is contained in:
김보곤
2026-03-05 11:36:58 +09:00
parent 999cbad667
commit 280367170a
7 changed files with 69 additions and 0 deletions

View File

@@ -494,6 +494,34 @@ public function badgeCounts(): JsonResponse
return response()->json(['success' => true, 'data' => $counts]);
}
/**
* 완료함 읽음 처리 (일괄)
*/
public function markCompletedAsRead(): JsonResponse
{
$count = $this->service->markCompletedAsRead(auth()->id());
return response()->json([
'success' => true,
'message' => $count > 0 ? "{$count}건 읽음 처리되었습니다." : '새로운 완료 건이 없습니다.',
'data' => ['marked_count' => $count],
]);
}
/**
* 개별 문서 기안자 읽음 처리
*/
public function markReadSingle(int $id): JsonResponse
{
$approval = $this->service->getApproval($id);
if ($approval->drafter_id === auth()->id() && ! $approval->drafter_read_at) {
$approval->update(['drafter_read_at' => now()]);
}
return response()->json(['success' => true]);
}
// =========================================================================
// 첨부파일
// =========================================================================

View File

@@ -20,6 +20,7 @@ class Approval extends Model
'attachments' => 'array',
'drafted_at' => 'datetime',
'completed_at' => 'datetime',
'drafter_read_at' => 'datetime',
'current_step' => 'integer',
'is_urgent' => 'boolean',
];
@@ -38,6 +39,7 @@ class Approval extends Model
'department_id',
'drafted_at',
'completed_at',
'drafter_read_at',
'current_step',
'attachments',
'recall_reason',

View File

@@ -78,6 +78,7 @@ public function boot(): void
$menuBadges['byUrl']['/approval-mgmt/pending'] = ['count' => $counts['pending'], 'color' => '#ef4444'];
$menuBadges['byUrl']['/approval-mgmt/drafts'] = ['count' => $counts['draft'], 'color' => '#3b82f6'];
$menuBadges['byUrl']['/approval-mgmt/references'] = ['count' => $counts['reference_unread'], 'color' => '#10b981'];
$menuBadges['byUrl']['/approval-mgmt/completed'] = ['count' => $counts['completed_unread'], 'color' => '#f59e0b'];
} catch (\Throwable $e) {
// 테이블 미존재 등 예외 무시
}

View File

@@ -303,6 +303,7 @@ public function approve(int $id, ?string $comment = null): Approval
$approval->update([
'status' => Approval::STATUS_APPROVED,
'completed_at' => now(),
'drafter_read_at' => null,
'updated_by' => auth()->id(),
]);
@@ -344,6 +345,7 @@ public function reject(int $id, string $comment): Approval
$approval->update([
'status' => Approval::STATUS_REJECTED,
'completed_at' => now(),
'drafter_read_at' => null,
'updated_by' => auth()->id(),
]);
@@ -507,6 +509,7 @@ public function preDecide(int $id, ?string $comment = null): Approval
$approval->update([
'status' => Approval::STATUS_APPROVED,
'completed_at' => now(),
'drafter_read_at' => null,
'updated_by' => auth()->id(),
]);
@@ -753,13 +756,30 @@ public function getBadgeCounts(int $userId): array
->where('is_read', false)
->count();
$completedUnreadCount = Approval::where('drafter_id', $userId)
->whereIn('status', [Approval::STATUS_APPROVED, Approval::STATUS_REJECTED])
->whereNull('drafter_read_at')
->count();
return [
'pending' => $pendingCount,
'draft' => $draftCount,
'reference_unread' => $referenceUnreadCount,
'completed_unread' => $completedUnreadCount,
];
}
/**
* 완료함 미읽음 일괄 읽음 처리
*/
public function markCompletedAsRead(int $userId): int
{
return Approval::where('drafter_id', $userId)
->whereIn('status', [Approval::STATUS_APPROVED, Approval::STATUS_REJECTED])
->whereNull('drafter_read_at')
->update(['drafter_read_at' => now()]);
}
// =========================================================================
// Private 헬퍼
// =========================================================================