Skip to content

Commit

Permalink
Merge pull request #114 from Atractorrr/refactor/inbox-ui-#ATR-571
Browse files Browse the repository at this point in the history
보관함 필터 UI 개편
  • Loading branch information
LC-02s authored Jul 21, 2024
2 parents fdb9d96 + cfcabfb commit e4d80fb
Show file tree
Hide file tree
Showing 18 changed files with 563 additions and 290 deletions.
1 change: 1 addition & 0 deletions apps/service/src/entities/user-article/constant/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as DEFAULT_LIST_SIZE } from './defaultListSize'
export * from './sortButton'
11 changes: 11 additions & 0 deletions apps/service/src/entities/user-article/constant/sortButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SortType } from '../model'

export const SORT_LABEL: Record<SortType, string> = {
'receivedAt,desc': '최신 순',
'receivedAt,asc': '오래된 순',
}

export const SORT_MENU: Array<[SortType, string]> = [
['receivedAt,desc', SORT_LABEL['receivedAt,desc']],
['receivedAt,asc', SORT_LABEL['receivedAt,asc']],
]
2 changes: 1 addition & 1 deletion apps/service/src/entities/user-article/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export { default as useCheckUserArticleBookmarkQuery } from './useCheckUserArtic
export { default as useInfiniteUserArticlesQuery } from './useInfiniteUserArticlesQuery'
export { default as useTrackingUserArticleScroll } from './useTrackingUserArticleScroll'
export { default as useUserArticleQuery } from './useUserArticleQuery'
export { default as useUserCategoriesQuery } from './useUserCategoriesQuery'
export * from './useUserCategoriesQuery'
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
'use client'

import { useQuery } from '@tanstack/react-query'
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { useAuth } from '@/entities/auth'
import { getUserCategories } from '../api'
import userArticleQueryKeys from './userArticleQueryKeys'

export default function useUserCategoriesQuery() {
export function useUserCategoriesQuery({ enabled }: { enabled?: boolean }) {
const { userEmail } = useAuth()

return useQuery({
queryKey: userArticleQueryKeys.userCategories({ userEmail }),
queryFn: () => getUserCategories({ userEmail }),
enabled,
})
}

export function useUserCategoriesSuspenseQuery() {
const { userEmail } = useAuth()

return useSuspenseQuery({
queryKey: userArticleQueryKeys.userCategories({ userEmail }),
queryFn: () => getUserCategories({ userEmail }),
})
}
80 changes: 80 additions & 0 deletions apps/service/src/features/filter-article/ui/CategoryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client'

import { PropsWithChildren, useState } from 'react'
import { ChevronDownOutline, TagOutline } from '@attraction/icons'
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@attraction/design-system'
import { useCheckDevice } from '@/shared/lib'
import type { NewsletterCategory } from '@/shared/type'
import { NEWSLETTER_CATEGORY } from '@/shared/constant'
import FilterButton from './FilterButton'
import CategoryDropdown from './CategoryDropdown'
import CategorySelectForDrawer from './CategorySelectForDrawer'

export interface CategorySelectProps {
selectedCategory?: NewsletterCategory
setCategory: (category?: NewsletterCategory) => void
}

function DropdownToDrawer({
children,
...props
}: PropsWithChildren<CategorySelectProps>) {
const { isMobileView } = useCheckDevice()
const [isOpen, setOpen] = useState(false)
const close = () => setOpen(false)

if (isMobileView) {
return (
<Drawer open={isOpen} onOpenChange={setOpen}>
<DrawerTrigger asChild>{children}</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>카테고리 선택</DrawerTitle>
</DrawerHeader>
<CategorySelectForDrawer {...props} close={close} />
<DrawerFooter>
<DrawerClose asChild>
<button
type="button"
title="닫기"
className="w-full whitespace-nowrap rounded-lg bg-gray-50 px-6 py-3 transition-colors active:bg-gray-100 xs:text-lg md:px-10 dark:bg-gray-700 dark:active:bg-gray-600"
onClick={close}>
닫기
</button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}

return (
<CategoryDropdown {...props} open={isOpen} onOpenChange={setOpen}>
{children}
</CategoryDropdown>
)
}

export default function CategoryButton({ ...props }: CategorySelectProps) {
return (
<DropdownToDrawer {...props}>
<FilterButton active={!!props.selectedCategory} title="카테고리 변경">
<TagOutline className="text-lg xs:text-xl" />
<span className="whitespace-nowrap text-sm xs:text-base">
{props.selectedCategory
? NEWSLETTER_CATEGORY[props.selectedCategory]
: '카테고리'}
</span>
<ChevronDownOutline className="text-base" />
</FilterButton>
</DropdownToDrawer>
)
}
72 changes: 41 additions & 31 deletions apps/service/src/features/filter-article/ui/CategoryDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,67 @@
'use client'

import type { PropsWithChildren } from 'react'
import {
DropdownMenuRadioItem,
DropdownMenuRadioGroup,
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@attraction/design-system/dist'
import { useUserCategoriesQuery } from '@/entities/user-article'
import { NewsletterCategory } from '@/shared/type'
import { NEWSLETTER_CATEGORY } from '@/shared/constant'
import { LoadingSpinner, WarnTxt } from '@/shared/ui'
import type { CategorySelectProps } from './CategoryButton'

export interface CategoryDropdownProps {
selectedCategory?: NewsletterCategory
setCategory: (category?: NewsletterCategory) => void
interface CategoryDropdownProps extends PropsWithChildren<CategorySelectProps> {
open?: boolean
onOpenChange?: (status: boolean) => void
}

export default function CategoryDropdown({
selectedCategory,
setCategory,
open,
onOpenChange,
children,
}: CategoryDropdownProps) {
const { data, isLoading, isError } = useUserCategoriesQuery()
const { data, isLoading, isError } = useUserCategoriesQuery({ enabled: open })

return (
<>
{isLoading && <LoadingSpinner />}
{isError && (
<div className="p-3">
<WarnTxt content="카테고리를 불러오지 못했어요" color="red" />
</div>
)}
{data && (
<>
{data.length === 0 && <WarnTxt content="구독한 뉴스레터가 없어요" />}
<DropdownMenuRadioGroup value={selectedCategory ?? 'all'}>
{data.length > 0 && (
<DropdownMenu open={open} onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent className="w-52">
{isLoading && <LoadingSpinner />}
{isError && (
<div className="p-3">
<WarnTxt content="카테고리를 불러오지 못했어요" color="red" />
</div>
)}
{data && (
<>
{data.length === 0 && (
<WarnTxt content="구독한 뉴스레터가 없어요" type="info" />
)}
<DropdownMenuRadioGroup value={selectedCategory ?? 'all'}>
<DropdownMenuRadioItem
value="all"
title="카테고리 선택: 전체"
onClick={() => setCategory(undefined)}>
전체
</DropdownMenuRadioItem>
)}
{data.map((category) => (
<DropdownMenuRadioItem
key={category}
title={`카테고리 선택: ${NEWSLETTER_CATEGORY[category]}`}
value={category}
onClick={() => setCategory(category)}>
{NEWSLETTER_CATEGORY[category]}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</>
)}
</>
{data.map((category) => (
<DropdownMenuRadioItem
key={category}
title={`카테고리 선택: ${NEWSLETTER_CATEGORY[category]}`}
value={category}
onClick={() => setCategory(category)}>
{NEWSLETTER_CATEGORY[category]}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client'

import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { Checkbox } from '@attraction/design-system'
import { useUserCategoriesSuspenseQuery } from '@/entities/user-article'
import { NEWSLETTER_CATEGORY } from '@/shared/constant'
import { GuideTxt, LoadingSpinner } from '@/shared/ui'
import type { CategorySelectProps } from './CategoryButton'

interface CategorySelectForDrawerProps extends CategorySelectProps {
close: () => void
}

function UserCategorySelect({
selectedCategory,
setCategory,
close,
}: CategorySelectForDrawerProps) {
const { data } = useUserCategoriesSuspenseQuery()

return (
<>
<li className="flex px-3">
<Checkbox
className="block w-full rounded-lg p-2 transition-colors active:bg-gray-50 dark:active:bg-gray-700"
checked={!selectedCategory}
onChange={() => {
setCategory(undefined)
close()
}}
label="전체"
/>
</li>
{data.map((category) => (
<li key={category} className="flex px-3">
<Checkbox
className="block w-full rounded-lg p-2 transition-colors active:bg-gray-50 dark:active:bg-gray-700"
checked={category === selectedCategory}
onChange={() => {
setCategory(category)
close()
}}
label={NEWSLETTER_CATEGORY[category]}
/>
</li>
))}
</>
)
}

export default function CategorySelectForDrawer(
props: CategorySelectForDrawerProps,
) {
return (
<ErrorBoundary
fallback={
<div className="scrollbar-hidden h-[48vh] overflow-y-auto px-5 py-16">
<GuideTxt
title="카테고리를 불러오지 못했어요"
sub="동일한 현상이 지속된다면 관리자에게 문의해주세요"
/>
</div>
}>
<ul className="scrollbar-hidden h-[48vh] overflow-y-auto">
<Suspense fallback={<LoadingSpinner />}>
<UserCategorySelect {...props} />
</Suspense>
</ul>
</ErrorBoundary>
)
}
34 changes: 34 additions & 0 deletions apps/service/src/features/filter-article/ui/FilterButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import React, {
DetailedHTMLProps,
InputHTMLAttributes,
LegacyRef,
forwardRef,
} from 'react'

const FilterButton = forwardRef<
HTMLInputElement,
DetailedHTMLProps<
InputHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> & { active?: boolean }
>(({ type, className, active: isActive = false, onClick, ...props }, ref) => (
<button
ref={ref as LegacyRef<HTMLButtonElement> | undefined}
type="button"
className={`flex h-10 items-center justify-center gap-2 rounded-lg px-3 py-2 transition-colors ${
isActive
? 'bg-blue-50 text-blue-400 dark:bg-gray-50 dark:text-gray-700'
: 'bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600'
}`}
onClick={(e) => {
onClick?.(e)
e.currentTarget?.scrollIntoView({ inline: 'center', behavior: 'smooth' })
}}
{...props}
/>
))
FilterButton.displayName = 'DefaultFilterButton'

export default FilterButton
Loading

0 comments on commit e4d80fb

Please sign in to comment.