Skip to content

Commit fb94507

Browse files
committed
refactor(page-manager): extract state management and UI components into separate modules
Moved complex state logic from PageManager.jsx into dedicated hooks directory and extracted UI components into ui directory to improve code organization and maintainability. This refactoring separates concerns by creating reusable components for headers, error states, modals, and empty states while centralizing state management logic in custom hooks.
1 parent b5e1c47 commit fb94507

6 files changed

Lines changed: 499 additions & 329 deletions

File tree

Lines changed: 36 additions & 329 deletions
Original file line numberDiff line numberDiff line change
@@ -1,312 +1,49 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2-
import { AlertCircle, Plus, RefreshCw } from 'lucide-react'
3-
import { api } from '../../api/client'
4-
import { useContent } from '../../context/ContentContext'
5-
import PageForm from './PageForm'
6-
import PostForm from './PostForm'
7-
import PostsPanel from './PostsPanel'
8-
import PageStats from './PageStats'
91
import PageList from './PageList'
2+
import PageStats from './PageStats'
3+
import PostsPanel from './PostsPanel'
4+
import usePageManagerState from './hooks/usePageManagerState'
5+
import PageManagerHeader from './ui/PageManagerHeader'
6+
import PageManagerError from './ui/PageManagerError'
7+
import PageManagerModals from './ui/PageManagerModals'
8+
import PageManagerEmptyState from './ui/PageManagerEmptyState'
109

1110
const PageManager = () => {
12-
const { navigation, pages: publishedPages } = useContent()
13-
const [pages, setPages] = useState([])
14-
const [loading, setLoading] = useState(true)
15-
const [error, setError] = useState(null)
16-
const [selectedPageId, setSelectedPageId] = useState(null)
17-
const [pageFormMode, setPageFormMode] = useState(null)
18-
const [pageFormData, setPageFormData] = useState(null)
19-
const [pageFormSubmitting, setPageFormSubmitting] = useState(false)
20-
const [posts, setPosts] = useState([])
21-
const [postsLoading, setPostsLoading] = useState(false)
22-
const [postsError, setPostsError] = useState(null)
23-
const postsRequestRef = useRef(0)
24-
const [postFormMode, setPostFormMode] = useState(null)
25-
const [postFormData, setPostFormData] = useState(null)
26-
const [postFormSubmitting, setPostFormSubmitting] = useState(false)
27-
const pagesAbortRef = useRef(null)
28-
const postsAbortRef = useRef(null)
29-
const isMountedRef = useRef(true)
30-
const normalizedPublishedSlugs = useMemo(() => {
31-
if (!Array.isArray(publishedPages?.publishedSlugs)) {
32-
return new Set()
33-
}
34-
return new Set(
35-
publishedPages.publishedSlugs
36-
.map((slug) => (typeof slug === 'string' ? slug.trim().toLowerCase() : ''))
37-
.filter(Boolean),
38-
)
39-
}, [publishedPages?.publishedSlugs])
40-
const publishedSlugList = useMemo(() => Array.from(normalizedPublishedSlugs).sort(), [normalizedPublishedSlugs])
41-
useEffect(() => {
42-
isMountedRef.current = true
43-
return () => {
44-
isMountedRef.current = false
45-
pagesAbortRef.current?.abort()
46-
postsAbortRef.current?.abort()
47-
}
48-
}, [])
49-
const selectedPage = useMemo(
50-
() => pages.find((item) => item.id === selectedPageId) ?? null,
51-
[pages, selectedPageId],
52-
)
53-
const loadPages = useCallback(async () => {
54-
const controller = new AbortController()
55-
if (pagesAbortRef.current) {
56-
pagesAbortRef.current.abort()
57-
}
58-
pagesAbortRef.current = controller
59-
try {
60-
setLoading(true)
61-
setError(null)
62-
const data = await api.listPages({ signal: controller.signal })
63-
if (controller.signal.aborted || !isMountedRef.current) {
64-
return
65-
}
66-
const items = Array.isArray(data?.items) ? data.items : []
67-
setPages(items)
68-
if (items.length === 0) {
69-
postsRequestRef.current += 1
70-
setSelectedPageId(null)
71-
setPosts([])
72-
setPostsError(null)
73-
setPostsLoading(false)
74-
return
75-
}
76-
if (!items.find((item) => item.id === selectedPageId)) {
77-
setSelectedPageId(items[0].id)
78-
}
79-
} catch (err) {
80-
if (!controller.signal.aborted && isMountedRef.current) {
81-
setError(err)
82-
}
83-
} finally {
84-
if (pagesAbortRef.current === controller) {
85-
pagesAbortRef.current = null
86-
if (isMountedRef.current) {
87-
setLoading(false)
88-
}
89-
}
90-
}
91-
}, [selectedPageId])
92-
const refreshNavigation = useCallback(() => {
93-
navigation?.refresh?.()
94-
publishedPages?.refresh?.()
95-
}, [navigation, publishedPages])
96-
const loadPosts = useCallback(
97-
async (pageId) => {
98-
if (!pageId) {
99-
postsRequestRef.current += 1
100-
if (postsAbortRef.current) {
101-
postsAbortRef.current.abort()
102-
postsAbortRef.current = null
103-
}
104-
setPosts([])
105-
setPostsLoading(false)
106-
setPostsError(null)
107-
return
108-
}
109-
const controller = new AbortController()
110-
if (postsAbortRef.current) {
111-
postsAbortRef.current.abort()
112-
}
113-
postsAbortRef.current = controller
114-
const requestId = postsRequestRef.current + 1
115-
postsRequestRef.current = requestId
116-
setPostsLoading(true)
117-
setPostsError(null)
118-
try {
119-
const data = await api.listPosts(pageId, { signal: controller.signal })
120-
if (controller.signal.aborted || postsRequestRef.current !== requestId || !isMountedRef.current) {
121-
return
122-
}
123-
const items = Array.isArray(data?.items) ? data.items : []
124-
setPosts(items)
125-
} catch (err) {
126-
if (!controller.signal.aborted && postsRequestRef.current === requestId && isMountedRef.current) {
127-
setPostsError(err)
128-
}
129-
} finally {
130-
if (postsAbortRef.current === controller) {
131-
postsAbortRef.current = null
132-
}
133-
if (!controller.signal.aborted && postsRequestRef.current === requestId && isMountedRef.current) {
134-
setPostsLoading(false)
135-
}
136-
}
137-
},
138-
[],
139-
)
140-
useEffect(() => {
141-
loadPages()
142-
}, [loadPages])
143-
useEffect(() => {
144-
if (selectedPageId) {
145-
loadPosts(selectedPageId)
146-
}
147-
}, [selectedPageId, loadPosts])
148-
const handleCreatePage = () => {
149-
setPageFormMode('create')
150-
setPageFormData(null)
151-
}
152-
const handleEditPage = (page) => {
153-
setPageFormMode('edit')
154-
setPageFormData(page)
155-
}
156-
const handleDeletePage = async (page) => {
157-
const pageId = page?.id
158-
if (!pageId) {
159-
return
160-
}
161-
if (!window.confirm('Soll diese Seite wirklich gelöscht werden? Alle zugehörigen Beiträge werden ebenfalls entfernt.')) {
162-
return
163-
}
164-
try {
165-
const pageSlug = page?.slug
166-
await api.deletePage(pageId)
167-
if (pageSlug) {
168-
publishedPages?.invalidate?.(pageSlug)
169-
} else {
170-
publishedPages?.invalidate?.()
171-
}
172-
await loadPages()
173-
refreshNavigation()
174-
} catch (err) {
175-
alert(err?.message || 'Seite konnte nicht gelöscht werden')
176-
}
177-
}
178-
const submitPageForm = async (payload) => {
179-
try {
180-
setPageFormSubmitting(true)
181-
let response
182-
if (pageFormMode === 'edit' && pageFormData?.id) {
183-
response = await api.updatePage(pageFormData.id, payload)
184-
} else {
185-
response = await api.createPage(payload)
186-
}
187-
const previousSlug = pageFormMode === 'edit' ? pageFormData?.slug : null
188-
const nextSlug = response?.slug ?? payload?.slug ?? previousSlug
189-
if (previousSlug && previousSlug !== nextSlug) {
190-
publishedPages?.invalidate?.(previousSlug)
191-
}
192-
if (nextSlug) {
193-
publishedPages?.invalidate?.(nextSlug)
194-
}
195-
setPageFormMode(null)
196-
setPageFormData(null)
197-
await loadPages()
198-
refreshNavigation()
199-
publishedPages?.refresh?.()
200-
} finally {
201-
setPageFormSubmitting(false)
202-
}
203-
}
204-
const handleCreatePost = () => {
205-
if (!selectedPage) return
206-
setPostFormMode('create')
207-
setPostFormData(null)
208-
}
209-
const handleEditPost = (post) => {
210-
setPostFormMode('edit')
211-
setPostFormData(post)
212-
}
213-
const handleDeletePost = async (post) => {
214-
if (!window.confirm('Soll dieser Beitrag wirklich gelöscht werden?')) {
215-
return
216-
}
217-
try {
218-
await api.deletePost(post.id)
219-
if (selectedPage?.slug) {
220-
publishedPages?.invalidate?.(selectedPage.slug)
221-
} else {
222-
publishedPages?.invalidate?.()
223-
}
224-
await loadPosts(selectedPageId)
225-
} catch (err) {
226-
alert(err?.message || 'Beitrag konnte nicht gelöscht werden')
227-
}
228-
}
229-
const submitPostForm = async (payload) => {
230-
if (!selectedPageId) {
231-
return
232-
}
233-
try {
234-
setPostFormSubmitting(true)
235-
if (postFormMode === 'edit' && postFormData?.id) {
236-
await api.updatePost(postFormData.id, payload)
237-
} else {
238-
await api.createPost(selectedPageId, payload)
239-
}
240-
if (selectedPage?.slug) {
241-
publishedPages?.invalidate?.(selectedPage.slug)
242-
} else {
243-
publishedPages?.invalidate?.()
244-
}
245-
setPostFormMode(null)
246-
setPostFormData(null)
247-
await loadPosts(selectedPageId)
248-
refreshNavigation()
249-
} finally {
250-
setPostFormSubmitting(false)
251-
}
252-
}
253-
const closePageForm = () => {
254-
setPageFormMode(null)
255-
setPageFormData(null)
256-
}
257-
const closePostForm = () => {
258-
setPostFormMode(null)
259-
setPostFormData(null)
260-
}
11+
const {
12+
navigation,
13+
publishedSlugList,
14+
pages,
15+
loading,
16+
error,
17+
selectedPage,
18+
selectedPageId,
19+
setSelectedPageId,
20+
posts,
21+
postsLoading,
22+
postsError,
23+
pageForm,
24+
postForm,
25+
pagesActions,
26+
postsActions,
27+
} = usePageManagerState()
28+
26129
return (
26230
<div className="space-y-8">
263-
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
264-
<div>
265-
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Seiten & Beiträge</h2>
266-
<p className="text-sm text-gray-600 dark:text-gray-300">
267-
Verwalte dynamische Seiten, Navigationseinträge und veröffentlichte Beiträge.
268-
</p>
269-
</div>
270-
<div className="flex flex-wrap items-center gap-3">
271-
<button
272-
onClick={loadPages}
273-
className="inline-flex items-center gap-2 rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-600 hover:bg-gray-50 disabled:opacity-60 dark:border-gray-700 dark:text-gray-200 dark:hover:bg-gray-800"
274-
disabled={loading}
275-
>
276-
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
277-
Seiten aktualisieren
278-
</button>
279-
<button
280-
onClick={handleCreatePage}
281-
className="inline-flex items-center gap-2 rounded-lg bg-gradient-to-r from-primary-600 to-primary-700 px-5 py-2.5 text-sm font-semibold text-white shadow-lg hover:from-primary-700 hover:to-primary-800"
282-
>
283-
<Plus className="h-4 w-4" /> Neue Seite
284-
</button>
285-
</div>
286-
</div>
31+
<PageManagerHeader onRefresh={pagesActions.refresh} refreshing={loading} onCreate={pageForm.openCreate} />
28732
<PageStats
28833
navigation={navigation}
28934
publishedSlugs={publishedSlugList}
29035
pages={pages}
29136
selectedPage={selectedPage}
29237
/>
293-
{error && (
294-
<div className="flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700">
295-
<AlertCircle className="h-4 w-4" />
296-
<div>
297-
<p className="font-semibold">Seiten konnten nicht geladen werden</p>
298-
<p>{error?.message || 'Unbekannter Fehler'}</p>
299-
</div>
300-
</div>
301-
)}
38+
{error && <PageManagerError message={error?.message} />}
30239
<div className="grid gap-6 lg:grid-cols-2">
30340
<PageList
30441
pages={pages}
30542
loading={loading}
30643
selectedPageId={selectedPageId}
30744
onSelect={setSelectedPageId}
308-
onEdit={handleEditPage}
309-
onDelete={handleDeletePage}
45+
onEdit={pageForm.openEdit}
46+
onDelete={pagesActions.delete}
31047
/>
31148
<div>
31249
{selectedPage ? (
@@ -315,49 +52,19 @@ const PageManager = () => {
31552
posts={posts}
31653
loading={postsLoading}
31754
error={postsError}
318-
onCreate={handleCreatePost}
319-
onEdit={handleEditPost}
320-
onDelete={handleDeletePost}
321-
onRefresh={() => loadPosts(selectedPageId)}
55+
onCreate={postForm.openCreate}
56+
onEdit={postForm.openEdit}
57+
onDelete={postsActions.delete}
58+
onRefresh={postsActions.refresh}
32259
/>
32360
) : (
324-
<div className="rounded-2xl border border-dashed border-gray-300 bg-gray-50 p-10 text-center text-gray-500">
325-
Wähle eine Seite aus, um ihre Beiträge zu verwalten.
326-
</div>
61+
<PageManagerEmptyState />
32762
)}
32863
</div>
32964
</div>
330-
{(pageFormMode || postFormMode) && (
331-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 px-4">
332-
{pageFormMode && (
333-
<PageForm
334-
mode={pageFormMode}
335-
initialData={pageFormData}
336-
submitting={pageFormSubmitting}
337-
onSubmit={submitPageForm}
338-
onCancel={closePageForm}
339-
/>
340-
)}
341-
{postFormMode && (
342-
<PostForm
343-
mode={postFormMode}
344-
initialData={postFormData}
345-
submitting={postFormSubmitting}
346-
onSubmit={submitPostForm}
347-
onCancel={closePostForm}
348-
/>
349-
)}
350-
</div>
351-
)}
65+
<PageManagerModals pageForm={pageForm} postForm={postForm} />
35266
</div>
35367
)
35468
}
355-
export default PageManager
356-
357-
358-
359-
360-
361-
362-
36369

70+
export default PageManager

0 commit comments

Comments
 (0)