diff --git a/src/components/ui/badge/Badge.tsx b/src/components/ui/badge/Badge.tsx new file mode 100644 index 0000000..eb6d8ec --- /dev/null +++ b/src/components/ui/badge/Badge.tsx @@ -0,0 +1,20 @@ +import { cn } from '@/lib/utils/cn'; +import { ReactNode } from 'react'; + +interface BadgeProps { + children: ReactNode; + className?: string; +} + +export default function Badge({ children, className }: BadgeProps) { + return ( + + {children} + + ); +} diff --git a/src/components/ui/badge/StatusBadge.tsx b/src/components/ui/badge/StatusBadge.tsx new file mode 100644 index 0000000..54a66df --- /dev/null +++ b/src/components/ui/badge/StatusBadge.tsx @@ -0,0 +1,37 @@ +import { Button } from '@/components/ui/button'; +import Badge from './Badge'; + +export type StatusType = 'pending' | 'approved' | 'rejected'; + +interface StatusBadgeProps { + status: StatusType; + variant: 'employer' | 'employee'; + onApprove: () => void; + onReject: () => void; +} + +export default function StatusBadge({ status, variant, onApprove, onReject }: StatusBadgeProps) { + if (status === 'pending' && variant === 'employer') { + return ( +
+ + +
+ ); + } + + const BADGE_CLASS = + status === 'pending' + ? 'bg-green-100 text-green-200' + : status === 'approved' + ? 'bg-blue-100 text-blue-200' + : 'bg-red-100 text-red-400'; + + const BADGE_TEXT = status === 'pending' ? '대기중' : status === 'approved' ? '승인 완료' : '거절'; + + return {BADGE_TEXT}; +} diff --git a/src/components/ui/badge/index.ts b/src/components/ui/badge/index.ts new file mode 100644 index 0000000..4d3516c --- /dev/null +++ b/src/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from '@/components/ui/badge/Badge'; +export { default as StatusBadge } from '@/components/ui/badge/StatusBadge'; diff --git a/src/components/ui/badge/statusbadge.stories.tsx b/src/components/ui/badge/statusbadge.stories.tsx new file mode 100644 index 0000000..5fa438b --- /dev/null +++ b/src/components/ui/badge/statusbadge.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; +import { default as StatusBadge } from './StatusBadge'; + +const meta: Meta = { + title: 'UI/StatusBadge', + component: StatusBadge, + tags: ['autodocs'], + parameters: { layout: 'centered' }, +}; +export default meta; + +type Story = StoryObj; + +// Approve 승인 완료 뱃지 +export const Approve: Story = { + args: { + status: 'approved', + variant: 'employer', + }, +}; + +// Reject 거절 뱃지 +export const Reject: Story = { + args: { + status: 'rejected', + variant: 'employer', + }, +}; + +// Pending 대기중 employee 뱃지 +export const PendingEmployee: Story = { + args: { + status: 'pending', + variant: 'employee', + }, +}; + +// Pending 대기중 employer 버튼 +export const PendingEmployer: Story = { + args: { + status: 'pending', + variant: 'employer', + onApprove: () => alert('승인!'), + onReject: () => alert('거절!'), + }, +}; diff --git a/src/components/ui/calendar/Calendar.tsx b/src/components/ui/calendar/Calendar.tsx index 6044ae9..9f40be1 100644 --- a/src/components/ui/calendar/Calendar.tsx +++ b/src/components/ui/calendar/Calendar.tsx @@ -54,7 +54,7 @@ export default function Calendar({ value, onSelect }: CalendarProps) { }; return ( -
+
)} - - +
+ +
); } diff --git a/src/components/ui/calendar/DayViewMode.tsx b/src/components/ui/calendar/DayViewMode.tsx index 0abdbc8..547673e 100644 --- a/src/components/ui/calendar/DayViewMode.tsx +++ b/src/components/ui/calendar/DayViewMode.tsx @@ -26,7 +26,7 @@ export default function DayViewMode({ currentMonth, currentDay, onSelect }: DayV })}
-
+
{DAYS.map((dayObj, i) => { const { date, isCurrentMonth } = dayObj; @@ -48,7 +48,7 @@ export default function DayViewMode({ currentMonth, currentDay, onSelect }: DayV onClick={() => !isDisabled && onSelect(date)} disabled={isDisabled} className={cn( - 'h-[3rem] w-[3rem] rounded-lg py-1.5 transition', + 'aspect-square w-full rounded-lg transition', isSelected ? 'bg-blue-200 font-semibold text-white' : !isDisabled diff --git a/src/components/ui/calendar/MonthViewMode.tsx b/src/components/ui/calendar/MonthViewMode.tsx index d80109c..6c0410c 100644 --- a/src/components/ui/calendar/MonthViewMode.tsx +++ b/src/components/ui/calendar/MonthViewMode.tsx @@ -2,12 +2,12 @@ import { MonthViewProps } from '@/types/calendar'; export default function MonthViewMode({ onSelect: onSelectMonth }: MonthViewProps) { return ( -
+
{Array.from({ length: 12 }).map((_, i) => ( diff --git a/src/components/ui/calendar/YearViewMode.tsx b/src/components/ui/calendar/YearViewMode.tsx index 91c65f5..c321bd6 100644 --- a/src/components/ui/calendar/YearViewMode.tsx +++ b/src/components/ui/calendar/YearViewMode.tsx @@ -4,14 +4,14 @@ export default function YearViewMode({ currentMonth, onSelect }: YearViewProps) const START_YEAR = Math.floor(currentMonth.getFullYear() / 10) * 10; return ( -
+
{Array.from({ length: 10 }).map((_, i) => { const YEAR = START_YEAR + i; return ( diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 031face..a74f51c 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -1,3 +1,4 @@ +export { Badge, StatusBadge } from '@/components/ui/badge'; export { Calendar, CalendarHeader, diff --git a/src/components/ui/input/DateInput.tsx b/src/components/ui/input/DateInput.tsx index 9721b6d..b4c8af6 100644 --- a/src/components/ui/input/DateInput.tsx +++ b/src/components/ui/input/DateInput.tsx @@ -40,9 +40,10 @@ export default function DateInput({ (date: Date) => { setSelectedDate(date); setInputValue(formatDate(date)); + setClose(); onChange?.(date); }, - [onChange] + [onChange, setClose] ); // 날짜 선택 @@ -124,7 +125,7 @@ export default function DateInput({ /> {isOpen && ( -
+
)} diff --git a/src/components/ui/pagination/index.ts b/src/components/ui/pagination/index.ts index 9128b29..fe89635 100644 --- a/src/components/ui/pagination/index.ts +++ b/src/components/ui/pagination/index.ts @@ -1 +1 @@ -export { default as Pagination } from './pagination'; +export { default as Pagination } from '@/components/ui/pagination/pagination'; diff --git a/src/components/ui/table/Table.stories.tsx b/src/components/ui/table/Table.stories.tsx index 2569e45..a77d293 100644 --- a/src/components/ui/table/Table.stories.tsx +++ b/src/components/ui/table/Table.stories.tsx @@ -8,12 +8,8 @@ import { fetchTableData } from './testApi'; const meta: Meta = { title: 'UI/Table', component: Table, - argTypes: { - userType: { - control: { type: 'radio' }, - options: ['employer', 'employee'], - }, - }, + tags: ['autodocs'], + parameters: { layout: 'fullscreen' }, }; export default meta; @@ -23,6 +19,8 @@ type Story = StoryObj; function TableWithTestApi({ userType }: { userType: UserType }) { const [headers, setHeaders] = useState([]); const [data, setData] = useState([]); + const [offset, setOffset] = useState(0); + const limit = 5; useEffect(() => { const getData = async () => { @@ -33,12 +31,32 @@ function TableWithTestApi({ userType }: { userType: UserType }) { getData(); }, [userType]); - return ; + const count = data.length; + const paginatedData = data.slice(offset, offset + limit); + + return ( +
+ ); } -export const TableExample: Story = { +export const EmployerTable: Story = { args: { userType: 'employer', }, render: args => , }; + +export const EmployeeTable: Story = { + args: { + userType: 'employee', + }, + render: args => , +}; diff --git a/src/components/ui/table/Table.tsx b/src/components/ui/table/Table.tsx index a9d99b8..1019d86 100644 --- a/src/components/ui/table/Table.tsx +++ b/src/components/ui/table/Table.tsx @@ -1,35 +1,68 @@ -import TableRow from '@/components/ui/table/TableRow'; +import { Pagination } from '@/components/ui'; import { TableRowProps } from '@/components/ui/table/TableRowProps'; +import { cn } from '@/lib/utils/cn'; import { UserType } from '@/types/user'; +import TableRow from './TableRow'; interface TableProps { data: TableRowProps[]; headers: string[]; userType: UserType; + total: number; + limit: number; + offset: number; + onPageChange: (newOffset: number) => void; } //
type은 확인이 좀 더 필요합니다 -export default function Table({ data, headers, userType }: TableProps) { +export default function Table({ + data, + headers, + userType, + total, + limit, + offset, + onPageChange, +}: TableProps) { return ( -
-
- - - {headers.map((header, index) => ( - - ))} - - - - {data.map((row, index) => ( - - ))} - -
- {header} -
-
페이지네이션
+
+
+ {userType === 'employer' ? '신청자 목록' : '신청 목록'} +
+
+
+ + + + {headers.map((header, index) => ( + + ))} + + + + {data.map(row => ( + + ))} + +
0 && index < headers.length - 1 && 'w-[245px]', + index === headers.length - 1 && 'w-[220px] md:w-[230px]' + )} + > + {header} +
+
+ +
+ +
+
); } diff --git a/src/components/ui/table/TableRow.tsx b/src/components/ui/table/TableRow.tsx index 3665c13..07beb3d 100644 --- a/src/components/ui/table/TableRow.tsx +++ b/src/components/ui/table/TableRow.tsx @@ -1,23 +1,31 @@ +import { StatusBadge } from '@/components/ui/badge'; +import { StatusType } from '@/components/ui/badge/StatusBadge'; import { TableRowProps } from '@/components/ui/table/TableRowProps'; +import { cn } from '@/lib/utils/cn'; import { getTime } from '@/lib/utils/dateFormatter'; +import { useState } from 'react'; interface TableTypeVariant { rowData: TableRowProps; variant: 'employer' | 'employee'; } -const TD_BASE = 'border-b px-2 py3'; +const TD_BASE = 'border-b border-r px-3 py-5 text-base gap-3 md:border-r-0'; const TD_STATUS = 'border-b px-2 py-[9px]'; export default function TableRow({ rowData, variant }: TableTypeVariant) { const { date, startTime, endTime, duration } = getTime(rowData.startsAt, rowData.workhour); + const [status, setStatus] = useState(rowData.status as StatusType); + + const handleApprove = () => setStatus('approved'); + const handleReject = () => setStatus('rejected'); return ( - {rowData.name} + {rowData.name} {variant === 'employee' ? ( <> - {`${date} ${startTime} ~ ${date} ${endTime}} (${duration})`} + {`${date} ${startTime} ~ ${date} ${endTime} (${duration})`} {rowData.hourlyPay} ) : ( @@ -26,7 +34,14 @@ export default function TableRow({ rowData, variant }: TableTypeVariant) { {rowData.phone} )} - {rowData.status} + + + ); } diff --git a/src/components/ui/table/TableRowProps.tsx b/src/components/ui/table/TableRowProps.tsx index 5544174..8605805 100644 --- a/src/components/ui/table/TableRowProps.tsx +++ b/src/components/ui/table/TableRowProps.tsx @@ -1,4 +1,5 @@ export type TableRowProps = { + id: string; name: string; startsAt: string; workhour: number; diff --git a/src/components/ui/table/testApi.tsx b/src/components/ui/table/testApi.tsx index 1060a38..6e6d4ae 100644 --- a/src/components/ui/table/testApi.tsx +++ b/src/components/ui/table/testApi.tsx @@ -11,31 +11,73 @@ export const fetchTableData = async (userType: UserType) => { name: '김강현', bio: '최선을 다해 열심히 일합니다. 다수의 업무 경험을 바탕으로 확실한 일처리 보여드리겠습니다.', phone: '010-1234-5678', - status: '버튼', + status: 'pending', }, { name: '서혜진', bio: '열심히 하겠습니다!', - phone: '010-0000-0000', + phone: '010-1111-2222', status: 'rejected', }, { name: '주진혁', bio: '성실한 자세로 열심히 일합니다.', - phone: '010-0000-0000', - status: 'accepted', + phone: '010-3333-4444', + status: 'approved', }, { name: '장민혁', bio: '일을 꼼꼼하게 하는 성격입니다.', - phone: '010-0000-0000', - status: 'accepted', + phone: '010-5555-5555', + status: 'approved', }, { name: '고기훈', bio: '하루라도 최선을 다해서 일하겠습니다!', - phone: '010-0000-0000', - status: 'accepted', + phone: '010-6666-6666', + status: 'rejected', + }, + { + name: '최현수', + bio: '열심히 하겠습니다!', + phone: '010-1123-5448', + status: 'pending', + }, + { + name: '강주하', + bio: '성실한 자세로 열심히 일합니다.', + phone: '010-4123-2323', + status: 'approved', + }, + { + name: '배수지', + bio: '열심히 배우고 일하겠습니다!', + phone: '010-3123-1111', + status: 'approved', + }, + { + name: '강규하', + bio: '꼼꼼한 일처리 보여드리겠습니다.', + phone: '010-5123-0098', + status: 'rejected', + }, + { + name: '고선영', + bio: '최선을 다해서 일하겠습니다!', + phone: '010-6662-6326', + status: 'peding', + }, + { + name: '박하연', + bio: '뽑아주시면 열심히 하겠습니다.', + phone: '010-1277-1385', + status: 'pending', + }, + { + name: '김연아', + bio: '잘 부탁드립니다!', + phone: '010-1232-6216', + status: 'rejected', }, ], }); @@ -48,7 +90,7 @@ export const fetchTableData = async (userType: UserType) => { startsAt: '2025-10-01T11:00', workhour: 2, hourlyPay: '12,500원', - status: 'accepted', + status: 'pending', }, { name: '너구리네 라면집', @@ -62,21 +104,28 @@ export const fetchTableData = async (userType: UserType) => { startsAt: '2025-10-01T11:00', workhour: 2, hourlyPay: '12,500원', - status: 'accepted', + status: 'approved', + }, + { + name: '너구리네 라면집', + startsAt: '2025-10-01T11:00', + workhour: 2, + hourlyPay: '12,500원', + status: 'rejected', }, { name: '너구리네 라면집', startsAt: '2025-10-01T11:00', workhour: 2, hourlyPay: '12,500원', - status: 'accepted', + status: 'approved', }, { name: '너구리네 라면집', startsAt: '2025-10-01T11:00', workhour: 2, hourlyPay: '12,500원', - status: 'accepted', + status: 'approved', }, ], });