Skip to content
23 changes: 23 additions & 0 deletions src/api/applications.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axiosInstance from '@/lib/axios';
import type { ApiResponse } from '@/types/api';
import { ApplicationItem, ApplicationListResponse } from '@/types/applications';
import { NoticeCard } from '@/types/notice';

// 유저의 공고 지원 내역 전체 조회
export async function getAllUserApplications({
Expand Down Expand Up @@ -39,3 +40,25 @@ export const putApplication = async (shopId: string, noticeId: string, applicati
status: 'canceled',
});
};

// 특정 가게 특정 공고 정보
export async function getNoticeById(shopId: string, noticeId: string): Promise<NoticeCard> {
const { data } = await axiosInstance.get<NoticeCard>(`/shops/${shopId}/notices/${noticeId}`);
return data;
}

// 특정 가게 특정 공고 지원자 목록
export async function getApplications(
shopId: string,
noticeId: string,
offset = 0,
limit = 5
): Promise<ApplicationItem[]> {
const { data } = await axiosInstance.get<ApplicationListResponse>(
`/shops/${shopId}/notices/${noticeId}/applications`,
{ params: { offset, limit } }
);
const applications: ApplicationItem[] = data.items.map(resp => resp.item);

return applications;
}
6 changes: 3 additions & 3 deletions src/components/ui/badge/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button';
import Badge from './Badge';

export type StatusType = 'pending' | 'approved' | 'rejected';
export type StatusType = 'pending' | 'accepted' | 'rejected';

interface StatusBadgeProps {
status: StatusType;
Expand All @@ -27,11 +27,11 @@ export default function StatusBadge({ status, variant, onApprove, onReject }: St
const BADGE_CLASS =
status === 'pending'
? 'bg-green-100 text-green-200'
: status === 'approved'
: status === 'accepted'
? 'bg-blue-100 text-blue-200'
: 'bg-red-100 text-red-400';

const BADGE_TEXT = status === 'pending' ? '대기중' : status === 'approved' ? '승인 완료' : '거절';
const BADGE_TEXT = status === 'pending' ? '대기중' : status === 'accepted' ? '승인 완료' : '거절';

return <Badge className={BADGE_CLASS}>{BADGE_TEXT}</Badge>;
}
8 changes: 4 additions & 4 deletions src/components/ui/badge/statusbadge.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export default meta;

type Story = StoryObj<typeof StatusBadge>;

// Approve 승인 완료 뱃지
export const Approve: Story = {
// Accept 승인 완료 뱃지
export const Accept: Story = {
args: {
status: 'approved',
status: 'accepted',
variant: 'employer',
},
};
Expand All @@ -35,7 +35,7 @@ export const PendingEmployee: Story = {
},
};

// Pending 대기중 employer 버튼
// Pending 대기중 employer
export const PendingEmployer: Story = {
args: {
status: 'pending',
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/card/notice/notice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { noticeWrapper } from './notice.styles';
interface NoticeProps<T extends Partial<NoticeCard>> {
notice: T;
variant?: NoticeVariant;
children: ReactNode;
children?: ReactNode;
className?: string;
}

Expand Down
12 changes: 6 additions & 6 deletions src/components/ui/input/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function DateInput({
};

return (
<div ref={wrapperRef} className='relative max-w-md'>
<div ref={wrapperRef} className='relative'>
<Input
id={id}
label={label}
Expand All @@ -124,11 +124,11 @@ export default function DateInput({
autoComplete='off'
/>

{isOpen && (
<div className='absolute z-10 w-full'>
<Calendar onSelect={handleDateSelect} value={selectedDate ?? new Date()} />
</div>
)}
<div
className={`overflow-hidden transition-all duration-300 ${isOpen ? 'mt-2 max-h-[700px] opacity-100' : 'max-h-0 opacity-0'} `}
>
<Calendar onSelect={handleDateSelect} value={selectedDate ?? new Date()} />
</div>
</div>
);
}
67 changes: 47 additions & 20 deletions src/components/ui/input/TimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ import useClickOutside from '@/hooks/useClickOutside';
import useToggle from '@/hooks/useToggle';
import { formatTime } from '@/lib/utils/dateFormatter';
import { Period } from '@/types/calendar';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import Input from './input';

export default function TimeInput() {
interface TimeInputProps {
label?: string;
requiredMark?: boolean;
value?: Date | null;
onChange?: (value: Date | null) => void;
}

export default function TimeInput({
label = '시간 선택',
requiredMark = false,
value,
onChange,
}: TimeInputProps) {
const { isOpen, toggle, setClose } = useToggle(false);
const [period, setPeriod] = useState<Period>('오전');
const [selectedTime, setSelectedTime] = useState<Date | null>(null);
Expand All @@ -19,11 +31,25 @@ export default function TimeInput() {
});

// 시간 업데이트 중앙 관리
const updateTime = useCallback((date: Date, selectedPeriod: Period) => {
setPeriod(selectedPeriod);
setSelectedTime(date);
setInputValue(formatTime(date));
}, []);
const updateTime = useCallback(
(date: Date, selectedPeriod: Period) => {
setPeriod(selectedPeriod);
setSelectedTime(date);
setInputValue(formatTime(date));
onChange?.(date);
},
[onChange]
);

useEffect(() => {
if (value) {
setSelectedTime(value);
setInputValue(formatTime(value));
} else {
setSelectedTime(null);
setInputValue('');
}
}, [value]);

// 시간 선택
const handleTimeSelect = useCallback(
Expand Down Expand Up @@ -85,26 +111,27 @@ export default function TimeInput() {
const minutes = selectedTime ? String(selectedTime.getMinutes()).padStart(2, '0') : '00';

return (
<div ref={wrapperRef} className='relative max-w-md'>
<div ref={wrapperRef} className='relative'>
<Input
value={inputValue ? `${period} ${inputValue}` : ''}
label='시간 선택'
label={label}
requiredMark={requiredMark}
placeholder='오전 12:30'
onClick={toggle}
onChange={handleTimeInputChange}
/>

{isOpen && (
<div className='z-1 absolute'>
<TimeSelector
onSelect={handleTimeSelect}
period={period}
hours={hours}
minutes={minutes}
value={selectedTime ? formatTime(selectedTime) : ''}
/>
</div>
)}
<div
className={`overflow-hidden transition-all duration-300 ${isOpen ? 'mt-2 max-h-[300px] opacity-100' : 'max-h-0 opacity-0'} `}
>
<TimeSelector
onSelect={handleTimeSelect}
period={period}
hours={hours}
minutes={minutes}
value={selectedTime ? formatTime(selectedTime) : ''}
/>
</div>
</div>
);
}
Loading