| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- 'use client';
- import './style.scss';
- import '../../board/_component/style.scss';
- import Link from 'next/link';
- import { useSearchParams } from 'next/navigation';
- import Image, { ImageProps } from 'next/image';
- import { useState, useEffect } from 'react';
- import { clsx } from 'clsx';
- import { BoardListMeta } from '@/types/forum/boardMeta';
- import { fetchLatestPosts } from '@/lib/api/forum/board';
- import LatestPostsRequest from '@/dtos/request/forum/board/latestPostsRequest'
- import LatestPostsResponse from '@/dtos/response/forum/board/latestPostsResponse'
- import { throwError, isNewPost, isHotPost, formatDate } from '@/lib/utils/client';
- import Loading from '@/app/component/Loading';
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
- import { faComment, faThumbsUp, faEye, faClock } from '@fortawesome/free-regular-svg-icons';
- import { BoardLayout } from '@/constants/forum';
- type Props = {
- boardListMeta: BoardListMeta;
- boardID: number;
- boardCode: string;
- postID?: number|null;
- }
- function ImageWithFallback({
- src,
- fallbackSrc = '/resources/no-image.png',
- alt,
- ...option
- }: ImageProps & { fallbackSrc?: string }) {
- const [imgSrc, setImgSrc] = useState(src);
- return (
- <Image
- {...option}
- src={imgSrc}
- alt={alt}
- onError={() => setImgSrc(fallbackSrc)}
- />
- );
- }
- export default function LatestPosts(params : Props)
- {
- const searchParams = useSearchParams();
- const [error, setError] = useState<string>('');
- const [loading, setLoading] = useState<boolean>(false);
- const [latestPosts, setLatestPosts] = useState<LatestPostsResponse>({
- list: []
- });
- const query = Object.fromEntries(searchParams.entries());
- useEffect(() => {
- if (error) {
- alert(error);
- setError('');
- }
- }, [error]);
- useEffect(() => {
- (async () => {
- setLoading(true);
- const page = searchParams.get('page');
- const perPage = searchParams.get('perPage');
- const boardPrefixID = searchParams.get('boardPrefixID');
- fetchLatestPosts({
- boardID: params.boardID,
- boardCode: params.boardCode,
- postID: params.postID,
- page: page ? Number(page) : null,
- perPage: perPage ? Number(perPage) : null,
- boardPrefixID: boardPrefixID ? Number(boardPrefixID) : null,
- sort: searchParams.get('sort'),
- search: searchParams.get('search'),
- keyword: searchParams.get('keyword'),
- } as LatestPostsRequest).then((res) => {
- throwError(res);
- setLatestPosts(res.data as LatestPostsResponse);
- }).catch((err) => {
- setError(err.message);
- }).finally(() => {
- setLoading(false);
- });
- })();
- }, []);
- const handleClick = (boardPrefixID: number|null) => {
- const querys = new URLSearchParams(searchParams.toString());
- if (boardPrefixID) {
- querys.set('prefix', String(boardPrefixID));
- }
- window.location.href = `/board/${params.boardCode}?${querys.toString()}`;
- }
- if (latestPosts.list.length <= 0) {
- return;
- }
- return (
- <>
- <div id="latestPosts">
- {loading && <Loading />}
- <article>
- {params.boardListMeta.layout == BoardLayout.Default && (
- <section className="default-list-layout" aria-label='일반 게시판'>
- <article>
- <ul>
- <li>번호</li>
- <li>제목</li>
- <li>작성자</li>
- <li>작성일</li>
- <li>조회수</li>
- <li>좋아요</li>
- </ul>
- </article>
- <article>
- {/* 일반 글 */}
- {(latestPosts.list.map((row) => {
- const query = Object.fromEntries(searchParams.entries());
- const isNew = isNewPost(params.boardListMeta.isNewIcon, row);
- const isHot = isHotPost(params.boardListMeta.isHotIcon, row);
- const createdAt = formatDate(row.createdAt);
- return (
- <section key={row.id} className={clsx({ active: row.isActive })}>
- {/* PC */}
- <ol>
- <li>
- <small>{row.no}</small>
- </li>
- <li>
- {row.boardPrefixID && (
- <button type="button" onClick={() => handleClick(row.boardPrefixID)}>
- [{row.boardPrefixName}]
- </button>
- )}
- <Link href={{
- pathname: '/post/' + row.id,
- query: {
- ...query
- }
- }}
- >
- <em>{row.subject}</em>
- {isNew && <span><img src='/resources/new.gif' alt='NEW'/></span>}
- {isHot && <span><img src='/resources/hot.gif' alt='HOT'/></span>}
- </Link>
- </li>
- <li>{row.name || row.sid}</li>
- <li>{createdAt}</li>
- <li>{row.views}</li>
- <li>{row.likes}</li>
- </ol>
- {/* Mobile */}
- <dl hidden>
- <dt>
- <Link href={{
- pathname: '/post/' + row.id,
- query: {
- ...query
- }
- }}
- >
- <em>{row.subject}</em>
- {isNew && <span><img src='/resources/new.gif' alt='NEW'/></span>}
- {isHot && <span><img src='/resources/hot.gif' alt='HOT'/></span>}
- </Link>
- </dt>
- <dd>
- <ul>
- <li>{row.name || row.sid}</li>
- <li><FontAwesomeIcon icon={faComment} /> {row.comments}</li>
- <li><FontAwesomeIcon icon={faEye} /> {row.views}</li>
- <li><FontAwesomeIcon icon={faThumbsUp} /> {row.likes}</li>
- <li>{createdAt}</li>
- </ul>
- </dd>
- </dl>
- </section>
- );
- })
- )}
- </article>
- </section>
- )}
- {/* 1:1문의 */}
- {params.boardListMeta.layout == BoardLayout.QnA && (
- <section className="qna-list-layout" aria-label='일반 게시판'>
- <article>
- <ul>
- <li>번호</li>
- <li>제목</li>
- <li>작성자</li>
- <li>답변 여부</li>
- <li>작성일</li>
- </ul>
- </article>
- <article>
- {(latestPosts.list.map((row) => {
- const query = Object.fromEntries(searchParams.entries());
- const isNew = isNewPost(params.boardListMeta.isNewIcon, row);
- const createdAt = formatDate(row.createdAt);
- return (
- <section key={row.id} className={clsx({ active: row.isActive })}>
- {/* PC */}
- <ol>
- <li>
- <small>{row.no}</small>
- </li>
- <li>
- {row.boardPrefixID && (
- <button type="button" onClick={() => handleClick(row.boardPrefixID)}>
- [{row.boardPrefixName}]
- </button>
- )}
- <Link href={{
- pathname: '/post/' + row.id,
- query: {
- ...query
- }
- }}
- >
- <em>{row.subject} {row.comments > 0 && (<span>[{row.comments}]</span>)}</em>
- {isNew && <span><img src='/resources/new.gif' alt='NEW'/></span>}
- </Link>
- </li>
- <li>{row.name || row.sid}</li>
- <li>
- {!row.isReply ? (
- <span className='inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-800 ring-1 ring-yellow-600/20 ring-inset'>
- 대기
- </span>
- ) : (
- <span className='inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset'>
- 완료
- </span>
- )}
- </li>
- <li>{createdAt}</li>
- </ol>
- {/* Mobile */}
- <dl hidden>
- <dt>
- <Link href={{
- pathname: '/post/' + row.id,
- query: {
- ...query
- }
- }}
- >
- <em>{row.subject}</em>
- {isNew && <span><img src='/resources/new.gif' alt='NEW'/></span>}
- </Link>
- </dt>
- <dd>
- <ul>
- <li>{row.name || row.sid}</li>
- <li><FontAwesomeIcon icon={faComment} /> {row.comments}</li>
- <li>
- {!row.isReply ? (
- <span className='inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-800 ring-1 ring-yellow-600/20 ring-inset'>
- 대기
- </span>
- ) : (
- <span className='inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset'>
- 완료
- </span>
- )}
- </li>
- <li>{createdAt}</li>
- </ul>
- </dd>
- </dl>
- </section>
- );
- })
- )}
- </article>
- </section>
- )}
- {/* 사진/동영상 글 */}
- {params.boardListMeta.layout == BoardLayout.Media && (
- <section aria-label='사진/동영상 게시판'>
- <article>
- <div className="album-list-layout">
- {(latestPosts.list.map((row) => {
- const query = Object.fromEntries(searchParams.entries());
- const isNew = isNewPost(params.boardListMeta.isNewIcon, row);
- const isHot = isHotPost(params.boardListMeta.isHotIcon, row);
- const createdAt = formatDate(row.createdAt);
- // 사진/동영상 게시판
- const href = `/post/${row.id}${window.location.search}`;
- return (
- <div key={row.id} className={clsx({ active: row.isActive })}>
- <figure>
- <article>
- <Link href={href}>
- {row.thumbnail ? (
- <ImageWithFallback src={row.thumbnail} alt={row.subject} fill style={{ objectFit: 'cover' }} loading="lazy" />
- ) : (
- <Image src='/resources/no-image.png' alt={row.subject} loading='lazy' fill />
- )}
- </Link>
- </article>
- <dl>
- <dt>
- {row.boardPrefixID && (
- <button type="button" onClick={() => handleClick(row.boardPrefixID)}>
- [{row.boardPrefixName}]
- </button>
- )}
- <Link href={{
- pathname: '/post/' + row.id,
- query: {
- ...query
- }
- }}
- >
- <em>{row.subject} {row.comments > 0 && (<span>[{row.comments}]</span>)}</em>
- {isNew && <span><img src='/resources/new.gif' alt='NEW'/></span>}
- {isHot && <span><img src='/resources/hot.gif' alt='HOT'/></span>}
- </Link>
- </dt>
- <dd>{row.name}</dd>
- </dl>
- <figcaption>
- <ul>
- <li><FontAwesomeIcon icon={faThumbsUp} /> {row.likes}</li>
- <li><FontAwesomeIcon icon={faEye} /> {row.views}</li>
- <li><FontAwesomeIcon icon={faClock} /> {createdAt}</li>
- </ul>
- </figcaption>
- </figure>
- </div>
- );
- })
- )}
- </div>
- </article>
- </section>
- )}
- </article>
- <article>
- <Link href={{
- pathname: '/board/' + params.boardCode,
- query: {
- ...query
- }
- }} className='btn btn-default'>목록으로</Link>
- </article>
- </div>
- </>
- );
- }
|