diff --git a/src/entities/meeting-note/index.ts b/src/entities/meeting-note/index.ts index ab2d43e..4c77ed4 100644 --- a/src/entities/meeting-note/index.ts +++ b/src/entities/meeting-note/index.ts @@ -12,7 +12,6 @@ export { toMeetingNoteUpdate, toMeetingDate, toMeetingAt, - getParticipantColor, MEETING_NOTE_SELECT_QUERY, type MeetingNoteQueryRow, } from './model/meeting-note.mapper'; diff --git a/src/entities/meeting-note/model/meeting-note.mapper.ts b/src/entities/meeting-note/model/meeting-note.mapper.ts index 66775c3..5c1e1fb 100644 --- a/src/entities/meeting-note/model/meeting-note.mapper.ts +++ b/src/entities/meeting-note/model/meeting-note.mapper.ts @@ -1,3 +1,4 @@ +import { getAvatarColor } from '@/shared/lib/avatar-color'; import type { GenericTablesInsert, GenericTablesUpdate } from '@/shared/model/supabase.types'; import type { MeetingNoteRow } from './meeting-note.db.types'; @@ -19,28 +20,8 @@ export type MeetingNoteQueryRow = Pick< export const MEETING_NOTE_SELECT_QUERY = 'id, workspace_id, author_id, title, meeting_at, participants, decisions, follow_up_actions'; -// 참석자 아바타 색상은 DB에 저장하지 않고 userId 해시로 결정론적으로 재생성한다. -// 같은 참석자는 어느 카드에서든 항상 같은 색으로 보인다. -const PARTICIPANT_PALETTE = [ - '#FE9A00', - '#00C950', - '#615FFF', - '#2B7FFF', - '#00B8DB', - '#FF6B6B', -] as const; - const KST_TIME_ZONE = 'Asia/Seoul'; -export function getParticipantColor(seed: string): string { - let hash = 0; - for (let index = 0; index < seed.length; index += 1) { - hash = (hash * 31 + seed.charCodeAt(index)) >>> 0; - } - - return PARTICIPANT_PALETTE[hash % PARTICIPANT_PALETTE.length]; -} - // DB의 meeting_at(timestamptz) → UI 표기용 KST 날짜(YYYY-MM-DD) export function toMeetingDate(meetingAt: string): string { const parts = new Intl.DateTimeFormat('en-CA', { @@ -73,7 +54,7 @@ function toParticipant(userId: string, profileNameById: Map): Me id: userId, name, initial: name.slice(0, 1), - color: getParticipantColor(userId), + color: getAvatarColor(userId), }; } diff --git a/src/entities/task/model/task.mapper.ts b/src/entities/task/model/task.mapper.ts index 3355604..c229b0b 100644 --- a/src/entities/task/model/task.mapper.ts +++ b/src/entities/task/model/task.mapper.ts @@ -1,18 +1,9 @@ +import { getAvatarColor } from '@/shared/lib/avatar-color'; import type { GenericTablesInsert, GenericTablesUpdate } from '@/shared/model/supabase.types'; import type { TaskRow, TaskStatusDb } from './task.db.types'; import type { Task, TaskStatus } from './task.types'; -const AVATAR_PALETTE = [ - '#00C950', - '#FE9A00', - '#615FFF', - '#00B8DB', - '#2B7FFF', - '#F6339A', - '#7E22CE', -] as const; - export interface TaskQueryRow extends Pick< TaskRow, 'id' | 'workspace_id' | 'title' | 'assignee_id' | 'due_date' | 'status' | 'sort_order' @@ -52,15 +43,6 @@ function formatDueDate(isoDate: string | null) { return `${Number(month)}/${Number(day)}`; } -function getAvatarColor(seed: string) { - let hash = 0; - for (let index = 0; index < seed.length; index += 1) { - hash = (hash * 31 + seed.charCodeAt(index)) >>> 0; - } - - return AVATAR_PALETTE[hash % AVATAR_PALETTE.length]; -} - export function toTask(row: TaskQueryRow): Task { const assigneeName = row.assignee_profile?.real_name ?? '미배정'; @@ -71,7 +53,8 @@ export function toTask(row: TaskQueryRow): Task { assigneeId: row.assignee_id, assignee: assigneeName, assigneeInitial: assigneeName.slice(0, 1), - assigneeColor: getAvatarColor(assigneeName), + // 색상은 userId(assignee_id) 기준 — 미배정이면 shared 유틸이 중립색을 돌려준다. + assigneeColor: getAvatarColor(row.assignee_id), dueDate: formatDueDate(row.due_date), status: toUiTaskStatus(row.status), sortOrder: row.sort_order, diff --git a/src/features/manage-chat/ui/ChatMessageList.tsx b/src/features/manage-chat/ui/ChatMessageList.tsx index e7218b4..e510430 100644 --- a/src/features/manage-chat/ui/ChatMessageList.tsx +++ b/src/features/manage-chat/ui/ChatMessageList.tsx @@ -3,6 +3,7 @@ // 메시지를 발신자 기준 말풍선으로 렌더링하고 새 메시지 도착 시 하단으로 스크롤합니다. import { useEffect, useRef } from 'react'; import type { ChatMessage } from '@/entities/chat'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; import { cn } from '@/shared/lib/utils'; interface ChatMessageListProps { @@ -83,7 +84,10 @@ export function ChatMessageList({ messages, viewerId, isLoading }: ChatMessageLi className={cn('flex items-end gap-3', isOwnMessage ? 'justify-end' : 'justify-start')} > {!isOwnMessage ? ( -
+
{getAvatarLabel(message.senderName)}
) : null} @@ -99,7 +103,7 @@ export function ChatMessageList({ messages, viewerId, isLoading }: ChatMessageLi

{isOwnMessage ? ( -

+
{getAvatarLabel(message.senderName)}
) : null} diff --git a/src/features/manage-meeting-notes/ui/MeetingNoteForm.tsx b/src/features/manage-meeting-notes/ui/MeetingNoteForm.tsx index 7cc9232..ab284bf 100644 --- a/src/features/manage-meeting-notes/ui/MeetingNoteForm.tsx +++ b/src/features/manage-meeting-notes/ui/MeetingNoteForm.tsx @@ -9,6 +9,7 @@ import { toast } from 'sonner'; import type { MeetingNote, MeetingNoteFormValues } from '@/entities/meeting-note'; import { createMeetingNote, meetingNotesQueryKey, updateMeetingNote } from '@/entities/meeting-note'; import { useWorkspaceMembersByWorkspaceId } from '@/entities/workspace-member'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; interface MeetingNoteFormProps { workspaceId: string; @@ -425,7 +426,10 @@ export function MeetingNoteForm({ workspaceId, meetingNote }: MeetingNoteFormPro className="hover:bg-brand-soft flex w-full items-center justify-between rounded-[14px] px-3 py-3 text-left transition" >
-
+
{member.avatarLabel}
diff --git a/src/features/manage-sprint-tasks/lib/avatar-color.ts b/src/features/manage-sprint-tasks/lib/avatar-color.ts deleted file mode 100644 index aa661ee..0000000 --- a/src/features/manage-sprint-tasks/lib/avatar-color.ts +++ /dev/null @@ -1,19 +0,0 @@ -// 담당자 아바타 배경색 — Task 모델에는 색상 필드가 없어, 표시명 기반으로 결정론적 색을 고른다. -// 같은 담당자는 항상 같은 색이 나오도록 간단한 해시로 팔레트를 선택한다. -const AVATAR_PALETTE = [ - '#00C950', - '#FE9A00', - '#615FFF', - '#00B8DB', - '#2B7FFF', - '#F6339A', - '#7E22CE', -]; - -export function getAvatarColor(seed: string): string { - let hash = 0; - for (let i = 0; i < seed.length; i += 1) { - hash = (hash * 31 + seed.charCodeAt(i)) >>> 0; - } - return AVATAR_PALETTE[hash % AVATAR_PALETTE.length]; -} diff --git a/src/features/manage-sprint-tasks/ui/TaskCard.tsx b/src/features/manage-sprint-tasks/ui/TaskCard.tsx index 9b204ac..dc9148c 100644 --- a/src/features/manage-sprint-tasks/ui/TaskCard.tsx +++ b/src/features/manage-sprint-tasks/ui/TaskCard.tsx @@ -7,8 +7,9 @@ import { Pencil, Trash2 } from 'lucide-react'; import { TASK_CATEGORY } from '@/entities/side-project/task'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; + import type { BoardTask } from '../model/board-task'; -import { getAvatarColor } from '../lib/avatar-color'; interface TaskCardProps { task: BoardTask; @@ -59,7 +60,7 @@ export function TaskCard({
{task.assignee.avatarLabel} diff --git a/src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx b/src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx index 3481828..73de9dc 100644 --- a/src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx +++ b/src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx @@ -16,7 +16,7 @@ import { import type { WorkspaceMember } from '@/entities/workspace-member'; import { cn } from '@/shared/lib/utils'; -import { getAvatarColor } from '../lib/avatar-color'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; import { EMPTY_TASK_FORM, type TaskFormValues } from '../model/task-form'; const inputClass = @@ -181,7 +181,7 @@ export function TaskFormDialog({ > {member.avatarLabel} diff --git a/src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx b/src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx index a2848e5..4604537 100644 --- a/src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx +++ b/src/features/manage-work-schedule/ui/WorkScheduleBoard.tsx @@ -19,6 +19,7 @@ import { updateWorkShiftType, } from '@/entities/work-schedule/api/work-schedule-actions'; import type { WorkspaceMember } from '@/entities/workspace-member'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; import { Dialog, DialogContent, @@ -218,7 +219,10 @@ export function WorkScheduleBoard({ className="grid grid-cols-[150px_repeat(7,minmax(96px,1fr))] border-b border-slate-100" >
- + {member.avatarLabel} @@ -284,7 +288,8 @@ export function WorkScheduleBoard({ {workMembers.map((member) => ( {member.avatarLabel} diff --git a/src/features/manage-workspace-members/ui/MemberList.tsx b/src/features/manage-workspace-members/ui/MemberList.tsx index de581f3..e3f38e0 100644 --- a/src/features/manage-workspace-members/ui/MemberList.tsx +++ b/src/features/manage-workspace-members/ui/MemberList.tsx @@ -6,6 +6,7 @@ import { WORKSPACE_MEMBER_STATUS_META, type WorkspaceMember, } from '@/entities/workspace-member'; +import { getAvatarColor } from '@/shared/lib/avatar-color'; import { Badge } from '@/shared/ui/badge'; import { cn } from '@/shared/lib/utils'; @@ -14,16 +15,6 @@ interface MemberListProps { currentUserId: string; } -// 아바타 배경 색상 팔레트 — 멤버 순서에 따라 순환 배정한다. -const AVATAR_COLORS = [ - 'bg-orange-400', - 'bg-indigo-400', - 'bg-emerald-400', - 'bg-rose-400', - 'bg-sky-400', - 'bg-amber-400', -]; - export function MemberList({ members, currentUserId }: MemberListProps) { return (
@@ -35,7 +26,7 @@ export function MemberList({ members, currentUserId }: MemberListProps) {

) : (
    - {members.map((member, index) => { + {members.map((member) => { const roleMeta = WORKSPACE_MEMBER_ROLE_META[member.role]; const statusMeta = WORKSPACE_MEMBER_STATUS_META[member.status]; const isCurrentUser = member.userId === currentUserId; @@ -49,10 +40,8 @@ export function MemberList({ members, currentUserId }: MemberListProps) { )} >