'use client' import {ChangeEvent, Dispatch, RefObject, SetStateAction, useCallback, useContext, useEffect, useRef, useState} from "react"; import {AlertContext} from "@/context/AlertContext"; import System from "@/lib/models/System"; import {SessionContext} from "@/context/SessionContext"; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import { faBook, faBookOpen, faBookmark, faFileImport, faFileWord, faLayerGroup, faSpinner, faSquare, faSquareCheck, faX } from "@fortawesome/free-solid-svg-icons"; import {SelectBoxProps} from "@/shared/interface"; import {bookTypes} from "@/lib/models/Book"; import {chapterVersions} from "@/lib/models/Chapter"; import {ParsedDocxResponse, ImportChapterSelection} from "@/lib/models/Import"; import {SyncedBook} from "@/lib/models/SyncedBook"; import InputField from "@/components/form/InputField"; import TextInput from "@/components/form/TextInput"; import TexteAreaInput from "@/components/form/TexteAreaInput"; import SelectBox from "@/components/form/SelectBox"; import CancelButton from "@/components/form/CancelButton"; import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading"; import {useTranslations} from "next-intl"; import {LangContext, LangContextProps} from "@/context/LangContext"; import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext"; const DOCX_ACCEPT: string = '.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document'; export default function ImportBookForm({setCloseForm}: { setCloseForm: Dispatch> }) { const t = useTranslations(); const {lang} = useContext(LangContext); const {session} = useContext(SessionContext); const {errorMessage, successMessage} = useContext(AlertContext); const {setServerOnlyBooks} = useContext(BooksSyncContext); const modalRef: RefObject = useRef(null); const token: string = session?.accessToken ?? ''; const [isParsing, setIsParsing] = useState(false); const [isImporting, setIsImporting] = useState(false); const [importId, setImportId] = useState(''); const [chapters, setChapters] = useState([]); const [title, setTitle] = useState(''); const [subTitle, setSubTitle] = useState(''); const [summary, setSummary] = useState(''); const [selectedBookType, setSelectedBookType] = useState('short'); const [selectedVersion, setSelectedVersion] = useState('2'); const hasParsedFile: boolean = importId.length > 0 && chapters.length > 0; const selectedCount: number = chapters.filter((chapter: ImportChapterSelection): boolean => chapter.selected).length; const canImport: boolean = !isImporting && hasParsedFile && selectedCount > 0 && title.trim().length > 0; useEffect((): () => void => { document.body.style.overflow = 'hidden'; return (): void => { document.body.style.overflow = 'auto'; }; }, []); const handleFileChange = useCallback(async (e: ChangeEvent): Promise => { const file: File | undefined = e.target.files?.[0]; if (!file) return; if (!file.name.endsWith('.docx')) { errorMessage(t('importBook.error.invalidFormat')); return; } setIsParsing(true); setImportId(''); setChapters([]); try { const response: ParsedDocxResponse = await System.authUploadFileToServer( 'book/import/parse', file, token, lang, ); setImportId(response.importId); setChapters( response.chapters.map((chapter: { index: number; title: string; wordCount: number }): ImportChapterSelection => ({ index: chapter.index, title: chapter.title, wordCount: chapter.wordCount, selected: true, })), ); } catch (parseError: unknown) { if (parseError instanceof Error) { errorMessage(parseError.message); } else { errorMessage(t('importBook.error.parseFailed')); } } finally { setIsParsing(false); } }, [token, lang, errorMessage, t]); const toggleChapter = useCallback((chapterIndex: number): void => { setChapters((previousChapters: ImportChapterSelection[]): ImportChapterSelection[] => previousChapters.map((chapter: ImportChapterSelection): ImportChapterSelection => chapter.index === chapterIndex ? {...chapter, selected: !chapter.selected} : chapter, ), ); }, []); const toggleAllChapters = useCallback((selectAll: boolean): void => { setChapters((previousChapters: ImportChapterSelection[]): ImportChapterSelection[] => previousChapters.map((chapter: ImportChapterSelection): ImportChapterSelection => ({ ...chapter, selected: selectAll, })), ); }, []); const handleImport = useCallback(async (): Promise => { if (!title.trim()) { errorMessage(t('importBook.error.titleRequired')); return; } if (!selectedBookType) { errorMessage(t('importBook.error.typeRequired')); return; } if (selectedCount === 0) { errorMessage(t('importBook.error.noChaptersSelected')); return; } setIsImporting(true); try { const selectedChapterIndexes: number[] = chapters .filter((chapter: ImportChapterSelection): boolean => chapter.selected) .map((chapter: ImportChapterSelection): number => chapter.index); await System.authPostToServer<{ bookId: string }>( 'book/import', { importId, title: title.trim(), subTitle: subTitle.trim(), summary: summary.trim(), type: selectedBookType, version: parseInt(selectedVersion, 10), selectedChapterIndexes, }, token, lang, ); setServerOnlyBooks((prevBooks: SyncedBook[]): SyncedBook[] => [...prevBooks, { id: importId, type: selectedBookType, title: title.trim(), subTitle: subTitle.trim(), lastUpdate: new Date().getTime() / 1000, chapters: [], characters: [], locations: [], worlds: [], incidents: [], plotPoints: [], issues: [], actSummaries: [], guideLine: null, aiGuideLine: null, bookTools: null, seriesId: null, spells: [], spellTags: [] }]); successMessage(t('importBook.success')); setCloseForm(false); } catch (importError: unknown) { if (importError instanceof Error) { errorMessage(importError.message); } else { errorMessage(t('importBook.error.importFailed')); } } finally { setIsImporting(false); } }, [title, subTitle, summary, selectedBookType, selectedVersion, importId, chapters, selectedCount, token, lang, errorMessage, successMessage, t, setCloseForm, setServerOnlyBooks]); return (

{t("importBook.header.title")}

{hasParsedFile && ( <> ): void => setSelectedBookType(e.target.value)} data={bookTypes.map((types: SelectBoxProps): SelectBoxProps => ({ value: types.value, label: t(types.label) }))} defaultValue={selectedBookType} /> }/> ): void => setTitle(e.target.value)} placeholder={t("importBook.fields.title.placeholder")} /> }/> ): void => setSubTitle(e.target.value)} placeholder={t("importBook.fields.subTitle.placeholder")} /> }/> ): void => setSummary(e.target.value)} placeholder={t("importBook.fields.summary.placeholder")} /> }/> ): void => setSelectedVersion(e.target.value)} data={chapterVersions.map((version: SelectBoxProps): SelectBoxProps => ({ value: version.value, label: t(version.label) }))} defaultValue={selectedVersion} /> }/>

{t('importBook.chapters.title')}

{t('importBook.chaptersDetected', {count: chapters.length})}
{chapters.map((chapter: ImportChapterSelection) => ( ))}
)}
{hasParsedFile && (
setCloseForm(false)}/>
)}
); }