-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature] 개인별 그룹 색상 할당 기능 구현 #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
13c738f
9cb07cc
ebf783e
5bb0b2d
5195835
53eba0e
0561f71
ae1e3db
1f55a9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
|
|
||
| import { type GroupColorKey } from '@/constants/groupColors'; | ||
| import { useGroupsQuery } from '@/features/groups/api/queries'; | ||
| import { useGroupColorKey } from '@/features/groups/hooks/useGroupColorKey'; | ||
| import { theme } from '@/theme'; | ||
|
|
||
| const DEFAULT_COLOR_KEY: GroupColorKey = theme.color.primary[500]; | ||
|
|
||
| export const useCategoryColor = (categoryId: number) => { | ||
| const { data: groupsData } = useGroupsQuery(); | ||
|
|
||
| const matchingGroupId = | ||
| groupsData?.find((g) => g.categoryIds.includes(categoryId))?.groupId ?? null; | ||
|
|
||
| const { colorKey: groupColor } = useGroupColorKey(matchingGroupId); | ||
| const colorKey = groupColor ?? DEFAULT_COLOR_KEY; | ||
|
|
||
| return { colorKey }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { useQuery, useQueryClient } from '@tanstack/react-query'; | ||
|
|
||
| import type { GroupColorKey } from '@/constants/groupColors'; | ||
| import { GROUP_COLORS } from '@/constants/groupColors'; | ||
| import { useProfileQuery } from '@/features/user/api/queries'; | ||
| import { ASYNC_KEYS, asyncStorage } from '@/utils/asyncStorage'; | ||
|
|
||
| const GROUP_COLOR_KEYS = Object.keys(GROUP_COLORS) as GroupColorKey[]; | ||
|
|
||
| const pickRandomGroupColorKey = (): GroupColorKey => | ||
| GROUP_COLOR_KEYS[Math.floor(Math.random() * GROUP_COLOR_KEYS.length)]; | ||
|
|
||
| const isGroupColorKey = (v: string): v is GroupColorKey => | ||
| (GROUP_COLOR_KEYS as readonly string[]).includes(v); | ||
|
|
||
| export const useGroupColorKey = (groupId: number | null) => { | ||
| const { data: profileData } = useProfileQuery(); | ||
| const userId = profileData?.id; | ||
| const qc = useQueryClient(); | ||
|
|
||
| const queryKey = ['groupColor', userId, groupId]; | ||
|
|
||
| const { data: colorKey } = useQuery({ | ||
| queryKey, | ||
| enabled: userId != null && groupId != null, | ||
| queryFn: async () => { | ||
| if (userId == null || groupId == null) return null; | ||
|
|
||
| const storageKey = ASYNC_KEYS.groupColor(userId, groupId); | ||
| const stored = await asyncStorage.get(storageKey); | ||
|
|
||
| if (stored && isGroupColorKey(stored)) return stored; | ||
|
|
||
| const newKey = pickRandomGroupColorKey(); | ||
| await asyncStorage.set(storageKey, newKey); | ||
| return newKey; | ||
| }, | ||
| staleTime: Infinity, | ||
| gcTime: Infinity, | ||
| }); | ||
|
|
||
| const updateColorKey = async (next: GroupColorKey) => { | ||
| if (userId == null || groupId == null) return; | ||
|
|
||
| qc.setQueryData(queryKey, next); | ||
| await asyncStorage.set(ASYNC_KEYS.groupColor(userId, groupId), next); | ||
| }; | ||
|
|
||
| return { colorKey: colorKey ?? null, updateColorKey }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ export const useStartStudyMutation = () => { | |
| studyStatByDateQueryOptions(new Date()), | ||
| ); | ||
| // TODO: 24시간이 넘어가는 공부시간에 대해 어떻게 처리할 것인지? | ||
| // TODO: StudyDataHydrator와 중복되는 로직 리팩토링 | ||
| const category = studyDataUtils.identifyCategory({ | ||
| categories: categories.data, | ||
| categoryId: response.data.categoryId, | ||
|
|
@@ -35,13 +36,7 @@ export const useStartStudyMutation = () => { | |
| const todayRecord = todayStudyRecords.find( | ||
| record => record.categoryId === response.data.categoryId, | ||
| ); | ||
| if (todayRecord == null) { | ||
| // eslint-disable-next-line no-console | ||
| console.error('Today study record must be defined when starting study session'); | ||
| return; | ||
| } | ||
|
|
||
| const recordedStudyTime = todayRecord.time; | ||
| const recordedStudyTime = todayRecord?.time ?? Time.fromMilliseconds(0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const progressiveStudyTime = Time.fromMilliseconds( | ||
| new Date().getTime() - response.data.startTime.getTime(), | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ const StackableScreenLayout = ({ | |
| headerElementColor = theme.color.primary[500], | ||
| header, | ||
| children, | ||
| safeAreas, | ||
| safeAreas = 'top', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ...screenContainerProps | ||
| }: StackableScreenProps) => { | ||
| const navigation = useStackNavigation(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ import { GroupButton } from '@features/groups/ui'; | |
|
|
||
| import { Button, Icon, Panel } from '@/components'; | ||
| import { useGroupsQuery } from '@/features/groups/api/queries'; | ||
| import { useGroupColorKey } from '@/features/groups/hooks/useGroupColorKey'; | ||
| import type { GetGroupsResponse } from '@/features/groups/model/groups'; | ||
| import { useStackNavigation } from '@/hooks'; | ||
| import { useTranslatedText } from '@/hooks/useTranslatedText'; | ||
| import { theme } from '@/theme'; | ||
|
|
@@ -14,34 +16,14 @@ const JoinedGroupPanel = () => { | |
| const tCreateNewGroup = useTranslatedText({ tKey: 'groups.create-new-group' }); | ||
| const navigation = useStackNavigation(); | ||
| const { data, isPending } = useGroupsQuery(); | ||
|
|
||
| if (!data || isPending) return null; | ||
|
|
||
| return ( | ||
| <Panel.View gap={theme.spacing[250]}> | ||
| <Panel.View.Header title={tMyParticipatedGroups} /> | ||
| {data.length ? | ||
| data.map(group => { | ||
| const leaderImageUrl = | ||
| group.profiles.find(profile => profile.role === 'LEADER')?.imageUrl ?? null; | ||
| const otherImageUrls = group.profiles | ||
| .filter(profile => profile.role !== 'LEADER') | ||
| .map(profile => profile.imageUrl); | ||
| return ( | ||
| <GroupButton | ||
| achievedDailyGoalCount={5} | ||
| chatRoomId={group.chatRoomId} | ||
| // TODO: 그룹 색상 관리 방식 논의 후 수정 필요 | ||
| groupColorKey={theme.color.blue[300]} | ||
| groupId={group.groupId} | ||
| groupName={group.groupName} | ||
| isPublic={group.isOpen} | ||
| key={group.groupId} | ||
| participantImageUrls={[leaderImageUrl, ...otherImageUrls]} | ||
| totalDailyGoalCount={10} | ||
| /> | ||
| ); | ||
| }) | ||
| data.map(group => <GroupListItem group={group} key={group.groupId} />) | ||
| : | ||
|
Comment on lines
+26
to
27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| <EmptyFallback />} | ||
| <Button.Basic | ||
|
|
@@ -55,4 +37,29 @@ const JoinedGroupPanel = () => { | |
| ); | ||
| }; | ||
|
|
||
| const GroupListItem = ({ group }: { group: GetGroupsResponse['data'][number] }) => { | ||
| const { colorKey: groupColorKey } = useGroupColorKey(group.groupId); | ||
| if (!groupColorKey) return null; | ||
|
Comment on lines
40
to
42
|
||
|
|
||
| const leaderImageUrl = | ||
| group.profiles.find(p => p.role === 'LEADER')?.imageUrl ?? null; | ||
|
|
||
| const otherImageUrls = group.profiles | ||
| .filter(p => p.role !== 'LEADER') | ||
| .map(p => p.imageUrl); | ||
|
|
||
| return ( | ||
| <GroupButton | ||
| achievedDailyGoalCount={5} | ||
| chatRoomId={group.chatRoomId} | ||
| groupColorKey={groupColorKey} | ||
| groupId={group.groupId} | ||
| groupName={group.groupName} | ||
| isPublic={group.isOpen} | ||
| participantImageUrls={[leaderImageUrl, ...otherImageUrls]} | ||
| totalDailyGoalCount={10} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| export default JoinedGroupPanel; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"todayRecord"가 "null"일 경우 "console.error"를 호출하고 함수를 종료하는 대신, "?? Time.fromMilliseconds(0)"를 사용하여 기본값을 제공하도록 변경한 것은 더 견고한 오류 처리 방식입니다. 이는 애플리케이션의 안정성을 높이고 예기치 않은 종료를 방지합니다.