Files
ERitors-Scribe-Desktop/components/book/settings/story/Issue.tsx
natreex 64ed90d993 Remove unused components and models for improved maintainability
- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files.
- Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter.
- Updated project structure and references to reflect these removals.
2026-03-22 22:37:31 -04:00

162 lines
6.6 KiB
TypeScript

import React, {ChangeEvent, useContext, useState} from 'react';
import {AlertTriangle, Plus, Trash2} from 'lucide-react';
import IconButton from "@/components/ui/IconButton";
import {Issue} from '@/lib/types/book';
import {apiDelete, apiPost} from '@/lib/api/client';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
import Collapse from "@/components/ui/Collapse";
import TextInput from "@/components/form/TextInput";
import InputField from "@/components/form/InputField";
import {useTranslations} from '@/lib/i18n';
import {LangContext, LangContextProps} from "@/context/LangContext";
interface IssuesProps {
issues: Issue[];
setIssues: React.Dispatch<React.SetStateAction<Issue[]>>;
}
export default function Issues({issues, setIssues}: IssuesProps) {
const t = useTranslations();
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const bookId: string | undefined = book?.bookId;
const token: string = session.accessToken;
const [newIssueName, setNewIssueName] = useState<string>('');
async function addNewIssue(): Promise<void> {
if (newIssueName.trim() === '') {
errorMessage(t("issues.errorEmptyName"));
return;
}
try {
let issueId: string;
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
issueId = await tauri.addIssue(bookId ?? '', newIssueName);
} else {
issueId = await apiPost<string>('book/issue/add', {
bookId,
name: newIssueName,
}, token, lang);
}
if (!issueId) {
errorMessage(t("issues.errorAdd"));
return;
}
const newIssue: Issue = {
name: newIssueName,
id: issueId,
};
setIssues([...issues, newIssue]);
setNewIssueName('');
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t("issues.errorUnknownAdd"));
}
}
}
async function deleteIssue(issueId: string): Promise<void> {
if (issueId === undefined) {
errorMessage(t("issues.errorInvalidId"));
}
try {
let response: boolean;
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.removeIssue(bookId ?? '', issueId, Date.now());
} else {
response = await apiDelete<boolean>(
'book/issue/remove',
{
bookId,
issueId,
},
token,
lang
);
}
if (response) {
const updatedIssues: Issue[] = issues.filter((issue: Issue): boolean => issue.id !== issueId,);
setIssues(updatedIssues);
} else {
errorMessage(t("issues.errorDelete"));
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t("issues.errorUnknownDelete"));
}
}
}
function updateIssueName(issueId: string, name: string): void {
const updatedIssues: Issue[] = issues.map((issue: Issue): Issue => {
if (issue.id === issueId) {
return {...issue, name};
}
return issue;
});
setIssues(updatedIssues);
}
return (
<Collapse variant="card" title={t("issues.title")} icon={AlertTriangle}>
<div className="space-y-2">
{issues && issues.length > 0 ? (
issues.map(function (item: Issue): React.JSX.Element {
return (
<div key={`issue-${item.id}`} className="flex items-center gap-2">
<div className="flex-1">
<TextInput
value={item.name}
setValue={function (e: ChangeEvent<HTMLInputElement>): void {
updateIssueName(item.id, e.target.value);
}}
placeholder={t("issues.issueNamePlaceholder")}
/>
</div>
<IconButton icon={Trash2} variant="danger" size="sm"
onClick={function (): Promise<void> {
return deleteIssue(item.id);
}}/>
</div>
);
})
) : (
<p className="text-text-secondary text-center py-2 text-sm">
{t("issues.noIssue")}
</p>
)}
<InputField
input={
<TextInput
value={newIssueName}
setValue={function (e: ChangeEvent<HTMLInputElement>): void {
setNewIssueName(e.target.value);
}}
placeholder={t("issues.newIssuePlaceholder")}
/>
}
actionIcon={Plus}
addButtonCallBack={addNewIssue}
isAddButtonDisabled={newIssueName.trim() === ''}
/>
</div>
</Collapse>
);
}