QUDO-270
This commit is contained in:
parent
0f9c070af1
commit
9dc4b64066
|
|
@ -0,0 +1,253 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { close, error, loading } from "@/config/swal";
|
||||
import {
|
||||
getKnowledgeBaseCategoryList,
|
||||
saveKnowledgeBase,
|
||||
} from "@/service/master/knowledge-base";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
|
||||
type ButtonProps = {
|
||||
title: string;
|
||||
type: "submit" | "link";
|
||||
href?: string;
|
||||
};
|
||||
|
||||
type KnowledgeBaseItem = {
|
||||
category: string;
|
||||
title: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
};
|
||||
|
||||
type CategoryItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({ title, type, href }) => {
|
||||
if (type === "submit") {
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="ml-2 rounded-lg bg-blue-600 text-white px-4 py-2 hover:bg-blue-700 transition"
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={href || "#"}>
|
||||
<button
|
||||
type="button"
|
||||
className="mr-3 rounded-lg border border-blue-600 text-blue-600 px-4 py-2 hover:bg-blue-50 transition"
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const ImportKnowledgeBase: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const [listCategory, setListCategory] = useState<CategoryItem[]>([]);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [savedArray, setSavedArray] = useState<KnowledgeBaseItem[]>([]);
|
||||
const [handleDelete, setHandleDelete] = useState(false);
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
loading();
|
||||
const response = await getKnowledgeBaseCategoryList();
|
||||
const data = response?.data?.data || [];
|
||||
setListCategory(data);
|
||||
close();
|
||||
}
|
||||
initState();
|
||||
}, []);
|
||||
|
||||
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
setFile(e.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const csvFileToArray = (content: string) => {
|
||||
const csvHeader = ["category", "title", "question", "answer"];
|
||||
const rows = content.split("\n").filter((line) => line.trim() !== "");
|
||||
|
||||
const parsedData: KnowledgeBaseItem[] = rows.map((row) => {
|
||||
const values = row.split(";").map((v) => v.trim());
|
||||
const obj = csvHeader.reduce((acc, header, index) => {
|
||||
acc[header as keyof KnowledgeBaseItem] = values[index] || "";
|
||||
return acc;
|
||||
}, {} as KnowledgeBaseItem);
|
||||
return obj;
|
||||
});
|
||||
|
||||
setSavedArray(parsedData);
|
||||
setHandleDelete(true);
|
||||
};
|
||||
|
||||
const handleOnSubmit = useCallback(
|
||||
(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const text = event.target?.result as string;
|
||||
csvFileToArray(text);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
},
|
||||
[file]
|
||||
);
|
||||
|
||||
const handleSave = async (data: KnowledgeBaseItem[]) => {
|
||||
const result = await MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: KnowledgeBaseItem[]) => {
|
||||
loading();
|
||||
for (const item of data) {
|
||||
const categoryStringId = listCategory.find(
|
||||
(c) => c.name.toLowerCase() === item.category.toLowerCase()
|
||||
)?.id;
|
||||
|
||||
const reqData = {
|
||||
title: item.title,
|
||||
categoryId: categoryStringId,
|
||||
question: item.question,
|
||||
answer: item.answer,
|
||||
};
|
||||
|
||||
const response = await saveKnowledgeBase(reqData);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
close();
|
||||
successSubmit("/supervisor/knowledge-base");
|
||||
};
|
||||
|
||||
const successSubmit = async (redirect: string) => {
|
||||
const result = await MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
router.push(redirect);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteFile = () => {
|
||||
setFile(null);
|
||||
setSavedArray([]);
|
||||
setHandleDelete(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<form onSubmit={handleOnSubmit}>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="csvFileInput" className="block font-medium mb-1">
|
||||
Input File CSV
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="csvFileInput"
|
||||
accept=".csv"
|
||||
onChange={handleOnChange}
|
||||
className="border border-gray-300 rounded px-3 py-2 w-full"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-2 rounded border border-green-600 text-green-600 px-4 py-2 hover:bg-green-50 transition"
|
||||
>
|
||||
Import Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{savedArray.length > 0 && (
|
||||
<div className="overflow-auto mb-4">
|
||||
<table className="min-w-full border text-center text-sm">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="border px-2 py-1">Kategori</th>
|
||||
<th className="border px-2 py-1">Judul</th>
|
||||
<th className="border px-2 py-1">Pertanyaan</th>
|
||||
<th className="border px-2 py-1">Jawaban</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{savedArray.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="border px-2 py-1">{item.category}</td>
|
||||
<td className="border px-2 py-1">{item.title}</td>
|
||||
<td className="border px-2 py-1">{item.question}</td>
|
||||
<td className="border px-2 py-1">{item.answer}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{handleDelete && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteFile}
|
||||
className="text-red-600 hover:underline mb-4"
|
||||
>
|
||||
Hapus File
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link href="/supervisor/knowledge-base">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded border border-blue-600 text-blue-600 px-4 py-2 hover:bg-blue-50 transition"
|
||||
>
|
||||
Kembali
|
||||
</button>
|
||||
</Link>
|
||||
{handleDelete && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSave(savedArray)}
|
||||
className="rounded bg-blue-600 text-white px-4 py-2 hover:bg-blue-700 transition"
|
||||
>
|
||||
Simpan
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportKnowledgeBase;
|
||||
|
|
@ -46,6 +46,7 @@ import { useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string().min(2, {
|
||||
|
|
@ -67,6 +68,7 @@ const FormSchemaCreate = z.object({
|
|||
|
||||
const KnowledgeBase = () => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const [categories, setCategories] = useState<any>([]);
|
||||
const [questions, setQuestions] = useState<any>([]);
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState(0);
|
||||
|
|
@ -318,14 +320,17 @@ const KnowledgeBase = () => {
|
|||
))}
|
||||
</Accordion>
|
||||
<div className="flex gap-3">
|
||||
<Button fullWidth size="md" variant="outline">
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
Import
|
||||
</Button>
|
||||
<Link className="w-full" href="/supervisor/knowledge-base/import">
|
||||
<Button fullWidth size="md" variant="outline">
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
Import
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
fullWidth
|
||||
size="md"
|
||||
onClick={() => setViewCreate(!viewCreate)}
|
||||
className="hover:bg-white hover:text-black"
|
||||
>
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
Add Question
|
||||
|
|
|
|||
Loading…
Reference in New Issue