저장소가 총 건수를 상한까지만 세게 되면서 마지막 페이지 번호가 없는 목록이 생겼는데, 화면은 그 값을 1 로 접거나 페이저 노출 조건으로만 쓰고 있었다. 앞은 "1페이지뿐" 으로 잘못 말하고 뒤는 페이저를 통째로 지워 뒤쪽 페이지로 갈 방법 자체를 없앤다. 둘 다 예외도 404 도 내지 않아 화면은 정상으로 보이고 기능만 없다. Pagination 컴포넌트가 세 형태를 모두 이해하도록 넓혔다. 마지막 페이지를 알면 페이지 산술이 우선이고, 모를 때만 다음 페이지 존재 여부로 판정한다. 처음·마지막 버튼을 따로 켜고 끌 수 있게 나눠, 마지막 페이지를 모르는 목록에서는 그 버튼만 감춰지고 나머지 이동은 그대로 열려 있다. DataGrid 는 페이저를 그리는 두 경로 모두에 그 값을 전달한다 — 한쪽만 전달하면 그 화면만 첫 페이지에 갇힌다. 마이페이지 주문·마일리지·위시리스트·알림함, 게시판 목록, 상품 리뷰·문의, 내가 쓴 글, 관리자 설정 탭과 스케줄 이력의 소비 지점을 전건 옮겼다. 검색 화면은 총 건수를 정확히 세지 못한 경우 "N건 이상" 으로 표기하고 검색어를 좁히면 정확한 건수를 볼 수 있다고 안내한다. 탭 배지도 세지 못한 값을 정확한 숫자처럼 보여주지 않는다. 최신순은 첫 페이지부터 커서로 이동하며, 주소로 특정 페이지를 지목한 딥링크는 종전 동작을 유지한다. 쇼핑 첫 화면과 분류 화면을 단일 요청으로 재배선하고, 섹션 partial 이 부모가 선언한 이름을 읽도록 통일했다. 데이터소스 이름에 직결돼 있으면 다른 화면이 재사용할 때 폴백에 걸려 예외도 로그도 없이 섹션이 사라진다. 상품 상세의 "최근 본 상품" 이 계속 비어 있던 것은 존재하지 않는 라우트 파라미터를 읽고 있어서였다. 게시판 목록 세 스킨의 블라인드 아이콘 대비와 이미지 위 공통 색의 다크 모드 표기를 정리했다.
503 lines
21 KiB
JSON
503 lines
21 KiB
JSON
{
|
|
"version": "1.0.0",
|
|
"layout_name": "users/posts",
|
|
"extends": "_user_base",
|
|
"permissions": [],
|
|
"meta": {
|
|
"seo": {
|
|
"enabled": true,
|
|
"data_sources": ["userProfile", "userPosts"],
|
|
"priority": 0.3,
|
|
"changefreq": "weekly",
|
|
"og": {
|
|
"type": "profile"
|
|
}
|
|
}
|
|
},
|
|
"data_sources": [
|
|
{
|
|
"id": "userProfile",
|
|
"label_key": "$t:editor.data_source.userProfile",
|
|
"type": "api",
|
|
"endpoint": "/api/users/{{route.userId}}/profile",
|
|
"method": "GET",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"refetchOnMount": true,
|
|
"loading_strategy": "progressive",
|
|
"errorHandling": {
|
|
"404": {
|
|
"handler": "showErrorPage",
|
|
"params": {
|
|
"target": "content"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "userPosts",
|
|
"label_key": "$t:editor.data_source.userPosts",
|
|
"type": "api",
|
|
"endpoint": "/api/modules/sirsoft-board/users/{{route.userId}}/posts",
|
|
"method": "GET",
|
|
"auto_fetch": true,
|
|
"auth_required": false,
|
|
"loading_strategy": "progressive",
|
|
"params": {
|
|
"page": "{{query.page || 1}}",
|
|
"per_page": 20
|
|
}
|
|
}
|
|
],
|
|
"slots": {
|
|
"content": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "min-h-screen bg-gray-50 dark:bg-gray-900"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "layout",
|
|
"name": "Container",
|
|
"props": {
|
|
"className": "py-8"
|
|
},
|
|
"responsive": {
|
|
"portable": {
|
|
"props": {
|
|
"className": "py-4"
|
|
}
|
|
}
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "로딩 상태",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{userPosts.loading && !userPosts.data}}",
|
|
"props": {
|
|
"className": "flex justify-center items-center py-20"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 dark:border-white"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "게시글 목록",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{userPosts.data && !userPosts.error}}",
|
|
"props": {
|
|
"className": "space-y-6"
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "브레드크럼 (1행)",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-2"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Button",
|
|
"props": {
|
|
"className": "hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer transition-colors"
|
|
},
|
|
"actions": [
|
|
{
|
|
"type": "click",
|
|
"handler": "navigate",
|
|
"params": {
|
|
"path": "/users/{{route.userId}}"
|
|
}
|
|
}
|
|
],
|
|
"text": "{{userProfile.data?.name ?? ''}} $t:user.profile.back_to_profile"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"text": ">"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "text-gray-900 dark:text-white font-medium"
|
|
},
|
|
"text": "$t:user.profile.posts_page_title"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "타이틀 + 총 게시글 수 (2행, 3행)",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "mb-4"
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "2행: 타이틀",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex items-center gap-2"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "file-lines",
|
|
"size": "xl",
|
|
"className": "text-gray-900 dark:text-white"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "H1",
|
|
"props": {
|
|
"className": "text-2xl font-bold text-gray-900 dark:text-white"
|
|
},
|
|
"text": "{{userProfile.data?.name ?? ''}}$t:user.profile.posts_suffix"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "3행: 총 게시글 수",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "text-sm text-gray-500 dark:text-gray-400 mt-1 block text-right"
|
|
},
|
|
"text": "$t:user.profile.total_posts|count={{userPosts.data?.total ?? 0}}"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "게시글 테이블",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "게시글 없음",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{!userPosts.data?.data?.length}}",
|
|
"props": {
|
|
"className": "text-center py-12"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "file-circle-xmark",
|
|
"size": "2xl",
|
|
"className": "text-gray-300 dark:text-gray-600 mb-4"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "P",
|
|
"props": {
|
|
"className": "text-gray-500 dark:text-gray-400"
|
|
},
|
|
"text": "$t:user.profile.no_posts"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "테이블 헤더",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{userPosts.data?.data?.length > 0}}",
|
|
"props": {
|
|
"className": "hidden md:grid grid-cols-12 gap-2 px-4 py-3 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "col-span-2"
|
|
},
|
|
"text": "$t:user.profile.post_board"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "col-span-6"
|
|
},
|
|
"text": "$t:user.profile.post_title"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "col-span-1 text-center"
|
|
},
|
|
"text": "$t:user.profile.post_views"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "col-span-3 text-right"
|
|
},
|
|
"text": "$t:user.profile.post_date"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "게시글 목록 (한 줄 표시)",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{userPosts.data?.data?.length > 0}}",
|
|
"props": {
|
|
"className": "divide-y divide-gray-200 dark:divide-gray-700"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Button",
|
|
"iteration": {
|
|
"source": "{{userPosts.data?.data ?? []}}",
|
|
"item_var": "post"
|
|
},
|
|
"props": {
|
|
"className": "w-full text-left grid grid-cols-1 md:grid-cols-12 gap-2 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors cursor-pointer items-center"
|
|
},
|
|
"actions": [
|
|
{
|
|
"type": "click",
|
|
"handler": "navigate",
|
|
"params": {
|
|
"path": "/board/{{post.board_slug}}/{{post.id}}"
|
|
}
|
|
}
|
|
],
|
|
"children": [
|
|
{
|
|
"comment": "게시판명 컬럼",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "hidden md:block col-span-2 text-sm text-gray-600 dark:text-gray-400 truncate"
|
|
},
|
|
"text": "{{post.board_name}}"
|
|
},
|
|
{
|
|
"comment": "제목 영역 (배지 + 제목 + 댓글수)",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "col-span-1 md:col-span-6 flex items-center gap-2 min-w-0 overflow-hidden"
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "게시판명 배지 (모바일용)",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "md:hidden flex-shrink-0 text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded whitespace-nowrap"
|
|
},
|
|
"text": "{{post.board_name}}"
|
|
},
|
|
{
|
|
"comment": "카테고리 배지",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"if": "{{post.category}}",
|
|
"props": {
|
|
"className": "flex-shrink-0 text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded whitespace-nowrap"
|
|
},
|
|
"text": "{{post.category}}"
|
|
},
|
|
{
|
|
"comment": "비밀글 아이콘",
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"if": "{{post.is_secret}}",
|
|
"props": {
|
|
"name": "lock",
|
|
"size": "xs",
|
|
"className": "flex-shrink-0 text-yellow-500 dark:text-yellow-400"
|
|
}
|
|
},
|
|
{
|
|
"comment": "블라인드 아이콘",
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"if": "{{post.status === 'blinded'}}",
|
|
"props": {
|
|
"name": "eye-slash",
|
|
"size": "xs",
|
|
"className": "flex-shrink-0 text-orange-500 dark:text-orange-400"
|
|
}
|
|
},
|
|
{
|
|
"comment": "제목 + 댓글수 컨테이너",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex-1 min-w-0 flex items-center gap-1"
|
|
},
|
|
"children": [
|
|
{
|
|
"comment": "제목",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "truncate font-medium text-gray-900 dark:text-white"
|
|
},
|
|
"text": "{{post.title}}"
|
|
},
|
|
{
|
|
"comment": "댓글 수",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"if": "{{post.comment_count > 0}}",
|
|
"props": {
|
|
"className": "flex-shrink-0 text-xs text-blue-600 dark:text-blue-400 font-medium"
|
|
},
|
|
"text": "[{{post.comment_count}}]"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "조회수",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "hidden md:flex col-span-1 justify-center items-center gap-1 text-sm text-gray-500 dark:text-gray-400"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "eye",
|
|
"size": "xs"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"text": "{{post.view_count ?? 0}}"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "작성일",
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "hidden md:block col-span-3 text-right text-sm text-gray-500 dark:text-gray-400",
|
|
"title": "{{post.created_at ?? ''}}"
|
|
},
|
|
"text": "{{post.created_at_formatted ?? ''}}"
|
|
},
|
|
{
|
|
"comment": "모바일용 하단 정보",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "md:hidden flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": {
|
|
"className": "flex items-center gap-1"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "eye",
|
|
"size": "xs"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"text": "{{post.view_count ?? 0}}"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"props": { "title": "{{post.created_at ?? ''}}" },
|
|
"text": "{{post.created_at_formatted ?? ''}}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"comment": "페이지네이션 (항상 표시)",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex justify-center mt-6"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "composite",
|
|
"name": "Pagination",
|
|
"props": {
|
|
"currentPage": "{{userPosts.data?.current_page ?? 1}}",
|
|
"totalPages": "{{userPosts.data?.last_page ?? null}}",
|
|
"hasMorePages": "{{userPosts.data?.has_more_pages ?? false}}"
|
|
},
|
|
"actions": [
|
|
{
|
|
"event": "onPageChange",
|
|
"handler": "navigate",
|
|
"params": {
|
|
"path": "/users/{{route.userId}}/posts",
|
|
"mergeQuery": true,
|
|
"query": {
|
|
"page": "{{$args[0]}}"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|