diff --git a/src/app/(pages)/alba/[formId]/page.tsx b/src/app/(pages)/alba/[formId]/page.tsx new file mode 100644 index 00000000..3b3d7590 --- /dev/null +++ b/src/app/(pages)/alba/[formId]/page.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useParams } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import { useUser } from "@/hooks/queries/user/me/useUser"; +import FormHeader from "../components/FormHeader"; +import FormDetails from "../components/FormDetail"; +import RecruitInformation from "../components/RecruitInfomation"; +import ApplicationStatus from "../components/ApplicationStatus"; +import { useFormDetail } from "@/hooks/queries/form/detail/useFormDetail"; + +export default function AlbaFormDetailPage() { + const { formId } = useParams(); // useParams로 formId 추출 + const [formIdState, setFormIdState] = useState(0); + const { user } = useUser(); + const isOwner = user?.role === "OWNER"; + + useEffect(() => { + // formId가 문자열로 전달되므로 숫자로 변환하여 상태에 저장 + if (formId) { + setFormIdState(Number(formId)); // formId를 숫자로 변환하여 상태에 저장 + } + }, [formId]); + + // formId가 설정되면 useFormDetail 호출 + const { albaFormDetailData, isLoading, error } = useFormDetail({ formId: formIdState }); + + if (isLoading) return
Loading...
; + + if (error) return
Error: 데이터를 불러오는데 문제가 발생했습니다.
; + + if (!albaFormDetailData) return
데이터가 없습니다.
; + + return ( +
+
사진영역
+
+ {/* 왼쪽 영역 */} +
+ + + {/* 지도 영역 */} +
카카오지도
+
+ {/* 오른쪽 영역 */} +
+ +
+
+ {/* 지원 현황 */} + {isOwner && } +
+ ); +} diff --git a/src/app/(pages)/alba/components/ApplicationStatus.tsx b/src/app/(pages)/alba/components/ApplicationStatus.tsx new file mode 100644 index 00000000..e4fa4247 --- /dev/null +++ b/src/app/(pages)/alba/components/ApplicationStatus.tsx @@ -0,0 +1,37 @@ +import ApplicationStatusCard from "@/app/components/card/cardList/ApplicationStatusCard"; +import { useApplicationStatus } from "@/hooks/queries/form/detail/useApplicationStatus"; +import React from "react"; + +interface ApplicationStatusProps { + formId: number; +} + +export default function ApplicationStatus({ formId }: ApplicationStatusProps) { + const { applicationStatusData, isLoading, error } = useApplicationStatus({ + formId, + limit: 5, // 요청당 데이터 수 제한 + }); + + if (isLoading) return
지원 현황을 불러오는 중입니다...
; + if (error && "status" in error) { + if (error.status === 403) { + return ( +
+

지원 현황을 볼 권한이 없습니다.

+

{error.message}

+
+ ); + } + console.log("지원현황 에러", error); + } + if (error || !applicationStatusData) return
지원 현황 데이터를 불러오는데 실패했습니다.
; + console.log("applicationStatusData:", applicationStatusData); + + return ( +
+

지원 현황

+ + +
+ ); +} diff --git a/src/app/(pages)/alba/components/FormActions.tsx b/src/app/(pages)/alba/components/FormActions.tsx new file mode 100644 index 00000000..acd4b8c5 --- /dev/null +++ b/src/app/(pages)/alba/components/FormActions.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import Button from "@/app/components/button/default/Button"; +import { FcEmptyTrash } from "react-icons/fc"; +import { FcEditImage } from "react-icons/fc"; +import { FcFile } from "react-icons/fc"; +import { FcSearch } from "react-icons/fc"; + +interface FormActionsProps { + isOwner: boolean; +} + +export default function FormActions({ isOwner }: FormActionsProps) { + return ( +
+ + +
+ ); +} diff --git a/src/app/(pages)/alba/components/FormDetail.tsx b/src/app/(pages)/alba/components/FormDetail.tsx new file mode 100644 index 00000000..157a19b8 --- /dev/null +++ b/src/app/(pages)/alba/components/FormDetail.tsx @@ -0,0 +1,36 @@ +import { FormDetailResponse } from "@/types/response/form"; +import React from "react"; +import toast from "react-hot-toast"; + +interface FormDetailsProps { + albaFormDetailData: FormDetailResponse; +} + +export default function FormDetails({ albaFormDetailData }: FormDetailsProps) { + const handleCopyLocation = () => { + navigator.clipboard.writeText(albaFormDetailData.location); + toast.success("근무지역이 복사되었습니다."); + }; + + return ( + <> +
+ {albaFormDetailData.storeName || "가게명"} + + {albaFormDetailData.location || "위치"} ・ {"경력 정보 없음"} + +
+

{albaFormDetailData.title}

+
+
{albaFormDetailData.description}
+

근무 지역

+
+
+

{albaFormDetailData.location}

+ +
+ + ); +} diff --git a/src/app/(pages)/alba/components/FormHeader.tsx b/src/app/(pages)/alba/components/FormHeader.tsx new file mode 100644 index 00000000..cf369862 --- /dev/null +++ b/src/app/(pages)/alba/components/FormHeader.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import Chip from "@/app/components/chip/Chip"; +import { FormDetailResponse } from "@/types/response/form"; + +interface FormHeaderProps { + albaFormDetailData: FormDetailResponse; +} + +export default function FormHeader({ albaFormDetailData }: FormHeaderProps) { + const recruitmentStatus = new Date(albaFormDetailData.recruitmentEndDate) > new Date() ? "모집중" : "모집완료"; + return ( +
+ + +

{new Date(albaFormDetailData.createdAt).toLocaleString()} 등록

+
+ ); +} diff --git a/src/app/(pages)/alba/components/RecruitInfomation.tsx b/src/app/(pages)/alba/components/RecruitInfomation.tsx new file mode 100644 index 00000000..458bff81 --- /dev/null +++ b/src/app/(pages)/alba/components/RecruitInfomation.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import RecruitDetail from "@/app/components/card/cardList/RecruitDetail"; +import RecruitIcon from "@/app/components/card/cardList/RecruitIcon"; +import RecruitCondition from "@/app/components/card/cardList/RecruitCondition"; +import { FormDetailResponse } from "@/types/response/form"; +import FormActions from "./FormActions"; +import { useUser } from "@/hooks/queries/user/me/useUser"; + +interface FormDetailsProps { + albaFormDetailData: FormDetailResponse; +} + +export default function RecruitInformation({ albaFormDetailData }: FormDetailsProps) { + const { user } = useUser(); + const isOwner = user?.role === "OWNER"; + + const recruitmentDetails = { + hourlyWage: albaFormDetailData.hourlyWage, + recruitmentStartDate: new Date(albaFormDetailData.recruitmentStartDate), + recruitmentEndDate: new Date(albaFormDetailData.recruitmentEndDate), + isNegotiableWorkDays: albaFormDetailData.isNegotiableWorkDays, + workDays: albaFormDetailData.workDays, + workStartTime: albaFormDetailData.workStartTime, + workEndTime: albaFormDetailData.workEndTime, + }; + + return ( + <> + + + + +

모집 조건

+ + + ); +} diff --git a/src/app/(pages)/albaFormDetail/layout.tsx b/src/app/(pages)/alba/layout.tsx similarity index 100% rename from src/app/(pages)/albaFormDetail/layout.tsx rename to src/app/(pages)/alba/layout.tsx diff --git a/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx b/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx deleted file mode 100644 index 29a68e2a..00000000 --- a/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { useUserFormDetail } from "@/hooks/queries/form/userFormDetail"; -import React, { useEffect, useState } from "react"; -import CardChipIcon from "@/app/components/card/cardList/CardChipIcon"; -import Chip from "@/app/components/chip/Chip"; - -export default function AlbaFormDetailPage() { - const { formId } = useParams(); // useParams로 formId 추출 - const [formIdState, setFormIdState] = useState(0); - - useEffect(() => { - // formId가 문자열로 전달되므로 숫자로 변환하여 상태에 저장 - if (formId) { - setFormIdState(Number(formId)); // formId를 숫자로 변환하여 상태에 저장 - } - }, [formId]); - - // formId가 설정되면 useUserFormDetail 호출 - const { data, isLoading, error } = useUserFormDetail({ formId: formIdState }); - - if (isLoading) { - return
Loading...
; - } - - if (error) { - return
Error: 데이터를 불러오는데 문제가 발생했습니다.
; - } - - if (!data) { - return
데이터가 없습니다.
; - } - - // 모집 상태 계산 - const recruitmentStatus = new Date(data.recruitmentEndDate) > new Date() ? "모집중" : "모집완료"; - - return ( -
-
사진영역
-
-
-
- - -

{new Date(data.createdAt).toLocaleString()} 등록

-
-
- {data.storeName || "가게명"} - - {data.location || "위치"} ・ {"경력 정보 없음"} - -
-

{data.title}

- -
{data.description}
-
-
-
- ); -} diff --git a/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx b/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx deleted file mode 100644 index 8754305e..00000000 --- a/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; -import CardChipIcon from "@/app/components/card/cardList/CardChipIcon"; -import Chip from "@/app/components/chip/Chip"; -import ChipWithIcon from "@/app/components/chip/ChipWithIcon"; -import React from "react"; - -// 알바폼 상세 사장님 페이지 -export default function AlbaFormDetailPage({ formId }: { formId: number }) { - return ( -
-
사진영역
-
-
-
- - -

2024. 05. 04 12:30:54 등록

-
-
- 코드잇 - 서울 종로구 ・ 경력 무관 -
-

코드잇 스터디카페 관리 (주말 오전) 모집합니다 서울 종로구 용산구 서대문

- -
- 코드잇 스터디 카페입니다. 주말 토, 일 오픈업무 하실 분 구합니다. 성실하게 일하실 분들만 지원 바랍니다. - 작성한 이력서(사진 부착)를 알바폼에 첨부해주시고, 아래와 같이 문자 보내주세요. 근무 중 전화통화 불가합니다. - 예) OOO입니다. __에 거주합니다. 알바폼 지원. 이력서 검토 후 면접진행자에 한해 면접일정 개별 - 연락드리겠습니다. 많은 지원 바랍니다. -
-
-
{/* 오른쪽 콘텐츠 */}
-
-
- ); -} diff --git a/src/app/(pages)/albaList/page.tsx b/src/app/(pages)/albaList/page.tsx index 721e35f0..11f0929a 100644 --- a/src/app/(pages)/albaList/page.tsx +++ b/src/app/(pages)/albaList/page.tsx @@ -149,7 +149,9 @@ export default function AlbaList() { {page.data.map((form) => (
- + + +
))}
diff --git a/src/app/components/button/default/Button.tsx b/src/app/components/button/default/Button.tsx index 842838a9..9d39aa88 100644 --- a/src/app/components/button/default/Button.tsx +++ b/src/app/components/button/default/Button.tsx @@ -50,10 +50,10 @@ const Button = ({ }; const widths = { - xs: "w-[80px]", - sm: "w-[180px]", - md: "w-[327px]", - lg: "w-[640px]", + xs: "w-[60px] md:w-[80px]", + sm: "w-[120px] md:w-[180px]", + md: "w-[240px] lg:w-[327px]", + lg: "w-[480px] xl:w-[640px]", }; const radiuses = { diff --git a/src/app/components/card/cardList/ApplicationStatusCard.tsx b/src/app/components/card/cardList/ApplicationStatusCard.tsx index 414ef8df..f0a7d841 100644 --- a/src/app/components/card/cardList/ApplicationStatusCard.tsx +++ b/src/app/components/card/cardList/ApplicationStatusCard.tsx @@ -1,81 +1,20 @@ "use client"; + import React, { useState } from "react"; import { ApplicationResponse } from "@/types/response/application"; import { FaSortAmountDown } from "react-icons/fa"; import { cn } from "@/lib/tailwindUtil"; + interface ApplicationStatusCardProps { - applications: ApplicationResponse[]; + applicationStatusData: ApplicationResponse[]; // 전달받는 데이터는 배열 } -// 지원현황 카드 컴포넌트 -const ApplicationStatusCard = ({ applications }: ApplicationStatusCardProps) => { - const [sortOrder, setSortOrder] = useState<{ experience: boolean; status: boolean }>({ - experience: true, // true: 오름차순, false: 내림차순 - status: true, - }); - - const handleSort = (key: "experience" | "status") => { - const newOrder = !sortOrder[key]; - setSortOrder((prev) => ({ ...prev, [key]: newOrder })); - }; - - const sortedApplications = [...applications] - .sort((a, b) => { - if (sortOrder.experience) { - return a.experienceMonths - b.experienceMonths; // 경력 오름차순 - } else { - return b.experienceMonths - a.experienceMonths; // 경력 내림차순 - } - }) - .sort((a, b) => { - if (sortOrder.status) { - return a.status.localeCompare(b.status); // 상태 오름차순 - } else { - return b.status.localeCompare(a.status); // 상태 내림차순 - } - }); - - const theadStyle = "text-left font-semibold text-grayscale-700"; - const tbodyStyle = "text-left"; - const sortIconStyle = "text-primary-orange-300 transition-transform hover:scale-110"; +export default function ApplicationStatusCard({ applicationStatusData }: ApplicationStatusCardProps) { return ( -
-
- {/* Thead */} -
- 이름 - 전화번호 -
- 경력 - -
-
- 상태 - -
-
- - {/* Tbody */} -
- {sortedApplications.map((application) => ( -
- {application.name} - {application.phoneNumber} - {application.experienceMonths}개월 - {application.status} -
- ))} -
-
+
+ {applicationStatusData.map((application) => ( +
{application.name}
// 고유 키 추가 + ))}
); -}; - -export default ApplicationStatusCard; +} diff --git a/src/app/stories/design-system/components/card/cardList/ApplicationStatusCard.stories.tsx b/src/app/stories/design-system/components/card/cardList/ApplicationStatusCard.stories.tsx deleted file mode 100644 index 7caf259a..00000000 --- a/src/app/stories/design-system/components/card/cardList/ApplicationStatusCard.stories.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Meta, StoryObj } from "@storybook/react"; -import ApplicationStatusCard from "@/app/components/card/cardList/ApplicationStatusCard"; -import { ApplicationResponse } from "@/types/response/application"; - -const meta: Meta = { - title: "Design System/Components/Card/CardList/ApplicationStatusCard", - component: ApplicationStatusCard, - parameters: { - layout: "centered", - }, -}; - -export default meta; -type Story = StoryObj; - -const mockApplications: ApplicationResponse[] = [ - { - applicantId: 1, - updatedAt: new Date(), - createdAt: new Date(), - status: "submitted", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서.pdf", - resumeId: 123, - experienceMonths: 12, - phoneNumber: "010-1234-5678", - name: "홍길동", - id: 1, - }, - { - applicantId: 2, - updatedAt: new Date(), - createdAt: new Date(), - status: "interview", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서2.pdf", - resumeId: 124, - experienceMonths: 6, - phoneNumber: "010-9876-5432", - name: "김철수", - id: 2, - }, - { - applicantId: 3, - updatedAt: new Date(), - createdAt: new Date(), - status: "hired", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서3.pdf", - resumeId: 125, - experienceMonths: 24, - phoneNumber: "010-1111-2222", - name: "이영희", - id: 3, - }, - { - applicantId: 4, - updatedAt: new Date(), - createdAt: new Date(), - status: "rejected", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서4.pdf", - resumeId: 126, - experienceMonths: 3, - phoneNumber: "010-2222-3333", - name: "박지민", - id: 4, - }, - { - applicantId: 5, - updatedAt: new Date(), - createdAt: new Date(), - status: "submitted", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서5.pdf", - resumeId: 127, - experienceMonths: 18, - phoneNumber: "010-4444-5555", - name: "최민수", - id: 5, - }, - { - applicantId: 6, - updatedAt: new Date(), - createdAt: new Date(), - status: "interview", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서6.pdf", - resumeId: 128, - experienceMonths: 8, - phoneNumber: "010-6666-7777", - name: "이수진", - id: 6, - }, - { - applicantId: 7, - updatedAt: new Date(), - createdAt: new Date(), - status: "hired", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서7.pdf", - resumeId: 129, - experienceMonths: 15, - phoneNumber: "010-8888-9999", - name: "김하늘", - id: 7, - }, - { - applicantId: 8, - updatedAt: new Date(), - createdAt: new Date(), - status: "rejected", - introduction: "안녕하세요, 저는 지원자입니다.", - resumeName: "이력서8.pdf", - resumeId: 130, - experienceMonths: 1, - phoneNumber: "010-0000-1111", - name: "이정민", - id: 8, - }, -]; - -export const Default: Story = { - args: { - applications: mockApplications, - }, -}; diff --git a/src/hooks/queries/form/detail/useApplicationStatus.ts b/src/hooks/queries/form/detail/useApplicationStatus.ts new file mode 100644 index 00000000..694de6c5 --- /dev/null +++ b/src/hooks/queries/form/detail/useApplicationStatus.ts @@ -0,0 +1,47 @@ +// 지원 현황 목록 조회 + +import { ApplicationListResponse } from "@/types/response/application"; +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; + +interface UseApplicationStatusProps { + formId: number; + limit: number; + cursor?: number; + orderByExperience?: string; + orderByStatus?: string; +} + +export const useApplicationStatus = (props: UseApplicationStatusProps) => { + const query = useQuery({ + queryKey: [ + "applicationStatus", + props.formId, + props.limit, + props.cursor, + props.orderByExperience, + props.orderByStatus, + ], + queryFn: async () => { + const response = await axios.get(`/api/forms/${props.formId}/applications`, { + params: { + limit: props.limit, + cursor: props.cursor, + orderByExperience: props.orderByExperience, + orderByStatus: props.orderByStatus, + }, + }); + + return response.data; + }, + enabled: !!props.formId, + }); + + console.log("지원형황", query.data); + return { + ...query, + applicationStatusData: query.data, + isPending: query.isPending, + error: query.error, + }; +}; diff --git a/src/hooks/queries/form/userFormDetail.ts b/src/hooks/queries/form/detail/useFormDetail.ts similarity index 70% rename from src/hooks/queries/form/userFormDetail.ts rename to src/hooks/queries/form/detail/useFormDetail.ts index a1029129..bc3844dc 100644 --- a/src/hooks/queries/form/userFormDetail.ts +++ b/src/hooks/queries/form/detail/useFormDetail.ts @@ -2,11 +2,11 @@ import { FormDetailResponse } from "@/types/response/form"; import { useQuery } from "@tanstack/react-query"; import axios from "axios"; -interface UseUserFormDetailParams { +interface UseFormDetailParams { formId?: number; } -export const useUserFormDetail = ({ formId }: UseUserFormDetailParams) => { +export const useFormDetail = ({ formId }: UseFormDetailParams) => { const query = useQuery({ queryKey: ["formDetail", formId], queryFn: async () => { @@ -14,13 +14,14 @@ export const useUserFormDetail = ({ formId }: UseUserFormDetailParams) => { throw new Error("formId가 없습니다."); } const response = await axios.get(`/api/forms/${formId}`); - return response.data; + return response.data; // 이 부분을 수정합니다. }, enabled: !!formId, // formId가 유효한 경우에만 쿼리를 실행 }); return { ...query, + albaFormDetailData: query.data, // response.data를 albaFormDetailData로 변경 isPending: query.isPending, error: query.error, };