Skip to content

Commit

Permalink
Merge pull request #122 from review-canvas/develop
Browse files Browse the repository at this point in the history
v0.2.0을 배포한다
  • Loading branch information
hello-mansoo authored May 27, 2024
2 parents 1532fc1 + 9e1015a commit 0104312
Show file tree
Hide file tree
Showing 36 changed files with 2,052 additions and 340 deletions.
495 changes: 425 additions & 70 deletions apps/shop-admin/src/app/dashboard/page.tsx

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions apps/shop-admin/src/app/demo/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import DashboardLayout from '@/components/layout/dashboard-layout';

export default function DemoPageLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return <DashboardLayout>{children}</DashboardLayout>;
}
78 changes: 78 additions & 0 deletions apps/shop-admin/src/app/demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client';

import { useOverlayAction } from '@/store/overlay.ts';

function DemoPage() {
const { openOverlay } = useOverlayAction();

return (
<main className="flex flex-col gap-4">
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay('test', <div className="bg-white">test</div>);
}}
type="button"
>
예시 1 : 모달 열기
</button>
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay(
'test',
<div className="bg-white">
test
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay('test2', <div className="bg-blue-200">test2</div>, { position: 'S' });
}}
type="button"
>
다음 모달 열기
</button>
</div>,
);
}}
type="button"
>
예시 2 : 모달 안에서 모달 열기
</button>
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay(
'test',
<div className="bg-white">
test
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay('test', <div className="bg-blue-200">test2</div>, { position: 'S' });
}}
type="button"
>
다음 모달 열기
</button>
</div>,
);
}}
type="button"
>
예시 3 : 모달 교체하기
</button>
<button
className="border border-gray-500 px-2 py-1"
onClick={() => {
openOverlay('test', <div className="bg-blue-200">test2</div>, { disposable: false });
}}
type="button"
>
예시 4 : 모달 안닫히게 하기
</button>
</main>
);
}

export default DemoPage;
9 changes: 7 additions & 2 deletions apps/shop-admin/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './globals.css';
import React from 'react';

import { ThemeProvider } from '@emotion/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import theme from '@review-canvas/admin-ui/theme';

Expand All @@ -14,6 +15,8 @@ import GlobalStyles from '@/components/global-styles.tsx';
import { notoSansKR, roboto } from '@/theme/font.ts';

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
const queryClient = new QueryClient();

return (
<html
className={`${roboto.className} ${notoSansKR.className}`}
Expand All @@ -22,8 +25,10 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
<body>
<ThemeProvider theme={theme}>
<GlobalStyles />
{children}
<GlobalOverlay />
<QueryClientProvider client={queryClient}>
{children}
<GlobalOverlay />
</QueryClientProvider>
</ThemeProvider>
</body>
</html>
Expand Down
152 changes: 152 additions & 0 deletions apps/shop-admin/src/components/dashboard/review-detail-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useQueryClient } from '@tanstack/react-query';
import { SolidButton } from '@ui/components';
import dayjs from 'dayjs';
import tw, { styled } from 'twin.macro';

import { ReviewService } from '@/service/review';
import type { ReviewDataType } from '@/types/review';

export interface ReviewDetailModalProps extends ReviewDataType {
onClose: () => void;
}

function ReviewDetailModal(props: ReviewDetailModalProps) {
const queryClient = useQueryClient();

const handlePressDeleteButton = async () => {
try {
const isSuccess = await ReviewService.deleteReview(props.reviewId);
if (isSuccess) {
// eslint-disable-next-line no-alert -- required
alert('리뷰가 삭제되었습니다.');
await queryClient.invalidateQueries({
queryKey: ['review-list'],
});

props.onClose();
} else {
throw new Error('Fail With Business Logic Error');
}
} catch (error) {
// eslint-disable-next-line no-alert -- required
alert('일시적으로 문제가 발생했습니다. 잠시 후 다시 시도해 주세요.');
// eslint-disable-next-line no-console -- track
console.error(error);
}
};

return (
<div tw="flex flex-col gap-8 w-[80vw] max-w-[800px] max-h-[90vh] p-10 bg-white rounded-md overflow-y-auto">
<div tw="flex flex-col gap-1">
<Title>리뷰 상세 정보</Title>
<Caption>리뷰와 관련해 자세하게 확인하고 수정 혹은 삭제할 수 있어요</Caption>
</div>

<div tw="flex flex-col gap-2">
<Row>
<RowTitle>상품</RowTitle>
<RowContent>
<span>{props.productName}</span>
<span tw="ml-2 text-sub-primary">(상품 ID: {props.productId})</span>
</RowContent>
</Row>

<Row>
<RowTitle>리뷰 내용</RowTitle>
<RowContent>{props.content}</RowContent>
</Row>

<Row>
<RowTitle>리뷰 별점</RowTitle>
<RowContent>{props.score}</RowContent>
</Row>

<Row>
<RowTitle>리뷰 작성자</RowTitle>
<RowContent>{props.nickname}</RowContent>
</Row>

<Row>
<RowTitle>리뷰 댓글</RowTitle>
<RowContent tw="flex flex-col w-full overflow-y-auto max-h-[140px]">
{props.replies.length > 0
? props.replies.map((reply) => {
return (
<div
tw="flex flex-col py-2 border-b-[1px] border-b-sub-secondary text-sm"
key={reply.replyId}
>
<div tw="flex gap-2 py-1">
<div tw="w-[15%]">댓글 작성자</div>
<div>{reply.nickname}</div>
</div>

<div tw="flex gap-2 py-1">
<div tw="w-[15%]">댓글 내용</div>
<div>{reply.content}</div>
</div>

<div tw="flex gap-2 py-1">
<div tw="w-[15%]">댓글 작성 일시</div>
<div>{dayjs(reply.updatedAt).format('YYYY/MM/DD HH:mm:ss')}</div>
</div>

<div tw="flex gap-2 py-1">
<div tw="w-[15%]">댓글 삭제 여부</div>
<div>{reply.deleted ? 'O' : 'X'}</div>
</div>
</div>
);
})
: null}
</RowContent>
</Row>

<Row>
<RowTitle>리뷰 최초 작성 일시</RowTitle>
<RowContent>{dayjs(props.createAt).format('YYYY/MM/DD HH:mm:ss')}</RowContent>
</Row>

<Row>
<RowTitle>리뷰 최종 수정 일시</RowTitle>
<RowContent>{dayjs(props.updatedAt).format('YYYY/MM/DD HH:mm:ss')}</RowContent>
</Row>
</div>

<div tw="flex justify-end items-center">
<SolidButton
variant="primary"
size="sm"
tw="bg-red-600"
onPress={() => {
void handlePressDeleteButton();
}}
>
삭제
</SolidButton>
</div>
</div>
);
}

export default ReviewDetailModal;

const Title = styled.div`
${tw`text-xl font-normal break-keep`}
`;

const Caption = styled.div`
${tw`text-sm text-stone-400 font-medium break-keep`}
`;

const Row = styled.div`
${tw`flex gap-4 py-2 border-t-[1px] border-t-sub-primary`}
`;

const RowTitle = styled.div`
${tw`inline-flex min-w-28 text-[#838383] text-base items-center`}
`;

const RowContent = styled.div`
${tw`inline-flex`}
`;
26 changes: 26 additions & 0 deletions apps/shop-admin/src/constants/review/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const REVIEW_PERIOD_OPTIONS_MAP = [
{ value: 'ALL', label: '전체' },
{ value: 'TODAY', label: '오늘' },
{ value: 'ONE_MONTH', label: '최근 1개월' },
{ value: 'THREE_MONTH', label: '최근 3개월' },
{ value: 'SIX_MONTH', label: '최근 6개월' },
];

export const REVIEW_FILTER_OPTIONS_MAP = [
{ value: 'PHOTO', label: '포토 리뷰' },
{ value: 'TEXT', label: '텍스트 리뷰' },
{ value: 'VIDEO', label: '동영상 리뷰' },
];

export const REVIEW_SCORE_OPTIONS_MAP = [
{ value: 'ONE', label: 1 },
{ value: 'TWO', label: 2 },
{ value: 'THREE', label: 3 },
{ value: 'FOUR', label: 4 },
{ value: 'FIVE', label: 5 },
];

export const REVIEW_REPLY_FILTER_OPTIONS_MAP = [
{ value: 'REPLIED', label: '답글 등록' },
{ value: 'NOT_REPLIED', label: '답글 미등록' },
];
45 changes: 45 additions & 0 deletions apps/shop-admin/src/lib/api/api-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,51 @@ class ApiService {
const response = await this.httpClient.get<API.GetFontInfoResponse>('/api/v1/font-info');
return response.data;
}

public async getProductReviews(request: API.GetProductReviewRequest): Promise<API.GetProductReviewResponse> {
const { productId, size, page, sort, period, reviewFilters, score, replyFilters } = request;

const params = new URLSearchParams();
params.set('size', String(size));
params.set('page', String(page));
params.set('sort', String(sort));
period && params.set('period', period);
reviewFilters && params.set('reviewFilters', reviewFilters);
score && params.set('score', score);
replyFilters && params.set('replyFilters', replyFilters);

const response = await this.httpClient.get<API.GetProductReviewResponse>(
`/api/v1/products/${productId}/reviews`,
params,
);

return response.data;
}

public async getShopProducts(request: API.GetShopProductsRequest): Promise<API.GetShopProductsResponse> {
const { shopAdminId, page, size } = request;

const params = new URLSearchParams();
params.set('size', String(size));
params.set('page', String(page));

const response = await this.httpClient.get<API.GetShopProductsResponse>(
`/api/v1/shops/${shopAdminId}/products`,
params,
);

return response.data;
}

public async deleteShopAdminReview(
request: API.DeleteShopAdminReviewRequest,
): Promise<CommonResponse<API.DeleteShopAdminReviewResponse>> {
const response = await this.httpClient.delete<CommonResponse<API.DeleteShopAdminReviewResponse>>(
`/api/v1/shop-admin/reviews/${request.reviewId}`,
);

return response;
}
}

const httpClient = HttpClient.getInstance();
Expand Down
Loading

0 comments on commit 0104312

Please sign in to comment.