Skip to content
6 changes: 6 additions & 0 deletions src/components/estimate/ReceivedRequestsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export default function ReceivedRequestsPage() {

return (
<>
<div className="border-border-subtle border-b">
<h1 className="text-text-primary mx-auto flex h-[54px] w-full max-w-[1200px] items-center px-24 text-lg font-semibold min-[744px]:px-[72px] lg:h-[96px] lg:px-0 lg:text-2xl">
받은 요청
</h1>
</div>

<main className="mx-auto flex max-w-[1200px] flex-col gap-0 px-24 pb-80 min-[744px]:px-[72px] lg:gap-40 lg:px-0">
<section className="flex flex-col gap-24">
<form onSubmit={submitSearch} className="mx-10 w-[calc(100%_-_20px)] lg:mx-0 lg:w-full">
Expand Down
35 changes: 24 additions & 11 deletions src/components/estimate/sent/SentEstimateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,32 @@ function RouteArrow() {
);
}

function formatMoveDate(date: string): string {
return formatKoreanDateTime(date).replace(
/^(\d{4})\.\s*(\d{2})\.\s*(\d{2})\.\s*/,
"$1년 $2월 $3일 ",
);
}

export default function SentEstimateCard({ estimate, onViewDetail }: SentEstimateCardProps) {
const isConfirmed = estimate.status !== "SENT";
const isCompleted = estimate.status === "COMPLETED";

return (
<article className="border-border-subtle bg-background-default shadow-estimate-card rounded-20 relative flex min-h-[333px] w-full flex-col gap-24 overflow-hidden border-[0.5px] px-20 py-24 md:min-h-[322px] md:gap-32 md:px-40 md:py-32 lg:min-h-[324px]">
<div className="flex flex-1 flex-col gap-16 md:gap-24">
<article className="border-border-subtle bg-background-default shadow-estimate-card rounded-20 relative flex min-h-[333px] w-full flex-col gap-24 overflow-hidden border-[0.5px] px-20 py-24 min-[744px]:min-h-[322px] min-[744px]:gap-32 min-[744px]:px-40 min-[744px]:py-32 lg:min-h-[324px]">
<div className="flex flex-1 flex-col gap-16 min-[744px]:gap-24">
<div className="flex min-h-26 items-center justify-between">
<div className="flex items-center gap-8">
<MoveTypeChip moveType={estimate.moveType} size="sm" className="md:hidden" />
<MoveTypeChip moveType={estimate.moveType} size="sm" className="min-[744px]:hidden" />
<MoveTypeChip
moveType={estimate.moveType}
size="md"
className="hidden md:inline-flex"
className="hidden min-[744px]:inline-flex"
/>
{estimate.isDesignated ? (
<>
<DesignatedChip size="sm" className="md:hidden" />
<DesignatedChip size="md" className="hidden md:inline-flex" />
<DesignatedChip size="sm" className="min-[744px]:hidden" />
<DesignatedChip size="md" className="hidden min-[744px]:inline-flex" />
</>
) : null}
</div>
Expand All @@ -68,7 +75,7 @@ export default function SentEstimateCard({ estimate, onViewDetail }: SentEstimat
<div className="bg-border-subtle h-px" />
</div>

<dl className="flex flex-col gap-12 md:flex-row md:items-start md:justify-between md:gap-20">
<dl className="flex flex-col gap-12 min-[744px]:flex-row min-[744px]:items-start min-[744px]:justify-between min-[744px]:gap-20">
<div className="flex items-end gap-12">
<div>
<Text as="dt" variant="md-regular" className="text-text-muted">
Expand All @@ -94,17 +101,23 @@ export default function SentEstimateCard({ estimate, onViewDetail }: SentEstimat
이사일
</Text>
<Text as="dd" variant="lg-semibold" className="text-text-secondary whitespace-nowrap">
{formatKoreanDateTime(estimate.moveDate)}
{formatMoveDate(estimate.moveDate)}
</Text>
</div>
</dl>
</div>

<div className="border-border-default flex h-47 items-end justify-between border-t md:h-52">
<Text variant={{ base: "md-medium", md: "lg-medium" }} className="text-text-muted">
<div className="border-border-default flex h-47 items-end justify-between border-t min-[744px]:h-52">
<Text
variant={{ base: "md-medium", md: "lg-medium" }}
className="text-text-muted min-[744px]:text-[length:var(--font-size-16)] min-[744px]:leading-[var(--line-height-26)]"
>
견적 금액
</Text>
<Text variant={{ base: "2lg-bold", md: "2xl-bold" }} className="text-text-secondary">
<Text
variant={{ base: "2lg-bold", md: "2xl-bold" }}
className="text-text-secondary min-[744px]:text-[length:var(--font-size-24)] min-[744px]:leading-[var(--line-height-32)]"
>
{estimate.price.toLocaleString("ko-KR")}원
</Text>
</div>
Expand Down
64 changes: 46 additions & 18 deletions src/components/estimate/sent/SentEstimateDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import EstimateDetailLayout, {
} from "@/components/estimate/detail/EstimateDetailLayout";
import { EstimateDetailInfoSection } from "@/components/estimate/detail/EstimateDetailInfoSection";
import EstimateDetailPrice from "@/components/estimate/detail/EstimateDetailPrice";
import EstimateDetailShare from "@/components/estimate/detail/EstimateDetailShare";
import { DesignatedChip, MoveTypeChip } from "@/components/estimate/received/MoveTypeChip";
import { useSentEstimateDetail } from "@/hooks/useSentEstimates";
import FrameIcon from "@/icons/frame.svg";
Expand Down Expand Up @@ -70,6 +69,32 @@ function SentEstimateSummary({ estimate }: { estimate: SentEstimate }) {
);
}

function SentEstimateComment({ comment }: { comment: string }) {
return (
<section
className="flex w-full flex-col gap-20 md:gap-28"
aria-labelledby="mover-comment-title"
>
<h2 id="mover-comment-title" className="text-text-primary">
<Text as="span" variant="lg-semibold" className="md:hidden">
기사님 코멘트
</Text>
<Text as="span" variant="xl-semibold" className="hidden md:inline">
기사님 코멘트
</Text>
</h2>

<Text
as="p"
variant="lg-medium"
className="text-text-muted wrap-break-word whitespace-pre-wrap"
>
{comment}
</Text>
</section>
);
}

export default function SentEstimateDetailPage({ estimateId }: SentEstimateDetailPageProps) {
const query = useSentEstimateDetail(estimateId);

Expand Down Expand Up @@ -97,32 +122,35 @@ export default function SentEstimateDetailPage({ estimateId }: SentEstimateDetai
contentClassName="pt-35 pb-64 md:pt-[46px] md:pb-80 lg:pt-[43px] lg:pb-37-5"
rowClassName="gap-20 md:gap-32 lg:gap-0"
mainClassName="gap-20 md:gap-30 lg:w-185"
asideClassName="border-border-subtle gap-12 border-t pt-20 md:gap-22 md:pt-32 lg:w-xs lg:border-t-0 lg:pt-0"
main={
<>
<div className="flex w-full flex-col gap-20 md:gap-26">
<SentEstimateSummary estimate={estimate} />
<EstimateDetailPrice price={estimate.price} />
</div>

<EstimateDetailInfoSection
rows={[
{ label: "견적 요청일", value: formatKoreanDateTime(request.requestedAt) },
{ label: "서비스", value: MOVE_TYPE_LABEL[request.moveType] },
{ label: "이용일", value: formatKoreanDateTime(request.moveDate) },
{
label: "출발지",
value: formatAddress(request.fromAddress, request.fromDetailAddress),
},
{
label: "도착지",
value: formatAddress(request.toAddress, request.toDetailAddress),
},
]}
/>
<div className="flex w-full flex-col gap-20 md:gap-28">
<EstimateDetailInfoSection
rows={[
{ label: "견적 요청일", value: formatKoreanDateTime(request.requestedAt) },
{ label: "서비스", value: MOVE_TYPE_LABEL[request.moveType] },
{ label: "이용일", value: formatKoreanDateTime(request.moveDate) },
{
label: "출발지",
value: formatAddress(request.fromAddress, request.fromDetailAddress),
},
{
label: "도착지",
value: formatAddress(request.toAddress, request.toDetailAddress),
},
]}
/>
<div className="border-border-subtle w-full border-t" aria-hidden="true" />
</div>

<SentEstimateComment comment={estimate.comment} />
</>
}
aside={<EstimateDetailShare linkAccess="owner" />}
/>
);
}
6 changes: 3 additions & 3 deletions src/components/estimate/sent/SentEstimatesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function SentEstimatesPage() {
return (
<>
<MoverEstimateTabs />
<main className="bg-background-subtle min-h-[calc(100vh-108px)] px-24 pt-24 pb-[77px] md:min-h-[calc(100vh-142px)] md:px-72 md:pt-32 md:pb-40 lg:min-h-[calc(100vh-168px)] lg:px-0 lg:pt-[59px] lg:pb-[107px]">
<main className="bg-background-subtle min-h-[calc(100vh-108px)] px-24 pt-24 pb-[77px] min-[744px]:min-h-[calc(100vh-142px)] min-[744px]:px-72 min-[744px]:pt-32 min-[744px]:pb-40 min-[1344px]:min-h-[calc(100vh-168px)] min-[1344px]:px-0 min-[1344px]:pt-[59px] min-[1344px]:pb-[107px]">
<h1 className="sr-only">보낸 견적 조회</h1>

{query.isPending ? <EstimatesQueryStatus message="보낸 견적을 불러오는 중이에요." /> : null}
Expand All @@ -52,9 +52,9 @@ export default function SentEstimatesPage() {
) : null}

{estimates.length > 0 ? (
<div className="mx-auto grid w-full grid-cols-1 gap-20 md:max-w-[600px] md:gap-32 lg:max-w-[1200px] lg:grid-cols-2 lg:gap-24">
<div className="mx-auto grid w-full min-w-0 grid-cols-1 gap-20 min-[744px]:max-w-[600px] min-[744px]:gap-32 min-[1344px]:max-w-[1200px] min-[1344px]:grid-cols-2 min-[1344px]:gap-24">
{estimates.map((estimate) => (
<div key={estimate.id}>
<div key={estimate.id} className="w-full min-w-0 min-[744px]:w-[588px]">
<SentEstimateCard
estimate={{
id: estimate.id,
Expand Down
12 changes: 7 additions & 5 deletions src/hooks/useMoverEstimateRequests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useQueryClient } from "@tanstack/react-query";

import { useApiInfiniteQuery } from "@/hooks/queries/useApiInfiniteQuery";
import { useApiMutation } from "@/hooks/queries/useApiMutation";
import {
getMoverEstimateRequests,
getRejectedEstimateRequests,
Expand All @@ -14,7 +16,7 @@ import type {
} from "@/types/moverEstimateRequest";

export function useMoverEstimateRequests(query: MoverEstimateRequestQuery) {
return useInfiniteQuery({
return useApiInfiniteQuery({
queryKey: [...QUERY_KEYS.ESTIMATES.ALL, query],
queryFn: ({ pageParam }) => getMoverEstimateRequests({ ...query, cursor: pageParam }),
initialPageParam: undefined as string | undefined,
Expand All @@ -24,7 +26,7 @@ export function useMoverEstimateRequests(query: MoverEstimateRequestQuery) {

//견적 반려 조회
export function useRejectedEstimateRequests() {
return useInfiniteQuery({
return useApiInfiniteQuery({
queryKey: QUERY_KEYS.ESTIMATES.REJECTED,
queryFn: ({ pageParam }) => getRejectedEstimateRequests(pageParam),
initialPageParam: undefined as string | undefined,
Expand All @@ -41,7 +43,7 @@ type SendMoverEstimateVariables = {
export function useSendMoverEstimate() {
const queryClient = useQueryClient();

return useMutation({
return useApiMutation({
mutationFn: ({ estimateRequestId, input }: SendMoverEstimateVariables) =>
sendMoverEstimate(estimateRequestId, input),

Expand All @@ -61,7 +63,7 @@ type RejectMoverEstimateVariables = {
export function useRejectMoverEstimate() {
const queryClient = useQueryClient();

return useMutation({
return useApiMutation({
mutationFn: ({ estimateRequestId, input }: RejectMoverEstimateVariables) =>
rejectMoverEstimate(estimateRequestId, input),
onSuccess: async () => {
Expand Down
48 changes: 12 additions & 36 deletions src/lib/api/moverEstimateRequests.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type {
MoverEstimateRequestQuery,
MoverEstimateRequestResponse,
MoverEstimateRequestResult,
RejectEstimateRequest,
RejectEstimateResponse,
RejectedEstimateRequestListResponse,
RejectedEstimate,
RejectedEstimateRequestListResult,
SendEstimateRequest,
SendEstimateResponse,
SentEstimate,
} from "@/types/moverEstimateRequest";

import { API_ROUTES } from "../constants/apiRoutes";
import axiosInstance from "./axiosInstance";
import fetchInstance from "@/lib/api/fetchInstance";
import { API_ROUTES } from "@/lib/constants/apiRoutes";

// 기사 견적 요청 목록 조회
// GET /api/estimates/requests
Expand All @@ -34,45 +34,27 @@ export async function getMoverEstimateRequests(query: MoverEstimateRequestQuery)
//이사 유형 필터
query.moveType?.forEach((moveType) => params.append("moveType", moveType));

const response = await axiosInstance.get<MoverEstimateRequestResponse>(
return fetchInstance.get<MoverEstimateRequestResult>(
`${API_ROUTES.ESTIMATES.REQUESTS}?${params.toString()}`,
);

if (!response.data.success) {
throw new Error(response.data.error.code);
}

return response.data.data;
}

// 기사가 고객의 견적 요청에 견적 전송
// POST /api/estimates/requests/:estimateRequestId
export async function sendMoverEstimate(estimateRequestId: number, input: SendEstimateRequest) {
const response = await axiosInstance.post<SendEstimateResponse>(
export function sendMoverEstimate(estimateRequestId: number, input: SendEstimateRequest) {
return fetchInstance.post<SentEstimate, SendEstimateRequest>(
API_ROUTES.ESTIMATES.SEND(estimateRequestId),
input,
);

if (!response.data.success) {
throw new Error(response.data.error.message);
}

return response.data.data;
}

// 기사 견적 반려
// POST /api/estimates/requests/:id/reject
export async function rejectMoverEstimate(estimateRequestId: number, input: RejectEstimateRequest) {
const response = await axiosInstance.post<RejectEstimateResponse>(
export function rejectMoverEstimate(estimateRequestId: number, input: RejectEstimateRequest) {
return fetchInstance.post<RejectedEstimate, RejectEstimateRequest>(
API_ROUTES.ESTIMATES.REJECT(estimateRequestId),
input,
);

if (!response.data.success) {
throw new Error(response.data.error.message);
}

return response.data.data;
}

//기사님 반려 내역 조회
Expand All @@ -81,13 +63,7 @@ export async function getRejectedEstimateRequests(cursor?: string, limit = 10) {
const params = new URLSearchParams({ limit: String(limit) });
if (cursor) params.set("cursor", cursor);

const response = await axiosInstance.get<RejectedEstimateRequestListResponse>(
return fetchInstance.get<RejectedEstimateRequestListResult>(
Comment thread
soooob43 marked this conversation as resolved.
`${API_ROUTES.ESTIMATES.REJECTIONS}?${params.toString()}`,
);

if (!response.data.success) {
throw new Error(response.data.error.message);
}

return response.data.data;
}
Loading