Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions src/entities/event/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,22 @@ export const eventDetail = async (dto: EventDetailRequest) => {
const response = await axiosClient.get(`/events/${dto.eventId}`, {
params: { userId: dto.userId },
});
return response.data.result;
return response.data;
};

// 이벤트 검색 (기본 정보)
export const searchEvents = async (keyword: string, { page, size }: PaginationParams): Promise<ApiResponse<EventItem[]>> => {
export const searchEvents = async (
keyword: string,
{ page, size }: PaginationParams
): Promise<ApiResponse<EventItem[]>> => {
const params = new URLSearchParams();

params.append('keyword', keyword);
params.append('page', page.toString());
params.append('size', size.toString());

const response = await axiosClient.get<ApiResponse<EventItem[]>>(`/events/search?${params.toString()}`);

return response.data;
};

Expand Down
4 changes: 2 additions & 2 deletions src/entities/event/hook/useEventHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const useEventDetail = () => {

const { data } = useQuery({
queryKey: ['eventDetail', eventId],
queryFn: () => eventDetail({ eventId, userId: user?.id }),
enabled: !!user?.id,
queryFn: () => eventDetail({ eventId, userId: user?.id }),
enabled: !!user?.id,
Comment on lines +14 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

공백 제거 변경이 잘 적용되었습니다.

이 부분의 변경은 단순 공백 제거에 불과하지만, eventDetail API 함수의 반환 구조 변경(response.data.result → response.data)과 연관되어 있습니다. 현재 이 훅 자체는 큰 수정이 필요하지 않았지만, 이 훅을 사용하는 컴포넌트들이 데이터 접근 방식을 적절히 수정했는지 확인해주세요.


🏁 Script executed:

#!/bin/bash
# 이 훅을 사용하는 컴포넌트 검색
echo "useEventDetail 훅을 사용하는 파일 검색:"
rg -l "useEventDetail" --type tsx

Length of output: 130


🏁 Script executed:

#!/bin/bash
# useEventHook 훅을 사용하는 컴포넌트 검색
echo "useEventHook 훅을 사용하는 파일 검색:"
rg -l "useEventHook" -g '*.{ts,tsx}'

Length of output: 318


🏁 Script executed:

#!/bin/bash
# data.result 사용 여부 검색
echo "data.result 사용 검색:"
rg -n "data\\.result" -g '*.{ts,tsx}'

Length of output: 2536


eventDetail 반환 구조 변경에 따른 컴포넌트 수정 필요

response.data.resultresponse.data로 API 반환값이 바뀌었으므로, 아직 data.result를 참조하고 있는 컴포넌트들을 직접 data로 참조하도록 수정해야 합니다. 다음 파일들을 점검 및 리팩토링해 주세요:

  • src/widgets/event/ui/TicketInfo.tsx
  • src/pages/dashboard/ui/EventInfoPage.tsx
  • src/pages/dashboard/ui/ticket/TicketListPage.tsx
  • src/pages/dashboard/ui/EventDetailPage.tsx
  • 기타 data.result를 사용 중인 파일들 (rg 결과 참조)

수정 예시:

- const tickets = data.result;
+ const tickets = data;

위 파일들의 모든 data.result 참조를 data로 교체하고, 맵핑 로직이나 변수명도 일관되게 업데이트해 주세요.

Committable suggestion skipped: line range outside the PR's diff.

});

return { data };
Expand Down
13 changes: 13 additions & 0 deletions src/entities/host/api/hostDashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { axiosClient } from '../../../shared/types/api/http-client';
import { HostDashboardResponse } from '../model/hostDashboard';
import { ApiResponse } from '../../../shared/types/api/apiResponse';

const hostDashboard = async (eventId: number) => {
const response = await axiosClient.get<ApiResponse<HostDashboardResponse>>(`/host-channels/dashboard`, {
params: { eventId },
});

return response.data.result;
};

export default hostDashboard;
19 changes: 19 additions & 0 deletions src/entities/host/hook/hostDashboardHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import hostDashboard from '../api/hostDashboard';
import { useParams } from 'react-router-dom';

const useHostDashboard = () => {
const { id } = useParams();

const eventId = Number(id);

const { data, refetch } = useQuery({
queryKey: ['hostDashboard', eventId],
queryFn: () => hostDashboard(eventId),
enabled: !!eventId,
});

return { data, refetch };
};

export default useHostDashboard;
9 changes: 9 additions & 0 deletions src/entities/host/model/hostDashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface HostDashboardResponse {
eventName: string;
eventStartDate: string;
eventEndDate: string;
totalTicketCnt: number;
totalPrice: number;
ticketOption: boolean;
ticket: boolean;
}
35 changes: 30 additions & 5 deletions src/features/dashboard/ui/Checklist.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useNavigate } from 'react-router-dom';
import check from '../../../../public/assets/dashboard/main/Check.svg';
import completeCheck from '../../../../public/assets/dashboard/main/Check(complete).svg';
import { getMenuLists } from '../../../shared/types/dashboardType';
import { useParams } from 'react-router-dom';
// import completeCheck from '../../../../public/assets/dashboard/main/Check(complete).svg';
import useHostDashboard from '../../../entities/host/hook/hostDashboardHook';

const CheckList = () => {
const { id } = useParams();
const navigate = useNavigate();
const { data, refetch } = useHostDashboard();

const menuLists = id ? getMenuLists(id) : []; // id가 있을 때만 리스트 생성

Expand All @@ -16,21 +18,44 @@ const CheckList = () => {
)
.map(items => ({ text: items.text, path: items.path }));

const getProgressStatus = (index: number) => {
const statusMap = {
0: true, // 이벤트 기본 정보는 항상 true
1: true, // 이벤트 상세와 사진은 항상 true
2: Boolean(data?.ticket), // 티켓 생성하기
3: Boolean(data?.ticketOption), // 티켓에 추가 옵션 부착
};

return statusMap[index as keyof typeof statusMap];
};

const handleNavigate = (path: string) => {
refetch();
navigate(path);
};

return (
<div className="flex flex-col w-full h-full bg-white shadow-md rounded-[10px] gap-4 px-4 py-6">
<div className="flex flex-col">
<h2 className="text-base font-semibold">체크리스트</h2>
<h3 className="text-11 text-gray-500">이벤트를 열기 위해 꼭 필요한 정보에요.</h3>
</div>
<div className="flex w-full justify-between gap-2">
{[...Array(3)].map((_, index) => (
<div key={index} className="w-full h-[0.8vh] bg-gray-200 rounded-full" />
{[...Array(4)].map((_, index) => (
<div
key={index}
className={`w-full h-[0.8vh] rounded-full ${getProgressStatus(index) ? 'bg-main' : 'bg-gray-200'}`}
/>
))}
</div>
<div className="flex flex-col gap-2">
{checkLists.map((item, index) => (
<div key={index} onClick={() => navigate(item.path)} className="flex items-center gap-3 cursor-pointer">
<img src={check} alt="체크" className="w-4 h-4" />
<div key={index} onClick={() => handleNavigate(item.path)} className="flex items-center gap-3 cursor-pointer">
{getProgressStatus(index) ? (
<img src={completeCheck} alt="완료된 체크" className="w-4 h-4" />
) : (
<img src={check} alt="체크" className="w-4 h-4" />
)}
<div className="text-xs md:text-sm">{item.text}</div>
<span className="font-bold">&gt;</span>
</div>
Expand Down
71 changes: 58 additions & 13 deletions src/features/event-manage/event-create/ui/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,55 @@ interface DatePickerProps {
className?: string;
eventState?: FunnelState['eventState'];
setEventState?: React.Dispatch<React.SetStateAction<FunnelState['eventState']>>;
startDate?: string;
endDate?: string;
onStartDateChange?: (date: string) => void;
onEndDateChange?: (date: string) => void;
isLabel?: boolean;
}

const EventDatePicker = ({ className, eventState, setEventState, isLabel = false }: DatePickerProps) => {
const [startDate, setStartDate] = useState<Date | null>(
eventState?.startDate ? new Date(eventState.startDate) : new Date()
);
const [endDate, setEndDate] = useState<Date | null>(eventState?.endDate ? new Date(eventState.endDate) : new Date());
const EventDatePicker = ({
className,
eventState,
setEventState,
startDate: initialStartDate,
endDate: initialEndDate,
onStartDateChange,
onEndDateChange,
isLabel = false,
}: DatePickerProps) => {
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [startTime, setStartTime] = useState<string>('06:00');
const [endTime, setEndTime] = useState<string>('23:00');

useEffect(() => {
const start = eventState?.startDate || initialStartDate;
const end = eventState?.endDate || initialEndDate;

if (start && !startDate) {
const startDate = new Date(start);
setStartDate(startDate);
const hours = startDate.getHours().toString().padStart(2, '0');
const minutes = startDate.getMinutes().toString().padStart(2, '0');
setStartTime(`${hours}:${minutes}`);
}

if (end && !endDate) {
const endDate = new Date(end);
setEndDate(endDate);
const hours = endDate.getHours().toString().padStart(2, '0');
const minutes = endDate.getMinutes().toString().padStart(2, '0');
setEndTime(`${hours}:${minutes}`);
}
}, [eventState, initialStartDate, initialEndDate]);

const generateTimeOptions = () => {
const options = [];
for (let i = 0; i < 24; i++) {
for (let j = 0; j < 4; j++) {
const hour = i.toString().padStart(2, '0');
const minute = (j * 15).toString().padEnd(2, '0');
const minute = (j * 15).toString().padStart(2, '0');
options.push(`${hour}:${minute}`);
}
}
Expand All @@ -34,7 +66,7 @@ const EventDatePicker = ({ className, eventState, setEventState, isLabel = false
const timeOptions = generateTimeOptions();

useEffect(() => {
if (setEventState && startDate && endDate) {
if (startDate && endDate) {
const [startHour, startMin] = startTime.split(':').map(Number);
const [endHour, endMin] = endTime.split(':').map(Number);

Expand All @@ -44,13 +76,26 @@ const EventDatePicker = ({ className, eventState, setEventState, isLabel = false
const end = new Date(endDate);
end.setHours(endHour, endMin, 0, 0);

setEventState(prev => ({
...prev,
startDate: start.toISOString(),
endDate: end.toISOString(),
}));
const startISO = new Date(start.getTime() + 9 * 60 * 60 * 1000).toISOString();
const endISO = new Date(end.getTime() + 9 * 60 * 60 * 1000).toISOString();
Comment on lines +79 to +80
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

타임존 처리 관련 고려사항

날짜에 9시간을 더하는 방식으로 타임존 처리를 하고 있는데, 이는 한국 시간(KST, UTC+9)을 위한 것으로 보입니다. 하지만 이러한 하드코딩된 시간 조정은 문제가 될 수 있습니다.

타임존 처리는 Date 객체의 메서드나 moment.js, date-fns와 같은 라이브러리를 사용하여 좀 더 명시적으로 처리하는 것이 좋습니다. 현재 방식은 다른 타임존의 사용자에게 혼란을 줄 수 있습니다.

- const startISO = new Date(start.getTime() + 9 * 60 * 60 * 1000).toISOString();
- const endISO = new Date(end.getTime() + 9 * 60 * 60 * 1000).toISOString();
+ // date-fns 사용 예시
+ import { format, addHours } from 'date-fns';
+ import { zonedTimeToUtc } from 'date-fns-tz';
+ 
+ const startISO = zonedTimeToUtc(start, 'Asia/Seoul').toISOString();
+ const endISO = zonedTimeToUtc(end, 'Asia/Seoul').toISOString();

Committable suggestion skipped: line range outside the PR's diff.


if (setEventState) {
setEventState(prev => ({
...prev,
startDate: startISO,
endDate: endISO,
}));
}

if (onStartDateChange) {
onStartDateChange(startISO);
}

if (onEndDateChange) {
onEndDateChange(endISO);
}
}
}, [startDate, endDate, startTime, endTime, setEventState]);
}, [startDate, endDate, startTime, endTime, setEventState, onStartDateChange, onEndDateChange]);

return (
<div className={`flex flex-col w-full ${className}`}>
Expand Down
13 changes: 7 additions & 6 deletions src/pages/dashboard/ui/DashbaordPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@ import TicketRevenue from '../../../widgets/dashboard/ui/TicketRevenue';
import ticket from '../../../../public/assets/dashboard/main/Ticket(white).svg';
import cash from '../../../../public/assets/dashboard/main/Cash.svg';
import CheckList from '../../../features/dashboard/ui/Checklist';
import { useGetEventHook } from '../../../features/dashboard/hook/useEventHook';
import useHostDashboard from '../../../entities/host/hook/hostDashboardHook';

const DashboardPage = () => {
const { eventInfo } = useGetEventHook();
const { data } = useHostDashboard();

return (
<DashboardLayout centerContent="대시보드" pinkBg={true}>
<div className="flex flex-col mt-8 md:mt-13 px-7 gap-4">
<h1 className="text-2xl font-bold">{eventInfo?.eventName || '이벤트 이름 없음'}</h1>
<h1 className="text-2xl font-bold">{data?.eventName || '이벤트 이름 없음'}</h1>
<CheckList />
<EventOverview eventInfo={eventInfo} />
<EventOverview eventInfo={data} />
<div className="flex w-full justify-between gap-3">
<TicketRevenue
icon={<img src={ticket} alt="티켓" className="w-8 md:w-9" />}
title="티켓 판매 수"
value={`${eventInfo?.totalTicketCnt || '-'}장`}
value={`${data?.totalTicketCnt || '-'}장`}
/>
<TicketRevenue
icon={<img src={cash} alt="돈" className="w-8 md:w-9" />}
title="판매 금액"
value={`${eventInfo?.totalPrice || '-'}원`}
value={`${data?.totalPrice || '-'}원`}
/>
</div>
</div>
Expand Down
31 changes: 21 additions & 10 deletions src/pages/dashboard/ui/EventDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import TextEditor from '../../../features/event-manage/event-create/ui/TextEdito
import DashboardLayout from '../../../shared/ui/backgrounds/DashboardLayout';
import useEventDetail from '../../../entities/event/hook/useEventHook';
import { useUpdateEventHook } from '../../../features/dashboard/hook/useEventHook';
import { formatEventRequest } from '../../../shared/lib/formatEventRequest';
import { UpdateEventRequest } from '../../../features/dashboard/model/event';
import { OnlineType } from '../../../shared/types/baseEventType';

const EventDetailPage = () => {
const navigate = useNavigate();
Expand All @@ -31,16 +32,26 @@ const EventDetailPage = () => {
const handleSave = () => {
if (!data?.result.id) return;

const formatData = formatEventRequest(data.result);

const finalPayload = {
...formatData,
hostChannelId,
bannerImageUrl,
description,
referenceLinks: referenceLinks.map(({ title, url }) => ({ title, url })),
const requestData: UpdateEventRequest = {
hostChannelId: data.result.hostChannelId || hostChannelId,
title: data.result.title,
startDate: data.result.startDate,
endDate: data.result.endDate,
bannerImageUrl: bannerImageUrl || data.result.bannerImageUrl || '',
description: description || data.result.description || '',
referenceLinks: referenceLinks.map(({ title, url }) => ({ title, url })) || data.result.referenceLinks || [],
onlineType: data.result.onlineType as OnlineType,
address: data.result.address || '',
detailAddress: data.result.detailAddress || '',
locationLat: data.result.locationLat || 0,
locationLng: data.result.locationLng || 0,
category: data.result.category || 'DEVELOPMENT_STUDY',
hashtags: data.result.hashtags || [],
organizerEmail: data.result.organizerEmail || '',
organizerPhoneNumber: data.result.organizerPhoneNumber || '',
};
mutate(finalPayload, {

mutate(requestData, {
onSuccess: () => {
alert('이벤트 정보가 저장되었습니다.');
navigate(`/dashboard/${data?.result.id}`);
Expand Down
Loading