Skip to content

Commit

Permalink
[Feat] 결과 수정 - 상세 페이지 api 연동 (#112)
Browse files Browse the repository at this point in the history
* feat: 게시물 조회 api 연동

* feat(apps/web): sidebar에 dnd 기능 적용

* fix(packages/ui): IconButton disabled 스타일 임시로 추가

* feat(apps/web): 게시글 위아래 이동 기능 추가

* feat(apps/web): 게시글 관련 api 추가

* feat(apps/web): api 연결

* fix: 빌드 에러

* feat: 페이지 합치기

* feat(apps/web): EditSideBar에서 스켈레톤 뜨도록

* fix(packages/ui): Accordion 제어 방식 지원하도록 수정, EditSidebar Empty view 추가

* feat: 이전 기록 페이지 api
  • Loading branch information
kongnayeon authored Feb 8, 2025
1 parent e0ba717 commit ab88781
Show file tree
Hide file tree
Showing 47 changed files with 1,411 additions and 338 deletions.
3 changes: 3 additions & 0 deletions apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const nextConfig = {
});
return config;
},
images: {
domains: ['instead-dev.s3.ap-northeast-2.amazonaws.com'],
},
};

export default withVanillaExtract(nextConfig);
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@repo/theme": "workspace:^",
"@repo/ui": "workspace:^",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"@vanilla-extract/css": "^1.17.0",
"cookies-next": "^5.1.0",
"emoji-picker-react": "^4.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 0,
},
{
id: 2,
Expand All @@ -28,6 +29,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 1,
},
{
id: 3,
Expand All @@ -38,6 +40,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 2,
},
{
id: 4,
Expand All @@ -48,6 +51,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 3,
},
{
id: 5,
Expand All @@ -58,5 +62,6 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 4,
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { style } from '@vanilla-extract/css';

export const editDetailPage = style({
display: 'flex',
height: '100vh',
flexShrink: 0,
});

export const flexColumn = style({
display: 'flex',
width: '100%',
justifyContent: 'center',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import { createContext, Dispatch, SetStateAction } from 'react';
import { EditPost } from './_components/EditPost/EditPost';
import { EditSidebar } from './_components/EditSidebar/EditSidebar';
import { editDetailPage, flexColumn } from './EditDetail.css';
import { useState } from 'react';
import { Post } from '@web/types';

// TODO 추후 Jotai, 또는 react-query 사용으로 수정할 예정
interface DetailPageContextType {
loadingPosts: Post['id'][];
setLoadingPosts: Dispatch<SetStateAction<Post['id'][]>>;
}

const defaultContextValue: DetailPageContextType = {
loadingPosts: [],
setLoadingPosts: () => {},
};

export const DetailPageContext =
createContext<DetailPageContextType>(defaultContextValue);

export function EditDetail() {
const [loadingPosts, setLoadingPosts] = useState<Post['id'][] | []>([]);

return (
<DetailPageContext.Provider value={{ loadingPosts, setLoadingPosts }}>
<div className={editDetailPage}>
<EditSidebar />
<div className={flexColumn}>
<EditPost />
</div>
</div>
</DetailPageContext.Provider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const contentItemStyle = style({
height: '6.4rem',
width: '100%',
padding: '1.6rem 0.8rem 1.2rem 1.2rem',
cursor: 'pointer',
});

export const titleStyle = style({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
contentItemStyle,
cursorGrabStyle,
cursorPointerStyle,
iconHoverStyle,
noShrinkStyle,
timeStyle,
Expand All @@ -10,15 +9,18 @@ import {
import { Icon } from '@repo/ui/Icon';
import { IconButton } from '@repo/ui/IconButton';
import { Text } from '@repo/ui/Text';
import { PostImage } from '@web/types';
import { getTimeAgo } from '@web/utils';

export type ContentItemProps = {
image?: string;
image?: PostImage[];
title?: string;
updatedAt: string;
onClick: () => void;
onRemove: () => void;
onModify: () => void;
onDrag: () => void;
isSelected?: boolean;
};

/**
Expand All @@ -31,10 +33,16 @@ export type ContentItemProps = {
* @property {() => void} onModify - 수정 아이콘 클릭 시 호출되는 콜백 함수.
* @property {() => void} onDrag - 드래그 아이콘 마우스 다운 시 호출되는 콜백 함수.
*/
export function ContentItem({ image, title, updatedAt }: ContentItemProps) {
export function ContentItem({
image,
title,
updatedAt,
onClick,
isSelected = false,
}: ContentItemProps) {
return (
<div className={contentItemStyle}>
{image ? (
<div className={contentItemStyle} onClick={onClick}>
{image?.[0] ? (
<div>x</div>
) : (
<Icon
Expand All @@ -49,7 +57,7 @@ export function ContentItem({ image, title, updatedAt }: ContentItemProps) {
className={titleStyle}
fontSize={18}
fontWeight="semibold"
color="grey600"
color={isSelected ? 'purple700' : 'grey600'}
>
{title}
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@repo/theme';

export const container = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%',
height: 'fit-content',
borderRadius: vars.borderRadius[24],
border: `1px solid ${vars.colors.grey50}`,
background: vars.colors.grey,
padding: vars.space[40],
});

export const description = style({
textAlign: 'center',
whiteSpace: 'pre-wrap',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as styles from './DragGuide.css';
import { Text } from '@repo/ui';

type DragGuideProps = {
description: string;
};

export function DragGuide({ description }: DragGuideProps) {
return (
<div className={styles.container}>
<Text.H2
className={styles.description}
fontSize={20}
fontWeight="semibold"
color="grey500"
>
{description}
</Text.H2>
<Text.H2
className={styles.description}
fontSize={20}
fontWeight="semibold"
color="grey500"
>
끌어서 여기에 놓아주세요
</Text.H2>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,51 @@ import { Badge } from '@repo/ui/Badge';
import { PostEditor } from '../PostEditor/PostEditor';
import { EditPromptField } from '../EditPromptField/EditPromptField';
import { FormProvider, useForm } from 'react-hook-form';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useGroupPostsQuery } from '@web/store/query/useGroupPostsQuery';
import { useAdjacentPosts } from '../../_hooks/useAdjacentPosts';

export function EditPost() {
const router = useRouter();
const methods = useForm();
const { agentId, postGroupId } = useParams();
const searchParams = useSearchParams();
const postId = searchParams.get('post');
const { data } = useGroupPostsQuery(Number(agentId), Number(postGroupId));
const post = data?.data?.posts.find((post) => post.id === Number(postId));
const { routePreviousPost, routeNextPost, canMoveUp, canMoveDown } =
useAdjacentPosts(data?.data?.posts, post);

return (
<div className={wrapper}>
<div className={controlBar}>
<div>
<IconButton icon="arrowLineTop" />
<IconButton icon="arrowLineBottom" />
<IconButton
icon="arrowLineTop"
disabled={!canMoveUp}
onClick={routePreviousPost}
/>
<IconButton
icon="arrowLineBottom"
disabled={!canMoveDown}
onClick={routeNextPost}
/>
</div>

<div>
<IconButton icon="dots" />
<IconButton icon="x" iconType="stroke" />
<IconButton
icon="x"
iconType="stroke"
onClick={() => router.push(`/edit/${agentId}/${postGroupId}`)}
/>
</div>
</div>

<div className={postWrapper}>
<div className={titleWrapper}>
<Text color="grey1000" fontSize={28} fontWeight="bold">
네이버 웨일, 본사로 이전하는 이유
{post?.summary}
</Text>
<Badge size="large" variant="neutral" shape="square">
요약
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,94 @@

import { Checkbox } from '@repo/ui/Checkbox';
import { TextField } from '@repo/ui/TextField';
import { useFormContext } from 'react-hook-form';
import { Controller, useForm, useFormContext } from 'react-hook-form';
import { wrapper } from './EditPromptField.css';
import { Spacing } from '@repo/ui/Spacing';
import { isEmptyStringOrNil } from '@web/utils';
import { usePatchPromptMutation } from '@web/store/mutation/usePatchPromptMutation';
import { useParams, useSearchParams } from 'next/navigation';
import { useContext, useEffect } from 'react';
import { DetailPageContext } from '../../EditDetail';
import { useGroupPostsQuery } from '@web/store/query/useGroupPostsQuery';

export function EditPromptField() {
const { register } = useFormContext();
const { register, watch, control, handleSubmit } = useForm<{
isEntire: boolean;
prompt: string;
}>({
defaultValues: {
isEntire: false,
prompt: '',
},
});
const { loadingPosts, setLoadingPosts } = useContext(DetailPageContext);
const isEntire = watch('isEntire');
const prompt = watch('prompt');
const isSubmitDisabled = isEmptyStringOrNil(prompt);
const { agentId, postGroupId } = useParams();
const searchParams = useSearchParams();
const postId = searchParams.get('post');
const { data } = useGroupPostsQuery(Number(agentId), Number(postGroupId));
const posts = data?.data.posts ?? [];
const editingPosts = posts
.filter((post) => post.status === 'EDITING')
.map((post) => post.id);

const { mutate: patchPrompt, isPending } = usePatchPromptMutation({
agentId: Number(agentId),
postGroupId: Number(postGroupId),
postId: Number(postId),
});

// TODO 제거 예정
useEffect(() => {
if (isPending) {
// 요청이 진행 중이면
if (isEntire) {
setLoadingPosts(editingPosts);
} else {
setLoadingPosts([Number(postId)]);
}
} else {
if (isEntire) {
setLoadingPosts((prev) =>
prev.filter((id) => !editingPosts.includes(id))
);
} else {
setLoadingPosts((prev) => prev.filter((id) => id !== Number(postId)));
}
}
}, [isPending]);

const onSubmit = async (data: { isEntire: boolean; prompt: string }) => {
patchPrompt({ ...data });
};

return (
<div className={wrapper}>
<Spacing size={8} />
<Checkbox
label="수정 중인 글을 모두 업그레이드하기"
defaultChecked
onChange={(checked) => console.log(checked)}
<Controller
name="isEntire"
control={control}
render={({ field }) => (
<Checkbox
label="수정 중인 글을 모두 업그레이드하기"
defaultChecked
checked={field.value}
onChange={field.onChange}
/>
)}
/>
<Spacing size={16} />
<TextField id="ai-field" variant="button">
<TextField.Input
sumbitButton={<TextField.Submit type="submit" />}
sumbitButton={
<TextField.Submit
type="submit"
onClick={handleSubmit(onSubmit)}
disabled={isSubmitDisabled}
/>
}
placeholder="AI에게 요청하여 글 업그레이드하기"
{...register('prompt', {
required: '메시지를 입력해주세요',
Expand Down
Loading

0 comments on commit ab88781

Please sign in to comment.