Skip to content

Commit 655b200

Browse files
authored
Merge pull request #45 from Yulikepython/feature/36-機能追加-検索ページング
#36検索・ページングの実装
2 parents 668f9fc + 57ad148 commit 655b200

2 files changed

Lines changed: 99 additions & 16 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
- **フロントエンド**: Vite + React をベースに、Markdownエディタや認証機能、UI等を提供
66
- **バックエンド**: AWS Lambda (Node.js) + API Gateway + DynamoDB のサーバーレス構成
77

8+
## デモアプリケーションイメージ
9+
### (認証不要)Markdownエディタ画面
10+
![image](https://github.com/user-attachments/assets/c41ac2b7-c24a-405d-9653-bf599715891b)
11+
12+
### (要認証)ドキュメント一覧画面
13+
![image](https://github.com/user-attachments/assets/99ea0435-ed2c-40a2-abad-9ec9094ea55a)
14+
15+
### (認証不要)公開ドキュメント閲覧画面
16+
![](https://github.com/user-attachments/assets/4d0d0eb4-7aae-45d1-97d7-93aed02a5491)
17+
818
## 主な特徴
919

1020
- **Markdown ドキュメントの作成・編集・公開**

frontend/src/pages/DocsListPage.tsx

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
// DocsListPage.tsx
2-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect, useState, useMemo } from "react";
32
import { Link } from "react-router-dom";
43
import { useApiClient } from "../services/apiClient";
5-
import { useAuthContextSwitch as useAuthContext} from "../context/useAuthContextSwitch";
4+
import { useAuthContextSwitch as useAuthContext } from "../context/useAuthContextSwitch";
65
import { AxiosError } from "axios";
7-
86
import styles from "../styles/DocsListPage.module.scss";
97

108
const extractTitle = (markdown: string): string => {
@@ -20,6 +18,14 @@ const extractTitle = (markdown: string): string => {
2018
const DocsListPage: React.FC = () => {
2119
const [documents, setDocuments] = useState<any[]>([]); //eslint-disable-line
2220
const [error, setError] = useState<string | null>(null);
21+
22+
// ★ 検索用ステートを追加
23+
const [searchTerm, setSearchTerm] = useState("");
24+
25+
// ★ ページング用ステート
26+
const [currentPage, setCurrentPage] = useState(1);
27+
const pageSize = 15; // 1ページあたりの表示件数
28+
2329
const { user, isSignedIn } = useAuthContext();
2430
const api = useApiClient();
2531

@@ -48,14 +54,42 @@ const DocsListPage: React.FC = () => {
4854
}
4955
}
5056
};
57+
5158
if (isSignedIn) {
5259
fetchDocs();
5360
}
5461
}, [api, user, isSignedIn]);
5562

56-
if (error) {
57-
return <div style={{ color: "red" }}>{error}</div>;
58-
}
63+
// ★ 1) 検索フィルタリング:searchTerm を含むドキュメントだけを抽出
64+
const filteredDocs = useMemo(() => {
65+
const lowerSearchTerm = searchTerm.toLowerCase();
66+
if (!lowerSearchTerm) return documents;
67+
68+
return documents.filter((doc) => {
69+
const title = extractTitle(doc.content).toLowerCase();
70+
const content = doc.content?.toLowerCase() || "";
71+
return (
72+
title.includes(lowerSearchTerm) || content.includes(lowerSearchTerm)
73+
);
74+
});
75+
}, [documents, searchTerm]);
76+
77+
// ★ 2) ページング
78+
const totalPages = Math.ceil(filteredDocs.length / pageSize);
79+
80+
const paginatedDocs = useMemo(() => {
81+
const startIndex = (currentPage - 1) * pageSize;
82+
const endIndex = startIndex + pageSize;
83+
return filteredDocs.slice(startIndex, endIndex);
84+
}, [filteredDocs, currentPage]);
85+
86+
// 前へ/次へボタンの挙動
87+
const goToPrevious = () => {
88+
setCurrentPage((prev) => Math.max(prev - 1, 1));
89+
};
90+
const goToNext = () => {
91+
setCurrentPage((prev) => Math.min(prev + 1, totalPages));
92+
};
5993

6094
// 「共有」ボタン押下時の挙動例
6195
const handleShare = (slug: string) => {
@@ -64,12 +98,15 @@ const DocsListPage: React.FC = () => {
6498
alert("公開URLをクリップボードにコピーしました!\n" + publicUrl);
6599
};
66100

101+
if (error) {
102+
return <div style={{ color: "red" }}>{error}</div>;
103+
}
104+
67105
return (
68-
<div style={{ width: "100%", maxWidth: "900px", margin: "10px auto", textAlign: "left" }}>
106+
<div style={{ width: "100%", maxWidth: "900px", margin: "10px auto" }}>
69107
{isSignedIn ? (
70108
<>
71109
<div style={{ margin: "16px 0", textAlign: "right" }}>
72-
{/* 新規ドキュメントは "/" に */}
73110
<Link
74111
to="/"
75112
style={{
@@ -86,6 +123,25 @@ const DocsListPage: React.FC = () => {
86123

87124
<h1>ドキュメント一覧</h1>
88125

126+
{/* ★ 検索入力欄 */}
127+
<div style={{ margin: "16px 0" }}>
128+
<input
129+
type="text"
130+
placeholder="検索キーワードを入力"
131+
value={searchTerm}
132+
onChange={(e) => {
133+
setSearchTerm(e.target.value);
134+
setCurrentPage(1); // 検索語が変わったら1ページ目に戻す
135+
}}
136+
style={{
137+
padding: "8px",
138+
borderRadius: "4px",
139+
border: "1px solid #ccc",
140+
width: "240px",
141+
}}
142+
/>
143+
</div>
144+
89145
<table className={styles.docsTable}>
90146
<thead>
91147
<tr style={{ backgroundColor: "#f0f0f0", borderBottom: "2px solid #ccc" }}>
@@ -99,12 +155,11 @@ const DocsListPage: React.FC = () => {
99155
</tr>
100156
</thead>
101157
<tbody>
102-
{documents.map((doc) => {
158+
{paginatedDocs.map((doc) => {
103159
const title = extractTitle(doc.content);
104160
const isPublic = doc.isPublic;
105161
return (
106162
<tr key={doc.slug} style={{ borderBottom: "1px solid #ddd" }}>
107-
{/* タイトル */}
108163
<td style={{ padding: "8px" }}>
109164
<Link
110165
to={`/my-docs/${doc.slug}`}
@@ -113,20 +168,19 @@ const DocsListPage: React.FC = () => {
113168
<strong>{title}</strong>
114169
</Link>
115170
</td>
116-
117-
{/* 公開/非公開 */}
118171
<td style={{ padding: "8px" }}>
119172
{isPublic ? (
120173
<span style={{ color: "green" }}>公開</span>
121174
) : (
122175
<span style={{ color: "gray" }}>非公開</span>
123176
)}
124177
</td>
125-
126-
{/* 共有ボタン(公開時のみ) */}
127178
<td style={{ padding: "8px" }}>
128179
{isPublic ? (
129-
<button className={styles.shareButton} onClick={() => handleShare(doc.slug)}>
180+
<button
181+
className={styles.shareButton}
182+
onClick={() => handleShare(doc.slug)}
183+
>
130184
共有
131185
</button>
132186
) : (
@@ -138,6 +192,25 @@ const DocsListPage: React.FC = () => {
138192
})}
139193
</tbody>
140194
</table>
195+
196+
{/* ★ ページングナビゲーション */}
197+
{filteredDocs.length > 0 && totalPages > 1 && (
198+
<div style={{ marginTop: "1rem", display: "flex", gap: "8px" }}>
199+
{/* 前へボタン: currentPage>1 のときだけ表示 */}
200+
{currentPage > 1 && (
201+
<button onClick={goToPrevious}>前へ</button>
202+
)}
203+
204+
<div style={{ lineHeight: "32px" }}>
205+
ページ {currentPage} / {totalPages}
206+
</div>
207+
208+
{/* 次へボタン: currentPage<totalPages のときだけ表示 */}
209+
{currentPage < totalPages && (
210+
<button onClick={goToNext}>次へ</button>
211+
)}
212+
</div>
213+
)}
141214
</>
142215
) : (
143216
<div>ログインしてください</div>

0 commit comments

Comments
 (0)