This commit is contained in:
Sabda Yagra 2025-06-30 13:25:00 +07:00
parent 0f9c070af1
commit 9dc4b64066
2 changed files with 262 additions and 4 deletions

View File

@ -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;

View File

@ -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