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'
91import 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
1110const 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