'use client' import React, {ChangeEvent, Dispatch, SetStateAction, useContext, useRef, useState} from "react"; import {AlertContext, AlertContextProps} from "@/context/AlertContext"; import {apiPost, apiUpload} from "@/lib/api/client"; import {SessionContext, SessionContextProps} from "@/context/SessionContext"; import {Book, BookOpen, FileInput, FileText, Layers, Pencil, Square, SquareCheck} from 'lucide-react'; import SelectBox, {SelectBoxProps} from "@/components/form/SelectBox"; import {bookTypes} from "@/lib/constants/book"; import {chapterVersions} from "@/lib/constants/chapter"; import {ImportChapterSelection, ImportConfirmBody, ParsedChapterPreview, ParsedDocxResponse} from "@/lib/types/import"; import InputField from "@/components/form/InputField"; import TextInput from "@/components/form/TextInput"; import TextAreaInput from "@/components/form/TextAreaInput"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; import Modal from "@/components/ui/Modal"; import {useTranslations} from '@/lib/i18n'; import {LangContext, LangContextProps} from "@/context/LangContext"; import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext"; import {SyncedBook} from "@/lib/types/synced-book"; const docxAccept: string = '.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document'; export default function ImportBookForm({setCloseForm}: { setCloseForm: Dispatch> }) { const t = useTranslations(); const {lang}: LangContextProps = useContext(LangContext); const {session}: SessionContextProps = useContext(SessionContext); const {errorMessage, successMessage}: AlertContextProps = useContext(AlertContext); const {setServerSyncedBooks}: BooksSyncContextProps = useContext(BooksSyncContext); const fileInputRef: React.RefObject = useRef(null); 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 token: string = session?.accessToken ?? ''; const hasParsedFile: boolean = importId.length > 0 && chapters.length > 0; const selectedCount: number = chapters.filter((chapter: ImportChapterSelection): boolean => chapter.selected).length; async function handleFileChange(event: ChangeEvent): Promise { const file: File | undefined = event.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 apiUpload( 'book/import/parse', file, token, lang ); setImportId(response.importId); setChapters( response.chapters.map((chapter: ParsedChapterPreview): 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); } } function toggleChapter(chapterIndex: number): void { setChapters((previousChapters: ImportChapterSelection[]): ImportChapterSelection[] => previousChapters.map((chapter: ImportChapterSelection): ImportChapterSelection => chapter.index === chapterIndex ? {...chapter, selected: !chapter.selected} : chapter ) ); } function toggleAllChapters(selectAll: boolean): void { setChapters((previousChapters: ImportChapterSelection[]): ImportChapterSelection[] => previousChapters.map((chapter: ImportChapterSelection): ImportChapterSelection => ({ ...chapter, selected: selectAll, })) ); } async function handleImport(): 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); const importBody: ImportConfirmBody = { importId, title: title.trim(), subTitle: subTitle.trim(), summary: summary.trim(), type: selectedBookType, version: parseInt(selectedVersion, 10), selectedChapterIndexes, }; const importResponse: { bookId: string } = await apiPost<{ bookId: string }>( 'book/import', importBody, token, lang ); const newBook: SyncedBook = { id: importResponse.bookId, type: selectedBookType, title: title.trim(), subTitle: subTitle.trim() || null, seriesId: null, lastUpdate: new Date().getTime() / 1000, chapters: [], characters: [], locations: [], worlds: [], incidents: [], plotPoints: [], issues: [], actSummaries: [], guideLine: null, aiGuideLine: null, }; setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev, newBook]); 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); } } return ( setCloseForm(false)} size="sm" footer={hasParsedFile ? ( <> ) : undefined} > {hasParsedFile && ( <> ): void => setSelectedBookType(e.target.value)} data={bookTypes.map((types: SelectBoxProps): SelectBoxProps => ({ value: types.value, label: t(types.label) }))} defaultValue={selectedBookType} placeholder={t("addNewBookForm.typePlaceholder")} /> }/> ): void => setTitle(e.target.value)} placeholder={t("addNewBookForm.bookTitlePlaceholder")} /> }/> ): void => setSubTitle(e.target.value)} placeholder={t("addNewBookForm.subtitlePlaceholder")} /> }/> ): void => setSummary(e.target.value)} placeholder={t("addNewBookForm.summaryPlaceholder")} /> }/> ): void => setSelectedVersion(e.target.value)} data={chapterVersions.map((version: SelectBoxProps): SelectBoxProps => ({ value: version.value, label: t(version.label) }))} defaultValue={selectedVersion} placeholder={""} /> }/>

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

{t('importBook.chapters.detected', {count: chapters.length})}
{chapters.map((chapter: ImportChapterSelection) => ( ))}
)}
); }