integratedhub/islands/TranslationList.tsx
2026-01-20 09:02:26 -06:00

408 lines
13 KiB
TypeScript

// islands/TranslationList.tsx
import { useEffect, useState } from "preact/hooks";
import { QuizToTextFile } from "../utils/QuizToTextFile.ts";
import { TextToQuizTranslations } from "../utils/TextToQuizTranslations.ts";
import { QuizConfig } from "../utils/types.ts";
//import { QuizData } from "../utils/database.ts";
import { AsyncYesNoPop } from "./AsyncYesNoPop.tsx";
export interface Translation {
id: number;
quiz_id: number;
owner_id: string;
question_text: string;
meaning_text: string;
order_num: number;
}
interface TranslationListProps {
quiz: QuizConfig;
userName: string;
}
export default function TranslationList(
{ quiz, userName }: TranslationListProps,
) {
// Theme classes are defined in styles.css with @media (prefers-color-scheme: dark)
const [ask, popupElement] = AsyncYesNoPop();
const [quizData, setQuizData] = useState<QuizConfig>(quiz);
const [quizConfigDataStr, setQuizConfigDataStr] = useState(
JSON.stringify(quiz),
);
const [quizNameStr, setQuizNameStr] = useState(quiz.quiz_name);
const [text1Str, setText1Str] = useState("");
const [text2Str, setText2Str] = useState("");
const [deleteIndexStr, setDeleteIndexStr] = useState("");
//const [fileContent, setFileContent] = useState("");
const [quiz_nameError, setquiz_nameError] = useState(false);
const [questionsError, setQuestionsError] = useState(false);
useEffect(() => {
const lockRoute = localStorage.getItem("lock-route");
if (lockRoute) {
globalThis.location.href = lockRoute;
}
//fetchQuizData();
}, []);
const justClear = async () => {
const clearQuiz = {
active_date: Date.now(),
quiz_mode: "",
owner_id: quiz.owner_id,
quiz_config_id: crypto.randomUUID(),
quiz_name: "",
questions: [],
results: [],
};
setQuizData(clearQuiz);
setQuizConfigDataStr(JSON.stringify(clearQuiz));
setQuizNameStr("");
setText1Str("");
setText2Str("");
setDeleteIndexStr("");
};
const handleSaveNewQuiz = async () => {
};
const handleUpdateTranslation = async (translation: Translation) => {
};
const handleAddTranslation = async () => {
};
const handleDeleteTranslation = async (translationId: number) => {
};
const uploadFile = () => {
setquiz_nameError(false);
const input = document.createElement("input");
input.type = "file";
input.accept = ".txt";
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const newQuizName = (file.name as string)?.split(".txt")[0]
?.trim();
if (!quizNameStr && !newQuizName) {
setquiz_nameError(true);
return;
}
if (
!quizNameStr && newQuizName && newQuizName.length > 0
) {
setQuizNameStr(newQuizName);
}
const text = (await file.text())?.trim();
if (text && text.length > 0) {
const textToQuiz = new TextToQuizTranslations(quizData);
const parsedQuizData = textToQuiz.parseText(text);
setQuizData(parsedQuizData);
setQuizConfigDataStr(JSON.stringify(parsedQuizData));
}
}
};
input.click();
};
const downloadFile = async () => {
if (quizData.questions.length === 0) {
setQuestionsError(true);
return;
}
if (!quizNameStr || quizNameStr.length === 0) {
setquiz_nameError(true);
return;
}
setQuestionsError(false);
const quizToText = new QuizToTextFile(quizData);
const content = quizToText.generateTextContent();
const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${quizNameStr}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const handleSaveAddQuiz = async (event: Event) => {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
if (deleteIndexStr) {
const userConfirmed = await ask(
"Are you sure you want to delete?",
Date.now(),
);
if (userConfirmed) {
form.submit();
}
} else {
if (!quizNameStr || quizNameStr.length === 0) {
setquiz_nameError(true);
} else {
form.submit();
}
}
};
return (
<>
{popupElement}
<div
className={`theme-container p-2 sm:p-3 md:p-4 rounded-lg shadow-md max-w-md mx-auto w-full overflow-auto`}
>
<div className="flex space-x-2 pb-[10px]">
<button
onClick={uploadFile}
className={`flex-1 theme-button py-2 px-2 rounded-md transition duration-300`}
>
Add from file
</button>
<button
onClick={downloadFile}
className={`flex-1 theme-button py-2 px-2 rounded-md transition duration-300`}
>
Out to file
</button>
<button
onClick={justClear}
className={`flex-1 theme-button rounded-md transition duration-300`}
>
Clear
</button>
</div>
<form
action="/language/lists"
method="POST"
onSubmit={handleSaveAddQuiz}
>
<input
type="hidden"
name="quizConfigData"
value={quizConfigDataStr}
/>
<input
type="hidden"
name="quizNameStrData"
value={quizNameStr}
/>
<input
type="hidden"
name="text1StrData"
value={text1Str}
/>
<input
type="hidden"
name="text2StrData"
value={text2Str}
/>
<input
type="hidden"
name="deleteIndexStrData"
value={deleteIndexStr}
/>
<div className="mb-4 sm:mb-6">
<input
type="text"
value={quizNameStr}
onChange={(e) => {
const target = (e.target as HTMLInputElement).value.trim();
if (!target) {
setQuizNameStr(quizNameStr);
} else {
setQuizNameStr(target);
}
}}
onKeyDown={() => setquiz_nameError(false)}
placeholder="Quiz Name"
className={`w-full p-2 border rounded theme-input ${
quiz_nameError ? "border-red-500" : ""
}`}
/>
</div>
<div className="mb-4 sm:mb-6">
<h3 className={`text-lg sm:text-xl font-semibold mb-2 theme-text`}>
Add New Translation
</h3>
<div className="flex flex-col space-y-2">
<input
type="text"
value={text1Str}
onChange={(e) => {
const target = (e.target as HTMLInputElement).value.trim();
setText1Str(target);
}}
placeholder="Bisaya"
onKeyDown={() => setQuestionsError(false)}
className={`flex-1 p-2 border rounded theme-input ${
questionsError ? "border-red-500" : ""
}`}
/>
<input
type="text"
value={text2Str}
onChange={(e) => {
const target = (e.target as HTMLInputElement).value.trim();
setText2Str(target);
}}
placeholder="English"
onKeyDown={() => setQuestionsError(false)}
className={`flex-1 p-2 border rounded theme-input ${
questionsError ? "border-red-500" : ""
}`}
/>
<button
type="submit"
// onClick={handleAddTranslation}
className={`theme-button px-4 py-2 rounded w-full`}
>
Add and Save
</button>
</div>
</div>
<ul className="space-y-2">
{quizData.questions.map((translation: any, index: number) => (
<li
key={translation.id}
className={`theme-secondary-container p-1 rounded relative group`}
>
<div className="flex flex-col scroll pr-2">
<div className={`w-full p-2 border rounded mb-2 theme-input`}>
{translation.text_1}
</div>
<div className={`w-full p-2 border rounded theme-input`}>
{translation.text_2}
</div>
</div>
{!(!!translation.audio_file_1 || !!translation.audio_file_2 ||
!!translation.video_file || !!translation.video_file_data ||
!!translation.background_file ||
!!translation.background_image_data)
? (
<button
type="submit"
onClick={() => {
setDeleteIndexStr(`${index}`);
}}
className="absolute top-1/2 -right-2 transform -translate-y-1/2 text-red-700 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
)
: (
<button
type="submit"
onClick={(e) => {
e.preventDefault();
}}
className="absolute top-1/2 -right-2 transform -translate-y-1/2 text-red-700 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<rect
x="5"
y="11"
width="14"
height="8"
rx="2"
stroke="currentColor"
strokeWidth={2}
fill="none"
/>
<path
d="M7 11V7a5 5 0 0110 0v4"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
<circle
cx="12"
cy="15"
r="1"
fill="currentColor"
/>
</svg>
</button>
)}
</li>
))}
</ul>
</form>
</div>
<div class="fixed bottom-4 inset-x-0 flex justify-center z-40 pointer-events-none">
<div class="flex gap-2 w-11/12 max-w-md pointer-events-auto">
<span class={`w-[200px] py-3 px-6`}></span>
<span class={`w-[16px]`}></span>
<button
type="button"
onClick={() => {
globalThis.location.href = "/language/dashboard";
}}
class={`w-[200px] theme-button font-semibold py-3 px-6 rounded-full shadow-lg text-center`}
>
Dashboard
</button>
</div>
</div>
</>
);
}
async function makeQuizWithName(
owner_id: string,
quiz_name: string,
): Promise<number> {
try {
const response = await fetch("/api/quiz", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
owner_id,
quiz_name,
}),
});
if (!response.ok) {
const errorData = await response.json();
console.error("Error creating quiz:", errorData.error);
return 0;
}
const result = await response.json();
return result.quiz_id;
} catch (error) {
console.error("Network error when creating quiz:", error);
return 0;
}
}