update
This commit is contained in:
parent
07316b0aa8
commit
439cf1b665
|
|
@ -5,6 +5,7 @@ import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
getArticleById,
|
getArticleById,
|
||||||
getArticleBySlug,
|
getArticleBySlug,
|
||||||
|
getArticleFiles,
|
||||||
getListArticle,
|
getListArticle,
|
||||||
} from "@/service/article";
|
} from "@/service/article";
|
||||||
import { close, error, loading } from "@/config/swal";
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
|
@ -74,7 +75,7 @@ export default function DetailContent() {
|
||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
});
|
});
|
||||||
const [detailfiles, setDetailFiles] = useState<any>([]);
|
const [detailFiles, setDetailFiles] = useState<any[]>([]);
|
||||||
const [mainImage, setMainImage] = useState(0);
|
const [mainImage, setMainImage] = useState(0);
|
||||||
const [thumbnail, setThumbnail] = useState("-");
|
const [thumbnail, setThumbnail] = useState("-");
|
||||||
const [diseId, setDiseId] = useState(0);
|
const [diseId, setDiseId] = useState(0);
|
||||||
|
|
@ -257,16 +258,55 @@ export default function DetailContent() {
|
||||||
initStateData();
|
initStateData();
|
||||||
}, [listCategory]);
|
}, [listCategory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedIndex(0);
|
||||||
|
}, [detailFiles]);
|
||||||
|
|
||||||
async function initStateData() {
|
async function initStateData() {
|
||||||
loading();
|
loading();
|
||||||
const res = await getArticleBySlug(slug);
|
try {
|
||||||
const data = res?.data?.data;
|
// 1️⃣ Ambil artikel by slug
|
||||||
|
const res = await getArticleBySlug(slug);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
if (!data?.id) return;
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
setArticleDetail(data);
|
||||||
setArticleDetail(data); // <-- Add this
|
setThumbnail(data.thumbnailUrl);
|
||||||
close();
|
setDiseId(data.aiArticleId);
|
||||||
|
|
||||||
|
// 2️⃣ Ambil SEMUA article files
|
||||||
|
const filesRes = await getArticleFiles();
|
||||||
|
const allFiles = filesRes?.data?.data ?? [];
|
||||||
|
|
||||||
|
// 3️⃣ FILTER sesuai articleId
|
||||||
|
const filteredFiles = allFiles.filter(
|
||||||
|
(file: any) => file.articleId === data.id
|
||||||
|
);
|
||||||
|
|
||||||
|
setDetailFiles(filteredFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Init state detail error:", error);
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHtmlString(raw: string = "") {
|
||||||
|
if (!raw) return "";
|
||||||
|
|
||||||
|
// 1️⃣ Hapus newline escape, backslash, dsb
|
||||||
|
let decoded = raw
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\"/g, '"') // ubah \" jadi "
|
||||||
|
.replace(/\\'/g, "'") // ubah \' jadi '
|
||||||
|
.replace(/\\\\/g, "\\") // ubah \\ jadi \
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 2️⃣ Decode entity HTML (misal ")
|
||||||
|
const el = document.createElement("textarea");
|
||||||
|
el.innerHTML = decoded;
|
||||||
|
return el.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -324,38 +364,43 @@ export default function DetailContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full h-auto mb-6">
|
<div className="w-full h-auto mb-6">
|
||||||
<div className="w-full">
|
{detailFiles.length > 0 ? (
|
||||||
<Image
|
<>
|
||||||
src={articleDetail?.files[selectedIndex].fileUrl}
|
<Image
|
||||||
alt={articleDetail?.files[selectedIndex].fileAlt || "Berita"}
|
src={detailFiles[selectedIndex]?.fileUrl}
|
||||||
width={800}
|
alt={detailFiles[selectedIndex]?.fileAlt || "Berita"}
|
||||||
height={400}
|
width={800}
|
||||||
className="rounded-lg w-full object-cover"
|
height={400}
|
||||||
/>
|
className="rounded-lg w-full object-cover"
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
{/* Thumbnail */}
|
<div className="flex gap-2 mt-3 overflow-x-auto">
|
||||||
<div className="flex gap-2 mt-3 overflow-x-auto">
|
{detailFiles.map((file, index) => (
|
||||||
{articleDetail?.files.map((file: any, index: number) => (
|
<button
|
||||||
<button
|
key={file.id}
|
||||||
key={file.id || index}
|
onClick={() => setSelectedIndex(index)}
|
||||||
onClick={() => setSelectedIndex(index)}
|
className={`border-2 rounded-lg ${
|
||||||
className={`border-2 rounded-lg overflow-hidden ${
|
selectedIndex === index
|
||||||
selectedIndex === index
|
? "border-red-500"
|
||||||
? "border-red-500"
|
: "border-transparent"
|
||||||
: "border-transparent"
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
<Image
|
||||||
<Image
|
src={file.fileUrl}
|
||||||
src={file.fileUrl}
|
alt={file.fileAlt || "Thumbnail"}
|
||||||
alt={file.fileAlt || "Thumbnail"}
|
width={100}
|
||||||
width={100}
|
height={80}
|
||||||
height={80}
|
className="object-cover"
|
||||||
className="object-cover"
|
/>
|
||||||
/>
|
</button>
|
||||||
</button>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="h-[400px] flex items-center justify-center bg-gray-100 rounded-lg">
|
||||||
|
<p className="text-gray-400">Gambar tidak tersedia</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className=" flex flex-row w-fit rounded overflow-hidden mr-5 gap-3 mt-3">
|
<div className=" flex flex-row w-fit rounded overflow-hidden mr-5 gap-3 mt-3">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -514,7 +514,7 @@ export default function CreateArticleForm() {
|
||||||
id="title"
|
id="title"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Masukkan judul artikel"
|
placeholder="Masukkan judul artikel"
|
||||||
className="w-full border rounded-lg dark:border-gray-400"
|
className="h-16 px-4 text-2xl leading-tight"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import {
|
||||||
deleteArticleFiles,
|
deleteArticleFiles,
|
||||||
getArticleByCategory,
|
getArticleByCategory,
|
||||||
getArticleById,
|
getArticleById,
|
||||||
|
getArticleFiles,
|
||||||
submitApproval,
|
submitApproval,
|
||||||
unPublishArticle,
|
unPublishArticle,
|
||||||
updateArticle,
|
updateArticle,
|
||||||
|
|
@ -196,27 +197,49 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
|
|
||||||
async function initState() {
|
async function initState() {
|
||||||
loading();
|
loading();
|
||||||
const res = await getArticleById(id);
|
try {
|
||||||
const data = res.data?.data;
|
// 1️⃣ Ambil ARTICLE
|
||||||
setDetailData(data);
|
const articleRes = await getArticleById(id);
|
||||||
setValue("title", data?.title);
|
const articleData = articleRes.data?.data;
|
||||||
setValue("customCreatorName", data?.customCreatorName);
|
|
||||||
setValue("slug", data?.slug);
|
|
||||||
setValue("source", data?.source);
|
|
||||||
const cleanDescription = data?.htmlDescription
|
|
||||||
? data.htmlDescription
|
|
||||||
.replace(/\\"/g, '"')
|
|
||||||
.replace(/\\n/g, "\n", "\\")
|
|
||||||
.trim()
|
|
||||||
: "";
|
|
||||||
setValue("description", cleanDescription);
|
|
||||||
setValue("tags", data?.tags ? data.tags.split(",") : []);
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
|
||||||
|
|
||||||
setupInitCategory(data?.categories);
|
if (!articleData) return;
|
||||||
close();
|
|
||||||
|
// ===== ARTICLE DATA =====
|
||||||
|
setDetailData(articleData);
|
||||||
|
setValue("title", articleData.title);
|
||||||
|
setValue("customCreatorName", articleData.customCreatorName);
|
||||||
|
setValue("slug", articleData.slug);
|
||||||
|
setValue("source", articleData.source);
|
||||||
|
|
||||||
|
const cleanDescription = articleData.htmlDescription
|
||||||
|
? articleData.htmlDescription
|
||||||
|
.replace(/\\"/g, '"')
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.trim()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
setValue("description", cleanDescription);
|
||||||
|
setValue("tags", articleData.tags ? articleData.tags.split(",") : []);
|
||||||
|
|
||||||
|
setThumbnail(articleData.thumbnailUrl);
|
||||||
|
setDiseId(articleData.aiArticleId);
|
||||||
|
setupInitCategory(articleData.categories);
|
||||||
|
|
||||||
|
// 2️⃣ Ambil SEMUA article files
|
||||||
|
const filesRes = await getArticleFiles();
|
||||||
|
const allFiles = filesRes.data?.data ?? [];
|
||||||
|
|
||||||
|
// 3️⃣ FILTER berdasarkan ARTICLE ID yang sedang dibuka
|
||||||
|
const filteredFiles = allFiles.filter(
|
||||||
|
(file: any) => file.articleId === articleData.id
|
||||||
|
);
|
||||||
|
|
||||||
|
setDetailFiles(filteredFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Init state error:", error);
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupInitCategory = (data: any) => {
|
const setupInitCategory = (data: any) => {
|
||||||
|
|
@ -667,9 +690,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
name="title"
|
name="title"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label htmlFor="title" className="block text-sm font-medium mb-1">
|
<label htmlFor="title" className="block text-xl font-medium mb-2">
|
||||||
Judul
|
Judul
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="title"
|
id="title"
|
||||||
|
|
@ -677,7 +701,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
readOnly={isDetail}
|
readOnly={isDetail}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="w-full border rounded-lg"
|
className="h-16 px-4 text-2xl leading-tight"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,13 @@ export async function getCategoryPagination(data: any) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getArticleFiles() {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
return await httpGet(`/article-files`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
export async function uploadArticleFile(id: string, data: any) {
|
export async function uploadArticleFile(id: string, data: any) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "multipart/form-data",
|
"content-type": "multipart/form-data",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
@ -11,7 +15,7 @@
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|
@ -19,9 +23,19 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue