update
This commit is contained in:
parent
fe4b0bd917
commit
8db50bdc90
|
|
@ -27,4 +27,4 @@ auto-deploy:
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
script:
|
script:
|
||||||
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-berita-bumn/build?token=autodeploymedols
|
- curl --user admin:$JENKINS_PWD http:// /job/auto-deploy-berita-bumn/build?token=autodeploymedols
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -300,17 +301,39 @@ export default function DetailContent() {
|
||||||
initStateData();
|
initStateData();
|
||||||
}, [listCategory]);
|
}, [listCategory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedIndex(0);
|
||||||
|
}, [detailFiles]);
|
||||||
|
|
||||||
async function initStateData() {
|
async function initStateData() {
|
||||||
loading();
|
loading();
|
||||||
|
try {
|
||||||
|
// 1️⃣ Ambil artikel by slug
|
||||||
const res = await getArticleBySlug(slug);
|
const res = await getArticleBySlug(slug);
|
||||||
const data = res.data?.data;
|
const data = res?.data?.data;
|
||||||
|
|
||||||
|
if (!data?.id) return;
|
||||||
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
|
||||||
setArticleDetail(data);
|
setArticleDetail(data);
|
||||||
|
setThumbnail(data.thumbnailUrl);
|
||||||
|
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();
|
close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!articleDetail?.files || articleDetail.files.length === 0) {
|
if (!articleDetail?.files || articleDetail.files.length === 0) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -320,6 +343,23 @@ export default function DetailContent() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
<>
|
<>
|
||||||
<div className="bg-white grid grid-cols-1 md:grid-cols-3 gap-6 px-8 py-8">
|
<div className="bg-white grid grid-cols-1 md:grid-cols-3 gap-6 px-8 py-8">
|
||||||
|
|
@ -370,23 +410,22 @@ export default function DetailContent() {
|
||||||
|
|
||||||
<div className="w-full h-auto mb-6">
|
<div className="w-full h-auto mb-6">
|
||||||
{/* Gambar utama */}
|
{/* Gambar utama */}
|
||||||
<div className="w-full">
|
{detailFiles.length > 0 ? (
|
||||||
|
<>
|
||||||
<Image
|
<Image
|
||||||
src={articleDetail.files[selectedIndex].fileUrl}
|
src={detailFiles[selectedIndex]?.fileUrl}
|
||||||
alt={articleDetail.files[selectedIndex].fileAlt || "Berita"}
|
alt={detailFiles[selectedIndex]?.fileAlt || "Berita"}
|
||||||
width={800}
|
width={800}
|
||||||
height={400}
|
height={400}
|
||||||
className="rounded-lg w-full object-cover"
|
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">
|
||||||
{articleDetail.files.map((file: any, index: number) => (
|
{detailFiles.map((file, index) => (
|
||||||
<button
|
<button
|
||||||
key={file.id || index}
|
key={file.id}
|
||||||
onClick={() => setSelectedIndex(index)}
|
onClick={() => setSelectedIndex(index)}
|
||||||
className={`border-2 rounded-lg overflow-hidden ${
|
className={`border-2 rounded-lg ${
|
||||||
selectedIndex === index
|
selectedIndex === index
|
||||||
? "border-red-500"
|
? "border-red-500"
|
||||||
: "border-transparent"
|
: "border-transparent"
|
||||||
|
|
@ -402,6 +441,12 @@ export default function DetailContent() {
|
||||||
</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>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Slug */}
|
{/* Slug */}
|
||||||
<p className="text-sm text-gray-500 mt-2 text-end">
|
<p className="text-sm text-gray-500 mt-2 text-end">
|
||||||
|
|
@ -475,10 +520,12 @@ export default function DetailContent() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<div className="text-gray-700 leading-relaxed text-justify">
|
<div className="prose max-w-none text-justify">
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: articleDetail?.htmlDescription || "",
|
__html: decodeHtmlString(
|
||||||
|
articleDetail?.htmlDescription || ""
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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,28 +197,50 @@ 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);
|
if (!articleData) return;
|
||||||
setValue("source", data?.source);
|
|
||||||
const cleanDescription = data?.htmlDescription
|
// ===== ARTICLE DATA =====
|
||||||
? data.htmlDescription
|
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(/\\"/g, '"')
|
||||||
.replace(/\\n/g, "\n", "\\")
|
.replace(/\\n/g, "\n")
|
||||||
.trim()
|
.trim()
|
||||||
: "";
|
: "";
|
||||||
setValue("description", cleanDescription);
|
|
||||||
setValue("tags", data?.tags ? data.tags.split(",") : []);
|
|
||||||
setThumbnail(data?.thumbnailUrl);
|
|
||||||
setDiseId(data?.aiArticleId);
|
|
||||||
setDetailFiles(data?.files);
|
|
||||||
|
|
||||||
setupInitCategory(data?.categories);
|
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();
|
close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setupInitCategory = (data: any) => {
|
const setupInitCategory = (data: any) => {
|
||||||
const temp: CategoryType[] = [];
|
const temp: CategoryType[] = [];
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,13 @@ export async function uploadArticleThumbnail(id: string, data: any) {
|
||||||
return await httpPostInterceptor(`/articles/thumbnail/${id}`, data, headers);
|
return await httpPostInterceptor(`/articles/thumbnail/${id}`, data, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getArticleFiles() {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
return await httpGet(`/article-files`, headers);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteArticleFiles(id: number) {
|
export async function deleteArticleFiles(id: number) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "multipart/form-data",
|
"content-type": "multipart/form-data",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue