Skip to content

Commit

Permalink
Merge pull request #59 from sangmaaaaan/Feat/user-detail-type
Browse files Browse the repository at this point in the history
[TSK-55] add dropdown atom-component
  • Loading branch information
swgvenghy authored Nov 5, 2024
2 parents 1df876e + 3cee920 commit d8fcce9
Show file tree
Hide file tree
Showing 13 changed files with 671 additions and 65 deletions.
433 changes: 412 additions & 21 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand Down
2 changes: 1 addition & 1 deletion src/api/get-admission-detail-type.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const getAllDetailType = async () => {

export const getDetailType = async ({ type }: TypeStatusProps) => {
try {
const response = await server_axiosInstance.get(`/api/admissions/detail/${type}`);
const response = await server_axiosInstance.get(`/api/admissions/details/${type}`);
return response.data;
} catch (error: any) {
throw new Error('get all detail type fail', error);
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/use-chat-section.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import useTypeStore, { TypeCategoryState } from '../store/type-category-store';
const useChatSection = () => {
const { setSelectedType, setSelectedCategory } = useTypeStore();
const [selectedTypeButton, setSelectedTypeButton] = React.useState<TypeCategoryState['type']>(undefined);
const [selectedCategoryButton, setSelectedCategoryButton] = React.useState<TypeCategoryState['category']>(undefined);
const [selectedCategoryButton, setSelectedCategoryButton] =
React.useState<TypeCategoryState['category']>('ADMISSION_GUIDELINE');
const handleTypeButtonClick = (selectedType: TypeCategoryState['type']) => {
setSelectedType(selectedType);
setSelectedTypeButton(selectedType);
Expand Down
35 changes: 35 additions & 0 deletions src/hooks/use-detail-type-prompt.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useUserDetailTypeStore } from '../store/user-detail-type-store';

interface DetailTypeItem {
middleName: string;
lastNames: string[];
}

export const useDetailTypePrompt = () => {
const { detailTypeData } = useUserDetailTypeStore();

const itemMap = new Map<string, Set<string>>();

detailTypeData.forEach((item) => {
const [middle, lastWithBracket] = item.name.split('(');
const middleTrimmed = middle.trim();
const lastTrimmed = lastWithBracket ? lastWithBracket.replace(/\)$/, '').trim() : '';

if (!itemMap.has(middleTrimmed)) {
itemMap.set(middleTrimmed, new Set<string>());
}

if (lastTrimmed) {
itemMap.get(middleTrimmed)?.add(lastTrimmed);
}
});

const itemList: DetailTypeItem[] = Array.from(itemMap.entries()).map(([middleName, lastNamesSet]) => ({
middleName,
lastNames: Array.from(lastNamesSet),
}));

return {
itemList,
};
};
15 changes: 15 additions & 0 deletions src/hooks/use-preset-button.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,24 @@ const usePresetButton = () => {
}
};

const handleDetailTypeButtonClick = async (detailTypeName: string) => {
try {
addMessage({ content: detailTypeName, role: 'user' });
addMessage({ content: 'loading', role: 'system' });
setLoading(true);

const response = await fetchResponse(`${detailTypeName}에 대해 설명해줘`);
updateStateWithResponse(response);
} catch (error) {
setLoading(false);
updateLastMessage('답변 생성에 실패했습니다. 새로고침해주세요');
}
};

return {
handleReferenceButtonClick,
handleButtonClick,
handleDetailTypeButtonClick,
};
};

Expand Down
4 changes: 2 additions & 2 deletions src/store/type-category-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { create } from 'zustand';

export interface TypeCategoryState {
type: undefined | 'SUSI' | 'PYEONIP' | 'JEONGSI';
category: undefined | 'ADMISSION_GUIDELINE' | 'PASSING_RESULT' | 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST';
category: 'ADMISSION_GUIDELINE' | 'PASSING_RESULT' | 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST';
setSelectedType: (button: TypeCategoryState['type']) => void;
setSelectedCategory: (button: TypeCategoryState['category']) => void;
}

const useTypeStore = create<TypeCategoryState>()((set) => ({
type: undefined,
category: undefined,
category: 'ADMISSION_GUIDELINE',
setSelectedType: (button) => set({ type: button }),
setSelectedCategory: (button) => set({ category: button }),
}));
Expand Down
34 changes: 34 additions & 0 deletions src/store/user-detail-type-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create } from 'zustand';
export interface DetailTypeItem {
middleName: string;
lastNames: string[];
}

//TSK-53 admission-detail-type-store.ts와 일부 겹침 나중에 병합 필요성
export interface UserDetailTypeState {
type: 'SUSI' | 'JEONGSI' | 'PYEONIP';
id: number;
name: string;
}

interface UserDetailTypeStoreState {
detailTypeData: UserDetailTypeState[];
updateDetailTypeData: (data: UserDetailTypeState[]) => void;
loading: boolean;
selectedName: string;
setSelectedName: (name: string) => void;
setLoading: (load: boolean) => void;
itemsArray: DetailTypeItem[];
setItemArray: (dataArray: DetailTypeItem[]) => void;
}

export const useUserDetailTypeStore = create<UserDetailTypeStoreState>((set) => ({
detailTypeData: [],
loading: false,
itemsArray: [],
selectedName: '',
setSelectedName: (name) => set({ selectedName: name }),
updateDetailTypeData: (data) => set({ detailTypeData: data }),
setLoading: (load) => set({ loading: load }),
setItemArray: (data) => set({ itemsArray: data }),
}));
4 changes: 3 additions & 1 deletion src/ui/components/atom/chat-card/chat-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import Loader from '../loader/loader';
interface ChatCardProps {
content: string;
role: 'user' | 'system';
children?: React.ReactNode;
}
const ChatCard = ({ content, role }: ChatCardProps) => {
const ChatCard = ({ content, role, children }: ChatCardProps) => {
return (
<div>
{role === 'user' ? null : <img src={maru} className="mt-2 h-8 w-8" />}
Expand All @@ -26,6 +27,7 @@ const ChatCard = ({ content, role }: ChatCardProps) => {
>
<div className="text-md max-w-full font-pretendard font-normal">
{content === 'loading' ? <Loader /> : <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>}
{children}
</div>
</div>
</div>
Expand Down
67 changes: 67 additions & 0 deletions src/ui/components/atom/dropdown/dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { Meta, StoryFn } from '@storybook/react';
import Dropdown from './dropdown';

export default {
title: 'Components/Dropdown',
component: Dropdown,
argTypes: {
type: {
control: { type: 'select' },
options: ['SUSI', 'JEONGSI', 'PYEONIP'],
},
},
} as Meta<typeof Dropdown>;

const Template: StoryFn<typeof Dropdown> = (args) => <Dropdown {...args} />;

export const SUSI = Template.bind({});
SUSI.args = {
type: 'SUSI',
items: [
{
middleName: '학생부교과',
lastNames: ['학교장추천', '교과면접', '기회균형', '특성화고교', '만학도', '특성화고등졸재직자', '특수교육대상자'],
},
{
middleName: '학생부종합',
lastNames: ['명지인재면접', '명지인재서류', '크리스천리더', '사회적배려대상자', '농어촌학생'],
},
{
middleName: '실기/실적',
lastNames: ['실기우수자', '특기자-문학/체육'],
},
],
};

export const JEONGSI = Template.bind({});
JEONGSI.args = {
type: 'JEONGSI',
items: [
{
middleName: '수능',
lastNames: ['일반-가/나/다', '실기-가/나/다', '농어촌학생', '특성화고교'],
},
{
middleName: '실기/실적',
lastNames: ['실기우수자'],
},
{
middleName: '학생부교과',
lastNames: ['만학도', '특성화고등졸재직자'],
},
],
};

export const PYEONIP = Template.bind({});
PYEONIP.args = {
type: 'PYEONIP',
items: [
{ middleName: '일반', lastNames: [] },
{ middleName: '학사', lastNames: [] },
{ middleName: '농어촌학생', lastNames: [] },
{ middleName: '특성화고교', lastNames: [] },
{ middleName: '재외국민', lastNames: [] },
{ middleName: '특성화고등졸재직자', lastNames: [] },
],
};
69 changes: 69 additions & 0 deletions src/ui/components/atom/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { ChevronRightIcon } from '@radix-ui/react-icons';
import { useDetailTypePrompt } from '../../../../hooks/use-detail-type-prompt.hooks';
import { useUserDetailTypeStore } from '../../../../store/user-detail-type-store';
import usePresetButton from '../../../../hooks/use-preset-button.hooks';

interface DropdownProps {
type: 'SUSI' | 'JEONGSI' | 'PYEONIP';
items: { middleName: string; lastNames: string[] }[];
}

const Dropdown: React.FC<DropdownProps> = ({ type }) => {
const { itemList: items } = useDetailTypePrompt();
const { setSelectedName } = useUserDetailTypeStore();
const { handleDetailTypeButtonClick } = usePresetButton();

const handleNameClick = (name: string) => {
setSelectedName(name);
handleDetailTypeButtonClick(name);
};

return (
<DropdownMenu.Root open>
<DropdownMenu.Trigger asChild>
<button style={{ display: 'hidden' }}></button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="rounded-md border-gray-300 bg-white" align="start">
{type === 'PYEONIP'
? // 편입의 경우, 한 번에 전체 목록 표시
items.map((item, index) => (
<DropdownMenu.Item
key={index}
className="cursor-pointer px-4 py-2 hover:bg-gray-100"
onClick={() => handleNameClick(item.middleName)}
>
{item.middleName}
</DropdownMenu.Item>
))
: // 수시, 정시의 경우, 중간 이름을 상위 메뉴로, 마지막 이름을 하위 메뉴로 표시
items.map((item, index) => (
<DropdownMenu.Sub key={index}>
<DropdownMenu.SubTrigger className="flex cursor-pointer items-center justify-between px-4 py-2 hover:bg-gray-100">
{item.middleName}
<ChevronRightIcon />
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
<DropdownMenu.SubContent className="rounded-md border border-gray-300 bg-white shadow-lg">
{item.lastNames.map((lastName, subIndex) => (
<DropdownMenu.Item
key={subIndex}
className="cursor-pointer px-4 py-2 hover:bg-gray-100"
onClick={() => handleNameClick(lastName)}
>
{lastName}
</DropdownMenu.Item>
))}
</DropdownMenu.SubContent>
</DropdownMenu.Portal>
</DropdownMenu.Sub>
))}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
};

export default Dropdown;
Loading

0 comments on commit d8fcce9

Please sign in to comment.