Skip to content

Commit

Permalink
Merge pull request #17 from wafflestudio/fix/#16
Browse files Browse the repository at this point in the history
Fix/#16
  • Loading branch information
khinwaiyan authored Mar 10, 2025
2 parents 1fd7fcb + a210716 commit 875823d
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 12 deletions.
97 changes: 97 additions & 0 deletions src/components/common/DropDown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useRef, useState } from 'react';

import useOuterClick from '@/hooks/useOuterClick';

export interface DropdownOption<T extends string> {
value: T;
label: string;
}

interface DropdownProps<T extends string> {
options: DropdownOption<T>[];
value?: T;
onChange?: (value: T) => void;
bgColor?: string;
textColor?: string;
iconColor?: string;
placeholder?: string;
}

/*아래는 미지정 시 기본값이며 이 컴포넌트 사용하실 때마다 디자인 맞춰서 바꿔주시면 됩니다!*/

const Dropdown = <T extends string>({
options,
value,
onChange,
bgColor = 'bg-textGreen',
textColor = 'text-black',
iconColor = 'fill-black',
placeholder = '전체',
}: DropdownProps<T>) => {
const [opened, setOpened] = useState<boolean>(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleRef = useRef<HTMLDivElement>(null);

const selectedOption = options.find((option) => option.value === value);

const handleToggle = () => {
setOpened((prev) => !prev);
};

const handleOptionClick = (newValue: T) => {
onChange?.(newValue);
setOpened(false);
};

const handleClickOutside = () => {
setOpened(false);
};

useOuterClick(dropdownRef, handleClickOutside, [toggleRef]);

return (
<div
className="relative inline-block text-left"
ref={toggleRef}
>
<div
className={`h-[26px] px-3 py-[5px] ${bgColor} flex cursor-pointer items-center gap-1 rounded-md`}
onClick={handleToggle}
>
<span className={`${textColor} text-sm font-medium`}>
{selectedOption?.label || placeholder}
</span>
<div data-svg-wrapper>
<svg
width="12"
height="7"
viewBox="0 0 12 7"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`transition-transform ${opened ? 'rotate-180' : ''} ${iconColor}`}
>
<path d="M6 6.35803L0.803849 0.358031L11.1962 0.358032L6 6.35803Z" />
</svg>
</div>
</div>
{opened && (
<div
className="absolute left-0 z-10 mt-1 w-max min-w-[60px] overflow-hidden rounded-md bg-white text-sm shadow-md"
ref={dropdownRef}
>
{options.map((option) => (
<div
key={option.value}
className="cursor-pointer px-3 py-2 text-sm text-gray-800 hover:bg-gray-200"
onClick={() => handleOptionClick(option.value)}
>
{option.label}
</div>
))}
</div>
)}
</div>
);
};

export default Dropdown;
2 changes: 1 addition & 1 deletion src/components/organization/GroupRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const groupRowVariants = cva(
hover: 'bg-backgroundGray',
hold: 'bg-[rgba(25,240,120,0.2)]',
unselected: 'bg-backgroundUnselected',
selected: 'text-backgroundGreen',
selected: 'text-backgroundGreen bg-backgroundSelected',
},
unclassified: {
true: 'text-textGray2',
Expand Down
29 changes: 29 additions & 0 deletions src/hooks/useOuterClick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RefObject, useEffect } from 'react';

const useOuterClick = (
ref: RefObject<HTMLElement>,
callback: () => void,
ignoreRefs: RefObject<HTMLElement>[] = []
) => {
useEffect(() => {
const handleClick = ({ target }: MouseEvent) => {
if (
!(target instanceof Node) ||
!ref.current ||
ref.current.contains(target)
)
return;

if (ignoreRefs.some((ignoreRef) => ignoreRef.current?.contains(target))) {
return;
}

callback();
};

document.addEventListener('mouseup', handleClick);
return () => document.removeEventListener('mouseup', handleClick);
}, [ref, callback, ignoreRefs]);
};

export default useOuterClick;
55 changes: 44 additions & 11 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
UseQueryOptions,
UseMutationOptions,
} from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { useToken } from '@/hooks/useToken';

export const API_DOMAIN = import.meta.env.VITE_API_DOMAIN;
Expand All @@ -15,16 +14,19 @@ type FetchOptions = {
method: RequestMethod;
headers?: Record<string, string>;
body?: any;
accessToken?: string | null;
clearTokens?: () => void;
};
export const fetchData = async <T>({
endpoint,
method,
headers,
body,
accessToken,
clearTokens,
}: FetchOptions) => {
const { accessToken, clearTokens } = useToken();
const navigate = useNavigate();
const url = `${API_DOMAIN}${endpoint}`;
console.log('url', url);
const response = await fetch(url, {
method,
headers: {
Expand All @@ -33,11 +35,9 @@ export const fetchData = async <T>({
},
body: body ? JSON.stringify(body) : undefined,
});

if (!response.ok) {
if (response.status === 401) {
clearTokens();
navigate('/auth/login', { replace: true });
clearTokens?.();
}
const error = await response.json();
throw new Error(error.message || 'Something went wrong!');
Expand Down Expand Up @@ -74,9 +74,11 @@ export const useGetRequest = <T>(
endpoint: string,
options?: Omit<UseQueryOptions<T>, 'queryKey'>
) => {
const { accessToken, clearTokens } = useToken();
return useQuery<T>({
queryKey,
queryFn: () => fetchData<T>({ endpoint, method: 'GET' }),
queryFn: () =>
fetchData<T>({ endpoint, method: 'GET', accessToken, clearTokens }),
...options,
});
};
Expand All @@ -98,9 +100,16 @@ export const usePostRequest = <T, Variables>(
endpoint: string,
options?: UseMutationOptions<T, Error, Variables>
) => {
const { accessToken, clearTokens } = useToken();
return useMutation<T, Error, Variables>({
mutationFn: (variables: Variables) => {
return fetchData<T>({ endpoint, method: 'POST', body: variables });
return fetchData<T>({
endpoint,
method: 'POST',
body: variables,
accessToken,
clearTokens,
});
},
...options,
});
Expand All @@ -111,9 +120,17 @@ export const usePutRequest = <T, Variables>(
endpoint: string,
options?: UseMutationOptions<T, Error, Variables>
) => {
const { accessToken, clearTokens } = useToken();

return useMutation<T, Error, Variables>({
mutationFn: (variables: Variables) => {
return fetchData<T>({ endpoint, method: 'PUT', body: variables });
return fetchData<T>({
endpoint,
method: 'PUT',
body: variables,
accessToken,
clearTokens,
});
},
...options,
});
Expand All @@ -124,9 +141,17 @@ export const useDeleteRequest = <T, Variables>(
endpoint: string,
options?: UseMutationOptions<T, Error, Variables>
) => {
const { accessToken, clearTokens } = useToken();

return useMutation<T, Error, Variables>({
mutationFn: (variables: Variables) => {
return fetchData<T>({ endpoint, method: 'DELETE', body: variables });
return fetchData<T>({
endpoint,
method: 'DELETE',
body: variables,
accessToken,
clearTokens,
});
},
...options,
});
Expand All @@ -137,9 +162,17 @@ export const usePatchRequest = <T, Variables>(
endpoint: string,
options?: UseMutationOptions<T, Error, Variables>
) => {
const { accessToken, clearTokens } = useToken();

return useMutation<T, Error, Variables>({
mutationFn: (variables: Variables) => {
return fetchData<T>({ endpoint, method: 'PATCH', body: variables });
return fetchData<T>({
endpoint,
method: 'PATCH',
body: variables,
accessToken,
clearTokens,
});
},
...options,
});
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
backgroundGray: '#F6F4F1',
hoverGray: '#EBE6E0',
backgroundUnselected: '#EBE6E0',
backgroundSelected: '#FBF9F8',
backgroundGreen: '#35C274',
errorRed: '#EC3737',
},
Expand Down

0 comments on commit 875823d

Please sign in to comment.