Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c7097e2
✨ feat: Table component 파일 추가
Moon-ju-young Jun 11, 2025
0b43d78
✨ feat: 기본 table 구조 생성
Moon-ju-young Jun 11, 2025
26f36db
✨ feat: 외부 데이터 type 임시 설정
Moon-ju-young Jun 11, 2025
a8dfdf3
✨ feat: Props 추가
Moon-ju-young Jun 11, 2025
875027b
✨ feat: 임시 Link type 추가
Moon-ju-young Jun 11, 2025
6b87d16
✨ feat: mode props 추가
Moon-ju-young Jun 11, 2025
44e8048
✨ feat: applications 배열 길이 설정
Moon-ju-young Jun 11, 2025
ec0fc3c
♻️ refactor: null 허용 배열 변경
Moon-ju-young Jun 11, 2025
c386100
♻️ refactor: applications 변수 길이 5로 고정
Moon-ju-young Jun 11, 2025
f1807c6
✨ feat: datas 변수 추가
Moon-ju-young Jun 11, 2025
9fdd1a6
✨ feat: headers 변수 추가
Moon-ju-young Jun 11, 2025
5cc44fe
✨ feat: thead(header) 적용 추가
Moon-ju-young Jun 11, 2025
761dda3
✨ feat: tbody data 적용 추가
Moon-ju-young Jun 12, 2025
326ff53
🎨 style: table 틀 스타일 적용
Moon-ju-young Jun 12, 2025
6fc8d4d
♻️ refactor: thead에서 td > th로 태그 변경
Moon-ju-young Jun 12, 2025
19f87c5
🎨 style: thead 스타일 적용
Moon-ju-young Jun 12, 2025
0bcb881
🎨 style: th 스타일 적용
Moon-ju-young Jun 12, 2025
7fe5486
🎨 style: tbody 스타일 적용 (font 설정 상속)
Moon-ju-young Jun 12, 2025
51a0eef
🎨 style: td 스타일 적용
Moon-ju-young Jun 12, 2025
c819b8e
🎨 style: 거절/승인 Button 스타일 적용
Moon-ju-young Jun 12, 2025
aac9cdf
✨ feat: isMobile state 추가
Moon-ju-young Jun 12, 2025
1f3ae04
🎨 style: Button size prop 추가
Moon-ju-young Jun 12, 2025
ff97cdf
🔥 remove: 불필요 스타일 제거
Moon-ju-young Jun 12, 2025
0f5c76e
♻️ refactor: tdStyle 변수 삭제
Moon-ju-young Jun 12, 2025
860e2f1
🎨 style: 상태 태그 스타일 적용
Moon-ju-young Jun 12, 2025
526d14b
♻️ refactor: TableStatus component 분리
Moon-ju-young Jun 13, 2025
3b78bc9
♻️ refactor: TableButtons components 분리
Moon-ju-young Jun 13, 2025
9dd52df
✨ feat: 횡스크롤 추가
Moon-ju-young Jun 13, 2025
63a0b57
♻️ refactor: tfoot div 태그로 변경 및 스타일 리팩토링
Moon-ju-young Jun 14, 2025
35227c9
🎨 style: 상태 행 고정
Moon-ju-young Jun 14, 2025
6a326f9
🎨 style: PC/Tablet에서 행 최소 높이 지정
Moon-ju-young Jun 14, 2025
d0a7e9d
🎨 style: Mobile에서 행 최소 높이 지정
Moon-ju-young Jun 14, 2025
51243c1
🎨 style: table 하단 너비 설정
Moon-ju-young Jun 14, 2025
5ada94d
🎨 style: 스크롤바 숨김
Moon-ju-young Jun 14, 2025
6cb2c69
🎨 style: 너비에 따른 상태 열 border 추가
Moon-ju-young Jun 14, 2025
b6823a4
🎨 style: 좌우 너비 알맞게 조정
Moon-ju-young Jun 14, 2025
fb7efd7
✨ feat: 두 줄 이하로 내용 자르는 기능 추가
Moon-ju-young Jun 14, 2025
7c4b91e
Merge branch 'develop' into feat/46-table
Moon-ju-young Jun 17, 2025
7848ac9
♻️ refactor: import 문 순서 변경
Moon-ju-young Jun 18, 2025
b714510
🐛 fix: formatWorkTime 수정에 의한 버그 수정
Moon-ju-young Jun 18, 2025
276bf9e
♻️ refactor: table 외부 파일에서 type import로 변경
Moon-ju-young Jun 18, 2025
47a7d7c
🐛 fix: applications에 대한 type error 없도록 수정
Moon-ju-young Jun 18, 2025
127ecea
♻️ refactor: TableStatus status prop type string으로 변경
Moon-ju-young Jun 18, 2025
807a9b4
🐛 fix: element가 없을 시 반환하는 배열 변경
Moon-ju-young Jun 18, 2025
9c6d9bf
♻️ refactor: datas 변수 state로 변경
Moon-ju-young Jun 18, 2025
7480a65
✨ feat: applications prop 삭제 및 api 적용
Moon-ju-young Jun 18, 2025
dcd6435
♻️ refactor: headers 변수 refactoring
Moon-ju-young Jun 18, 2025
18a2046
✨ feat: datas state 기본값 추가
Moon-ju-young Jun 18, 2025
9c7f79f
🐛 fix: TableStatus status prop에 넘겨주는 데이터 오류 수정
Moon-ju-young Jun 18, 2025
faace2d
✨ feat: page state 추가
Moon-ju-young Jun 18, 2025
a20ce98
✨ feat: totalPage state 추가
Moon-ju-young Jun 18, 2025
1be4805
Merge branch 'develop' into feat/46-table
Moon-ju-young Jun 18, 2025
4bfe776
🐛 fix: totalPage 최소 개수 1로 조정
Moon-ju-young Jun 18, 2025
0959889
✨ feat: Table Pagination 추가
Moon-ju-young Jun 18, 2025
10a6273
✨ feat: api 부분 error 처리 추가
Moon-ju-young Jun 18, 2025
b50d8e0
✨ feat: TableButtons api request 및 props 추가
Moon-ju-young Jun 18, 2025
eb1f624
✨ feat: Table TableButtons props 알맞게 추가
Moon-ju-young Jun 18, 2025
17cb0a0
📝 docs: 주석 추가
Moon-ju-young Jun 18, 2025
4659bd7
🐛 fix: 텍스트 넘침 스타일 조정
Moon-ju-young Jun 18, 2025
e0609dc
✨ feat: TableButtons Modal component 및 state 추가
Moon-ju-young Jun 18, 2025
aaaeb74
✨ feat: modal state property 변경
Moon-ju-young Jun 18, 2025
d2a9be3
✨ feat: handleClick 함수 추가 및 변경
Moon-ju-young Jun 18, 2025
1e29baa
🐛 fix: changeStatus > handleModalClick 으로 변경
Moon-ju-young Jun 18, 2025
1aacae5
✨ feat: TableButtons closeModal 함수 추가
Moon-ju-young Jun 18, 2025
ea206c0
✨ feat: TableButtons Modal에 yesButtonContent prop 추가
Moon-ju-young Jun 18, 2025
d5a9ecf
♻️ refactor: TableButtons 내부 함수 선언 순서 변경
Moon-ju-young Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions src/components/common/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { useEffect, useState } from 'react';
import TableStatus from './TableStatus';
import TableButtons from './TableButtons';
import Pagination from '../Pagination';
import formatWorkTime from '@/utils/formatWorkTime';
import {
getNoticeApplications,
getUserApplications,
} from '@/api/applicationApi';

interface UserProps {
className?: string;
mode: 'user';
userId: string;
}

interface NoticeProps {
className?: string;
mode: 'notice';
shopId: string;
noticeId: string;
}

export default function Table(props: UserProps | NoticeProps) {
const { className, mode } = props;
const headers =
mode === 'notice'
? ['신청자', '소개', '전화번호', '상태']
: ['가게', '일자', '시급', '상태'];
const [datas, setDatas] = useState<(string | undefined)[][]>(
Array(5).fill(['', '', '', '']),
);
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);

useEffect(() => {
(async () => {
try {
if (mode === 'notice') {
const { items, count } = await getNoticeApplications(
props.shopId,
props.noticeId,
{
offset: (page - 1) * 5,
limit: 5,
},
);

setTotalPage(count ? Math.ceil(count / 5) : 1);
setDatas(
[...items, ...[null, null, null, null, null]]
.slice(0, 5)
.map((element) => {
if (!element) return ['', '', '', ''];

return [
element.item.user.item.name,
element.item.user.item.bio,
element.item.user.item.phone,
element.item.status,
element.item.id,
];
}),
);
} else {
const { items, count } = await getUserApplications(props.userId, {
offset: (page - 1) * 5,
limit: 5,
});

setTotalPage(count ? Math.ceil(count / 5) : 1);
setDatas(
[...items, ...[null, null, null, null, null]]
.slice(0, 5)
.map((element) => {
if (!element) return ['', '', '', ''];

return [
element.item.shop.item.name,
`${formatWorkTime({
startsAt: element.item.notice.item.startsAt,
workhour: element.item.notice.item.workhour,
})} (${element.item.notice.item.workhour}시간)`,
element.item.notice.item.hourlyPay.toLocaleString('ko-KR') +
'원',
element.item.status,
];
}),
);
}
} catch {
setDatas(Array(5).fill(['', '', '', '']));
setPage(1);
setTotalPage(1);
}
})();
}, [mode, page]);

return (
<div
className={`@container scrollbar-hide overflow-x-auto rounded-[10px] border border-gray-20 bg-white ${className}`}
>
<table className="w-full min-w-888 border-separate border-spacing-0 text-left md:min-w-946">
<thead className="h-39 bg-red-10 text-caption/16 md:h-49 md:text-body2/22">
<tr>
<th className="w-227 border-b border-gray-20 px-7 font-regular md:px-11">
{headers[0]}
</th>
<th className="w-300 border-b border-gray-20 px-8 font-regular md:px-12">
{headers[1]}
</th>
<th className="w-200 border-b border-gray-20 px-8 font-regular md:px-12">
{headers[2]}
</th>
<th className="sticky right-0 border-b border-l border-gray-20 bg-red-10 px-7 font-regular md:px-11 @min-[947px]:border-l-transparent">
{headers[3]}
</th>
</tr>
</thead>
<tbody className="text-body2/22 font-regular md:text-body1/26">
{datas.map((data, index) => (
<tr key={index}>
<td className="border-b border-gray-20 pt-12 pr-8 pb-11 pl-7 md:pt-20 md:pr-12 md:pb-19 md:pl-11">
<div className="line-clamp-1 max-h-22 overflow-hidden md:line-clamp-2 md:max-h-51">
{data[0]}
</div>
</td>
<td className="border-b border-gray-20 px-8 pt-12 pb-11 md:px-12 md:pt-20 md:pb-19">
<div className="line-clamp-1 max-h-22 overflow-hidden md:line-clamp-2 md:max-h-51">
{data[1]}
</div>
</td>
<td className="border-b border-gray-20 px-8 pt-12 pb-11 md:px-12 md:pt-20 md:pb-19">
<div className="line-clamp-1 max-h-22 overflow-hidden md:line-clamp-2 md:max-h-51">
{data[2]}
</div>
</td>
<td className="sticky right-0 border-b border-l border-gray-20 bg-white px-7 pt-1 md:px-11 @min-[947px]:border-l-transparent">
<div className="min-h-44 content-center md:min-h-67">
{mode === 'notice' && data[3] === 'pending' ? (
<TableButtons
shopId={props.shopId}
noticeId={props.noticeId}
applicaitonId={data[4] ?? ''}
/>
) : (
<TableStatus status={data[3] ?? ''} />
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
<div className="sticky right-0 bottom-0 left-0">
<Pagination
currentPage={page}
setCurrentPage={setPage}
totalPages={totalPage}
/>
</div>
</div>
);
}
103 changes: 103 additions & 0 deletions src/components/common/table/TableButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useEffect, useState, type MouseEvent } from 'react';
import Button from '../Button';
import TableStatus from './TableStatus';
import { putNoticeApplications } from '@/api/applicationApi';
import Modal from '../Modal';

interface Props {
shopId: string;
noticeId: string;
applicaitonId: string;
}

export default function TableButtons({
shopId,
noticeId,
applicaitonId,
}: Props) {
const [status, setStatus] = useState<'pending' | 'accepted' | 'rejected'>(
'pending',
);
const [modal, setModal] = useState<{
isOpen: boolean;
status: 'accepted' | 'rejected';
}>({
isOpen: false,
status: 'accepted',
});
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);

const closeModal = () =>
setModal((prev) => {
return { ...prev, isOpen: false };
});

const handleModalClick = async () => {
closeModal();
setStatus(modal.status);
try {
await putNoticeApplications(shopId, noticeId, applicaitonId, {
status: modal.status,
});
} catch {
setStatus('pending');
}
};

const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
setModal({
isOpen: true,
status: e.currentTarget.value as 'accepted' | 'rejected',
});
};

useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

return status === 'pending' ? (
<>
<div className="flex gap-8 md:gap-12">
<Button
solid={false}
size={isMobile ? 'small' : 'medium'}
className="w-69 md:w-92"
value="rejected"
onClick={handleClick}
>
거절하기
</Button>
<Button
solid={false}
size={isMobile ? 'small' : 'medium'}
className="w-69 md:w-92"
style={{
color: 'var(--color-blue-20)',
borderColor: 'var(--color-blue-20)',
}}
value="accepted"
onClick={handleClick}
>
승인하기
</Button>
</div>
{modal.isOpen && (
<Modal
option="action"
onButtonClick={closeModal}
onYesButtonClick={handleModalClick}
yesButtonContent="예"
onClose={closeModal}
>
신청을 {modal.status === 'accepted' ? '승인' : '거절'}하시겠어요?
</Modal>
)}
</>
) : (
<TableStatus status={status} />
);
}
19 changes: 19 additions & 0 deletions src/components/common/table/TableStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function TableStatus({ status }: { status: string }) {
return status === 'pending' ? (
<div className="inline-block rounded-full bg-green-10 px-10 py-6 text-caption/16 text-green-20 md:text-body2/17 md:font-bold">
대기중
</div>
) : status === 'accepted' ? (
<div className="inline-block rounded-full bg-blue-10 px-10 py-6 text-caption/16 text-blue-20 md:text-body2/17 md:font-bold">
승인 완료
</div>
) : status === 'rejected' ? (
<div className="inline-block rounded-full bg-red-10 px-10 py-6 text-caption/16 text-red-40 md:text-body2/17 md:font-bold">
거절
</div>
) : status === 'canceled' ? (
<div className="inline-block rounded-full bg-gray-20 px-10 py-6 text-caption/16 text-gray-50 md:text-body2/17 md:font-bold">
취소
</div>
) : null;
}