247 lines
7.6 KiB
TypeScript
247 lines
7.6 KiB
TypeScript
// islands/QuizConfigure.tsx
|
|
import { useEffect } from "preact/hooks";
|
|
import { KvObj, QuizConfig } from "../utils/types.ts";
|
|
import { Signal } from "@preact/signals";
|
|
import { AsyncYesNoPop } from "./AsyncYesNoPop.tsx";
|
|
import { User } from "../db/interfaces.ts";
|
|
|
|
export interface AdminProps {
|
|
allUsers: User[];
|
|
allQuizs: KvObj<QuizConfig>[];
|
|
}
|
|
|
|
export default function AdminPage(
|
|
{ allUsers, allQuizs }: AdminProps,
|
|
) {
|
|
// Theme classes are defined in styles.css with @media (prefers-color-scheme: dark)
|
|
|
|
const [_ask, popupElement] = AsyncYesNoPop();
|
|
const _changeMode = new Signal<{ mode: string } | null>(null);
|
|
const users = new Signal<User[]>(allUsers);
|
|
const _nameError = new Signal(false);
|
|
const _randomOrder = new Signal(true);
|
|
const _modeType = new Signal("photo-see-bisaya");
|
|
const userContext = new Signal((() => {
|
|
if (allUsers && allUsers.length) {
|
|
return allUsers[0].id;
|
|
} else {
|
|
return "";
|
|
}
|
|
})());
|
|
//const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const count = new Signal(0);
|
|
|
|
// const handlePop = async () => {
|
|
// const answer = await ask("Do you want to continue?");
|
|
// if (answer) {
|
|
// alert("You chose YES!");
|
|
// // ...continue logic
|
|
// } else {
|
|
// alert("You chose NO!");
|
|
// // ...alternative logic
|
|
// }
|
|
// };
|
|
|
|
const handleClick = () => {
|
|
const newCount = count.value + 1;
|
|
count.value = newCount;
|
|
};
|
|
|
|
useEffect(() => {
|
|
//console.log("allUsers",allUsers.length);
|
|
}, []);
|
|
|
|
const uploadFile = () => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = ".txt";
|
|
// Safari fix: input must be in DOM for change event to fire reliably
|
|
input.className = "hidden-file-input";
|
|
document.body.appendChild(input);
|
|
|
|
const cleanup = () => {
|
|
if (input.parentNode) {
|
|
document.body.removeChild(input);
|
|
}
|
|
};
|
|
|
|
input.addEventListener("change", async (e) => {
|
|
console.log("uploading file");
|
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
console.log("file", file);
|
|
if (file) {
|
|
const filePeaces = (file.name as string)?.split(".txt")[0]?.trim()
|
|
.split(" ");
|
|
const _newQuizName = filePeaces[0];
|
|
|
|
//console.log("context id", userContext.value);
|
|
|
|
const text = (await file.text())?.trim();
|
|
|
|
if (userContext.value && text && text.length > 0) {
|
|
const obj: KvObj<QuizConfig> = JSON.parse(text) as KvObj<QuizConfig>;
|
|
console.log(obj.value?.quiz_name);
|
|
obj.key[0] = "quiz_configs";
|
|
obj.key[1] = "" + userContext.value;
|
|
obj.key[2] = crypto.randomUUID();
|
|
if (obj.value) {
|
|
obj.value.owner_id = obj.key[1];
|
|
obj.value.quiz_config_id = obj.key[2];
|
|
|
|
if (obj.value.questions) {
|
|
obj.value.questions.forEach((question) => {
|
|
question.owner_id = obj.key[1];
|
|
question.quiz_id = obj.key[2];
|
|
});
|
|
}
|
|
// const textToQuiz = new TextToQuizTranslations(quizData);
|
|
// const parsedQuizData = textToQuiz.parseText(text);
|
|
try {
|
|
const response = await fetch("/api/quiz-up", {
|
|
method: "POST",
|
|
body: JSON.stringify(obj),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Upload failed");
|
|
}
|
|
|
|
const _data = await response.json();
|
|
} catch (err) {
|
|
console.error(err);
|
|
console.log("Failed to fetch create quiz.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cleanup();
|
|
});
|
|
|
|
// Cleanup if user cancels file dialog (focus returns to window)
|
|
const handleFocus = () => {
|
|
setTimeout(cleanup, 300);
|
|
globalThis.removeEventListener("focus", handleFocus);
|
|
};
|
|
globalThis.addEventListener("focus", handleFocus);
|
|
|
|
input.click();
|
|
};
|
|
|
|
const downloadFile = () => {
|
|
allQuizs.forEach((quiz) => {
|
|
let quizName = `${JSON.stringify(quiz.key)}.txt`;
|
|
if (quiz.value) {
|
|
quizName = `${quiz.value.quiz_name} ${quizName}.txt`;
|
|
}
|
|
const userConfirmed = globalThis.confirm(`Download? ${quizName}`);
|
|
|
|
if (userConfirmed) {
|
|
const content = JSON.stringify(quiz);
|
|
const blob = new Blob([content], { type: "text/plain" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = quizName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{popupElement}
|
|
<div className="theme-container rounded-xl shadow-2xl p-8 max-w-md w-full">
|
|
<div hidden>
|
|
<button type="button" onClick={handleClick}>Increment</button>
|
|
<p>Count: {count}</p>
|
|
</div>
|
|
<h2 className="text-2xl theme-text font-bold mb-4">Configure Quiz</h2>
|
|
<div className="w-[300px] mx-0 px-0">
|
|
<div className="flex space-x-2 pb-[10px]">
|
|
<button
|
|
type="button"
|
|
onClick={uploadFile}
|
|
className="flex-1 theme-button py-2 px-2 rounded-md transition duration-300"
|
|
>
|
|
Add from file
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={downloadFile}
|
|
className="flex-1 theme-button py-2 px-2 rounded-md transition duration-300"
|
|
>
|
|
Out to file
|
|
</button>
|
|
</div>
|
|
<div className="mb-4">
|
|
<label htmlFor="quizMode" className="block theme-text mb-2">
|
|
Admin Page
|
|
</label>
|
|
<select
|
|
id="quizMode"
|
|
value={"" + userContext.value}
|
|
onChange={(e) => {
|
|
userContext.value = e.currentTarget.value;
|
|
//handleOwnerChange(e, selectedUser);
|
|
|
|
// if (selectedUser) {
|
|
// thisQuizConfig.owner_id = selectedUser.id;
|
|
// setQuizConfig(thisQuizConfig);
|
|
// }
|
|
// setChangeMode({ mode:"session" });
|
|
// setQuizConfigStr(JSON.stringify({ theUpdate:{ mode:"session" }, quiz: thisQuizConfig, kvObj:kvObj,quizOrder:thisQuizOder }));
|
|
}}
|
|
className="w-full px-3 py-2 theme-select rounded-md"
|
|
>
|
|
{users.value.map((user: User) => (
|
|
<option
|
|
key={user.id}
|
|
className="text-black"
|
|
value={user.id}
|
|
>
|
|
{user.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
{
|
|
/* <form
|
|
hidden
|
|
action="/dashboard"
|
|
method="POST"
|
|
>
|
|
<input
|
|
type="hidden"
|
|
name="pageConfigData"
|
|
value={postDataStr}
|
|
ref={postInputValueRef}
|
|
/>
|
|
<button hidden ref={postButtonRef} type="submit">
|
|
</button>
|
|
</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>
|
|
</>
|
|
);
|
|
}
|