Compare commits
10 Commits
0a9c119df1
...
9ba7172d45
| Author | SHA1 | Date |
|---|---|---|
|
|
9ba7172d45 | |
|
|
042cbd9733 | |
|
|
187fe3a7c9 | |
|
|
c2da413522 | |
|
|
b3e573792f | |
|
|
8e58a91210 | |
|
|
4b815b4e5a | |
|
|
355c086353 | |
|
|
1761010341 | |
|
|
93fb5d7e5d |
|
|
@ -11,12 +11,12 @@ build-dev:
|
|||
name: docker:25.0.3-cli
|
||||
services:
|
||||
- name: docker:25.0.3-dind
|
||||
command: ["--insecure-registry=103.82.242.92:8900"]
|
||||
command: ["--insecure-registry=38.47.185.86:8900"]
|
||||
script:
|
||||
- docker logout
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
||||
- docker build -t 103.82.242.92:8900/medols/web-arah-negeri:dev .
|
||||
- docker push 103.82.242.92:8900/medols/web-arah-negeri:dev
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900
|
||||
- docker build -t 38.47.185.86:8900/medols/web-arah-negeri:dev .
|
||||
- docker push 38.47.185.86:8900/medols/web-arah-negeri:dev
|
||||
|
||||
auto-deploy:
|
||||
stage: deploy
|
||||
|
|
@ -27,4 +27,4 @@ auto-deploy:
|
|||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- curl --user admin:$JENKINS_PWD http://103.31.38.120:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols
|
||||
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Link from "next/link";
|
|||
import {
|
||||
getArticleById,
|
||||
getArticleBySlug,
|
||||
getArticleFiles,
|
||||
getListArticle,
|
||||
} from "@/service/article";
|
||||
import { close, error, loading } from "@/config/swal";
|
||||
|
|
@ -20,6 +21,7 @@ import {
|
|||
} from "@/service/master-user";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { formatTextToHtmlTag } from "@/utils/global";
|
||||
|
||||
type TabKey = "trending" | "comments" | "latest";
|
||||
|
||||
|
|
@ -75,13 +77,13 @@ export default function DetailContent() {
|
|||
startDate: null,
|
||||
endDate: null,
|
||||
});
|
||||
const [detailfiles, setDetailFiles] = useState<any>([]);
|
||||
const [detailFiles, setDetailFiles] = useState<any[]>([]);
|
||||
const [mainImage, setMainImage] = useState(0);
|
||||
const [thumbnail, setThumbnail] = useState("-");
|
||||
const [diseId, setDiseId] = useState(0);
|
||||
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
||||
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
|
|
@ -297,18 +299,39 @@ export default function DetailContent() {
|
|||
initStateData();
|
||||
}, [listCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedIndex(0);
|
||||
}, [detailFiles]);
|
||||
|
||||
async function initStateData() {
|
||||
loading();
|
||||
const res = await getArticleBySlug(slug);
|
||||
const data = res?.data?.data;
|
||||
try {
|
||||
// 1️⃣ Ambil artikel by slug
|
||||
const res = await getArticleBySlug(slug);
|
||||
const data = res?.data?.data;
|
||||
|
||||
setThumbnail(data?.thumbnailUrl);
|
||||
setDiseId(data?.aiArticleId);
|
||||
setDetailFiles(data?.files);
|
||||
setArticleDetail(data); // <-- Add this
|
||||
close();
|
||||
if (!data?.id) return;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// if (!articleDetail?.files || articleDetail?.files?.length === 0) {
|
||||
// return (
|
||||
// <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
|
||||
|
|
@ -316,6 +339,16 @@ export default function DetailContent() {
|
|||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
function removeImgTags(htmlString?: { __html: string }) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(String(htmlString?.__html), "text/html");
|
||||
|
||||
const images = doc.querySelectorAll("img");
|
||||
images.forEach((img) => img.remove());
|
||||
|
||||
return { __html: doc.body.innerHTML };
|
||||
}
|
||||
function decodeHtmlString(raw: string = "") {
|
||||
if (!raw) return "";
|
||||
|
||||
|
|
@ -367,16 +400,18 @@ export default function DetailContent() {
|
|||
</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
<span>
|
||||
{new Date(articleDetail?.publishedAt).toLocaleDateString(
|
||||
"id-ID",
|
||||
{
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
{new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt)
|
||||
.toLocaleString("id-ID", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace("pukul ", "")}{" "}
|
||||
WIB
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>{articleDetail?.categories?.[0]?.title}</span>
|
||||
|
|
@ -384,36 +419,30 @@ export default function DetailContent() {
|
|||
|
||||
<div className="w-full h-auto mb-6">
|
||||
{/* Gambar utama */}
|
||||
{articleDetail?.files && articleDetail.files.length > 0 ? (
|
||||
{detailFiles.length > 0 ? (
|
||||
<>
|
||||
{/* Gambar utama */}
|
||||
<div className="w-full">
|
||||
<Image
|
||||
src={articleDetail.files[selectedIndex]?.fileUrl}
|
||||
alt={
|
||||
articleDetail.files[selectedIndex]?.fileAlt || "Berita"
|
||||
}
|
||||
width={800}
|
||||
height={400}
|
||||
className="rounded-lg w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<Image
|
||||
src={detailFiles[selectedIndex]?.fileUrl}
|
||||
alt={detailFiles[selectedIndex]?.fileAlt || "Berita"}
|
||||
width={800}
|
||||
height={400}
|
||||
className="rounded-lg w-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Thumbnail */}
|
||||
<div className="flex gap-2 mt-3 overflow-x-auto">
|
||||
{articleDetail.files.map((file: any, index: number) => (
|
||||
{detailFiles.map((file, index) => (
|
||||
<button
|
||||
key={file?.id || index}
|
||||
key={file.id}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
className={`border-2 rounded-lg overflow-hidden ${
|
||||
className={`border-2 rounded-lg ${
|
||||
selectedIndex === index
|
||||
? "border-red-500"
|
||||
: "border-transparent"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={file?.fileUrl}
|
||||
alt={file?.fileAlt || "Thumbnail"}
|
||||
src={file.fileUrl}
|
||||
alt={file.fileAlt || "Thumbnail"}
|
||||
width={100}
|
||||
height={80}
|
||||
className="object-cover"
|
||||
|
|
@ -423,9 +452,8 @@ export default function DetailContent() {
|
|||
</div>
|
||||
</>
|
||||
) : (
|
||||
// Jika file kosong/null tapi tetap render data lainnya
|
||||
<div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
|
||||
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p>
|
||||
<div className="h-[400px] flex items-center justify-center bg-gray-100 rounded-lg">
|
||||
<p className="text-gray-400">Gambar tidak tersedia</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -503,11 +531,10 @@ export default function DetailContent() {
|
|||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="prose max-w-none text-justify">
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: decodeHtmlString(
|
||||
articleDetail?.htmlDescription || ""
|
||||
),
|
||||
}}
|
||||
dangerouslySetInnerHTML={removeImgTags(
|
||||
formatTextToHtmlTag(articleDetail?.htmlDescription),
|
||||
)}
|
||||
className="text-sm lg:text-xl lg:leading-8 text-justify space-y-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -815,7 +842,7 @@ export default function DetailContent() {
|
|||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -847,7 +874,7 @@ export default function DetailContent() {
|
|||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// components/custom-editor.js
|
||||
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { CKEditor } from "@ckeditor/ckeditor5-react";
|
||||
|
||||
import "@/styles/custom-editor.css";
|
||||
import Editor from "@/vendor/ckeditor5/build/ckeditor";
|
||||
|
||||
function CustomEditor(props) {
|
||||
|
|
@ -47,7 +47,7 @@ function CustomEditor(props) {
|
|||
padding: 1rem;
|
||||
}
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
margin: 0.5em 0 !important;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 1em 0 0.5em 0;
|
||||
|
|
@ -72,98 +72,6 @@ function CustomEditor(props) {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
<style jsx>{`
|
||||
.ckeditor-wrapper {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.ckeditor-wrapper :global(.ck.ck-editor__main) {
|
||||
min-height: ${props.height || 400}px;
|
||||
max-height: ${maxHeight}px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
||||
min-height: ${(props.height || 400) - 50}px;
|
||||
max-height: ${maxHeight - 50}px;
|
||||
overflow-y: auto !important;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||
background: #fff !important;
|
||||
color: #111 !important;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
||||
background: #111 !important;
|
||||
color: #f9fafb !important;
|
||||
}
|
||||
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h1),
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h2),
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h3),
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h4),
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h5),
|
||||
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h6) {
|
||||
color: #f9fafb !important;
|
||||
}
|
||||
|
||||
:global(.dark)
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable blockquote) {
|
||||
background-color: #1f2937 !important;
|
||||
border-left-color: #374151 !important;
|
||||
color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbar styling for webkit browsers */
|
||||
.ckeditor-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar) {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:global(.dark)
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||
background: #1f2937;
|
||||
}
|
||||
|
||||
:global(.dark)
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
:global(.dark)
|
||||
.ckeditor-wrapper
|
||||
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||
background: #6b7280;
|
||||
}
|
||||
|
||||
/* Ensure content doesn't overflow */
|
||||
.ckeditor-wrapper :global(.ck.ck-editor__editable .ck-content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ function ViewEditor(props) {
|
|||
.ckeditor-view-wrapper {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const CustomEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
|
|
@ -118,14 +118,14 @@ export default function CreateArticleForm() {
|
|||
const [tag, setTag] = useState("");
|
||||
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
|
||||
const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [thumbnailValidation, setThumbnailValidation] = useState("");
|
||||
const [filesValidation, setFileValidation] = useState("");
|
||||
const [diseData, setDiseData] = useState<DiseData>();
|
||||
const [selectedWritingType, setSelectedWritingType] = useState("single");
|
||||
const [status, setStatus] = useState<"publish" | "draft" | "scheduled">(
|
||||
"publish"
|
||||
"publish",
|
||||
);
|
||||
const [isScheduled, setIsScheduled] = useState(false);
|
||||
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
|
||||
|
|
@ -230,7 +230,7 @@ export default function CreateArticleForm() {
|
|||
}
|
||||
|
||||
const saveArticleToDise = async (
|
||||
values: z.infer<typeof createArticleSchema>
|
||||
values: z.infer<typeof createArticleSchema>,
|
||||
) => {
|
||||
if (useAi) {
|
||||
const request = {
|
||||
|
|
@ -351,12 +351,12 @@ export default function CreateArticleForm() {
|
|||
|
||||
// format: 2025-10-08 14:30:00
|
||||
const formattedDateTime = `${combinedDate.getFullYear()}-${String(
|
||||
combinedDate.getMonth() + 1
|
||||
combinedDate.getMonth() + 1,
|
||||
).padStart(2, "0")}-${String(combinedDate.getDate()).padStart(
|
||||
2,
|
||||
"0"
|
||||
"0",
|
||||
)} ${String(combinedDate.getHours()).padStart(2, "0")}:${String(
|
||||
combinedDate.getMinutes()
|
||||
combinedDate.getMinutes(),
|
||||
).padStart(2, "0")}:00`;
|
||||
|
||||
const request = {
|
||||
|
|
@ -493,7 +493,7 @@ export default function CreateArticleForm() {
|
|||
}
|
||||
}
|
||||
const uniqueArray = temp.filter(
|
||||
(item, index) => temp.indexOf(item) === index
|
||||
(item, index) => temp.indexOf(item) === index,
|
||||
);
|
||||
|
||||
setValue("tags", uniqueArray as [string, ...string[]]);
|
||||
|
|
@ -505,7 +505,7 @@ export default function CreateArticleForm() {
|
|||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
|
||||
<p className="text-sm">Judul</p>
|
||||
<p className="text-sm">Judulss</p>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -514,7 +514,7 @@ export default function CreateArticleForm() {
|
|||
id="title"
|
||||
type="text"
|
||||
placeholder="Masukkan judul artikel"
|
||||
className="w-full border rounded-lg dark:border-gray-400"
|
||||
className="h-16 px-4 text-2xl leading-tight"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -578,7 +578,7 @@ export default function CreateArticleForm() {
|
|||
// });
|
||||
setValue(
|
||||
"description",
|
||||
data?.articleBody ? data?.articleBody : ""
|
||||
data?.articleBody ? data?.articleBody : "",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -588,7 +588,7 @@ export default function CreateArticleForm() {
|
|||
setDiseData(data);
|
||||
setValue(
|
||||
"description",
|
||||
data?.articleBody ? data?.articleBody : ""
|
||||
data?.articleBody ? data?.articleBody : "",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -781,7 +781,7 @@ export default function CreateArticleForm() {
|
|||
type="button"
|
||||
onClick={() => {
|
||||
const filteredTags = value.filter(
|
||||
(tag: string) => tag !== item
|
||||
(tag: string) => tag !== item,
|
||||
);
|
||||
if (filteredTags.length === 0) {
|
||||
setError("tags", {
|
||||
|
|
@ -792,7 +792,7 @@ export default function CreateArticleForm() {
|
|||
clearErrors("tags");
|
||||
setValue(
|
||||
"tags",
|
||||
filteredTags as [string, ...string[]]
|
||||
filteredTags as [string, ...string[]],
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
deleteArticleFiles,
|
||||
getArticleByCategory,
|
||||
getArticleById,
|
||||
getArticleFiles,
|
||||
submitApproval,
|
||||
unPublishArticle,
|
||||
updateArticle,
|
||||
|
|
@ -196,27 +197,49 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
|
||||
async function initState() {
|
||||
loading();
|
||||
const res = await getArticleById(id);
|
||||
const data = res.data?.data;
|
||||
setDetailData(data);
|
||||
setValue("title", data?.title);
|
||||
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);
|
||||
try {
|
||||
// 1️⃣ Ambil ARTICLE
|
||||
const articleRes = await getArticleById(id);
|
||||
const articleData = articleRes.data?.data;
|
||||
|
||||
setupInitCategory(data?.categories);
|
||||
close();
|
||||
if (!articleData) return;
|
||||
|
||||
// ===== 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) => {
|
||||
|
|
@ -667,9 +690,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
name="title"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<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
|
||||
</label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
id="title"
|
||||
|
|
@ -677,7 +701,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
value={value ?? ""}
|
||||
readOnly={isDetail}
|
||||
onChange={onChange}
|
||||
className="w-full border rounded-lg"
|
||||
className="h-16 px-4 text-2xl leading-tight"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ export default function ArticleTable() {
|
|||
return cellValue;
|
||||
}
|
||||
},
|
||||
[article, page]
|
||||
[article, page],
|
||||
);
|
||||
|
||||
let typingTimer: NodeJS.Timeout;
|
||||
|
|
@ -445,8 +445,8 @@ export default function ArticleTable() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="w-full overflow-x-hidden">
|
||||
<div className="w-full mx-auto overflow-x-hidden">
|
||||
<Table className="w-full table-fixed border text-sm">
|
||||
<div className="w-full overflow-x-auto">
|
||||
<Table className="min-w-[1000px] w-full table-auto border text-sm">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{(username === "admin-mabes"
|
||||
|
|
@ -455,7 +455,18 @@ export default function ArticleTable() {
|
|||
).map((column) => (
|
||||
<TableHead
|
||||
key={column.uid}
|
||||
className="truncate bg-white dark:bg-black text-black dark:text-white border-b text-md"
|
||||
className={`bg-white dark:bg-black text-black dark:text-white
|
||||
text-sm font-semibold border-b px-3 py-3
|
||||
${
|
||||
column.uid === "no"
|
||||
? "min-w-[60px] text-center"
|
||||
: column.uid === "title"
|
||||
? "min-w-[280px]"
|
||||
: column.uid === "actions"
|
||||
? "min-w-[100px] text-center"
|
||||
: "min-w-[160px]"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{column.name}
|
||||
</TableHead>
|
||||
|
|
@ -472,7 +483,17 @@ export default function ArticleTable() {
|
|||
).map((column) => (
|
||||
<TableCell
|
||||
key={column.uid}
|
||||
className="truncate text-black dark:text-white max-w-[200px]"
|
||||
className={`text-black dark:text-white text-sm px-3 py-3 align-top
|
||||
${
|
||||
column.uid === "no"
|
||||
? "min-w-[60px] text-center font-medium"
|
||||
: column.uid === "title"
|
||||
? "min-w-[280px] whitespace-normal break-words leading-snug"
|
||||
: column.uid === "actions"
|
||||
? "min-w-[100px] text-center"
|
||||
: "min-w-[160px] whitespace-normal break-words"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{renderCell(item, column.uid)}
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
domains: ["mikulnews.com", "dev.mikulnews.com"],
|
||||
domains: [
|
||||
"mikulnews.com",
|
||||
"dev.mikulnews.com",
|
||||
"jaecoocihampelasbdg.com",
|
||||
"dev.arahnegeri.com",
|
||||
],
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
// Add experimental features for better chunk handling
|
||||
experimental: {
|
||||
optimizePackageImports: ["@ckeditor/ckeditor5-react", "react-apexcharts"],
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,12 +39,12 @@
|
|||
"js-cookie": "^3.0.5",
|
||||
"lightningcss": "^1.30.1",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "15.3.4",
|
||||
"react": "^19.0.0",
|
||||
"next": "^16.1.1",
|
||||
"react": "^19.2.3",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-day-picker": "^9.7.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-hook-form": "^7.65.0",
|
||||
"react-password-checklist": "^1.8.1",
|
||||
|
|
|
|||
|
|
@ -136,6 +136,13 @@ export async function uploadArticleFile(id: string, data: any) {
|
|||
return await httpPostInterceptor(`/article-files/${id}`, data, headers);
|
||||
}
|
||||
|
||||
export async function getArticleFiles() {
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
};
|
||||
return await httpGet(`/article-files`, headers);
|
||||
}
|
||||
|
||||
export async function uploadArticleThumbnail(id: string, data: any) {
|
||||
const headers = {
|
||||
"content-type": "multipart/form-data",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
|
||||
const baseURL = "https://dev.mikulnews.com/api";
|
||||
const baseURL = "https://arahnegeri.com/api";
|
||||
|
||||
const axiosBaseInstance = axios.create({
|
||||
baseURL,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import axios from "axios";
|
|||
import { postSignIn } from "../master-user";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const baseURL = "https://dev.mikulnews.com/api";
|
||||
const baseURL = "https://arahnegeri.com/api";
|
||||
|
||||
const refreshToken = Cookies.get("refresh_token");
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ axiosInterceptorInstance.interceptors.request.use(
|
|||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Response interceptor
|
||||
|
|
@ -66,7 +66,7 @@ axiosInterceptorInstance.interceptors.response.use(
|
|||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default axiosInterceptorInstance;
|
||||
|
|
|
|||
|
|
@ -1,215 +0,0 @@
|
|||
/* CKEditor Custom Styling */
|
||||
|
||||
/* Main editor container */
|
||||
.ck.ck-editor {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Toolbar styling */
|
||||
.ck.ck-toolbar {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #d1d5db;
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ck.ck-toolbar .ck-toolbar__items {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Main editable area */
|
||||
.ck.ck-editor__editable {
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
padding: 1.5em 2em !important;
|
||||
min-height: 400px;
|
||||
line-height: 1.6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Focus state */
|
||||
.ck.ck-editor__editable.ck-focused {
|
||||
border-color: #1a9aef;
|
||||
box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Content styling */
|
||||
.ck.ck-editor__editable .ck-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Typography improvements */
|
||||
.ck.ck-editor__editable p {
|
||||
margin: 0.5em 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable h1,
|
||||
.ck.ck-editor__editable h2,
|
||||
.ck.ck-editor__editable h3,
|
||||
.ck.ck-editor__editable h4,
|
||||
.ck.ck-editor__editable h5,
|
||||
.ck.ck-editor__editable h6 {
|
||||
margin: 1em 0 0.5em 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable h1 { font-size: 1.75em; }
|
||||
.ck.ck-editor__editable h2 { font-size: 1.5em; }
|
||||
.ck.ck-editor__editable h3 { font-size: 1.25em; }
|
||||
.ck.ck-editor__editable h4 { font-size: 1.1em; }
|
||||
.ck.ck-editor__editable h5 { font-size: 1em; }
|
||||
.ck.ck-editor__editable h6 { font-size: 0.9em; }
|
||||
|
||||
/* Lists */
|
||||
.ck.ck-editor__editable ul,
|
||||
.ck.ck-editor__editable ol {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable li {
|
||||
margin: 0.25em 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
.ck.ck-editor__editable blockquote {
|
||||
margin: 1em 0;
|
||||
padding: 0.75em 1em;
|
||||
border-left: 4px solid #1a9aef;
|
||||
background-color: #f8fafc;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-style: italic;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.ck.ck-editor__editable table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable table td,
|
||||
.ck.ck-editor__editable table th {
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.5em 0.75em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable table th {
|
||||
background-color: #f8fafc;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.ck.ck-editor__editable a {
|
||||
color: #1a9aef;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable a:hover {
|
||||
color: #0d7cd6;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
.ck.ck-editor__editable pre {
|
||||
background-color: #f8fafc;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 1em;
|
||||
margin: 1em 0;
|
||||
overflow-x: auto;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable code {
|
||||
background-color: #f1f5f9;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.ck.ck-editor__editable img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
/* Horizontal rule */
|
||||
.ck.ck-editor__editable hr {
|
||||
border: none;
|
||||
border-top: 1px solid #d1d5db;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
/* Placeholder text */
|
||||
.ck.ck-editor__editable.ck-blurred:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.ck.ck-editor__editable {
|
||||
padding: 1em 1.5em !important;
|
||||
font-size: 16px; /* Better for mobile */
|
||||
}
|
||||
|
||||
.ck.ck-toolbar {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.ck.ck-toolbar .ck-toolbar__items {
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ck.ck-editor__editable {
|
||||
background: #1f2937;
|
||||
color: #f9fafb;
|
||||
border-color: #4b5563;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable h1,
|
||||
.ck.ck-editor__editable h2,
|
||||
.ck.ck-editor__editable h3,
|
||||
.ck.ck-editor__editable h4,
|
||||
.ck.ck-editor__editable h5,
|
||||
.ck.ck-editor__editable h6 {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable blockquote {
|
||||
background-color: #374151;
|
||||
border-left-color: #1a9aef;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable pre {
|
||||
background-color: #374151;
|
||||
border-color: #4b5563;
|
||||
}
|
||||
|
||||
.ck.ck-editor__editable code {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/* ========== CKEditor Wrapper ========== */
|
||||
.ckeditor-wrapper {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* ========== Main Editor Container ========== */
|
||||
.ckeditor-wrapper .ck.ck-editor__main {
|
||||
min-height: var(--editor-min-height, 400px);
|
||||
max-height: var(--editor-max-height, 600px);
|
||||
}
|
||||
|
||||
/* ========== Editable Content Area (ClassicEditor) ========== */
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
|
||||
min-height: calc(var(--editor-min-height, 400px) - 50px);
|
||||
max-height: calc(var(--editor-max-height, 600px) - 50px);
|
||||
overflow-y: auto !important;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||
background: #fff !important;
|
||||
color: #111 !important;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
padding: 1rem;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* ========== Headings and Text Formatting ========== */
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
|
||||
margin: 1em 0 0.5em 0;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline p {
|
||||
margin: 0.5em 0 !important;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ul,
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ol {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em 1em;
|
||||
border-left: 4px solid #d1d5db;
|
||||
background-color: #f9fafb;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* ========== Dark Mode Support ========== */
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
|
||||
background: #111 !important;
|
||||
color: #f9fafb !important;
|
||||
}
|
||||
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
|
||||
color: #f9fafb !important;
|
||||
}
|
||||
|
||||
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
|
||||
background-color: #1f2937 !important;
|
||||
border-left-color: #374151 !important;
|
||||
color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
/* ========== Custom Scrollbars (Light & Dark) ========== */
|
||||
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.dark
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
|
||||
background: #1f2937;
|
||||
}
|
||||
|
||||
.dark
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
.dark
|
||||
.ckeditor-wrapper
|
||||
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
|
||||
background: #6b7280;
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
|
@ -11,7 +15,7 @@
|
|||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
|
@ -19,9 +23,19 @@
|
|||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export function delay(ms: number) {
|
|||
export function textEllipsis(
|
||||
str: string,
|
||||
maxLength: number,
|
||||
{ side = "end", ellipsis = "..." } = {}
|
||||
{ side = "end", ellipsis = "..." } = {},
|
||||
) {
|
||||
if (str !== undefined && str?.length > maxLength) {
|
||||
switch (side) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue